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);
143 static void foreach_ref(bool (*visitor)(void *data, struct ref *ref), void *data);
146 FORMAT_ALL, /* Perform replacement in all arguments. */
147 FORMAT_DASH, /* Perform replacement up until "--". */
148 FORMAT_NONE /* No replacement should be performed. */
151 static bool format_argv(const char *dst[], const char *src[], enum format_flags flags);
160 typedef enum input_status (*input_handler)(void *data, char *buf, int c);
162 static char *prompt_input(const char *prompt, input_handler handler, void *data);
163 static bool prompt_yesno(const char *prompt);
166 * Allocation helpers ... Entering macro hell to never be seen again.
169 #define DEFINE_ALLOCATOR(name, type, chunk_size) \
171 name(type **mem, size_t size, size_t increase) \
173 size_t num_chunks = (size + chunk_size - 1) / chunk_size; \
174 size_t num_chunks_new = (size + increase + chunk_size - 1) / chunk_size;\
177 if (mem == NULL || num_chunks != num_chunks_new) { \
178 tmp = realloc(tmp, num_chunks_new * chunk_size * sizeof(type)); \
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)
630 DEFINE_ALLOCATOR(realloc_io_buf, char, BUFSIZ)
633 io_get(struct io *io, int c, bool can_read)
639 if (io->bufsize > 0) {
640 eol = memchr(io->bufpos, c, io->bufsize);
642 char *line = io->bufpos;
645 io->bufpos = eol + 1;
646 io->bufsize -= io->bufpos - line;
653 io->bufpos[io->bufsize] = 0;
663 if (io->bufsize > 0 && io->bufpos > io->buf)
664 memmove(io->buf, io->bufpos, io->bufsize);
666 if (io->bufalloc == io->bufsize) {
667 if (!realloc_io_buf(&io->buf, io->bufalloc, BUFSIZ))
669 io->bufalloc += BUFSIZ;
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)
703 char *result = io_get(io, '\n', TRUE);
706 result = chomp_string(result);
707 string_ncopy_do(buf, bufsize, result, strlen(result));
710 return done_io(io) && result;
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_BRANCH, "Show branch view"), \
784 REQ_(VIEW_HELP, "Show help page"), \
785 REQ_(VIEW_PAGER, "Show pager view"), \
786 REQ_(VIEW_STATUS, "Show status view"), \
787 REQ_(VIEW_STAGE, "Show stage view"), \
789 REQ_GROUP("View manipulation") \
790 REQ_(ENTER, "Enter current line and scroll"), \
791 REQ_(NEXT, "Move to next"), \
792 REQ_(PREVIOUS, "Move to previous"), \
793 REQ_(PARENT, "Move to parent"), \
794 REQ_(VIEW_NEXT, "Move focus to next view"), \
795 REQ_(REFRESH, "Reload and refresh"), \
796 REQ_(MAXIMIZE, "Maximize the current view"), \
797 REQ_(VIEW_CLOSE, "Close the current view"), \
798 REQ_(QUIT, "Close all views and quit"), \
800 REQ_GROUP("View specific requests") \
801 REQ_(STATUS_UPDATE, "Update file status"), \
802 REQ_(STATUS_REVERT, "Revert file changes"), \
803 REQ_(STATUS_MERGE, "Merge file using external tool"), \
804 REQ_(STAGE_NEXT, "Find next chunk to stage"), \
806 REQ_GROUP("Cursor navigation") \
807 REQ_(MOVE_UP, "Move cursor one line up"), \
808 REQ_(MOVE_DOWN, "Move cursor one line down"), \
809 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
810 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
811 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
812 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
814 REQ_GROUP("Scrolling") \
815 REQ_(SCROLL_LEFT, "Scroll two columns left"), \
816 REQ_(SCROLL_RIGHT, "Scroll two columns right"), \
817 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
818 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
819 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
820 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
822 REQ_GROUP("Searching") \
823 REQ_(SEARCH, "Search the view"), \
824 REQ_(SEARCH_BACK, "Search backwards in the view"), \
825 REQ_(FIND_NEXT, "Find next search match"), \
826 REQ_(FIND_PREV, "Find previous search match"), \
828 REQ_GROUP("Option manipulation") \
829 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
830 REQ_(TOGGLE_DATE, "Toggle date display"), \
831 REQ_(TOGGLE_AUTHOR, "Toggle author display"), \
832 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
833 REQ_(TOGGLE_REFS, "Toggle reference display (tags/branches)"), \
834 REQ_(TOGGLE_SORT_ORDER, "Toggle ascending/descending sort order"), \
835 REQ_(TOGGLE_SORT_FIELD, "Toggle field to sort by"), \
838 REQ_(PROMPT, "Bring up the prompt"), \
839 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
840 REQ_(SHOW_VERSION, "Show version information"), \
841 REQ_(STOP_LOADING, "Stop all loading views"), \
842 REQ_(EDIT, "Open in editor"), \
843 REQ_(NONE, "Do nothing")
846 /* User action requests. */
848 #define REQ_GROUP(help)
849 #define REQ_(req, help) REQ_##req
851 /* Offset all requests to avoid conflicts with ncurses getch values. */
852 REQ_OFFSET = KEY_MAX + 1,
859 struct request_info {
860 enum request request;
866 static const struct request_info req_info[] = {
867 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
868 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
875 get_request(const char *name)
877 int namelen = strlen(name);
880 for (i = 0; i < ARRAY_SIZE(req_info); i++)
881 if (req_info[i].namelen == namelen &&
882 !string_enum_compare(req_info[i].name, name, namelen))
883 return req_info[i].request;
893 /* Option and state variables. */
894 static bool opt_date = TRUE;
895 static bool opt_author = TRUE;
896 static bool opt_line_number = FALSE;
897 static bool opt_line_graphics = TRUE;
898 static bool opt_rev_graph = FALSE;
899 static bool opt_show_refs = TRUE;
900 static int opt_num_interval = NUMBER_INTERVAL;
901 static double opt_hscroll = 0.50;
902 static int opt_tab_size = TAB_SIZE;
903 static int opt_author_cols = AUTHOR_COLS-1;
904 static char opt_path[SIZEOF_STR] = "";
905 static char opt_file[SIZEOF_STR] = "";
906 static char opt_ref[SIZEOF_REF] = "";
907 static char opt_head[SIZEOF_REF] = "";
908 static char opt_head_rev[SIZEOF_REV] = "";
909 static char opt_remote[SIZEOF_REF] = "";
910 static char opt_encoding[20] = "UTF-8";
911 static bool opt_utf8 = TRUE;
912 static char opt_codeset[20] = "UTF-8";
913 static iconv_t opt_iconv = ICONV_NONE;
914 static char opt_search[SIZEOF_STR] = "";
915 static char opt_cdup[SIZEOF_STR] = "";
916 static char opt_prefix[SIZEOF_STR] = "";
917 static char opt_git_dir[SIZEOF_STR] = "";
918 static signed char opt_is_inside_work_tree = -1; /* set to TRUE or FALSE */
919 static char opt_editor[SIZEOF_STR] = "";
920 static FILE *opt_tty = NULL;
922 #define is_initial_commit() (!*opt_head_rev)
923 #define is_head_commit(rev) (!strcmp((rev), "HEAD") || !strcmp(opt_head_rev, (rev)))
927 * Line-oriented content detection.
931 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
932 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
933 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
934 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
935 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
936 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
937 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
938 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
939 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
940 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
941 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
942 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
943 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
944 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
945 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
946 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
947 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
948 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
949 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
950 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
951 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
952 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
953 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
954 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
955 LINE(AUTHOR, "author ", COLOR_GREEN, COLOR_DEFAULT, 0), \
956 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
957 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
958 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
959 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
960 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
961 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
962 LINE(DELIMITER, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
963 LINE(DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
964 LINE(MODE, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
965 LINE(LINE_NUMBER, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
966 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
967 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
968 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
969 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
970 LINE(MAIN_LOCAL_TAG,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
971 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
972 LINE(MAIN_TRACKED, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
973 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
974 LINE(MAIN_HEAD, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
975 LINE(MAIN_REVGRAPH,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
976 LINE(TREE_HEAD, "", COLOR_DEFAULT, COLOR_DEFAULT, A_BOLD), \
977 LINE(TREE_DIR, "", COLOR_YELLOW, COLOR_DEFAULT, A_NORMAL), \
978 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
979 LINE(STAT_HEAD, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
980 LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
981 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
982 LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
983 LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
984 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
985 LINE(BLAME_ID, "", COLOR_MAGENTA, COLOR_DEFAULT, 0)
988 #define LINE(type, line, fg, bg, attr) \
996 const char *name; /* Option name. */
997 int namelen; /* Size of option name. */
998 const char *line; /* The start of line to match. */
999 int linelen; /* Size of string to match. */
1000 int fg, bg, attr; /* Color and text attributes for the lines. */
1003 static struct line_info line_info[] = {
1004 #define LINE(type, line, fg, bg, attr) \
1005 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1010 static enum line_type
1011 get_line_type(const char *line)
1013 int linelen = strlen(line);
1014 enum line_type type;
1016 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1017 /* Case insensitive search matches Signed-off-by lines better. */
1018 if (linelen >= line_info[type].linelen &&
1019 !strncasecmp(line_info[type].line, line, line_info[type].linelen))
1022 return LINE_DEFAULT;
1026 get_line_attr(enum line_type type)
1028 assert(type < ARRAY_SIZE(line_info));
1029 return COLOR_PAIR(type) | line_info[type].attr;
1032 static struct line_info *
1033 get_line_info(const char *name)
1035 size_t namelen = strlen(name);
1036 enum line_type type;
1038 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1039 if (namelen == line_info[type].namelen &&
1040 !string_enum_compare(line_info[type].name, name, namelen))
1041 return &line_info[type];
1049 int default_bg = line_info[LINE_DEFAULT].bg;
1050 int default_fg = line_info[LINE_DEFAULT].fg;
1051 enum line_type type;
1055 if (assume_default_colors(default_fg, default_bg) == ERR) {
1056 default_bg = COLOR_BLACK;
1057 default_fg = COLOR_WHITE;
1060 for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1061 struct line_info *info = &line_info[type];
1062 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1063 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1065 init_pair(type, fg, bg);
1070 enum line_type type;
1073 unsigned int selected:1;
1074 unsigned int dirty:1;
1075 unsigned int cleareol:1;
1077 void *data; /* User data */
1087 enum request request;
1090 static const struct keybinding default_keybindings[] = {
1091 /* View switching */
1092 { 'm', REQ_VIEW_MAIN },
1093 { 'd', REQ_VIEW_DIFF },
1094 { 'l', REQ_VIEW_LOG },
1095 { 't', REQ_VIEW_TREE },
1096 { 'f', REQ_VIEW_BLOB },
1097 { 'B', REQ_VIEW_BLAME },
1098 { 'H', REQ_VIEW_BRANCH },
1099 { 'p', REQ_VIEW_PAGER },
1100 { 'h', REQ_VIEW_HELP },
1101 { 'S', REQ_VIEW_STATUS },
1102 { 'c', REQ_VIEW_STAGE },
1104 /* View manipulation */
1105 { 'q', REQ_VIEW_CLOSE },
1106 { KEY_TAB, REQ_VIEW_NEXT },
1107 { KEY_RETURN, REQ_ENTER },
1108 { KEY_UP, REQ_PREVIOUS },
1109 { KEY_DOWN, REQ_NEXT },
1110 { 'R', REQ_REFRESH },
1111 { KEY_F(5), REQ_REFRESH },
1112 { 'O', REQ_MAXIMIZE },
1114 /* Cursor navigation */
1115 { 'k', REQ_MOVE_UP },
1116 { 'j', REQ_MOVE_DOWN },
1117 { KEY_HOME, REQ_MOVE_FIRST_LINE },
1118 { KEY_END, REQ_MOVE_LAST_LINE },
1119 { KEY_NPAGE, REQ_MOVE_PAGE_DOWN },
1120 { ' ', REQ_MOVE_PAGE_DOWN },
1121 { KEY_PPAGE, REQ_MOVE_PAGE_UP },
1122 { 'b', REQ_MOVE_PAGE_UP },
1123 { '-', REQ_MOVE_PAGE_UP },
1126 { KEY_LEFT, REQ_SCROLL_LEFT },
1127 { KEY_RIGHT, REQ_SCROLL_RIGHT },
1128 { KEY_IC, REQ_SCROLL_LINE_UP },
1129 { KEY_DC, REQ_SCROLL_LINE_DOWN },
1130 { 'w', REQ_SCROLL_PAGE_UP },
1131 { 's', REQ_SCROLL_PAGE_DOWN },
1134 { '/', REQ_SEARCH },
1135 { '?', REQ_SEARCH_BACK },
1136 { 'n', REQ_FIND_NEXT },
1137 { 'N', REQ_FIND_PREV },
1141 { 'z', REQ_STOP_LOADING },
1142 { 'v', REQ_SHOW_VERSION },
1143 { 'r', REQ_SCREEN_REDRAW },
1144 { '.', REQ_TOGGLE_LINENO },
1145 { 'D', REQ_TOGGLE_DATE },
1146 { 'A', REQ_TOGGLE_AUTHOR },
1147 { 'g', REQ_TOGGLE_REV_GRAPH },
1148 { 'F', REQ_TOGGLE_REFS },
1149 { 'I', REQ_TOGGLE_SORT_ORDER },
1150 { 'i', REQ_TOGGLE_SORT_FIELD },
1151 { ':', REQ_PROMPT },
1152 { 'u', REQ_STATUS_UPDATE },
1153 { '!', REQ_STATUS_REVERT },
1154 { 'M', REQ_STATUS_MERGE },
1155 { '@', REQ_STAGE_NEXT },
1156 { ',', REQ_PARENT },
1160 #define KEYMAP_INFO \
1175 #define KEYMAP_(name) KEYMAP_##name
1180 static const struct enum_map keymap_table[] = {
1181 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1186 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1188 struct keybinding_table {
1189 struct keybinding *data;
1193 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1196 add_keybinding(enum keymap keymap, enum request request, int key)
1198 struct keybinding_table *table = &keybindings[keymap];
1200 table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1202 die("Failed to allocate keybinding");
1203 table->data[table->size].alias = key;
1204 table->data[table->size++].request = request;
1207 /* Looks for a key binding first in the given map, then in the generic map, and
1208 * lastly in the default keybindings. */
1210 get_keybinding(enum keymap keymap, int key)
1214 for (i = 0; i < keybindings[keymap].size; i++)
1215 if (keybindings[keymap].data[i].alias == key)
1216 return keybindings[keymap].data[i].request;
1218 for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1219 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1220 return keybindings[KEYMAP_GENERIC].data[i].request;
1222 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1223 if (default_keybindings[i].alias == key)
1224 return default_keybindings[i].request;
1226 return (enum request) key;
1235 static const struct key key_table[] = {
1236 { "Enter", KEY_RETURN },
1238 { "Backspace", KEY_BACKSPACE },
1240 { "Escape", KEY_ESC },
1241 { "Left", KEY_LEFT },
1242 { "Right", KEY_RIGHT },
1244 { "Down", KEY_DOWN },
1245 { "Insert", KEY_IC },
1246 { "Delete", KEY_DC },
1248 { "Home", KEY_HOME },
1250 { "PageUp", KEY_PPAGE },
1251 { "PageDown", KEY_NPAGE },
1261 { "F10", KEY_F(10) },
1262 { "F11", KEY_F(11) },
1263 { "F12", KEY_F(12) },
1267 get_key_value(const char *name)
1271 for (i = 0; i < ARRAY_SIZE(key_table); i++)
1272 if (!strcasecmp(key_table[i].name, name))
1273 return key_table[i].value;
1275 if (strlen(name) == 1 && isprint(*name))
1282 get_key_name(int key_value)
1284 static char key_char[] = "'X'";
1285 const char *seq = NULL;
1288 for (key = 0; key < ARRAY_SIZE(key_table); key++)
1289 if (key_table[key].value == key_value)
1290 seq = key_table[key].name;
1294 isprint(key_value)) {
1295 key_char[1] = (char) key_value;
1299 return seq ? seq : "(no key)";
1303 get_key(enum request request)
1305 static char buf[BUFSIZ];
1312 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1313 const struct keybinding *keybinding = &default_keybindings[i];
1315 if (keybinding->request != request)
1318 if (!string_format_from(buf, &pos, "%s%s", sep,
1319 get_key_name(keybinding->alias)))
1320 return "Too many keybindings!";
1327 struct run_request {
1330 const char *argv[SIZEOF_ARG];
1333 static struct run_request *run_request;
1334 static size_t run_requests;
1336 DEFINE_ALLOCATOR(realloc_run_requests, struct run_request, 8)
1339 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1341 struct run_request *req;
1343 if (argc >= ARRAY_SIZE(req->argv) - 1)
1346 if (!realloc_run_requests(&run_request, run_requests, 1))
1349 req = &run_request[run_requests];
1350 req->keymap = keymap;
1352 req->argv[0] = NULL;
1354 if (!format_argv(req->argv, argv, FORMAT_NONE))
1357 return REQ_NONE + ++run_requests;
1360 static struct run_request *
1361 get_run_request(enum request request)
1363 if (request <= REQ_NONE)
1365 return &run_request[request - REQ_NONE - 1];
1369 add_builtin_run_requests(void)
1371 const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1372 const char *commit[] = { "git", "commit", NULL };
1373 const char *gc[] = { "git", "gc", NULL };
1380 { KEYMAP_MAIN, 'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1381 { KEYMAP_STATUS, 'C', ARRAY_SIZE(commit) - 1, commit },
1382 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1386 for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1389 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1390 if (req != REQ_NONE)
1391 add_keybinding(reqs[i].keymap, req, reqs[i].key);
1396 * User config file handling.
1399 static int config_lineno;
1400 static bool config_errors;
1401 static const char *config_msg;
1403 static const struct enum_map color_map[] = {
1404 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1416 static const struct enum_map attr_map[] = {
1417 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1424 ATTR_MAP(UNDERLINE),
1427 #define set_attribute(attr, name) map_enum(attr, attr_map, name)
1429 static int parse_step(double *opt, const char *arg)
1432 if (!strchr(arg, '%'))
1435 /* "Shift down" so 100% and 1 does not conflict. */
1436 *opt = (*opt - 1) / 100;
1439 config_msg = "Step value larger than 100%";
1444 config_msg = "Invalid step value";
1451 parse_int(int *opt, const char *arg, int min, int max)
1453 int value = atoi(arg);
1455 if (min <= value && value <= max) {
1460 config_msg = "Integer value out of bound";
1465 set_color(int *color, const char *name)
1467 if (map_enum(color, color_map, name))
1469 if (!prefixcmp(name, "color"))
1470 return parse_int(color, name + 5, 0, 255) == OK;
1474 /* Wants: object fgcolor bgcolor [attribute] */
1476 option_color_command(int argc, const char *argv[])
1478 struct line_info *info;
1480 if (argc != 3 && argc != 4) {
1481 config_msg = "Wrong number of arguments given to color command";
1485 info = get_line_info(argv[0]);
1487 static const struct enum_map obsolete[] = {
1488 ENUM_MAP("main-delim", LINE_DELIMITER),
1489 ENUM_MAP("main-date", LINE_DATE),
1490 ENUM_MAP("main-author", LINE_AUTHOR),
1494 if (!map_enum(&index, obsolete, argv[0])) {
1495 config_msg = "Unknown color name";
1498 info = &line_info[index];
1501 if (!set_color(&info->fg, argv[1]) ||
1502 !set_color(&info->bg, argv[2])) {
1503 config_msg = "Unknown color";
1507 if (argc == 4 && !set_attribute(&info->attr, argv[3])) {
1508 config_msg = "Unknown attribute";
1515 static int parse_bool(bool *opt, const char *arg)
1517 *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1523 parse_string(char *opt, const char *arg, size_t optsize)
1525 int arglen = strlen(arg);
1530 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1531 config_msg = "Unmatched quotation";
1534 arg += 1; arglen -= 2;
1536 string_ncopy_do(opt, optsize, arg, arglen);
1541 /* Wants: name = value */
1543 option_set_command(int argc, const char *argv[])
1546 config_msg = "Wrong number of arguments given to set command";
1550 if (strcmp(argv[1], "=")) {
1551 config_msg = "No value assigned";
1555 if (!strcmp(argv[0], "show-author"))
1556 return parse_bool(&opt_author, argv[2]);
1558 if (!strcmp(argv[0], "show-date"))
1559 return parse_bool(&opt_date, argv[2]);
1561 if (!strcmp(argv[0], "show-rev-graph"))
1562 return parse_bool(&opt_rev_graph, argv[2]);
1564 if (!strcmp(argv[0], "show-refs"))
1565 return parse_bool(&opt_show_refs, argv[2]);
1567 if (!strcmp(argv[0], "show-line-numbers"))
1568 return parse_bool(&opt_line_number, argv[2]);
1570 if (!strcmp(argv[0], "line-graphics"))
1571 return parse_bool(&opt_line_graphics, argv[2]);
1573 if (!strcmp(argv[0], "line-number-interval"))
1574 return parse_int(&opt_num_interval, argv[2], 1, 1024);
1576 if (!strcmp(argv[0], "author-width"))
1577 return parse_int(&opt_author_cols, argv[2], 0, 1024);
1579 if (!strcmp(argv[0], "horizontal-scroll"))
1580 return parse_step(&opt_hscroll, argv[2]);
1582 if (!strcmp(argv[0], "tab-size"))
1583 return parse_int(&opt_tab_size, argv[2], 1, 1024);
1585 if (!strcmp(argv[0], "commit-encoding"))
1586 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
1588 config_msg = "Unknown variable name";
1592 /* Wants: mode request key */
1594 option_bind_command(int argc, const char *argv[])
1596 enum request request;
1601 config_msg = "Wrong number of arguments given to bind command";
1605 if (set_keymap(&keymap, argv[0]) == ERR) {
1606 config_msg = "Unknown key map";
1610 key = get_key_value(argv[1]);
1612 config_msg = "Unknown key";
1616 request = get_request(argv[2]);
1617 if (request == REQ_NONE) {
1618 static const struct enum_map obsolete[] = {
1619 ENUM_MAP("cherry-pick", REQ_NONE),
1620 ENUM_MAP("screen-resize", REQ_NONE),
1621 ENUM_MAP("tree-parent", REQ_PARENT),
1625 if (map_enum(&alias, obsolete, argv[2])) {
1626 if (alias != REQ_NONE)
1627 add_keybinding(keymap, alias, key);
1628 config_msg = "Obsolete request name";
1632 if (request == REQ_NONE && *argv[2]++ == '!')
1633 request = add_run_request(keymap, key, argc - 2, argv + 2);
1634 if (request == REQ_NONE) {
1635 config_msg = "Unknown request name";
1639 add_keybinding(keymap, request, key);
1645 set_option(const char *opt, char *value)
1647 const char *argv[SIZEOF_ARG];
1650 if (!argv_from_string(argv, &argc, value)) {
1651 config_msg = "Too many option arguments";
1655 if (!strcmp(opt, "color"))
1656 return option_color_command(argc, argv);
1658 if (!strcmp(opt, "set"))
1659 return option_set_command(argc, argv);
1661 if (!strcmp(opt, "bind"))
1662 return option_bind_command(argc, argv);
1664 config_msg = "Unknown option command";
1669 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1674 config_msg = "Internal error";
1676 /* Check for comment markers, since read_properties() will
1677 * only ensure opt and value are split at first " \t". */
1678 optlen = strcspn(opt, "#");
1682 if (opt[optlen] != 0) {
1683 config_msg = "No option value";
1687 /* Look for comment endings in the value. */
1688 size_t len = strcspn(value, "#");
1690 if (len < valuelen) {
1692 value[valuelen] = 0;
1695 status = set_option(opt, value);
1698 if (status == ERR) {
1699 warn("Error on line %d, near '%.*s': %s",
1700 config_lineno, (int) optlen, opt, config_msg);
1701 config_errors = TRUE;
1704 /* Always keep going if errors are encountered. */
1709 load_option_file(const char *path)
1713 /* It's OK that the file doesn't exist. */
1714 if (!io_open(&io, path))
1718 config_errors = FALSE;
1720 if (io_load(&io, " \t", read_option) == ERR ||
1721 config_errors == TRUE)
1722 warn("Errors while loading %s.", path);
1728 const char *home = getenv("HOME");
1729 const char *tigrc_user = getenv("TIGRC_USER");
1730 const char *tigrc_system = getenv("TIGRC_SYSTEM");
1731 char buf[SIZEOF_STR];
1733 add_builtin_run_requests();
1736 tigrc_system = SYSCONFDIR "/tigrc";
1737 load_option_file(tigrc_system);
1740 if (!home || !string_format(buf, "%s/.tigrc", home))
1744 load_option_file(tigrc_user);
1757 /* The display array of active views and the index of the current view. */
1758 static struct view *display[2];
1759 static unsigned int current_view;
1761 #define foreach_displayed_view(view, i) \
1762 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1764 #define displayed_views() (display[1] != NULL ? 2 : 1)
1766 /* Current head and commit ID */
1767 static char ref_blob[SIZEOF_REF] = "";
1768 static char ref_commit[SIZEOF_REF] = "HEAD";
1769 static char ref_head[SIZEOF_REF] = "HEAD";
1772 const char *name; /* View name */
1773 const char *cmd_env; /* Command line set via environment */
1774 const char *id; /* Points to either of ref_{head,commit,blob} */
1776 struct view_ops *ops; /* View operations */
1778 enum keymap keymap; /* What keymap does this view have */
1779 bool git_dir; /* Whether the view requires a git directory. */
1781 char ref[SIZEOF_REF]; /* Hovered commit reference */
1782 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
1784 int height, width; /* The width and height of the main window */
1785 WINDOW *win; /* The main window */
1786 WINDOW *title; /* The title window living below the main window */
1789 unsigned long offset; /* Offset of the window top */
1790 unsigned long yoffset; /* Offset from the window side. */
1791 unsigned long lineno; /* Current line number */
1792 unsigned long p_offset; /* Previous offset of the window top */
1793 unsigned long p_yoffset;/* Previous offset from the window side */
1794 unsigned long p_lineno; /* Previous current line number */
1795 bool p_restore; /* Should the previous position be restored. */
1798 char grep[SIZEOF_STR]; /* Search string */
1799 regex_t *regex; /* Pre-compiled regexp */
1801 /* If non-NULL, points to the view that opened this view. If this view
1802 * is closed tig will switch back to the parent view. */
1803 struct view *parent;
1806 size_t lines; /* Total number of lines */
1807 struct line *line; /* Line index */
1808 unsigned int digits; /* Number of digits in the lines member. */
1811 struct line *curline; /* Line currently being drawn. */
1812 enum line_type curtype; /* Attribute currently used for drawing. */
1813 unsigned long col; /* Column when drawing. */
1814 bool has_scrolled; /* View was scrolled. */
1824 /* What type of content being displayed. Used in the title bar. */
1826 /* Default command arguments. */
1828 /* Open and reads in all view content. */
1829 bool (*open)(struct view *view);
1830 /* Read one line; updates view->line. */
1831 bool (*read)(struct view *view, char *data);
1832 /* Draw one line; @lineno must be < view->height. */
1833 bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
1834 /* Depending on view handle a special requests. */
1835 enum request (*request)(struct view *view, enum request request, struct line *line);
1836 /* Search for regexp in a line. */
1837 bool (*grep)(struct view *view, struct line *line);
1839 void (*select)(struct view *view, struct line *line);
1842 static struct view_ops blame_ops;
1843 static struct view_ops blob_ops;
1844 static struct view_ops diff_ops;
1845 static struct view_ops help_ops;
1846 static struct view_ops log_ops;
1847 static struct view_ops main_ops;
1848 static struct view_ops pager_ops;
1849 static struct view_ops stage_ops;
1850 static struct view_ops status_ops;
1851 static struct view_ops tree_ops;
1852 static struct view_ops branch_ops;
1854 #define VIEW_STR(name, env, ref, ops, map, git) \
1855 { name, #env, ref, ops, map, git }
1857 #define VIEW_(id, name, ops, git, ref) \
1858 VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
1861 static struct view views[] = {
1862 VIEW_(MAIN, "main", &main_ops, TRUE, ref_head),
1863 VIEW_(DIFF, "diff", &diff_ops, TRUE, ref_commit),
1864 VIEW_(LOG, "log", &log_ops, TRUE, ref_head),
1865 VIEW_(TREE, "tree", &tree_ops, TRUE, ref_commit),
1866 VIEW_(BLOB, "blob", &blob_ops, TRUE, ref_blob),
1867 VIEW_(BLAME, "blame", &blame_ops, TRUE, ref_commit),
1868 VIEW_(BRANCH, "branch", &branch_ops, TRUE, ref_head),
1869 VIEW_(HELP, "help", &help_ops, FALSE, ""),
1870 VIEW_(PAGER, "pager", &pager_ops, FALSE, "stdin"),
1871 VIEW_(STATUS, "status", &status_ops, TRUE, ""),
1872 VIEW_(STAGE, "stage", &stage_ops, TRUE, ""),
1875 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
1876 #define VIEW_REQ(view) ((view) - views + REQ_OFFSET + 1)
1878 #define foreach_view(view, i) \
1879 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1881 #define view_is_displayed(view) \
1882 (view == display[0] || view == display[1])
1889 static chtype line_graphics[] = {
1890 /* LINE_GRAPHIC_VLINE: */ '|'
1894 set_view_attr(struct view *view, enum line_type type)
1896 if (!view->curline->selected && view->curtype != type) {
1897 wattrset(view->win, get_line_attr(type));
1898 wchgat(view->win, -1, 0, type, NULL);
1899 view->curtype = type;
1904 draw_chars(struct view *view, enum line_type type, const char *string,
1905 int max_len, bool use_tilde)
1909 int trimmed = FALSE;
1910 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
1916 len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde);
1918 col = len = strlen(string);
1919 if (len > max_len) {
1923 col = len = max_len;
1928 set_view_attr(view, type);
1930 waddnstr(view->win, string, len);
1931 if (trimmed && use_tilde) {
1932 set_view_attr(view, LINE_DELIMITER);
1933 waddch(view->win, '~');
1941 draw_space(struct view *view, enum line_type type, int max, int spaces)
1943 static char space[] = " ";
1946 spaces = MIN(max, spaces);
1948 while (spaces > 0) {
1949 int len = MIN(spaces, sizeof(space) - 1);
1951 col += draw_chars(view, type, space, len, FALSE);
1959 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
1961 view->col += draw_chars(view, type, string, view->width + view->yoffset - view->col, trim);
1962 return view->width + view->yoffset <= view->col;
1966 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
1968 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
1969 int max = view->width + view->yoffset - view->col;
1975 set_view_attr(view, type);
1976 /* Using waddch() instead of waddnstr() ensures that
1977 * they'll be rendered correctly for the cursor line. */
1978 for (i = skip; i < size; i++)
1979 waddch(view->win, graphic[i]);
1982 if (size < max && skip <= size)
1983 waddch(view->win, ' ');
1986 return view->width + view->yoffset <= view->col;
1990 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
1992 int max = MIN(view->width + view->yoffset - view->col, len);
1996 col = draw_chars(view, type, text, max - 1, trim);
1998 col = draw_space(view, type, max - 1, max - 1);
2001 view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
2002 return view->width + view->yoffset <= view->col;
2006 draw_date(struct view *view, time_t *time)
2008 const char *date = mkdate(time);
2010 return draw_field(view, LINE_DATE, date, DATE_COLS, FALSE);
2014 draw_author(struct view *view, const char *author)
2016 bool trim = opt_author_cols == 0 || opt_author_cols > 5 || !author;
2019 static char initials[10];
2022 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@')
2024 memset(initials, 0, sizeof(initials));
2025 for (pos = 0; *author && pos < opt_author_cols - 1; author++, pos++) {
2026 while (is_initial_sep(*author))
2028 strncpy(&initials[pos], author, sizeof(initials) - 1 - pos);
2029 while (*author && !is_initial_sep(author[1]))
2036 return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2040 draw_mode(struct view *view, mode_t mode)
2046 else if (S_ISLNK(mode))
2048 else if (S_ISGITLINK(mode))
2050 else if (S_ISREG(mode) && mode & S_IXUSR)
2052 else if (S_ISREG(mode))
2057 return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2061 draw_lineno(struct view *view, unsigned int lineno)
2064 int digits3 = view->digits < 3 ? 3 : view->digits;
2065 int max = MIN(view->width + view->yoffset - view->col, digits3);
2068 lineno += view->offset + 1;
2069 if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2070 static char fmt[] = "%1ld";
2072 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2073 if (string_format(number, fmt, lineno))
2077 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2079 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2080 return draw_graphic(view, LINE_DEFAULT, &line_graphics[LINE_GRAPHIC_VLINE], 1);
2084 draw_view_line(struct view *view, unsigned int lineno)
2087 bool selected = (view->offset + lineno == view->lineno);
2089 assert(view_is_displayed(view));
2091 if (view->offset + lineno >= view->lines)
2094 line = &view->line[view->offset + lineno];
2096 wmove(view->win, lineno, 0);
2098 wclrtoeol(view->win);
2100 view->curline = line;
2101 view->curtype = LINE_NONE;
2102 line->selected = FALSE;
2103 line->dirty = line->cleareol = 0;
2106 set_view_attr(view, LINE_CURSOR);
2107 line->selected = TRUE;
2108 view->ops->select(view, line);
2111 return view->ops->draw(view, line, lineno);
2115 redraw_view_dirty(struct view *view)
2120 for (lineno = 0; lineno < view->height; lineno++) {
2121 if (view->offset + lineno >= view->lines)
2123 if (!view->line[view->offset + lineno].dirty)
2126 if (!draw_view_line(view, lineno))
2132 wnoutrefresh(view->win);
2136 redraw_view_from(struct view *view, int lineno)
2138 assert(0 <= lineno && lineno < view->height);
2140 for (; lineno < view->height; lineno++) {
2141 if (!draw_view_line(view, lineno))
2145 wnoutrefresh(view->win);
2149 redraw_view(struct view *view)
2152 redraw_view_from(view, 0);
2157 update_view_title(struct view *view)
2159 char buf[SIZEOF_STR];
2160 char state[SIZEOF_STR];
2161 size_t bufpos = 0, statelen = 0;
2163 assert(view_is_displayed(view));
2165 if (view != VIEW(REQ_VIEW_STATUS) && view->lines) {
2166 unsigned int view_lines = view->offset + view->height;
2167 unsigned int lines = view->lines
2168 ? MIN(view_lines, view->lines) * 100 / view->lines
2171 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2180 time_t secs = time(NULL) - view->start_time;
2182 /* Three git seconds are a long time ... */
2184 string_format_from(state, &statelen, " loading %lds", secs);
2187 string_format_from(buf, &bufpos, "[%s]", view->name);
2188 if (*view->ref && bufpos < view->width) {
2189 size_t refsize = strlen(view->ref);
2190 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2192 if (minsize < view->width)
2193 refsize = view->width - minsize + 7;
2194 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2197 if (statelen && bufpos < view->width) {
2198 string_format_from(buf, &bufpos, "%s", state);
2201 if (view == display[current_view])
2202 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2204 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2206 mvwaddnstr(view->title, 0, 0, buf, bufpos);
2207 wclrtoeol(view->title);
2208 wnoutrefresh(view->title);
2212 resize_display(void)
2215 struct view *base = display[0];
2216 struct view *view = display[1] ? display[1] : display[0];
2218 /* Setup window dimensions */
2220 getmaxyx(stdscr, base->height, base->width);
2222 /* Make room for the status window. */
2226 /* Horizontal split. */
2227 view->width = base->width;
2228 view->height = SCALE_SPLIT_VIEW(base->height);
2229 base->height -= view->height;
2231 /* Make room for the title bar. */
2235 /* Make room for the title bar. */
2240 foreach_displayed_view (view, i) {
2242 view->win = newwin(view->height, 0, offset, 0);
2244 die("Failed to create %s view", view->name);
2246 scrollok(view->win, FALSE);
2248 view->title = newwin(1, 0, offset + view->height, 0);
2250 die("Failed to create title window");
2253 wresize(view->win, view->height, view->width);
2254 mvwin(view->win, offset, 0);
2255 mvwin(view->title, offset + view->height, 0);
2258 offset += view->height + 1;
2263 redraw_display(bool clear)
2268 foreach_displayed_view (view, i) {
2272 update_view_title(view);
2277 toggle_view_option(bool *option, const char *help)
2280 redraw_display(FALSE);
2281 report("%sabling %s", *option ? "En" : "Dis", help);
2285 maximize_view(struct view *view)
2287 memset(display, 0, sizeof(display));
2289 display[current_view] = view;
2291 redraw_display(FALSE);
2301 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2303 if (lineno >= view->lines)
2304 lineno = view->lines > 0 ? view->lines - 1 : 0;
2306 if (offset > lineno || offset + view->height <= lineno) {
2307 unsigned long half = view->height / 2;
2310 offset = lineno - half;
2315 if (offset != view->offset || lineno != view->lineno) {
2316 view->offset = offset;
2317 view->lineno = lineno;
2325 apply_step(double step, int value)
2329 value *= step + 0.01;
2330 return value ? value : 1;
2333 /* Scrolling backend */
2335 do_scroll_view(struct view *view, int lines)
2337 bool redraw_current_line = FALSE;
2339 /* The rendering expects the new offset. */
2340 view->offset += lines;
2342 assert(0 <= view->offset && view->offset < view->lines);
2345 /* Move current line into the view. */
2346 if (view->lineno < view->offset) {
2347 view->lineno = view->offset;
2348 redraw_current_line = TRUE;
2349 } else if (view->lineno >= view->offset + view->height) {
2350 view->lineno = view->offset + view->height - 1;
2351 redraw_current_line = TRUE;
2354 assert(view->offset <= view->lineno && view->lineno < view->lines);
2356 /* Redraw the whole screen if scrolling is pointless. */
2357 if (view->height < ABS(lines)) {
2361 int line = lines > 0 ? view->height - lines : 0;
2362 int end = line + ABS(lines);
2364 scrollok(view->win, TRUE);
2365 wscrl(view->win, lines);
2366 scrollok(view->win, FALSE);
2368 while (line < end && draw_view_line(view, line))
2371 if (redraw_current_line)
2372 draw_view_line(view, view->lineno - view->offset);
2373 wnoutrefresh(view->win);
2376 view->has_scrolled = TRUE;
2380 /* Scroll frontend */
2382 scroll_view(struct view *view, enum request request)
2386 assert(view_is_displayed(view));
2389 case REQ_SCROLL_LEFT:
2390 if (view->yoffset == 0) {
2391 report("Cannot scroll beyond the first column");
2394 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2397 view->yoffset -= apply_step(opt_hscroll, view->width);
2398 redraw_view_from(view, 0);
2401 case REQ_SCROLL_RIGHT:
2402 view->yoffset += apply_step(opt_hscroll, view->width);
2406 case REQ_SCROLL_PAGE_DOWN:
2407 lines = view->height;
2408 case REQ_SCROLL_LINE_DOWN:
2409 if (view->offset + lines > view->lines)
2410 lines = view->lines - view->offset;
2412 if (lines == 0 || view->offset + view->height >= view->lines) {
2413 report("Cannot scroll beyond the last line");
2418 case REQ_SCROLL_PAGE_UP:
2419 lines = view->height;
2420 case REQ_SCROLL_LINE_UP:
2421 if (lines > view->offset)
2422 lines = view->offset;
2425 report("Cannot scroll beyond the first line");
2433 die("request %d not handled in switch", request);
2436 do_scroll_view(view, lines);
2441 move_view(struct view *view, enum request request)
2443 int scroll_steps = 0;
2447 case REQ_MOVE_FIRST_LINE:
2448 steps = -view->lineno;
2451 case REQ_MOVE_LAST_LINE:
2452 steps = view->lines - view->lineno - 1;
2455 case REQ_MOVE_PAGE_UP:
2456 steps = view->height > view->lineno
2457 ? -view->lineno : -view->height;
2460 case REQ_MOVE_PAGE_DOWN:
2461 steps = view->lineno + view->height >= view->lines
2462 ? view->lines - view->lineno - 1 : view->height;
2474 die("request %d not handled in switch", request);
2477 if (steps <= 0 && view->lineno == 0) {
2478 report("Cannot move beyond the first line");
2481 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2482 report("Cannot move beyond the last line");
2486 /* Move the current line */
2487 view->lineno += steps;
2488 assert(0 <= view->lineno && view->lineno < view->lines);
2490 /* Check whether the view needs to be scrolled */
2491 if (view->lineno < view->offset ||
2492 view->lineno >= view->offset + view->height) {
2493 scroll_steps = steps;
2494 if (steps < 0 && -steps > view->offset) {
2495 scroll_steps = -view->offset;
2497 } else if (steps > 0) {
2498 if (view->lineno == view->lines - 1 &&
2499 view->lines > view->height) {
2500 scroll_steps = view->lines - view->offset - 1;
2501 if (scroll_steps >= view->height)
2502 scroll_steps -= view->height - 1;
2507 if (!view_is_displayed(view)) {
2508 view->offset += scroll_steps;
2509 assert(0 <= view->offset && view->offset < view->lines);
2510 view->ops->select(view, &view->line[view->lineno]);
2514 /* Repaint the old "current" line if we be scrolling */
2515 if (ABS(steps) < view->height)
2516 draw_view_line(view, view->lineno - steps - view->offset);
2519 do_scroll_view(view, scroll_steps);
2523 /* Draw the current line */
2524 draw_view_line(view, view->lineno - view->offset);
2526 wnoutrefresh(view->win);
2535 static void search_view(struct view *view, enum request request);
2538 grep_text(struct view *view, const char *text[])
2543 for (i = 0; text[i]; i++)
2545 regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
2551 select_view_line(struct view *view, unsigned long lineno)
2553 unsigned long old_lineno = view->lineno;
2554 unsigned long old_offset = view->offset;
2556 if (goto_view_line(view, view->offset, lineno)) {
2557 if (view_is_displayed(view)) {
2558 if (old_offset != view->offset) {
2561 draw_view_line(view, old_lineno - view->offset);
2562 draw_view_line(view, view->lineno - view->offset);
2563 wnoutrefresh(view->win);
2566 view->ops->select(view, &view->line[view->lineno]);
2572 find_next(struct view *view, enum request request)
2574 unsigned long lineno = view->lineno;
2579 report("No previous search");
2581 search_view(view, request);
2591 case REQ_SEARCH_BACK:
2600 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2601 lineno += direction;
2603 /* Note, lineno is unsigned long so will wrap around in which case it
2604 * will become bigger than view->lines. */
2605 for (; lineno < view->lines; lineno += direction) {
2606 if (view->ops->grep(view, &view->line[lineno])) {
2607 select_view_line(view, lineno);
2608 report("Line %ld matches '%s'", lineno + 1, view->grep);
2613 report("No match found for '%s'", view->grep);
2617 search_view(struct view *view, enum request request)
2622 regfree(view->regex);
2625 view->regex = calloc(1, sizeof(*view->regex));
2630 regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2631 if (regex_err != 0) {
2632 char buf[SIZEOF_STR] = "unknown error";
2634 regerror(regex_err, view->regex, buf, sizeof(buf));
2635 report("Search failed: %s", buf);
2639 string_copy(view->grep, opt_search);
2641 find_next(view, request);
2645 * Incremental updating
2649 reset_view(struct view *view)
2653 for (i = 0; i < view->lines; i++)
2654 free(view->line[i].data);
2657 view->p_offset = view->offset;
2658 view->p_yoffset = view->yoffset;
2659 view->p_lineno = view->lineno;
2667 view->update_secs = 0;
2671 free_argv(const char *argv[])
2675 for (argc = 0; argv[argc]; argc++)
2676 free((void *) argv[argc]);
2680 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
2682 char buf[SIZEOF_STR];
2684 bool noreplace = flags == FORMAT_NONE;
2686 free_argv(dst_argv);
2688 for (argc = 0; src_argv[argc]; argc++) {
2689 const char *arg = src_argv[argc];
2693 char *next = strstr(arg, "%(");
2694 int len = next - arg;
2697 if (!next || noreplace) {
2698 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
2703 } else if (!prefixcmp(next, "%(directory)")) {
2706 } else if (!prefixcmp(next, "%(file)")) {
2709 } else if (!prefixcmp(next, "%(ref)")) {
2710 value = *opt_ref ? opt_ref : "HEAD";
2712 } else if (!prefixcmp(next, "%(head)")) {
2715 } else if (!prefixcmp(next, "%(commit)")) {
2718 } else if (!prefixcmp(next, "%(blob)")) {
2722 report("Unknown replacement: `%s`", next);
2726 if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
2729 arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
2732 dst_argv[argc] = strdup(buf);
2733 if (!dst_argv[argc])
2737 dst_argv[argc] = NULL;
2739 return src_argv[argc] == NULL;
2743 restore_view_position(struct view *view)
2745 if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
2748 /* Changing the view position cancels the restoring. */
2749 /* FIXME: Changing back to the first line is not detected. */
2750 if (view->offset != 0 || view->lineno != 0) {
2751 view->p_restore = FALSE;
2755 if (goto_view_line(view, view->p_offset, view->p_lineno) &&
2756 view_is_displayed(view))
2759 view->yoffset = view->p_yoffset;
2760 view->p_restore = FALSE;
2766 end_update(struct view *view, bool force)
2770 while (!view->ops->read(view, NULL))
2773 set_nonblocking_input(FALSE);
2775 kill_io(view->pipe);
2776 done_io(view->pipe);
2781 setup_update(struct view *view, const char *vid)
2783 set_nonblocking_input(TRUE);
2785 string_copy_rev(view->vid, vid);
2786 view->pipe = &view->io;
2787 view->start_time = time(NULL);
2791 prepare_update(struct view *view, const char *argv[], const char *dir,
2792 enum format_flags flags)
2795 end_update(view, TRUE);
2796 return init_io_rd(&view->io, argv, dir, flags);
2800 prepare_update_file(struct view *view, const char *name)
2803 end_update(view, TRUE);
2804 return io_open(&view->io, name);
2808 begin_update(struct view *view, bool refresh)
2811 end_update(view, TRUE);
2814 if (!start_io(&view->io))
2818 if (view == VIEW(REQ_VIEW_TREE) && strcmp(view->vid, view->id))
2821 if (!run_io_rd(&view->io, view->ops->argv, FORMAT_ALL))
2824 /* Put the current ref_* value to the view title ref
2825 * member. This is needed by the blob view. Most other
2826 * views sets it automatically after loading because the
2827 * first line is a commit line. */
2828 string_copy_rev(view->ref, view->id);
2831 setup_update(view, view->id);
2837 update_view(struct view *view)
2839 char out_buffer[BUFSIZ * 2];
2841 /* Clear the view and redraw everything since the tree sorting
2842 * might have rearranged things. */
2843 bool redraw = view->lines == 0;
2844 bool can_read = TRUE;
2849 if (!io_can_read(view->pipe)) {
2850 if (view->lines == 0 && view_is_displayed(view)) {
2851 time_t secs = time(NULL) - view->start_time;
2853 if (secs > 1 && secs > view->update_secs) {
2854 if (view->update_secs == 0)
2856 update_view_title(view);
2857 view->update_secs = secs;
2863 for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
2864 if (opt_iconv != ICONV_NONE) {
2865 ICONV_CONST char *inbuf = line;
2866 size_t inlen = strlen(line) + 1;
2868 char *outbuf = out_buffer;
2869 size_t outlen = sizeof(out_buffer);
2873 ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2874 if (ret != (size_t) -1)
2878 if (!view->ops->read(view, line)) {
2879 report("Allocation failure");
2880 end_update(view, TRUE);
2886 unsigned long lines = view->lines;
2889 for (digits = 0; lines; digits++)
2892 /* Keep the displayed view in sync with line number scaling. */
2893 if (digits != view->digits) {
2894 view->digits = digits;
2895 if (opt_line_number || view == VIEW(REQ_VIEW_BLAME))
2900 if (io_error(view->pipe)) {
2901 report("Failed to read: %s", io_strerror(view->pipe));
2902 end_update(view, TRUE);
2904 } else if (io_eof(view->pipe)) {
2906 end_update(view, FALSE);
2909 if (restore_view_position(view))
2912 if (!view_is_displayed(view))
2916 redraw_view_from(view, 0);
2918 redraw_view_dirty(view);
2920 /* Update the title _after_ the redraw so that if the redraw picks up a
2921 * commit reference in view->ref it'll be available here. */
2922 update_view_title(view);
2926 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
2928 static struct line *
2929 add_line_data(struct view *view, void *data, enum line_type type)
2933 if (!realloc_lines(&view->line, view->lines, 1))
2936 line = &view->line[view->lines++];
2937 memset(line, 0, sizeof(*line));
2945 static struct line *
2946 add_line_text(struct view *view, const char *text, enum line_type type)
2948 char *data = text ? strdup(text) : NULL;
2950 return data ? add_line_data(view, data, type) : NULL;
2953 static struct line *
2954 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
2956 char buf[SIZEOF_STR];
2959 va_start(args, fmt);
2960 if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
2964 return buf[0] ? add_line_text(view, buf, type) : NULL;
2972 OPEN_DEFAULT = 0, /* Use default view switching. */
2973 OPEN_SPLIT = 1, /* Split current view. */
2974 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
2975 OPEN_REFRESH = 16, /* Refresh view using previous command. */
2976 OPEN_PREPARED = 32, /* Open already prepared command. */
2980 open_view(struct view *prev, enum request request, enum open_flags flags)
2982 bool split = !!(flags & OPEN_SPLIT);
2983 bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
2984 bool nomaximize = !!(flags & OPEN_REFRESH);
2985 struct view *view = VIEW(request);
2986 int nviews = displayed_views();
2987 struct view *base_view = display[0];
2989 if (view == prev && nviews == 1 && !reload) {
2990 report("Already in %s view", view->name);
2994 if (view->git_dir && !opt_git_dir[0]) {
2995 report("The %s view is disabled in pager view", view->name);
3002 } else if (!nomaximize) {
3003 /* Maximize the current view. */
3004 memset(display, 0, sizeof(display));
3006 display[current_view] = view;
3009 /* Resize the view when switching between split- and full-screen,
3010 * or when switching between two different full-screen views. */
3011 if (nviews != displayed_views() ||
3012 (nviews == 1 && base_view != display[0]))
3015 if (view->ops->open) {
3017 end_update(view, TRUE);
3018 if (!view->ops->open(view)) {
3019 report("Failed to load %s view", view->name);
3022 restore_view_position(view);
3024 } else if ((reload || strcmp(view->vid, view->id)) &&
3025 !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3026 report("Failed to load %s view", view->name);
3030 if (split && prev->lineno - prev->offset >= prev->height) {
3031 /* Take the title line into account. */
3032 int lines = prev->lineno - prev->offset - prev->height + 1;
3034 /* Scroll the view that was split if the current line is
3035 * outside the new limited view. */
3036 do_scroll_view(prev, lines);
3039 if (prev && view != prev) {
3041 /* "Blur" the previous view. */
3042 update_view_title(prev);
3045 view->parent = prev;
3048 if (view->pipe && view->lines == 0) {
3049 /* Clear the old view and let the incremental updating refill
3052 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3054 } else if (view_is_displayed(view)) {
3061 open_external_viewer(const char *argv[], const char *dir)
3063 def_prog_mode(); /* save current tty modes */
3064 endwin(); /* restore original tty modes */
3065 run_io_fg(argv, dir);
3066 fprintf(stderr, "Press Enter to continue");
3069 redraw_display(TRUE);
3073 open_mergetool(const char *file)
3075 const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3077 open_external_viewer(mergetool_argv, opt_cdup);
3081 open_editor(bool from_root, const char *file)
3083 const char *editor_argv[] = { "vi", file, NULL };
3086 editor = getenv("GIT_EDITOR");
3087 if (!editor && *opt_editor)
3088 editor = opt_editor;
3090 editor = getenv("VISUAL");
3092 editor = getenv("EDITOR");
3096 editor_argv[0] = editor;
3097 open_external_viewer(editor_argv, from_root ? opt_cdup : NULL);
3101 open_run_request(enum request request)
3103 struct run_request *req = get_run_request(request);
3104 const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3107 report("Unknown run request");
3111 if (format_argv(argv, req->argv, FORMAT_ALL))
3112 open_external_viewer(argv, NULL);
3117 * User request switch noodle
3121 view_driver(struct view *view, enum request request)
3125 if (request == REQ_NONE)
3128 if (request > REQ_NONE) {
3129 open_run_request(request);
3130 /* FIXME: When all views can refresh always do this. */
3131 if (view == VIEW(REQ_VIEW_STATUS) ||
3132 view == VIEW(REQ_VIEW_MAIN) ||
3133 view == VIEW(REQ_VIEW_LOG) ||
3134 view == VIEW(REQ_VIEW_BRANCH) ||
3135 view == VIEW(REQ_VIEW_STAGE))
3136 request = REQ_REFRESH;
3141 if (view && view->lines) {
3142 request = view->ops->request(view, request, &view->line[view->lineno]);
3143 if (request == REQ_NONE)
3150 case REQ_MOVE_PAGE_UP:
3151 case REQ_MOVE_PAGE_DOWN:
3152 case REQ_MOVE_FIRST_LINE:
3153 case REQ_MOVE_LAST_LINE:
3154 move_view(view, request);
3157 case REQ_SCROLL_LEFT:
3158 case REQ_SCROLL_RIGHT:
3159 case REQ_SCROLL_LINE_DOWN:
3160 case REQ_SCROLL_LINE_UP:
3161 case REQ_SCROLL_PAGE_DOWN:
3162 case REQ_SCROLL_PAGE_UP:
3163 scroll_view(view, request);
3166 case REQ_VIEW_BLAME:
3168 report("No file chosen, press %s to open tree view",
3169 get_key(REQ_VIEW_TREE));
3172 open_view(view, request, OPEN_DEFAULT);
3177 report("No file chosen, press %s to open tree view",
3178 get_key(REQ_VIEW_TREE));
3181 open_view(view, request, OPEN_DEFAULT);
3184 case REQ_VIEW_PAGER:
3185 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3186 report("No pager content, press %s to run command from prompt",
3187 get_key(REQ_PROMPT));
3190 open_view(view, request, OPEN_DEFAULT);
3193 case REQ_VIEW_STAGE:
3194 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3195 report("No stage content, press %s to open the status view and choose file",
3196 get_key(REQ_VIEW_STATUS));
3199 open_view(view, request, OPEN_DEFAULT);
3202 case REQ_VIEW_STATUS:
3203 if (opt_is_inside_work_tree == FALSE) {
3204 report("The status view requires a working tree");
3207 open_view(view, request, OPEN_DEFAULT);
3215 case REQ_VIEW_BRANCH:
3216 open_view(view, request, OPEN_DEFAULT);
3221 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3223 if ((view == VIEW(REQ_VIEW_DIFF) &&
3224 view->parent == VIEW(REQ_VIEW_MAIN)) ||
3225 (view == VIEW(REQ_VIEW_DIFF) &&
3226 view->parent == VIEW(REQ_VIEW_BLAME)) ||
3227 (view == VIEW(REQ_VIEW_STAGE) &&
3228 view->parent == VIEW(REQ_VIEW_STATUS)) ||
3229 (view == VIEW(REQ_VIEW_BLOB) &&
3230 view->parent == VIEW(REQ_VIEW_TREE)) ||
3231 (view == VIEW(REQ_VIEW_MAIN) &&
3232 view->parent == VIEW(REQ_VIEW_BRANCH))) {
3235 view = view->parent;
3236 line = view->lineno;
3237 move_view(view, request);
3238 if (view_is_displayed(view))
3239 update_view_title(view);
3240 if (line != view->lineno)
3241 view->ops->request(view, REQ_ENTER,
3242 &view->line[view->lineno]);
3245 move_view(view, request);
3251 int nviews = displayed_views();
3252 int next_view = (current_view + 1) % nviews;
3254 if (next_view == current_view) {
3255 report("Only one view is displayed");
3259 current_view = next_view;
3260 /* Blur out the title of the previous view. */
3261 update_view_title(view);
3266 report("Refreshing is not yet supported for the %s view", view->name);
3270 if (displayed_views() == 2)
3271 maximize_view(view);
3274 case REQ_TOGGLE_LINENO:
3275 toggle_view_option(&opt_line_number, "line numbers");
3278 case REQ_TOGGLE_DATE:
3279 toggle_view_option(&opt_date, "date display");
3282 case REQ_TOGGLE_AUTHOR:
3283 toggle_view_option(&opt_author, "author display");
3286 case REQ_TOGGLE_REV_GRAPH:
3287 toggle_view_option(&opt_rev_graph, "revision graph display");
3290 case REQ_TOGGLE_REFS:
3291 toggle_view_option(&opt_show_refs, "reference display");
3294 case REQ_TOGGLE_SORT_FIELD:
3295 case REQ_TOGGLE_SORT_ORDER:
3296 report("Sorting is not yet supported for the %s view", view->name);
3300 case REQ_SEARCH_BACK:
3301 search_view(view, request);
3306 find_next(view, request);
3309 case REQ_STOP_LOADING:
3310 for (i = 0; i < ARRAY_SIZE(views); i++) {
3313 report("Stopped loading the %s view", view->name),
3314 end_update(view, TRUE);
3318 case REQ_SHOW_VERSION:
3319 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3322 case REQ_SCREEN_REDRAW:
3323 redraw_display(TRUE);
3327 report("Nothing to edit");
3331 report("Nothing to enter");
3334 case REQ_VIEW_CLOSE:
3335 /* XXX: Mark closed views by letting view->parent point to the
3336 * view itself. Parents to closed view should never be
3339 view->parent->parent != view->parent) {
3340 maximize_view(view->parent);
3341 view->parent = view;
3349 report("Unknown key, press 'h' for help");
3358 * View backend utilities
3368 const enum sort_field *fields;
3369 size_t size, current;
3373 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3374 #define get_sort_field(state) ((state).fields[(state).current])
3375 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3378 sort_view(struct view *view, enum request request, struct sort_state *state,
3379 int (*compare)(const void *, const void *))
3382 case REQ_TOGGLE_SORT_FIELD:
3383 state->current = (state->current + 1) % state->size;
3386 case REQ_TOGGLE_SORT_ORDER:
3387 state->reverse = !state->reverse;
3390 die("Not a sort request");
3393 qsort(view->line, view->lines, sizeof(*view->line), compare);
3397 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3399 /* Small author cache to reduce memory consumption. It uses binary
3400 * search to lookup or find place to position new entries. No entries
3401 * are ever freed. */
3403 get_author(const char *name)
3405 static const char **authors;
3406 static size_t authors_size;
3407 int from = 0, to = authors_size - 1;
3409 while (from <= to) {
3410 size_t pos = (to + from) / 2;
3411 int cmp = strcmp(name, authors[pos]);
3414 return authors[pos];
3422 if (!realloc_authors(&authors, authors_size, 1))
3424 name = strdup(name);
3428 memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3429 authors[from] = name;
3436 parse_timezone(time_t *time, const char *zone)
3440 tz = ('0' - zone[1]) * 60 * 60 * 10;
3441 tz += ('0' - zone[2]) * 60 * 60;
3442 tz += ('0' - zone[3]) * 60;
3443 tz += ('0' - zone[4]);
3451 /* Parse author lines where the name may be empty:
3452 * author <email@address.tld> 1138474660 +0100
3455 parse_author_line(char *ident, const char **author, time_t *time)
3457 char *nameend = strchr(ident, '<');
3458 char *emailend = strchr(ident, '>');
3460 if (nameend && emailend)
3461 *nameend = *emailend = 0;
3462 ident = chomp_string(ident);
3465 ident = chomp_string(nameend + 1);
3470 *author = get_author(ident);
3472 /* Parse epoch and timezone */
3473 if (emailend && emailend[1] == ' ') {
3474 char *secs = emailend + 2;
3475 char *zone = strchr(secs, ' ');
3477 *time = (time_t) atol(secs);
3479 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
3480 parse_timezone(time, zone + 1);
3484 static enum input_status
3485 select_commit_parent_handler(void *data, char *buf, int c)
3487 size_t parents = *(size_t *) data;
3494 parent = atoi(buf) * 10;
3497 if (parent > parents)
3503 select_commit_parent(const char *id, char rev[SIZEOF_REV], const char *path)
3505 char buf[SIZEOF_STR * 4];
3506 const char *revlist_argv[] = {
3507 "git", "rev-list", "-1", "--parents", id, "--", path, NULL
3511 if (!run_io_buf(revlist_argv, buf, sizeof(buf)) ||
3512 (parents = (strlen(buf) / 40) - 1) < 0) {
3513 report("Failed to get parent information");
3516 } else if (parents == 0) {
3518 report("Path '%s' does not exist in the parent", path);
3520 report("The selected commit has no parents");
3525 char prompt[SIZEOF_STR];
3528 if (!string_format(prompt, "Which parent? [1..%d] ", parents))
3530 result = prompt_input(prompt, select_commit_parent_handler, &parents);
3533 parents = atoi(result);
3536 string_copy_rev(rev, &buf[41 * parents]);
3545 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3547 char text[SIZEOF_STR];
3549 if (opt_line_number && draw_lineno(view, lineno))
3552 string_expand(text, sizeof(text), line->data, opt_tab_size);
3553 draw_text(view, line->type, text, TRUE);
3558 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3560 const char *describe_argv[] = { "git", "describe", commit_id, NULL };
3561 char ref[SIZEOF_STR];
3563 if (!run_io_buf(describe_argv, ref, sizeof(ref)) || !*ref)
3566 /* This is the only fatal call, since it can "corrupt" the buffer. */
3567 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
3574 add_pager_refs(struct view *view, struct line *line)
3576 char buf[SIZEOF_STR];
3577 char *commit_id = (char *)line->data + STRING_SIZE("commit ");
3579 size_t bufpos = 0, refpos = 0;
3580 const char *sep = "Refs: ";
3581 bool is_tag = FALSE;
3583 assert(line->type == LINE_COMMIT);
3585 refs = get_refs(commit_id);
3587 if (view == VIEW(REQ_VIEW_DIFF))
3588 goto try_add_describe_ref;
3593 struct ref *ref = refs[refpos];
3594 const char *fmt = ref->tag ? "%s[%s]" :
3595 ref->remote ? "%s<%s>" : "%s%s";
3597 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
3602 } while (refs[refpos++]->next);
3604 if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
3605 try_add_describe_ref:
3606 /* Add <tag>-g<commit_id> "fake" reference. */
3607 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
3614 add_line_text(view, buf, LINE_PP_REFS);
3618 pager_read(struct view *view, char *data)
3625 line = add_line_text(view, data, get_line_type(data));
3629 if (line->type == LINE_COMMIT &&
3630 (view == VIEW(REQ_VIEW_DIFF) ||
3631 view == VIEW(REQ_VIEW_LOG)))
3632 add_pager_refs(view, line);
3638 pager_request(struct view *view, enum request request, struct line *line)
3642 if (request != REQ_ENTER)
3645 if (line->type == LINE_COMMIT &&
3646 (view == VIEW(REQ_VIEW_LOG) ||
3647 view == VIEW(REQ_VIEW_PAGER))) {
3648 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3652 /* Always scroll the view even if it was split. That way
3653 * you can use Enter to scroll through the log view and
3654 * split open each commit diff. */
3655 scroll_view(view, REQ_SCROLL_LINE_DOWN);
3657 /* FIXME: A minor workaround. Scrolling the view will call report("")
3658 * but if we are scrolling a non-current view this won't properly
3659 * update the view title. */
3661 update_view_title(view);
3667 pager_grep(struct view *view, struct line *line)
3669 const char *text[] = { line->data, NULL };
3671 return grep_text(view, text);
3675 pager_select(struct view *view, struct line *line)
3677 if (line->type == LINE_COMMIT) {
3678 char *text = (char *)line->data + STRING_SIZE("commit ");
3680 if (view != VIEW(REQ_VIEW_PAGER))
3681 string_copy_rev(view->ref, text);
3682 string_copy_rev(ref_commit, text);
3686 static struct view_ops pager_ops = {
3697 static const char *log_argv[SIZEOF_ARG] = {
3698 "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3702 log_request(struct view *view, enum request request, struct line *line)
3707 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
3710 return pager_request(view, request, line);
3714 static struct view_ops log_ops = {
3725 static const char *diff_argv[SIZEOF_ARG] = {
3726 "git", "show", "--pretty=fuller", "--no-color", "--root",
3727 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
3730 static struct view_ops diff_ops = {
3746 help_open(struct view *view)
3748 char buf[SIZEOF_STR];
3752 if (view->lines > 0)
3755 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
3757 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
3760 if (req_info[i].request == REQ_NONE)
3763 if (!req_info[i].request) {
3764 add_line_text(view, "", LINE_DEFAULT);
3765 add_line_text(view, req_info[i].help, LINE_DEFAULT);
3769 key = get_key(req_info[i].request);
3771 key = "(no key defined)";
3773 for (bufpos = 0; bufpos <= req_info[i].namelen; bufpos++) {
3774 buf[bufpos] = tolower(req_info[i].name[bufpos]);
3775 if (buf[bufpos] == '_')
3779 add_line_format(view, LINE_DEFAULT, " %-25s %-20s %s",
3780 key, buf, req_info[i].help);
3784 add_line_text(view, "", LINE_DEFAULT);
3785 add_line_text(view, "External commands:", LINE_DEFAULT);
3788 for (i = 0; i < run_requests; i++) {
3789 struct run_request *req = get_run_request(REQ_NONE + i + 1);
3796 key = get_key_name(req->key);
3798 key = "(no key defined)";
3800 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
3801 if (!string_format_from(buf, &bufpos, "%s%s",
3802 argc ? " " : "", req->argv[argc]))
3805 add_line_format(view, LINE_DEFAULT, " %-10s %-14s `%s`",
3806 keymap_table[req->keymap].name, key, buf);
3812 static struct view_ops help_ops = {
3828 struct tree_stack_entry {
3829 struct tree_stack_entry *prev; /* Entry below this in the stack */
3830 unsigned long lineno; /* Line number to restore */
3831 char *name; /* Position of name in opt_path */
3834 /* The top of the path stack. */
3835 static struct tree_stack_entry *tree_stack = NULL;
3836 unsigned long tree_lineno = 0;
3839 pop_tree_stack_entry(void)
3841 struct tree_stack_entry *entry = tree_stack;
3843 tree_lineno = entry->lineno;
3845 tree_stack = entry->prev;
3850 push_tree_stack_entry(const char *name, unsigned long lineno)
3852 struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
3853 size_t pathlen = strlen(opt_path);
3858 entry->prev = tree_stack;
3859 entry->name = opt_path + pathlen;
3862 if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
3863 pop_tree_stack_entry();
3867 /* Move the current line to the first tree entry. */
3869 entry->lineno = lineno;
3872 /* Parse output from git-ls-tree(1):
3874 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
3877 #define SIZEOF_TREE_ATTR \
3878 STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
3880 #define SIZEOF_TREE_MODE \
3881 STRING_SIZE("100644 ")
3883 #define TREE_ID_OFFSET \
3884 STRING_SIZE("100644 blob ")
3887 char id[SIZEOF_REV];
3889 time_t time; /* Date from the author ident. */
3890 const char *author; /* Author of the commit. */
3895 tree_path(const struct line *line)
3897 return ((struct tree_entry *) line->data)->name;
3901 tree_compare_entry(const struct line *line1, const struct line *line2)
3903 if (line1->type != line2->type)
3904 return line1->type == LINE_TREE_DIR ? -1 : 1;
3905 return strcmp(tree_path(line1), tree_path(line2));
3908 static const enum sort_field tree_sort_fields[] = {
3909 ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
3911 static struct sort_state tree_sort_state = SORT_STATE(tree_sort_fields);
3914 tree_compare(const void *l1, const void *l2)
3916 const struct line *line1 = (const struct line *) l1;
3917 const struct line *line2 = (const struct line *) l2;
3918 const struct tree_entry *entry1 = ((const struct line *) l1)->data;
3919 const struct tree_entry *entry2 = ((const struct line *) l2)->data;
3921 if (line1->type == LINE_TREE_HEAD)
3923 if (line2->type == LINE_TREE_HEAD)
3926 switch (get_sort_field(tree_sort_state)) {
3928 return sort_order(tree_sort_state, entry1->time - entry2->time);
3930 case ORDERBY_AUTHOR:
3931 return sort_order(tree_sort_state, strcmp(entry1->author, entry2->author));
3935 return sort_order(tree_sort_state, tree_compare_entry(line1, line2));
3940 static struct line *
3941 tree_entry(struct view *view, enum line_type type, const char *path,
3942 const char *mode, const char *id)
3944 struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
3945 struct line *line = entry ? add_line_data(view, entry, type) : NULL;
3947 if (!entry || !line) {
3952 strncpy(entry->name, path, strlen(path));
3954 entry->mode = strtoul(mode, NULL, 8);
3956 string_copy_rev(entry->id, id);
3962 tree_read_date(struct view *view, char *text, bool *read_date)
3964 static const char *author_name;
3965 static time_t author_time;
3967 if (!text && *read_date) {
3972 char *path = *opt_path ? opt_path : ".";
3973 /* Find next entry to process */
3974 const char *log_file[] = {
3975 "git", "log", "--no-color", "--pretty=raw",
3976 "--cc", "--raw", view->id, "--", path, NULL
3981 tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
3982 report("Tree is empty");
3986 if (!run_io_rd(&io, log_file, FORMAT_NONE)) {
3987 report("Failed to load tree data");
3991 done_io(view->pipe);
3996 } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
3997 parse_author_line(text + STRING_SIZE("author "),
3998 &author_name, &author_time);
4000 } else if (*text == ':') {
4002 size_t annotated = 1;
4005 pos = strchr(text, '\t');
4009 if (*opt_prefix && !strncmp(text, opt_prefix, strlen(opt_prefix)))
4010 text += strlen(opt_prefix);
4011 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
4012 text += strlen(opt_path);
4013 pos = strchr(text, '/');
4017 for (i = 1; i < view->lines; i++) {
4018 struct line *line = &view->line[i];
4019 struct tree_entry *entry = line->data;
4021 annotated += !!entry->author;
4022 if (entry->author || strcmp(entry->name, text))
4025 entry->author = author_name;
4026 entry->time = author_time;
4031 if (annotated == view->lines)
4032 kill_io(view->pipe);
4038 tree_read(struct view *view, char *text)
4040 static bool read_date = FALSE;
4041 struct tree_entry *data;
4042 struct line *entry, *line;
4043 enum line_type type;
4044 size_t textlen = text ? strlen(text) : 0;
4045 char *path = text + SIZEOF_TREE_ATTR;
4047 if (read_date || !text)
4048 return tree_read_date(view, text, &read_date);
4050 if (textlen <= SIZEOF_TREE_ATTR)
4052 if (view->lines == 0 &&
4053 !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
4056 /* Strip the path part ... */
4058 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
4059 size_t striplen = strlen(opt_path);
4061 if (pathlen > striplen)
4062 memmove(path, path + striplen,
4063 pathlen - striplen + 1);
4065 /* Insert "link" to parent directory. */
4066 if (view->lines == 1 &&
4067 !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
4071 type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
4072 entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
4077 /* Skip "Directory ..." and ".." line. */
4078 for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
4079 if (tree_compare_entry(line, entry) <= 0)
4082 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4086 for (; line <= entry; line++)
4087 line->dirty = line->cleareol = 1;
4091 if (tree_lineno > view->lineno) {
4092 view->lineno = tree_lineno;
4100 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4102 struct tree_entry *entry = line->data;
4104 if (line->type == LINE_TREE_HEAD) {
4105 if (draw_text(view, line->type, "Directory path /", TRUE))
4108 if (draw_mode(view, entry->mode))
4111 if (opt_author && draw_author(view, entry->author))
4114 if (opt_date && draw_date(view, entry->author ? &entry->time : NULL))
4117 if (draw_text(view, line->type, entry->name, TRUE))
4125 char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4126 int fd = mkstemp(file);
4129 report("Failed to create temporary file");
4130 else if (!run_io_append(blob_ops.argv, FORMAT_ALL, fd))
4131 report("Failed to save blob data to file");
4133 open_editor(FALSE, file);
4139 tree_request(struct view *view, enum request request, struct line *line)
4141 enum open_flags flags;
4144 case REQ_VIEW_BLAME:
4145 if (line->type != LINE_TREE_FILE) {
4146 report("Blame only supported for files");
4150 string_copy(opt_ref, view->vid);
4154 if (line->type != LINE_TREE_FILE) {
4155 report("Edit only supported for files");
4156 } else if (!is_head_commit(view->vid)) {
4159 open_editor(TRUE, opt_file);
4163 case REQ_TOGGLE_SORT_FIELD:
4164 case REQ_TOGGLE_SORT_ORDER:
4165 sort_view(view, request, &tree_sort_state, tree_compare);
4170 /* quit view if at top of tree */
4171 return REQ_VIEW_CLOSE;
4174 line = &view->line[1];
4184 /* Cleanup the stack if the tree view is at a different tree. */
4185 while (!*opt_path && tree_stack)
4186 pop_tree_stack_entry();
4188 switch (line->type) {
4190 /* Depending on whether it is a subdirectory or parent link
4191 * mangle the path buffer. */
4192 if (line == &view->line[1] && *opt_path) {
4193 pop_tree_stack_entry();
4196 const char *basename = tree_path(line);
4198 push_tree_stack_entry(basename, view->lineno);
4201 /* Trees and subtrees share the same ID, so they are not not
4202 * unique like blobs. */
4203 flags = OPEN_RELOAD;
4204 request = REQ_VIEW_TREE;
4207 case LINE_TREE_FILE:
4208 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4209 request = REQ_VIEW_BLOB;
4216 open_view(view, request, flags);
4217 if (request == REQ_VIEW_TREE)
4218 view->lineno = tree_lineno;
4224 tree_grep(struct view *view, struct line *line)
4226 struct tree_entry *entry = line->data;
4227 const char *text[] = {
4229 opt_author ? entry->author : "",
4230 opt_date ? mkdate(&entry->time) : "",
4234 return grep_text(view, text);
4238 tree_select(struct view *view, struct line *line)
4240 struct tree_entry *entry = line->data;
4242 if (line->type == LINE_TREE_FILE) {
4243 string_copy_rev(ref_blob, entry->id);
4244 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4246 } else if (line->type != LINE_TREE_DIR) {
4250 string_copy_rev(view->ref, entry->id);
4253 static const char *tree_argv[SIZEOF_ARG] = {
4254 "git", "ls-tree", "%(commit)", "%(directory)", NULL
4257 static struct view_ops tree_ops = {
4269 blob_read(struct view *view, char *line)
4273 return add_line_text(view, line, LINE_DEFAULT) != NULL;
4277 blob_request(struct view *view, enum request request, struct line *line)
4284 return pager_request(view, request, line);
4288 static const char *blob_argv[SIZEOF_ARG] = {
4289 "git", "cat-file", "blob", "%(blob)", NULL
4292 static struct view_ops blob_ops = {
4306 * Loading the blame view is a two phase job:
4308 * 1. File content is read either using opt_file from the
4309 * filesystem or using git-cat-file.
4310 * 2. Then blame information is incrementally added by
4311 * reading output from git-blame.
4314 static const char *blame_head_argv[] = {
4315 "git", "blame", "--incremental", "--", "%(file)", NULL
4318 static const char *blame_ref_argv[] = {
4319 "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4322 static const char *blame_cat_file_argv[] = {
4323 "git", "cat-file", "blob", "%(ref):%(file)", NULL
4326 struct blame_commit {
4327 char id[SIZEOF_REV]; /* SHA1 ID. */
4328 char title[128]; /* First line of the commit message. */
4329 const char *author; /* Author of the commit. */
4330 time_t time; /* Date from the author ident. */
4331 char filename[128]; /* Name of file. */
4332 bool has_previous; /* Was a "previous" line detected. */
4336 struct blame_commit *commit;
4337 unsigned long lineno;
4342 blame_open(struct view *view)
4344 if (*opt_ref || !io_open(&view->io, opt_file)) {
4345 if (!run_io_rd(&view->io, blame_cat_file_argv, FORMAT_ALL))
4349 setup_update(view, opt_file);
4350 string_format(view->ref, "%s ...", opt_file);
4355 static struct blame_commit *
4356 get_blame_commit(struct view *view, const char *id)
4360 for (i = 0; i < view->lines; i++) {
4361 struct blame *blame = view->line[i].data;
4366 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4367 return blame->commit;
4371 struct blame_commit *commit = calloc(1, sizeof(*commit));
4374 string_ncopy(commit->id, id, SIZEOF_REV);
4380 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4382 const char *pos = *posref;
4385 pos = strchr(pos + 1, ' ');
4386 if (!pos || !isdigit(pos[1]))
4388 *number = atoi(pos + 1);
4389 if (*number < min || *number > max)
4396 static struct blame_commit *
4397 parse_blame_commit(struct view *view, const char *text, int *blamed)
4399 struct blame_commit *commit;
4400 struct blame *blame;
4401 const char *pos = text + SIZEOF_REV - 2;
4402 size_t orig_lineno = 0;
4406 if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
4409 if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
4410 !parse_number(&pos, &lineno, 1, view->lines) ||
4411 !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4414 commit = get_blame_commit(view, text);
4420 struct line *line = &view->line[lineno + group - 1];
4423 blame->commit = commit;
4424 blame->lineno = orig_lineno + group - 1;
4432 blame_read_file(struct view *view, const char *line, bool *read_file)
4435 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
4438 if (view->lines == 0 && !view->parent)
4439 die("No blame exist for %s", view->vid);
4441 if (view->lines == 0 || !run_io_rd(&io, argv, FORMAT_ALL)) {
4442 report("Failed to load blame data");
4446 done_io(view->pipe);
4452 size_t linelen = strlen(line);
4453 struct blame *blame = malloc(sizeof(*blame) + linelen);
4458 blame->commit = NULL;
4459 strncpy(blame->text, line, linelen);
4460 blame->text[linelen] = 0;
4461 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
4466 match_blame_header(const char *name, char **line)
4468 size_t namelen = strlen(name);
4469 bool matched = !strncmp(name, *line, namelen);
4478 blame_read(struct view *view, char *line)
4480 static struct blame_commit *commit = NULL;
4481 static int blamed = 0;
4482 static bool read_file = TRUE;
4485 return blame_read_file(view, line, &read_file);
4492 string_format(view->ref, "%s", view->vid);
4493 if (view_is_displayed(view)) {
4494 update_view_title(view);
4495 redraw_view_from(view, 0);
4501 commit = parse_blame_commit(view, line, &blamed);
4502 string_format(view->ref, "%s %2d%%", view->vid,
4503 view->lines ? blamed * 100 / view->lines : 0);
4505 } else if (match_blame_header("author ", &line)) {
4506 commit->author = get_author(line);
4508 } else if (match_blame_header("author-time ", &line)) {
4509 commit->time = (time_t) atol(line);
4511 } else if (match_blame_header("author-tz ", &line)) {
4512 parse_timezone(&commit->time, line);
4514 } else if (match_blame_header("summary ", &line)) {
4515 string_ncopy(commit->title, line, strlen(line));
4517 } else if (match_blame_header("previous ", &line)) {
4518 commit->has_previous = TRUE;
4520 } else if (match_blame_header("filename ", &line)) {
4521 string_ncopy(commit->filename, line, strlen(line));
4529 blame_draw(struct view *view, struct line *line, unsigned int lineno)
4531 struct blame *blame = line->data;
4532 time_t *time = NULL;
4533 const char *id = NULL, *author = NULL;
4534 char text[SIZEOF_STR];
4536 if (blame->commit && *blame->commit->filename) {
4537 id = blame->commit->id;
4538 author = blame->commit->author;
4539 time = &blame->commit->time;
4542 if (opt_date && draw_date(view, time))
4545 if (opt_author && draw_author(view, author))
4548 if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
4551 if (draw_lineno(view, lineno))
4554 string_expand(text, sizeof(text), blame->text, opt_tab_size);
4555 draw_text(view, LINE_DEFAULT, text, TRUE);
4560 check_blame_commit(struct blame *blame, bool check_null_id)
4563 report("Commit data not loaded yet");
4564 else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
4565 report("No commit exist for the selected line");
4572 setup_blame_parent_line(struct view *view, struct blame *blame)
4574 const char *diff_tree_argv[] = {
4575 "git", "diff-tree", "-U0", blame->commit->id,
4576 "--", blame->commit->filename, NULL
4579 int parent_lineno = -1;
4580 int blamed_lineno = -1;
4583 if (!run_io(&io, diff_tree_argv, NULL, IO_RD))
4586 while ((line = io_get(&io, '\n', TRUE))) {
4588 char *pos = strchr(line, '+');
4590 parent_lineno = atoi(line + 4);
4592 blamed_lineno = atoi(pos + 1);
4594 } else if (*line == '+' && parent_lineno != -1) {
4595 if (blame->lineno == blamed_lineno - 1 &&
4596 !strcmp(blame->text, line + 1)) {
4597 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
4608 blame_request(struct view *view, enum request request, struct line *line)
4610 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4611 struct blame *blame = line->data;
4614 case REQ_VIEW_BLAME:
4615 if (check_blame_commit(blame, TRUE)) {
4616 string_copy(opt_ref, blame->commit->id);
4617 string_copy(opt_file, blame->commit->filename);
4619 view->lineno = blame->lineno;
4620 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4625 if (check_blame_commit(blame, TRUE) &&
4626 select_commit_parent(blame->commit->id, opt_ref,
4627 blame->commit->filename)) {
4628 string_copy(opt_file, blame->commit->filename);
4629 setup_blame_parent_line(view, blame);
4630 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4635 if (!check_blame_commit(blame, FALSE))
4638 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
4639 !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
4642 if (!strcmp(blame->commit->id, NULL_ID)) {
4643 struct view *diff = VIEW(REQ_VIEW_DIFF);
4644 const char *diff_index_argv[] = {
4645 "git", "diff-index", "--root", "--patch-with-stat",
4646 "-C", "-M", "HEAD", "--", view->vid, NULL
4649 if (!blame->commit->has_previous) {
4650 diff_index_argv[1] = "diff";
4651 diff_index_argv[2] = "--no-color";
4652 diff_index_argv[6] = "--";
4653 diff_index_argv[7] = "/dev/null";
4656 if (!prepare_update(diff, diff_index_argv, NULL, FORMAT_DASH)) {
4657 report("Failed to allocate diff command");
4660 flags |= OPEN_PREPARED;
4663 open_view(view, REQ_VIEW_DIFF, flags);
4664 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
4665 string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
4676 blame_grep(struct view *view, struct line *line)
4678 struct blame *blame = line->data;
4679 struct blame_commit *commit = blame->commit;
4680 const char *text[] = {
4682 commit ? commit->title : "",
4683 commit ? commit->id : "",
4684 commit && opt_author ? commit->author : "",
4685 commit && opt_date ? mkdate(&commit->time) : "",
4689 return grep_text(view, text);
4693 blame_select(struct view *view, struct line *line)
4695 struct blame *blame = line->data;
4696 struct blame_commit *commit = blame->commit;
4701 if (!strcmp(commit->id, NULL_ID))
4702 string_ncopy(ref_commit, "HEAD", 4);
4704 string_copy_rev(ref_commit, commit->id);
4707 static struct view_ops blame_ops = {
4723 const char *author; /* Author of the last commit. */
4724 time_t time; /* Date of the last activity. */
4725 struct ref *ref; /* Name and commit ID information. */
4728 static const enum sort_field branch_sort_fields[] = {
4729 ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4731 static struct sort_state branch_sort_state = SORT_STATE(branch_sort_fields);
4734 branch_compare(const void *l1, const void *l2)
4736 const struct branch *branch1 = ((const struct line *) l1)->data;
4737 const struct branch *branch2 = ((const struct line *) l2)->data;
4739 switch (get_sort_field(branch_sort_state)) {
4741 return sort_order(branch_sort_state, branch1->time - branch2->time);
4743 case ORDERBY_AUTHOR:
4744 return sort_order(branch_sort_state, strcmp(branch1->author, branch2->author));
4748 return sort_order(branch_sort_state, strcmp(branch1->ref->name, branch2->ref->name));
4753 branch_draw(struct view *view, struct line *line, unsigned int lineno)
4755 struct branch *branch = line->data;
4756 enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
4758 if (opt_date && draw_date(view, branch->author ? &branch->time : NULL))
4761 if (opt_author && draw_author(view, branch->author))
4764 draw_text(view, type, branch->ref->name, TRUE);
4769 branch_request(struct view *view, enum request request, struct line *line)
4774 open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
4777 case REQ_TOGGLE_SORT_FIELD:
4778 case REQ_TOGGLE_SORT_ORDER:
4779 sort_view(view, request, &branch_sort_state, branch_compare);
4783 open_view(view, REQ_VIEW_MAIN, OPEN_SPLIT);
4792 branch_read(struct view *view, char *line)
4794 static char id[SIZEOF_REV];
4795 struct branch *reference;
4801 switch (get_line_type(line)) {
4803 string_copy_rev(id, line + STRING_SIZE("commit "));
4807 for (i = 0, reference = NULL; i < view->lines; i++) {
4808 struct branch *branch = view->line[i].data;
4810 if (strcmp(branch->ref->id, id))
4813 view->line[i].dirty = TRUE;
4815 branch->author = reference->author;
4816 branch->time = reference->time;
4820 parse_author_line(line + STRING_SIZE("author "),
4821 &branch->author, &branch->time);
4833 branch_open_visitor(void *data, struct ref *ref)
4835 struct view *view = data;
4836 struct branch *branch;
4838 if (ref->tag || ref->ltag || ref->remote)
4841 branch = calloc(1, sizeof(*branch));
4846 return !!add_line_data(view, branch, LINE_DEFAULT);
4850 branch_open(struct view *view)
4852 const char *branch_log[] = {
4853 "git", "log", "--no-color", "--pretty=raw",
4854 "--simplify-by-decoration", "--all", NULL
4857 if (!run_io_rd(&view->io, branch_log, FORMAT_NONE)) {
4858 report("Failed to load branch data");
4862 setup_update(view, view->id);
4863 foreach_ref(branch_open_visitor, view);
4869 branch_grep(struct view *view, struct line *line)
4871 struct branch *branch = line->data;
4872 const char *text[] = {
4878 return grep_text(view, text);
4882 branch_select(struct view *view, struct line *line)
4884 struct branch *branch = line->data;
4886 string_copy_rev(view->ref, branch->ref->id);
4887 string_copy_rev(ref_commit, branch->ref->id);
4888 string_copy_rev(ref_head, branch->ref->id);
4891 static struct view_ops branch_ops = {
4910 char rev[SIZEOF_REV];
4911 char name[SIZEOF_STR];
4915 char rev[SIZEOF_REV];
4916 char name[SIZEOF_STR];
4920 static char status_onbranch[SIZEOF_STR];
4921 static struct status stage_status;
4922 static enum line_type stage_line_type;
4923 static size_t stage_chunks;
4924 static int *stage_chunk;
4926 DEFINE_ALLOCATOR(realloc_ints, int, 32)
4928 /* This should work even for the "On branch" line. */
4930 status_has_none(struct view *view, struct line *line)
4932 return line < view->line + view->lines && !line[1].data;
4935 /* Get fields from the diff line:
4936 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
4939 status_get_diff(struct status *file, const char *buf, size_t bufsize)
4941 const char *old_mode = buf + 1;
4942 const char *new_mode = buf + 8;
4943 const char *old_rev = buf + 15;
4944 const char *new_rev = buf + 56;
4945 const char *status = buf + 97;
4948 old_mode[-1] != ':' ||
4949 new_mode[-1] != ' ' ||
4950 old_rev[-1] != ' ' ||
4951 new_rev[-1] != ' ' ||
4955 file->status = *status;
4957 string_copy_rev(file->old.rev, old_rev);
4958 string_copy_rev(file->new.rev, new_rev);
4960 file->old.mode = strtoul(old_mode, NULL, 8);
4961 file->new.mode = strtoul(new_mode, NULL, 8);
4963 file->old.name[0] = file->new.name[0] = 0;
4969 status_run(struct view *view, const char *argv[], char status, enum line_type type)
4971 struct status *unmerged = NULL;
4975 if (!run_io(&io, argv, NULL, IO_RD))
4978 add_line_data(view, NULL, type);
4980 while ((buf = io_get(&io, 0, TRUE))) {
4981 struct status *file = unmerged;
4984 file = calloc(1, sizeof(*file));
4985 if (!file || !add_line_data(view, file, type))
4989 /* Parse diff info part. */
4991 file->status = status;
4993 string_copy(file->old.rev, NULL_ID);
4995 } else if (!file->status || file == unmerged) {
4996 if (!status_get_diff(file, buf, strlen(buf)))
4999 buf = io_get(&io, 0, TRUE);
5003 /* Collapse all modified entries that follow an
5004 * associated unmerged entry. */
5005 if (unmerged == file) {
5006 unmerged->status = 'U';
5008 } else if (file->status == 'U') {
5013 /* Grab the old name for rename/copy. */
5014 if (!*file->old.name &&
5015 (file->status == 'R' || file->status == 'C')) {
5016 string_ncopy(file->old.name, buf, strlen(buf));
5018 buf = io_get(&io, 0, TRUE);
5023 /* git-ls-files just delivers a NUL separated list of
5024 * file names similar to the second half of the
5025 * git-diff-* output. */
5026 string_ncopy(file->new.name, buf, strlen(buf));
5027 if (!*file->old.name)
5028 string_copy(file->old.name, file->new.name);
5032 if (io_error(&io)) {
5038 if (!view->line[view->lines - 1].data)
5039 add_line_data(view, NULL, LINE_STAT_NONE);
5045 /* Don't show unmerged entries in the staged section. */
5046 static const char *status_diff_index_argv[] = {
5047 "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5048 "--cached", "-M", "HEAD", NULL
5051 static const char *status_diff_files_argv[] = {
5052 "git", "diff-files", "-z", NULL
5055 static const char *status_list_other_argv[] = {
5056 "git", "ls-files", "-z", "--others", "--exclude-standard", NULL
5059 static const char *status_list_no_head_argv[] = {
5060 "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5063 static const char *update_index_argv[] = {
5064 "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5067 /* Restore the previous line number to stay in the context or select a
5068 * line with something that can be updated. */
5070 status_restore(struct view *view)
5072 if (view->p_lineno >= view->lines)
5073 view->p_lineno = view->lines - 1;
5074 while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
5076 while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
5079 /* If the above fails, always skip the "On branch" line. */
5080 if (view->p_lineno < view->lines)
5081 view->lineno = view->p_lineno;
5085 if (view->lineno < view->offset)
5086 view->offset = view->lineno;
5087 else if (view->offset + view->height <= view->lineno)
5088 view->offset = view->lineno - view->height + 1;
5090 view->p_restore = FALSE;
5094 status_update_onbranch(void)
5096 static const char *paths[][2] = {
5097 { "rebase-apply/rebasing", "Rebasing" },
5098 { "rebase-apply/applying", "Applying mailbox" },
5099 { "rebase-apply/", "Rebasing mailbox" },
5100 { "rebase-merge/interactive", "Interactive rebase" },
5101 { "rebase-merge/", "Rebase merge" },
5102 { "MERGE_HEAD", "Merging" },
5103 { "BISECT_LOG", "Bisecting" },
5104 { "HEAD", "On branch" },
5106 char buf[SIZEOF_STR];
5110 if (is_initial_commit()) {
5111 string_copy(status_onbranch, "Initial commit");
5115 for (i = 0; i < ARRAY_SIZE(paths); i++) {
5116 char *head = opt_head;
5118 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5119 lstat(buf, &stat) < 0)
5125 if (string_format(buf, "%s/rebase-merge/head-name", opt_git_dir) &&
5126 io_open(&io, buf) &&
5127 io_read_buf(&io, buf, sizeof(buf))) {
5129 if (!prefixcmp(head, "refs/heads/"))
5130 head += STRING_SIZE("refs/heads/");
5134 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5135 string_copy(status_onbranch, opt_head);
5139 string_copy(status_onbranch, "Not currently on any branch");
5142 /* First parse staged info using git-diff-index(1), then parse unstaged
5143 * info using git-diff-files(1), and finally untracked files using
5144 * git-ls-files(1). */
5146 status_open(struct view *view)
5150 add_line_data(view, NULL, LINE_STAT_HEAD);
5151 status_update_onbranch();
5153 run_io_bg(update_index_argv);
5155 if (is_initial_commit()) {
5156 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5158 } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5162 if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5163 !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5166 /* Restore the exact position or use the specialized restore
5168 if (!view->p_restore)
5169 status_restore(view);
5174 status_draw(struct view *view, struct line *line, unsigned int lineno)
5176 struct status *status = line->data;
5177 enum line_type type;
5181 switch (line->type) {
5182 case LINE_STAT_STAGED:
5183 type = LINE_STAT_SECTION;
5184 text = "Changes to be committed:";
5187 case LINE_STAT_UNSTAGED:
5188 type = LINE_STAT_SECTION;
5189 text = "Changed but not updated:";
5192 case LINE_STAT_UNTRACKED:
5193 type = LINE_STAT_SECTION;
5194 text = "Untracked files:";
5197 case LINE_STAT_NONE:
5198 type = LINE_DEFAULT;
5199 text = " (no files)";
5202 case LINE_STAT_HEAD:
5203 type = LINE_STAT_HEAD;
5204 text = status_onbranch;
5211 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5213 buf[0] = status->status;
5214 if (draw_text(view, line->type, buf, TRUE))
5216 type = LINE_DEFAULT;
5217 text = status->new.name;
5220 draw_text(view, type, text, TRUE);
5225 status_load_error(struct view *view, struct view *stage, const char *path)
5227 if (displayed_views() == 2 || display[current_view] != view)
5228 maximize_view(view);
5229 report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5234 status_enter(struct view *view, struct line *line)
5236 struct status *status = line->data;
5237 const char *oldpath = status ? status->old.name : NULL;
5238 /* Diffs for unmerged entries are empty when passing the new
5239 * path, so leave it empty. */
5240 const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5242 enum open_flags split;
5243 struct view *stage = VIEW(REQ_VIEW_STAGE);
5245 if (line->type == LINE_STAT_NONE ||
5246 (!status && line[1].type == LINE_STAT_NONE)) {
5247 report("No file to diff");
5251 switch (line->type) {
5252 case LINE_STAT_STAGED:
5253 if (is_initial_commit()) {
5254 const char *no_head_diff_argv[] = {
5255 "git", "diff", "--no-color", "--patch-with-stat",
5256 "--", "/dev/null", newpath, NULL
5259 if (!prepare_update(stage, no_head_diff_argv, opt_cdup, FORMAT_DASH))
5260 return status_load_error(view, stage, newpath);
5262 const char *index_show_argv[] = {
5263 "git", "diff-index", "--root", "--patch-with-stat",
5264 "-C", "-M", "--cached", "HEAD", "--",
5265 oldpath, newpath, NULL
5268 if (!prepare_update(stage, index_show_argv, opt_cdup, FORMAT_DASH))
5269 return status_load_error(view, stage, newpath);
5273 info = "Staged changes to %s";
5275 info = "Staged changes";
5278 case LINE_STAT_UNSTAGED:
5280 const char *files_show_argv[] = {
5281 "git", "diff-files", "--root", "--patch-with-stat",
5282 "-C", "-M", "--", oldpath, newpath, NULL
5285 if (!prepare_update(stage, files_show_argv, opt_cdup, FORMAT_DASH))
5286 return status_load_error(view, stage, newpath);
5288 info = "Unstaged changes to %s";
5290 info = "Unstaged changes";
5293 case LINE_STAT_UNTRACKED:
5295 report("No file to show");
5299 if (!suffixcmp(status->new.name, -1, "/")) {
5300 report("Cannot display a directory");
5304 if (!prepare_update_file(stage, newpath))
5305 return status_load_error(view, stage, newpath);
5306 info = "Untracked file %s";
5309 case LINE_STAT_HEAD:
5313 die("line type %d not handled in switch", line->type);
5316 split = view_is_displayed(view) ? OPEN_SPLIT : 0;
5317 open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5318 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5320 stage_status = *status;
5322 memset(&stage_status, 0, sizeof(stage_status));
5325 stage_line_type = line->type;
5327 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5334 status_exists(struct status *status, enum line_type type)
5336 struct view *view = VIEW(REQ_VIEW_STATUS);
5337 unsigned long lineno;
5339 for (lineno = 0; lineno < view->lines; lineno++) {
5340 struct line *line = &view->line[lineno];
5341 struct status *pos = line->data;
5343 if (line->type != type)
5345 if (!pos && (!status || !status->status) && line[1].data) {
5346 select_view_line(view, lineno);
5349 if (pos && !strcmp(status->new.name, pos->new.name)) {
5350 select_view_line(view, lineno);
5360 status_update_prepare(struct io *io, enum line_type type)
5362 const char *staged_argv[] = {
5363 "git", "update-index", "-z", "--index-info", NULL
5365 const char *others_argv[] = {
5366 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5370 case LINE_STAT_STAGED:
5371 return run_io(io, staged_argv, opt_cdup, IO_WR);
5373 case LINE_STAT_UNSTAGED:
5374 return run_io(io, others_argv, opt_cdup, IO_WR);
5376 case LINE_STAT_UNTRACKED:
5377 return run_io(io, others_argv, NULL, IO_WR);
5380 die("line type %d not handled in switch", type);
5386 status_update_write(struct io *io, struct status *status, enum line_type type)
5388 char buf[SIZEOF_STR];
5392 case LINE_STAT_STAGED:
5393 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
5396 status->old.name, 0))
5400 case LINE_STAT_UNSTAGED:
5401 case LINE_STAT_UNTRACKED:
5402 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
5407 die("line type %d not handled in switch", type);
5410 return io_write(io, buf, bufsize);
5414 status_update_file(struct status *status, enum line_type type)
5419 if (!status_update_prepare(&io, type))
5422 result = status_update_write(&io, status, type);
5423 return done_io(&io) && result;
5427 status_update_files(struct view *view, struct line *line)
5429 char buf[sizeof(view->ref)];
5432 struct line *pos = view->line + view->lines;
5435 int cursor_y, cursor_x;
5437 if (!status_update_prepare(&io, line->type))
5440 for (pos = line; pos < view->line + view->lines && pos->data; pos++)
5443 string_copy(buf, view->ref);
5444 getsyx(cursor_y, cursor_x);
5445 for (file = 0, done = 5; result && file < files; line++, file++) {
5446 int almost_done = file * 100 / files;
5448 if (almost_done > done) {
5450 string_format(view->ref, "updating file %u of %u (%d%% done)",
5452 update_view_title(view);
5453 setsyx(cursor_y, cursor_x);
5456 result = status_update_write(&io, line->data, line->type);
5458 string_copy(view->ref, buf);
5460 return done_io(&io) && result;
5464 status_update(struct view *view)
5466 struct line *line = &view->line[view->lineno];
5468 assert(view->lines);
5471 /* This should work even for the "On branch" line. */
5472 if (line < view->line + view->lines && !line[1].data) {
5473 report("Nothing to update");
5477 if (!status_update_files(view, line + 1)) {
5478 report("Failed to update file status");
5482 } else if (!status_update_file(line->data, line->type)) {
5483 report("Failed to update file status");
5491 status_revert(struct status *status, enum line_type type, bool has_none)
5493 if (!status || type != LINE_STAT_UNSTAGED) {
5494 if (type == LINE_STAT_STAGED) {
5495 report("Cannot revert changes to staged files");
5496 } else if (type == LINE_STAT_UNTRACKED) {
5497 report("Cannot revert changes to untracked files");
5498 } else if (has_none) {
5499 report("Nothing to revert");
5501 report("Cannot revert changes to multiple files");
5506 char mode[10] = "100644";
5507 const char *reset_argv[] = {
5508 "git", "update-index", "--cacheinfo", mode,
5509 status->old.rev, status->old.name, NULL
5511 const char *checkout_argv[] = {
5512 "git", "checkout", "--", status->old.name, NULL
5515 if (!prompt_yesno("Are you sure you want to overwrite any changes?"))
5517 string_format(mode, "%o", status->old.mode);
5518 return (status->status != 'U' || run_io_fg(reset_argv, opt_cdup)) &&
5519 run_io_fg(checkout_argv, opt_cdup);
5524 status_request(struct view *view, enum request request, struct line *line)
5526 struct status *status = line->data;
5529 case REQ_STATUS_UPDATE:
5530 if (!status_update(view))
5534 case REQ_STATUS_REVERT:
5535 if (!status_revert(status, line->type, status_has_none(view, line)))
5539 case REQ_STATUS_MERGE:
5540 if (!status || status->status != 'U') {
5541 report("Merging only possible for files with unmerged status ('U').");
5544 open_mergetool(status->new.name);
5550 if (status->status == 'D') {
5551 report("File has been deleted.");
5555 open_editor(status->status != '?', status->new.name);
5558 case REQ_VIEW_BLAME:
5560 string_copy(opt_file, status->new.name);
5566 /* After returning the status view has been split to
5567 * show the stage view. No further reloading is
5569 return status_enter(view, line);
5572 /* Simply reload the view. */
5579 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
5585 status_select(struct view *view, struct line *line)
5587 struct status *status = line->data;
5588 char file[SIZEOF_STR] = "all files";
5592 if (status && !string_format(file, "'%s'", status->new.name))
5595 if (!status && line[1].type == LINE_STAT_NONE)
5598 switch (line->type) {
5599 case LINE_STAT_STAGED:
5600 text = "Press %s to unstage %s for commit";
5603 case LINE_STAT_UNSTAGED:
5604 text = "Press %s to stage %s for commit";
5607 case LINE_STAT_UNTRACKED:
5608 text = "Press %s to stage %s for addition";
5611 case LINE_STAT_HEAD:
5612 case LINE_STAT_NONE:
5613 text = "Nothing to update";
5617 die("line type %d not handled in switch", line->type);
5620 if (status && status->status == 'U') {
5621 text = "Press %s to resolve conflict in %s";
5622 key = get_key(REQ_STATUS_MERGE);
5625 key = get_key(REQ_STATUS_UPDATE);
5628 string_format(view->ref, text, key, file);
5632 status_grep(struct view *view, struct line *line)
5634 struct status *status = line->data;
5637 const char buf[2] = { status->status, 0 };
5638 const char *text[] = { status->new.name, buf, NULL };
5640 return grep_text(view, text);
5646 static struct view_ops status_ops = {
5659 stage_diff_write(struct io *io, struct line *line, struct line *end)
5661 while (line < end) {
5662 if (!io_write(io, line->data, strlen(line->data)) ||
5663 !io_write(io, "\n", 1))
5666 if (line->type == LINE_DIFF_CHUNK ||
5667 line->type == LINE_DIFF_HEADER)
5674 static struct line *
5675 stage_diff_find(struct view *view, struct line *line, enum line_type type)
5677 for (; view->line < line; line--)
5678 if (line->type == type)
5685 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
5687 const char *apply_argv[SIZEOF_ARG] = {
5688 "git", "apply", "--whitespace=nowarn", NULL
5690 struct line *diff_hdr;
5694 diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
5699 apply_argv[argc++] = "--cached";
5700 if (revert || stage_line_type == LINE_STAT_STAGED)
5701 apply_argv[argc++] = "-R";
5702 apply_argv[argc++] = "-";
5703 apply_argv[argc++] = NULL;
5704 if (!run_io(&io, apply_argv, opt_cdup, IO_WR))
5707 if (!stage_diff_write(&io, diff_hdr, chunk) ||
5708 !stage_diff_write(&io, chunk, view->line + view->lines))
5712 run_io_bg(update_index_argv);
5714 return chunk ? TRUE : FALSE;
5718 stage_update(struct view *view, struct line *line)
5720 struct line *chunk = NULL;
5722 if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
5723 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5726 if (!stage_apply_chunk(view, chunk, FALSE)) {
5727 report("Failed to apply chunk");
5731 } else if (!stage_status.status) {
5732 view = VIEW(REQ_VIEW_STATUS);
5734 for (line = view->line; line < view->line + view->lines; line++)
5735 if (line->type == stage_line_type)
5738 if (!status_update_files(view, line + 1)) {
5739 report("Failed to update files");
5743 } else if (!status_update_file(&stage_status, stage_line_type)) {
5744 report("Failed to update file");
5752 stage_revert(struct view *view, struct line *line)
5754 struct line *chunk = NULL;
5756 if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
5757 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5760 if (!prompt_yesno("Are you sure you want to revert changes?"))
5763 if (!stage_apply_chunk(view, chunk, TRUE)) {
5764 report("Failed to revert chunk");
5770 return status_revert(stage_status.status ? &stage_status : NULL,
5771 stage_line_type, FALSE);
5777 stage_next(struct view *view, struct line *line)
5781 if (!stage_chunks) {
5782 for (line = view->line; line < view->line + view->lines; line++) {
5783 if (line->type != LINE_DIFF_CHUNK)
5786 if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
5787 report("Allocation failure");
5791 stage_chunk[stage_chunks++] = line - view->line;
5795 for (i = 0; i < stage_chunks; i++) {
5796 if (stage_chunk[i] > view->lineno) {
5797 do_scroll_view(view, stage_chunk[i] - view->lineno);
5798 report("Chunk %d of %d", i + 1, stage_chunks);
5803 report("No next chunk found");
5807 stage_request(struct view *view, enum request request, struct line *line)
5810 case REQ_STATUS_UPDATE:
5811 if (!stage_update(view, line))
5815 case REQ_STATUS_REVERT:
5816 if (!stage_revert(view, line))
5820 case REQ_STAGE_NEXT:
5821 if (stage_line_type == LINE_STAT_UNTRACKED) {
5822 report("File is untracked; press %s to add",
5823 get_key(REQ_STATUS_UPDATE));
5826 stage_next(view, line);
5830 if (!stage_status.new.name[0])
5832 if (stage_status.status == 'D') {
5833 report("File has been deleted.");
5837 open_editor(stage_status.status != '?', stage_status.new.name);
5841 /* Reload everything ... */
5844 case REQ_VIEW_BLAME:
5845 if (stage_status.new.name[0]) {
5846 string_copy(opt_file, stage_status.new.name);
5852 return pager_request(view, request, line);
5858 VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
5859 open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
5861 /* Check whether the staged entry still exists, and close the
5862 * stage view if it doesn't. */
5863 if (!status_exists(&stage_status, stage_line_type)) {
5864 status_restore(VIEW(REQ_VIEW_STATUS));
5865 return REQ_VIEW_CLOSE;
5868 if (stage_line_type == LINE_STAT_UNTRACKED) {
5869 if (!suffixcmp(stage_status.new.name, -1, "/")) {
5870 report("Cannot display a directory");
5874 if (!prepare_update_file(view, stage_status.new.name)) {
5875 report("Failed to open file: %s", strerror(errno));
5879 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
5884 static struct view_ops stage_ops = {
5901 char id[SIZEOF_REV]; /* SHA1 ID. */
5902 char title[128]; /* First line of the commit message. */
5903 const char *author; /* Author of the commit. */
5904 time_t time; /* Date from the author ident. */
5905 struct ref **refs; /* Repository references. */
5906 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
5907 size_t graph_size; /* The width of the graph array. */
5908 bool has_parents; /* Rewritten --parents seen. */
5911 /* Size of rev graph with no "padding" columns */
5912 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
5915 struct rev_graph *prev, *next, *parents;
5916 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
5918 struct commit *commit;
5920 unsigned int boundary:1;
5923 /* Parents of the commit being visualized. */
5924 static struct rev_graph graph_parents[4];
5926 /* The current stack of revisions on the graph. */
5927 static struct rev_graph graph_stacks[4] = {
5928 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
5929 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
5930 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
5931 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
5935 graph_parent_is_merge(struct rev_graph *graph)
5937 return graph->parents->size > 1;
5941 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
5943 struct commit *commit = graph->commit;
5945 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
5946 commit->graph[commit->graph_size++] = symbol;
5950 clear_rev_graph(struct rev_graph *graph)
5952 graph->boundary = 0;
5953 graph->size = graph->pos = 0;
5954 graph->commit = NULL;
5955 memset(graph->parents, 0, sizeof(*graph->parents));
5959 done_rev_graph(struct rev_graph *graph)
5961 if (graph_parent_is_merge(graph) &&
5962 graph->pos < graph->size - 1 &&
5963 graph->next->size == graph->size + graph->parents->size - 1) {
5964 size_t i = graph->pos + graph->parents->size - 1;
5966 graph->commit->graph_size = i * 2;
5967 while (i < graph->next->size - 1) {
5968 append_to_rev_graph(graph, ' ');
5969 append_to_rev_graph(graph, '\\');
5974 clear_rev_graph(graph);
5978 push_rev_graph(struct rev_graph *graph, const char *parent)
5982 /* "Collapse" duplicate parents lines.
5984 * FIXME: This needs to also update update the drawn graph but
5985 * for now it just serves as a method for pruning graph lines. */
5986 for (i = 0; i < graph->size; i++)
5987 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
5990 if (graph->size < SIZEOF_REVITEMS) {
5991 string_copy_rev(graph->rev[graph->size++], parent);
5996 get_rev_graph_symbol(struct rev_graph *graph)
6000 if (graph->boundary)
6001 symbol = REVGRAPH_BOUND;
6002 else if (graph->parents->size == 0)
6003 symbol = REVGRAPH_INIT;
6004 else if (graph_parent_is_merge(graph))
6005 symbol = REVGRAPH_MERGE;
6006 else if (graph->pos >= graph->size)
6007 symbol = REVGRAPH_BRANCH;
6009 symbol = REVGRAPH_COMMIT;
6015 draw_rev_graph(struct rev_graph *graph)
6018 chtype separator, line;
6020 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
6021 static struct rev_filler fillers[] = {
6027 chtype symbol = get_rev_graph_symbol(graph);
6028 struct rev_filler *filler;
6031 if (opt_line_graphics)
6032 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
6034 filler = &fillers[DEFAULT];
6036 for (i = 0; i < graph->pos; i++) {
6037 append_to_rev_graph(graph, filler->line);
6038 if (graph_parent_is_merge(graph->prev) &&
6039 graph->prev->pos == i)
6040 filler = &fillers[RSHARP];
6042 append_to_rev_graph(graph, filler->separator);
6045 /* Place the symbol for this revision. */
6046 append_to_rev_graph(graph, symbol);
6048 if (graph->prev->size > graph->size)
6049 filler = &fillers[RDIAG];
6051 filler = &fillers[DEFAULT];
6055 for (; i < graph->size; i++) {
6056 append_to_rev_graph(graph, filler->separator);
6057 append_to_rev_graph(graph, filler->line);
6058 if (graph_parent_is_merge(graph->prev) &&
6059 i < graph->prev->pos + graph->parents->size)
6060 filler = &fillers[RSHARP];
6061 if (graph->prev->size > graph->size)
6062 filler = &fillers[LDIAG];
6065 if (graph->prev->size > graph->size) {
6066 append_to_rev_graph(graph, filler->separator);
6067 if (filler->line != ' ')
6068 append_to_rev_graph(graph, filler->line);
6072 /* Prepare the next rev graph */
6074 prepare_rev_graph(struct rev_graph *graph)
6078 /* First, traverse all lines of revisions up to the active one. */
6079 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6080 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6083 push_rev_graph(graph->next, graph->rev[graph->pos]);
6086 /* Interleave the new revision parent(s). */
6087 for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6088 push_rev_graph(graph->next, graph->parents->rev[i]);
6090 /* Lastly, put any remaining revisions. */
6091 for (i = graph->pos + 1; i < graph->size; i++)
6092 push_rev_graph(graph->next, graph->rev[i]);
6096 update_rev_graph(struct view *view, struct rev_graph *graph)
6098 /* If this is the finalizing update ... */
6100 prepare_rev_graph(graph);
6102 /* Graph visualization needs a one rev look-ahead,
6103 * so the first update doesn't visualize anything. */
6104 if (!graph->prev->commit)
6107 if (view->lines > 2)
6108 view->line[view->lines - 3].dirty = 1;
6109 if (view->lines > 1)
6110 view->line[view->lines - 2].dirty = 1;
6111 draw_rev_graph(graph->prev);
6112 done_rev_graph(graph->prev->prev);
6120 static const char *main_argv[SIZEOF_ARG] = {
6121 "git", "log", "--no-color", "--pretty=raw", "--parents",
6122 "--topo-order", "%(head)", NULL
6126 main_draw(struct view *view, struct line *line, unsigned int lineno)
6128 struct commit *commit = line->data;
6130 if (!commit->author)
6133 if (opt_date && draw_date(view, &commit->time))
6136 if (opt_author && draw_author(view, commit->author))
6139 if (opt_rev_graph && commit->graph_size &&
6140 draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6143 if (opt_show_refs && commit->refs) {
6147 struct ref *ref = commit->refs[i];
6148 enum line_type type;
6151 type = LINE_MAIN_HEAD;
6153 type = LINE_MAIN_LOCAL_TAG;
6155 type = LINE_MAIN_TAG;
6156 else if (ref->tracked)
6157 type = LINE_MAIN_TRACKED;
6158 else if (ref->remote)
6159 type = LINE_MAIN_REMOTE;
6161 type = LINE_MAIN_REF;
6163 if (draw_text(view, type, "[", TRUE) ||
6164 draw_text(view, type, ref->name, TRUE) ||
6165 draw_text(view, type, "]", TRUE))
6168 if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6170 } while (commit->refs[i++]->next);
6173 draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6177 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6179 main_read(struct view *view, char *line)
6181 static struct rev_graph *graph = graph_stacks;
6182 enum line_type type;
6183 struct commit *commit;
6188 if (!view->lines && !view->parent)
6189 die("No revisions match the given arguments.");
6190 if (view->lines > 0) {
6191 commit = view->line[view->lines - 1].data;
6192 view->line[view->lines - 1].dirty = 1;
6193 if (!commit->author) {
6196 graph->commit = NULL;
6199 update_rev_graph(view, graph);
6201 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6202 clear_rev_graph(&graph_stacks[i]);
6206 type = get_line_type(line);
6207 if (type == LINE_COMMIT) {
6208 commit = calloc(1, sizeof(struct commit));
6212 line += STRING_SIZE("commit ");
6214 graph->boundary = 1;
6218 string_copy_rev(commit->id, line);
6219 commit->refs = get_refs(commit->id);
6220 graph->commit = commit;
6221 add_line_data(view, commit, LINE_MAIN_COMMIT);
6223 while ((line = strchr(line, ' '))) {
6225 push_rev_graph(graph->parents, line);
6226 commit->has_parents = TRUE;
6233 commit = view->line[view->lines - 1].data;
6237 if (commit->has_parents)
6239 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6243 parse_author_line(line + STRING_SIZE("author "),
6244 &commit->author, &commit->time);
6245 update_rev_graph(view, graph);
6246 graph = graph->next;
6250 /* Fill in the commit title if it has not already been set. */
6251 if (commit->title[0])
6254 /* Require titles to start with a non-space character at the
6255 * offset used by git log. */
6256 if (strncmp(line, " ", 4))
6259 /* Well, if the title starts with a whitespace character,
6260 * try to be forgiving. Otherwise we end up with no title. */
6261 while (isspace(*line))
6265 /* FIXME: More graceful handling of titles; append "..." to
6266 * shortened titles, etc. */
6268 string_expand(commit->title, sizeof(commit->title), line, 1);
6269 view->line[view->lines - 1].dirty = 1;
6276 main_request(struct view *view, enum request request, struct line *line)
6278 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
6282 open_view(view, REQ_VIEW_DIFF, flags);
6286 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6296 grep_refs(struct ref **refs, regex_t *regex)
6301 if (!opt_show_refs || !refs)
6304 if (regexec(regex, refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6306 } while (refs[i++]->next);
6312 main_grep(struct view *view, struct line *line)
6314 struct commit *commit = line->data;
6315 const char *text[] = {
6317 opt_author ? commit->author : "",
6318 opt_date ? mkdate(&commit->time) : "",
6322 return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6326 main_select(struct view *view, struct line *line)
6328 struct commit *commit = line->data;
6330 string_copy_rev(view->ref, commit->id);
6331 string_copy_rev(ref_commit, view->ref);
6334 static struct view_ops main_ops = {
6347 * Unicode / UTF-8 handling
6349 * NOTE: Much of the following code for dealing with Unicode is derived from
6350 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
6351 * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
6355 unicode_width(unsigned long c)
6358 (c <= 0x115f /* Hangul Jamo */
6361 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
6363 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
6364 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
6365 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
6366 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
6367 || (c >= 0xffe0 && c <= 0xffe6)
6368 || (c >= 0x20000 && c <= 0x2fffd)
6369 || (c >= 0x30000 && c <= 0x3fffd)))
6373 return opt_tab_size;
6378 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
6379 * Illegal bytes are set one. */
6380 static const unsigned char utf8_bytes[256] = {
6381 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,
6382 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,
6383 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,
6384 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,
6385 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,
6386 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,
6387 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,
6388 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,
6391 /* Decode UTF-8 multi-byte representation into a Unicode character. */
6392 static inline unsigned long
6393 utf8_to_unicode(const char *string, size_t length)
6395 unsigned long unicode;
6399 unicode = string[0];
6402 unicode = (string[0] & 0x1f) << 6;
6403 unicode += (string[1] & 0x3f);
6406 unicode = (string[0] & 0x0f) << 12;
6407 unicode += ((string[1] & 0x3f) << 6);
6408 unicode += (string[2] & 0x3f);
6411 unicode = (string[0] & 0x0f) << 18;
6412 unicode += ((string[1] & 0x3f) << 12);
6413 unicode += ((string[2] & 0x3f) << 6);
6414 unicode += (string[3] & 0x3f);
6417 unicode = (string[0] & 0x0f) << 24;
6418 unicode += ((string[1] & 0x3f) << 18);
6419 unicode += ((string[2] & 0x3f) << 12);
6420 unicode += ((string[3] & 0x3f) << 6);
6421 unicode += (string[4] & 0x3f);
6424 unicode = (string[0] & 0x01) << 30;
6425 unicode += ((string[1] & 0x3f) << 24);
6426 unicode += ((string[2] & 0x3f) << 18);
6427 unicode += ((string[3] & 0x3f) << 12);
6428 unicode += ((string[4] & 0x3f) << 6);
6429 unicode += (string[5] & 0x3f);
6432 die("Invalid Unicode length");
6435 /* Invalid characters could return the special 0xfffd value but NUL
6436 * should be just as good. */
6437 return unicode > 0xffff ? 0 : unicode;
6440 /* Calculates how much of string can be shown within the given maximum width
6441 * and sets trimmed parameter to non-zero value if all of string could not be
6442 * shown. If the reserve flag is TRUE, it will reserve at least one
6443 * trailing character, which can be useful when drawing a delimiter.
6445 * Returns the number of bytes to output from string to satisfy max_width. */
6447 utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve)
6449 const char *string = *start;
6450 const char *end = strchr(string, '\0');
6451 unsigned char last_bytes = 0;
6452 size_t last_ucwidth = 0;
6457 while (string < end) {
6458 int c = *(unsigned char *) string;
6459 unsigned char bytes = utf8_bytes[c];
6461 unsigned long unicode;
6463 if (string + bytes > end)
6466 /* Change representation to figure out whether
6467 * it is a single- or double-width character. */
6469 unicode = utf8_to_unicode(string, bytes);
6470 /* FIXME: Graceful handling of invalid Unicode character. */
6474 ucwidth = unicode_width(unicode);
6476 skip -= ucwidth <= skip ? ucwidth : skip;
6480 if (*width > max_width) {
6483 if (reserve && *width == max_width) {
6484 string -= last_bytes;
6485 *width -= last_ucwidth;
6491 last_bytes = ucwidth ? bytes : 0;
6492 last_ucwidth = ucwidth;
6495 return string - *start;
6503 /* Whether or not the curses interface has been initialized. */
6504 static bool cursed = FALSE;
6506 /* Terminal hacks and workarounds. */
6507 static bool use_scroll_redrawwin;
6508 static bool use_scroll_status_wclear;
6510 /* The status window is used for polling keystrokes. */
6511 static WINDOW *status_win;
6513 /* Reading from the prompt? */
6514 static bool input_mode = FALSE;
6516 static bool status_empty = FALSE;
6518 /* Update status and title window. */
6520 report(const char *msg, ...)
6522 struct view *view = display[current_view];
6528 char buf[SIZEOF_STR];
6531 va_start(args, msg);
6532 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6533 buf[sizeof(buf) - 1] = 0;
6534 buf[sizeof(buf) - 2] = '.';
6535 buf[sizeof(buf) - 3] = '.';
6536 buf[sizeof(buf) - 4] = '.';
6542 if (!status_empty || *msg) {
6545 va_start(args, msg);
6547 wmove(status_win, 0, 0);
6548 if (view->has_scrolled && use_scroll_status_wclear)
6551 vwprintw(status_win, msg, args);
6552 status_empty = FALSE;
6554 status_empty = TRUE;
6556 wclrtoeol(status_win);
6557 wnoutrefresh(status_win);
6562 update_view_title(view);
6565 /* Controls when nodelay should be in effect when polling user input. */
6567 set_nonblocking_input(bool loading)
6569 static unsigned int loading_views;
6571 if ((loading == FALSE && loading_views-- == 1) ||
6572 (loading == TRUE && loading_views++ == 0))
6573 nodelay(status_win, loading);
6582 /* Initialize the curses library */
6583 if (isatty(STDIN_FILENO)) {
6584 cursed = !!initscr();
6587 /* Leave stdin and stdout alone when acting as a pager. */
6588 opt_tty = fopen("/dev/tty", "r+");
6590 die("Failed to open /dev/tty");
6591 cursed = !!newterm(NULL, opt_tty, opt_tty);
6595 die("Failed to initialize curses");
6597 nonl(); /* Disable conversion and detect newlines from input. */
6598 cbreak(); /* Take input chars one at a time, no wait for \n */
6599 noecho(); /* Don't echo input */
6600 leaveok(stdscr, FALSE);
6605 getmaxyx(stdscr, y, x);
6606 status_win = newwin(1, 0, y - 1, 0);
6608 die("Failed to create status window");
6610 /* Enable keyboard mapping */
6611 keypad(status_win, TRUE);
6612 wbkgdset(status_win, get_line_attr(LINE_STATUS));
6614 TABSIZE = opt_tab_size;
6615 if (opt_line_graphics) {
6616 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
6619 term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
6620 if (term && !strcmp(term, "gnome-terminal")) {
6621 /* In the gnome-terminal-emulator, the message from
6622 * scrolling up one line when impossible followed by
6623 * scrolling down one line causes corruption of the
6624 * status line. This is fixed by calling wclear. */
6625 use_scroll_status_wclear = TRUE;
6626 use_scroll_redrawwin = FALSE;
6628 } else if (term && !strcmp(term, "xrvt-xpm")) {
6629 /* No problems with full optimizations in xrvt-(unicode)
6631 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
6634 /* When scrolling in (u)xterm the last line in the
6635 * scrolling direction will update slowly. */
6636 use_scroll_redrawwin = TRUE;
6637 use_scroll_status_wclear = FALSE;
6642 get_input(int prompt_position)
6645 int i, key, cursor_y, cursor_x;
6647 if (prompt_position)
6651 foreach_view (view, i) {
6653 if (view_is_displayed(view) && view->has_scrolled &&
6654 use_scroll_redrawwin)
6655 redrawwin(view->win);
6656 view->has_scrolled = FALSE;
6659 /* Update the cursor position. */
6660 if (prompt_position) {
6661 getbegyx(status_win, cursor_y, cursor_x);
6662 cursor_x = prompt_position;
6664 view = display[current_view];
6665 getbegyx(view->win, cursor_y, cursor_x);
6666 cursor_x = view->width - 1;
6667 cursor_y += view->lineno - view->offset;
6669 setsyx(cursor_y, cursor_x);
6671 /* Refresh, accept single keystroke of input */
6673 key = wgetch(status_win);
6675 /* wgetch() with nodelay() enabled returns ERR when
6676 * there's no input. */
6679 } else if (key == KEY_RESIZE) {
6682 getmaxyx(stdscr, height, width);
6684 wresize(status_win, 1, width);
6685 mvwin(status_win, height - 1, 0);
6686 wnoutrefresh(status_win);
6688 redraw_display(TRUE);
6698 prompt_input(const char *prompt, input_handler handler, void *data)
6700 enum input_status status = INPUT_OK;
6701 static char buf[SIZEOF_STR];
6706 while (status == INPUT_OK || status == INPUT_SKIP) {
6709 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
6710 wclrtoeol(status_win);
6712 key = get_input(pos + 1);
6717 status = pos ? INPUT_STOP : INPUT_CANCEL;
6724 status = INPUT_CANCEL;
6728 status = INPUT_CANCEL;
6732 if (pos >= sizeof(buf)) {
6733 report("Input string too long");
6737 status = handler(data, buf, key);
6738 if (status == INPUT_OK)
6739 buf[pos++] = (char) key;
6743 /* Clear the status window */
6744 status_empty = FALSE;
6747 if (status == INPUT_CANCEL)
6755 static enum input_status
6756 prompt_yesno_handler(void *data, char *buf, int c)
6758 if (c == 'y' || c == 'Y')
6760 if (c == 'n' || c == 'N')
6761 return INPUT_CANCEL;
6766 prompt_yesno(const char *prompt)
6768 char prompt2[SIZEOF_STR];
6770 if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
6773 return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
6776 static enum input_status
6777 read_prompt_handler(void *data, char *buf, int c)
6779 return isprint(c) ? INPUT_OK : INPUT_SKIP;
6783 read_prompt(const char *prompt)
6785 return prompt_input(prompt, read_prompt_handler, NULL);
6789 * Repository properties
6792 static struct ref *refs = NULL;
6793 static size_t refs_size = 0;
6795 /* Id <-> ref store */
6796 static struct ref ***id_refs = NULL;
6797 static size_t id_refs_size = 0;
6799 DEFINE_ALLOCATOR(realloc_refs, struct ref, 256)
6800 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
6801 DEFINE_ALLOCATOR(realloc_refs_lists, struct ref **, 8)
6804 compare_refs(const void *ref1_, const void *ref2_)
6806 const struct ref *ref1 = *(const struct ref **)ref1_;
6807 const struct ref *ref2 = *(const struct ref **)ref2_;
6809 if (ref1->tag != ref2->tag)
6810 return ref2->tag - ref1->tag;
6811 if (ref1->ltag != ref2->ltag)
6812 return ref2->ltag - ref2->ltag;
6813 if (ref1->head != ref2->head)
6814 return ref2->head - ref1->head;
6815 if (ref1->tracked != ref2->tracked)
6816 return ref2->tracked - ref1->tracked;
6817 if (ref1->remote != ref2->remote)
6818 return ref2->remote - ref1->remote;
6819 return strcmp(ref1->name, ref2->name);
6823 foreach_ref(bool (*visitor)(void *data, struct ref *ref), void *data)
6827 for (i = 0; i < refs_size; i++)
6828 if (!visitor(data, &refs[i]))
6832 static struct ref **
6833 get_refs(const char *id)
6835 struct ref **ref_list = NULL;
6836 size_t ref_list_size = 0;
6839 for (i = 0; i < id_refs_size; i++)
6840 if (!strcmp(id, id_refs[i][0]->id))
6843 if (!realloc_refs_lists(&id_refs, id_refs_size, 1))
6846 for (i = 0; i < refs_size; i++) {
6847 if (strcmp(id, refs[i].id))
6850 if (!realloc_refs_list(&ref_list, ref_list_size, 1))
6853 ref_list[ref_list_size] = &refs[i];
6854 /* XXX: The properties of the commit chains ensures that we can
6855 * safely modify the shared ref. The repo references will
6856 * always be similar for the same id. */
6857 ref_list[ref_list_size]->next = 1;
6862 qsort(ref_list, ref_list_size, sizeof(*ref_list), compare_refs);
6863 ref_list[ref_list_size - 1]->next = 0;
6864 id_refs[id_refs_size++] = ref_list;
6871 read_ref(char *id, size_t idlen, char *name, size_t namelen)
6876 bool remote = FALSE;
6877 bool tracked = FALSE;
6878 bool check_replace = FALSE;
6881 if (!prefixcmp(name, "refs/tags/")) {
6882 if (!suffixcmp(name, namelen, "^{}")) {
6885 if (refs_size > 0 && refs[refs_size - 1].ltag == TRUE)
6886 check_replace = TRUE;
6892 namelen -= STRING_SIZE("refs/tags/");
6893 name += STRING_SIZE("refs/tags/");
6895 } else if (!prefixcmp(name, "refs/remotes/")) {
6897 namelen -= STRING_SIZE("refs/remotes/");
6898 name += STRING_SIZE("refs/remotes/");
6899 tracked = !strcmp(opt_remote, name);
6901 } else if (!prefixcmp(name, "refs/heads/")) {
6902 namelen -= STRING_SIZE("refs/heads/");
6903 name += STRING_SIZE("refs/heads/");
6904 head = !strncmp(opt_head, name, namelen);
6906 } else if (!strcmp(name, "HEAD")) {
6907 string_ncopy(opt_head_rev, id, idlen);
6911 if (check_replace && !strcmp(name, refs[refs_size - 1].name)) {
6912 /* it's an annotated tag, replace the previous SHA1 with the
6913 * resolved commit id; relies on the fact git-ls-remote lists
6914 * the commit id of an annotated tag right before the commit id
6916 refs[refs_size - 1].ltag = ltag;
6917 string_copy_rev(refs[refs_size - 1].id, id);
6922 if (!realloc_refs(&refs, refs_size, 1))
6925 ref = &refs[refs_size++];
6926 ref->name = malloc(namelen + 1);
6930 strncpy(ref->name, name, namelen);
6931 ref->name[namelen] = 0;
6935 ref->remote = remote;
6936 ref->tracked = tracked;
6937 string_copy_rev(ref->id, id);
6945 const char *head_argv[] = {
6946 "git", "symbolic-ref", "HEAD", NULL
6948 static const char *ls_remote_argv[SIZEOF_ARG] = {
6949 "git", "ls-remote", opt_git_dir, NULL
6951 static bool init = FALSE;
6954 argv_from_env(ls_remote_argv, "TIG_LS_REMOTE");
6961 if (run_io_buf(head_argv, opt_head, sizeof(opt_head)) &&
6962 !prefixcmp(opt_head, "refs/heads/")) {
6963 char *offset = opt_head + STRING_SIZE("refs/heads/");
6965 memmove(opt_head, offset, strlen(offset) + 1);
6968 while (refs_size > 0)
6969 free(refs[--refs_size].name);
6970 while (id_refs_size > 0)
6971 free(id_refs[--id_refs_size]);
6973 return run_io_load(ls_remote_argv, "\t", read_ref);
6977 set_remote_branch(const char *name, const char *value, size_t valuelen)
6979 if (!strcmp(name, ".remote")) {
6980 string_ncopy(opt_remote, value, valuelen);
6982 } else if (*opt_remote && !strcmp(name, ".merge")) {
6983 size_t from = strlen(opt_remote);
6985 if (!prefixcmp(value, "refs/heads/"))
6986 value += STRING_SIZE("refs/heads/");
6988 if (!string_format_from(opt_remote, &from, "/%s", value))
6994 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
6996 const char *argv[SIZEOF_ARG] = { name, "=" };
6997 int argc = 1 + (cmd == option_set_command);
7000 if (!argv_from_string(argv, &argc, value))
7001 config_msg = "Too many option arguments";
7003 error = cmd(argc, argv);
7006 warn("Option 'tig.%s': %s", name, config_msg);
7010 set_environment_variable(const char *name, const char *value)
7012 size_t len = strlen(name) + 1 + strlen(value) + 1;
7013 char *env = malloc(len);
7016 string_nformat(env, len, NULL, "%s=%s", name, value) &&
7024 set_work_tree(const char *value)
7026 char cwd[SIZEOF_STR];
7028 if (!getcwd(cwd, sizeof(cwd)))
7029 die("Failed to get cwd path: %s", strerror(errno));
7030 if (chdir(opt_git_dir) < 0)
7031 die("Failed to chdir(%s): %s", strerror(errno));
7032 if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7033 die("Failed to get git path: %s", strerror(errno));
7035 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7036 if (chdir(value) < 0)
7037 die("Failed to chdir(%s): %s", value, strerror(errno));
7038 if (!getcwd(cwd, sizeof(cwd)))
7039 die("Failed to get cwd path: %s", strerror(errno));
7040 if (!set_environment_variable("GIT_WORK_TREE", cwd))
7041 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7042 if (!set_environment_variable("GIT_DIR", opt_git_dir))
7043 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7044 opt_is_inside_work_tree = TRUE;
7048 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
7050 if (!strcmp(name, "i18n.commitencoding"))
7051 string_ncopy(opt_encoding, value, valuelen);
7053 else if (!strcmp(name, "core.editor"))
7054 string_ncopy(opt_editor, value, valuelen);
7056 else if (!strcmp(name, "core.worktree"))
7057 set_work_tree(value);
7059 else if (!prefixcmp(name, "tig.color."))
7060 set_repo_config_option(name + 10, value, option_color_command);
7062 else if (!prefixcmp(name, "tig.bind."))
7063 set_repo_config_option(name + 9, value, option_bind_command);
7065 else if (!prefixcmp(name, "tig."))
7066 set_repo_config_option(name + 4, value, option_set_command);
7068 else if (*opt_head && !prefixcmp(name, "branch.") &&
7069 !strncmp(name + 7, opt_head, strlen(opt_head)))
7070 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7076 load_git_config(void)
7078 const char *config_list_argv[] = { "git", GIT_CONFIG, "--list", NULL };
7080 return run_io_load(config_list_argv, "=", read_repo_config_option);
7084 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
7086 if (!opt_git_dir[0]) {
7087 string_ncopy(opt_git_dir, name, namelen);
7089 } else if (opt_is_inside_work_tree == -1) {
7090 /* This can be 3 different values depending on the
7091 * version of git being used. If git-rev-parse does not
7092 * understand --is-inside-work-tree it will simply echo
7093 * the option else either "true" or "false" is printed.
7094 * Default to true for the unknown case. */
7095 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7097 } else if (*name == '.') {
7098 string_ncopy(opt_cdup, name, namelen);
7101 string_ncopy(opt_prefix, name, namelen);
7108 load_repo_info(void)
7110 const char *rev_parse_argv[] = {
7111 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7112 "--show-cdup", "--show-prefix", NULL
7115 return run_io_load(rev_parse_argv, "=", read_repo_info);
7123 static const char usage[] =
7124 "tig " TIG_VERSION " (" __DATE__ ")\n"
7126 "Usage: tig [options] [revs] [--] [paths]\n"
7127 " or: tig show [options] [revs] [--] [paths]\n"
7128 " or: tig blame [rev] path\n"
7130 " or: tig < [git command output]\n"
7133 " -v, --version Show version and exit\n"
7134 " -h, --help Show help message and exit";
7136 static void __NORETURN
7139 /* XXX: Restore tty modes and let the OS cleanup the rest! */
7145 static void __NORETURN
7146 die(const char *err, ...)
7152 va_start(args, err);
7153 fputs("tig: ", stderr);
7154 vfprintf(stderr, err, args);
7155 fputs("\n", stderr);
7162 warn(const char *msg, ...)
7166 va_start(args, msg);
7167 fputs("tig warning: ", stderr);
7168 vfprintf(stderr, msg, args);
7169 fputs("\n", stderr);
7174 parse_options(int argc, const char *argv[])
7176 enum request request = REQ_VIEW_MAIN;
7177 const char *subcommand;
7178 bool seen_dashdash = FALSE;
7179 /* XXX: This is vulnerable to the user overriding options
7180 * required for the main view parser. */
7181 const char *custom_argv[SIZEOF_ARG] = {
7182 "git", "log", "--no-color", "--pretty=raw", "--parents",
7183 "--topo-order", NULL
7187 if (!isatty(STDIN_FILENO)) {
7188 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
7189 return REQ_VIEW_PAGER;
7195 subcommand = argv[1];
7196 if (!strcmp(subcommand, "status")) {
7198 warn("ignoring arguments after `%s'", subcommand);
7199 return REQ_VIEW_STATUS;
7201 } else if (!strcmp(subcommand, "blame")) {
7202 if (argc <= 2 || argc > 4)
7203 die("invalid number of options to blame\n\n%s", usage);
7207 string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7211 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7212 return REQ_VIEW_BLAME;
7214 } else if (!strcmp(subcommand, "show")) {
7215 request = REQ_VIEW_DIFF;
7222 custom_argv[1] = subcommand;
7226 for (i = 1 + !!subcommand; i < argc; i++) {
7227 const char *opt = argv[i];
7229 if (seen_dashdash || !strcmp(opt, "--")) {
7230 seen_dashdash = TRUE;
7232 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7233 printf("tig version %s\n", TIG_VERSION);
7236 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7237 printf("%s\n", usage);
7241 custom_argv[j++] = opt;
7242 if (j >= ARRAY_SIZE(custom_argv))
7243 die("command too long");
7246 if (!prepare_update(VIEW(request), custom_argv, NULL, FORMAT_NONE))
7247 die("Failed to format arguments");
7253 main(int argc, const char *argv[])
7255 enum request request = parse_options(argc, argv);
7259 signal(SIGINT, quit);
7260 signal(SIGPIPE, SIG_IGN);
7262 if (setlocale(LC_ALL, "")) {
7263 char *codeset = nl_langinfo(CODESET);
7265 string_ncopy(opt_codeset, codeset, strlen(codeset));
7268 if (load_repo_info() == ERR)
7269 die("Failed to load repo info.");
7271 if (load_options() == ERR)
7272 die("Failed to load user config.");
7274 if (load_git_config() == ERR)
7275 die("Failed to load repo config.");
7277 /* Require a git repository unless when running in pager mode. */
7278 if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7279 die("Not a git repository");
7281 if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
7284 if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
7285 opt_iconv = iconv_open(opt_codeset, opt_encoding);
7286 if (opt_iconv == ICONV_NONE)
7287 die("Failed to initialize character set conversion");
7290 if (load_refs() == ERR)
7291 die("Failed to load refs.");
7293 foreach_view (view, i)
7294 argv_from_env(view->ops->argv, view->cmd_env);
7298 if (request != REQ_NONE)
7299 open_view(NULL, request, OPEN_PREPARED);
7300 request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
7302 while (view_driver(display[current_view], request)) {
7303 int key = get_input(0);
7305 view = display[current_view];
7306 request = get_keybinding(view->keymap, key);
7308 /* Some low-level request handling. This keeps access to
7309 * status_win restricted. */
7313 char *cmd = read_prompt(":");
7315 if (cmd && isdigit(*cmd)) {
7316 int lineno = view->lineno + 1;
7318 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7319 select_view_line(view, lineno - 1);
7322 report("Unable to parse '%s' as a line number", cmd);
7326 struct view *next = VIEW(REQ_VIEW_PAGER);
7327 const char *argv[SIZEOF_ARG] = { "git" };
7330 /* When running random commands, initially show the
7331 * command in the title. However, it maybe later be
7332 * overwritten if a commit line is selected. */
7333 string_ncopy(next->ref, cmd, strlen(cmd));
7335 if (!argv_from_string(argv, &argc, cmd)) {
7336 report("Too many arguments");
7337 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
7338 report("Failed to format command");
7340 open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7348 case REQ_SEARCH_BACK:
7350 const char *prompt = request == REQ_SEARCH ? "/" : "?";
7351 char *search = read_prompt(prompt);
7354 string_ncopy(opt_search, search, strlen(search));
7355 else if (*opt_search)
7356 request = request == REQ_SEARCH ?