1 /* Copyright (c) 2006-2009 Jonas Fonseca <fonseca@diku.dk>
3 * This program is free software; you can redistribute it and/or
4 * modify it under the terms of the GNU General Public License as
5 * published by the Free Software Foundation; either version 2 of
6 * the License, or (at your option) any later version.
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
19 #define TIG_VERSION "unknown-version"
34 #include <sys/types.h>
37 #include <sys/select.h>
48 /* ncurses(3): Must be defined to have extended wide-character functions. */
49 #define _XOPEN_SOURCE_EXTENDED
51 #ifdef HAVE_NCURSESW_NCURSES_H
52 #include <ncursesw/ncurses.h>
54 #ifdef HAVE_NCURSES_NCURSES_H
55 #include <ncurses/ncurses.h>
62 #define __NORETURN __attribute__((__noreturn__))
67 static void __NORETURN die(const char *err, ...);
68 static void warn(const char *msg, ...);
69 static void report(const char *msg, ...);
70 static void set_nonblocking_input(bool loading);
71 static int load_refs(void);
72 static size_t utf8_length(const char **string, size_t col, int *width, size_t max_width, int *trimmed, bool reserve);
74 #define ABS(x) ((x) >= 0 ? (x) : -(x))
75 #define MIN(x, y) ((x) < (y) ? (x) : (y))
77 #define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
78 #define STRING_SIZE(x) (sizeof(x) - 1)
80 #define SIZEOF_STR 1024 /* Default string size. */
81 #define SIZEOF_REF 256 /* Size of symbolic or SHA1 ID. */
82 #define SIZEOF_REV 41 /* Holds a SHA-1 and an ending NUL. */
83 #define SIZEOF_ARG 32 /* Default argument array size. */
87 #define REVGRAPH_INIT 'I'
88 #define REVGRAPH_MERGE 'M'
89 #define REVGRAPH_BRANCH '+'
90 #define REVGRAPH_COMMIT '*'
91 #define REVGRAPH_BOUND '^'
93 #define SIZEOF_REVGRAPH 19 /* Size of revision ancestry graphics. */
95 /* This color name can be used to refer to the default term colors. */
96 #define COLOR_DEFAULT (-1)
98 #define ICONV_NONE ((iconv_t) -1)
100 #define ICONV_CONST /* nothing */
103 /* The format and size of the date column in the main view. */
104 #define DATE_FORMAT "%Y-%m-%d %H:%M"
105 #define DATE_COLS STRING_SIZE("2006-04-29 14:21 ")
107 #define AUTHOR_COLS 20
110 /* The default interval between line numbers. */
111 #define NUMBER_INTERVAL 5
112 #define SCROLL_INTERVAL 1
116 #define SCALE_SPLIT_VIEW(height) ((height) * 2 / 3)
118 #define NULL_ID "0000000000000000000000000000000000000000"
121 #define GIT_CONFIG "config"
124 /* Some ASCII-shorthands fitted into the ncurses namespace. */
126 #define KEY_RETURN '\r'
131 char *name; /* Ref name; tag or head names are shortened. */
132 char id[SIZEOF_REV]; /* Commit SHA1 ID */
133 unsigned int head:1; /* Is it the current HEAD? */
134 unsigned int tag:1; /* Is it a tag? */
135 unsigned int ltag:1; /* If so, is the tag local? */
136 unsigned int remote:1; /* Is it a remote ref? */
137 unsigned int tracked:1; /* Is it the remote for the current HEAD? */
138 unsigned int next:1; /* For ref lists: are there more refs? */
141 static struct ref **get_refs(const char *id);
144 FORMAT_ALL, /* Perform replacement in all arguments. */
145 FORMAT_DASH, /* Perform replacement up until "--". */
146 FORMAT_NONE /* No replacement should be performed. */
149 static bool format_argv(const char *dst[], const char *src[], enum format_flags flags);
158 typedef enum input_status (*input_handler)(void *data, char *buf, int c);
160 static char *prompt_input(const char *prompt, input_handler handler, void *data);
161 static bool prompt_yesno(const char *prompt);
168 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
170 if (srclen > dstlen - 1)
173 strncpy(dst, src, srclen);
177 /* Shorthands for safely copying into a fixed buffer. */
179 #define string_copy(dst, src) \
180 string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
182 #define string_ncopy(dst, src, srclen) \
183 string_ncopy_do(dst, sizeof(dst), src, srclen)
185 #define string_copy_rev(dst, src) \
186 string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
188 #define string_add(dst, from, src) \
189 string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
192 string_expand_length(const char *line, int tabsize)
196 for (pos = 0; line[pos]; pos++) {
197 if (line[pos] == '\t' && tabsize > 0)
198 size += tabsize - (size % tabsize);
206 string_expand(char *dst, size_t dstlen, const char *src, int tabsize)
210 for (size = pos = 0; size < dstlen - 1 && src[pos]; pos++) {
211 if (src[pos] == '\t') {
212 size_t expanded = tabsize - (size % tabsize);
214 if (expanded + size >= dstlen - 1)
215 expanded = dstlen - size - 1;
216 memcpy(dst + size, " ", expanded);
219 dst[size++] = src[pos];
227 chomp_string(char *name)
231 while (isspace(*name))
234 namelen = strlen(name) - 1;
235 while (namelen > 0 && isspace(name[namelen]))
242 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
245 size_t pos = bufpos ? *bufpos : 0;
248 pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
254 return pos >= bufsize ? FALSE : TRUE;
257 #define string_format(buf, fmt, args...) \
258 string_nformat(buf, sizeof(buf), NULL, fmt, args)
260 #define string_format_from(buf, from, fmt, args...) \
261 string_nformat(buf, sizeof(buf), from, fmt, args)
264 string_enum_compare(const char *str1, const char *str2, int len)
268 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
270 /* Diff-Header == DIFF_HEADER */
271 for (i = 0; i < len; i++) {
272 if (toupper(str1[i]) == toupper(str2[i]))
275 if (string_enum_sep(str1[i]) &&
276 string_enum_sep(str2[i]))
279 return str1[i] - str2[i];
291 #define ENUM_MAP(name, value) { name, STRING_SIZE(name), value }
294 map_enum_do(struct enum_map *map, size_t map_size, int *value, const char *name)
296 size_t namelen = strlen(name);
299 for (i = 0; i < map_size; i++)
300 if (namelen == map[i].namelen &&
301 !string_enum_compare(name, map[i].name, namelen)) {
302 *value = map[i].value;
309 #define map_enum(attr, map, name) \
310 map_enum_do(map, ARRAY_SIZE(map), attr, name)
312 #define prefixcmp(str1, str2) \
313 strncmp(str1, str2, STRING_SIZE(str2))
316 suffixcmp(const char *str, int slen, const char *suffix)
318 size_t len = slen >= 0 ? slen : strlen(str);
319 size_t suffixlen = strlen(suffix);
321 return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
326 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
330 while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
331 bool advance = cmd[valuelen] != 0;
334 argv[(*argc)++] = chomp_string(cmd);
335 cmd = chomp_string(cmd + valuelen + advance);
338 if (*argc < SIZEOF_ARG)
340 return *argc < SIZEOF_ARG;
344 argv_from_env(const char **argv, const char *name)
346 char *env = argv ? getenv(name) : NULL;
351 if (env && !argv_from_string(argv, &argc, env))
352 die("Too many arguments in the `%s` environment variable", name);
357 * Executing external commands.
361 IO_FD, /* File descriptor based IO. */
362 IO_BG, /* Execute command in the background. */
363 IO_FG, /* Execute command with same std{in,out,err}. */
364 IO_RD, /* Read only fork+exec IO. */
365 IO_WR, /* Write only fork+exec IO. */
366 IO_AP, /* Append fork+exec output to file. */
370 enum io_type type; /* The requested type of pipe. */
371 const char *dir; /* Directory from which to execute. */
372 pid_t pid; /* Pipe for reading or writing. */
373 int pipe; /* Pipe end for reading or writing. */
374 int error; /* Error status. */
375 const char *argv[SIZEOF_ARG]; /* Shell command arguments. */
376 char *buf; /* Read buffer. */
377 size_t bufalloc; /* Allocated buffer size. */
378 size_t bufsize; /* Buffer content size. */
379 char *bufpos; /* Current buffer position. */
380 unsigned int eof:1; /* Has end of file been reached. */
384 reset_io(struct io *io)
388 io->buf = io->bufpos = NULL;
389 io->bufalloc = io->bufsize = 0;
395 init_io(struct io *io, const char *dir, enum io_type type)
403 init_io_rd(struct io *io, const char *argv[], const char *dir,
404 enum format_flags flags)
406 init_io(io, dir, IO_RD);
407 return format_argv(io->argv, argv, flags);
411 io_open(struct io *io, const char *name)
413 init_io(io, NULL, IO_FD);
414 io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
415 return io->pipe != -1;
419 kill_io(struct io *io)
421 return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
425 done_io(struct io *io)
436 pid_t waiting = waitpid(pid, &status, 0);
441 report("waitpid failed (%s)", strerror(errno));
445 return waiting == pid &&
446 !WIFSIGNALED(status) &&
448 !WEXITSTATUS(status);
455 start_io(struct io *io)
457 int pipefds[2] = { -1, -1 };
459 if (io->type == IO_FD)
462 if ((io->type == IO_RD || io->type == IO_WR) &&
465 else if (io->type == IO_AP)
466 pipefds[1] = io->pipe;
468 if ((io->pid = fork())) {
469 if (pipefds[!(io->type == IO_WR)] != -1)
470 close(pipefds[!(io->type == IO_WR)]);
472 io->pipe = pipefds[!!(io->type == IO_WR)];
477 if (io->type != IO_FG) {
478 int devnull = open("/dev/null", O_RDWR);
479 int readfd = io->type == IO_WR ? pipefds[0] : devnull;
480 int writefd = (io->type == IO_RD || io->type == IO_AP)
481 ? pipefds[1] : devnull;
483 dup2(readfd, STDIN_FILENO);
484 dup2(writefd, STDOUT_FILENO);
485 dup2(devnull, STDERR_FILENO);
488 if (pipefds[0] != -1)
490 if (pipefds[1] != -1)
494 if (io->dir && *io->dir && chdir(io->dir) == -1)
495 die("Failed to change directory: %s", strerror(errno));
497 execvp(io->argv[0], (char *const*) io->argv);
498 die("Failed to execute program: %s", strerror(errno));
501 if (pipefds[!!(io->type == IO_WR)] != -1)
502 close(pipefds[!!(io->type == IO_WR)]);
507 run_io(struct io *io, const char **argv, const char *dir, enum io_type type)
509 init_io(io, dir, type);
510 if (!format_argv(io->argv, argv, FORMAT_NONE))
516 run_io_do(struct io *io)
518 return start_io(io) && done_io(io);
522 run_io_bg(const char **argv)
526 init_io(&io, NULL, IO_BG);
527 if (!format_argv(io.argv, argv, FORMAT_NONE))
529 return run_io_do(&io);
533 run_io_fg(const char **argv, const char *dir)
537 init_io(&io, dir, IO_FG);
538 if (!format_argv(io.argv, argv, FORMAT_NONE))
540 return run_io_do(&io);
544 run_io_append(const char **argv, enum format_flags flags, int fd)
548 init_io(&io, NULL, IO_AP);
550 if (format_argv(io.argv, argv, flags))
551 return run_io_do(&io);
557 run_io_rd(struct io *io, const char **argv, enum format_flags flags)
559 return init_io_rd(io, argv, NULL, flags) && start_io(io);
563 io_eof(struct io *io)
569 io_error(struct io *io)
575 io_strerror(struct io *io)
577 return strerror(io->error);
581 io_can_read(struct io *io)
583 struct timeval tv = { 0, 500 };
587 FD_SET(io->pipe, &fds);
589 return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
593 io_read(struct io *io, void *buf, size_t bufsize)
596 ssize_t readsize = read(io->pipe, buf, bufsize);
598 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
600 else if (readsize == -1)
602 else if (readsize == 0)
609 io_get(struct io *io, int c, bool can_read)
615 io->buf = io->bufpos = malloc(BUFSIZ);
618 io->bufalloc = BUFSIZ;
623 if (io->bufsize > 0) {
624 eol = memchr(io->bufpos, c, io->bufsize);
626 char *line = io->bufpos;
629 io->bufpos = eol + 1;
630 io->bufsize -= io->bufpos - line;
637 io->bufpos[io->bufsize] = 0;
647 if (io->bufsize > 0 && io->bufpos > io->buf)
648 memmove(io->buf, io->bufpos, io->bufsize);
650 io->bufpos = io->buf;
651 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
654 io->bufsize += readsize;
659 io_write(struct io *io, const void *buf, size_t bufsize)
663 while (!io_error(io) && written < bufsize) {
666 size = write(io->pipe, buf + written, bufsize - written);
667 if (size < 0 && (errno == EAGAIN || errno == EINTR))
675 return written == bufsize;
679 run_io_buf(const char **argv, char buf[], size_t bufsize)
684 if (!run_io_rd(&io, argv, FORMAT_NONE))
687 io.buf = io.bufpos = buf;
688 io.bufalloc = bufsize;
689 error = !io_get(&io, '\n', TRUE) && io_error(&io);
692 return done_io(&io) || error;
696 io_load(struct io *io, const char *separators,
697 int (*read_property)(char *, size_t, char *, size_t))
705 while (state == OK && (name = io_get(io, '\n', TRUE))) {
710 name = chomp_string(name);
711 namelen = strcspn(name, separators);
715 value = chomp_string(name + namelen + 1);
716 valuelen = strlen(value);
723 state = read_property(name, namelen, value, valuelen);
726 if (state != ERR && io_error(io))
734 run_io_load(const char **argv, const char *separators,
735 int (*read_property)(char *, size_t, char *, size_t))
739 return init_io_rd(&io, argv, NULL, FORMAT_NONE)
740 ? io_load(&io, separators, read_property) : ERR;
749 /* XXX: Keep the view request first and in sync with views[]. */ \
750 REQ_GROUP("View switching") \
751 REQ_(VIEW_MAIN, "Show main view"), \
752 REQ_(VIEW_DIFF, "Show diff view"), \
753 REQ_(VIEW_LOG, "Show log view"), \
754 REQ_(VIEW_TREE, "Show tree view"), \
755 REQ_(VIEW_BLOB, "Show blob view"), \
756 REQ_(VIEW_BLAME, "Show blame view"), \
757 REQ_(VIEW_HELP, "Show help page"), \
758 REQ_(VIEW_PAGER, "Show pager view"), \
759 REQ_(VIEW_STATUS, "Show status view"), \
760 REQ_(VIEW_STAGE, "Show stage view"), \
762 REQ_GROUP("View manipulation") \
763 REQ_(ENTER, "Enter current line and scroll"), \
764 REQ_(NEXT, "Move to next"), \
765 REQ_(PREVIOUS, "Move to previous"), \
766 REQ_(PARENT, "Move to parent"), \
767 REQ_(VIEW_NEXT, "Move focus to next view"), \
768 REQ_(REFRESH, "Reload and refresh"), \
769 REQ_(MAXIMIZE, "Maximize the current view"), \
770 REQ_(VIEW_CLOSE, "Close the current view"), \
771 REQ_(QUIT, "Close all views and quit"), \
773 REQ_GROUP("View specific requests") \
774 REQ_(STATUS_UPDATE, "Update file status"), \
775 REQ_(STATUS_REVERT, "Revert file changes"), \
776 REQ_(STATUS_MERGE, "Merge file using external tool"), \
777 REQ_(STAGE_NEXT, "Find next chunk to stage"), \
779 REQ_GROUP("Cursor navigation") \
780 REQ_(MOVE_UP, "Move cursor one line up"), \
781 REQ_(MOVE_DOWN, "Move cursor one line down"), \
782 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
783 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
784 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
785 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
787 REQ_GROUP("Scrolling") \
788 REQ_(SCROLL_LEFT, "Scroll two columns left"), \
789 REQ_(SCROLL_RIGHT, "Scroll two columns right"), \
790 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
791 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
792 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
793 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
795 REQ_GROUP("Searching") \
796 REQ_(SEARCH, "Search the view"), \
797 REQ_(SEARCH_BACK, "Search backwards in the view"), \
798 REQ_(FIND_NEXT, "Find next search match"), \
799 REQ_(FIND_PREV, "Find previous search match"), \
801 REQ_GROUP("Option manipulation") \
802 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
803 REQ_(TOGGLE_DATE, "Toggle date display"), \
804 REQ_(TOGGLE_AUTHOR, "Toggle author display"), \
805 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
806 REQ_(TOGGLE_REFS, "Toggle reference display (tags/branches)"), \
809 REQ_(PROMPT, "Bring up the prompt"), \
810 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
811 REQ_(SHOW_VERSION, "Show version information"), \
812 REQ_(STOP_LOADING, "Stop all loading views"), \
813 REQ_(EDIT, "Open in editor"), \
814 REQ_(NONE, "Do nothing")
817 /* User action requests. */
819 #define REQ_GROUP(help)
820 #define REQ_(req, help) REQ_##req
822 /* Offset all requests to avoid conflicts with ncurses getch values. */
823 REQ_OFFSET = KEY_MAX + 1,
830 struct request_info {
831 enum request request;
837 static struct request_info req_info[] = {
838 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
839 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
846 get_request(const char *name)
848 int namelen = strlen(name);
851 for (i = 0; i < ARRAY_SIZE(req_info); i++)
852 if (req_info[i].namelen == namelen &&
853 !string_enum_compare(req_info[i].name, name, namelen))
854 return req_info[i].request;
864 /* Option and state variables. */
865 static bool opt_date = TRUE;
866 static bool opt_author = TRUE;
867 static bool opt_line_number = FALSE;
868 static bool opt_line_graphics = TRUE;
869 static bool opt_rev_graph = FALSE;
870 static bool opt_show_refs = TRUE;
871 static int opt_num_interval = NUMBER_INTERVAL;
872 static int opt_tab_size = TAB_SIZE;
873 static int opt_author_cols = AUTHOR_COLS-1;
874 static char opt_path[SIZEOF_STR] = "";
875 static char opt_file[SIZEOF_STR] = "";
876 static char opt_ref[SIZEOF_REF] = "";
877 static char opt_head[SIZEOF_REF] = "";
878 static char opt_head_rev[SIZEOF_REV] = "";
879 static char opt_remote[SIZEOF_REF] = "";
880 static char opt_encoding[20] = "UTF-8";
881 static bool opt_utf8 = TRUE;
882 static char opt_codeset[20] = "UTF-8";
883 static iconv_t opt_iconv = ICONV_NONE;
884 static char opt_search[SIZEOF_STR] = "";
885 static char opt_cdup[SIZEOF_STR] = "";
886 static char opt_prefix[SIZEOF_STR] = "";
887 static char opt_git_dir[SIZEOF_STR] = "";
888 static signed char opt_is_inside_work_tree = -1; /* set to TRUE or FALSE */
889 static char opt_editor[SIZEOF_STR] = "";
890 static FILE *opt_tty = NULL;
892 #define is_initial_commit() (!*opt_head_rev)
893 #define is_head_commit(rev) (!strcmp((rev), "HEAD") || !strcmp(opt_head_rev, (rev)))
897 * Line-oriented content detection.
901 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
902 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
903 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
904 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
905 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
906 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
907 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
908 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
909 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
910 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
911 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
912 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
913 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
914 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
915 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
916 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
917 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
918 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
919 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
920 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
921 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
922 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
923 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
924 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
925 LINE(AUTHOR, "author ", COLOR_GREEN, COLOR_DEFAULT, 0), \
926 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
927 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
928 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
929 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
930 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
931 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
932 LINE(DELIMITER, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
933 LINE(DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
934 LINE(MODE, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
935 LINE(LINE_NUMBER, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
936 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
937 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
938 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
939 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
940 LINE(MAIN_LOCAL_TAG,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
941 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
942 LINE(MAIN_TRACKED, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
943 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
944 LINE(MAIN_HEAD, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
945 LINE(MAIN_REVGRAPH,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
946 LINE(TREE_HEAD, "", COLOR_DEFAULT, COLOR_DEFAULT, A_BOLD), \
947 LINE(TREE_DIR, "", COLOR_YELLOW, COLOR_DEFAULT, A_NORMAL), \
948 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
949 LINE(STAT_HEAD, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
950 LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
951 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
952 LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
953 LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
954 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
955 LINE(BLAME_ID, "", COLOR_MAGENTA, COLOR_DEFAULT, 0)
958 #define LINE(type, line, fg, bg, attr) \
966 const char *name; /* Option name. */
967 int namelen; /* Size of option name. */
968 const char *line; /* The start of line to match. */
969 int linelen; /* Size of string to match. */
970 int fg, bg, attr; /* Color and text attributes for the lines. */
973 static struct line_info line_info[] = {
974 #define LINE(type, line, fg, bg, attr) \
975 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
980 static enum line_type
981 get_line_type(const char *line)
983 int linelen = strlen(line);
986 for (type = 0; type < ARRAY_SIZE(line_info); type++)
987 /* Case insensitive search matches Signed-off-by lines better. */
988 if (linelen >= line_info[type].linelen &&
989 !strncasecmp(line_info[type].line, line, line_info[type].linelen))
996 get_line_attr(enum line_type type)
998 assert(type < ARRAY_SIZE(line_info));
999 return COLOR_PAIR(type) | line_info[type].attr;
1002 static struct line_info *
1003 get_line_info(const char *name)
1005 size_t namelen = strlen(name);
1006 enum line_type type;
1008 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1009 if (namelen == line_info[type].namelen &&
1010 !string_enum_compare(line_info[type].name, name, namelen))
1011 return &line_info[type];
1019 int default_bg = line_info[LINE_DEFAULT].bg;
1020 int default_fg = line_info[LINE_DEFAULT].fg;
1021 enum line_type type;
1025 if (assume_default_colors(default_fg, default_bg) == ERR) {
1026 default_bg = COLOR_BLACK;
1027 default_fg = COLOR_WHITE;
1030 for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1031 struct line_info *info = &line_info[type];
1032 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1033 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1035 init_pair(type, fg, bg);
1040 enum line_type type;
1043 unsigned int selected:1;
1044 unsigned int dirty:1;
1045 unsigned int cleareol:1;
1047 void *data; /* User data */
1057 enum request request;
1060 static struct keybinding default_keybindings[] = {
1061 /* View switching */
1062 { 'm', REQ_VIEW_MAIN },
1063 { 'd', REQ_VIEW_DIFF },
1064 { 'l', REQ_VIEW_LOG },
1065 { 't', REQ_VIEW_TREE },
1066 { 'f', REQ_VIEW_BLOB },
1067 { 'B', REQ_VIEW_BLAME },
1068 { 'p', REQ_VIEW_PAGER },
1069 { 'h', REQ_VIEW_HELP },
1070 { 'S', REQ_VIEW_STATUS },
1071 { 'c', REQ_VIEW_STAGE },
1073 /* View manipulation */
1074 { 'q', REQ_VIEW_CLOSE },
1075 { KEY_TAB, REQ_VIEW_NEXT },
1076 { KEY_RETURN, REQ_ENTER },
1077 { KEY_UP, REQ_PREVIOUS },
1078 { KEY_DOWN, REQ_NEXT },
1079 { 'R', REQ_REFRESH },
1080 { KEY_F(5), REQ_REFRESH },
1081 { 'O', REQ_MAXIMIZE },
1083 /* Cursor navigation */
1084 { 'k', REQ_MOVE_UP },
1085 { 'j', REQ_MOVE_DOWN },
1086 { KEY_HOME, REQ_MOVE_FIRST_LINE },
1087 { KEY_END, REQ_MOVE_LAST_LINE },
1088 { KEY_NPAGE, REQ_MOVE_PAGE_DOWN },
1089 { ' ', REQ_MOVE_PAGE_DOWN },
1090 { KEY_PPAGE, REQ_MOVE_PAGE_UP },
1091 { 'b', REQ_MOVE_PAGE_UP },
1092 { '-', REQ_MOVE_PAGE_UP },
1095 { KEY_LEFT, REQ_SCROLL_LEFT },
1096 { KEY_RIGHT, REQ_SCROLL_RIGHT },
1097 { KEY_IC, REQ_SCROLL_LINE_UP },
1098 { KEY_DC, REQ_SCROLL_LINE_DOWN },
1099 { 'w', REQ_SCROLL_PAGE_UP },
1100 { 's', REQ_SCROLL_PAGE_DOWN },
1103 { '/', REQ_SEARCH },
1104 { '?', REQ_SEARCH_BACK },
1105 { 'n', REQ_FIND_NEXT },
1106 { 'N', REQ_FIND_PREV },
1110 { 'z', REQ_STOP_LOADING },
1111 { 'v', REQ_SHOW_VERSION },
1112 { 'r', REQ_SCREEN_REDRAW },
1113 { '.', REQ_TOGGLE_LINENO },
1114 { 'D', REQ_TOGGLE_DATE },
1115 { 'A', REQ_TOGGLE_AUTHOR },
1116 { 'g', REQ_TOGGLE_REV_GRAPH },
1117 { 'F', REQ_TOGGLE_REFS },
1118 { ':', REQ_PROMPT },
1119 { 'u', REQ_STATUS_UPDATE },
1120 { '!', REQ_STATUS_REVERT },
1121 { 'M', REQ_STATUS_MERGE },
1122 { '@', REQ_STAGE_NEXT },
1123 { ',', REQ_PARENT },
1127 #define KEYMAP_INFO \
1141 #define KEYMAP_(name) KEYMAP_##name
1146 static struct enum_map keymap_table[] = {
1147 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1152 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1154 struct keybinding_table {
1155 struct keybinding *data;
1159 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1162 add_keybinding(enum keymap keymap, enum request request, int key)
1164 struct keybinding_table *table = &keybindings[keymap];
1166 table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1168 die("Failed to allocate keybinding");
1169 table->data[table->size].alias = key;
1170 table->data[table->size++].request = request;
1173 /* Looks for a key binding first in the given map, then in the generic map, and
1174 * lastly in the default keybindings. */
1176 get_keybinding(enum keymap keymap, int key)
1180 for (i = 0; i < keybindings[keymap].size; i++)
1181 if (keybindings[keymap].data[i].alias == key)
1182 return keybindings[keymap].data[i].request;
1184 for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1185 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1186 return keybindings[KEYMAP_GENERIC].data[i].request;
1188 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1189 if (default_keybindings[i].alias == key)
1190 return default_keybindings[i].request;
1192 return (enum request) key;
1201 static struct key key_table[] = {
1202 { "Enter", KEY_RETURN },
1204 { "Backspace", KEY_BACKSPACE },
1206 { "Escape", KEY_ESC },
1207 { "Left", KEY_LEFT },
1208 { "Right", KEY_RIGHT },
1210 { "Down", KEY_DOWN },
1211 { "Insert", KEY_IC },
1212 { "Delete", KEY_DC },
1214 { "Home", KEY_HOME },
1216 { "PageUp", KEY_PPAGE },
1217 { "PageDown", KEY_NPAGE },
1227 { "F10", KEY_F(10) },
1228 { "F11", KEY_F(11) },
1229 { "F12", KEY_F(12) },
1233 get_key_value(const char *name)
1237 for (i = 0; i < ARRAY_SIZE(key_table); i++)
1238 if (!strcasecmp(key_table[i].name, name))
1239 return key_table[i].value;
1241 if (strlen(name) == 1 && isprint(*name))
1248 get_key_name(int key_value)
1250 static char key_char[] = "'X'";
1251 const char *seq = NULL;
1254 for (key = 0; key < ARRAY_SIZE(key_table); key++)
1255 if (key_table[key].value == key_value)
1256 seq = key_table[key].name;
1260 isprint(key_value)) {
1261 key_char[1] = (char) key_value;
1265 return seq ? seq : "(no key)";
1269 get_key(enum request request)
1271 static char buf[BUFSIZ];
1278 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1279 struct keybinding *keybinding = &default_keybindings[i];
1281 if (keybinding->request != request)
1284 if (!string_format_from(buf, &pos, "%s%s", sep,
1285 get_key_name(keybinding->alias)))
1286 return "Too many keybindings!";
1293 struct run_request {
1296 const char *argv[SIZEOF_ARG];
1299 static struct run_request *run_request;
1300 static size_t run_requests;
1303 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1305 struct run_request *req;
1307 if (argc >= ARRAY_SIZE(req->argv) - 1)
1310 req = realloc(run_request, (run_requests + 1) * sizeof(*run_request));
1315 req = &run_request[run_requests];
1316 req->keymap = keymap;
1318 req->argv[0] = NULL;
1320 if (!format_argv(req->argv, argv, FORMAT_NONE))
1323 return REQ_NONE + ++run_requests;
1326 static struct run_request *
1327 get_run_request(enum request request)
1329 if (request <= REQ_NONE)
1331 return &run_request[request - REQ_NONE - 1];
1335 add_builtin_run_requests(void)
1337 const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1338 const char *gc[] = { "git", "gc", NULL };
1345 { KEYMAP_MAIN, 'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1346 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1350 for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1353 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1354 if (req != REQ_NONE)
1355 add_keybinding(reqs[i].keymap, req, reqs[i].key);
1360 * User config file handling.
1363 static struct enum_map color_map[] = {
1364 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1376 static struct enum_map attr_map[] = {
1377 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1384 ATTR_MAP(UNDERLINE),
1387 #define set_color(color, name) map_enum(color, color_map, name)
1388 #define set_attribute(attr, name) map_enum(attr, attr_map, name)
1390 static int config_lineno;
1391 static bool config_errors;
1392 static const char *config_msg;
1394 /* Wants: object fgcolor bgcolor [attribute] */
1396 option_color_command(int argc, const char *argv[])
1398 struct line_info *info;
1400 if (argc != 3 && argc != 4) {
1401 config_msg = "Wrong number of arguments given to color command";
1405 info = get_line_info(argv[0]);
1407 if (!string_enum_compare(argv[0], "main-delim", strlen("main-delim"))) {
1408 info = get_line_info("delimiter");
1410 } else if (!string_enum_compare(argv[0], "main-date", strlen("main-date"))) {
1411 info = get_line_info("date");
1413 } else if (!string_enum_compare(argv[0], "main-author", strlen("main-author"))) {
1414 info = get_line_info("author");
1417 config_msg = "Unknown color name";
1422 if (!set_color(&info->fg, argv[1]) ||
1423 !set_color(&info->bg, argv[2])) {
1424 config_msg = "Unknown color";
1428 if (argc == 4 && !set_attribute(&info->attr, argv[3])) {
1429 config_msg = "Unknown attribute";
1436 static int parse_bool(bool *opt, const char *arg)
1438 *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1444 parse_int(int *opt, const char *arg, int min, int max)
1446 int value = atoi(arg);
1448 if (min <= value && value <= max)
1454 parse_string(char *opt, const char *arg, size_t optsize)
1456 int arglen = strlen(arg);
1461 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1462 config_msg = "Unmatched quotation";
1465 arg += 1; arglen -= 2;
1467 string_ncopy_do(opt, optsize, arg, strlen(arg));
1472 /* Wants: name = value */
1474 option_set_command(int argc, const char *argv[])
1477 config_msg = "Wrong number of arguments given to set command";
1481 if (strcmp(argv[1], "=")) {
1482 config_msg = "No value assigned";
1486 if (!strcmp(argv[0], "show-author"))
1487 return parse_bool(&opt_author, argv[2]);
1489 if (!strcmp(argv[0], "show-date"))
1490 return parse_bool(&opt_date, argv[2]);
1492 if (!strcmp(argv[0], "show-rev-graph"))
1493 return parse_bool(&opt_rev_graph, argv[2]);
1495 if (!strcmp(argv[0], "show-refs"))
1496 return parse_bool(&opt_show_refs, argv[2]);
1498 if (!strcmp(argv[0], "show-line-numbers"))
1499 return parse_bool(&opt_line_number, argv[2]);
1501 if (!strcmp(argv[0], "line-graphics"))
1502 return parse_bool(&opt_line_graphics, argv[2]);
1504 if (!strcmp(argv[0], "line-number-interval"))
1505 return parse_int(&opt_num_interval, argv[2], 1, 1024);
1507 if (!strcmp(argv[0], "author-width"))
1508 return parse_int(&opt_author_cols, argv[2], 0, 1024);
1510 if (!strcmp(argv[0], "tab-size"))
1511 return parse_int(&opt_tab_size, argv[2], 1, 1024);
1513 if (!strcmp(argv[0], "commit-encoding"))
1514 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
1516 config_msg = "Unknown variable name";
1520 /* Wants: mode request key */
1522 option_bind_command(int argc, const char *argv[])
1524 enum request request;
1529 config_msg = "Wrong number of arguments given to bind command";
1533 if (set_keymap(&keymap, argv[0]) == ERR) {
1534 config_msg = "Unknown key map";
1538 key = get_key_value(argv[1]);
1540 config_msg = "Unknown key";
1544 request = get_request(argv[2]);
1545 if (request == REQ_NONE) {
1546 static struct enum_map obsolete[] = {
1547 ENUM_MAP("cherry-pick", REQ_NONE),
1548 ENUM_MAP("screen-resize", REQ_NONE),
1549 ENUM_MAP("tree-parent", REQ_PARENT),
1553 if (map_enum(&alias, obsolete, argv[2])) {
1554 if (alias != REQ_NONE)
1555 add_keybinding(keymap, alias, key);
1556 config_msg = "Obsolete request name";
1560 if (request == REQ_NONE && *argv[2]++ == '!')
1561 request = add_run_request(keymap, key, argc - 2, argv + 2);
1562 if (request == REQ_NONE) {
1563 config_msg = "Unknown request name";
1567 add_keybinding(keymap, request, key);
1573 set_option(const char *opt, char *value)
1575 const char *argv[SIZEOF_ARG];
1578 if (!argv_from_string(argv, &argc, value)) {
1579 config_msg = "Too many option arguments";
1583 if (!strcmp(opt, "color"))
1584 return option_color_command(argc, argv);
1586 if (!strcmp(opt, "set"))
1587 return option_set_command(argc, argv);
1589 if (!strcmp(opt, "bind"))
1590 return option_bind_command(argc, argv);
1592 config_msg = "Unknown option command";
1597 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1602 config_msg = "Internal error";
1604 /* Check for comment markers, since read_properties() will
1605 * only ensure opt and value are split at first " \t". */
1606 optlen = strcspn(opt, "#");
1610 if (opt[optlen] != 0) {
1611 config_msg = "No option value";
1615 /* Look for comment endings in the value. */
1616 size_t len = strcspn(value, "#");
1618 if (len < valuelen) {
1620 value[valuelen] = 0;
1623 status = set_option(opt, value);
1626 if (status == ERR) {
1627 warn("Error on line %d, near '%.*s': %s",
1628 config_lineno, (int) optlen, opt, config_msg);
1629 config_errors = TRUE;
1632 /* Always keep going if errors are encountered. */
1637 load_option_file(const char *path)
1641 /* It's OK that the file doesn't exist. */
1642 if (!io_open(&io, path))
1646 config_errors = FALSE;
1648 if (io_load(&io, " \t", read_option) == ERR ||
1649 config_errors == TRUE)
1650 warn("Errors while loading %s.", path);
1656 const char *home = getenv("HOME");
1657 const char *tigrc_user = getenv("TIGRC_USER");
1658 const char *tigrc_system = getenv("TIGRC_SYSTEM");
1659 char buf[SIZEOF_STR];
1661 add_builtin_run_requests();
1663 if (!tigrc_system) {
1664 if (!string_format(buf, "%s/tigrc", SYSCONFDIR))
1668 load_option_file(tigrc_system);
1671 if (!home || !string_format(buf, "%s/.tigrc", home))
1675 load_option_file(tigrc_user);
1688 /* The display array of active views and the index of the current view. */
1689 static struct view *display[2];
1690 static unsigned int current_view;
1692 #define foreach_displayed_view(view, i) \
1693 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1695 #define displayed_views() (display[1] != NULL ? 2 : 1)
1697 /* Current head and commit ID */
1698 static char ref_blob[SIZEOF_REF] = "";
1699 static char ref_commit[SIZEOF_REF] = "HEAD";
1700 static char ref_head[SIZEOF_REF] = "HEAD";
1703 const char *name; /* View name */
1704 const char *cmd_env; /* Command line set via environment */
1705 const char *id; /* Points to either of ref_{head,commit,blob} */
1707 struct view_ops *ops; /* View operations */
1709 enum keymap keymap; /* What keymap does this view have */
1710 bool git_dir; /* Whether the view requires a git directory. */
1712 char ref[SIZEOF_REF]; /* Hovered commit reference */
1713 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
1715 int height, width; /* The width and height of the main window */
1716 WINDOW *win; /* The main window */
1717 WINDOW *title; /* The title window living below the main window */
1720 unsigned long offset; /* Offset of the window top */
1721 unsigned long yoffset; /* Offset from the window side. */
1722 unsigned long lineno; /* Current line number */
1723 unsigned long p_offset; /* Previous offset of the window top */
1724 unsigned long p_yoffset;/* Previous offset from the window side */
1725 unsigned long p_lineno; /* Previous current line number */
1726 bool p_restore; /* Should the previous position be restored. */
1729 char grep[SIZEOF_STR]; /* Search string */
1730 regex_t *regex; /* Pre-compiled regexp */
1732 /* If non-NULL, points to the view that opened this view. If this view
1733 * is closed tig will switch back to the parent view. */
1734 struct view *parent;
1737 size_t lines; /* Total number of lines */
1738 struct line *line; /* Line index */
1739 size_t line_alloc; /* Total number of allocated lines */
1740 unsigned int digits; /* Number of digits in the lines member. */
1743 struct line *curline; /* Line currently being drawn. */
1744 enum line_type curtype; /* Attribute currently used for drawing. */
1745 unsigned long col; /* Column when drawing. */
1746 bool has_scrolled; /* View was scrolled. */
1747 bool can_hscroll; /* View can be scrolled horizontally. */
1757 /* What type of content being displayed. Used in the title bar. */
1759 /* Default command arguments. */
1761 /* Open and reads in all view content. */
1762 bool (*open)(struct view *view);
1763 /* Read one line; updates view->line. */
1764 bool (*read)(struct view *view, char *data);
1765 /* Draw one line; @lineno must be < view->height. */
1766 bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
1767 /* Depending on view handle a special requests. */
1768 enum request (*request)(struct view *view, enum request request, struct line *line);
1769 /* Search for regexp in a line. */
1770 bool (*grep)(struct view *view, struct line *line);
1772 void (*select)(struct view *view, struct line *line);
1775 static struct view_ops blame_ops;
1776 static struct view_ops blob_ops;
1777 static struct view_ops diff_ops;
1778 static struct view_ops help_ops;
1779 static struct view_ops log_ops;
1780 static struct view_ops main_ops;
1781 static struct view_ops pager_ops;
1782 static struct view_ops stage_ops;
1783 static struct view_ops status_ops;
1784 static struct view_ops tree_ops;
1786 #define VIEW_STR(name, env, ref, ops, map, git) \
1787 { name, #env, ref, ops, map, git }
1789 #define VIEW_(id, name, ops, git, ref) \
1790 VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
1793 static struct view views[] = {
1794 VIEW_(MAIN, "main", &main_ops, TRUE, ref_head),
1795 VIEW_(DIFF, "diff", &diff_ops, TRUE, ref_commit),
1796 VIEW_(LOG, "log", &log_ops, TRUE, ref_head),
1797 VIEW_(TREE, "tree", &tree_ops, TRUE, ref_commit),
1798 VIEW_(BLOB, "blob", &blob_ops, TRUE, ref_blob),
1799 VIEW_(BLAME, "blame", &blame_ops, TRUE, ref_commit),
1800 VIEW_(HELP, "help", &help_ops, FALSE, ""),
1801 VIEW_(PAGER, "pager", &pager_ops, FALSE, "stdin"),
1802 VIEW_(STATUS, "status", &status_ops, TRUE, ""),
1803 VIEW_(STAGE, "stage", &stage_ops, TRUE, ""),
1806 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
1807 #define VIEW_REQ(view) ((view) - views + REQ_OFFSET + 1)
1809 #define foreach_view(view, i) \
1810 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1812 #define view_is_displayed(view) \
1813 (view == display[0] || view == display[1])
1820 static int line_graphics[] = {
1821 /* LINE_GRAPHIC_VLINE: */ '|'
1825 set_view_attr(struct view *view, enum line_type type)
1827 if (!view->curline->selected && view->curtype != type) {
1828 wattrset(view->win, get_line_attr(type));
1829 wchgat(view->win, -1, 0, type, NULL);
1830 view->curtype = type;
1835 draw_chars(struct view *view, enum line_type type, const char *string,
1836 int max_len, bool use_tilde)
1840 int trimmed = FALSE;
1841 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
1847 len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde);
1849 col = len = strlen(string);
1850 if (len > max_len) {
1854 col = len = max_len;
1859 set_view_attr(view, type);
1861 waddnstr(view->win, string, len);
1862 if (trimmed && use_tilde) {
1863 set_view_attr(view, LINE_DELIMITER);
1864 waddch(view->win, '~');
1868 if (view->col + col >= view->width + view->yoffset)
1869 view->can_hscroll = TRUE;
1875 draw_space(struct view *view, enum line_type type, int max, int spaces)
1877 static char space[] = " ";
1880 spaces = MIN(max, spaces);
1882 while (spaces > 0) {
1883 int len = MIN(spaces, sizeof(space) - 1);
1885 col += draw_chars(view, type, space, spaces, FALSE);
1893 draw_lineno(struct view *view, unsigned int lineno)
1895 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
1897 int digits3 = view->digits < 3 ? 3 : view->digits;
1898 int max_number = MIN(digits3, STRING_SIZE(number));
1899 int max = view->width - view->col;
1902 if (max < max_number)
1905 lineno += view->offset + 1;
1906 if (lineno == 1 || (lineno % opt_num_interval) == 0) {
1907 static char fmt[] = "%1ld";
1909 if (view->digits <= 9)
1910 fmt[1] = '0' + digits3;
1912 if (!string_format(number, fmt, lineno))
1914 col = draw_chars(view, LINE_LINE_NUMBER, number, max_number, TRUE);
1916 col = draw_space(view, LINE_LINE_NUMBER, max_number, max_number);
1919 if (col < max && skip <= col) {
1920 set_view_attr(view, LINE_DEFAULT);
1921 waddch(view->win, line_graphics[LINE_GRAPHIC_VLINE]);
1926 if (col < max && skip <= col)
1927 col = draw_space(view, LINE_DEFAULT, max - col, 1);
1930 return view->width + view->yoffset <= view->col;
1934 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
1936 view->col += draw_chars(view, type, string, view->width + view->yoffset - view->col, trim);
1937 return view->width - view->col <= 0;
1941 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
1943 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
1944 int max = view->width - view->col;
1950 set_view_attr(view, type);
1951 /* Using waddch() instead of waddnstr() ensures that
1952 * they'll be rendered correctly for the cursor line. */
1953 for (i = skip; i < size; i++)
1954 waddch(view->win, graphic[i]);
1957 if (size < max && skip <= size)
1958 waddch(view->win, ' ');
1961 return view->width - view->col <= 0;
1965 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
1967 int max = MIN(view->width - view->col, len);
1971 col = draw_chars(view, type, text, max - 1, trim);
1973 col = draw_space(view, type, max - 1, max - 1);
1976 view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
1977 return view->width + view->yoffset <= view->col;
1981 draw_date(struct view *view, struct tm *time)
1983 char buf[DATE_COLS];
1988 timelen = strftime(buf, sizeof(buf), DATE_FORMAT, time);
1989 date = timelen ? buf : NULL;
1991 return draw_field(view, LINE_DATE, date, DATE_COLS, FALSE);
1995 draw_author(struct view *view, const char *author)
1997 bool trim = opt_author_cols == 0 || opt_author_cols > 5 || !author;
2000 static char initials[10];
2003 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@')
2005 memset(initials, 0, sizeof(initials));
2006 for (pos = 0; *author && pos < opt_author_cols - 1; author++, pos++) {
2007 while (is_initial_sep(*author))
2009 strncpy(&initials[pos], author, sizeof(initials) - 1 - pos);
2010 while (*author && !is_initial_sep(author[1]))
2017 return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2021 draw_mode(struct view *view, mode_t mode)
2023 static const char dir_mode[] = "drwxr-xr-x";
2024 static const char link_mode[] = "lrwxrwxrwx";
2025 static const char exe_mode[] = "-rwxr-xr-x";
2026 static const char file_mode[] = "-rw-r--r--";
2031 else if (S_ISLNK(mode))
2033 else if (mode & S_IXUSR)
2038 return draw_field(view, LINE_MODE, str, sizeof(file_mode), FALSE);
2042 draw_view_line(struct view *view, unsigned int lineno)
2045 bool selected = (view->offset + lineno == view->lineno);
2047 assert(view_is_displayed(view));
2049 if (view->offset + lineno >= view->lines)
2052 line = &view->line[view->offset + lineno];
2054 wmove(view->win, lineno, 0);
2056 wclrtoeol(view->win);
2058 view->curline = line;
2059 view->curtype = LINE_NONE;
2060 line->selected = FALSE;
2061 line->dirty = line->cleareol = 0;
2064 set_view_attr(view, LINE_CURSOR);
2065 line->selected = TRUE;
2066 view->ops->select(view, line);
2069 return view->ops->draw(view, line, lineno);
2073 redraw_view_dirty(struct view *view)
2078 for (lineno = 0; lineno < view->height; lineno++) {
2079 if (view->offset + lineno >= view->lines)
2081 if (!view->line[view->offset + lineno].dirty)
2084 if (!draw_view_line(view, lineno))
2090 wnoutrefresh(view->win);
2094 redraw_view_from(struct view *view, int lineno)
2096 assert(0 <= lineno && lineno < view->height);
2099 view->can_hscroll = FALSE;
2101 for (; lineno < view->height; lineno++) {
2102 if (!draw_view_line(view, lineno))
2106 wnoutrefresh(view->win);
2110 redraw_view(struct view *view)
2113 redraw_view_from(view, 0);
2118 update_view_title(struct view *view)
2120 char buf[SIZEOF_STR];
2121 char state[SIZEOF_STR];
2122 size_t bufpos = 0, statelen = 0;
2124 assert(view_is_displayed(view));
2126 if (view != VIEW(REQ_VIEW_STATUS) && view->lines) {
2127 unsigned int view_lines = view->offset + view->height;
2128 unsigned int lines = view->lines
2129 ? MIN(view_lines, view->lines) * 100 / view->lines
2132 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2141 time_t secs = time(NULL) - view->start_time;
2143 /* Three git seconds are a long time ... */
2145 string_format_from(state, &statelen, " loading %lds", secs);
2148 string_format_from(buf, &bufpos, "[%s]", view->name);
2149 if (*view->ref && bufpos < view->width) {
2150 size_t refsize = strlen(view->ref);
2151 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2153 if (minsize < view->width)
2154 refsize = view->width - minsize + 7;
2155 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2158 if (statelen && bufpos < view->width) {
2159 string_format_from(buf, &bufpos, "%s", state);
2162 if (view == display[current_view])
2163 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2165 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2167 mvwaddnstr(view->title, 0, 0, buf, bufpos);
2168 wclrtoeol(view->title);
2169 wnoutrefresh(view->title);
2173 resize_display(void)
2176 struct view *base = display[0];
2177 struct view *view = display[1] ? display[1] : display[0];
2179 /* Setup window dimensions */
2181 getmaxyx(stdscr, base->height, base->width);
2183 /* Make room for the status window. */
2187 /* Horizontal split. */
2188 view->width = base->width;
2189 view->height = SCALE_SPLIT_VIEW(base->height);
2190 base->height -= view->height;
2192 /* Make room for the title bar. */
2196 /* Make room for the title bar. */
2201 foreach_displayed_view (view, i) {
2203 view->win = newwin(view->height, 0, offset, 0);
2205 die("Failed to create %s view", view->name);
2207 scrollok(view->win, FALSE);
2209 view->title = newwin(1, 0, offset + view->height, 0);
2211 die("Failed to create title window");
2214 wresize(view->win, view->height, view->width);
2215 mvwin(view->win, offset, 0);
2216 mvwin(view->title, offset + view->height, 0);
2219 offset += view->height + 1;
2224 redraw_display(bool clear)
2229 foreach_displayed_view (view, i) {
2233 update_view_title(view);
2238 toggle_view_option(bool *option, const char *help)
2241 redraw_display(FALSE);
2242 report("%sabling %s", *option ? "En" : "Dis", help);
2249 /* Scrolling backend */
2251 do_scroll_view(struct view *view, int lines)
2253 bool redraw_current_line = FALSE;
2255 /* The rendering expects the new offset. */
2256 view->offset += lines;
2258 assert(0 <= view->offset && view->offset < view->lines);
2261 /* Move current line into the view. */
2262 if (view->lineno < view->offset) {
2263 view->lineno = view->offset;
2264 redraw_current_line = TRUE;
2265 } else if (view->lineno >= view->offset + view->height) {
2266 view->lineno = view->offset + view->height - 1;
2267 redraw_current_line = TRUE;
2270 assert(view->offset <= view->lineno && view->lineno < view->lines);
2272 /* Redraw the whole screen if scrolling is pointless. */
2273 if (view->height < ABS(lines)) {
2277 int line = lines > 0 ? view->height - lines : 0;
2278 int end = line + ABS(lines);
2280 scrollok(view->win, TRUE);
2281 wscrl(view->win, lines);
2282 scrollok(view->win, FALSE);
2284 while (line < end && draw_view_line(view, line))
2287 if (redraw_current_line)
2288 draw_view_line(view, view->lineno - view->offset);
2289 wnoutrefresh(view->win);
2292 view->has_scrolled = TRUE;
2296 /* Scroll frontend */
2298 scroll_view(struct view *view, enum request request)
2302 assert(view_is_displayed(view));
2305 case REQ_SCROLL_LEFT:
2306 if (view->yoffset == 0) {
2307 report("Cannot scroll beyond the first column");
2310 if (view->yoffset <= SCROLL_INTERVAL)
2313 view->yoffset -= SCROLL_INTERVAL;
2314 redraw_view_from(view, 0);
2317 case REQ_SCROLL_RIGHT:
2318 if (!view->can_hscroll) {
2319 report("Cannot scroll beyond the last column");
2322 view->yoffset += SCROLL_INTERVAL;
2326 case REQ_SCROLL_PAGE_DOWN:
2327 lines = view->height;
2328 case REQ_SCROLL_LINE_DOWN:
2329 if (view->offset + lines > view->lines)
2330 lines = view->lines - view->offset;
2332 if (lines == 0 || view->offset + view->height >= view->lines) {
2333 report("Cannot scroll beyond the last line");
2338 case REQ_SCROLL_PAGE_UP:
2339 lines = view->height;
2340 case REQ_SCROLL_LINE_UP:
2341 if (lines > view->offset)
2342 lines = view->offset;
2345 report("Cannot scroll beyond the first line");
2353 die("request %d not handled in switch", request);
2356 do_scroll_view(view, lines);
2361 move_view(struct view *view, enum request request)
2363 int scroll_steps = 0;
2367 case REQ_MOVE_FIRST_LINE:
2368 steps = -view->lineno;
2371 case REQ_MOVE_LAST_LINE:
2372 steps = view->lines - view->lineno - 1;
2375 case REQ_MOVE_PAGE_UP:
2376 steps = view->height > view->lineno
2377 ? -view->lineno : -view->height;
2380 case REQ_MOVE_PAGE_DOWN:
2381 steps = view->lineno + view->height >= view->lines
2382 ? view->lines - view->lineno - 1 : view->height;
2394 die("request %d not handled in switch", request);
2397 if (steps <= 0 && view->lineno == 0) {
2398 report("Cannot move beyond the first line");
2401 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2402 report("Cannot move beyond the last line");
2406 /* Move the current line */
2407 view->lineno += steps;
2408 assert(0 <= view->lineno && view->lineno < view->lines);
2410 /* Check whether the view needs to be scrolled */
2411 if (view->lineno < view->offset ||
2412 view->lineno >= view->offset + view->height) {
2413 scroll_steps = steps;
2414 if (steps < 0 && -steps > view->offset) {
2415 scroll_steps = -view->offset;
2417 } else if (steps > 0) {
2418 if (view->lineno == view->lines - 1 &&
2419 view->lines > view->height) {
2420 scroll_steps = view->lines - view->offset - 1;
2421 if (scroll_steps >= view->height)
2422 scroll_steps -= view->height - 1;
2427 if (!view_is_displayed(view)) {
2428 view->offset += scroll_steps;
2429 assert(0 <= view->offset && view->offset < view->lines);
2430 view->ops->select(view, &view->line[view->lineno]);
2434 /* Repaint the old "current" line if we be scrolling */
2435 if (ABS(steps) < view->height)
2436 draw_view_line(view, view->lineno - steps - view->offset);
2439 do_scroll_view(view, scroll_steps);
2443 /* Draw the current line */
2444 draw_view_line(view, view->lineno - view->offset);
2446 wnoutrefresh(view->win);
2455 static void search_view(struct view *view, enum request request);
2458 select_view_line(struct view *view, unsigned long lineno)
2460 if (lineno - view->offset >= view->height) {
2461 view->offset = lineno;
2462 view->lineno = lineno;
2463 if (view_is_displayed(view))
2467 unsigned long old_lineno = view->lineno - view->offset;
2469 view->lineno = lineno;
2470 if (view_is_displayed(view)) {
2471 draw_view_line(view, old_lineno);
2472 draw_view_line(view, view->lineno - view->offset);
2473 wnoutrefresh(view->win);
2475 view->ops->select(view, &view->line[view->lineno]);
2481 find_next(struct view *view, enum request request)
2483 unsigned long lineno = view->lineno;
2488 report("No previous search");
2490 search_view(view, request);
2500 case REQ_SEARCH_BACK:
2509 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2510 lineno += direction;
2512 /* Note, lineno is unsigned long so will wrap around in which case it
2513 * will become bigger than view->lines. */
2514 for (; lineno < view->lines; lineno += direction) {
2515 if (view->ops->grep(view, &view->line[lineno])) {
2516 select_view_line(view, lineno);
2517 report("Line %ld matches '%s'", lineno + 1, view->grep);
2522 report("No match found for '%s'", view->grep);
2526 search_view(struct view *view, enum request request)
2531 regfree(view->regex);
2534 view->regex = calloc(1, sizeof(*view->regex));
2539 regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2540 if (regex_err != 0) {
2541 char buf[SIZEOF_STR] = "unknown error";
2543 regerror(regex_err, view->regex, buf, sizeof(buf));
2544 report("Search failed: %s", buf);
2548 string_copy(view->grep, opt_search);
2550 find_next(view, request);
2554 * Incremental updating
2558 reset_view(struct view *view)
2562 for (i = 0; i < view->lines; i++)
2563 free(view->line[i].data);
2566 view->p_offset = view->offset;
2567 view->p_yoffset = view->yoffset;
2568 view->p_lineno = view->lineno;
2575 view->line_alloc = 0;
2577 view->update_secs = 0;
2581 free_argv(const char *argv[])
2585 for (argc = 0; argv[argc]; argc++)
2586 free((void *) argv[argc]);
2590 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
2592 char buf[SIZEOF_STR];
2594 bool noreplace = flags == FORMAT_NONE;
2596 free_argv(dst_argv);
2598 for (argc = 0; src_argv[argc]; argc++) {
2599 const char *arg = src_argv[argc];
2603 char *next = strstr(arg, "%(");
2604 int len = next - arg;
2607 if (!next || noreplace) {
2608 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
2613 } else if (!prefixcmp(next, "%(directory)")) {
2616 } else if (!prefixcmp(next, "%(file)")) {
2619 } else if (!prefixcmp(next, "%(ref)")) {
2620 value = *opt_ref ? opt_ref : "HEAD";
2622 } else if (!prefixcmp(next, "%(head)")) {
2625 } else if (!prefixcmp(next, "%(commit)")) {
2628 } else if (!prefixcmp(next, "%(blob)")) {
2632 report("Unknown replacement: `%s`", next);
2636 if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
2639 arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
2642 dst_argv[argc] = strdup(buf);
2643 if (!dst_argv[argc])
2647 dst_argv[argc] = NULL;
2649 return src_argv[argc] == NULL;
2653 restore_view_position(struct view *view)
2655 if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
2658 /* Changing the view position cancels the restoring. */
2659 /* FIXME: Changing back to the first line is not detected. */
2660 if (view->offset != 0 || view->lineno != 0) {
2661 view->p_restore = FALSE;
2665 if (view->p_lineno >= view->lines) {
2666 view->p_lineno = view->lines > 0 ? view->lines - 1 : 0;
2667 if (view->p_offset >= view->p_lineno) {
2668 unsigned long half = view->height / 2;
2670 if (view->p_lineno > half)
2671 view->p_offset = view->p_lineno - half;
2677 if (view_is_displayed(view) &&
2678 view->offset != view->p_offset &&
2679 view->lineno != view->p_lineno)
2682 view->offset = view->p_offset;
2683 view->yoffset = view->p_yoffset;
2684 view->lineno = view->p_lineno;
2685 view->p_restore = FALSE;
2691 end_update(struct view *view, bool force)
2695 while (!view->ops->read(view, NULL))
2698 set_nonblocking_input(FALSE);
2700 kill_io(view->pipe);
2701 done_io(view->pipe);
2706 setup_update(struct view *view, const char *vid)
2708 set_nonblocking_input(TRUE);
2710 string_copy_rev(view->vid, vid);
2711 view->pipe = &view->io;
2712 view->start_time = time(NULL);
2716 prepare_update(struct view *view, const char *argv[], const char *dir,
2717 enum format_flags flags)
2720 end_update(view, TRUE);
2721 return init_io_rd(&view->io, argv, dir, flags);
2725 prepare_update_file(struct view *view, const char *name)
2728 end_update(view, TRUE);
2729 return io_open(&view->io, name);
2733 begin_update(struct view *view, bool refresh)
2736 end_update(view, TRUE);
2739 if (!start_io(&view->io))
2743 if (view == VIEW(REQ_VIEW_TREE) && strcmp(view->vid, view->id))
2746 if (!run_io_rd(&view->io, view->ops->argv, FORMAT_ALL))
2749 /* Put the current ref_* value to the view title ref
2750 * member. This is needed by the blob view. Most other
2751 * views sets it automatically after loading because the
2752 * first line is a commit line. */
2753 string_copy_rev(view->ref, view->id);
2756 setup_update(view, view->id);
2761 #define ITEM_CHUNK_SIZE 256
2763 realloc_items(void *mem, size_t *size, size_t new_size, size_t item_size)
2765 size_t num_chunks = *size / ITEM_CHUNK_SIZE;
2766 size_t num_chunks_new = (new_size + ITEM_CHUNK_SIZE - 1) / ITEM_CHUNK_SIZE;
2768 if (mem == NULL || num_chunks != num_chunks_new) {
2769 *size = num_chunks_new * ITEM_CHUNK_SIZE;
2770 mem = realloc(mem, *size * item_size);
2776 static struct line *
2777 realloc_lines(struct view *view, size_t line_size)
2779 size_t alloc = view->line_alloc;
2780 struct line *tmp = realloc_items(view->line, &alloc, line_size,
2781 sizeof(*view->line));
2787 view->line_alloc = alloc;
2792 update_view(struct view *view)
2794 char out_buffer[BUFSIZ * 2];
2796 /* Clear the view and redraw everything since the tree sorting
2797 * might have rearranged things. */
2798 bool redraw = view->lines == 0;
2799 bool can_read = TRUE;
2804 if (!io_can_read(view->pipe)) {
2805 if (view->lines == 0) {
2806 time_t secs = time(NULL) - view->start_time;
2808 if (secs > 1 && secs > view->update_secs) {
2809 if (view->update_secs == 0)
2811 update_view_title(view);
2812 view->update_secs = secs;
2818 for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
2819 if (opt_iconv != ICONV_NONE) {
2820 ICONV_CONST char *inbuf = line;
2821 size_t inlen = strlen(line) + 1;
2823 char *outbuf = out_buffer;
2824 size_t outlen = sizeof(out_buffer);
2828 ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2829 if (ret != (size_t) -1)
2833 if (!view->ops->read(view, line)) {
2834 report("Allocation failure");
2835 end_update(view, TRUE);
2841 unsigned long lines = view->lines;
2844 for (digits = 0; lines; digits++)
2847 /* Keep the displayed view in sync with line number scaling. */
2848 if (digits != view->digits) {
2849 view->digits = digits;
2850 if (opt_line_number || view == VIEW(REQ_VIEW_BLAME))
2855 if (io_error(view->pipe)) {
2856 report("Failed to read: %s", io_strerror(view->pipe));
2857 end_update(view, TRUE);
2859 } else if (io_eof(view->pipe)) {
2861 end_update(view, FALSE);
2864 if (restore_view_position(view))
2867 if (!view_is_displayed(view))
2871 redraw_view_from(view, 0);
2873 redraw_view_dirty(view);
2875 /* Update the title _after_ the redraw so that if the redraw picks up a
2876 * commit reference in view->ref it'll be available here. */
2877 update_view_title(view);
2881 static struct line *
2882 add_line_data(struct view *view, void *data, enum line_type type)
2886 if (!realloc_lines(view, view->lines + 1))
2889 line = &view->line[view->lines++];
2890 memset(line, 0, sizeof(*line));
2898 static struct line *
2899 add_line_text(struct view *view, const char *text, enum line_type type)
2901 char *data = text ? strdup(text) : NULL;
2903 return data ? add_line_data(view, data, type) : NULL;
2906 static struct line *
2907 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
2909 char buf[SIZEOF_STR];
2912 va_start(args, fmt);
2913 if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
2917 return buf[0] ? add_line_text(view, buf, type) : NULL;
2925 OPEN_DEFAULT = 0, /* Use default view switching. */
2926 OPEN_SPLIT = 1, /* Split current view. */
2927 OPEN_BACKGROUNDED = 2, /* Backgrounded. */
2928 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
2929 OPEN_NOMAXIMIZE = 8, /* Do not maximize the current view. */
2930 OPEN_REFRESH = 16, /* Refresh view using previous command. */
2931 OPEN_PREPARED = 32, /* Open already prepared command. */
2935 open_view(struct view *prev, enum request request, enum open_flags flags)
2937 bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
2938 bool split = !!(flags & OPEN_SPLIT);
2939 bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
2940 bool nomaximize = !!(flags & (OPEN_NOMAXIMIZE | OPEN_REFRESH));
2941 struct view *view = VIEW(request);
2942 int nviews = displayed_views();
2943 struct view *base_view = display[0];
2945 if (view == prev && nviews == 1 && !reload) {
2946 report("Already in %s view", view->name);
2950 if (view->git_dir && !opt_git_dir[0]) {
2951 report("The %s view is disabled in pager view", view->name);
2959 } else if (!nomaximize) {
2960 /* Maximize the current view. */
2961 memset(display, 0, sizeof(display));
2963 display[current_view] = view;
2966 /* Resize the view when switching between split- and full-screen,
2967 * or when switching between two different full-screen views. */
2968 if (nviews != displayed_views() ||
2969 (nviews == 1 && base_view != display[0]))
2972 if (view->ops->open) {
2974 end_update(view, TRUE);
2975 if (!view->ops->open(view)) {
2976 report("Failed to load %s view", view->name);
2979 restore_view_position(view);
2981 } else if ((reload || strcmp(view->vid, view->id)) &&
2982 !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
2983 report("Failed to load %s view", view->name);
2987 if (split && prev->lineno - prev->offset >= prev->height) {
2988 /* Take the title line into account. */
2989 int lines = prev->lineno - prev->offset - prev->height + 1;
2991 /* Scroll the view that was split if the current line is
2992 * outside the new limited view. */
2993 do_scroll_view(prev, lines);
2996 if (prev && view != prev) {
2997 if (split && !backgrounded) {
2998 /* "Blur" the previous view. */
2999 update_view_title(prev);
3002 view->parent = prev;
3005 if (view->pipe && view->lines == 0) {
3006 /* Clear the old view and let the incremental updating refill
3009 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3011 } else if (view_is_displayed(view)) {
3016 /* If the view is backgrounded the above calls to report()
3017 * won't redraw the view title. */
3019 update_view_title(view);
3023 open_external_viewer(const char *argv[], const char *dir)
3025 def_prog_mode(); /* save current tty modes */
3026 endwin(); /* restore original tty modes */
3027 run_io_fg(argv, dir);
3028 fprintf(stderr, "Press Enter to continue");
3031 redraw_display(TRUE);
3035 open_mergetool(const char *file)
3037 const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3039 open_external_viewer(mergetool_argv, opt_cdup);
3043 open_editor(bool from_root, const char *file)
3045 const char *editor_argv[] = { "vi", file, NULL };
3048 editor = getenv("GIT_EDITOR");
3049 if (!editor && *opt_editor)
3050 editor = opt_editor;
3052 editor = getenv("VISUAL");
3054 editor = getenv("EDITOR");
3058 editor_argv[0] = editor;
3059 open_external_viewer(editor_argv, from_root ? opt_cdup : NULL);
3063 open_run_request(enum request request)
3065 struct run_request *req = get_run_request(request);
3066 const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3069 report("Unknown run request");
3073 if (format_argv(argv, req->argv, FORMAT_ALL))
3074 open_external_viewer(argv, NULL);
3079 * User request switch noodle
3083 view_driver(struct view *view, enum request request)
3087 if (request == REQ_NONE) {
3092 if (request > REQ_NONE) {
3093 open_run_request(request);
3094 /* FIXME: When all views can refresh always do this. */
3095 if (view == VIEW(REQ_VIEW_STATUS) ||
3096 view == VIEW(REQ_VIEW_MAIN) ||
3097 view == VIEW(REQ_VIEW_LOG) ||
3098 view == VIEW(REQ_VIEW_STAGE))
3099 request = REQ_REFRESH;
3104 if (view && view->lines) {
3105 request = view->ops->request(view, request, &view->line[view->lineno]);
3106 if (request == REQ_NONE)
3113 case REQ_MOVE_PAGE_UP:
3114 case REQ_MOVE_PAGE_DOWN:
3115 case REQ_MOVE_FIRST_LINE:
3116 case REQ_MOVE_LAST_LINE:
3117 move_view(view, request);
3120 case REQ_SCROLL_LEFT:
3121 case REQ_SCROLL_RIGHT:
3122 case REQ_SCROLL_LINE_DOWN:
3123 case REQ_SCROLL_LINE_UP:
3124 case REQ_SCROLL_PAGE_DOWN:
3125 case REQ_SCROLL_PAGE_UP:
3126 scroll_view(view, request);
3129 case REQ_VIEW_BLAME:
3131 report("No file chosen, press %s to open tree view",
3132 get_key(REQ_VIEW_TREE));
3135 open_view(view, request, OPEN_DEFAULT);
3140 report("No file chosen, press %s to open tree view",
3141 get_key(REQ_VIEW_TREE));
3144 open_view(view, request, OPEN_DEFAULT);
3147 case REQ_VIEW_PAGER:
3148 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3149 report("No pager content, press %s to run command from prompt",
3150 get_key(REQ_PROMPT));
3153 open_view(view, request, OPEN_DEFAULT);
3156 case REQ_VIEW_STAGE:
3157 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3158 report("No stage content, press %s to open the status view and choose file",
3159 get_key(REQ_VIEW_STATUS));
3162 open_view(view, request, OPEN_DEFAULT);
3165 case REQ_VIEW_STATUS:
3166 if (opt_is_inside_work_tree == FALSE) {
3167 report("The status view requires a working tree");
3170 open_view(view, request, OPEN_DEFAULT);
3178 open_view(view, request, OPEN_DEFAULT);
3183 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3185 if ((view == VIEW(REQ_VIEW_DIFF) &&
3186 view->parent == VIEW(REQ_VIEW_MAIN)) ||
3187 (view == VIEW(REQ_VIEW_DIFF) &&
3188 view->parent == VIEW(REQ_VIEW_BLAME)) ||
3189 (view == VIEW(REQ_VIEW_STAGE) &&
3190 view->parent == VIEW(REQ_VIEW_STATUS)) ||
3191 (view == VIEW(REQ_VIEW_BLOB) &&
3192 view->parent == VIEW(REQ_VIEW_TREE))) {
3195 view = view->parent;
3196 line = view->lineno;
3197 move_view(view, request);
3198 if (view_is_displayed(view))
3199 update_view_title(view);
3200 if (line != view->lineno)
3201 view->ops->request(view, REQ_ENTER,
3202 &view->line[view->lineno]);
3205 move_view(view, request);
3211 int nviews = displayed_views();
3212 int next_view = (current_view + 1) % nviews;
3214 if (next_view == current_view) {
3215 report("Only one view is displayed");
3219 current_view = next_view;
3220 /* Blur out the title of the previous view. */
3221 update_view_title(view);
3226 report("Refreshing is not yet supported for the %s view", view->name);
3230 if (displayed_views() == 2)
3231 open_view(view, VIEW_REQ(view), OPEN_DEFAULT);
3234 case REQ_TOGGLE_LINENO:
3235 toggle_view_option(&opt_line_number, "line numbers");
3238 case REQ_TOGGLE_DATE:
3239 toggle_view_option(&opt_date, "date display");
3242 case REQ_TOGGLE_AUTHOR:
3243 toggle_view_option(&opt_author, "author display");
3246 case REQ_TOGGLE_REV_GRAPH:
3247 toggle_view_option(&opt_rev_graph, "revision graph display");
3250 case REQ_TOGGLE_REFS:
3251 toggle_view_option(&opt_show_refs, "reference display");
3255 case REQ_SEARCH_BACK:
3256 search_view(view, request);
3261 find_next(view, request);
3264 case REQ_STOP_LOADING:
3265 for (i = 0; i < ARRAY_SIZE(views); i++) {
3268 report("Stopped loading the %s view", view->name),
3269 end_update(view, TRUE);
3273 case REQ_SHOW_VERSION:
3274 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3277 case REQ_SCREEN_REDRAW:
3278 redraw_display(TRUE);
3282 report("Nothing to edit");
3286 report("Nothing to enter");
3289 case REQ_VIEW_CLOSE:
3290 /* XXX: Mark closed views by letting view->parent point to the
3291 * view itself. Parents to closed view should never be
3294 view->parent->parent != view->parent) {
3295 memset(display, 0, sizeof(display));
3297 display[current_view] = view->parent;
3298 view->parent = view;
3300 redraw_display(FALSE);
3309 report("Unknown key, press 'h' for help");
3318 * View backend utilities
3321 /* Parse author lines where the name may be empty:
3322 * author <email@address.tld> 1138474660 +0100
3325 parse_author_line(char *ident, char *author, size_t authorsize, struct tm *tm)
3327 char *nameend = strchr(ident, '<');
3328 char *emailend = strchr(ident, '>');
3330 if (nameend && emailend)
3331 *nameend = *emailend = 0;
3332 ident = chomp_string(ident);
3335 ident = chomp_string(nameend + 1);
3340 string_ncopy_do(author, authorsize, ident, strlen(ident));
3342 /* Parse epoch and timezone */
3343 if (emailend && emailend[1] == ' ') {
3344 char *secs = emailend + 2;
3345 char *zone = strchr(secs, ' ');
3346 time_t time = (time_t) atol(secs);
3348 if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
3352 tz = ('0' - zone[1]) * 60 * 60 * 10;
3353 tz += ('0' - zone[2]) * 60 * 60;
3354 tz += ('0' - zone[3]) * 60;
3355 tz += ('0' - zone[4]) * 60;
3363 gmtime_r(&time, tm);
3367 static enum input_status
3368 select_commit_parent_handler(void *data, char *buf, int c)
3370 size_t parents = *(size_t *) data;
3377 parent = atoi(buf) * 10;
3380 if (parent > parents)
3386 select_commit_parent(const char *id, char rev[SIZEOF_REV])
3388 char buf[SIZEOF_STR * 4];
3389 const char *revlist_argv[] = {
3390 "git", "rev-list", "-1", "--parents", id, NULL
3394 if (!run_io_buf(revlist_argv, buf, sizeof(buf)) ||
3395 !*chomp_string(buf) ||
3396 (parents = (strlen(buf) / 40) - 1) < 0) {
3397 report("Failed to get parent information");
3400 } else if (parents == 0) {
3401 report("The selected commit has no parents");
3406 char prompt[SIZEOF_STR];
3409 if (!string_format(prompt, "Which parent? [1..%d] ", parents))
3411 result = prompt_input(prompt, select_commit_parent_handler, &parents);
3414 parents = atoi(result);
3417 string_copy_rev(rev, &buf[41 * parents]);
3426 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3428 char text[SIZEOF_STR];
3430 if (opt_line_number && draw_lineno(view, lineno))
3433 string_expand(text, sizeof(text), line->data, opt_tab_size);
3434 draw_text(view, line->type, text, TRUE);
3439 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3441 const char *describe_argv[] = { "git", "describe", commit_id, NULL };
3442 char refbuf[SIZEOF_STR];
3445 if (run_io_buf(describe_argv, refbuf, sizeof(refbuf)))
3446 ref = chomp_string(refbuf);
3451 /* This is the only fatal call, since it can "corrupt" the buffer. */
3452 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
3459 add_pager_refs(struct view *view, struct line *line)
3461 char buf[SIZEOF_STR];
3462 char *commit_id = (char *)line->data + STRING_SIZE("commit ");
3464 size_t bufpos = 0, refpos = 0;
3465 const char *sep = "Refs: ";
3466 bool is_tag = FALSE;
3468 assert(line->type == LINE_COMMIT);
3470 refs = get_refs(commit_id);
3472 if (view == VIEW(REQ_VIEW_DIFF))
3473 goto try_add_describe_ref;
3478 struct ref *ref = refs[refpos];
3479 const char *fmt = ref->tag ? "%s[%s]" :
3480 ref->remote ? "%s<%s>" : "%s%s";
3482 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
3487 } while (refs[refpos++]->next);
3489 if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
3490 try_add_describe_ref:
3491 /* Add <tag>-g<commit_id> "fake" reference. */
3492 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
3499 add_line_text(view, buf, LINE_PP_REFS);
3503 pager_read(struct view *view, char *data)
3510 line = add_line_text(view, data, get_line_type(data));
3514 if (line->type == LINE_COMMIT &&
3515 (view == VIEW(REQ_VIEW_DIFF) ||
3516 view == VIEW(REQ_VIEW_LOG)))
3517 add_pager_refs(view, line);
3523 pager_request(struct view *view, enum request request, struct line *line)
3527 if (request != REQ_ENTER)
3530 if (line->type == LINE_COMMIT &&
3531 (view == VIEW(REQ_VIEW_LOG) ||
3532 view == VIEW(REQ_VIEW_PAGER))) {
3533 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3537 /* Always scroll the view even if it was split. That way
3538 * you can use Enter to scroll through the log view and
3539 * split open each commit diff. */
3540 scroll_view(view, REQ_SCROLL_LINE_DOWN);
3542 /* FIXME: A minor workaround. Scrolling the view will call report("")
3543 * but if we are scrolling a non-current view this won't properly
3544 * update the view title. */
3546 update_view_title(view);
3552 pager_grep(struct view *view, struct line *line)
3555 char *text = line->data;
3560 if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
3567 pager_select(struct view *view, struct line *line)
3569 if (line->type == LINE_COMMIT) {
3570 char *text = (char *)line->data + STRING_SIZE("commit ");
3572 if (view != VIEW(REQ_VIEW_PAGER))
3573 string_copy_rev(view->ref, text);
3574 string_copy_rev(ref_commit, text);
3578 static struct view_ops pager_ops = {
3589 static const char *log_argv[SIZEOF_ARG] = {
3590 "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3594 log_request(struct view *view, enum request request, struct line *line)
3599 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
3602 return pager_request(view, request, line);
3606 static struct view_ops log_ops = {
3617 static const char *diff_argv[SIZEOF_ARG] = {
3618 "git", "show", "--pretty=fuller", "--no-color", "--root",
3619 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
3622 static struct view_ops diff_ops = {
3638 help_open(struct view *view)
3640 char buf[SIZEOF_STR];
3644 if (view->lines > 0)
3647 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
3649 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
3652 if (req_info[i].request == REQ_NONE)
3655 if (!req_info[i].request) {
3656 add_line_text(view, "", LINE_DEFAULT);
3657 add_line_text(view, req_info[i].help, LINE_DEFAULT);
3661 key = get_key(req_info[i].request);
3663 key = "(no key defined)";
3665 for (bufpos = 0; bufpos <= req_info[i].namelen; bufpos++) {
3666 buf[bufpos] = tolower(req_info[i].name[bufpos]);
3667 if (buf[bufpos] == '_')
3671 add_line_format(view, LINE_DEFAULT, " %-25s %-20s %s",
3672 key, buf, req_info[i].help);
3676 add_line_text(view, "", LINE_DEFAULT);
3677 add_line_text(view, "External commands:", LINE_DEFAULT);
3680 for (i = 0; i < run_requests; i++) {
3681 struct run_request *req = get_run_request(REQ_NONE + i + 1);
3688 key = get_key_name(req->key);
3690 key = "(no key defined)";
3692 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
3693 if (!string_format_from(buf, &bufpos, "%s%s",
3694 argc ? " " : "", req->argv[argc]))
3697 add_line_format(view, LINE_DEFAULT, " %-10s %-14s `%s`",
3698 keymap_table[req->keymap].name, key, buf);
3704 static struct view_ops help_ops = {
3720 struct tree_stack_entry {
3721 struct tree_stack_entry *prev; /* Entry below this in the stack */
3722 unsigned long lineno; /* Line number to restore */
3723 char *name; /* Position of name in opt_path */
3726 /* The top of the path stack. */
3727 static struct tree_stack_entry *tree_stack = NULL;
3728 unsigned long tree_lineno = 0;
3731 pop_tree_stack_entry(void)
3733 struct tree_stack_entry *entry = tree_stack;
3735 tree_lineno = entry->lineno;
3737 tree_stack = entry->prev;
3742 push_tree_stack_entry(const char *name, unsigned long lineno)
3744 struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
3745 size_t pathlen = strlen(opt_path);
3750 entry->prev = tree_stack;
3751 entry->name = opt_path + pathlen;
3754 if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
3755 pop_tree_stack_entry();
3759 /* Move the current line to the first tree entry. */
3761 entry->lineno = lineno;
3764 /* Parse output from git-ls-tree(1):
3766 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
3769 #define SIZEOF_TREE_ATTR \
3770 STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
3772 #define SIZEOF_TREE_MODE \
3773 STRING_SIZE("100644 ")
3775 #define TREE_ID_OFFSET \
3776 STRING_SIZE("100644 blob ")
3779 char id[SIZEOF_REV];
3781 struct tm time; /* Date from the author ident. */
3782 char author[75]; /* Author of the commit. */
3787 tree_path(struct line *line)
3789 return ((struct tree_entry *) line->data)->name;
3794 tree_compare_entry(struct line *line1, struct line *line2)
3796 if (line1->type != line2->type)
3797 return line1->type == LINE_TREE_DIR ? -1 : 1;
3798 return strcmp(tree_path(line1), tree_path(line2));
3801 static struct line *
3802 tree_entry(struct view *view, enum line_type type, const char *path,
3803 const char *mode, const char *id)
3805 struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
3806 struct line *line = entry ? add_line_data(view, entry, type) : NULL;
3808 if (!entry || !line) {
3813 strncpy(entry->name, path, strlen(path));
3815 entry->mode = strtoul(mode, NULL, 8);
3817 string_copy_rev(entry->id, id);
3823 tree_read_date(struct view *view, char *text, bool *read_date)
3825 static char author_name[SIZEOF_STR];
3826 static struct tm author_time;
3828 if (!text && *read_date) {
3833 char *path = *opt_path ? opt_path : ".";
3834 /* Find next entry to process */
3835 const char *log_file[] = {
3836 "git", "log", "--no-color", "--pretty=raw",
3837 "--cc", "--raw", view->id, "--", path, NULL
3842 tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
3843 report("Tree is empty");
3847 if (!run_io_rd(&io, log_file, FORMAT_NONE)) {
3848 report("Failed to load tree data");
3852 done_io(view->pipe);
3857 } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
3858 parse_author_line(text + STRING_SIZE("author "),
3859 author_name, sizeof(author_name), &author_time);
3861 } else if (*text == ':') {
3863 size_t annotated = 1;
3866 pos = strchr(text, '\t');
3870 if (*opt_prefix && !strncmp(text, opt_prefix, strlen(opt_prefix)))
3871 text += strlen(opt_prefix);
3872 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
3873 text += strlen(opt_path);
3874 pos = strchr(text, '/');
3878 for (i = 1; i < view->lines; i++) {
3879 struct line *line = &view->line[i];
3880 struct tree_entry *entry = line->data;
3882 annotated += !!*entry->author;
3883 if (*entry->author || strcmp(entry->name, text))
3886 string_copy(entry->author, author_name);
3887 memcpy(&entry->time, &author_time, sizeof(entry->time));
3892 if (annotated == view->lines)
3893 kill_io(view->pipe);
3899 tree_read(struct view *view, char *text)
3901 static bool read_date = FALSE;
3902 struct tree_entry *data;
3903 struct line *entry, *line;
3904 enum line_type type;
3905 size_t textlen = text ? strlen(text) : 0;
3906 char *path = text + SIZEOF_TREE_ATTR;
3908 if (read_date || !text)
3909 return tree_read_date(view, text, &read_date);
3911 if (textlen <= SIZEOF_TREE_ATTR)
3913 if (view->lines == 0 &&
3914 !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
3917 /* Strip the path part ... */
3919 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
3920 size_t striplen = strlen(opt_path);
3922 if (pathlen > striplen)
3923 memmove(path, path + striplen,
3924 pathlen - striplen + 1);
3926 /* Insert "link" to parent directory. */
3927 if (view->lines == 1 &&
3928 !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
3932 type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
3933 entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
3938 /* Skip "Directory ..." and ".." line. */
3939 for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
3940 if (tree_compare_entry(line, entry) <= 0)
3943 memmove(line + 1, line, (entry - line) * sizeof(*entry));
3947 for (; line <= entry; line++)
3948 line->dirty = line->cleareol = 1;
3952 if (tree_lineno > view->lineno) {
3953 view->lineno = tree_lineno;
3961 tree_draw(struct view *view, struct line *line, unsigned int lineno)
3963 struct tree_entry *entry = line->data;
3965 if (line->type == LINE_TREE_HEAD) {
3966 if (draw_text(view, line->type, "Directory path /", TRUE))
3969 if (draw_mode(view, entry->mode))
3972 if (opt_author && draw_author(view, entry->author))
3975 if (opt_date && draw_date(view, *entry->author ? &entry->time : NULL))
3978 if (draw_text(view, line->type, entry->name, TRUE))
3986 char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
3987 int fd = mkstemp(file);
3990 report("Failed to create temporary file");
3991 else if (!run_io_append(blob_ops.argv, FORMAT_ALL, fd))
3992 report("Failed to save blob data to file");
3994 open_editor(FALSE, file);
4000 tree_request(struct view *view, enum request request, struct line *line)
4002 enum open_flags flags;
4005 case REQ_VIEW_BLAME:
4006 if (line->type != LINE_TREE_FILE) {
4007 report("Blame only supported for files");
4011 string_copy(opt_ref, view->vid);
4015 if (line->type != LINE_TREE_FILE) {
4016 report("Edit only supported for files");
4017 } else if (!is_head_commit(view->vid)) {
4020 open_editor(TRUE, opt_file);
4026 /* quit view if at top of tree */
4027 return REQ_VIEW_CLOSE;
4030 line = &view->line[1];
4040 /* Cleanup the stack if the tree view is at a different tree. */
4041 while (!*opt_path && tree_stack)
4042 pop_tree_stack_entry();
4044 switch (line->type) {
4046 /* Depending on whether it is a subdirectory or parent link
4047 * mangle the path buffer. */
4048 if (line == &view->line[1] && *opt_path) {
4049 pop_tree_stack_entry();
4052 const char *basename = tree_path(line);
4054 push_tree_stack_entry(basename, view->lineno);
4057 /* Trees and subtrees share the same ID, so they are not not
4058 * unique like blobs. */
4059 flags = OPEN_RELOAD;
4060 request = REQ_VIEW_TREE;
4063 case LINE_TREE_FILE:
4064 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4065 request = REQ_VIEW_BLOB;
4072 open_view(view, request, flags);
4073 if (request == REQ_VIEW_TREE)
4074 view->lineno = tree_lineno;
4080 tree_select(struct view *view, struct line *line)
4082 struct tree_entry *entry = line->data;
4084 if (line->type == LINE_TREE_FILE) {
4085 string_copy_rev(ref_blob, entry->id);
4086 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4088 } else if (line->type != LINE_TREE_DIR) {
4092 string_copy_rev(view->ref, entry->id);
4095 static const char *tree_argv[SIZEOF_ARG] = {
4096 "git", "ls-tree", "%(commit)", "%(directory)", NULL
4099 static struct view_ops tree_ops = {
4111 blob_read(struct view *view, char *line)
4115 return add_line_text(view, line, LINE_DEFAULT) != NULL;
4119 blob_request(struct view *view, enum request request, struct line *line)
4126 return pager_request(view, request, line);
4130 static const char *blob_argv[SIZEOF_ARG] = {
4131 "git", "cat-file", "blob", "%(blob)", NULL
4134 static struct view_ops blob_ops = {
4148 * Loading the blame view is a two phase job:
4150 * 1. File content is read either using opt_file from the
4151 * filesystem or using git-cat-file.
4152 * 2. Then blame information is incrementally added by
4153 * reading output from git-blame.
4156 static const char *blame_head_argv[] = {
4157 "git", "blame", "--incremental", "--", "%(file)", NULL
4160 static const char *blame_ref_argv[] = {
4161 "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4164 static const char *blame_cat_file_argv[] = {
4165 "git", "cat-file", "blob", "%(ref):%(file)", NULL
4168 struct blame_commit {
4169 char id[SIZEOF_REV]; /* SHA1 ID. */
4170 char title[128]; /* First line of the commit message. */
4171 char author[75]; /* Author of the commit. */
4172 struct tm time; /* Date from the author ident. */
4173 char filename[128]; /* Name of file. */
4174 bool has_previous; /* Was a "previous" line detected. */
4178 struct blame_commit *commit;
4183 blame_open(struct view *view)
4185 if (*opt_ref || !io_open(&view->io, opt_file)) {
4186 if (!run_io_rd(&view->io, blame_cat_file_argv, FORMAT_ALL))
4190 setup_update(view, opt_file);
4191 string_format(view->ref, "%s ...", opt_file);
4196 static struct blame_commit *
4197 get_blame_commit(struct view *view, const char *id)
4201 for (i = 0; i < view->lines; i++) {
4202 struct blame *blame = view->line[i].data;
4207 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4208 return blame->commit;
4212 struct blame_commit *commit = calloc(1, sizeof(*commit));
4215 string_ncopy(commit->id, id, SIZEOF_REV);
4221 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4223 const char *pos = *posref;
4226 pos = strchr(pos + 1, ' ');
4227 if (!pos || !isdigit(pos[1]))
4229 *number = atoi(pos + 1);
4230 if (*number < min || *number > max)
4237 static struct blame_commit *
4238 parse_blame_commit(struct view *view, const char *text, int *blamed)
4240 struct blame_commit *commit;
4241 struct blame *blame;
4242 const char *pos = text + SIZEOF_REV - 1;
4246 if (strlen(text) <= SIZEOF_REV || *pos != ' ')
4249 if (!parse_number(&pos, &lineno, 1, view->lines) ||
4250 !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4253 commit = get_blame_commit(view, text);
4259 struct line *line = &view->line[lineno + group - 1];
4262 blame->commit = commit;
4270 blame_read_file(struct view *view, const char *line, bool *read_file)
4273 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
4276 if (view->lines == 0 && !view->parent)
4277 die("No blame exist for %s", view->vid);
4279 if (view->lines == 0 || !run_io_rd(&io, argv, FORMAT_ALL)) {
4280 report("Failed to load blame data");
4284 done_io(view->pipe);
4290 size_t linelen = string_expand_length(line, opt_tab_size);
4291 struct blame *blame = malloc(sizeof(*blame) + linelen);
4296 blame->commit = NULL;
4297 string_expand(blame->text, linelen + 1, line, opt_tab_size);
4298 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
4303 match_blame_header(const char *name, char **line)
4305 size_t namelen = strlen(name);
4306 bool matched = !strncmp(name, *line, namelen);
4315 blame_read(struct view *view, char *line)
4317 static struct blame_commit *commit = NULL;
4318 static int blamed = 0;
4319 static time_t author_time;
4320 static bool read_file = TRUE;
4323 return blame_read_file(view, line, &read_file);
4330 string_format(view->ref, "%s", view->vid);
4331 if (view_is_displayed(view)) {
4332 update_view_title(view);
4333 redraw_view_from(view, 0);
4339 commit = parse_blame_commit(view, line, &blamed);
4340 string_format(view->ref, "%s %2d%%", view->vid,
4341 view->lines ? blamed * 100 / view->lines : 0);
4343 } else if (match_blame_header("author ", &line)) {
4344 string_ncopy(commit->author, line, strlen(line));
4346 } else if (match_blame_header("author-time ", &line)) {
4347 author_time = (time_t) atol(line);
4349 } else if (match_blame_header("author-tz ", &line)) {
4352 tz = ('0' - line[1]) * 60 * 60 * 10;
4353 tz += ('0' - line[2]) * 60 * 60;
4354 tz += ('0' - line[3]) * 60;
4355 tz += ('0' - line[4]) * 60;
4361 gmtime_r(&author_time, &commit->time);
4363 } else if (match_blame_header("summary ", &line)) {
4364 string_ncopy(commit->title, line, strlen(line));
4366 } else if (match_blame_header("previous ", &line)) {
4367 commit->has_previous = TRUE;
4369 } else if (match_blame_header("filename ", &line)) {
4370 string_ncopy(commit->filename, line, strlen(line));
4378 blame_draw(struct view *view, struct line *line, unsigned int lineno)
4380 struct blame *blame = line->data;
4381 struct tm *time = NULL;
4382 const char *id = NULL, *author = NULL;
4384 if (blame->commit && *blame->commit->filename) {
4385 id = blame->commit->id;
4386 author = blame->commit->author;
4387 time = &blame->commit->time;
4390 if (opt_date && draw_date(view, time))
4393 if (opt_author && draw_author(view, author))
4396 if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
4399 if (draw_lineno(view, lineno))
4402 draw_text(view, LINE_DEFAULT, blame->text, TRUE);
4407 check_blame_commit(struct blame *blame)
4410 report("Commit data not loaded yet");
4411 else if (!strcmp(blame->commit->id, NULL_ID))
4412 report("No commit exist for the selected line");
4419 blame_request(struct view *view, enum request request, struct line *line)
4421 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4422 struct blame *blame = line->data;
4425 case REQ_VIEW_BLAME:
4426 if (check_blame_commit(blame)) {
4427 string_copy(opt_ref, blame->commit->id);
4428 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4433 if (check_blame_commit(blame) &&
4434 select_commit_parent(blame->commit->id, opt_ref))
4435 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4439 if (!blame->commit) {
4440 report("No commit loaded yet");
4444 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
4445 !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
4448 if (!strcmp(blame->commit->id, NULL_ID)) {
4449 struct view *diff = VIEW(REQ_VIEW_DIFF);
4450 const char *diff_index_argv[] = {
4451 "git", "diff-index", "--root", "--patch-with-stat",
4452 "-C", "-M", "HEAD", "--", view->vid, NULL
4455 if (!blame->commit->has_previous) {
4456 diff_index_argv[1] = "diff";
4457 diff_index_argv[2] = "--no-color";
4458 diff_index_argv[6] = "--";
4459 diff_index_argv[7] = "/dev/null";
4462 if (!prepare_update(diff, diff_index_argv, NULL, FORMAT_DASH)) {
4463 report("Failed to allocate diff command");
4466 flags |= OPEN_PREPARED;
4469 open_view(view, REQ_VIEW_DIFF, flags);
4470 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
4471 string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
4482 blame_grep(struct view *view, struct line *line)
4484 struct blame *blame = line->data;
4485 struct blame_commit *commit = blame->commit;
4488 #define MATCH(text, on) \
4489 (on && *text && regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4492 char buf[DATE_COLS + 1];
4494 if (MATCH(commit->title, 1) ||
4495 MATCH(commit->author, opt_author) ||
4496 MATCH(commit->id, opt_date))
4499 if (strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time) &&
4504 return MATCH(blame->text, 1);
4510 blame_select(struct view *view, struct line *line)
4512 struct blame *blame = line->data;
4513 struct blame_commit *commit = blame->commit;
4518 if (!strcmp(commit->id, NULL_ID))
4519 string_ncopy(ref_commit, "HEAD", 4);
4521 string_copy_rev(ref_commit, commit->id);
4524 static struct view_ops blame_ops = {
4543 char rev[SIZEOF_REV];
4544 char name[SIZEOF_STR];
4548 char rev[SIZEOF_REV];
4549 char name[SIZEOF_STR];
4553 static char status_onbranch[SIZEOF_STR];
4554 static struct status stage_status;
4555 static enum line_type stage_line_type;
4556 static size_t stage_chunks;
4557 static int *stage_chunk;
4559 /* This should work even for the "On branch" line. */
4561 status_has_none(struct view *view, struct line *line)
4563 return line < view->line + view->lines && !line[1].data;
4566 /* Get fields from the diff line:
4567 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
4570 status_get_diff(struct status *file, const char *buf, size_t bufsize)
4572 const char *old_mode = buf + 1;
4573 const char *new_mode = buf + 8;
4574 const char *old_rev = buf + 15;
4575 const char *new_rev = buf + 56;
4576 const char *status = buf + 97;
4579 old_mode[-1] != ':' ||
4580 new_mode[-1] != ' ' ||
4581 old_rev[-1] != ' ' ||
4582 new_rev[-1] != ' ' ||
4586 file->status = *status;
4588 string_copy_rev(file->old.rev, old_rev);
4589 string_copy_rev(file->new.rev, new_rev);
4591 file->old.mode = strtoul(old_mode, NULL, 8);
4592 file->new.mode = strtoul(new_mode, NULL, 8);
4594 file->old.name[0] = file->new.name[0] = 0;
4600 status_run(struct view *view, const char *argv[], char status, enum line_type type)
4602 struct status *unmerged = NULL;
4606 if (!run_io(&io, argv, NULL, IO_RD))
4609 add_line_data(view, NULL, type);
4611 while ((buf = io_get(&io, 0, TRUE))) {
4612 struct status *file = unmerged;
4615 file = calloc(1, sizeof(*file));
4616 if (!file || !add_line_data(view, file, type))
4620 /* Parse diff info part. */
4622 file->status = status;
4624 string_copy(file->old.rev, NULL_ID);
4626 } else if (!file->status || file == unmerged) {
4627 if (!status_get_diff(file, buf, strlen(buf)))
4630 buf = io_get(&io, 0, TRUE);
4634 /* Collapse all modified entries that follow an
4635 * associated unmerged entry. */
4636 if (unmerged == file) {
4637 unmerged->status = 'U';
4639 } else if (file->status == 'U') {
4644 /* Grab the old name for rename/copy. */
4645 if (!*file->old.name &&
4646 (file->status == 'R' || file->status == 'C')) {
4647 string_ncopy(file->old.name, buf, strlen(buf));
4649 buf = io_get(&io, 0, TRUE);
4654 /* git-ls-files just delivers a NUL separated list of
4655 * file names similar to the second half of the
4656 * git-diff-* output. */
4657 string_ncopy(file->new.name, buf, strlen(buf));
4658 if (!*file->old.name)
4659 string_copy(file->old.name, file->new.name);
4663 if (io_error(&io)) {
4669 if (!view->line[view->lines - 1].data)
4670 add_line_data(view, NULL, LINE_STAT_NONE);
4676 /* Don't show unmerged entries in the staged section. */
4677 static const char *status_diff_index_argv[] = {
4678 "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
4679 "--cached", "-M", "HEAD", NULL
4682 static const char *status_diff_files_argv[] = {
4683 "git", "diff-files", "-z", NULL
4686 static const char *status_list_other_argv[] = {
4687 "git", "ls-files", "-z", "--others", "--exclude-standard", NULL
4690 static const char *status_list_no_head_argv[] = {
4691 "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
4694 static const char *update_index_argv[] = {
4695 "git", "update-index", "-q", "--unmerged", "--refresh", NULL
4698 /* Restore the previous line number to stay in the context or select a
4699 * line with something that can be updated. */
4701 status_restore(struct view *view)
4703 if (view->p_lineno >= view->lines)
4704 view->p_lineno = view->lines - 1;
4705 while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
4707 while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
4710 /* If the above fails, always skip the "On branch" line. */
4711 if (view->p_lineno < view->lines)
4712 view->lineno = view->p_lineno;
4716 if (view->lineno < view->offset)
4717 view->offset = view->lineno;
4718 else if (view->offset + view->height <= view->lineno)
4719 view->offset = view->lineno - view->height + 1;
4721 view->p_restore = FALSE;
4724 /* First parse staged info using git-diff-index(1), then parse unstaged
4725 * info using git-diff-files(1), and finally untracked files using
4726 * git-ls-files(1). */
4728 status_open(struct view *view)
4732 add_line_data(view, NULL, LINE_STAT_HEAD);
4733 if (is_initial_commit())
4734 string_copy(status_onbranch, "Initial commit");
4735 else if (!*opt_head)
4736 string_copy(status_onbranch, "Not currently on any branch");
4737 else if (!string_format(status_onbranch, "On branch %s", opt_head))
4740 run_io_bg(update_index_argv);
4742 if (is_initial_commit()) {
4743 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
4745 } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
4749 if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
4750 !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
4753 /* Restore the exact position or use the specialized restore
4755 if (!view->p_restore)
4756 status_restore(view);
4761 status_draw(struct view *view, struct line *line, unsigned int lineno)
4763 struct status *status = line->data;
4764 enum line_type type;
4768 switch (line->type) {
4769 case LINE_STAT_STAGED:
4770 type = LINE_STAT_SECTION;
4771 text = "Changes to be committed:";
4774 case LINE_STAT_UNSTAGED:
4775 type = LINE_STAT_SECTION;
4776 text = "Changed but not updated:";
4779 case LINE_STAT_UNTRACKED:
4780 type = LINE_STAT_SECTION;
4781 text = "Untracked files:";
4784 case LINE_STAT_NONE:
4785 type = LINE_DEFAULT;
4786 text = " (no files)";
4789 case LINE_STAT_HEAD:
4790 type = LINE_STAT_HEAD;
4791 text = status_onbranch;
4798 static char buf[] = { '?', ' ', ' ', ' ', 0 };
4800 buf[0] = status->status;
4801 if (draw_text(view, line->type, buf, TRUE))
4803 type = LINE_DEFAULT;
4804 text = status->new.name;
4807 draw_text(view, type, text, TRUE);
4812 status_enter(struct view *view, struct line *line)
4814 struct status *status = line->data;
4815 const char *oldpath = status ? status->old.name : NULL;
4816 /* Diffs for unmerged entries are empty when passing the new
4817 * path, so leave it empty. */
4818 const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
4820 enum open_flags split;
4821 struct view *stage = VIEW(REQ_VIEW_STAGE);
4823 if (line->type == LINE_STAT_NONE ||
4824 (!status && line[1].type == LINE_STAT_NONE)) {
4825 report("No file to diff");
4829 switch (line->type) {
4830 case LINE_STAT_STAGED:
4831 if (is_initial_commit()) {
4832 const char *no_head_diff_argv[] = {
4833 "git", "diff", "--no-color", "--patch-with-stat",
4834 "--", "/dev/null", newpath, NULL
4837 if (!prepare_update(stage, no_head_diff_argv, opt_cdup, FORMAT_DASH))
4840 const char *index_show_argv[] = {
4841 "git", "diff-index", "--root", "--patch-with-stat",
4842 "-C", "-M", "--cached", "HEAD", "--",
4843 oldpath, newpath, NULL
4846 if (!prepare_update(stage, index_show_argv, opt_cdup, FORMAT_DASH))
4851 info = "Staged changes to %s";
4853 info = "Staged changes";
4856 case LINE_STAT_UNSTAGED:
4858 const char *files_show_argv[] = {
4859 "git", "diff-files", "--root", "--patch-with-stat",
4860 "-C", "-M", "--", oldpath, newpath, NULL
4863 if (!prepare_update(stage, files_show_argv, opt_cdup, FORMAT_DASH))
4866 info = "Unstaged changes to %s";
4868 info = "Unstaged changes";
4871 case LINE_STAT_UNTRACKED:
4873 report("No file to show");
4877 if (!suffixcmp(status->new.name, -1, "/")) {
4878 report("Cannot display a directory");
4882 if (!prepare_update_file(stage, newpath))
4884 info = "Untracked file %s";
4887 case LINE_STAT_HEAD:
4891 die("line type %d not handled in switch", line->type);
4894 split = view_is_displayed(view) ? OPEN_SPLIT : 0;
4895 open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
4896 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
4898 stage_status = *status;
4900 memset(&stage_status, 0, sizeof(stage_status));
4903 stage_line_type = line->type;
4905 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
4912 status_exists(struct status *status, enum line_type type)
4914 struct view *view = VIEW(REQ_VIEW_STATUS);
4915 unsigned long lineno;
4917 for (lineno = 0; lineno < view->lines; lineno++) {
4918 struct line *line = &view->line[lineno];
4919 struct status *pos = line->data;
4921 if (line->type != type)
4923 if (!pos && (!status || !status->status) && line[1].data) {
4924 select_view_line(view, lineno);
4927 if (pos && !strcmp(status->new.name, pos->new.name)) {
4928 select_view_line(view, lineno);
4938 status_update_prepare(struct io *io, enum line_type type)
4940 const char *staged_argv[] = {
4941 "git", "update-index", "-z", "--index-info", NULL
4943 const char *others_argv[] = {
4944 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
4948 case LINE_STAT_STAGED:
4949 return run_io(io, staged_argv, opt_cdup, IO_WR);
4951 case LINE_STAT_UNSTAGED:
4952 return run_io(io, others_argv, opt_cdup, IO_WR);
4954 case LINE_STAT_UNTRACKED:
4955 return run_io(io, others_argv, NULL, IO_WR);
4958 die("line type %d not handled in switch", type);
4964 status_update_write(struct io *io, struct status *status, enum line_type type)
4966 char buf[SIZEOF_STR];
4970 case LINE_STAT_STAGED:
4971 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
4974 status->old.name, 0))
4978 case LINE_STAT_UNSTAGED:
4979 case LINE_STAT_UNTRACKED:
4980 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
4985 die("line type %d not handled in switch", type);
4988 return io_write(io, buf, bufsize);
4992 status_update_file(struct status *status, enum line_type type)
4997 if (!status_update_prepare(&io, type))
5000 result = status_update_write(&io, status, type);
5006 status_update_files(struct view *view, struct line *line)
5010 struct line *pos = view->line + view->lines;
5014 if (!status_update_prepare(&io, line->type))
5017 for (pos = line; pos < view->line + view->lines && pos->data; pos++)
5020 for (file = 0, done = 0; result && file < files; line++, file++) {
5021 int almost_done = file * 100 / files;
5023 if (almost_done > done) {
5025 string_format(view->ref, "updating file %u of %u (%d%% done)",
5027 update_view_title(view);
5029 result = status_update_write(&io, line->data, line->type);
5037 status_update(struct view *view)
5039 struct line *line = &view->line[view->lineno];
5041 assert(view->lines);
5044 /* This should work even for the "On branch" line. */
5045 if (line < view->line + view->lines && !line[1].data) {
5046 report("Nothing to update");
5050 if (!status_update_files(view, line + 1)) {
5051 report("Failed to update file status");
5055 } else if (!status_update_file(line->data, line->type)) {
5056 report("Failed to update file status");
5064 status_revert(struct status *status, enum line_type type, bool has_none)
5066 if (!status || type != LINE_STAT_UNSTAGED) {
5067 if (type == LINE_STAT_STAGED) {
5068 report("Cannot revert changes to staged files");
5069 } else if (type == LINE_STAT_UNTRACKED) {
5070 report("Cannot revert changes to untracked files");
5071 } else if (has_none) {
5072 report("Nothing to revert");
5074 report("Cannot revert changes to multiple files");
5079 char mode[10] = "100644";
5080 const char *reset_argv[] = {
5081 "git", "update-index", "--cacheinfo", mode,
5082 status->old.rev, status->old.name, NULL
5084 const char *checkout_argv[] = {
5085 "git", "checkout", "--", status->old.name, NULL
5088 if (!prompt_yesno("Are you sure you want to overwrite any changes?"))
5090 string_format(mode, "%o", status->old.mode);
5091 return (status->status != 'U' || run_io_fg(reset_argv, opt_cdup)) &&
5092 run_io_fg(checkout_argv, opt_cdup);
5097 status_request(struct view *view, enum request request, struct line *line)
5099 struct status *status = line->data;
5102 case REQ_STATUS_UPDATE:
5103 if (!status_update(view))
5107 case REQ_STATUS_REVERT:
5108 if (!status_revert(status, line->type, status_has_none(view, line)))
5112 case REQ_STATUS_MERGE:
5113 if (!status || status->status != 'U') {
5114 report("Merging only possible for files with unmerged status ('U').");
5117 open_mergetool(status->new.name);
5123 if (status->status == 'D') {
5124 report("File has been deleted.");
5128 open_editor(status->status != '?', status->new.name);
5131 case REQ_VIEW_BLAME:
5133 string_copy(opt_file, status->new.name);
5139 /* After returning the status view has been split to
5140 * show the stage view. No further reloading is
5142 status_enter(view, line);
5146 /* Simply reload the view. */
5153 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
5159 status_select(struct view *view, struct line *line)
5161 struct status *status = line->data;
5162 char file[SIZEOF_STR] = "all files";
5166 if (status && !string_format(file, "'%s'", status->new.name))
5169 if (!status && line[1].type == LINE_STAT_NONE)
5172 switch (line->type) {
5173 case LINE_STAT_STAGED:
5174 text = "Press %s to unstage %s for commit";
5177 case LINE_STAT_UNSTAGED:
5178 text = "Press %s to stage %s for commit";
5181 case LINE_STAT_UNTRACKED:
5182 text = "Press %s to stage %s for addition";
5185 case LINE_STAT_HEAD:
5186 case LINE_STAT_NONE:
5187 text = "Nothing to update";
5191 die("line type %d not handled in switch", line->type);
5194 if (status && status->status == 'U') {
5195 text = "Press %s to resolve conflict in %s";
5196 key = get_key(REQ_STATUS_MERGE);
5199 key = get_key(REQ_STATUS_UPDATE);
5202 string_format(view->ref, text, key, file);
5206 status_grep(struct view *view, struct line *line)
5208 struct status *status = line->data;
5209 enum { S_STATUS, S_NAME, S_END } state;
5216 for (state = S_STATUS; state < S_END; state++) {
5220 case S_NAME: text = status->new.name; break;
5222 buf[0] = status->status;
5230 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
5237 static struct view_ops status_ops = {
5250 stage_diff_write(struct io *io, struct line *line, struct line *end)
5252 while (line < end) {
5253 if (!io_write(io, line->data, strlen(line->data)) ||
5254 !io_write(io, "\n", 1))
5257 if (line->type == LINE_DIFF_CHUNK ||
5258 line->type == LINE_DIFF_HEADER)
5265 static struct line *
5266 stage_diff_find(struct view *view, struct line *line, enum line_type type)
5268 for (; view->line < line; line--)
5269 if (line->type == type)
5276 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
5278 const char *apply_argv[SIZEOF_ARG] = {
5279 "git", "apply", "--whitespace=nowarn", NULL
5281 struct line *diff_hdr;
5285 diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
5290 apply_argv[argc++] = "--cached";
5291 if (revert || stage_line_type == LINE_STAT_STAGED)
5292 apply_argv[argc++] = "-R";
5293 apply_argv[argc++] = "-";
5294 apply_argv[argc++] = NULL;
5295 if (!run_io(&io, apply_argv, opt_cdup, IO_WR))
5298 if (!stage_diff_write(&io, diff_hdr, chunk) ||
5299 !stage_diff_write(&io, chunk, view->line + view->lines))
5303 run_io_bg(update_index_argv);
5305 return chunk ? TRUE : FALSE;
5309 stage_update(struct view *view, struct line *line)
5311 struct line *chunk = NULL;
5313 if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
5314 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5317 if (!stage_apply_chunk(view, chunk, FALSE)) {
5318 report("Failed to apply chunk");
5322 } else if (!stage_status.status) {
5323 view = VIEW(REQ_VIEW_STATUS);
5325 for (line = view->line; line < view->line + view->lines; line++)
5326 if (line->type == stage_line_type)
5329 if (!status_update_files(view, line + 1)) {
5330 report("Failed to update files");
5334 } else if (!status_update_file(&stage_status, stage_line_type)) {
5335 report("Failed to update file");
5343 stage_revert(struct view *view, struct line *line)
5345 struct line *chunk = NULL;
5347 if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
5348 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5351 if (!prompt_yesno("Are you sure you want to revert changes?"))
5354 if (!stage_apply_chunk(view, chunk, TRUE)) {
5355 report("Failed to revert chunk");
5361 return status_revert(stage_status.status ? &stage_status : NULL,
5362 stage_line_type, FALSE);
5368 stage_next(struct view *view, struct line *line)
5372 if (!stage_chunks) {
5373 static size_t alloc = 0;
5376 for (line = view->line; line < view->line + view->lines; line++) {
5377 if (line->type != LINE_DIFF_CHUNK)
5380 tmp = realloc_items(stage_chunk, &alloc,
5381 stage_chunks, sizeof(*tmp));
5383 report("Allocation failure");
5388 stage_chunk[stage_chunks++] = line - view->line;
5392 for (i = 0; i < stage_chunks; i++) {
5393 if (stage_chunk[i] > view->lineno) {
5394 do_scroll_view(view, stage_chunk[i] - view->lineno);
5395 report("Chunk %d of %d", i + 1, stage_chunks);
5400 report("No next chunk found");
5404 stage_request(struct view *view, enum request request, struct line *line)
5407 case REQ_STATUS_UPDATE:
5408 if (!stage_update(view, line))
5412 case REQ_STATUS_REVERT:
5413 if (!stage_revert(view, line))
5417 case REQ_STAGE_NEXT:
5418 if (stage_line_type == LINE_STAT_UNTRACKED) {
5419 report("File is untracked; press %s to add",
5420 get_key(REQ_STATUS_UPDATE));
5423 stage_next(view, line);
5427 if (!stage_status.new.name[0])
5429 if (stage_status.status == 'D') {
5430 report("File has been deleted.");
5434 open_editor(stage_status.status != '?', stage_status.new.name);
5438 /* Reload everything ... */
5441 case REQ_VIEW_BLAME:
5442 if (stage_status.new.name[0]) {
5443 string_copy(opt_file, stage_status.new.name);
5449 return pager_request(view, request, line);
5455 VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
5456 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD | OPEN_NOMAXIMIZE);
5458 /* Check whether the staged entry still exists, and close the
5459 * stage view if it doesn't. */
5460 if (!status_exists(&stage_status, stage_line_type)) {
5461 status_restore(VIEW(REQ_VIEW_STATUS));
5462 return REQ_VIEW_CLOSE;
5465 if (stage_line_type == LINE_STAT_UNTRACKED) {
5466 if (!suffixcmp(stage_status.new.name, -1, "/")) {
5467 report("Cannot display a directory");
5471 if (!prepare_update_file(view, stage_status.new.name)) {
5472 report("Failed to open file: %s", strerror(errno));
5476 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
5481 static struct view_ops stage_ops = {
5498 char id[SIZEOF_REV]; /* SHA1 ID. */
5499 char title[128]; /* First line of the commit message. */
5500 char author[75]; /* Author of the commit. */
5501 struct tm time; /* Date from the author ident. */
5502 struct ref **refs; /* Repository references. */
5503 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
5504 size_t graph_size; /* The width of the graph array. */
5505 bool has_parents; /* Rewritten --parents seen. */
5508 /* Size of rev graph with no "padding" columns */
5509 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
5512 struct rev_graph *prev, *next, *parents;
5513 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
5515 struct commit *commit;
5517 unsigned int boundary:1;
5520 /* Parents of the commit being visualized. */
5521 static struct rev_graph graph_parents[4];
5523 /* The current stack of revisions on the graph. */
5524 static struct rev_graph graph_stacks[4] = {
5525 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
5526 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
5527 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
5528 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
5532 graph_parent_is_merge(struct rev_graph *graph)
5534 return graph->parents->size > 1;
5538 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
5540 struct commit *commit = graph->commit;
5542 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
5543 commit->graph[commit->graph_size++] = symbol;
5547 clear_rev_graph(struct rev_graph *graph)
5549 graph->boundary = 0;
5550 graph->size = graph->pos = 0;
5551 graph->commit = NULL;
5552 memset(graph->parents, 0, sizeof(*graph->parents));
5556 done_rev_graph(struct rev_graph *graph)
5558 if (graph_parent_is_merge(graph) &&
5559 graph->pos < graph->size - 1 &&
5560 graph->next->size == graph->size + graph->parents->size - 1) {
5561 size_t i = graph->pos + graph->parents->size - 1;
5563 graph->commit->graph_size = i * 2;
5564 while (i < graph->next->size - 1) {
5565 append_to_rev_graph(graph, ' ');
5566 append_to_rev_graph(graph, '\\');
5571 clear_rev_graph(graph);
5575 push_rev_graph(struct rev_graph *graph, const char *parent)
5579 /* "Collapse" duplicate parents lines.
5581 * FIXME: This needs to also update update the drawn graph but
5582 * for now it just serves as a method for pruning graph lines. */
5583 for (i = 0; i < graph->size; i++)
5584 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
5587 if (graph->size < SIZEOF_REVITEMS) {
5588 string_copy_rev(graph->rev[graph->size++], parent);
5593 get_rev_graph_symbol(struct rev_graph *graph)
5597 if (graph->boundary)
5598 symbol = REVGRAPH_BOUND;
5599 else if (graph->parents->size == 0)
5600 symbol = REVGRAPH_INIT;
5601 else if (graph_parent_is_merge(graph))
5602 symbol = REVGRAPH_MERGE;
5603 else if (graph->pos >= graph->size)
5604 symbol = REVGRAPH_BRANCH;
5606 symbol = REVGRAPH_COMMIT;
5612 draw_rev_graph(struct rev_graph *graph)
5615 chtype separator, line;
5617 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
5618 static struct rev_filler fillers[] = {
5624 chtype symbol = get_rev_graph_symbol(graph);
5625 struct rev_filler *filler;
5628 if (opt_line_graphics)
5629 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
5631 filler = &fillers[DEFAULT];
5633 for (i = 0; i < graph->pos; i++) {
5634 append_to_rev_graph(graph, filler->line);
5635 if (graph_parent_is_merge(graph->prev) &&
5636 graph->prev->pos == i)
5637 filler = &fillers[RSHARP];
5639 append_to_rev_graph(graph, filler->separator);
5642 /* Place the symbol for this revision. */
5643 append_to_rev_graph(graph, symbol);
5645 if (graph->prev->size > graph->size)
5646 filler = &fillers[RDIAG];
5648 filler = &fillers[DEFAULT];
5652 for (; i < graph->size; i++) {
5653 append_to_rev_graph(graph, filler->separator);
5654 append_to_rev_graph(graph, filler->line);
5655 if (graph_parent_is_merge(graph->prev) &&
5656 i < graph->prev->pos + graph->parents->size)
5657 filler = &fillers[RSHARP];
5658 if (graph->prev->size > graph->size)
5659 filler = &fillers[LDIAG];
5662 if (graph->prev->size > graph->size) {
5663 append_to_rev_graph(graph, filler->separator);
5664 if (filler->line != ' ')
5665 append_to_rev_graph(graph, filler->line);
5669 /* Prepare the next rev graph */
5671 prepare_rev_graph(struct rev_graph *graph)
5675 /* First, traverse all lines of revisions up to the active one. */
5676 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
5677 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
5680 push_rev_graph(graph->next, graph->rev[graph->pos]);
5683 /* Interleave the new revision parent(s). */
5684 for (i = 0; !graph->boundary && i < graph->parents->size; i++)
5685 push_rev_graph(graph->next, graph->parents->rev[i]);
5687 /* Lastly, put any remaining revisions. */
5688 for (i = graph->pos + 1; i < graph->size; i++)
5689 push_rev_graph(graph->next, graph->rev[i]);
5693 update_rev_graph(struct view *view, struct rev_graph *graph)
5695 /* If this is the finalizing update ... */
5697 prepare_rev_graph(graph);
5699 /* Graph visualization needs a one rev look-ahead,
5700 * so the first update doesn't visualize anything. */
5701 if (!graph->prev->commit)
5704 if (view->lines > 2)
5705 view->line[view->lines - 3].dirty = 1;
5706 if (view->lines > 1)
5707 view->line[view->lines - 2].dirty = 1;
5708 draw_rev_graph(graph->prev);
5709 done_rev_graph(graph->prev->prev);
5717 static const char *main_argv[SIZEOF_ARG] = {
5718 "git", "log", "--no-color", "--pretty=raw", "--parents",
5719 "--topo-order", "%(head)", NULL
5723 main_draw(struct view *view, struct line *line, unsigned int lineno)
5725 struct commit *commit = line->data;
5727 if (!*commit->author)
5730 if (opt_date && draw_date(view, &commit->time))
5733 if (opt_author && draw_author(view, commit->author))
5736 if (opt_rev_graph && commit->graph_size &&
5737 draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
5740 if (opt_show_refs && commit->refs) {
5744 enum line_type type;
5746 if (commit->refs[i]->head)
5747 type = LINE_MAIN_HEAD;
5748 else if (commit->refs[i]->ltag)
5749 type = LINE_MAIN_LOCAL_TAG;
5750 else if (commit->refs[i]->tag)
5751 type = LINE_MAIN_TAG;
5752 else if (commit->refs[i]->tracked)
5753 type = LINE_MAIN_TRACKED;
5754 else if (commit->refs[i]->remote)
5755 type = LINE_MAIN_REMOTE;
5757 type = LINE_MAIN_REF;
5759 if (draw_text(view, type, "[", TRUE) ||
5760 draw_text(view, type, commit->refs[i]->name, TRUE) ||
5761 draw_text(view, type, "]", TRUE))
5764 if (draw_text(view, LINE_DEFAULT, " ", TRUE))
5766 } while (commit->refs[i++]->next);
5769 draw_text(view, LINE_DEFAULT, commit->title, TRUE);
5773 /* Reads git log --pretty=raw output and parses it into the commit struct. */
5775 main_read(struct view *view, char *line)
5777 static struct rev_graph *graph = graph_stacks;
5778 enum line_type type;
5779 struct commit *commit;
5784 if (!view->lines && !view->parent)
5785 die("No revisions match the given arguments.");
5786 if (view->lines > 0) {
5787 commit = view->line[view->lines - 1].data;
5788 view->line[view->lines - 1].dirty = 1;
5789 if (!*commit->author) {
5792 graph->commit = NULL;
5795 update_rev_graph(view, graph);
5797 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
5798 clear_rev_graph(&graph_stacks[i]);
5802 type = get_line_type(line);
5803 if (type == LINE_COMMIT) {
5804 commit = calloc(1, sizeof(struct commit));
5808 line += STRING_SIZE("commit ");
5810 graph->boundary = 1;
5814 string_copy_rev(commit->id, line);
5815 commit->refs = get_refs(commit->id);
5816 graph->commit = commit;
5817 add_line_data(view, commit, LINE_MAIN_COMMIT);
5819 while ((line = strchr(line, ' '))) {
5821 push_rev_graph(graph->parents, line);
5822 commit->has_parents = TRUE;
5829 commit = view->line[view->lines - 1].data;
5833 if (commit->has_parents)
5835 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
5839 parse_author_line(line + STRING_SIZE("author "),
5840 commit->author, sizeof(commit->author),
5842 update_rev_graph(view, graph);
5843 graph = graph->next;
5847 /* Fill in the commit title if it has not already been set. */
5848 if (commit->title[0])
5851 /* Require titles to start with a non-space character at the
5852 * offset used by git log. */
5853 if (strncmp(line, " ", 4))
5856 /* Well, if the title starts with a whitespace character,
5857 * try to be forgiving. Otherwise we end up with no title. */
5858 while (isspace(*line))
5862 /* FIXME: More graceful handling of titles; append "..." to
5863 * shortened titles, etc. */
5865 string_expand(commit->title, sizeof(commit->title), line, 1);
5866 view->line[view->lines - 1].dirty = 1;
5873 main_request(struct view *view, enum request request, struct line *line)
5875 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
5879 open_view(view, REQ_VIEW_DIFF, flags);
5883 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
5893 grep_refs(struct ref **refs, regex_t *regex)
5901 if (regexec(regex, refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
5903 } while (refs[i++]->next);
5909 main_grep(struct view *view, struct line *line)
5911 struct commit *commit = line->data;
5912 enum { S_TITLE, S_AUTHOR, S_DATE, S_REFS, S_END } state;
5913 char buf[DATE_COLS + 1];
5916 for (state = S_TITLE; state < S_END; state++) {
5920 case S_TITLE: text = commit->title; break;
5924 text = commit->author;
5929 if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
5936 if (grep_refs(commit->refs, view->regex) == TRUE)
5943 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
5951 main_select(struct view *view, struct line *line)
5953 struct commit *commit = line->data;
5955 string_copy_rev(view->ref, commit->id);
5956 string_copy_rev(ref_commit, view->ref);
5959 static struct view_ops main_ops = {
5972 * Unicode / UTF-8 handling
5974 * NOTE: Much of the following code for dealing with Unicode is derived from
5975 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
5976 * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
5980 unicode_width(unsigned long c)
5983 (c <= 0x115f /* Hangul Jamo */
5986 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
5988 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
5989 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
5990 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
5991 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
5992 || (c >= 0xffe0 && c <= 0xffe6)
5993 || (c >= 0x20000 && c <= 0x2fffd)
5994 || (c >= 0x30000 && c <= 0x3fffd)))
5998 return opt_tab_size;
6003 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
6004 * Illegal bytes are set one. */
6005 static const unsigned char utf8_bytes[256] = {
6006 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
6007 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
6008 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
6009 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
6010 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
6011 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
6012 2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2,
6013 3,3,3,3,3,3,3,3, 3,3,3,3,3,3,3,3, 4,4,4,4,4,4,4,4, 5,5,5,5,6,6,1,1,
6016 /* Decode UTF-8 multi-byte representation into a Unicode character. */
6017 static inline unsigned long
6018 utf8_to_unicode(const char *string, size_t length)
6020 unsigned long unicode;
6024 unicode = string[0];
6027 unicode = (string[0] & 0x1f) << 6;
6028 unicode += (string[1] & 0x3f);
6031 unicode = (string[0] & 0x0f) << 12;
6032 unicode += ((string[1] & 0x3f) << 6);
6033 unicode += (string[2] & 0x3f);
6036 unicode = (string[0] & 0x0f) << 18;
6037 unicode += ((string[1] & 0x3f) << 12);
6038 unicode += ((string[2] & 0x3f) << 6);
6039 unicode += (string[3] & 0x3f);
6042 unicode = (string[0] & 0x0f) << 24;
6043 unicode += ((string[1] & 0x3f) << 18);
6044 unicode += ((string[2] & 0x3f) << 12);
6045 unicode += ((string[3] & 0x3f) << 6);
6046 unicode += (string[4] & 0x3f);
6049 unicode = (string[0] & 0x01) << 30;
6050 unicode += ((string[1] & 0x3f) << 24);
6051 unicode += ((string[2] & 0x3f) << 18);
6052 unicode += ((string[3] & 0x3f) << 12);
6053 unicode += ((string[4] & 0x3f) << 6);
6054 unicode += (string[5] & 0x3f);
6057 die("Invalid Unicode length");
6060 /* Invalid characters could return the special 0xfffd value but NUL
6061 * should be just as good. */
6062 return unicode > 0xffff ? 0 : unicode;
6065 /* Calculates how much of string can be shown within the given maximum width
6066 * and sets trimmed parameter to non-zero value if all of string could not be
6067 * shown. If the reserve flag is TRUE, it will reserve at least one
6068 * trailing character, which can be useful when drawing a delimiter.
6070 * Returns the number of bytes to output from string to satisfy max_width. */
6072 utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve)
6074 const char *string = *start;
6075 const char *end = strchr(string, '\0');
6076 unsigned char last_bytes = 0;
6077 size_t last_ucwidth = 0;
6082 while (string < end) {
6083 int c = *(unsigned char *) string;
6084 unsigned char bytes = utf8_bytes[c];
6086 unsigned long unicode;
6088 if (string + bytes > end)
6091 /* Change representation to figure out whether
6092 * it is a single- or double-width character. */
6094 unicode = utf8_to_unicode(string, bytes);
6095 /* FIXME: Graceful handling of invalid Unicode character. */
6099 ucwidth = unicode_width(unicode);
6101 skip -= ucwidth <= skip ? ucwidth : skip;
6105 if (*width > max_width) {
6108 if (reserve && *width == max_width) {
6109 string -= last_bytes;
6110 *width -= last_ucwidth;
6116 last_bytes = ucwidth ? bytes : 0;
6117 last_ucwidth = ucwidth;
6120 return string - *start;
6128 /* Whether or not the curses interface has been initialized. */
6129 static bool cursed = FALSE;
6131 /* Terminal hacks and workarounds. */
6132 static bool use_scroll_redrawwin;
6133 static bool use_scroll_status_wclear;
6135 /* The status window is used for polling keystrokes. */
6136 static WINDOW *status_win;
6138 /* Reading from the prompt? */
6139 static bool input_mode = FALSE;
6141 static bool status_empty = FALSE;
6143 /* Update status and title window. */
6145 report(const char *msg, ...)
6147 struct view *view = display[current_view];
6153 char buf[SIZEOF_STR];
6156 va_start(args, msg);
6157 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6158 buf[sizeof(buf) - 1] = 0;
6159 buf[sizeof(buf) - 2] = '.';
6160 buf[sizeof(buf) - 3] = '.';
6161 buf[sizeof(buf) - 4] = '.';
6167 if (!status_empty || *msg) {
6170 va_start(args, msg);
6172 wmove(status_win, 0, 0);
6173 if (view->has_scrolled && use_scroll_status_wclear)
6176 vwprintw(status_win, msg, args);
6177 status_empty = FALSE;
6179 status_empty = TRUE;
6181 wclrtoeol(status_win);
6182 wnoutrefresh(status_win);
6187 update_view_title(view);
6190 /* Controls when nodelay should be in effect when polling user input. */
6192 set_nonblocking_input(bool loading)
6194 static unsigned int loading_views;
6196 if ((loading == FALSE && loading_views-- == 1) ||
6197 (loading == TRUE && loading_views++ == 0))
6198 nodelay(status_win, loading);
6207 /* Initialize the curses library */
6208 if (isatty(STDIN_FILENO)) {
6209 cursed = !!initscr();
6212 /* Leave stdin and stdout alone when acting as a pager. */
6213 opt_tty = fopen("/dev/tty", "r+");
6215 die("Failed to open /dev/tty");
6216 cursed = !!newterm(NULL, opt_tty, opt_tty);
6220 die("Failed to initialize curses");
6222 nonl(); /* Disable conversion and detect newlines from input. */
6223 cbreak(); /* Take input chars one at a time, no wait for \n */
6224 noecho(); /* Don't echo input */
6225 leaveok(stdscr, FALSE);
6230 getmaxyx(stdscr, y, x);
6231 status_win = newwin(1, 0, y - 1, 0);
6233 die("Failed to create status window");
6235 /* Enable keyboard mapping */
6236 keypad(status_win, TRUE);
6237 wbkgdset(status_win, get_line_attr(LINE_STATUS));
6239 TABSIZE = opt_tab_size;
6240 if (opt_line_graphics) {
6241 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
6244 term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
6245 if (term && !strcmp(term, "gnome-terminal")) {
6246 /* In the gnome-terminal-emulator, the message from
6247 * scrolling up one line when impossible followed by
6248 * scrolling down one line causes corruption of the
6249 * status line. This is fixed by calling wclear. */
6250 use_scroll_status_wclear = TRUE;
6251 use_scroll_redrawwin = FALSE;
6253 } else if (term && !strcmp(term, "xrvt-xpm")) {
6254 /* No problems with full optimizations in xrvt-(unicode)
6256 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
6259 /* When scrolling in (u)xterm the last line in the
6260 * scrolling direction will update slowly. */
6261 use_scroll_redrawwin = TRUE;
6262 use_scroll_status_wclear = FALSE;
6267 get_input(int prompt_position)
6270 int i, key, cursor_y, cursor_x;
6272 if (prompt_position)
6276 foreach_view (view, i) {
6278 if (view_is_displayed(view) && view->has_scrolled &&
6279 use_scroll_redrawwin)
6280 redrawwin(view->win);
6281 view->has_scrolled = FALSE;
6284 /* Update the cursor position. */
6285 if (prompt_position) {
6286 getbegyx(status_win, cursor_y, cursor_x);
6287 cursor_x = prompt_position;
6289 view = display[current_view];
6290 getbegyx(view->win, cursor_y, cursor_x);
6291 cursor_x = view->width - 1;
6292 cursor_y += view->lineno - view->offset;
6294 setsyx(cursor_y, cursor_x);
6296 /* Refresh, accept single keystroke of input */
6298 key = wgetch(status_win);
6300 /* wgetch() with nodelay() enabled returns ERR when
6301 * there's no input. */
6304 } else if (key == KEY_RESIZE) {
6307 getmaxyx(stdscr, height, width);
6309 wresize(status_win, 1, width);
6310 mvwin(status_win, height - 1, 0);
6311 wnoutrefresh(status_win);
6313 redraw_display(TRUE);
6323 prompt_input(const char *prompt, input_handler handler, void *data)
6325 enum input_status status = INPUT_OK;
6326 static char buf[SIZEOF_STR];
6331 while (status == INPUT_OK || status == INPUT_SKIP) {
6334 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
6335 wclrtoeol(status_win);
6337 key = get_input(pos + 1);
6342 status = pos ? INPUT_STOP : INPUT_CANCEL;
6349 status = INPUT_CANCEL;
6353 status = INPUT_CANCEL;
6357 if (pos >= sizeof(buf)) {
6358 report("Input string too long");
6362 status = handler(data, buf, key);
6363 if (status == INPUT_OK)
6364 buf[pos++] = (char) key;
6368 /* Clear the status window */
6369 status_empty = FALSE;
6372 if (status == INPUT_CANCEL)
6380 static enum input_status
6381 prompt_yesno_handler(void *data, char *buf, int c)
6383 if (c == 'y' || c == 'Y')
6385 if (c == 'n' || c == 'N')
6386 return INPUT_CANCEL;
6391 prompt_yesno(const char *prompt)
6393 char prompt2[SIZEOF_STR];
6395 if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
6398 return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
6401 static enum input_status
6402 read_prompt_handler(void *data, char *buf, int c)
6404 return isprint(c) ? INPUT_OK : INPUT_SKIP;
6408 read_prompt(const char *prompt)
6410 return prompt_input(prompt, read_prompt_handler, NULL);
6414 * Repository properties
6417 static struct ref *refs = NULL;
6418 static size_t refs_alloc = 0;
6419 static size_t refs_size = 0;
6421 /* Id <-> ref store */
6422 static struct ref ***id_refs = NULL;
6423 static size_t id_refs_alloc = 0;
6424 static size_t id_refs_size = 0;
6427 compare_refs(const void *ref1_, const void *ref2_)
6429 const struct ref *ref1 = *(const struct ref **)ref1_;
6430 const struct ref *ref2 = *(const struct ref **)ref2_;
6432 if (ref1->tag != ref2->tag)
6433 return ref2->tag - ref1->tag;
6434 if (ref1->ltag != ref2->ltag)
6435 return ref2->ltag - ref2->ltag;
6436 if (ref1->head != ref2->head)
6437 return ref2->head - ref1->head;
6438 if (ref1->tracked != ref2->tracked)
6439 return ref2->tracked - ref1->tracked;
6440 if (ref1->remote != ref2->remote)
6441 return ref2->remote - ref1->remote;
6442 return strcmp(ref1->name, ref2->name);
6445 static struct ref **
6446 get_refs(const char *id)
6448 struct ref ***tmp_id_refs;
6449 struct ref **ref_list = NULL;
6450 size_t ref_list_alloc = 0;
6451 size_t ref_list_size = 0;
6454 for (i = 0; i < id_refs_size; i++)
6455 if (!strcmp(id, id_refs[i][0]->id))
6458 tmp_id_refs = realloc_items(id_refs, &id_refs_alloc, id_refs_size + 1,
6463 id_refs = tmp_id_refs;
6465 for (i = 0; i < refs_size; i++) {
6468 if (strcmp(id, refs[i].id))
6471 tmp = realloc_items(ref_list, &ref_list_alloc,
6472 ref_list_size + 1, sizeof(*ref_list));
6480 ref_list[ref_list_size] = &refs[i];
6481 /* XXX: The properties of the commit chains ensures that we can
6482 * safely modify the shared ref. The repo references will
6483 * always be similar for the same id. */
6484 ref_list[ref_list_size]->next = 1;
6490 qsort(ref_list, ref_list_size, sizeof(*ref_list), compare_refs);
6491 ref_list[ref_list_size - 1]->next = 0;
6492 id_refs[id_refs_size++] = ref_list;
6499 read_ref(char *id, size_t idlen, char *name, size_t namelen)
6504 bool remote = FALSE;
6505 bool tracked = FALSE;
6506 bool check_replace = FALSE;
6509 if (!prefixcmp(name, "refs/tags/")) {
6510 if (!suffixcmp(name, namelen, "^{}")) {
6513 if (refs_size > 0 && refs[refs_size - 1].ltag == TRUE)
6514 check_replace = TRUE;
6520 namelen -= STRING_SIZE("refs/tags/");
6521 name += STRING_SIZE("refs/tags/");
6523 } else if (!prefixcmp(name, "refs/remotes/")) {
6525 namelen -= STRING_SIZE("refs/remotes/");
6526 name += STRING_SIZE("refs/remotes/");
6527 tracked = !strcmp(opt_remote, name);
6529 } else if (!prefixcmp(name, "refs/heads/")) {
6530 namelen -= STRING_SIZE("refs/heads/");
6531 name += STRING_SIZE("refs/heads/");
6532 head = !strncmp(opt_head, name, namelen);
6534 } else if (!strcmp(name, "HEAD")) {
6535 string_ncopy(opt_head_rev, id, idlen);
6539 if (check_replace && !strcmp(name, refs[refs_size - 1].name)) {
6540 /* it's an annotated tag, replace the previous SHA1 with the
6541 * resolved commit id; relies on the fact git-ls-remote lists
6542 * the commit id of an annotated tag right before the commit id
6544 refs[refs_size - 1].ltag = ltag;
6545 string_copy_rev(refs[refs_size - 1].id, id);
6549 refs = realloc_items(refs, &refs_alloc, refs_size + 1, sizeof(*refs));
6553 ref = &refs[refs_size++];
6554 ref->name = malloc(namelen + 1);
6558 strncpy(ref->name, name, namelen);
6559 ref->name[namelen] = 0;
6563 ref->remote = remote;
6564 ref->tracked = tracked;
6565 string_copy_rev(ref->id, id);
6573 static const char *ls_remote_argv[SIZEOF_ARG] = {
6574 "git", "ls-remote", ".", NULL
6576 static bool init = FALSE;
6579 argv_from_env(ls_remote_argv, "TIG_LS_REMOTE");
6586 while (refs_size > 0)
6587 free(refs[--refs_size].name);
6588 while (id_refs_size > 0)
6589 free(id_refs[--id_refs_size]);
6591 return run_io_load(ls_remote_argv, "\t", read_ref);
6595 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
6597 const char *argv[SIZEOF_ARG] = { name, "=" };
6598 int argc = 1 + (cmd == option_set_command);
6601 if (!argv_from_string(argv, &argc, value))
6602 config_msg = "Too many option arguments";
6604 error = cmd(argc, argv);
6607 warn("Option 'tig.%s': %s", name, config_msg);
6611 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
6613 if (!strcmp(name, "i18n.commitencoding"))
6614 string_ncopy(opt_encoding, value, valuelen);
6616 if (!strcmp(name, "core.editor"))
6617 string_ncopy(opt_editor, value, valuelen);
6619 if (!prefixcmp(name, "tig.color."))
6620 set_repo_config_option(name + 10, value, option_color_command);
6622 else if (!prefixcmp(name, "tig.bind."))
6623 set_repo_config_option(name + 9, value, option_bind_command);
6625 else if (!prefixcmp(name, "tig."))
6626 set_repo_config_option(name + 4, value, option_set_command);
6628 /* branch.<head>.remote */
6630 !strncmp(name, "branch.", 7) &&
6631 !strncmp(name + 7, opt_head, strlen(opt_head)) &&
6632 !strcmp(name + 7 + strlen(opt_head), ".remote"))
6633 string_ncopy(opt_remote, value, valuelen);
6635 if (*opt_head && *opt_remote &&
6636 !strncmp(name, "branch.", 7) &&
6637 !strncmp(name + 7, opt_head, strlen(opt_head)) &&
6638 !strcmp(name + 7 + strlen(opt_head), ".merge")) {
6639 size_t from = strlen(opt_remote);
6641 if (!prefixcmp(value, "refs/heads/")) {
6642 value += STRING_SIZE("refs/heads/");
6643 valuelen -= STRING_SIZE("refs/heads/");
6646 if (!string_format_from(opt_remote, &from, "/%s", value))
6654 load_git_config(void)
6656 const char *config_list_argv[] = { "git", GIT_CONFIG, "--list", NULL };
6658 return run_io_load(config_list_argv, "=", read_repo_config_option);
6662 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
6664 if (!opt_git_dir[0]) {
6665 string_ncopy(opt_git_dir, name, namelen);
6667 } else if (opt_is_inside_work_tree == -1) {
6668 /* This can be 3 different values depending on the
6669 * version of git being used. If git-rev-parse does not
6670 * understand --is-inside-work-tree it will simply echo
6671 * the option else either "true" or "false" is printed.
6672 * Default to true for the unknown case. */
6673 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
6675 } else if (*name == '.') {
6676 string_ncopy(opt_cdup, name, namelen);
6679 string_ncopy(opt_prefix, name, namelen);
6686 load_repo_info(void)
6688 const char *head_argv[] = {
6689 "git", "symbolic-ref", "HEAD", NULL
6691 const char *rev_parse_argv[] = {
6692 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
6693 "--show-cdup", "--show-prefix", NULL
6696 if (run_io_buf(head_argv, opt_head, sizeof(opt_head))) {
6697 chomp_string(opt_head);
6698 if (!prefixcmp(opt_head, "refs/heads/")) {
6699 char *offset = opt_head + STRING_SIZE("refs/heads/");
6701 memmove(opt_head, offset, strlen(offset) + 1);
6705 return run_io_load(rev_parse_argv, "=", read_repo_info);
6713 static const char usage[] =
6714 "tig " TIG_VERSION " (" __DATE__ ")\n"
6716 "Usage: tig [options] [revs] [--] [paths]\n"
6717 " or: tig show [options] [revs] [--] [paths]\n"
6718 " or: tig blame [rev] path\n"
6720 " or: tig < [git command output]\n"
6723 " -v, --version Show version and exit\n"
6724 " -h, --help Show help message and exit";
6726 static void __NORETURN
6729 /* XXX: Restore tty modes and let the OS cleanup the rest! */
6735 static void __NORETURN
6736 die(const char *err, ...)
6742 va_start(args, err);
6743 fputs("tig: ", stderr);
6744 vfprintf(stderr, err, args);
6745 fputs("\n", stderr);
6752 warn(const char *msg, ...)
6756 va_start(args, msg);
6757 fputs("tig warning: ", stderr);
6758 vfprintf(stderr, msg, args);
6759 fputs("\n", stderr);
6764 parse_options(int argc, const char *argv[])
6766 enum request request = REQ_VIEW_MAIN;
6767 const char *subcommand;
6768 bool seen_dashdash = FALSE;
6769 /* XXX: This is vulnerable to the user overriding options
6770 * required for the main view parser. */
6771 const char *custom_argv[SIZEOF_ARG] = {
6772 "git", "log", "--no-color", "--pretty=raw", "--parents",
6773 "--topo-order", NULL
6777 if (!isatty(STDIN_FILENO)) {
6778 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
6779 return REQ_VIEW_PAGER;
6785 subcommand = argv[1];
6786 if (!strcmp(subcommand, "status")) {
6788 warn("ignoring arguments after `%s'", subcommand);
6789 return REQ_VIEW_STATUS;
6791 } else if (!strcmp(subcommand, "blame")) {
6792 if (argc <= 2 || argc > 4)
6793 die("invalid number of options to blame\n\n%s", usage);
6797 string_ncopy(opt_ref, argv[i], strlen(argv[i]));
6801 string_ncopy(opt_file, argv[i], strlen(argv[i]));
6802 return REQ_VIEW_BLAME;
6804 } else if (!strcmp(subcommand, "show")) {
6805 request = REQ_VIEW_DIFF;
6812 custom_argv[1] = subcommand;
6816 for (i = 1 + !!subcommand; i < argc; i++) {
6817 const char *opt = argv[i];
6819 if (seen_dashdash || !strcmp(opt, "--")) {
6820 seen_dashdash = TRUE;
6822 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
6823 printf("tig version %s\n", TIG_VERSION);
6826 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
6827 printf("%s\n", usage);
6831 custom_argv[j++] = opt;
6832 if (j >= ARRAY_SIZE(custom_argv))
6833 die("command too long");
6836 if (!prepare_update(VIEW(request), custom_argv, NULL, FORMAT_NONE))
6837 die("Failed to format arguments");
6843 main(int argc, const char *argv[])
6845 enum request request = parse_options(argc, argv);
6849 signal(SIGINT, quit);
6851 if (setlocale(LC_ALL, "")) {
6852 char *codeset = nl_langinfo(CODESET);
6854 string_ncopy(opt_codeset, codeset, strlen(codeset));
6857 if (load_repo_info() == ERR)
6858 die("Failed to load repo info.");
6860 if (load_options() == ERR)
6861 die("Failed to load user config.");
6863 if (load_git_config() == ERR)
6864 die("Failed to load repo config.");
6866 /* Require a git repository unless when running in pager mode. */
6867 if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
6868 die("Not a git repository");
6870 if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
6873 if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
6874 opt_iconv = iconv_open(opt_codeset, opt_encoding);
6875 if (opt_iconv == ICONV_NONE)
6876 die("Failed to initialize character set conversion");
6879 if (load_refs() == ERR)
6880 die("Failed to load refs.");
6882 foreach_view (view, i)
6883 argv_from_env(view->ops->argv, view->cmd_env);
6887 if (request != REQ_NONE)
6888 open_view(NULL, request, OPEN_PREPARED);
6889 request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
6891 while (view_driver(display[current_view], request)) {
6892 int key = get_input(0);
6894 view = display[current_view];
6895 request = get_keybinding(view->keymap, key);
6897 /* Some low-level request handling. This keeps access to
6898 * status_win restricted. */
6902 char *cmd = read_prompt(":");
6905 struct view *next = VIEW(REQ_VIEW_PAGER);
6906 const char *argv[SIZEOF_ARG] = { "git" };
6909 /* When running random commands, initially show the
6910 * command in the title. However, it maybe later be
6911 * overwritten if a commit line is selected. */
6912 string_ncopy(next->ref, cmd, strlen(cmd));
6914 if (!argv_from_string(argv, &argc, cmd)) {
6915 report("Too many arguments");
6916 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
6917 report("Failed to format command");
6919 open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
6927 case REQ_SEARCH_BACK:
6929 const char *prompt = request == REQ_SEARCH ? "/" : "?";
6930 char *search = read_prompt(prompt);
6933 string_ncopy(opt_search, search, strlen(search));
6934 else if (*opt_search)
6935 request = request == REQ_SEARCH ?