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
115 #define SCALE_SPLIT_VIEW(height) ((height) * 2 / 3)
117 #define NULL_ID "0000000000000000000000000000000000000000"
119 #define S_ISGITLINK(mode) (((mode) & S_IFMT) == 0160000)
122 #define GIT_CONFIG "config"
125 /* Some ASCII-shorthands fitted into the ncurses namespace. */
127 #define KEY_RETURN '\r'
132 char *name; /* Ref name; tag or head names are shortened. */
133 char id[SIZEOF_REV]; /* Commit SHA1 ID */
134 unsigned int head:1; /* Is it the current HEAD? */
135 unsigned int tag:1; /* Is it a tag? */
136 unsigned int ltag:1; /* If so, is the tag local? */
137 unsigned int remote:1; /* Is it a remote ref? */
138 unsigned int tracked:1; /* Is it the remote for the current HEAD? */
139 unsigned int next:1; /* For ref lists: are there more refs? */
142 static struct ref **get_refs(const char *id);
145 FORMAT_ALL, /* Perform replacement in all arguments. */
146 FORMAT_DASH, /* Perform replacement up until "--". */
147 FORMAT_NONE /* No replacement should be performed. */
150 static bool format_argv(const char *dst[], const char *src[], enum format_flags flags);
159 typedef enum input_status (*input_handler)(void *data, char *buf, int c);
161 static char *prompt_input(const char *prompt, input_handler handler, void *data);
162 static bool prompt_yesno(const char *prompt);
165 * Allocation helpers ... Entering macro hell to never be seen again.
168 #define DEFINE_ALLOCATOR(name, type, chunk_size) \
170 name(type **mem, size_t *alloc, size_t new_size) \
172 size_t num_chunks = *alloc / chunk_size; \
173 size_t num_chunks_new = (new_size + chunk_size - 1) / chunk_size; \
176 if (mem == NULL || num_chunks != num_chunks_new) { \
177 size_t memsize = num_chunks_new * chunk_size; \
178 tmp = realloc(tmp, memsize * sizeof(type)); \
180 *mem = tmp, *alloc = memsize; \
191 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
193 if (srclen > dstlen - 1)
196 strncpy(dst, src, srclen);
200 /* Shorthands for safely copying into a fixed buffer. */
202 #define string_copy(dst, src) \
203 string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
205 #define string_ncopy(dst, src, srclen) \
206 string_ncopy_do(dst, sizeof(dst), src, srclen)
208 #define string_copy_rev(dst, src) \
209 string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
211 #define string_add(dst, from, src) \
212 string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
215 string_expand(char *dst, size_t dstlen, const char *src, int tabsize)
219 for (size = pos = 0; size < dstlen - 1 && src[pos]; pos++) {
220 if (src[pos] == '\t') {
221 size_t expanded = tabsize - (size % tabsize);
223 if (expanded + size >= dstlen - 1)
224 expanded = dstlen - size - 1;
225 memcpy(dst + size, " ", expanded);
228 dst[size++] = src[pos];
236 chomp_string(char *name)
240 while (isspace(*name))
243 namelen = strlen(name) - 1;
244 while (namelen > 0 && isspace(name[namelen]))
251 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
254 size_t pos = bufpos ? *bufpos : 0;
257 pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
263 return pos >= bufsize ? FALSE : TRUE;
266 #define string_format(buf, fmt, args...) \
267 string_nformat(buf, sizeof(buf), NULL, fmt, args)
269 #define string_format_from(buf, from, fmt, args...) \
270 string_nformat(buf, sizeof(buf), from, fmt, args)
273 string_enum_compare(const char *str1, const char *str2, int len)
277 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
279 /* Diff-Header == DIFF_HEADER */
280 for (i = 0; i < len; i++) {
281 if (toupper(str1[i]) == toupper(str2[i]))
284 if (string_enum_sep(str1[i]) &&
285 string_enum_sep(str2[i]))
288 return str1[i] - str2[i];
300 #define ENUM_MAP(name, value) { name, STRING_SIZE(name), value }
303 map_enum_do(const struct enum_map *map, size_t map_size, int *value, const char *name)
305 size_t namelen = strlen(name);
308 for (i = 0; i < map_size; i++)
309 if (namelen == map[i].namelen &&
310 !string_enum_compare(name, map[i].name, namelen)) {
311 *value = map[i].value;
318 #define map_enum(attr, map, name) \
319 map_enum_do(map, ARRAY_SIZE(map), attr, name)
321 #define prefixcmp(str1, str2) \
322 strncmp(str1, str2, STRING_SIZE(str2))
325 suffixcmp(const char *str, int slen, const char *suffix)
327 size_t len = slen >= 0 ? slen : strlen(str);
328 size_t suffixlen = strlen(suffix);
330 return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
335 mkdate(const time_t *time)
337 static char buf[DATE_COLS + 1];
341 return strftime(buf, sizeof(buf), DATE_FORMAT, &tm) ? buf : NULL;
346 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
350 while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
351 bool advance = cmd[valuelen] != 0;
354 argv[(*argc)++] = chomp_string(cmd);
355 cmd = chomp_string(cmd + valuelen + advance);
358 if (*argc < SIZEOF_ARG)
360 return *argc < SIZEOF_ARG;
364 argv_from_env(const char **argv, const char *name)
366 char *env = argv ? getenv(name) : NULL;
371 if (env && !argv_from_string(argv, &argc, env))
372 die("Too many arguments in the `%s` environment variable", name);
377 * Executing external commands.
381 IO_FD, /* File descriptor based IO. */
382 IO_BG, /* Execute command in the background. */
383 IO_FG, /* Execute command with same std{in,out,err}. */
384 IO_RD, /* Read only fork+exec IO. */
385 IO_WR, /* Write only fork+exec IO. */
386 IO_AP, /* Append fork+exec output to file. */
390 enum io_type type; /* The requested type of pipe. */
391 const char *dir; /* Directory from which to execute. */
392 pid_t pid; /* Pipe for reading or writing. */
393 int pipe; /* Pipe end for reading or writing. */
394 int error; /* Error status. */
395 const char *argv[SIZEOF_ARG]; /* Shell command arguments. */
396 char *buf; /* Read buffer. */
397 size_t bufalloc; /* Allocated buffer size. */
398 size_t bufsize; /* Buffer content size. */
399 char *bufpos; /* Current buffer position. */
400 unsigned int eof:1; /* Has end of file been reached. */
404 reset_io(struct io *io)
408 io->buf = io->bufpos = NULL;
409 io->bufalloc = io->bufsize = 0;
415 init_io(struct io *io, const char *dir, enum io_type type)
423 init_io_rd(struct io *io, const char *argv[], const char *dir,
424 enum format_flags flags)
426 init_io(io, dir, IO_RD);
427 return format_argv(io->argv, argv, flags);
431 io_open(struct io *io, const char *name)
433 init_io(io, NULL, IO_FD);
434 io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
437 return io->pipe != -1;
441 kill_io(struct io *io)
443 return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
447 done_io(struct io *io)
458 pid_t waiting = waitpid(pid, &status, 0);
463 report("waitpid failed (%s)", strerror(errno));
467 return waiting == pid &&
468 !WIFSIGNALED(status) &&
470 !WEXITSTATUS(status);
477 start_io(struct io *io)
479 int pipefds[2] = { -1, -1 };
481 if (io->type == IO_FD)
484 if ((io->type == IO_RD || io->type == IO_WR) &&
487 else if (io->type == IO_AP)
488 pipefds[1] = io->pipe;
490 if ((io->pid = fork())) {
491 if (pipefds[!(io->type == IO_WR)] != -1)
492 close(pipefds[!(io->type == IO_WR)]);
494 io->pipe = pipefds[!!(io->type == IO_WR)];
499 if (io->type != IO_FG) {
500 int devnull = open("/dev/null", O_RDWR);
501 int readfd = io->type == IO_WR ? pipefds[0] : devnull;
502 int writefd = (io->type == IO_RD || io->type == IO_AP)
503 ? pipefds[1] : devnull;
505 dup2(readfd, STDIN_FILENO);
506 dup2(writefd, STDOUT_FILENO);
507 dup2(devnull, STDERR_FILENO);
510 if (pipefds[0] != -1)
512 if (pipefds[1] != -1)
516 if (io->dir && *io->dir && chdir(io->dir) == -1)
517 die("Failed to change directory: %s", strerror(errno));
519 execvp(io->argv[0], (char *const*) io->argv);
520 die("Failed to execute program: %s", strerror(errno));
523 if (pipefds[!!(io->type == IO_WR)] != -1)
524 close(pipefds[!!(io->type == IO_WR)]);
529 run_io(struct io *io, const char **argv, const char *dir, enum io_type type)
531 init_io(io, dir, type);
532 if (!format_argv(io->argv, argv, FORMAT_NONE))
538 run_io_do(struct io *io)
540 return start_io(io) && done_io(io);
544 run_io_bg(const char **argv)
548 init_io(&io, NULL, IO_BG);
549 if (!format_argv(io.argv, argv, FORMAT_NONE))
551 return run_io_do(&io);
555 run_io_fg(const char **argv, const char *dir)
559 init_io(&io, dir, IO_FG);
560 if (!format_argv(io.argv, argv, FORMAT_NONE))
562 return run_io_do(&io);
566 run_io_append(const char **argv, enum format_flags flags, int fd)
570 init_io(&io, NULL, IO_AP);
572 if (format_argv(io.argv, argv, flags))
573 return run_io_do(&io);
579 run_io_rd(struct io *io, const char **argv, enum format_flags flags)
581 return init_io_rd(io, argv, NULL, flags) && start_io(io);
585 io_eof(struct io *io)
591 io_error(struct io *io)
597 io_strerror(struct io *io)
599 return strerror(io->error);
603 io_can_read(struct io *io)
605 struct timeval tv = { 0, 500 };
609 FD_SET(io->pipe, &fds);
611 return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
615 io_read(struct io *io, void *buf, size_t bufsize)
618 ssize_t readsize = read(io->pipe, buf, bufsize);
620 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
622 else if (readsize == -1)
624 else if (readsize == 0)
631 io_get(struct io *io, int c, bool can_read)
637 io->buf = io->bufpos = malloc(BUFSIZ);
640 io->bufalloc = BUFSIZ;
645 if (io->bufsize > 0) {
646 eol = memchr(io->bufpos, c, io->bufsize);
648 char *line = io->bufpos;
651 io->bufpos = eol + 1;
652 io->bufsize -= io->bufpos - line;
659 io->bufpos[io->bufsize] = 0;
669 if (io->bufsize > 0 && io->bufpos > io->buf)
670 memmove(io->buf, io->bufpos, io->bufsize);
672 io->bufpos = io->buf;
673 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
676 io->bufsize += readsize;
681 io_write(struct io *io, const void *buf, size_t bufsize)
685 while (!io_error(io) && written < bufsize) {
688 size = write(io->pipe, buf + written, bufsize - written);
689 if (size < 0 && (errno == EAGAIN || errno == EINTR))
697 return written == bufsize;
701 io_read_buf(struct io *io, char buf[], size_t bufsize)
705 io->buf = io->bufpos = buf;
706 io->bufalloc = bufsize;
707 error = !io_get(io, '\n', TRUE) && io_error(io);
710 return done_io(io) || error;
714 run_io_buf(const char **argv, char buf[], size_t bufsize)
718 return run_io_rd(&io, argv, FORMAT_NONE) && io_read_buf(&io, buf, bufsize);
722 io_load(struct io *io, const char *separators,
723 int (*read_property)(char *, size_t, char *, size_t))
731 while (state == OK && (name = io_get(io, '\n', TRUE))) {
736 name = chomp_string(name);
737 namelen = strcspn(name, separators);
741 value = chomp_string(name + namelen + 1);
742 valuelen = strlen(value);
749 state = read_property(name, namelen, value, valuelen);
752 if (state != ERR && io_error(io))
760 run_io_load(const char **argv, const char *separators,
761 int (*read_property)(char *, size_t, char *, size_t))
765 return init_io_rd(&io, argv, NULL, FORMAT_NONE)
766 ? io_load(&io, separators, read_property) : ERR;
775 /* XXX: Keep the view request first and in sync with views[]. */ \
776 REQ_GROUP("View switching") \
777 REQ_(VIEW_MAIN, "Show main view"), \
778 REQ_(VIEW_DIFF, "Show diff view"), \
779 REQ_(VIEW_LOG, "Show log view"), \
780 REQ_(VIEW_TREE, "Show tree view"), \
781 REQ_(VIEW_BLOB, "Show blob view"), \
782 REQ_(VIEW_BLAME, "Show blame view"), \
783 REQ_(VIEW_HELP, "Show help page"), \
784 REQ_(VIEW_PAGER, "Show pager view"), \
785 REQ_(VIEW_STATUS, "Show status view"), \
786 REQ_(VIEW_STAGE, "Show stage view"), \
788 REQ_GROUP("View manipulation") \
789 REQ_(ENTER, "Enter current line and scroll"), \
790 REQ_(NEXT, "Move to next"), \
791 REQ_(PREVIOUS, "Move to previous"), \
792 REQ_(PARENT, "Move to parent"), \
793 REQ_(VIEW_NEXT, "Move focus to next view"), \
794 REQ_(REFRESH, "Reload and refresh"), \
795 REQ_(MAXIMIZE, "Maximize the current view"), \
796 REQ_(VIEW_CLOSE, "Close the current view"), \
797 REQ_(QUIT, "Close all views and quit"), \
799 REQ_GROUP("View specific requests") \
800 REQ_(STATUS_UPDATE, "Update file status"), \
801 REQ_(STATUS_REVERT, "Revert file changes"), \
802 REQ_(STATUS_MERGE, "Merge file using external tool"), \
803 REQ_(STAGE_NEXT, "Find next chunk to stage"), \
805 REQ_GROUP("Cursor navigation") \
806 REQ_(MOVE_UP, "Move cursor one line up"), \
807 REQ_(MOVE_DOWN, "Move cursor one line down"), \
808 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
809 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
810 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
811 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
813 REQ_GROUP("Scrolling") \
814 REQ_(SCROLL_LEFT, "Scroll two columns left"), \
815 REQ_(SCROLL_RIGHT, "Scroll two columns right"), \
816 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
817 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
818 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
819 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
821 REQ_GROUP("Searching") \
822 REQ_(SEARCH, "Search the view"), \
823 REQ_(SEARCH_BACK, "Search backwards in the view"), \
824 REQ_(FIND_NEXT, "Find next search match"), \
825 REQ_(FIND_PREV, "Find previous search match"), \
827 REQ_GROUP("Option manipulation") \
828 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
829 REQ_(TOGGLE_DATE, "Toggle date display"), \
830 REQ_(TOGGLE_AUTHOR, "Toggle author display"), \
831 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
832 REQ_(TOGGLE_REFS, "Toggle reference display (tags/branches)"), \
835 REQ_(PROMPT, "Bring up the prompt"), \
836 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
837 REQ_(SHOW_VERSION, "Show version information"), \
838 REQ_(STOP_LOADING, "Stop all loading views"), \
839 REQ_(EDIT, "Open in editor"), \
840 REQ_(NONE, "Do nothing")
843 /* User action requests. */
845 #define REQ_GROUP(help)
846 #define REQ_(req, help) REQ_##req
848 /* Offset all requests to avoid conflicts with ncurses getch values. */
849 REQ_OFFSET = KEY_MAX + 1,
856 struct request_info {
857 enum request request;
863 static const struct request_info req_info[] = {
864 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
865 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
872 get_request(const char *name)
874 int namelen = strlen(name);
877 for (i = 0; i < ARRAY_SIZE(req_info); i++)
878 if (req_info[i].namelen == namelen &&
879 !string_enum_compare(req_info[i].name, name, namelen))
880 return req_info[i].request;
890 /* Option and state variables. */
891 static bool opt_date = TRUE;
892 static bool opt_author = TRUE;
893 static bool opt_line_number = FALSE;
894 static bool opt_line_graphics = TRUE;
895 static bool opt_rev_graph = FALSE;
896 static bool opt_show_refs = TRUE;
897 static int opt_num_interval = NUMBER_INTERVAL;
898 static double opt_hscroll = 0.50;
899 static int opt_tab_size = TAB_SIZE;
900 static int opt_author_cols = AUTHOR_COLS-1;
901 static char opt_path[SIZEOF_STR] = "";
902 static char opt_file[SIZEOF_STR] = "";
903 static char opt_ref[SIZEOF_REF] = "";
904 static char opt_head[SIZEOF_REF] = "";
905 static char opt_head_rev[SIZEOF_REV] = "";
906 static char opt_remote[SIZEOF_REF] = "";
907 static char opt_encoding[20] = "UTF-8";
908 static bool opt_utf8 = TRUE;
909 static char opt_codeset[20] = "UTF-8";
910 static iconv_t opt_iconv = ICONV_NONE;
911 static char opt_search[SIZEOF_STR] = "";
912 static char opt_cdup[SIZEOF_STR] = "";
913 static char opt_prefix[SIZEOF_STR] = "";
914 static char opt_git_dir[SIZEOF_STR] = "";
915 static signed char opt_is_inside_work_tree = -1; /* set to TRUE or FALSE */
916 static char opt_editor[SIZEOF_STR] = "";
917 static FILE *opt_tty = NULL;
919 #define is_initial_commit() (!*opt_head_rev)
920 #define is_head_commit(rev) (!strcmp((rev), "HEAD") || !strcmp(opt_head_rev, (rev)))
924 * Line-oriented content detection.
928 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
929 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
930 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
931 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
932 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
933 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
934 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
935 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
936 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
937 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
938 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
939 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
940 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
941 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
942 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
943 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
944 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
945 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
946 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
947 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
948 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
949 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
950 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
951 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
952 LINE(AUTHOR, "author ", COLOR_GREEN, COLOR_DEFAULT, 0), \
953 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
954 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
955 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
956 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
957 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
958 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
959 LINE(DELIMITER, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
960 LINE(DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
961 LINE(MODE, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
962 LINE(LINE_NUMBER, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
963 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
964 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
965 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
966 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
967 LINE(MAIN_LOCAL_TAG,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
968 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
969 LINE(MAIN_TRACKED, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
970 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
971 LINE(MAIN_HEAD, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
972 LINE(MAIN_REVGRAPH,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
973 LINE(TREE_HEAD, "", COLOR_DEFAULT, COLOR_DEFAULT, A_BOLD), \
974 LINE(TREE_DIR, "", COLOR_YELLOW, COLOR_DEFAULT, A_NORMAL), \
975 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
976 LINE(STAT_HEAD, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
977 LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
978 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
979 LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
980 LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
981 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
982 LINE(BLAME_ID, "", COLOR_MAGENTA, COLOR_DEFAULT, 0)
985 #define LINE(type, line, fg, bg, attr) \
993 const char *name; /* Option name. */
994 int namelen; /* Size of option name. */
995 const char *line; /* The start of line to match. */
996 int linelen; /* Size of string to match. */
997 int fg, bg, attr; /* Color and text attributes for the lines. */
1000 static struct line_info line_info[] = {
1001 #define LINE(type, line, fg, bg, attr) \
1002 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1007 static enum line_type
1008 get_line_type(const char *line)
1010 int linelen = strlen(line);
1011 enum line_type type;
1013 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1014 /* Case insensitive search matches Signed-off-by lines better. */
1015 if (linelen >= line_info[type].linelen &&
1016 !strncasecmp(line_info[type].line, line, line_info[type].linelen))
1019 return LINE_DEFAULT;
1023 get_line_attr(enum line_type type)
1025 assert(type < ARRAY_SIZE(line_info));
1026 return COLOR_PAIR(type) | line_info[type].attr;
1029 static struct line_info *
1030 get_line_info(const char *name)
1032 size_t namelen = strlen(name);
1033 enum line_type type;
1035 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1036 if (namelen == line_info[type].namelen &&
1037 !string_enum_compare(line_info[type].name, name, namelen))
1038 return &line_info[type];
1046 int default_bg = line_info[LINE_DEFAULT].bg;
1047 int default_fg = line_info[LINE_DEFAULT].fg;
1048 enum line_type type;
1052 if (assume_default_colors(default_fg, default_bg) == ERR) {
1053 default_bg = COLOR_BLACK;
1054 default_fg = COLOR_WHITE;
1057 for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1058 struct line_info *info = &line_info[type];
1059 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1060 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1062 init_pair(type, fg, bg);
1067 enum line_type type;
1070 unsigned int selected:1;
1071 unsigned int dirty:1;
1072 unsigned int cleareol:1;
1074 void *data; /* User data */
1084 enum request request;
1087 static const struct keybinding default_keybindings[] = {
1088 /* View switching */
1089 { 'm', REQ_VIEW_MAIN },
1090 { 'd', REQ_VIEW_DIFF },
1091 { 'l', REQ_VIEW_LOG },
1092 { 't', REQ_VIEW_TREE },
1093 { 'f', REQ_VIEW_BLOB },
1094 { 'B', REQ_VIEW_BLAME },
1095 { 'p', REQ_VIEW_PAGER },
1096 { 'h', REQ_VIEW_HELP },
1097 { 'S', REQ_VIEW_STATUS },
1098 { 'c', REQ_VIEW_STAGE },
1100 /* View manipulation */
1101 { 'q', REQ_VIEW_CLOSE },
1102 { KEY_TAB, REQ_VIEW_NEXT },
1103 { KEY_RETURN, REQ_ENTER },
1104 { KEY_UP, REQ_PREVIOUS },
1105 { KEY_DOWN, REQ_NEXT },
1106 { 'R', REQ_REFRESH },
1107 { KEY_F(5), REQ_REFRESH },
1108 { 'O', REQ_MAXIMIZE },
1110 /* Cursor navigation */
1111 { 'k', REQ_MOVE_UP },
1112 { 'j', REQ_MOVE_DOWN },
1113 { KEY_HOME, REQ_MOVE_FIRST_LINE },
1114 { KEY_END, REQ_MOVE_LAST_LINE },
1115 { KEY_NPAGE, REQ_MOVE_PAGE_DOWN },
1116 { ' ', REQ_MOVE_PAGE_DOWN },
1117 { KEY_PPAGE, REQ_MOVE_PAGE_UP },
1118 { 'b', REQ_MOVE_PAGE_UP },
1119 { '-', REQ_MOVE_PAGE_UP },
1122 { KEY_LEFT, REQ_SCROLL_LEFT },
1123 { KEY_RIGHT, REQ_SCROLL_RIGHT },
1124 { KEY_IC, REQ_SCROLL_LINE_UP },
1125 { KEY_DC, REQ_SCROLL_LINE_DOWN },
1126 { 'w', REQ_SCROLL_PAGE_UP },
1127 { 's', REQ_SCROLL_PAGE_DOWN },
1130 { '/', REQ_SEARCH },
1131 { '?', REQ_SEARCH_BACK },
1132 { 'n', REQ_FIND_NEXT },
1133 { 'N', REQ_FIND_PREV },
1137 { 'z', REQ_STOP_LOADING },
1138 { 'v', REQ_SHOW_VERSION },
1139 { 'r', REQ_SCREEN_REDRAW },
1140 { '.', REQ_TOGGLE_LINENO },
1141 { 'D', REQ_TOGGLE_DATE },
1142 { 'A', REQ_TOGGLE_AUTHOR },
1143 { 'g', REQ_TOGGLE_REV_GRAPH },
1144 { 'F', REQ_TOGGLE_REFS },
1145 { ':', REQ_PROMPT },
1146 { 'u', REQ_STATUS_UPDATE },
1147 { '!', REQ_STATUS_REVERT },
1148 { 'M', REQ_STATUS_MERGE },
1149 { '@', REQ_STAGE_NEXT },
1150 { ',', REQ_PARENT },
1154 #define KEYMAP_INFO \
1168 #define KEYMAP_(name) KEYMAP_##name
1173 static const struct enum_map keymap_table[] = {
1174 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1179 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1181 struct keybinding_table {
1182 struct keybinding *data;
1186 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1189 add_keybinding(enum keymap keymap, enum request request, int key)
1191 struct keybinding_table *table = &keybindings[keymap];
1193 table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1195 die("Failed to allocate keybinding");
1196 table->data[table->size].alias = key;
1197 table->data[table->size++].request = request;
1200 /* Looks for a key binding first in the given map, then in the generic map, and
1201 * lastly in the default keybindings. */
1203 get_keybinding(enum keymap keymap, int key)
1207 for (i = 0; i < keybindings[keymap].size; i++)
1208 if (keybindings[keymap].data[i].alias == key)
1209 return keybindings[keymap].data[i].request;
1211 for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1212 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1213 return keybindings[KEYMAP_GENERIC].data[i].request;
1215 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1216 if (default_keybindings[i].alias == key)
1217 return default_keybindings[i].request;
1219 return (enum request) key;
1228 static const struct key key_table[] = {
1229 { "Enter", KEY_RETURN },
1231 { "Backspace", KEY_BACKSPACE },
1233 { "Escape", KEY_ESC },
1234 { "Left", KEY_LEFT },
1235 { "Right", KEY_RIGHT },
1237 { "Down", KEY_DOWN },
1238 { "Insert", KEY_IC },
1239 { "Delete", KEY_DC },
1241 { "Home", KEY_HOME },
1243 { "PageUp", KEY_PPAGE },
1244 { "PageDown", KEY_NPAGE },
1254 { "F10", KEY_F(10) },
1255 { "F11", KEY_F(11) },
1256 { "F12", KEY_F(12) },
1260 get_key_value(const char *name)
1264 for (i = 0; i < ARRAY_SIZE(key_table); i++)
1265 if (!strcasecmp(key_table[i].name, name))
1266 return key_table[i].value;
1268 if (strlen(name) == 1 && isprint(*name))
1275 get_key_name(int key_value)
1277 static char key_char[] = "'X'";
1278 const char *seq = NULL;
1281 for (key = 0; key < ARRAY_SIZE(key_table); key++)
1282 if (key_table[key].value == key_value)
1283 seq = key_table[key].name;
1287 isprint(key_value)) {
1288 key_char[1] = (char) key_value;
1292 return seq ? seq : "(no key)";
1296 get_key(enum request request)
1298 static char buf[BUFSIZ];
1305 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1306 const struct keybinding *keybinding = &default_keybindings[i];
1308 if (keybinding->request != request)
1311 if (!string_format_from(buf, &pos, "%s%s", sep,
1312 get_key_name(keybinding->alias)))
1313 return "Too many keybindings!";
1320 struct run_request {
1323 const char *argv[SIZEOF_ARG];
1326 static struct run_request *run_request;
1327 static size_t run_requests;
1330 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1332 struct run_request *req;
1334 if (argc >= ARRAY_SIZE(req->argv) - 1)
1337 req = realloc(run_request, (run_requests + 1) * sizeof(*run_request));
1342 req = &run_request[run_requests];
1343 req->keymap = keymap;
1345 req->argv[0] = NULL;
1347 if (!format_argv(req->argv, argv, FORMAT_NONE))
1350 return REQ_NONE + ++run_requests;
1353 static struct run_request *
1354 get_run_request(enum request request)
1356 if (request <= REQ_NONE)
1358 return &run_request[request - REQ_NONE - 1];
1362 add_builtin_run_requests(void)
1364 const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1365 const char *gc[] = { "git", "gc", NULL };
1372 { KEYMAP_MAIN, 'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1373 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1377 for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1380 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1381 if (req != REQ_NONE)
1382 add_keybinding(reqs[i].keymap, req, reqs[i].key);
1387 * User config file handling.
1390 static int config_lineno;
1391 static bool config_errors;
1392 static const char *config_msg;
1394 static const struct enum_map color_map[] = {
1395 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1407 static const struct enum_map attr_map[] = {
1408 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1415 ATTR_MAP(UNDERLINE),
1418 #define set_attribute(attr, name) map_enum(attr, attr_map, name)
1420 static int parse_step(double *opt, const char *arg)
1423 if (!strchr(arg, '%'))
1426 /* "Shift down" so 100% and 1 does not conflict. */
1427 *opt = (*opt - 1) / 100;
1430 config_msg = "Step value larger than 100%";
1435 config_msg = "Invalid step value";
1442 parse_int(int *opt, const char *arg, int min, int max)
1444 int value = atoi(arg);
1446 if (min <= value && value <= max) {
1451 config_msg = "Integer value out of bound";
1456 set_color(int *color, const char *name)
1458 if (map_enum(color, color_map, name))
1460 if (!prefixcmp(name, "color"))
1461 return parse_int(color, name + 5, 0, 255) == OK;
1465 /* Wants: object fgcolor bgcolor [attribute] */
1467 option_color_command(int argc, const char *argv[])
1469 struct line_info *info;
1471 if (argc != 3 && argc != 4) {
1472 config_msg = "Wrong number of arguments given to color command";
1476 info = get_line_info(argv[0]);
1478 static const struct enum_map obsolete[] = {
1479 ENUM_MAP("main-delim", LINE_DELIMITER),
1480 ENUM_MAP("main-date", LINE_DATE),
1481 ENUM_MAP("main-author", LINE_AUTHOR),
1485 if (!map_enum(&index, obsolete, argv[0])) {
1486 config_msg = "Unknown color name";
1489 info = &line_info[index];
1492 if (!set_color(&info->fg, argv[1]) ||
1493 !set_color(&info->bg, argv[2])) {
1494 config_msg = "Unknown color";
1498 if (argc == 4 && !set_attribute(&info->attr, argv[3])) {
1499 config_msg = "Unknown attribute";
1506 static int parse_bool(bool *opt, const char *arg)
1508 *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1514 parse_string(char *opt, const char *arg, size_t optsize)
1516 int arglen = strlen(arg);
1521 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1522 config_msg = "Unmatched quotation";
1525 arg += 1; arglen -= 2;
1527 string_ncopy_do(opt, optsize, arg, arglen);
1532 /* Wants: name = value */
1534 option_set_command(int argc, const char *argv[])
1537 config_msg = "Wrong number of arguments given to set command";
1541 if (strcmp(argv[1], "=")) {
1542 config_msg = "No value assigned";
1546 if (!strcmp(argv[0], "show-author"))
1547 return parse_bool(&opt_author, argv[2]);
1549 if (!strcmp(argv[0], "show-date"))
1550 return parse_bool(&opt_date, argv[2]);
1552 if (!strcmp(argv[0], "show-rev-graph"))
1553 return parse_bool(&opt_rev_graph, argv[2]);
1555 if (!strcmp(argv[0], "show-refs"))
1556 return parse_bool(&opt_show_refs, argv[2]);
1558 if (!strcmp(argv[0], "show-line-numbers"))
1559 return parse_bool(&opt_line_number, argv[2]);
1561 if (!strcmp(argv[0], "line-graphics"))
1562 return parse_bool(&opt_line_graphics, argv[2]);
1564 if (!strcmp(argv[0], "line-number-interval"))
1565 return parse_int(&opt_num_interval, argv[2], 1, 1024);
1567 if (!strcmp(argv[0], "author-width"))
1568 return parse_int(&opt_author_cols, argv[2], 0, 1024);
1570 if (!strcmp(argv[0], "horizontal-scroll"))
1571 return parse_step(&opt_hscroll, argv[2]);
1573 if (!strcmp(argv[0], "tab-size"))
1574 return parse_int(&opt_tab_size, argv[2], 1, 1024);
1576 if (!strcmp(argv[0], "commit-encoding"))
1577 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
1579 config_msg = "Unknown variable name";
1583 /* Wants: mode request key */
1585 option_bind_command(int argc, const char *argv[])
1587 enum request request;
1592 config_msg = "Wrong number of arguments given to bind command";
1596 if (set_keymap(&keymap, argv[0]) == ERR) {
1597 config_msg = "Unknown key map";
1601 key = get_key_value(argv[1]);
1603 config_msg = "Unknown key";
1607 request = get_request(argv[2]);
1608 if (request == REQ_NONE) {
1609 static const struct enum_map obsolete[] = {
1610 ENUM_MAP("cherry-pick", REQ_NONE),
1611 ENUM_MAP("screen-resize", REQ_NONE),
1612 ENUM_MAP("tree-parent", REQ_PARENT),
1616 if (map_enum(&alias, obsolete, argv[2])) {
1617 if (alias != REQ_NONE)
1618 add_keybinding(keymap, alias, key);
1619 config_msg = "Obsolete request name";
1623 if (request == REQ_NONE && *argv[2]++ == '!')
1624 request = add_run_request(keymap, key, argc - 2, argv + 2);
1625 if (request == REQ_NONE) {
1626 config_msg = "Unknown request name";
1630 add_keybinding(keymap, request, key);
1636 set_option(const char *opt, char *value)
1638 const char *argv[SIZEOF_ARG];
1641 if (!argv_from_string(argv, &argc, value)) {
1642 config_msg = "Too many option arguments";
1646 if (!strcmp(opt, "color"))
1647 return option_color_command(argc, argv);
1649 if (!strcmp(opt, "set"))
1650 return option_set_command(argc, argv);
1652 if (!strcmp(opt, "bind"))
1653 return option_bind_command(argc, argv);
1655 config_msg = "Unknown option command";
1660 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1665 config_msg = "Internal error";
1667 /* Check for comment markers, since read_properties() will
1668 * only ensure opt and value are split at first " \t". */
1669 optlen = strcspn(opt, "#");
1673 if (opt[optlen] != 0) {
1674 config_msg = "No option value";
1678 /* Look for comment endings in the value. */
1679 size_t len = strcspn(value, "#");
1681 if (len < valuelen) {
1683 value[valuelen] = 0;
1686 status = set_option(opt, value);
1689 if (status == ERR) {
1690 warn("Error on line %d, near '%.*s': %s",
1691 config_lineno, (int) optlen, opt, config_msg);
1692 config_errors = TRUE;
1695 /* Always keep going if errors are encountered. */
1700 load_option_file(const char *path)
1704 /* It's OK that the file doesn't exist. */
1705 if (!io_open(&io, path))
1709 config_errors = FALSE;
1711 if (io_load(&io, " \t", read_option) == ERR ||
1712 config_errors == TRUE)
1713 warn("Errors while loading %s.", path);
1719 const char *home = getenv("HOME");
1720 const char *tigrc_user = getenv("TIGRC_USER");
1721 const char *tigrc_system = getenv("TIGRC_SYSTEM");
1722 char buf[SIZEOF_STR];
1724 add_builtin_run_requests();
1727 tigrc_system = SYSCONFDIR "/tigrc";
1728 load_option_file(tigrc_system);
1731 if (!home || !string_format(buf, "%s/.tigrc", home))
1735 load_option_file(tigrc_user);
1748 /* The display array of active views and the index of the current view. */
1749 static struct view *display[2];
1750 static unsigned int current_view;
1752 #define foreach_displayed_view(view, i) \
1753 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1755 #define displayed_views() (display[1] != NULL ? 2 : 1)
1757 /* Current head and commit ID */
1758 static char ref_blob[SIZEOF_REF] = "";
1759 static char ref_commit[SIZEOF_REF] = "HEAD";
1760 static char ref_head[SIZEOF_REF] = "HEAD";
1763 const char *name; /* View name */
1764 const char *cmd_env; /* Command line set via environment */
1765 const char *id; /* Points to either of ref_{head,commit,blob} */
1767 struct view_ops *ops; /* View operations */
1769 enum keymap keymap; /* What keymap does this view have */
1770 bool git_dir; /* Whether the view requires a git directory. */
1772 char ref[SIZEOF_REF]; /* Hovered commit reference */
1773 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
1775 int height, width; /* The width and height of the main window */
1776 WINDOW *win; /* The main window */
1777 WINDOW *title; /* The title window living below the main window */
1780 unsigned long offset; /* Offset of the window top */
1781 unsigned long yoffset; /* Offset from the window side. */
1782 unsigned long lineno; /* Current line number */
1783 unsigned long p_offset; /* Previous offset of the window top */
1784 unsigned long p_yoffset;/* Previous offset from the window side */
1785 unsigned long p_lineno; /* Previous current line number */
1786 bool p_restore; /* Should the previous position be restored. */
1789 char grep[SIZEOF_STR]; /* Search string */
1790 regex_t *regex; /* Pre-compiled regexp */
1792 /* If non-NULL, points to the view that opened this view. If this view
1793 * is closed tig will switch back to the parent view. */
1794 struct view *parent;
1797 size_t lines; /* Total number of lines */
1798 struct line *line; /* Line index */
1799 size_t line_alloc; /* Total number of allocated lines */
1800 unsigned int digits; /* Number of digits in the lines member. */
1803 struct line *curline; /* Line currently being drawn. */
1804 enum line_type curtype; /* Attribute currently used for drawing. */
1805 unsigned long col; /* Column when drawing. */
1806 bool has_scrolled; /* View was scrolled. */
1816 /* What type of content being displayed. Used in the title bar. */
1818 /* Default command arguments. */
1820 /* Open and reads in all view content. */
1821 bool (*open)(struct view *view);
1822 /* Read one line; updates view->line. */
1823 bool (*read)(struct view *view, char *data);
1824 /* Draw one line; @lineno must be < view->height. */
1825 bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
1826 /* Depending on view handle a special requests. */
1827 enum request (*request)(struct view *view, enum request request, struct line *line);
1828 /* Search for regexp in a line. */
1829 bool (*grep)(struct view *view, struct line *line);
1831 void (*select)(struct view *view, struct line *line);
1834 static struct view_ops blame_ops;
1835 static struct view_ops blob_ops;
1836 static struct view_ops diff_ops;
1837 static struct view_ops help_ops;
1838 static struct view_ops log_ops;
1839 static struct view_ops main_ops;
1840 static struct view_ops pager_ops;
1841 static struct view_ops stage_ops;
1842 static struct view_ops status_ops;
1843 static struct view_ops tree_ops;
1845 #define VIEW_STR(name, env, ref, ops, map, git) \
1846 { name, #env, ref, ops, map, git }
1848 #define VIEW_(id, name, ops, git, ref) \
1849 VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
1852 static struct view views[] = {
1853 VIEW_(MAIN, "main", &main_ops, TRUE, ref_head),
1854 VIEW_(DIFF, "diff", &diff_ops, TRUE, ref_commit),
1855 VIEW_(LOG, "log", &log_ops, TRUE, ref_head),
1856 VIEW_(TREE, "tree", &tree_ops, TRUE, ref_commit),
1857 VIEW_(BLOB, "blob", &blob_ops, TRUE, ref_blob),
1858 VIEW_(BLAME, "blame", &blame_ops, TRUE, ref_commit),
1859 VIEW_(HELP, "help", &help_ops, FALSE, ""),
1860 VIEW_(PAGER, "pager", &pager_ops, FALSE, "stdin"),
1861 VIEW_(STATUS, "status", &status_ops, TRUE, ""),
1862 VIEW_(STAGE, "stage", &stage_ops, TRUE, ""),
1865 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
1866 #define VIEW_REQ(view) ((view) - views + REQ_OFFSET + 1)
1868 #define foreach_view(view, i) \
1869 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1871 #define view_is_displayed(view) \
1872 (view == display[0] || view == display[1])
1879 static chtype line_graphics[] = {
1880 /* LINE_GRAPHIC_VLINE: */ '|'
1884 set_view_attr(struct view *view, enum line_type type)
1886 if (!view->curline->selected && view->curtype != type) {
1887 wattrset(view->win, get_line_attr(type));
1888 wchgat(view->win, -1, 0, type, NULL);
1889 view->curtype = type;
1894 draw_chars(struct view *view, enum line_type type, const char *string,
1895 int max_len, bool use_tilde)
1899 int trimmed = FALSE;
1900 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
1906 len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde);
1908 col = len = strlen(string);
1909 if (len > max_len) {
1913 col = len = max_len;
1918 set_view_attr(view, type);
1920 waddnstr(view->win, string, len);
1921 if (trimmed && use_tilde) {
1922 set_view_attr(view, LINE_DELIMITER);
1923 waddch(view->win, '~');
1931 draw_space(struct view *view, enum line_type type, int max, int spaces)
1933 static char space[] = " ";
1936 spaces = MIN(max, spaces);
1938 while (spaces > 0) {
1939 int len = MIN(spaces, sizeof(space) - 1);
1941 col += draw_chars(view, type, space, len, FALSE);
1949 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
1951 view->col += draw_chars(view, type, string, view->width + view->yoffset - view->col, trim);
1952 return view->width + view->yoffset <= view->col;
1956 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
1958 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
1959 int max = view->width + view->yoffset - view->col;
1965 set_view_attr(view, type);
1966 /* Using waddch() instead of waddnstr() ensures that
1967 * they'll be rendered correctly for the cursor line. */
1968 for (i = skip; i < size; i++)
1969 waddch(view->win, graphic[i]);
1972 if (size < max && skip <= size)
1973 waddch(view->win, ' ');
1976 return view->width + view->yoffset <= view->col;
1980 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
1982 int max = MIN(view->width + view->yoffset - view->col, len);
1986 col = draw_chars(view, type, text, max - 1, trim);
1988 col = draw_space(view, type, max - 1, max - 1);
1991 view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
1992 return view->width + view->yoffset <= view->col;
1996 draw_date(struct view *view, time_t *time)
1998 const char *date = mkdate(time);
2000 return draw_field(view, LINE_DATE, date, DATE_COLS, FALSE);
2004 draw_author(struct view *view, const char *author)
2006 bool trim = opt_author_cols == 0 || opt_author_cols > 5 || !author;
2009 static char initials[10];
2012 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@')
2014 memset(initials, 0, sizeof(initials));
2015 for (pos = 0; *author && pos < opt_author_cols - 1; author++, pos++) {
2016 while (is_initial_sep(*author))
2018 strncpy(&initials[pos], author, sizeof(initials) - 1 - pos);
2019 while (*author && !is_initial_sep(author[1]))
2026 return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2030 draw_mode(struct view *view, mode_t mode)
2036 else if (S_ISLNK(mode))
2038 else if (S_ISGITLINK(mode))
2040 else if (S_ISREG(mode) && mode & S_IXUSR)
2042 else if (S_ISREG(mode))
2047 return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2051 draw_lineno(struct view *view, unsigned int lineno)
2054 int digits3 = view->digits < 3 ? 3 : view->digits;
2055 int max = MIN(view->width + view->yoffset - view->col, digits3);
2058 lineno += view->offset + 1;
2059 if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2060 static char fmt[] = "%1ld";
2062 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2063 if (string_format(number, fmt, lineno))
2067 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2069 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2070 return draw_graphic(view, LINE_DEFAULT, &line_graphics[LINE_GRAPHIC_VLINE], 1);
2074 draw_view_line(struct view *view, unsigned int lineno)
2077 bool selected = (view->offset + lineno == view->lineno);
2079 assert(view_is_displayed(view));
2081 if (view->offset + lineno >= view->lines)
2084 line = &view->line[view->offset + lineno];
2086 wmove(view->win, lineno, 0);
2088 wclrtoeol(view->win);
2090 view->curline = line;
2091 view->curtype = LINE_NONE;
2092 line->selected = FALSE;
2093 line->dirty = line->cleareol = 0;
2096 set_view_attr(view, LINE_CURSOR);
2097 line->selected = TRUE;
2098 view->ops->select(view, line);
2101 return view->ops->draw(view, line, lineno);
2105 redraw_view_dirty(struct view *view)
2110 for (lineno = 0; lineno < view->height; lineno++) {
2111 if (view->offset + lineno >= view->lines)
2113 if (!view->line[view->offset + lineno].dirty)
2116 if (!draw_view_line(view, lineno))
2122 wnoutrefresh(view->win);
2126 redraw_view_from(struct view *view, int lineno)
2128 assert(0 <= lineno && lineno < view->height);
2130 for (; lineno < view->height; lineno++) {
2131 if (!draw_view_line(view, lineno))
2135 wnoutrefresh(view->win);
2139 redraw_view(struct view *view)
2142 redraw_view_from(view, 0);
2147 update_view_title(struct view *view)
2149 char buf[SIZEOF_STR];
2150 char state[SIZEOF_STR];
2151 size_t bufpos = 0, statelen = 0;
2153 assert(view_is_displayed(view));
2155 if (view != VIEW(REQ_VIEW_STATUS) && view->lines) {
2156 unsigned int view_lines = view->offset + view->height;
2157 unsigned int lines = view->lines
2158 ? MIN(view_lines, view->lines) * 100 / view->lines
2161 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2170 time_t secs = time(NULL) - view->start_time;
2172 /* Three git seconds are a long time ... */
2174 string_format_from(state, &statelen, " loading %lds", secs);
2177 string_format_from(buf, &bufpos, "[%s]", view->name);
2178 if (*view->ref && bufpos < view->width) {
2179 size_t refsize = strlen(view->ref);
2180 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2182 if (minsize < view->width)
2183 refsize = view->width - minsize + 7;
2184 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2187 if (statelen && bufpos < view->width) {
2188 string_format_from(buf, &bufpos, "%s", state);
2191 if (view == display[current_view])
2192 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2194 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2196 mvwaddnstr(view->title, 0, 0, buf, bufpos);
2197 wclrtoeol(view->title);
2198 wnoutrefresh(view->title);
2202 resize_display(void)
2205 struct view *base = display[0];
2206 struct view *view = display[1] ? display[1] : display[0];
2208 /* Setup window dimensions */
2210 getmaxyx(stdscr, base->height, base->width);
2212 /* Make room for the status window. */
2216 /* Horizontal split. */
2217 view->width = base->width;
2218 view->height = SCALE_SPLIT_VIEW(base->height);
2219 base->height -= view->height;
2221 /* Make room for the title bar. */
2225 /* Make room for the title bar. */
2230 foreach_displayed_view (view, i) {
2232 view->win = newwin(view->height, 0, offset, 0);
2234 die("Failed to create %s view", view->name);
2236 scrollok(view->win, FALSE);
2238 view->title = newwin(1, 0, offset + view->height, 0);
2240 die("Failed to create title window");
2243 wresize(view->win, view->height, view->width);
2244 mvwin(view->win, offset, 0);
2245 mvwin(view->title, offset + view->height, 0);
2248 offset += view->height + 1;
2253 redraw_display(bool clear)
2258 foreach_displayed_view (view, i) {
2262 update_view_title(view);
2267 toggle_view_option(bool *option, const char *help)
2270 redraw_display(FALSE);
2271 report("%sabling %s", *option ? "En" : "Dis", help);
2275 maximize_view(struct view *view)
2277 memset(display, 0, sizeof(display));
2279 display[current_view] = view;
2281 redraw_display(FALSE);
2291 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2293 if (lineno >= view->lines)
2294 lineno = view->lines > 0 ? view->lines - 1 : 0;
2296 if (offset > lineno || offset + view->height <= lineno) {
2297 unsigned long half = view->height / 2;
2300 offset = lineno - half;
2305 if (offset != view->offset || lineno != view->lineno) {
2306 view->offset = offset;
2307 view->lineno = lineno;
2315 apply_step(double step, int value)
2319 value *= step + 0.01;
2320 return value ? value : 1;
2323 /* Scrolling backend */
2325 do_scroll_view(struct view *view, int lines)
2327 bool redraw_current_line = FALSE;
2329 /* The rendering expects the new offset. */
2330 view->offset += lines;
2332 assert(0 <= view->offset && view->offset < view->lines);
2335 /* Move current line into the view. */
2336 if (view->lineno < view->offset) {
2337 view->lineno = view->offset;
2338 redraw_current_line = TRUE;
2339 } else if (view->lineno >= view->offset + view->height) {
2340 view->lineno = view->offset + view->height - 1;
2341 redraw_current_line = TRUE;
2344 assert(view->offset <= view->lineno && view->lineno < view->lines);
2346 /* Redraw the whole screen if scrolling is pointless. */
2347 if (view->height < ABS(lines)) {
2351 int line = lines > 0 ? view->height - lines : 0;
2352 int end = line + ABS(lines);
2354 scrollok(view->win, TRUE);
2355 wscrl(view->win, lines);
2356 scrollok(view->win, FALSE);
2358 while (line < end && draw_view_line(view, line))
2361 if (redraw_current_line)
2362 draw_view_line(view, view->lineno - view->offset);
2363 wnoutrefresh(view->win);
2366 view->has_scrolled = TRUE;
2370 /* Scroll frontend */
2372 scroll_view(struct view *view, enum request request)
2376 assert(view_is_displayed(view));
2379 case REQ_SCROLL_LEFT:
2380 if (view->yoffset == 0) {
2381 report("Cannot scroll beyond the first column");
2384 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2387 view->yoffset -= apply_step(opt_hscroll, view->width);
2388 redraw_view_from(view, 0);
2391 case REQ_SCROLL_RIGHT:
2392 view->yoffset += apply_step(opt_hscroll, view->width);
2396 case REQ_SCROLL_PAGE_DOWN:
2397 lines = view->height;
2398 case REQ_SCROLL_LINE_DOWN:
2399 if (view->offset + lines > view->lines)
2400 lines = view->lines - view->offset;
2402 if (lines == 0 || view->offset + view->height >= view->lines) {
2403 report("Cannot scroll beyond the last line");
2408 case REQ_SCROLL_PAGE_UP:
2409 lines = view->height;
2410 case REQ_SCROLL_LINE_UP:
2411 if (lines > view->offset)
2412 lines = view->offset;
2415 report("Cannot scroll beyond the first line");
2423 die("request %d not handled in switch", request);
2426 do_scroll_view(view, lines);
2431 move_view(struct view *view, enum request request)
2433 int scroll_steps = 0;
2437 case REQ_MOVE_FIRST_LINE:
2438 steps = -view->lineno;
2441 case REQ_MOVE_LAST_LINE:
2442 steps = view->lines - view->lineno - 1;
2445 case REQ_MOVE_PAGE_UP:
2446 steps = view->height > view->lineno
2447 ? -view->lineno : -view->height;
2450 case REQ_MOVE_PAGE_DOWN:
2451 steps = view->lineno + view->height >= view->lines
2452 ? view->lines - view->lineno - 1 : view->height;
2464 die("request %d not handled in switch", request);
2467 if (steps <= 0 && view->lineno == 0) {
2468 report("Cannot move beyond the first line");
2471 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2472 report("Cannot move beyond the last line");
2476 /* Move the current line */
2477 view->lineno += steps;
2478 assert(0 <= view->lineno && view->lineno < view->lines);
2480 /* Check whether the view needs to be scrolled */
2481 if (view->lineno < view->offset ||
2482 view->lineno >= view->offset + view->height) {
2483 scroll_steps = steps;
2484 if (steps < 0 && -steps > view->offset) {
2485 scroll_steps = -view->offset;
2487 } else if (steps > 0) {
2488 if (view->lineno == view->lines - 1 &&
2489 view->lines > view->height) {
2490 scroll_steps = view->lines - view->offset - 1;
2491 if (scroll_steps >= view->height)
2492 scroll_steps -= view->height - 1;
2497 if (!view_is_displayed(view)) {
2498 view->offset += scroll_steps;
2499 assert(0 <= view->offset && view->offset < view->lines);
2500 view->ops->select(view, &view->line[view->lineno]);
2504 /* Repaint the old "current" line if we be scrolling */
2505 if (ABS(steps) < view->height)
2506 draw_view_line(view, view->lineno - steps - view->offset);
2509 do_scroll_view(view, scroll_steps);
2513 /* Draw the current line */
2514 draw_view_line(view, view->lineno - view->offset);
2516 wnoutrefresh(view->win);
2525 static void search_view(struct view *view, enum request request);
2528 grep_text(struct view *view, const char *text[])
2533 for (i = 0; text[i]; i++)
2535 regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
2541 select_view_line(struct view *view, unsigned long lineno)
2543 unsigned long old_lineno = view->lineno;
2544 unsigned long old_offset = view->offset;
2546 if (goto_view_line(view, view->offset, lineno)) {
2547 if (view_is_displayed(view)) {
2548 if (old_offset != view->offset) {
2551 draw_view_line(view, old_lineno - view->offset);
2552 draw_view_line(view, view->lineno - view->offset);
2553 wnoutrefresh(view->win);
2556 view->ops->select(view, &view->line[view->lineno]);
2562 find_next(struct view *view, enum request request)
2564 unsigned long lineno = view->lineno;
2569 report("No previous search");
2571 search_view(view, request);
2581 case REQ_SEARCH_BACK:
2590 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2591 lineno += direction;
2593 /* Note, lineno is unsigned long so will wrap around in which case it
2594 * will become bigger than view->lines. */
2595 for (; lineno < view->lines; lineno += direction) {
2596 if (view->ops->grep(view, &view->line[lineno])) {
2597 select_view_line(view, lineno);
2598 report("Line %ld matches '%s'", lineno + 1, view->grep);
2603 report("No match found for '%s'", view->grep);
2607 search_view(struct view *view, enum request request)
2612 regfree(view->regex);
2615 view->regex = calloc(1, sizeof(*view->regex));
2620 regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2621 if (regex_err != 0) {
2622 char buf[SIZEOF_STR] = "unknown error";
2624 regerror(regex_err, view->regex, buf, sizeof(buf));
2625 report("Search failed: %s", buf);
2629 string_copy(view->grep, opt_search);
2631 find_next(view, request);
2635 * Incremental updating
2639 reset_view(struct view *view)
2643 for (i = 0; i < view->lines; i++)
2644 free(view->line[i].data);
2647 view->p_offset = view->offset;
2648 view->p_yoffset = view->yoffset;
2649 view->p_lineno = view->lineno;
2656 view->line_alloc = 0;
2658 view->update_secs = 0;
2662 free_argv(const char *argv[])
2666 for (argc = 0; argv[argc]; argc++)
2667 free((void *) argv[argc]);
2671 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
2673 char buf[SIZEOF_STR];
2675 bool noreplace = flags == FORMAT_NONE;
2677 free_argv(dst_argv);
2679 for (argc = 0; src_argv[argc]; argc++) {
2680 const char *arg = src_argv[argc];
2684 char *next = strstr(arg, "%(");
2685 int len = next - arg;
2688 if (!next || noreplace) {
2689 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
2694 } else if (!prefixcmp(next, "%(directory)")) {
2697 } else if (!prefixcmp(next, "%(file)")) {
2700 } else if (!prefixcmp(next, "%(ref)")) {
2701 value = *opt_ref ? opt_ref : "HEAD";
2703 } else if (!prefixcmp(next, "%(head)")) {
2706 } else if (!prefixcmp(next, "%(commit)")) {
2709 } else if (!prefixcmp(next, "%(blob)")) {
2713 report("Unknown replacement: `%s`", next);
2717 if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
2720 arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
2723 dst_argv[argc] = strdup(buf);
2724 if (!dst_argv[argc])
2728 dst_argv[argc] = NULL;
2730 return src_argv[argc] == NULL;
2734 restore_view_position(struct view *view)
2736 if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
2739 /* Changing the view position cancels the restoring. */
2740 /* FIXME: Changing back to the first line is not detected. */
2741 if (view->offset != 0 || view->lineno != 0) {
2742 view->p_restore = FALSE;
2746 if (goto_view_line(view, view->p_offset, view->p_lineno) &&
2747 view_is_displayed(view))
2750 view->yoffset = view->p_yoffset;
2751 view->p_restore = FALSE;
2757 end_update(struct view *view, bool force)
2761 while (!view->ops->read(view, NULL))
2764 set_nonblocking_input(FALSE);
2766 kill_io(view->pipe);
2767 done_io(view->pipe);
2772 setup_update(struct view *view, const char *vid)
2774 set_nonblocking_input(TRUE);
2776 string_copy_rev(view->vid, vid);
2777 view->pipe = &view->io;
2778 view->start_time = time(NULL);
2782 prepare_update(struct view *view, const char *argv[], const char *dir,
2783 enum format_flags flags)
2786 end_update(view, TRUE);
2787 return init_io_rd(&view->io, argv, dir, flags);
2791 prepare_update_file(struct view *view, const char *name)
2794 end_update(view, TRUE);
2795 return io_open(&view->io, name);
2799 begin_update(struct view *view, bool refresh)
2802 end_update(view, TRUE);
2805 if (!start_io(&view->io))
2809 if (view == VIEW(REQ_VIEW_TREE) && strcmp(view->vid, view->id))
2812 if (!run_io_rd(&view->io, view->ops->argv, FORMAT_ALL))
2815 /* Put the current ref_* value to the view title ref
2816 * member. This is needed by the blob view. Most other
2817 * views sets it automatically after loading because the
2818 * first line is a commit line. */
2819 string_copy_rev(view->ref, view->id);
2822 setup_update(view, view->id);
2828 update_view(struct view *view)
2830 char out_buffer[BUFSIZ * 2];
2832 /* Clear the view and redraw everything since the tree sorting
2833 * might have rearranged things. */
2834 bool redraw = view->lines == 0;
2835 bool can_read = TRUE;
2840 if (!io_can_read(view->pipe)) {
2841 if (view->lines == 0 && view_is_displayed(view)) {
2842 time_t secs = time(NULL) - view->start_time;
2844 if (secs > 1 && secs > view->update_secs) {
2845 if (view->update_secs == 0)
2847 update_view_title(view);
2848 view->update_secs = secs;
2854 for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
2855 if (opt_iconv != ICONV_NONE) {
2856 ICONV_CONST char *inbuf = line;
2857 size_t inlen = strlen(line) + 1;
2859 char *outbuf = out_buffer;
2860 size_t outlen = sizeof(out_buffer);
2864 ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2865 if (ret != (size_t) -1)
2869 if (!view->ops->read(view, line)) {
2870 report("Allocation failure");
2871 end_update(view, TRUE);
2877 unsigned long lines = view->lines;
2880 for (digits = 0; lines; digits++)
2883 /* Keep the displayed view in sync with line number scaling. */
2884 if (digits != view->digits) {
2885 view->digits = digits;
2886 if (opt_line_number || view == VIEW(REQ_VIEW_BLAME))
2891 if (io_error(view->pipe)) {
2892 report("Failed to read: %s", io_strerror(view->pipe));
2893 end_update(view, TRUE);
2895 } else if (io_eof(view->pipe)) {
2897 end_update(view, FALSE);
2900 if (restore_view_position(view))
2903 if (!view_is_displayed(view))
2907 redraw_view_from(view, 0);
2909 redraw_view_dirty(view);
2911 /* Update the title _after_ the redraw so that if the redraw picks up a
2912 * commit reference in view->ref it'll be available here. */
2913 update_view_title(view);
2917 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
2919 static struct line *
2920 add_line_data(struct view *view, void *data, enum line_type type)
2924 if (!realloc_lines(&view->line, &view->line_alloc, view->lines + 1))
2927 line = &view->line[view->lines++];
2928 memset(line, 0, sizeof(*line));
2936 static struct line *
2937 add_line_text(struct view *view, const char *text, enum line_type type)
2939 char *data = text ? strdup(text) : NULL;
2941 return data ? add_line_data(view, data, type) : NULL;
2944 static struct line *
2945 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
2947 char buf[SIZEOF_STR];
2950 va_start(args, fmt);
2951 if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
2955 return buf[0] ? add_line_text(view, buf, type) : NULL;
2963 OPEN_DEFAULT = 0, /* Use default view switching. */
2964 OPEN_SPLIT = 1, /* Split current view. */
2965 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
2966 OPEN_REFRESH = 16, /* Refresh view using previous command. */
2967 OPEN_PREPARED = 32, /* Open already prepared command. */
2971 open_view(struct view *prev, enum request request, enum open_flags flags)
2973 bool split = !!(flags & OPEN_SPLIT);
2974 bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
2975 bool nomaximize = !!(flags & OPEN_REFRESH);
2976 struct view *view = VIEW(request);
2977 int nviews = displayed_views();
2978 struct view *base_view = display[0];
2980 if (view == prev && nviews == 1 && !reload) {
2981 report("Already in %s view", view->name);
2985 if (view->git_dir && !opt_git_dir[0]) {
2986 report("The %s view is disabled in pager view", view->name);
2993 } else if (!nomaximize) {
2994 /* Maximize the current view. */
2995 memset(display, 0, sizeof(display));
2997 display[current_view] = view;
3000 /* Resize the view when switching between split- and full-screen,
3001 * or when switching between two different full-screen views. */
3002 if (nviews != displayed_views() ||
3003 (nviews == 1 && base_view != display[0]))
3006 if (view->ops->open) {
3008 end_update(view, TRUE);
3009 if (!view->ops->open(view)) {
3010 report("Failed to load %s view", view->name);
3013 restore_view_position(view);
3015 } else if ((reload || strcmp(view->vid, view->id)) &&
3016 !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3017 report("Failed to load %s view", view->name);
3021 if (split && prev->lineno - prev->offset >= prev->height) {
3022 /* Take the title line into account. */
3023 int lines = prev->lineno - prev->offset - prev->height + 1;
3025 /* Scroll the view that was split if the current line is
3026 * outside the new limited view. */
3027 do_scroll_view(prev, lines);
3030 if (prev && view != prev) {
3032 /* "Blur" the previous view. */
3033 update_view_title(prev);
3036 view->parent = prev;
3039 if (view->pipe && view->lines == 0) {
3040 /* Clear the old view and let the incremental updating refill
3043 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3045 } else if (view_is_displayed(view)) {
3052 open_external_viewer(const char *argv[], const char *dir)
3054 def_prog_mode(); /* save current tty modes */
3055 endwin(); /* restore original tty modes */
3056 run_io_fg(argv, dir);
3057 fprintf(stderr, "Press Enter to continue");
3060 redraw_display(TRUE);
3064 open_mergetool(const char *file)
3066 const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3068 open_external_viewer(mergetool_argv, opt_cdup);
3072 open_editor(bool from_root, const char *file)
3074 const char *editor_argv[] = { "vi", file, NULL };
3077 editor = getenv("GIT_EDITOR");
3078 if (!editor && *opt_editor)
3079 editor = opt_editor;
3081 editor = getenv("VISUAL");
3083 editor = getenv("EDITOR");
3087 editor_argv[0] = editor;
3088 open_external_viewer(editor_argv, from_root ? opt_cdup : NULL);
3092 open_run_request(enum request request)
3094 struct run_request *req = get_run_request(request);
3095 const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3098 report("Unknown run request");
3102 if (format_argv(argv, req->argv, FORMAT_ALL))
3103 open_external_viewer(argv, NULL);
3108 * User request switch noodle
3112 view_driver(struct view *view, enum request request)
3116 if (request == REQ_NONE)
3119 if (request > REQ_NONE) {
3120 open_run_request(request);
3121 /* FIXME: When all views can refresh always do this. */
3122 if (view == VIEW(REQ_VIEW_STATUS) ||
3123 view == VIEW(REQ_VIEW_MAIN) ||
3124 view == VIEW(REQ_VIEW_LOG) ||
3125 view == VIEW(REQ_VIEW_STAGE))
3126 request = REQ_REFRESH;
3131 if (view && view->lines) {
3132 request = view->ops->request(view, request, &view->line[view->lineno]);
3133 if (request == REQ_NONE)
3140 case REQ_MOVE_PAGE_UP:
3141 case REQ_MOVE_PAGE_DOWN:
3142 case REQ_MOVE_FIRST_LINE:
3143 case REQ_MOVE_LAST_LINE:
3144 move_view(view, request);
3147 case REQ_SCROLL_LEFT:
3148 case REQ_SCROLL_RIGHT:
3149 case REQ_SCROLL_LINE_DOWN:
3150 case REQ_SCROLL_LINE_UP:
3151 case REQ_SCROLL_PAGE_DOWN:
3152 case REQ_SCROLL_PAGE_UP:
3153 scroll_view(view, request);
3156 case REQ_VIEW_BLAME:
3158 report("No file chosen, press %s to open tree view",
3159 get_key(REQ_VIEW_TREE));
3162 open_view(view, request, OPEN_DEFAULT);
3167 report("No file chosen, press %s to open tree view",
3168 get_key(REQ_VIEW_TREE));
3171 open_view(view, request, OPEN_DEFAULT);
3174 case REQ_VIEW_PAGER:
3175 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3176 report("No pager content, press %s to run command from prompt",
3177 get_key(REQ_PROMPT));
3180 open_view(view, request, OPEN_DEFAULT);
3183 case REQ_VIEW_STAGE:
3184 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3185 report("No stage content, press %s to open the status view and choose file",
3186 get_key(REQ_VIEW_STATUS));
3189 open_view(view, request, OPEN_DEFAULT);
3192 case REQ_VIEW_STATUS:
3193 if (opt_is_inside_work_tree == FALSE) {
3194 report("The status view requires a working tree");
3197 open_view(view, request, OPEN_DEFAULT);
3205 open_view(view, request, OPEN_DEFAULT);
3210 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3212 if ((view == VIEW(REQ_VIEW_DIFF) &&
3213 view->parent == VIEW(REQ_VIEW_MAIN)) ||
3214 (view == VIEW(REQ_VIEW_DIFF) &&
3215 view->parent == VIEW(REQ_VIEW_BLAME)) ||
3216 (view == VIEW(REQ_VIEW_STAGE) &&
3217 view->parent == VIEW(REQ_VIEW_STATUS)) ||
3218 (view == VIEW(REQ_VIEW_BLOB) &&
3219 view->parent == VIEW(REQ_VIEW_TREE))) {
3222 view = view->parent;
3223 line = view->lineno;
3224 move_view(view, request);
3225 if (view_is_displayed(view))
3226 update_view_title(view);
3227 if (line != view->lineno)
3228 view->ops->request(view, REQ_ENTER,
3229 &view->line[view->lineno]);
3232 move_view(view, request);
3238 int nviews = displayed_views();
3239 int next_view = (current_view + 1) % nviews;
3241 if (next_view == current_view) {
3242 report("Only one view is displayed");
3246 current_view = next_view;
3247 /* Blur out the title of the previous view. */
3248 update_view_title(view);
3253 report("Refreshing is not yet supported for the %s view", view->name);
3257 if (displayed_views() == 2)
3258 maximize_view(view);
3261 case REQ_TOGGLE_LINENO:
3262 toggle_view_option(&opt_line_number, "line numbers");
3265 case REQ_TOGGLE_DATE:
3266 toggle_view_option(&opt_date, "date display");
3269 case REQ_TOGGLE_AUTHOR:
3270 toggle_view_option(&opt_author, "author display");
3273 case REQ_TOGGLE_REV_GRAPH:
3274 toggle_view_option(&opt_rev_graph, "revision graph display");
3277 case REQ_TOGGLE_REFS:
3278 toggle_view_option(&opt_show_refs, "reference display");
3282 case REQ_SEARCH_BACK:
3283 search_view(view, request);
3288 find_next(view, request);
3291 case REQ_STOP_LOADING:
3292 for (i = 0; i < ARRAY_SIZE(views); i++) {
3295 report("Stopped loading the %s view", view->name),
3296 end_update(view, TRUE);
3300 case REQ_SHOW_VERSION:
3301 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3304 case REQ_SCREEN_REDRAW:
3305 redraw_display(TRUE);
3309 report("Nothing to edit");
3313 report("Nothing to enter");
3316 case REQ_VIEW_CLOSE:
3317 /* XXX: Mark closed views by letting view->parent point to the
3318 * view itself. Parents to closed view should never be
3321 view->parent->parent != view->parent) {
3322 maximize_view(view->parent);
3323 view->parent = view;
3331 report("Unknown key, press 'h' for help");
3340 * View backend utilities
3343 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3345 /* Small author cache to reduce memory consumption. It uses binary
3346 * search to lookup or find place to position new entries. No entries
3347 * are ever freed. */
3349 get_author(const char *name)
3351 static const char **authors;
3352 static size_t authors_alloc;
3353 static size_t authors_size;
3354 int from = 0, to = authors_size - 1;
3356 while (from <= to) {
3357 size_t pos = (to + from) / 2;
3358 int cmp = strcmp(name, authors[pos]);
3361 return authors[pos];
3369 if (!realloc_authors(&authors, &authors_alloc, authors_size + 1))
3371 name = strdup(name);
3375 memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3376 authors[from] = name;
3383 parse_timezone(time_t *time, const char *zone)
3387 tz = ('0' - zone[1]) * 60 * 60 * 10;
3388 tz += ('0' - zone[2]) * 60 * 60;
3389 tz += ('0' - zone[3]) * 60;
3390 tz += ('0' - zone[4]);
3398 /* Parse author lines where the name may be empty:
3399 * author <email@address.tld> 1138474660 +0100
3402 parse_author_line(char *ident, const char **author, time_t *time)
3404 char *nameend = strchr(ident, '<');
3405 char *emailend = strchr(ident, '>');
3407 if (nameend && emailend)
3408 *nameend = *emailend = 0;
3409 ident = chomp_string(ident);
3412 ident = chomp_string(nameend + 1);
3417 *author = get_author(ident);
3419 /* Parse epoch and timezone */
3420 if (emailend && emailend[1] == ' ') {
3421 char *secs = emailend + 2;
3422 char *zone = strchr(secs, ' ');
3424 *time = (time_t) atol(secs);
3426 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
3427 parse_timezone(time, zone + 1);
3431 static enum input_status
3432 select_commit_parent_handler(void *data, char *buf, int c)
3434 size_t parents = *(size_t *) data;
3441 parent = atoi(buf) * 10;
3444 if (parent > parents)
3450 select_commit_parent(const char *id, char rev[SIZEOF_REV], const char *path)
3452 char buf[SIZEOF_STR * 4];
3453 const char *revlist_argv[] = {
3454 "git", "rev-list", "-1", "--parents", id, "--", path, NULL
3458 if (!run_io_buf(revlist_argv, buf, sizeof(buf)) ||
3459 !*chomp_string(buf) ||
3460 (parents = (strlen(buf) / 40) - 1) < 0) {
3461 report("Failed to get parent information");
3464 } else if (parents == 0) {
3466 report("Path '%s' does not exist in the parent", path);
3468 report("The selected commit has no parents");
3473 char prompt[SIZEOF_STR];
3476 if (!string_format(prompt, "Which parent? [1..%d] ", parents))
3478 result = prompt_input(prompt, select_commit_parent_handler, &parents);
3481 parents = atoi(result);
3484 string_copy_rev(rev, &buf[41 * parents]);
3493 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3495 char text[SIZEOF_STR];
3497 if (opt_line_number && draw_lineno(view, lineno))
3500 string_expand(text, sizeof(text), line->data, opt_tab_size);
3501 draw_text(view, line->type, text, TRUE);
3506 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3508 const char *describe_argv[] = { "git", "describe", commit_id, NULL };
3509 char refbuf[SIZEOF_STR];
3512 if (run_io_buf(describe_argv, refbuf, sizeof(refbuf)))
3513 ref = chomp_string(refbuf);
3518 /* This is the only fatal call, since it can "corrupt" the buffer. */
3519 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
3526 add_pager_refs(struct view *view, struct line *line)
3528 char buf[SIZEOF_STR];
3529 char *commit_id = (char *)line->data + STRING_SIZE("commit ");
3531 size_t bufpos = 0, refpos = 0;
3532 const char *sep = "Refs: ";
3533 bool is_tag = FALSE;
3535 assert(line->type == LINE_COMMIT);
3537 refs = get_refs(commit_id);
3539 if (view == VIEW(REQ_VIEW_DIFF))
3540 goto try_add_describe_ref;
3545 struct ref *ref = refs[refpos];
3546 const char *fmt = ref->tag ? "%s[%s]" :
3547 ref->remote ? "%s<%s>" : "%s%s";
3549 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
3554 } while (refs[refpos++]->next);
3556 if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
3557 try_add_describe_ref:
3558 /* Add <tag>-g<commit_id> "fake" reference. */
3559 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
3566 add_line_text(view, buf, LINE_PP_REFS);
3570 pager_read(struct view *view, char *data)
3577 line = add_line_text(view, data, get_line_type(data));
3581 if (line->type == LINE_COMMIT &&
3582 (view == VIEW(REQ_VIEW_DIFF) ||
3583 view == VIEW(REQ_VIEW_LOG)))
3584 add_pager_refs(view, line);
3590 pager_request(struct view *view, enum request request, struct line *line)
3594 if (request != REQ_ENTER)
3597 if (line->type == LINE_COMMIT &&
3598 (view == VIEW(REQ_VIEW_LOG) ||
3599 view == VIEW(REQ_VIEW_PAGER))) {
3600 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3604 /* Always scroll the view even if it was split. That way
3605 * you can use Enter to scroll through the log view and
3606 * split open each commit diff. */
3607 scroll_view(view, REQ_SCROLL_LINE_DOWN);
3609 /* FIXME: A minor workaround. Scrolling the view will call report("")
3610 * but if we are scrolling a non-current view this won't properly
3611 * update the view title. */
3613 update_view_title(view);
3619 pager_grep(struct view *view, struct line *line)
3621 const char *text[] = { line->data, NULL };
3623 return grep_text(view, text);
3627 pager_select(struct view *view, struct line *line)
3629 if (line->type == LINE_COMMIT) {
3630 char *text = (char *)line->data + STRING_SIZE("commit ");
3632 if (view != VIEW(REQ_VIEW_PAGER))
3633 string_copy_rev(view->ref, text);
3634 string_copy_rev(ref_commit, text);
3638 static struct view_ops pager_ops = {
3649 static const char *log_argv[SIZEOF_ARG] = {
3650 "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3654 log_request(struct view *view, enum request request, struct line *line)
3659 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
3662 return pager_request(view, request, line);
3666 static struct view_ops log_ops = {
3677 static const char *diff_argv[SIZEOF_ARG] = {
3678 "git", "show", "--pretty=fuller", "--no-color", "--root",
3679 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
3682 static struct view_ops diff_ops = {
3698 help_open(struct view *view)
3700 char buf[SIZEOF_STR];
3704 if (view->lines > 0)
3707 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
3709 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
3712 if (req_info[i].request == REQ_NONE)
3715 if (!req_info[i].request) {
3716 add_line_text(view, "", LINE_DEFAULT);
3717 add_line_text(view, req_info[i].help, LINE_DEFAULT);
3721 key = get_key(req_info[i].request);
3723 key = "(no key defined)";
3725 for (bufpos = 0; bufpos <= req_info[i].namelen; bufpos++) {
3726 buf[bufpos] = tolower(req_info[i].name[bufpos]);
3727 if (buf[bufpos] == '_')
3731 add_line_format(view, LINE_DEFAULT, " %-25s %-20s %s",
3732 key, buf, req_info[i].help);
3736 add_line_text(view, "", LINE_DEFAULT);
3737 add_line_text(view, "External commands:", LINE_DEFAULT);
3740 for (i = 0; i < run_requests; i++) {
3741 struct run_request *req = get_run_request(REQ_NONE + i + 1);
3748 key = get_key_name(req->key);
3750 key = "(no key defined)";
3752 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
3753 if (!string_format_from(buf, &bufpos, "%s%s",
3754 argc ? " " : "", req->argv[argc]))
3757 add_line_format(view, LINE_DEFAULT, " %-10s %-14s `%s`",
3758 keymap_table[req->keymap].name, key, buf);
3764 static struct view_ops help_ops = {
3780 struct tree_stack_entry {
3781 struct tree_stack_entry *prev; /* Entry below this in the stack */
3782 unsigned long lineno; /* Line number to restore */
3783 char *name; /* Position of name in opt_path */
3786 /* The top of the path stack. */
3787 static struct tree_stack_entry *tree_stack = NULL;
3788 unsigned long tree_lineno = 0;
3791 pop_tree_stack_entry(void)
3793 struct tree_stack_entry *entry = tree_stack;
3795 tree_lineno = entry->lineno;
3797 tree_stack = entry->prev;
3802 push_tree_stack_entry(const char *name, unsigned long lineno)
3804 struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
3805 size_t pathlen = strlen(opt_path);
3810 entry->prev = tree_stack;
3811 entry->name = opt_path + pathlen;
3814 if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
3815 pop_tree_stack_entry();
3819 /* Move the current line to the first tree entry. */
3821 entry->lineno = lineno;
3824 /* Parse output from git-ls-tree(1):
3826 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
3829 #define SIZEOF_TREE_ATTR \
3830 STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
3832 #define SIZEOF_TREE_MODE \
3833 STRING_SIZE("100644 ")
3835 #define TREE_ID_OFFSET \
3836 STRING_SIZE("100644 blob ")
3839 char id[SIZEOF_REV];
3841 time_t time; /* Date from the author ident. */
3842 const char *author; /* Author of the commit. */
3847 tree_path(struct line *line)
3849 return ((struct tree_entry *) line->data)->name;
3854 tree_compare_entry(struct line *line1, struct line *line2)
3856 if (line1->type != line2->type)
3857 return line1->type == LINE_TREE_DIR ? -1 : 1;
3858 return strcmp(tree_path(line1), tree_path(line2));
3861 static struct line *
3862 tree_entry(struct view *view, enum line_type type, const char *path,
3863 const char *mode, const char *id)
3865 struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
3866 struct line *line = entry ? add_line_data(view, entry, type) : NULL;
3868 if (!entry || !line) {
3873 strncpy(entry->name, path, strlen(path));
3875 entry->mode = strtoul(mode, NULL, 8);
3877 string_copy_rev(entry->id, id);
3883 tree_read_date(struct view *view, char *text, bool *read_date)
3885 static const char *author_name;
3886 static time_t author_time;
3888 if (!text && *read_date) {
3893 char *path = *opt_path ? opt_path : ".";
3894 /* Find next entry to process */
3895 const char *log_file[] = {
3896 "git", "log", "--no-color", "--pretty=raw",
3897 "--cc", "--raw", view->id, "--", path, NULL
3902 tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
3903 report("Tree is empty");
3907 if (!run_io_rd(&io, log_file, FORMAT_NONE)) {
3908 report("Failed to load tree data");
3912 done_io(view->pipe);
3917 } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
3918 parse_author_line(text + STRING_SIZE("author "),
3919 &author_name, &author_time);
3921 } else if (*text == ':') {
3923 size_t annotated = 1;
3926 pos = strchr(text, '\t');
3930 if (*opt_prefix && !strncmp(text, opt_prefix, strlen(opt_prefix)))
3931 text += strlen(opt_prefix);
3932 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
3933 text += strlen(opt_path);
3934 pos = strchr(text, '/');
3938 for (i = 1; i < view->lines; i++) {
3939 struct line *line = &view->line[i];
3940 struct tree_entry *entry = line->data;
3942 annotated += !!entry->author;
3943 if (entry->author || strcmp(entry->name, text))
3946 entry->author = author_name;
3947 entry->time = author_time;
3952 if (annotated == view->lines)
3953 kill_io(view->pipe);
3959 tree_read(struct view *view, char *text)
3961 static bool read_date = FALSE;
3962 struct tree_entry *data;
3963 struct line *entry, *line;
3964 enum line_type type;
3965 size_t textlen = text ? strlen(text) : 0;
3966 char *path = text + SIZEOF_TREE_ATTR;
3968 if (read_date || !text)
3969 return tree_read_date(view, text, &read_date);
3971 if (textlen <= SIZEOF_TREE_ATTR)
3973 if (view->lines == 0 &&
3974 !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
3977 /* Strip the path part ... */
3979 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
3980 size_t striplen = strlen(opt_path);
3982 if (pathlen > striplen)
3983 memmove(path, path + striplen,
3984 pathlen - striplen + 1);
3986 /* Insert "link" to parent directory. */
3987 if (view->lines == 1 &&
3988 !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
3992 type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
3993 entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
3998 /* Skip "Directory ..." and ".." line. */
3999 for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
4000 if (tree_compare_entry(line, entry) <= 0)
4003 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4007 for (; line <= entry; line++)
4008 line->dirty = line->cleareol = 1;
4012 if (tree_lineno > view->lineno) {
4013 view->lineno = tree_lineno;
4021 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4023 struct tree_entry *entry = line->data;
4025 if (line->type == LINE_TREE_HEAD) {
4026 if (draw_text(view, line->type, "Directory path /", TRUE))
4029 if (draw_mode(view, entry->mode))
4032 if (opt_author && draw_author(view, entry->author))
4035 if (opt_date && draw_date(view, entry->author ? &entry->time : NULL))
4038 if (draw_text(view, line->type, entry->name, TRUE))
4046 char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4047 int fd = mkstemp(file);
4050 report("Failed to create temporary file");
4051 else if (!run_io_append(blob_ops.argv, FORMAT_ALL, fd))
4052 report("Failed to save blob data to file");
4054 open_editor(FALSE, file);
4060 tree_request(struct view *view, enum request request, struct line *line)
4062 enum open_flags flags;
4065 case REQ_VIEW_BLAME:
4066 if (line->type != LINE_TREE_FILE) {
4067 report("Blame only supported for files");
4071 string_copy(opt_ref, view->vid);
4075 if (line->type != LINE_TREE_FILE) {
4076 report("Edit only supported for files");
4077 } else if (!is_head_commit(view->vid)) {
4080 open_editor(TRUE, opt_file);
4086 /* quit view if at top of tree */
4087 return REQ_VIEW_CLOSE;
4090 line = &view->line[1];
4100 /* Cleanup the stack if the tree view is at a different tree. */
4101 while (!*opt_path && tree_stack)
4102 pop_tree_stack_entry();
4104 switch (line->type) {
4106 /* Depending on whether it is a subdirectory or parent link
4107 * mangle the path buffer. */
4108 if (line == &view->line[1] && *opt_path) {
4109 pop_tree_stack_entry();
4112 const char *basename = tree_path(line);
4114 push_tree_stack_entry(basename, view->lineno);
4117 /* Trees and subtrees share the same ID, so they are not not
4118 * unique like blobs. */
4119 flags = OPEN_RELOAD;
4120 request = REQ_VIEW_TREE;
4123 case LINE_TREE_FILE:
4124 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4125 request = REQ_VIEW_BLOB;
4132 open_view(view, request, flags);
4133 if (request == REQ_VIEW_TREE)
4134 view->lineno = tree_lineno;
4140 tree_grep(struct view *view, struct line *line)
4142 struct tree_entry *entry = line->data;
4143 const char *text[] = {
4145 opt_author ? entry->author : "",
4146 opt_date ? mkdate(&entry->time) : "",
4150 return grep_text(view, text);
4154 tree_select(struct view *view, struct line *line)
4156 struct tree_entry *entry = line->data;
4158 if (line->type == LINE_TREE_FILE) {
4159 string_copy_rev(ref_blob, entry->id);
4160 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4162 } else if (line->type != LINE_TREE_DIR) {
4166 string_copy_rev(view->ref, entry->id);
4169 static const char *tree_argv[SIZEOF_ARG] = {
4170 "git", "ls-tree", "%(commit)", "%(directory)", NULL
4173 static struct view_ops tree_ops = {
4185 blob_read(struct view *view, char *line)
4189 return add_line_text(view, line, LINE_DEFAULT) != NULL;
4193 blob_request(struct view *view, enum request request, struct line *line)
4200 return pager_request(view, request, line);
4204 static const char *blob_argv[SIZEOF_ARG] = {
4205 "git", "cat-file", "blob", "%(blob)", NULL
4208 static struct view_ops blob_ops = {
4222 * Loading the blame view is a two phase job:
4224 * 1. File content is read either using opt_file from the
4225 * filesystem or using git-cat-file.
4226 * 2. Then blame information is incrementally added by
4227 * reading output from git-blame.
4230 static const char *blame_head_argv[] = {
4231 "git", "blame", "--incremental", "--", "%(file)", NULL
4234 static const char *blame_ref_argv[] = {
4235 "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4238 static const char *blame_cat_file_argv[] = {
4239 "git", "cat-file", "blob", "%(ref):%(file)", NULL
4242 struct blame_commit {
4243 char id[SIZEOF_REV]; /* SHA1 ID. */
4244 char title[128]; /* First line of the commit message. */
4245 const char *author; /* Author of the commit. */
4246 time_t time; /* Date from the author ident. */
4247 char filename[128]; /* Name of file. */
4248 bool has_previous; /* Was a "previous" line detected. */
4252 struct blame_commit *commit;
4253 unsigned long lineno;
4258 blame_open(struct view *view)
4260 if (*opt_ref || !io_open(&view->io, opt_file)) {
4261 if (!run_io_rd(&view->io, blame_cat_file_argv, FORMAT_ALL))
4265 setup_update(view, opt_file);
4266 string_format(view->ref, "%s ...", opt_file);
4271 static struct blame_commit *
4272 get_blame_commit(struct view *view, const char *id)
4276 for (i = 0; i < view->lines; i++) {
4277 struct blame *blame = view->line[i].data;
4282 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4283 return blame->commit;
4287 struct blame_commit *commit = calloc(1, sizeof(*commit));
4290 string_ncopy(commit->id, id, SIZEOF_REV);
4296 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4298 const char *pos = *posref;
4301 pos = strchr(pos + 1, ' ');
4302 if (!pos || !isdigit(pos[1]))
4304 *number = atoi(pos + 1);
4305 if (*number < min || *number > max)
4312 static struct blame_commit *
4313 parse_blame_commit(struct view *view, const char *text, int *blamed)
4315 struct blame_commit *commit;
4316 struct blame *blame;
4317 const char *pos = text + SIZEOF_REV - 2;
4318 size_t orig_lineno = 0;
4322 if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
4325 if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
4326 !parse_number(&pos, &lineno, 1, view->lines) ||
4327 !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4330 commit = get_blame_commit(view, text);
4336 struct line *line = &view->line[lineno + group - 1];
4339 blame->commit = commit;
4340 blame->lineno = orig_lineno + group - 1;
4348 blame_read_file(struct view *view, const char *line, bool *read_file)
4351 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
4354 if (view->lines == 0 && !view->parent)
4355 die("No blame exist for %s", view->vid);
4357 if (view->lines == 0 || !run_io_rd(&io, argv, FORMAT_ALL)) {
4358 report("Failed to load blame data");
4362 done_io(view->pipe);
4368 size_t linelen = strlen(line);
4369 struct blame *blame = malloc(sizeof(*blame) + linelen);
4374 blame->commit = NULL;
4375 strncpy(blame->text, line, linelen);
4376 blame->text[linelen] = 0;
4377 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
4382 match_blame_header(const char *name, char **line)
4384 size_t namelen = strlen(name);
4385 bool matched = !strncmp(name, *line, namelen);
4394 blame_read(struct view *view, char *line)
4396 static struct blame_commit *commit = NULL;
4397 static int blamed = 0;
4398 static bool read_file = TRUE;
4401 return blame_read_file(view, line, &read_file);
4408 string_format(view->ref, "%s", view->vid);
4409 if (view_is_displayed(view)) {
4410 update_view_title(view);
4411 redraw_view_from(view, 0);
4417 commit = parse_blame_commit(view, line, &blamed);
4418 string_format(view->ref, "%s %2d%%", view->vid,
4419 view->lines ? blamed * 100 / view->lines : 0);
4421 } else if (match_blame_header("author ", &line)) {
4422 commit->author = get_author(line);
4424 } else if (match_blame_header("author-time ", &line)) {
4425 commit->time = (time_t) atol(line);
4427 } else if (match_blame_header("author-tz ", &line)) {
4428 parse_timezone(&commit->time, line);
4430 } else if (match_blame_header("summary ", &line)) {
4431 string_ncopy(commit->title, line, strlen(line));
4433 } else if (match_blame_header("previous ", &line)) {
4434 commit->has_previous = TRUE;
4436 } else if (match_blame_header("filename ", &line)) {
4437 string_ncopy(commit->filename, line, strlen(line));
4445 blame_draw(struct view *view, struct line *line, unsigned int lineno)
4447 struct blame *blame = line->data;
4448 time_t *time = NULL;
4449 const char *id = NULL, *author = NULL;
4450 char text[SIZEOF_STR];
4452 if (blame->commit && *blame->commit->filename) {
4453 id = blame->commit->id;
4454 author = blame->commit->author;
4455 time = &blame->commit->time;
4458 if (opt_date && draw_date(view, time))
4461 if (opt_author && draw_author(view, author))
4464 if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
4467 if (draw_lineno(view, lineno))
4470 string_expand(text, sizeof(text), blame->text, opt_tab_size);
4471 draw_text(view, LINE_DEFAULT, text, TRUE);
4476 check_blame_commit(struct blame *blame, bool check_null_id)
4479 report("Commit data not loaded yet");
4480 else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
4481 report("No commit exist for the selected line");
4488 setup_blame_parent_line(struct view *view, struct blame *blame)
4490 const char *diff_tree_argv[] = {
4491 "git", "diff-tree", "-U0", blame->commit->id,
4492 "--", blame->commit->filename, NULL
4495 int parent_lineno = -1;
4496 int blamed_lineno = -1;
4499 if (!run_io(&io, diff_tree_argv, NULL, IO_RD))
4502 while ((line = io_get(&io, '\n', TRUE))) {
4504 char *pos = strchr(line, '+');
4506 parent_lineno = atoi(line + 4);
4508 blamed_lineno = atoi(pos + 1);
4510 } else if (*line == '+' && parent_lineno != -1) {
4511 if (blame->lineno == blamed_lineno - 1 &&
4512 !strcmp(blame->text, line + 1)) {
4513 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
4524 blame_request(struct view *view, enum request request, struct line *line)
4526 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4527 struct blame *blame = line->data;
4530 case REQ_VIEW_BLAME:
4531 if (check_blame_commit(blame, TRUE)) {
4532 string_copy(opt_ref, blame->commit->id);
4533 string_copy(opt_file, blame->commit->filename);
4535 view->lineno = blame->lineno;
4536 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4541 if (check_blame_commit(blame, TRUE) &&
4542 select_commit_parent(blame->commit->id, opt_ref,
4543 blame->commit->filename)) {
4544 string_copy(opt_file, blame->commit->filename);
4545 setup_blame_parent_line(view, blame);
4546 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4551 if (!check_blame_commit(blame, FALSE))
4554 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
4555 !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
4558 if (!strcmp(blame->commit->id, NULL_ID)) {
4559 struct view *diff = VIEW(REQ_VIEW_DIFF);
4560 const char *diff_index_argv[] = {
4561 "git", "diff-index", "--root", "--patch-with-stat",
4562 "-C", "-M", "HEAD", "--", view->vid, NULL
4565 if (!blame->commit->has_previous) {
4566 diff_index_argv[1] = "diff";
4567 diff_index_argv[2] = "--no-color";
4568 diff_index_argv[6] = "--";
4569 diff_index_argv[7] = "/dev/null";
4572 if (!prepare_update(diff, diff_index_argv, NULL, FORMAT_DASH)) {
4573 report("Failed to allocate diff command");
4576 flags |= OPEN_PREPARED;
4579 open_view(view, REQ_VIEW_DIFF, flags);
4580 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
4581 string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
4592 blame_grep(struct view *view, struct line *line)
4594 struct blame *blame = line->data;
4595 struct blame_commit *commit = blame->commit;
4596 const char *text[] = {
4598 commit ? commit->title : "",
4599 commit ? commit->id : "",
4600 commit && opt_author ? commit->author : "",
4601 commit && opt_date ? mkdate(&commit->time) : "",
4604 return grep_text(view, text);
4608 blame_select(struct view *view, struct line *line)
4610 struct blame *blame = line->data;
4611 struct blame_commit *commit = blame->commit;
4616 if (!strcmp(commit->id, NULL_ID))
4617 string_ncopy(ref_commit, "HEAD", 4);
4619 string_copy_rev(ref_commit, commit->id);
4622 static struct view_ops blame_ops = {
4641 char rev[SIZEOF_REV];
4642 char name[SIZEOF_STR];
4646 char rev[SIZEOF_REV];
4647 char name[SIZEOF_STR];
4651 static char status_onbranch[SIZEOF_STR];
4652 static struct status stage_status;
4653 static enum line_type stage_line_type;
4654 static size_t stage_chunks;
4655 static int *stage_chunk;
4657 DEFINE_ALLOCATOR(realloc_ints, int, 32)
4659 /* This should work even for the "On branch" line. */
4661 status_has_none(struct view *view, struct line *line)
4663 return line < view->line + view->lines && !line[1].data;
4666 /* Get fields from the diff line:
4667 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
4670 status_get_diff(struct status *file, const char *buf, size_t bufsize)
4672 const char *old_mode = buf + 1;
4673 const char *new_mode = buf + 8;
4674 const char *old_rev = buf + 15;
4675 const char *new_rev = buf + 56;
4676 const char *status = buf + 97;
4679 old_mode[-1] != ':' ||
4680 new_mode[-1] != ' ' ||
4681 old_rev[-1] != ' ' ||
4682 new_rev[-1] != ' ' ||
4686 file->status = *status;
4688 string_copy_rev(file->old.rev, old_rev);
4689 string_copy_rev(file->new.rev, new_rev);
4691 file->old.mode = strtoul(old_mode, NULL, 8);
4692 file->new.mode = strtoul(new_mode, NULL, 8);
4694 file->old.name[0] = file->new.name[0] = 0;
4700 status_run(struct view *view, const char *argv[], char status, enum line_type type)
4702 struct status *unmerged = NULL;
4706 if (!run_io(&io, argv, NULL, IO_RD))
4709 add_line_data(view, NULL, type);
4711 while ((buf = io_get(&io, 0, TRUE))) {
4712 struct status *file = unmerged;
4715 file = calloc(1, sizeof(*file));
4716 if (!file || !add_line_data(view, file, type))
4720 /* Parse diff info part. */
4722 file->status = status;
4724 string_copy(file->old.rev, NULL_ID);
4726 } else if (!file->status || file == unmerged) {
4727 if (!status_get_diff(file, buf, strlen(buf)))
4730 buf = io_get(&io, 0, TRUE);
4734 /* Collapse all modified entries that follow an
4735 * associated unmerged entry. */
4736 if (unmerged == file) {
4737 unmerged->status = 'U';
4739 } else if (file->status == 'U') {
4744 /* Grab the old name for rename/copy. */
4745 if (!*file->old.name &&
4746 (file->status == 'R' || file->status == 'C')) {
4747 string_ncopy(file->old.name, buf, strlen(buf));
4749 buf = io_get(&io, 0, TRUE);
4754 /* git-ls-files just delivers a NUL separated list of
4755 * file names similar to the second half of the
4756 * git-diff-* output. */
4757 string_ncopy(file->new.name, buf, strlen(buf));
4758 if (!*file->old.name)
4759 string_copy(file->old.name, file->new.name);
4763 if (io_error(&io)) {
4769 if (!view->line[view->lines - 1].data)
4770 add_line_data(view, NULL, LINE_STAT_NONE);
4776 /* Don't show unmerged entries in the staged section. */
4777 static const char *status_diff_index_argv[] = {
4778 "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
4779 "--cached", "-M", "HEAD", NULL
4782 static const char *status_diff_files_argv[] = {
4783 "git", "diff-files", "-z", NULL
4786 static const char *status_list_other_argv[] = {
4787 "git", "ls-files", "-z", "--others", "--exclude-standard", NULL
4790 static const char *status_list_no_head_argv[] = {
4791 "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
4794 static const char *update_index_argv[] = {
4795 "git", "update-index", "-q", "--unmerged", "--refresh", NULL
4798 /* Restore the previous line number to stay in the context or select a
4799 * line with something that can be updated. */
4801 status_restore(struct view *view)
4803 if (view->p_lineno >= view->lines)
4804 view->p_lineno = view->lines - 1;
4805 while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
4807 while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
4810 /* If the above fails, always skip the "On branch" line. */
4811 if (view->p_lineno < view->lines)
4812 view->lineno = view->p_lineno;
4816 if (view->lineno < view->offset)
4817 view->offset = view->lineno;
4818 else if (view->offset + view->height <= view->lineno)
4819 view->offset = view->lineno - view->height + 1;
4821 view->p_restore = FALSE;
4825 status_update_onbranch(void)
4827 static const char *paths[][2] = {
4828 { "rebase-apply/rebasing", "Rebasing" },
4829 { "rebase-apply/applying", "Applying mailbox" },
4830 { "rebase-apply/", "Rebasing mailbox" },
4831 { "rebase-merge/interactive", "Interactive rebase" },
4832 { "rebase-merge/", "Rebase merge" },
4833 { "MERGE_HEAD", "Merging" },
4834 { "BISECT_LOG", "Bisecting" },
4835 { "HEAD", "On branch" },
4837 char buf[SIZEOF_STR];
4841 if (is_initial_commit()) {
4842 string_copy(status_onbranch, "Initial commit");
4846 for (i = 0; i < ARRAY_SIZE(paths); i++) {
4847 char *head = opt_head;
4849 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
4850 lstat(buf, &stat) < 0)
4856 if (string_format(buf, "%s/rebase-merge/head-name", opt_git_dir) &&
4857 io_open(&io, buf) &&
4858 io_read_buf(&io, buf, sizeof(buf))) {
4859 head = chomp_string(buf);
4860 if (!prefixcmp(head, "refs/heads/"))
4861 head += STRING_SIZE("refs/heads/");
4865 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
4866 string_copy(status_onbranch, opt_head);
4870 string_copy(status_onbranch, "Not currently on any branch");
4873 /* First parse staged info using git-diff-index(1), then parse unstaged
4874 * info using git-diff-files(1), and finally untracked files using
4875 * git-ls-files(1). */
4877 status_open(struct view *view)
4881 add_line_data(view, NULL, LINE_STAT_HEAD);
4882 status_update_onbranch();
4884 run_io_bg(update_index_argv);
4886 if (is_initial_commit()) {
4887 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
4889 } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
4893 if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
4894 !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
4897 /* Restore the exact position or use the specialized restore
4899 if (!view->p_restore)
4900 status_restore(view);
4905 status_draw(struct view *view, struct line *line, unsigned int lineno)
4907 struct status *status = line->data;
4908 enum line_type type;
4912 switch (line->type) {
4913 case LINE_STAT_STAGED:
4914 type = LINE_STAT_SECTION;
4915 text = "Changes to be committed:";
4918 case LINE_STAT_UNSTAGED:
4919 type = LINE_STAT_SECTION;
4920 text = "Changed but not updated:";
4923 case LINE_STAT_UNTRACKED:
4924 type = LINE_STAT_SECTION;
4925 text = "Untracked files:";
4928 case LINE_STAT_NONE:
4929 type = LINE_DEFAULT;
4930 text = " (no files)";
4933 case LINE_STAT_HEAD:
4934 type = LINE_STAT_HEAD;
4935 text = status_onbranch;
4942 static char buf[] = { '?', ' ', ' ', ' ', 0 };
4944 buf[0] = status->status;
4945 if (draw_text(view, line->type, buf, TRUE))
4947 type = LINE_DEFAULT;
4948 text = status->new.name;
4951 draw_text(view, type, text, TRUE);
4956 status_load_error(struct view *view, struct view *stage, const char *path)
4958 if (displayed_views() == 2 || display[current_view] != view)
4959 maximize_view(view);
4960 report("Failed to load '%s': %s", path, io_strerror(&stage->io));
4965 status_enter(struct view *view, struct line *line)
4967 struct status *status = line->data;
4968 const char *oldpath = status ? status->old.name : NULL;
4969 /* Diffs for unmerged entries are empty when passing the new
4970 * path, so leave it empty. */
4971 const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
4973 enum open_flags split;
4974 struct view *stage = VIEW(REQ_VIEW_STAGE);
4976 if (line->type == LINE_STAT_NONE ||
4977 (!status && line[1].type == LINE_STAT_NONE)) {
4978 report("No file to diff");
4982 switch (line->type) {
4983 case LINE_STAT_STAGED:
4984 if (is_initial_commit()) {
4985 const char *no_head_diff_argv[] = {
4986 "git", "diff", "--no-color", "--patch-with-stat",
4987 "--", "/dev/null", newpath, NULL
4990 if (!prepare_update(stage, no_head_diff_argv, opt_cdup, FORMAT_DASH))
4991 return status_load_error(view, stage, newpath);
4993 const char *index_show_argv[] = {
4994 "git", "diff-index", "--root", "--patch-with-stat",
4995 "-C", "-M", "--cached", "HEAD", "--",
4996 oldpath, newpath, NULL
4999 if (!prepare_update(stage, index_show_argv, opt_cdup, FORMAT_DASH))
5000 return status_load_error(view, stage, newpath);
5004 info = "Staged changes to %s";
5006 info = "Staged changes";
5009 case LINE_STAT_UNSTAGED:
5011 const char *files_show_argv[] = {
5012 "git", "diff-files", "--root", "--patch-with-stat",
5013 "-C", "-M", "--", oldpath, newpath, NULL
5016 if (!prepare_update(stage, files_show_argv, opt_cdup, FORMAT_DASH))
5017 return status_load_error(view, stage, newpath);
5019 info = "Unstaged changes to %s";
5021 info = "Unstaged changes";
5024 case LINE_STAT_UNTRACKED:
5026 report("No file to show");
5030 if (!suffixcmp(status->new.name, -1, "/")) {
5031 report("Cannot display a directory");
5035 if (!prepare_update_file(stage, newpath))
5036 return status_load_error(view, stage, newpath);
5037 info = "Untracked file %s";
5040 case LINE_STAT_HEAD:
5044 die("line type %d not handled in switch", line->type);
5047 split = view_is_displayed(view) ? OPEN_SPLIT : 0;
5048 open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5049 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5051 stage_status = *status;
5053 memset(&stage_status, 0, sizeof(stage_status));
5056 stage_line_type = line->type;
5058 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5065 status_exists(struct status *status, enum line_type type)
5067 struct view *view = VIEW(REQ_VIEW_STATUS);
5068 unsigned long lineno;
5070 for (lineno = 0; lineno < view->lines; lineno++) {
5071 struct line *line = &view->line[lineno];
5072 struct status *pos = line->data;
5074 if (line->type != type)
5076 if (!pos && (!status || !status->status) && line[1].data) {
5077 select_view_line(view, lineno);
5080 if (pos && !strcmp(status->new.name, pos->new.name)) {
5081 select_view_line(view, lineno);
5091 status_update_prepare(struct io *io, enum line_type type)
5093 const char *staged_argv[] = {
5094 "git", "update-index", "-z", "--index-info", NULL
5096 const char *others_argv[] = {
5097 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5101 case LINE_STAT_STAGED:
5102 return run_io(io, staged_argv, opt_cdup, IO_WR);
5104 case LINE_STAT_UNSTAGED:
5105 return run_io(io, others_argv, opt_cdup, IO_WR);
5107 case LINE_STAT_UNTRACKED:
5108 return run_io(io, others_argv, NULL, IO_WR);
5111 die("line type %d not handled in switch", type);
5117 status_update_write(struct io *io, struct status *status, enum line_type type)
5119 char buf[SIZEOF_STR];
5123 case LINE_STAT_STAGED:
5124 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
5127 status->old.name, 0))
5131 case LINE_STAT_UNSTAGED:
5132 case LINE_STAT_UNTRACKED:
5133 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
5138 die("line type %d not handled in switch", type);
5141 return io_write(io, buf, bufsize);
5145 status_update_file(struct status *status, enum line_type type)
5150 if (!status_update_prepare(&io, type))
5153 result = status_update_write(&io, status, type);
5154 return done_io(&io) && result;
5158 status_update_files(struct view *view, struct line *line)
5160 char buf[sizeof(view->ref)];
5163 struct line *pos = view->line + view->lines;
5166 int cursor_y, cursor_x;
5168 if (!status_update_prepare(&io, line->type))
5171 for (pos = line; pos < view->line + view->lines && pos->data; pos++)
5174 string_copy(buf, view->ref);
5175 getsyx(cursor_y, cursor_x);
5176 for (file = 0, done = 5; result && file < files; line++, file++) {
5177 int almost_done = file * 100 / files;
5179 if (almost_done > done) {
5181 string_format(view->ref, "updating file %u of %u (%d%% done)",
5183 update_view_title(view);
5184 setsyx(cursor_y, cursor_x);
5187 result = status_update_write(&io, line->data, line->type);
5189 string_copy(view->ref, buf);
5191 return done_io(&io) && result;
5195 status_update(struct view *view)
5197 struct line *line = &view->line[view->lineno];
5199 assert(view->lines);
5202 /* This should work even for the "On branch" line. */
5203 if (line < view->line + view->lines && !line[1].data) {
5204 report("Nothing to update");
5208 if (!status_update_files(view, line + 1)) {
5209 report("Failed to update file status");
5213 } else if (!status_update_file(line->data, line->type)) {
5214 report("Failed to update file status");
5222 status_revert(struct status *status, enum line_type type, bool has_none)
5224 if (!status || type != LINE_STAT_UNSTAGED) {
5225 if (type == LINE_STAT_STAGED) {
5226 report("Cannot revert changes to staged files");
5227 } else if (type == LINE_STAT_UNTRACKED) {
5228 report("Cannot revert changes to untracked files");
5229 } else if (has_none) {
5230 report("Nothing to revert");
5232 report("Cannot revert changes to multiple files");
5237 char mode[10] = "100644";
5238 const char *reset_argv[] = {
5239 "git", "update-index", "--cacheinfo", mode,
5240 status->old.rev, status->old.name, NULL
5242 const char *checkout_argv[] = {
5243 "git", "checkout", "--", status->old.name, NULL
5246 if (!prompt_yesno("Are you sure you want to overwrite any changes?"))
5248 string_format(mode, "%o", status->old.mode);
5249 return (status->status != 'U' || run_io_fg(reset_argv, opt_cdup)) &&
5250 run_io_fg(checkout_argv, opt_cdup);
5255 status_request(struct view *view, enum request request, struct line *line)
5257 struct status *status = line->data;
5260 case REQ_STATUS_UPDATE:
5261 if (!status_update(view))
5265 case REQ_STATUS_REVERT:
5266 if (!status_revert(status, line->type, status_has_none(view, line)))
5270 case REQ_STATUS_MERGE:
5271 if (!status || status->status != 'U') {
5272 report("Merging only possible for files with unmerged status ('U').");
5275 open_mergetool(status->new.name);
5281 if (status->status == 'D') {
5282 report("File has been deleted.");
5286 open_editor(status->status != '?', status->new.name);
5289 case REQ_VIEW_BLAME:
5291 string_copy(opt_file, status->new.name);
5297 /* After returning the status view has been split to
5298 * show the stage view. No further reloading is
5300 return status_enter(view, line);
5303 /* Simply reload the view. */
5310 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
5316 status_select(struct view *view, struct line *line)
5318 struct status *status = line->data;
5319 char file[SIZEOF_STR] = "all files";
5323 if (status && !string_format(file, "'%s'", status->new.name))
5326 if (!status && line[1].type == LINE_STAT_NONE)
5329 switch (line->type) {
5330 case LINE_STAT_STAGED:
5331 text = "Press %s to unstage %s for commit";
5334 case LINE_STAT_UNSTAGED:
5335 text = "Press %s to stage %s for commit";
5338 case LINE_STAT_UNTRACKED:
5339 text = "Press %s to stage %s for addition";
5342 case LINE_STAT_HEAD:
5343 case LINE_STAT_NONE:
5344 text = "Nothing to update";
5348 die("line type %d not handled in switch", line->type);
5351 if (status && status->status == 'U') {
5352 text = "Press %s to resolve conflict in %s";
5353 key = get_key(REQ_STATUS_MERGE);
5356 key = get_key(REQ_STATUS_UPDATE);
5359 string_format(view->ref, text, key, file);
5363 status_grep(struct view *view, struct line *line)
5365 struct status *status = line->data;
5368 const char buf[2] = { status->status, 0 };
5369 const char *text[] = { status->new.name, buf, NULL };
5371 return grep_text(view, text);
5377 static struct view_ops status_ops = {
5390 stage_diff_write(struct io *io, struct line *line, struct line *end)
5392 while (line < end) {
5393 if (!io_write(io, line->data, strlen(line->data)) ||
5394 !io_write(io, "\n", 1))
5397 if (line->type == LINE_DIFF_CHUNK ||
5398 line->type == LINE_DIFF_HEADER)
5405 static struct line *
5406 stage_diff_find(struct view *view, struct line *line, enum line_type type)
5408 for (; view->line < line; line--)
5409 if (line->type == type)
5416 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
5418 const char *apply_argv[SIZEOF_ARG] = {
5419 "git", "apply", "--whitespace=nowarn", NULL
5421 struct line *diff_hdr;
5425 diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
5430 apply_argv[argc++] = "--cached";
5431 if (revert || stage_line_type == LINE_STAT_STAGED)
5432 apply_argv[argc++] = "-R";
5433 apply_argv[argc++] = "-";
5434 apply_argv[argc++] = NULL;
5435 if (!run_io(&io, apply_argv, opt_cdup, IO_WR))
5438 if (!stage_diff_write(&io, diff_hdr, chunk) ||
5439 !stage_diff_write(&io, chunk, view->line + view->lines))
5443 run_io_bg(update_index_argv);
5445 return chunk ? TRUE : FALSE;
5449 stage_update(struct view *view, struct line *line)
5451 struct line *chunk = NULL;
5453 if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
5454 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5457 if (!stage_apply_chunk(view, chunk, FALSE)) {
5458 report("Failed to apply chunk");
5462 } else if (!stage_status.status) {
5463 view = VIEW(REQ_VIEW_STATUS);
5465 for (line = view->line; line < view->line + view->lines; line++)
5466 if (line->type == stage_line_type)
5469 if (!status_update_files(view, line + 1)) {
5470 report("Failed to update files");
5474 } else if (!status_update_file(&stage_status, stage_line_type)) {
5475 report("Failed to update file");
5483 stage_revert(struct view *view, struct line *line)
5485 struct line *chunk = NULL;
5487 if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
5488 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5491 if (!prompt_yesno("Are you sure you want to revert changes?"))
5494 if (!stage_apply_chunk(view, chunk, TRUE)) {
5495 report("Failed to revert chunk");
5501 return status_revert(stage_status.status ? &stage_status : NULL,
5502 stage_line_type, FALSE);
5508 stage_next(struct view *view, struct line *line)
5512 if (!stage_chunks) {
5513 static size_t alloc = 0;
5515 for (line = view->line; line < view->line + view->lines; line++) {
5516 if (line->type != LINE_DIFF_CHUNK)
5519 if (!realloc_ints(&stage_chunk, &alloc, stage_chunks + 1)) {
5520 report("Allocation failure");
5524 stage_chunk[stage_chunks++] = line - view->line;
5528 for (i = 0; i < stage_chunks; i++) {
5529 if (stage_chunk[i] > view->lineno) {
5530 do_scroll_view(view, stage_chunk[i] - view->lineno);
5531 report("Chunk %d of %d", i + 1, stage_chunks);
5536 report("No next chunk found");
5540 stage_request(struct view *view, enum request request, struct line *line)
5543 case REQ_STATUS_UPDATE:
5544 if (!stage_update(view, line))
5548 case REQ_STATUS_REVERT:
5549 if (!stage_revert(view, line))
5553 case REQ_STAGE_NEXT:
5554 if (stage_line_type == LINE_STAT_UNTRACKED) {
5555 report("File is untracked; press %s to add",
5556 get_key(REQ_STATUS_UPDATE));
5559 stage_next(view, line);
5563 if (!stage_status.new.name[0])
5565 if (stage_status.status == 'D') {
5566 report("File has been deleted.");
5570 open_editor(stage_status.status != '?', stage_status.new.name);
5574 /* Reload everything ... */
5577 case REQ_VIEW_BLAME:
5578 if (stage_status.new.name[0]) {
5579 string_copy(opt_file, stage_status.new.name);
5585 return pager_request(view, request, line);
5591 VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
5592 open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
5594 /* Check whether the staged entry still exists, and close the
5595 * stage view if it doesn't. */
5596 if (!status_exists(&stage_status, stage_line_type)) {
5597 status_restore(VIEW(REQ_VIEW_STATUS));
5598 return REQ_VIEW_CLOSE;
5601 if (stage_line_type == LINE_STAT_UNTRACKED) {
5602 if (!suffixcmp(stage_status.new.name, -1, "/")) {
5603 report("Cannot display a directory");
5607 if (!prepare_update_file(view, stage_status.new.name)) {
5608 report("Failed to open file: %s", strerror(errno));
5612 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
5617 static struct view_ops stage_ops = {
5634 char id[SIZEOF_REV]; /* SHA1 ID. */
5635 char title[128]; /* First line of the commit message. */
5636 const char *author; /* Author of the commit. */
5637 time_t time; /* Date from the author ident. */
5638 struct ref **refs; /* Repository references. */
5639 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
5640 size_t graph_size; /* The width of the graph array. */
5641 bool has_parents; /* Rewritten --parents seen. */
5644 /* Size of rev graph with no "padding" columns */
5645 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
5648 struct rev_graph *prev, *next, *parents;
5649 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
5651 struct commit *commit;
5653 unsigned int boundary:1;
5656 /* Parents of the commit being visualized. */
5657 static struct rev_graph graph_parents[4];
5659 /* The current stack of revisions on the graph. */
5660 static struct rev_graph graph_stacks[4] = {
5661 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
5662 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
5663 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
5664 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
5668 graph_parent_is_merge(struct rev_graph *graph)
5670 return graph->parents->size > 1;
5674 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
5676 struct commit *commit = graph->commit;
5678 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
5679 commit->graph[commit->graph_size++] = symbol;
5683 clear_rev_graph(struct rev_graph *graph)
5685 graph->boundary = 0;
5686 graph->size = graph->pos = 0;
5687 graph->commit = NULL;
5688 memset(graph->parents, 0, sizeof(*graph->parents));
5692 done_rev_graph(struct rev_graph *graph)
5694 if (graph_parent_is_merge(graph) &&
5695 graph->pos < graph->size - 1 &&
5696 graph->next->size == graph->size + graph->parents->size - 1) {
5697 size_t i = graph->pos + graph->parents->size - 1;
5699 graph->commit->graph_size = i * 2;
5700 while (i < graph->next->size - 1) {
5701 append_to_rev_graph(graph, ' ');
5702 append_to_rev_graph(graph, '\\');
5707 clear_rev_graph(graph);
5711 push_rev_graph(struct rev_graph *graph, const char *parent)
5715 /* "Collapse" duplicate parents lines.
5717 * FIXME: This needs to also update update the drawn graph but
5718 * for now it just serves as a method for pruning graph lines. */
5719 for (i = 0; i < graph->size; i++)
5720 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
5723 if (graph->size < SIZEOF_REVITEMS) {
5724 string_copy_rev(graph->rev[graph->size++], parent);
5729 get_rev_graph_symbol(struct rev_graph *graph)
5733 if (graph->boundary)
5734 symbol = REVGRAPH_BOUND;
5735 else if (graph->parents->size == 0)
5736 symbol = REVGRAPH_INIT;
5737 else if (graph_parent_is_merge(graph))
5738 symbol = REVGRAPH_MERGE;
5739 else if (graph->pos >= graph->size)
5740 symbol = REVGRAPH_BRANCH;
5742 symbol = REVGRAPH_COMMIT;
5748 draw_rev_graph(struct rev_graph *graph)
5751 chtype separator, line;
5753 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
5754 static struct rev_filler fillers[] = {
5760 chtype symbol = get_rev_graph_symbol(graph);
5761 struct rev_filler *filler;
5764 if (opt_line_graphics)
5765 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
5767 filler = &fillers[DEFAULT];
5769 for (i = 0; i < graph->pos; i++) {
5770 append_to_rev_graph(graph, filler->line);
5771 if (graph_parent_is_merge(graph->prev) &&
5772 graph->prev->pos == i)
5773 filler = &fillers[RSHARP];
5775 append_to_rev_graph(graph, filler->separator);
5778 /* Place the symbol for this revision. */
5779 append_to_rev_graph(graph, symbol);
5781 if (graph->prev->size > graph->size)
5782 filler = &fillers[RDIAG];
5784 filler = &fillers[DEFAULT];
5788 for (; i < graph->size; i++) {
5789 append_to_rev_graph(graph, filler->separator);
5790 append_to_rev_graph(graph, filler->line);
5791 if (graph_parent_is_merge(graph->prev) &&
5792 i < graph->prev->pos + graph->parents->size)
5793 filler = &fillers[RSHARP];
5794 if (graph->prev->size > graph->size)
5795 filler = &fillers[LDIAG];
5798 if (graph->prev->size > graph->size) {
5799 append_to_rev_graph(graph, filler->separator);
5800 if (filler->line != ' ')
5801 append_to_rev_graph(graph, filler->line);
5805 /* Prepare the next rev graph */
5807 prepare_rev_graph(struct rev_graph *graph)
5811 /* First, traverse all lines of revisions up to the active one. */
5812 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
5813 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
5816 push_rev_graph(graph->next, graph->rev[graph->pos]);
5819 /* Interleave the new revision parent(s). */
5820 for (i = 0; !graph->boundary && i < graph->parents->size; i++)
5821 push_rev_graph(graph->next, graph->parents->rev[i]);
5823 /* Lastly, put any remaining revisions. */
5824 for (i = graph->pos + 1; i < graph->size; i++)
5825 push_rev_graph(graph->next, graph->rev[i]);
5829 update_rev_graph(struct view *view, struct rev_graph *graph)
5831 /* If this is the finalizing update ... */
5833 prepare_rev_graph(graph);
5835 /* Graph visualization needs a one rev look-ahead,
5836 * so the first update doesn't visualize anything. */
5837 if (!graph->prev->commit)
5840 if (view->lines > 2)
5841 view->line[view->lines - 3].dirty = 1;
5842 if (view->lines > 1)
5843 view->line[view->lines - 2].dirty = 1;
5844 draw_rev_graph(graph->prev);
5845 done_rev_graph(graph->prev->prev);
5853 static const char *main_argv[SIZEOF_ARG] = {
5854 "git", "log", "--no-color", "--pretty=raw", "--parents",
5855 "--topo-order", "%(head)", NULL
5859 main_draw(struct view *view, struct line *line, unsigned int lineno)
5861 struct commit *commit = line->data;
5863 if (!commit->author)
5866 if (opt_date && draw_date(view, &commit->time))
5869 if (opt_author && draw_author(view, commit->author))
5872 if (opt_rev_graph && commit->graph_size &&
5873 draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
5876 if (opt_show_refs && commit->refs) {
5880 enum line_type type;
5882 if (commit->refs[i]->head)
5883 type = LINE_MAIN_HEAD;
5884 else if (commit->refs[i]->ltag)
5885 type = LINE_MAIN_LOCAL_TAG;
5886 else if (commit->refs[i]->tag)
5887 type = LINE_MAIN_TAG;
5888 else if (commit->refs[i]->tracked)
5889 type = LINE_MAIN_TRACKED;
5890 else if (commit->refs[i]->remote)
5891 type = LINE_MAIN_REMOTE;
5893 type = LINE_MAIN_REF;
5895 if (draw_text(view, type, "[", TRUE) ||
5896 draw_text(view, type, commit->refs[i]->name, TRUE) ||
5897 draw_text(view, type, "]", TRUE))
5900 if (draw_text(view, LINE_DEFAULT, " ", TRUE))
5902 } while (commit->refs[i++]->next);
5905 draw_text(view, LINE_DEFAULT, commit->title, TRUE);
5909 /* Reads git log --pretty=raw output and parses it into the commit struct. */
5911 main_read(struct view *view, char *line)
5913 static struct rev_graph *graph = graph_stacks;
5914 enum line_type type;
5915 struct commit *commit;
5920 if (!view->lines && !view->parent)
5921 die("No revisions match the given arguments.");
5922 if (view->lines > 0) {
5923 commit = view->line[view->lines - 1].data;
5924 view->line[view->lines - 1].dirty = 1;
5925 if (!commit->author) {
5928 graph->commit = NULL;
5931 update_rev_graph(view, graph);
5933 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
5934 clear_rev_graph(&graph_stacks[i]);
5938 type = get_line_type(line);
5939 if (type == LINE_COMMIT) {
5940 commit = calloc(1, sizeof(struct commit));
5944 line += STRING_SIZE("commit ");
5946 graph->boundary = 1;
5950 string_copy_rev(commit->id, line);
5951 commit->refs = get_refs(commit->id);
5952 graph->commit = commit;
5953 add_line_data(view, commit, LINE_MAIN_COMMIT);
5955 while ((line = strchr(line, ' '))) {
5957 push_rev_graph(graph->parents, line);
5958 commit->has_parents = TRUE;
5965 commit = view->line[view->lines - 1].data;
5969 if (commit->has_parents)
5971 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
5975 parse_author_line(line + STRING_SIZE("author "),
5976 &commit->author, &commit->time);
5977 update_rev_graph(view, graph);
5978 graph = graph->next;
5982 /* Fill in the commit title if it has not already been set. */
5983 if (commit->title[0])
5986 /* Require titles to start with a non-space character at the
5987 * offset used by git log. */
5988 if (strncmp(line, " ", 4))
5991 /* Well, if the title starts with a whitespace character,
5992 * try to be forgiving. Otherwise we end up with no title. */
5993 while (isspace(*line))
5997 /* FIXME: More graceful handling of titles; append "..." to
5998 * shortened titles, etc. */
6000 string_expand(commit->title, sizeof(commit->title), line, 1);
6001 view->line[view->lines - 1].dirty = 1;
6008 main_request(struct view *view, enum request request, struct line *line)
6010 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
6014 open_view(view, REQ_VIEW_DIFF, flags);
6018 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6028 grep_refs(struct ref **refs, regex_t *regex)
6033 if (!opt_show_refs || !refs)
6036 if (regexec(regex, refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6038 } while (refs[i++]->next);
6044 main_grep(struct view *view, struct line *line)
6046 struct commit *commit = line->data;
6047 const char *text[] = {
6049 opt_author ? commit->author : "",
6050 opt_date ? mkdate(&commit->time) : "",
6054 return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6058 main_select(struct view *view, struct line *line)
6060 struct commit *commit = line->data;
6062 string_copy_rev(view->ref, commit->id);
6063 string_copy_rev(ref_commit, view->ref);
6066 static struct view_ops main_ops = {
6079 * Unicode / UTF-8 handling
6081 * NOTE: Much of the following code for dealing with Unicode is derived from
6082 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
6083 * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
6087 unicode_width(unsigned long c)
6090 (c <= 0x115f /* Hangul Jamo */
6093 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
6095 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
6096 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
6097 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
6098 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
6099 || (c >= 0xffe0 && c <= 0xffe6)
6100 || (c >= 0x20000 && c <= 0x2fffd)
6101 || (c >= 0x30000 && c <= 0x3fffd)))
6105 return opt_tab_size;
6110 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
6111 * Illegal bytes are set one. */
6112 static const unsigned char utf8_bytes[256] = {
6113 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,
6114 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,
6115 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,
6116 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,
6117 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,
6118 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,
6119 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,
6120 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,
6123 /* Decode UTF-8 multi-byte representation into a Unicode character. */
6124 static inline unsigned long
6125 utf8_to_unicode(const char *string, size_t length)
6127 unsigned long unicode;
6131 unicode = string[0];
6134 unicode = (string[0] & 0x1f) << 6;
6135 unicode += (string[1] & 0x3f);
6138 unicode = (string[0] & 0x0f) << 12;
6139 unicode += ((string[1] & 0x3f) << 6);
6140 unicode += (string[2] & 0x3f);
6143 unicode = (string[0] & 0x0f) << 18;
6144 unicode += ((string[1] & 0x3f) << 12);
6145 unicode += ((string[2] & 0x3f) << 6);
6146 unicode += (string[3] & 0x3f);
6149 unicode = (string[0] & 0x0f) << 24;
6150 unicode += ((string[1] & 0x3f) << 18);
6151 unicode += ((string[2] & 0x3f) << 12);
6152 unicode += ((string[3] & 0x3f) << 6);
6153 unicode += (string[4] & 0x3f);
6156 unicode = (string[0] & 0x01) << 30;
6157 unicode += ((string[1] & 0x3f) << 24);
6158 unicode += ((string[2] & 0x3f) << 18);
6159 unicode += ((string[3] & 0x3f) << 12);
6160 unicode += ((string[4] & 0x3f) << 6);
6161 unicode += (string[5] & 0x3f);
6164 die("Invalid Unicode length");
6167 /* Invalid characters could return the special 0xfffd value but NUL
6168 * should be just as good. */
6169 return unicode > 0xffff ? 0 : unicode;
6172 /* Calculates how much of string can be shown within the given maximum width
6173 * and sets trimmed parameter to non-zero value if all of string could not be
6174 * shown. If the reserve flag is TRUE, it will reserve at least one
6175 * trailing character, which can be useful when drawing a delimiter.
6177 * Returns the number of bytes to output from string to satisfy max_width. */
6179 utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve)
6181 const char *string = *start;
6182 const char *end = strchr(string, '\0');
6183 unsigned char last_bytes = 0;
6184 size_t last_ucwidth = 0;
6189 while (string < end) {
6190 int c = *(unsigned char *) string;
6191 unsigned char bytes = utf8_bytes[c];
6193 unsigned long unicode;
6195 if (string + bytes > end)
6198 /* Change representation to figure out whether
6199 * it is a single- or double-width character. */
6201 unicode = utf8_to_unicode(string, bytes);
6202 /* FIXME: Graceful handling of invalid Unicode character. */
6206 ucwidth = unicode_width(unicode);
6208 skip -= ucwidth <= skip ? ucwidth : skip;
6212 if (*width > max_width) {
6215 if (reserve && *width == max_width) {
6216 string -= last_bytes;
6217 *width -= last_ucwidth;
6223 last_bytes = ucwidth ? bytes : 0;
6224 last_ucwidth = ucwidth;
6227 return string - *start;
6235 /* Whether or not the curses interface has been initialized. */
6236 static bool cursed = FALSE;
6238 /* Terminal hacks and workarounds. */
6239 static bool use_scroll_redrawwin;
6240 static bool use_scroll_status_wclear;
6242 /* The status window is used for polling keystrokes. */
6243 static WINDOW *status_win;
6245 /* Reading from the prompt? */
6246 static bool input_mode = FALSE;
6248 static bool status_empty = FALSE;
6250 /* Update status and title window. */
6252 report(const char *msg, ...)
6254 struct view *view = display[current_view];
6260 char buf[SIZEOF_STR];
6263 va_start(args, msg);
6264 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6265 buf[sizeof(buf) - 1] = 0;
6266 buf[sizeof(buf) - 2] = '.';
6267 buf[sizeof(buf) - 3] = '.';
6268 buf[sizeof(buf) - 4] = '.';
6274 if (!status_empty || *msg) {
6277 va_start(args, msg);
6279 wmove(status_win, 0, 0);
6280 if (view->has_scrolled && use_scroll_status_wclear)
6283 vwprintw(status_win, msg, args);
6284 status_empty = FALSE;
6286 status_empty = TRUE;
6288 wclrtoeol(status_win);
6289 wnoutrefresh(status_win);
6294 update_view_title(view);
6297 /* Controls when nodelay should be in effect when polling user input. */
6299 set_nonblocking_input(bool loading)
6301 static unsigned int loading_views;
6303 if ((loading == FALSE && loading_views-- == 1) ||
6304 (loading == TRUE && loading_views++ == 0))
6305 nodelay(status_win, loading);
6314 /* Initialize the curses library */
6315 if (isatty(STDIN_FILENO)) {
6316 cursed = !!initscr();
6319 /* Leave stdin and stdout alone when acting as a pager. */
6320 opt_tty = fopen("/dev/tty", "r+");
6322 die("Failed to open /dev/tty");
6323 cursed = !!newterm(NULL, opt_tty, opt_tty);
6327 die("Failed to initialize curses");
6329 nonl(); /* Disable conversion and detect newlines from input. */
6330 cbreak(); /* Take input chars one at a time, no wait for \n */
6331 noecho(); /* Don't echo input */
6332 leaveok(stdscr, FALSE);
6337 getmaxyx(stdscr, y, x);
6338 status_win = newwin(1, 0, y - 1, 0);
6340 die("Failed to create status window");
6342 /* Enable keyboard mapping */
6343 keypad(status_win, TRUE);
6344 wbkgdset(status_win, get_line_attr(LINE_STATUS));
6346 TABSIZE = opt_tab_size;
6347 if (opt_line_graphics) {
6348 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
6351 term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
6352 if (term && !strcmp(term, "gnome-terminal")) {
6353 /* In the gnome-terminal-emulator, the message from
6354 * scrolling up one line when impossible followed by
6355 * scrolling down one line causes corruption of the
6356 * status line. This is fixed by calling wclear. */
6357 use_scroll_status_wclear = TRUE;
6358 use_scroll_redrawwin = FALSE;
6360 } else if (term && !strcmp(term, "xrvt-xpm")) {
6361 /* No problems with full optimizations in xrvt-(unicode)
6363 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
6366 /* When scrolling in (u)xterm the last line in the
6367 * scrolling direction will update slowly. */
6368 use_scroll_redrawwin = TRUE;
6369 use_scroll_status_wclear = FALSE;
6374 get_input(int prompt_position)
6377 int i, key, cursor_y, cursor_x;
6379 if (prompt_position)
6383 foreach_view (view, i) {
6385 if (view_is_displayed(view) && view->has_scrolled &&
6386 use_scroll_redrawwin)
6387 redrawwin(view->win);
6388 view->has_scrolled = FALSE;
6391 /* Update the cursor position. */
6392 if (prompt_position) {
6393 getbegyx(status_win, cursor_y, cursor_x);
6394 cursor_x = prompt_position;
6396 view = display[current_view];
6397 getbegyx(view->win, cursor_y, cursor_x);
6398 cursor_x = view->width - 1;
6399 cursor_y += view->lineno - view->offset;
6401 setsyx(cursor_y, cursor_x);
6403 /* Refresh, accept single keystroke of input */
6405 key = wgetch(status_win);
6407 /* wgetch() with nodelay() enabled returns ERR when
6408 * there's no input. */
6411 } else if (key == KEY_RESIZE) {
6414 getmaxyx(stdscr, height, width);
6416 wresize(status_win, 1, width);
6417 mvwin(status_win, height - 1, 0);
6418 wnoutrefresh(status_win);
6420 redraw_display(TRUE);
6430 prompt_input(const char *prompt, input_handler handler, void *data)
6432 enum input_status status = INPUT_OK;
6433 static char buf[SIZEOF_STR];
6438 while (status == INPUT_OK || status == INPUT_SKIP) {
6441 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
6442 wclrtoeol(status_win);
6444 key = get_input(pos + 1);
6449 status = pos ? INPUT_STOP : INPUT_CANCEL;
6456 status = INPUT_CANCEL;
6460 status = INPUT_CANCEL;
6464 if (pos >= sizeof(buf)) {
6465 report("Input string too long");
6469 status = handler(data, buf, key);
6470 if (status == INPUT_OK)
6471 buf[pos++] = (char) key;
6475 /* Clear the status window */
6476 status_empty = FALSE;
6479 if (status == INPUT_CANCEL)
6487 static enum input_status
6488 prompt_yesno_handler(void *data, char *buf, int c)
6490 if (c == 'y' || c == 'Y')
6492 if (c == 'n' || c == 'N')
6493 return INPUT_CANCEL;
6498 prompt_yesno(const char *prompt)
6500 char prompt2[SIZEOF_STR];
6502 if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
6505 return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
6508 static enum input_status
6509 read_prompt_handler(void *data, char *buf, int c)
6511 return isprint(c) ? INPUT_OK : INPUT_SKIP;
6515 read_prompt(const char *prompt)
6517 return prompt_input(prompt, read_prompt_handler, NULL);
6521 * Repository properties
6524 static struct ref *refs = NULL;
6525 static size_t refs_alloc = 0;
6526 static size_t refs_size = 0;
6528 /* Id <-> ref store */
6529 static struct ref ***id_refs = NULL;
6530 static size_t id_refs_alloc = 0;
6531 static size_t id_refs_size = 0;
6533 DEFINE_ALLOCATOR(realloc_refs, struct ref, 256)
6534 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
6535 DEFINE_ALLOCATOR(realloc_refs_lists, struct ref **, 8)
6538 compare_refs(const void *ref1_, const void *ref2_)
6540 const struct ref *ref1 = *(const struct ref **)ref1_;
6541 const struct ref *ref2 = *(const struct ref **)ref2_;
6543 if (ref1->tag != ref2->tag)
6544 return ref2->tag - ref1->tag;
6545 if (ref1->ltag != ref2->ltag)
6546 return ref2->ltag - ref2->ltag;
6547 if (ref1->head != ref2->head)
6548 return ref2->head - ref1->head;
6549 if (ref1->tracked != ref2->tracked)
6550 return ref2->tracked - ref1->tracked;
6551 if (ref1->remote != ref2->remote)
6552 return ref2->remote - ref1->remote;
6553 return strcmp(ref1->name, ref2->name);
6556 static struct ref **
6557 get_refs(const char *id)
6559 struct ref **ref_list = NULL;
6560 size_t ref_list_alloc = 0;
6561 size_t ref_list_size = 0;
6564 for (i = 0; i < id_refs_size; i++)
6565 if (!strcmp(id, id_refs[i][0]->id))
6568 if (!realloc_refs_lists(&id_refs, &id_refs_alloc, id_refs_size + 1))
6571 for (i = 0; i < refs_size; i++) {
6572 if (strcmp(id, refs[i].id))
6575 if (!realloc_refs_list(&ref_list, &ref_list_alloc, ref_list_size + 1))
6578 ref_list[ref_list_size] = &refs[i];
6579 /* XXX: The properties of the commit chains ensures that we can
6580 * safely modify the shared ref. The repo references will
6581 * always be similar for the same id. */
6582 ref_list[ref_list_size]->next = 1;
6587 qsort(ref_list, ref_list_size, sizeof(*ref_list), compare_refs);
6588 ref_list[ref_list_size - 1]->next = 0;
6589 id_refs[id_refs_size++] = ref_list;
6596 read_ref(char *id, size_t idlen, char *name, size_t namelen)
6601 bool remote = FALSE;
6602 bool tracked = FALSE;
6603 bool check_replace = FALSE;
6606 if (!prefixcmp(name, "refs/tags/")) {
6607 if (!suffixcmp(name, namelen, "^{}")) {
6610 if (refs_size > 0 && refs[refs_size - 1].ltag == TRUE)
6611 check_replace = TRUE;
6617 namelen -= STRING_SIZE("refs/tags/");
6618 name += STRING_SIZE("refs/tags/");
6620 } else if (!prefixcmp(name, "refs/remotes/")) {
6622 namelen -= STRING_SIZE("refs/remotes/");
6623 name += STRING_SIZE("refs/remotes/");
6624 tracked = !strcmp(opt_remote, name);
6626 } else if (!prefixcmp(name, "refs/heads/")) {
6627 namelen -= STRING_SIZE("refs/heads/");
6628 name += STRING_SIZE("refs/heads/");
6629 head = !strncmp(opt_head, name, namelen);
6631 } else if (!strcmp(name, "HEAD")) {
6632 string_ncopy(opt_head_rev, id, idlen);
6636 if (check_replace && !strcmp(name, refs[refs_size - 1].name)) {
6637 /* it's an annotated tag, replace the previous SHA1 with the
6638 * resolved commit id; relies on the fact git-ls-remote lists
6639 * the commit id of an annotated tag right before the commit id
6641 refs[refs_size - 1].ltag = ltag;
6642 string_copy_rev(refs[refs_size - 1].id, id);
6647 if (!realloc_refs(&refs, &refs_alloc, refs_size + 1))
6650 ref = &refs[refs_size++];
6651 ref->name = malloc(namelen + 1);
6655 strncpy(ref->name, name, namelen);
6656 ref->name[namelen] = 0;
6660 ref->remote = remote;
6661 ref->tracked = tracked;
6662 string_copy_rev(ref->id, id);
6670 static const char *ls_remote_argv[SIZEOF_ARG] = {
6671 "git", "ls-remote", opt_git_dir, NULL
6673 static bool init = FALSE;
6676 argv_from_env(ls_remote_argv, "TIG_LS_REMOTE");
6683 while (refs_size > 0)
6684 free(refs[--refs_size].name);
6685 while (id_refs_size > 0)
6686 free(id_refs[--id_refs_size]);
6688 return run_io_load(ls_remote_argv, "\t", read_ref);
6692 set_remote_branch(const char *name, const char *value, size_t valuelen)
6694 if (!strcmp(name, ".remote")) {
6695 string_ncopy(opt_remote, value, valuelen);
6697 } else if (*opt_remote && !strcmp(name, ".merge")) {
6698 size_t from = strlen(opt_remote);
6700 if (!prefixcmp(value, "refs/heads/"))
6701 value += STRING_SIZE("refs/heads/");
6703 if (!string_format_from(opt_remote, &from, "/%s", value))
6709 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
6711 const char *argv[SIZEOF_ARG] = { name, "=" };
6712 int argc = 1 + (cmd == option_set_command);
6715 if (!argv_from_string(argv, &argc, value))
6716 config_msg = "Too many option arguments";
6718 error = cmd(argc, argv);
6721 warn("Option 'tig.%s': %s", name, config_msg);
6725 set_environment_variable(const char *name, const char *value)
6727 size_t len = strlen(name) + 1 + strlen(value) + 1;
6728 char *env = malloc(len);
6731 string_nformat(env, len, NULL, "%s=%s", name, value) &&
6739 set_work_tree(const char *value)
6741 char cwd[SIZEOF_STR];
6743 if (!getcwd(cwd, sizeof(cwd)))
6744 die("Failed to get cwd path: %s", strerror(errno));
6745 if (chdir(opt_git_dir) < 0)
6746 die("Failed to chdir(%s): %s", strerror(errno));
6747 if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
6748 die("Failed to get git path: %s", strerror(errno));
6750 die("Failed to chdir(%s): %s", cwd, strerror(errno));
6751 if (chdir(value) < 0)
6752 die("Failed to chdir(%s): %s", value, strerror(errno));
6753 if (!getcwd(cwd, sizeof(cwd)))
6754 die("Failed to get cwd path: %s", strerror(errno));
6755 if (!set_environment_variable("GIT_WORK_TREE", cwd))
6756 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
6757 if (!set_environment_variable("GIT_DIR", opt_git_dir))
6758 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
6759 opt_is_inside_work_tree = TRUE;
6763 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
6765 if (!strcmp(name, "i18n.commitencoding"))
6766 string_ncopy(opt_encoding, value, valuelen);
6768 else if (!strcmp(name, "core.editor"))
6769 string_ncopy(opt_editor, value, valuelen);
6771 else if (!strcmp(name, "core.worktree"))
6772 set_work_tree(value);
6774 else if (!prefixcmp(name, "tig.color."))
6775 set_repo_config_option(name + 10, value, option_color_command);
6777 else if (!prefixcmp(name, "tig.bind."))
6778 set_repo_config_option(name + 9, value, option_bind_command);
6780 else if (!prefixcmp(name, "tig."))
6781 set_repo_config_option(name + 4, value, option_set_command);
6783 else if (*opt_head && !prefixcmp(name, "branch.") &&
6784 !strncmp(name + 7, opt_head, strlen(opt_head)))
6785 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
6791 load_git_config(void)
6793 const char *config_list_argv[] = { "git", GIT_CONFIG, "--list", NULL };
6795 return run_io_load(config_list_argv, "=", read_repo_config_option);
6799 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
6801 if (!opt_git_dir[0]) {
6802 string_ncopy(opt_git_dir, name, namelen);
6804 } else if (opt_is_inside_work_tree == -1) {
6805 /* This can be 3 different values depending on the
6806 * version of git being used. If git-rev-parse does not
6807 * understand --is-inside-work-tree it will simply echo
6808 * the option else either "true" or "false" is printed.
6809 * Default to true for the unknown case. */
6810 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
6812 } else if (*name == '.') {
6813 string_ncopy(opt_cdup, name, namelen);
6816 string_ncopy(opt_prefix, name, namelen);
6823 load_repo_info(void)
6825 const char *head_argv[] = {
6826 "git", "symbolic-ref", "HEAD", NULL
6828 const char *rev_parse_argv[] = {
6829 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
6830 "--show-cdup", "--show-prefix", NULL
6833 if (run_io_buf(head_argv, opt_head, sizeof(opt_head))) {
6834 chomp_string(opt_head);
6835 if (!prefixcmp(opt_head, "refs/heads/")) {
6836 char *offset = opt_head + STRING_SIZE("refs/heads/");
6838 memmove(opt_head, offset, strlen(offset) + 1);
6842 return run_io_load(rev_parse_argv, "=", read_repo_info);
6850 static const char usage[] =
6851 "tig " TIG_VERSION " (" __DATE__ ")\n"
6853 "Usage: tig [options] [revs] [--] [paths]\n"
6854 " or: tig show [options] [revs] [--] [paths]\n"
6855 " or: tig blame [rev] path\n"
6857 " or: tig < [git command output]\n"
6860 " -v, --version Show version and exit\n"
6861 " -h, --help Show help message and exit";
6863 static void __NORETURN
6866 /* XXX: Restore tty modes and let the OS cleanup the rest! */
6872 static void __NORETURN
6873 die(const char *err, ...)
6879 va_start(args, err);
6880 fputs("tig: ", stderr);
6881 vfprintf(stderr, err, args);
6882 fputs("\n", stderr);
6889 warn(const char *msg, ...)
6893 va_start(args, msg);
6894 fputs("tig warning: ", stderr);
6895 vfprintf(stderr, msg, args);
6896 fputs("\n", stderr);
6901 parse_options(int argc, const char *argv[])
6903 enum request request = REQ_VIEW_MAIN;
6904 const char *subcommand;
6905 bool seen_dashdash = FALSE;
6906 /* XXX: This is vulnerable to the user overriding options
6907 * required for the main view parser. */
6908 const char *custom_argv[SIZEOF_ARG] = {
6909 "git", "log", "--no-color", "--pretty=raw", "--parents",
6910 "--topo-order", NULL
6914 if (!isatty(STDIN_FILENO)) {
6915 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
6916 return REQ_VIEW_PAGER;
6922 subcommand = argv[1];
6923 if (!strcmp(subcommand, "status")) {
6925 warn("ignoring arguments after `%s'", subcommand);
6926 return REQ_VIEW_STATUS;
6928 } else if (!strcmp(subcommand, "blame")) {
6929 if (argc <= 2 || argc > 4)
6930 die("invalid number of options to blame\n\n%s", usage);
6934 string_ncopy(opt_ref, argv[i], strlen(argv[i]));
6938 string_ncopy(opt_file, argv[i], strlen(argv[i]));
6939 return REQ_VIEW_BLAME;
6941 } else if (!strcmp(subcommand, "show")) {
6942 request = REQ_VIEW_DIFF;
6949 custom_argv[1] = subcommand;
6953 for (i = 1 + !!subcommand; i < argc; i++) {
6954 const char *opt = argv[i];
6956 if (seen_dashdash || !strcmp(opt, "--")) {
6957 seen_dashdash = TRUE;
6959 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
6960 printf("tig version %s\n", TIG_VERSION);
6963 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
6964 printf("%s\n", usage);
6968 custom_argv[j++] = opt;
6969 if (j >= ARRAY_SIZE(custom_argv))
6970 die("command too long");
6973 if (!prepare_update(VIEW(request), custom_argv, NULL, FORMAT_NONE))
6974 die("Failed to format arguments");
6980 main(int argc, const char *argv[])
6982 enum request request = parse_options(argc, argv);
6986 signal(SIGINT, quit);
6987 signal(SIGPIPE, SIG_IGN);
6989 if (setlocale(LC_ALL, "")) {
6990 char *codeset = nl_langinfo(CODESET);
6992 string_ncopy(opt_codeset, codeset, strlen(codeset));
6995 if (load_repo_info() == ERR)
6996 die("Failed to load repo info.");
6998 if (load_options() == ERR)
6999 die("Failed to load user config.");
7001 if (load_git_config() == ERR)
7002 die("Failed to load repo config.");
7004 /* Require a git repository unless when running in pager mode. */
7005 if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7006 die("Not a git repository");
7008 if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
7011 if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
7012 opt_iconv = iconv_open(opt_codeset, opt_encoding);
7013 if (opt_iconv == ICONV_NONE)
7014 die("Failed to initialize character set conversion");
7017 if (load_refs() == ERR)
7018 die("Failed to load refs.");
7020 foreach_view (view, i)
7021 argv_from_env(view->ops->argv, view->cmd_env);
7025 if (request != REQ_NONE)
7026 open_view(NULL, request, OPEN_PREPARED);
7027 request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
7029 while (view_driver(display[current_view], request)) {
7030 int key = get_input(0);
7032 view = display[current_view];
7033 request = get_keybinding(view->keymap, key);
7035 /* Some low-level request handling. This keeps access to
7036 * status_win restricted. */
7040 char *cmd = read_prompt(":");
7042 if (cmd && isdigit(*cmd)) {
7043 int lineno = view->lineno + 1;
7045 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7046 select_view_line(view, lineno - 1);
7049 report("Unable to parse '%s' as a line number", cmd);
7053 struct view *next = VIEW(REQ_VIEW_PAGER);
7054 const char *argv[SIZEOF_ARG] = { "git" };
7057 /* When running random commands, initially show the
7058 * command in the title. However, it maybe later be
7059 * overwritten if a commit line is selected. */
7060 string_ncopy(next->ref, cmd, strlen(cmd));
7062 if (!argv_from_string(argv, &argc, cmd)) {
7063 report("Too many arguments");
7064 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
7065 report("Failed to format command");
7067 open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7075 case REQ_SEARCH_BACK:
7077 const char *prompt = request == REQ_SEARCH ? "/" : "?";
7078 char *search = read_prompt(prompt);
7081 string_ncopy(opt_search, search, strlen(search));
7082 else if (*opt_search)
7083 request = request == REQ_SEARCH ?