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 *gc[] = { "git", "gc", NULL };
1379 { KEYMAP_MAIN, 'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1380 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1384 for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1387 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1388 if (req != REQ_NONE)
1389 add_keybinding(reqs[i].keymap, req, reqs[i].key);
1394 * User config file handling.
1397 static int config_lineno;
1398 static bool config_errors;
1399 static const char *config_msg;
1401 static const struct enum_map color_map[] = {
1402 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1414 static const struct enum_map attr_map[] = {
1415 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1422 ATTR_MAP(UNDERLINE),
1425 #define set_attribute(attr, name) map_enum(attr, attr_map, name)
1427 static int parse_step(double *opt, const char *arg)
1430 if (!strchr(arg, '%'))
1433 /* "Shift down" so 100% and 1 does not conflict. */
1434 *opt = (*opt - 1) / 100;
1437 config_msg = "Step value larger than 100%";
1442 config_msg = "Invalid step value";
1449 parse_int(int *opt, const char *arg, int min, int max)
1451 int value = atoi(arg);
1453 if (min <= value && value <= max) {
1458 config_msg = "Integer value out of bound";
1463 set_color(int *color, const char *name)
1465 if (map_enum(color, color_map, name))
1467 if (!prefixcmp(name, "color"))
1468 return parse_int(color, name + 5, 0, 255) == OK;
1472 /* Wants: object fgcolor bgcolor [attribute] */
1474 option_color_command(int argc, const char *argv[])
1476 struct line_info *info;
1478 if (argc != 3 && argc != 4) {
1479 config_msg = "Wrong number of arguments given to color command";
1483 info = get_line_info(argv[0]);
1485 static const struct enum_map obsolete[] = {
1486 ENUM_MAP("main-delim", LINE_DELIMITER),
1487 ENUM_MAP("main-date", LINE_DATE),
1488 ENUM_MAP("main-author", LINE_AUTHOR),
1492 if (!map_enum(&index, obsolete, argv[0])) {
1493 config_msg = "Unknown color name";
1496 info = &line_info[index];
1499 if (!set_color(&info->fg, argv[1]) ||
1500 !set_color(&info->bg, argv[2])) {
1501 config_msg = "Unknown color";
1505 if (argc == 4 && !set_attribute(&info->attr, argv[3])) {
1506 config_msg = "Unknown attribute";
1513 static int parse_bool(bool *opt, const char *arg)
1515 *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1521 parse_string(char *opt, const char *arg, size_t optsize)
1523 int arglen = strlen(arg);
1528 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1529 config_msg = "Unmatched quotation";
1532 arg += 1; arglen -= 2;
1534 string_ncopy_do(opt, optsize, arg, arglen);
1539 /* Wants: name = value */
1541 option_set_command(int argc, const char *argv[])
1544 config_msg = "Wrong number of arguments given to set command";
1548 if (strcmp(argv[1], "=")) {
1549 config_msg = "No value assigned";
1553 if (!strcmp(argv[0], "show-author"))
1554 return parse_bool(&opt_author, argv[2]);
1556 if (!strcmp(argv[0], "show-date"))
1557 return parse_bool(&opt_date, argv[2]);
1559 if (!strcmp(argv[0], "show-rev-graph"))
1560 return parse_bool(&opt_rev_graph, argv[2]);
1562 if (!strcmp(argv[0], "show-refs"))
1563 return parse_bool(&opt_show_refs, argv[2]);
1565 if (!strcmp(argv[0], "show-line-numbers"))
1566 return parse_bool(&opt_line_number, argv[2]);
1568 if (!strcmp(argv[0], "line-graphics"))
1569 return parse_bool(&opt_line_graphics, argv[2]);
1571 if (!strcmp(argv[0], "line-number-interval"))
1572 return parse_int(&opt_num_interval, argv[2], 1, 1024);
1574 if (!strcmp(argv[0], "author-width"))
1575 return parse_int(&opt_author_cols, argv[2], 0, 1024);
1577 if (!strcmp(argv[0], "horizontal-scroll"))
1578 return parse_step(&opt_hscroll, argv[2]);
1580 if (!strcmp(argv[0], "tab-size"))
1581 return parse_int(&opt_tab_size, argv[2], 1, 1024);
1583 if (!strcmp(argv[0], "commit-encoding"))
1584 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
1586 config_msg = "Unknown variable name";
1590 /* Wants: mode request key */
1592 option_bind_command(int argc, const char *argv[])
1594 enum request request;
1599 config_msg = "Wrong number of arguments given to bind command";
1603 if (set_keymap(&keymap, argv[0]) == ERR) {
1604 config_msg = "Unknown key map";
1608 key = get_key_value(argv[1]);
1610 config_msg = "Unknown key";
1614 request = get_request(argv[2]);
1615 if (request == REQ_NONE) {
1616 static const struct enum_map obsolete[] = {
1617 ENUM_MAP("cherry-pick", REQ_NONE),
1618 ENUM_MAP("screen-resize", REQ_NONE),
1619 ENUM_MAP("tree-parent", REQ_PARENT),
1623 if (map_enum(&alias, obsolete, argv[2])) {
1624 if (alias != REQ_NONE)
1625 add_keybinding(keymap, alias, key);
1626 config_msg = "Obsolete request name";
1630 if (request == REQ_NONE && *argv[2]++ == '!')
1631 request = add_run_request(keymap, key, argc - 2, argv + 2);
1632 if (request == REQ_NONE) {
1633 config_msg = "Unknown request name";
1637 add_keybinding(keymap, request, key);
1643 set_option(const char *opt, char *value)
1645 const char *argv[SIZEOF_ARG];
1648 if (!argv_from_string(argv, &argc, value)) {
1649 config_msg = "Too many option arguments";
1653 if (!strcmp(opt, "color"))
1654 return option_color_command(argc, argv);
1656 if (!strcmp(opt, "set"))
1657 return option_set_command(argc, argv);
1659 if (!strcmp(opt, "bind"))
1660 return option_bind_command(argc, argv);
1662 config_msg = "Unknown option command";
1667 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1672 config_msg = "Internal error";
1674 /* Check for comment markers, since read_properties() will
1675 * only ensure opt and value are split at first " \t". */
1676 optlen = strcspn(opt, "#");
1680 if (opt[optlen] != 0) {
1681 config_msg = "No option value";
1685 /* Look for comment endings in the value. */
1686 size_t len = strcspn(value, "#");
1688 if (len < valuelen) {
1690 value[valuelen] = 0;
1693 status = set_option(opt, value);
1696 if (status == ERR) {
1697 warn("Error on line %d, near '%.*s': %s",
1698 config_lineno, (int) optlen, opt, config_msg);
1699 config_errors = TRUE;
1702 /* Always keep going if errors are encountered. */
1707 load_option_file(const char *path)
1711 /* It's OK that the file doesn't exist. */
1712 if (!io_open(&io, path))
1716 config_errors = FALSE;
1718 if (io_load(&io, " \t", read_option) == ERR ||
1719 config_errors == TRUE)
1720 warn("Errors while loading %s.", path);
1726 const char *home = getenv("HOME");
1727 const char *tigrc_user = getenv("TIGRC_USER");
1728 const char *tigrc_system = getenv("TIGRC_SYSTEM");
1729 char buf[SIZEOF_STR];
1731 add_builtin_run_requests();
1734 tigrc_system = SYSCONFDIR "/tigrc";
1735 load_option_file(tigrc_system);
1738 if (!home || !string_format(buf, "%s/.tigrc", home))
1742 load_option_file(tigrc_user);
1755 /* The display array of active views and the index of the current view. */
1756 static struct view *display[2];
1757 static unsigned int current_view;
1759 #define foreach_displayed_view(view, i) \
1760 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1762 #define displayed_views() (display[1] != NULL ? 2 : 1)
1764 /* Current head and commit ID */
1765 static char ref_blob[SIZEOF_REF] = "";
1766 static char ref_commit[SIZEOF_REF] = "HEAD";
1767 static char ref_head[SIZEOF_REF] = "HEAD";
1770 const char *name; /* View name */
1771 const char *cmd_env; /* Command line set via environment */
1772 const char *id; /* Points to either of ref_{head,commit,blob} */
1774 struct view_ops *ops; /* View operations */
1776 enum keymap keymap; /* What keymap does this view have */
1777 bool git_dir; /* Whether the view requires a git directory. */
1779 char ref[SIZEOF_REF]; /* Hovered commit reference */
1780 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
1782 int height, width; /* The width and height of the main window */
1783 WINDOW *win; /* The main window */
1784 WINDOW *title; /* The title window living below the main window */
1787 unsigned long offset; /* Offset of the window top */
1788 unsigned long yoffset; /* Offset from the window side. */
1789 unsigned long lineno; /* Current line number */
1790 unsigned long p_offset; /* Previous offset of the window top */
1791 unsigned long p_yoffset;/* Previous offset from the window side */
1792 unsigned long p_lineno; /* Previous current line number */
1793 bool p_restore; /* Should the previous position be restored. */
1796 char grep[SIZEOF_STR]; /* Search string */
1797 regex_t *regex; /* Pre-compiled regexp */
1799 /* If non-NULL, points to the view that opened this view. If this view
1800 * is closed tig will switch back to the parent view. */
1801 struct view *parent;
1804 size_t lines; /* Total number of lines */
1805 struct line *line; /* Line index */
1806 unsigned int digits; /* Number of digits in the lines member. */
1809 struct line *curline; /* Line currently being drawn. */
1810 enum line_type curtype; /* Attribute currently used for drawing. */
1811 unsigned long col; /* Column when drawing. */
1812 bool has_scrolled; /* View was scrolled. */
1822 /* What type of content being displayed. Used in the title bar. */
1824 /* Default command arguments. */
1826 /* Open and reads in all view content. */
1827 bool (*open)(struct view *view);
1828 /* Read one line; updates view->line. */
1829 bool (*read)(struct view *view, char *data);
1830 /* Draw one line; @lineno must be < view->height. */
1831 bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
1832 /* Depending on view handle a special requests. */
1833 enum request (*request)(struct view *view, enum request request, struct line *line);
1834 /* Search for regexp in a line. */
1835 bool (*grep)(struct view *view, struct line *line);
1837 void (*select)(struct view *view, struct line *line);
1840 static struct view_ops blame_ops;
1841 static struct view_ops blob_ops;
1842 static struct view_ops diff_ops;
1843 static struct view_ops help_ops;
1844 static struct view_ops log_ops;
1845 static struct view_ops main_ops;
1846 static struct view_ops pager_ops;
1847 static struct view_ops stage_ops;
1848 static struct view_ops status_ops;
1849 static struct view_ops tree_ops;
1850 static struct view_ops branch_ops;
1852 #define VIEW_STR(name, env, ref, ops, map, git) \
1853 { name, #env, ref, ops, map, git }
1855 #define VIEW_(id, name, ops, git, ref) \
1856 VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
1859 static struct view views[] = {
1860 VIEW_(MAIN, "main", &main_ops, TRUE, ref_head),
1861 VIEW_(DIFF, "diff", &diff_ops, TRUE, ref_commit),
1862 VIEW_(LOG, "log", &log_ops, TRUE, ref_head),
1863 VIEW_(TREE, "tree", &tree_ops, TRUE, ref_commit),
1864 VIEW_(BLOB, "blob", &blob_ops, TRUE, ref_blob),
1865 VIEW_(BLAME, "blame", &blame_ops, TRUE, ref_commit),
1866 VIEW_(BRANCH, "branch", &branch_ops, TRUE, ref_head),
1867 VIEW_(HELP, "help", &help_ops, FALSE, ""),
1868 VIEW_(PAGER, "pager", &pager_ops, FALSE, "stdin"),
1869 VIEW_(STATUS, "status", &status_ops, TRUE, ""),
1870 VIEW_(STAGE, "stage", &stage_ops, TRUE, ""),
1873 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
1874 #define VIEW_REQ(view) ((view) - views + REQ_OFFSET + 1)
1876 #define foreach_view(view, i) \
1877 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1879 #define view_is_displayed(view) \
1880 (view == display[0] || view == display[1])
1887 static chtype line_graphics[] = {
1888 /* LINE_GRAPHIC_VLINE: */ '|'
1892 set_view_attr(struct view *view, enum line_type type)
1894 if (!view->curline->selected && view->curtype != type) {
1895 wattrset(view->win, get_line_attr(type));
1896 wchgat(view->win, -1, 0, type, NULL);
1897 view->curtype = type;
1902 draw_chars(struct view *view, enum line_type type, const char *string,
1903 int max_len, bool use_tilde)
1907 int trimmed = FALSE;
1908 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
1914 len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde);
1916 col = len = strlen(string);
1917 if (len > max_len) {
1921 col = len = max_len;
1926 set_view_attr(view, type);
1928 waddnstr(view->win, string, len);
1929 if (trimmed && use_tilde) {
1930 set_view_attr(view, LINE_DELIMITER);
1931 waddch(view->win, '~');
1939 draw_space(struct view *view, enum line_type type, int max, int spaces)
1941 static char space[] = " ";
1944 spaces = MIN(max, spaces);
1946 while (spaces > 0) {
1947 int len = MIN(spaces, sizeof(space) - 1);
1949 col += draw_chars(view, type, space, len, FALSE);
1957 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
1959 view->col += draw_chars(view, type, string, view->width + view->yoffset - view->col, trim);
1960 return view->width + view->yoffset <= view->col;
1964 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
1966 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
1967 int max = view->width + view->yoffset - view->col;
1973 set_view_attr(view, type);
1974 /* Using waddch() instead of waddnstr() ensures that
1975 * they'll be rendered correctly for the cursor line. */
1976 for (i = skip; i < size; i++)
1977 waddch(view->win, graphic[i]);
1980 if (size < max && skip <= size)
1981 waddch(view->win, ' ');
1984 return view->width + view->yoffset <= view->col;
1988 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
1990 int max = MIN(view->width + view->yoffset - view->col, len);
1994 col = draw_chars(view, type, text, max - 1, trim);
1996 col = draw_space(view, type, max - 1, max - 1);
1999 view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
2000 return view->width + view->yoffset <= view->col;
2004 draw_date(struct view *view, time_t *time)
2006 const char *date = mkdate(time);
2008 return draw_field(view, LINE_DATE, date, DATE_COLS, FALSE);
2012 draw_author(struct view *view, const char *author)
2014 bool trim = opt_author_cols == 0 || opt_author_cols > 5 || !author;
2017 static char initials[10];
2020 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@')
2022 memset(initials, 0, sizeof(initials));
2023 for (pos = 0; *author && pos < opt_author_cols - 1; author++, pos++) {
2024 while (is_initial_sep(*author))
2026 strncpy(&initials[pos], author, sizeof(initials) - 1 - pos);
2027 while (*author && !is_initial_sep(author[1]))
2034 return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2038 draw_mode(struct view *view, mode_t mode)
2044 else if (S_ISLNK(mode))
2046 else if (S_ISGITLINK(mode))
2048 else if (S_ISREG(mode) && mode & S_IXUSR)
2050 else if (S_ISREG(mode))
2055 return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2059 draw_lineno(struct view *view, unsigned int lineno)
2062 int digits3 = view->digits < 3 ? 3 : view->digits;
2063 int max = MIN(view->width + view->yoffset - view->col, digits3);
2066 lineno += view->offset + 1;
2067 if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2068 static char fmt[] = "%1ld";
2070 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2071 if (string_format(number, fmt, lineno))
2075 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2077 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2078 return draw_graphic(view, LINE_DEFAULT, &line_graphics[LINE_GRAPHIC_VLINE], 1);
2082 draw_view_line(struct view *view, unsigned int lineno)
2085 bool selected = (view->offset + lineno == view->lineno);
2087 assert(view_is_displayed(view));
2089 if (view->offset + lineno >= view->lines)
2092 line = &view->line[view->offset + lineno];
2094 wmove(view->win, lineno, 0);
2096 wclrtoeol(view->win);
2098 view->curline = line;
2099 view->curtype = LINE_NONE;
2100 line->selected = FALSE;
2101 line->dirty = line->cleareol = 0;
2104 set_view_attr(view, LINE_CURSOR);
2105 line->selected = TRUE;
2106 view->ops->select(view, line);
2109 return view->ops->draw(view, line, lineno);
2113 redraw_view_dirty(struct view *view)
2118 for (lineno = 0; lineno < view->height; lineno++) {
2119 if (view->offset + lineno >= view->lines)
2121 if (!view->line[view->offset + lineno].dirty)
2124 if (!draw_view_line(view, lineno))
2130 wnoutrefresh(view->win);
2134 redraw_view_from(struct view *view, int lineno)
2136 assert(0 <= lineno && lineno < view->height);
2138 for (; lineno < view->height; lineno++) {
2139 if (!draw_view_line(view, lineno))
2143 wnoutrefresh(view->win);
2147 redraw_view(struct view *view)
2150 redraw_view_from(view, 0);
2155 update_view_title(struct view *view)
2157 char buf[SIZEOF_STR];
2158 char state[SIZEOF_STR];
2159 size_t bufpos = 0, statelen = 0;
2161 assert(view_is_displayed(view));
2163 if (view != VIEW(REQ_VIEW_STATUS) && view->lines) {
2164 unsigned int view_lines = view->offset + view->height;
2165 unsigned int lines = view->lines
2166 ? MIN(view_lines, view->lines) * 100 / view->lines
2169 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2178 time_t secs = time(NULL) - view->start_time;
2180 /* Three git seconds are a long time ... */
2182 string_format_from(state, &statelen, " loading %lds", secs);
2185 string_format_from(buf, &bufpos, "[%s]", view->name);
2186 if (*view->ref && bufpos < view->width) {
2187 size_t refsize = strlen(view->ref);
2188 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2190 if (minsize < view->width)
2191 refsize = view->width - minsize + 7;
2192 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2195 if (statelen && bufpos < view->width) {
2196 string_format_from(buf, &bufpos, "%s", state);
2199 if (view == display[current_view])
2200 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2202 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2204 mvwaddnstr(view->title, 0, 0, buf, bufpos);
2205 wclrtoeol(view->title);
2206 wnoutrefresh(view->title);
2210 resize_display(void)
2213 struct view *base = display[0];
2214 struct view *view = display[1] ? display[1] : display[0];
2216 /* Setup window dimensions */
2218 getmaxyx(stdscr, base->height, base->width);
2220 /* Make room for the status window. */
2224 /* Horizontal split. */
2225 view->width = base->width;
2226 view->height = SCALE_SPLIT_VIEW(base->height);
2227 base->height -= view->height;
2229 /* Make room for the title bar. */
2233 /* Make room for the title bar. */
2238 foreach_displayed_view (view, i) {
2240 view->win = newwin(view->height, 0, offset, 0);
2242 die("Failed to create %s view", view->name);
2244 scrollok(view->win, FALSE);
2246 view->title = newwin(1, 0, offset + view->height, 0);
2248 die("Failed to create title window");
2251 wresize(view->win, view->height, view->width);
2252 mvwin(view->win, offset, 0);
2253 mvwin(view->title, offset + view->height, 0);
2256 offset += view->height + 1;
2261 redraw_display(bool clear)
2266 foreach_displayed_view (view, i) {
2270 update_view_title(view);
2275 toggle_view_option(bool *option, const char *help)
2278 redraw_display(FALSE);
2279 report("%sabling %s", *option ? "En" : "Dis", help);
2283 maximize_view(struct view *view)
2285 memset(display, 0, sizeof(display));
2287 display[current_view] = view;
2289 redraw_display(FALSE);
2299 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2301 if (lineno >= view->lines)
2302 lineno = view->lines > 0 ? view->lines - 1 : 0;
2304 if (offset > lineno || offset + view->height <= lineno) {
2305 unsigned long half = view->height / 2;
2308 offset = lineno - half;
2313 if (offset != view->offset || lineno != view->lineno) {
2314 view->offset = offset;
2315 view->lineno = lineno;
2323 apply_step(double step, int value)
2327 value *= step + 0.01;
2328 return value ? value : 1;
2331 /* Scrolling backend */
2333 do_scroll_view(struct view *view, int lines)
2335 bool redraw_current_line = FALSE;
2337 /* The rendering expects the new offset. */
2338 view->offset += lines;
2340 assert(0 <= view->offset && view->offset < view->lines);
2343 /* Move current line into the view. */
2344 if (view->lineno < view->offset) {
2345 view->lineno = view->offset;
2346 redraw_current_line = TRUE;
2347 } else if (view->lineno >= view->offset + view->height) {
2348 view->lineno = view->offset + view->height - 1;
2349 redraw_current_line = TRUE;
2352 assert(view->offset <= view->lineno && view->lineno < view->lines);
2354 /* Redraw the whole screen if scrolling is pointless. */
2355 if (view->height < ABS(lines)) {
2359 int line = lines > 0 ? view->height - lines : 0;
2360 int end = line + ABS(lines);
2362 scrollok(view->win, TRUE);
2363 wscrl(view->win, lines);
2364 scrollok(view->win, FALSE);
2366 while (line < end && draw_view_line(view, line))
2369 if (redraw_current_line)
2370 draw_view_line(view, view->lineno - view->offset);
2371 wnoutrefresh(view->win);
2374 view->has_scrolled = TRUE;
2378 /* Scroll frontend */
2380 scroll_view(struct view *view, enum request request)
2384 assert(view_is_displayed(view));
2387 case REQ_SCROLL_LEFT:
2388 if (view->yoffset == 0) {
2389 report("Cannot scroll beyond the first column");
2392 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2395 view->yoffset -= apply_step(opt_hscroll, view->width);
2396 redraw_view_from(view, 0);
2399 case REQ_SCROLL_RIGHT:
2400 view->yoffset += apply_step(opt_hscroll, view->width);
2404 case REQ_SCROLL_PAGE_DOWN:
2405 lines = view->height;
2406 case REQ_SCROLL_LINE_DOWN:
2407 if (view->offset + lines > view->lines)
2408 lines = view->lines - view->offset;
2410 if (lines == 0 || view->offset + view->height >= view->lines) {
2411 report("Cannot scroll beyond the last line");
2416 case REQ_SCROLL_PAGE_UP:
2417 lines = view->height;
2418 case REQ_SCROLL_LINE_UP:
2419 if (lines > view->offset)
2420 lines = view->offset;
2423 report("Cannot scroll beyond the first line");
2431 die("request %d not handled in switch", request);
2434 do_scroll_view(view, lines);
2439 move_view(struct view *view, enum request request)
2441 int scroll_steps = 0;
2445 case REQ_MOVE_FIRST_LINE:
2446 steps = -view->lineno;
2449 case REQ_MOVE_LAST_LINE:
2450 steps = view->lines - view->lineno - 1;
2453 case REQ_MOVE_PAGE_UP:
2454 steps = view->height > view->lineno
2455 ? -view->lineno : -view->height;
2458 case REQ_MOVE_PAGE_DOWN:
2459 steps = view->lineno + view->height >= view->lines
2460 ? view->lines - view->lineno - 1 : view->height;
2472 die("request %d not handled in switch", request);
2475 if (steps <= 0 && view->lineno == 0) {
2476 report("Cannot move beyond the first line");
2479 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2480 report("Cannot move beyond the last line");
2484 /* Move the current line */
2485 view->lineno += steps;
2486 assert(0 <= view->lineno && view->lineno < view->lines);
2488 /* Check whether the view needs to be scrolled */
2489 if (view->lineno < view->offset ||
2490 view->lineno >= view->offset + view->height) {
2491 scroll_steps = steps;
2492 if (steps < 0 && -steps > view->offset) {
2493 scroll_steps = -view->offset;
2495 } else if (steps > 0) {
2496 if (view->lineno == view->lines - 1 &&
2497 view->lines > view->height) {
2498 scroll_steps = view->lines - view->offset - 1;
2499 if (scroll_steps >= view->height)
2500 scroll_steps -= view->height - 1;
2505 if (!view_is_displayed(view)) {
2506 view->offset += scroll_steps;
2507 assert(0 <= view->offset && view->offset < view->lines);
2508 view->ops->select(view, &view->line[view->lineno]);
2512 /* Repaint the old "current" line if we be scrolling */
2513 if (ABS(steps) < view->height)
2514 draw_view_line(view, view->lineno - steps - view->offset);
2517 do_scroll_view(view, scroll_steps);
2521 /* Draw the current line */
2522 draw_view_line(view, view->lineno - view->offset);
2524 wnoutrefresh(view->win);
2533 static void search_view(struct view *view, enum request request);
2536 grep_text(struct view *view, const char *text[])
2541 for (i = 0; text[i]; i++)
2543 regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
2549 select_view_line(struct view *view, unsigned long lineno)
2551 unsigned long old_lineno = view->lineno;
2552 unsigned long old_offset = view->offset;
2554 if (goto_view_line(view, view->offset, lineno)) {
2555 if (view_is_displayed(view)) {
2556 if (old_offset != view->offset) {
2559 draw_view_line(view, old_lineno - view->offset);
2560 draw_view_line(view, view->lineno - view->offset);
2561 wnoutrefresh(view->win);
2564 view->ops->select(view, &view->line[view->lineno]);
2570 find_next(struct view *view, enum request request)
2572 unsigned long lineno = view->lineno;
2577 report("No previous search");
2579 search_view(view, request);
2589 case REQ_SEARCH_BACK:
2598 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2599 lineno += direction;
2601 /* Note, lineno is unsigned long so will wrap around in which case it
2602 * will become bigger than view->lines. */
2603 for (; lineno < view->lines; lineno += direction) {
2604 if (view->ops->grep(view, &view->line[lineno])) {
2605 select_view_line(view, lineno);
2606 report("Line %ld matches '%s'", lineno + 1, view->grep);
2611 report("No match found for '%s'", view->grep);
2615 search_view(struct view *view, enum request request)
2620 regfree(view->regex);
2623 view->regex = calloc(1, sizeof(*view->regex));
2628 regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2629 if (regex_err != 0) {
2630 char buf[SIZEOF_STR] = "unknown error";
2632 regerror(regex_err, view->regex, buf, sizeof(buf));
2633 report("Search failed: %s", buf);
2637 string_copy(view->grep, opt_search);
2639 find_next(view, request);
2643 * Incremental updating
2647 reset_view(struct view *view)
2651 for (i = 0; i < view->lines; i++)
2652 free(view->line[i].data);
2655 view->p_offset = view->offset;
2656 view->p_yoffset = view->yoffset;
2657 view->p_lineno = view->lineno;
2665 view->update_secs = 0;
2669 free_argv(const char *argv[])
2673 for (argc = 0; argv[argc]; argc++)
2674 free((void *) argv[argc]);
2678 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
2680 char buf[SIZEOF_STR];
2682 bool noreplace = flags == FORMAT_NONE;
2684 free_argv(dst_argv);
2686 for (argc = 0; src_argv[argc]; argc++) {
2687 const char *arg = src_argv[argc];
2691 char *next = strstr(arg, "%(");
2692 int len = next - arg;
2695 if (!next || noreplace) {
2696 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
2701 } else if (!prefixcmp(next, "%(directory)")) {
2704 } else if (!prefixcmp(next, "%(file)")) {
2707 } else if (!prefixcmp(next, "%(ref)")) {
2708 value = *opt_ref ? opt_ref : "HEAD";
2710 } else if (!prefixcmp(next, "%(head)")) {
2713 } else if (!prefixcmp(next, "%(commit)")) {
2716 } else if (!prefixcmp(next, "%(blob)")) {
2720 report("Unknown replacement: `%s`", next);
2724 if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
2727 arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
2730 dst_argv[argc] = strdup(buf);
2731 if (!dst_argv[argc])
2735 dst_argv[argc] = NULL;
2737 return src_argv[argc] == NULL;
2741 restore_view_position(struct view *view)
2743 if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
2746 /* Changing the view position cancels the restoring. */
2747 /* FIXME: Changing back to the first line is not detected. */
2748 if (view->offset != 0 || view->lineno != 0) {
2749 view->p_restore = FALSE;
2753 if (goto_view_line(view, view->p_offset, view->p_lineno) &&
2754 view_is_displayed(view))
2757 view->yoffset = view->p_yoffset;
2758 view->p_restore = FALSE;
2764 end_update(struct view *view, bool force)
2768 while (!view->ops->read(view, NULL))
2771 set_nonblocking_input(FALSE);
2773 kill_io(view->pipe);
2774 done_io(view->pipe);
2779 setup_update(struct view *view, const char *vid)
2781 set_nonblocking_input(TRUE);
2783 string_copy_rev(view->vid, vid);
2784 view->pipe = &view->io;
2785 view->start_time = time(NULL);
2789 prepare_update(struct view *view, const char *argv[], const char *dir,
2790 enum format_flags flags)
2793 end_update(view, TRUE);
2794 return init_io_rd(&view->io, argv, dir, flags);
2798 prepare_update_file(struct view *view, const char *name)
2801 end_update(view, TRUE);
2802 return io_open(&view->io, name);
2806 begin_update(struct view *view, bool refresh)
2809 end_update(view, TRUE);
2812 if (!start_io(&view->io))
2816 if (view == VIEW(REQ_VIEW_TREE) && strcmp(view->vid, view->id))
2819 if (!run_io_rd(&view->io, view->ops->argv, FORMAT_ALL))
2822 /* Put the current ref_* value to the view title ref
2823 * member. This is needed by the blob view. Most other
2824 * views sets it automatically after loading because the
2825 * first line is a commit line. */
2826 string_copy_rev(view->ref, view->id);
2829 setup_update(view, view->id);
2835 update_view(struct view *view)
2837 char out_buffer[BUFSIZ * 2];
2839 /* Clear the view and redraw everything since the tree sorting
2840 * might have rearranged things. */
2841 bool redraw = view->lines == 0;
2842 bool can_read = TRUE;
2847 if (!io_can_read(view->pipe)) {
2848 if (view->lines == 0 && view_is_displayed(view)) {
2849 time_t secs = time(NULL) - view->start_time;
2851 if (secs > 1 && secs > view->update_secs) {
2852 if (view->update_secs == 0)
2854 update_view_title(view);
2855 view->update_secs = secs;
2861 for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
2862 if (opt_iconv != ICONV_NONE) {
2863 ICONV_CONST char *inbuf = line;
2864 size_t inlen = strlen(line) + 1;
2866 char *outbuf = out_buffer;
2867 size_t outlen = sizeof(out_buffer);
2871 ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2872 if (ret != (size_t) -1)
2876 if (!view->ops->read(view, line)) {
2877 report("Allocation failure");
2878 end_update(view, TRUE);
2884 unsigned long lines = view->lines;
2887 for (digits = 0; lines; digits++)
2890 /* Keep the displayed view in sync with line number scaling. */
2891 if (digits != view->digits) {
2892 view->digits = digits;
2893 if (opt_line_number || view == VIEW(REQ_VIEW_BLAME))
2898 if (io_error(view->pipe)) {
2899 report("Failed to read: %s", io_strerror(view->pipe));
2900 end_update(view, TRUE);
2902 } else if (io_eof(view->pipe)) {
2904 end_update(view, FALSE);
2907 if (restore_view_position(view))
2910 if (!view_is_displayed(view))
2914 redraw_view_from(view, 0);
2916 redraw_view_dirty(view);
2918 /* Update the title _after_ the redraw so that if the redraw picks up a
2919 * commit reference in view->ref it'll be available here. */
2920 update_view_title(view);
2924 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
2926 static struct line *
2927 add_line_data(struct view *view, void *data, enum line_type type)
2931 if (!realloc_lines(&view->line, view->lines, 1))
2934 line = &view->line[view->lines++];
2935 memset(line, 0, sizeof(*line));
2943 static struct line *
2944 add_line_text(struct view *view, const char *text, enum line_type type)
2946 char *data = text ? strdup(text) : NULL;
2948 return data ? add_line_data(view, data, type) : NULL;
2951 static struct line *
2952 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
2954 char buf[SIZEOF_STR];
2957 va_start(args, fmt);
2958 if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
2962 return buf[0] ? add_line_text(view, buf, type) : NULL;
2970 OPEN_DEFAULT = 0, /* Use default view switching. */
2971 OPEN_SPLIT = 1, /* Split current view. */
2972 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
2973 OPEN_REFRESH = 16, /* Refresh view using previous command. */
2974 OPEN_PREPARED = 32, /* Open already prepared command. */
2978 open_view(struct view *prev, enum request request, enum open_flags flags)
2980 bool split = !!(flags & OPEN_SPLIT);
2981 bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
2982 bool nomaximize = !!(flags & OPEN_REFRESH);
2983 struct view *view = VIEW(request);
2984 int nviews = displayed_views();
2985 struct view *base_view = display[0];
2987 if (view == prev && nviews == 1 && !reload) {
2988 report("Already in %s view", view->name);
2992 if (view->git_dir && !opt_git_dir[0]) {
2993 report("The %s view is disabled in pager view", view->name);
3000 } else if (!nomaximize) {
3001 /* Maximize the current view. */
3002 memset(display, 0, sizeof(display));
3004 display[current_view] = view;
3007 /* Resize the view when switching between split- and full-screen,
3008 * or when switching between two different full-screen views. */
3009 if (nviews != displayed_views() ||
3010 (nviews == 1 && base_view != display[0]))
3013 if (view->ops->open) {
3015 end_update(view, TRUE);
3016 if (!view->ops->open(view)) {
3017 report("Failed to load %s view", view->name);
3020 restore_view_position(view);
3022 } else if ((reload || strcmp(view->vid, view->id)) &&
3023 !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3024 report("Failed to load %s view", view->name);
3028 if (split && prev->lineno - prev->offset >= prev->height) {
3029 /* Take the title line into account. */
3030 int lines = prev->lineno - prev->offset - prev->height + 1;
3032 /* Scroll the view that was split if the current line is
3033 * outside the new limited view. */
3034 do_scroll_view(prev, lines);
3037 if (prev && view != prev) {
3039 /* "Blur" the previous view. */
3040 update_view_title(prev);
3043 view->parent = prev;
3046 if (view->pipe && view->lines == 0) {
3047 /* Clear the old view and let the incremental updating refill
3050 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3052 } else if (view_is_displayed(view)) {
3059 open_external_viewer(const char *argv[], const char *dir)
3061 def_prog_mode(); /* save current tty modes */
3062 endwin(); /* restore original tty modes */
3063 run_io_fg(argv, dir);
3064 fprintf(stderr, "Press Enter to continue");
3067 redraw_display(TRUE);
3071 open_mergetool(const char *file)
3073 const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3075 open_external_viewer(mergetool_argv, opt_cdup);
3079 open_editor(bool from_root, const char *file)
3081 const char *editor_argv[] = { "vi", file, NULL };
3084 editor = getenv("GIT_EDITOR");
3085 if (!editor && *opt_editor)
3086 editor = opt_editor;
3088 editor = getenv("VISUAL");
3090 editor = getenv("EDITOR");
3094 editor_argv[0] = editor;
3095 open_external_viewer(editor_argv, from_root ? opt_cdup : NULL);
3099 open_run_request(enum request request)
3101 struct run_request *req = get_run_request(request);
3102 const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3105 report("Unknown run request");
3109 if (format_argv(argv, req->argv, FORMAT_ALL))
3110 open_external_viewer(argv, NULL);
3115 * User request switch noodle
3119 view_driver(struct view *view, enum request request)
3123 if (request == REQ_NONE)
3126 if (request > REQ_NONE) {
3127 open_run_request(request);
3128 /* FIXME: When all views can refresh always do this. */
3129 if (view == VIEW(REQ_VIEW_STATUS) ||
3130 view == VIEW(REQ_VIEW_MAIN) ||
3131 view == VIEW(REQ_VIEW_LOG) ||
3132 view == VIEW(REQ_VIEW_BRANCH) ||
3133 view == VIEW(REQ_VIEW_STAGE))
3134 request = REQ_REFRESH;
3139 if (view && view->lines) {
3140 request = view->ops->request(view, request, &view->line[view->lineno]);
3141 if (request == REQ_NONE)
3148 case REQ_MOVE_PAGE_UP:
3149 case REQ_MOVE_PAGE_DOWN:
3150 case REQ_MOVE_FIRST_LINE:
3151 case REQ_MOVE_LAST_LINE:
3152 move_view(view, request);
3155 case REQ_SCROLL_LEFT:
3156 case REQ_SCROLL_RIGHT:
3157 case REQ_SCROLL_LINE_DOWN:
3158 case REQ_SCROLL_LINE_UP:
3159 case REQ_SCROLL_PAGE_DOWN:
3160 case REQ_SCROLL_PAGE_UP:
3161 scroll_view(view, request);
3164 case REQ_VIEW_BLAME:
3166 report("No file chosen, press %s to open tree view",
3167 get_key(REQ_VIEW_TREE));
3170 open_view(view, request, OPEN_DEFAULT);
3175 report("No file chosen, press %s to open tree view",
3176 get_key(REQ_VIEW_TREE));
3179 open_view(view, request, OPEN_DEFAULT);
3182 case REQ_VIEW_PAGER:
3183 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3184 report("No pager content, press %s to run command from prompt",
3185 get_key(REQ_PROMPT));
3188 open_view(view, request, OPEN_DEFAULT);
3191 case REQ_VIEW_STAGE:
3192 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3193 report("No stage content, press %s to open the status view and choose file",
3194 get_key(REQ_VIEW_STATUS));
3197 open_view(view, request, OPEN_DEFAULT);
3200 case REQ_VIEW_STATUS:
3201 if (opt_is_inside_work_tree == FALSE) {
3202 report("The status view requires a working tree");
3205 open_view(view, request, OPEN_DEFAULT);
3213 case REQ_VIEW_BRANCH:
3214 open_view(view, request, OPEN_DEFAULT);
3219 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3221 if ((view == VIEW(REQ_VIEW_DIFF) &&
3222 view->parent == VIEW(REQ_VIEW_MAIN)) ||
3223 (view == VIEW(REQ_VIEW_DIFF) &&
3224 view->parent == VIEW(REQ_VIEW_BLAME)) ||
3225 (view == VIEW(REQ_VIEW_STAGE) &&
3226 view->parent == VIEW(REQ_VIEW_STATUS)) ||
3227 (view == VIEW(REQ_VIEW_BLOB) &&
3228 view->parent == VIEW(REQ_VIEW_TREE))) {
3231 view = view->parent;
3232 line = view->lineno;
3233 move_view(view, request);
3234 if (view_is_displayed(view))
3235 update_view_title(view);
3236 if (line != view->lineno)
3237 view->ops->request(view, REQ_ENTER,
3238 &view->line[view->lineno]);
3241 move_view(view, request);
3247 int nviews = displayed_views();
3248 int next_view = (current_view + 1) % nviews;
3250 if (next_view == current_view) {
3251 report("Only one view is displayed");
3255 current_view = next_view;
3256 /* Blur out the title of the previous view. */
3257 update_view_title(view);
3262 report("Refreshing is not yet supported for the %s view", view->name);
3266 if (displayed_views() == 2)
3267 maximize_view(view);
3270 case REQ_TOGGLE_LINENO:
3271 toggle_view_option(&opt_line_number, "line numbers");
3274 case REQ_TOGGLE_DATE:
3275 toggle_view_option(&opt_date, "date display");
3278 case REQ_TOGGLE_AUTHOR:
3279 toggle_view_option(&opt_author, "author display");
3282 case REQ_TOGGLE_REV_GRAPH:
3283 toggle_view_option(&opt_rev_graph, "revision graph display");
3286 case REQ_TOGGLE_REFS:
3287 toggle_view_option(&opt_show_refs, "reference display");
3290 case REQ_TOGGLE_SORT_FIELD:
3291 case REQ_TOGGLE_SORT_ORDER:
3292 report("Sorting is not yet supported for the %s view", view->name);
3296 case REQ_SEARCH_BACK:
3297 search_view(view, request);
3302 find_next(view, request);
3305 case REQ_STOP_LOADING:
3306 for (i = 0; i < ARRAY_SIZE(views); i++) {
3309 report("Stopped loading the %s view", view->name),
3310 end_update(view, TRUE);
3314 case REQ_SHOW_VERSION:
3315 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3318 case REQ_SCREEN_REDRAW:
3319 redraw_display(TRUE);
3323 report("Nothing to edit");
3327 report("Nothing to enter");
3330 case REQ_VIEW_CLOSE:
3331 /* XXX: Mark closed views by letting view->parent point to the
3332 * view itself. Parents to closed view should never be
3335 view->parent->parent != view->parent) {
3336 maximize_view(view->parent);
3337 view->parent = view;
3345 report("Unknown key, press 'h' for help");
3354 * View backend utilities
3364 const enum sort_field *fields;
3365 size_t size, current;
3369 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3370 #define get_sort_field(state) ((state).fields[(state).current])
3371 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3374 sort_view(struct view *view, enum request request, struct sort_state *state,
3375 int (*compare)(const void *, const void *))
3378 case REQ_TOGGLE_SORT_FIELD:
3379 state->current = (state->current + 1) % state->size;
3382 case REQ_TOGGLE_SORT_ORDER:
3383 state->reverse = !state->reverse;
3386 die("Not a sort request");
3389 qsort(view->line, view->lines, sizeof(*view->line), compare);
3393 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3395 /* Small author cache to reduce memory consumption. It uses binary
3396 * search to lookup or find place to position new entries. No entries
3397 * are ever freed. */
3399 get_author(const char *name)
3401 static const char **authors;
3402 static size_t authors_size;
3403 int from = 0, to = authors_size - 1;
3405 while (from <= to) {
3406 size_t pos = (to + from) / 2;
3407 int cmp = strcmp(name, authors[pos]);
3410 return authors[pos];
3418 if (!realloc_authors(&authors, authors_size, 1))
3420 name = strdup(name);
3424 memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3425 authors[from] = name;
3432 parse_timezone(time_t *time, const char *zone)
3436 tz = ('0' - zone[1]) * 60 * 60 * 10;
3437 tz += ('0' - zone[2]) * 60 * 60;
3438 tz += ('0' - zone[3]) * 60;
3439 tz += ('0' - zone[4]);
3447 /* Parse author lines where the name may be empty:
3448 * author <email@address.tld> 1138474660 +0100
3451 parse_author_line(char *ident, const char **author, time_t *time)
3453 char *nameend = strchr(ident, '<');
3454 char *emailend = strchr(ident, '>');
3456 if (nameend && emailend)
3457 *nameend = *emailend = 0;
3458 ident = chomp_string(ident);
3461 ident = chomp_string(nameend + 1);
3466 *author = get_author(ident);
3468 /* Parse epoch and timezone */
3469 if (emailend && emailend[1] == ' ') {
3470 char *secs = emailend + 2;
3471 char *zone = strchr(secs, ' ');
3473 *time = (time_t) atol(secs);
3475 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
3476 parse_timezone(time, zone + 1);
3480 static enum input_status
3481 select_commit_parent_handler(void *data, char *buf, int c)
3483 size_t parents = *(size_t *) data;
3490 parent = atoi(buf) * 10;
3493 if (parent > parents)
3499 select_commit_parent(const char *id, char rev[SIZEOF_REV], const char *path)
3501 char buf[SIZEOF_STR * 4];
3502 const char *revlist_argv[] = {
3503 "git", "rev-list", "-1", "--parents", id, "--", path, NULL
3507 if (!run_io_buf(revlist_argv, buf, sizeof(buf)) ||
3508 (parents = (strlen(buf) / 40) - 1) < 0) {
3509 report("Failed to get parent information");
3512 } else if (parents == 0) {
3514 report("Path '%s' does not exist in the parent", path);
3516 report("The selected commit has no parents");
3521 char prompt[SIZEOF_STR];
3524 if (!string_format(prompt, "Which parent? [1..%d] ", parents))
3526 result = prompt_input(prompt, select_commit_parent_handler, &parents);
3529 parents = atoi(result);
3532 string_copy_rev(rev, &buf[41 * parents]);
3541 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3543 char text[SIZEOF_STR];
3545 if (opt_line_number && draw_lineno(view, lineno))
3548 string_expand(text, sizeof(text), line->data, opt_tab_size);
3549 draw_text(view, line->type, text, TRUE);
3554 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3556 const char *describe_argv[] = { "git", "describe", commit_id, NULL };
3557 char ref[SIZEOF_STR];
3559 if (!run_io_buf(describe_argv, ref, sizeof(ref)) || !*ref)
3562 /* This is the only fatal call, since it can "corrupt" the buffer. */
3563 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
3570 add_pager_refs(struct view *view, struct line *line)
3572 char buf[SIZEOF_STR];
3573 char *commit_id = (char *)line->data + STRING_SIZE("commit ");
3575 size_t bufpos = 0, refpos = 0;
3576 const char *sep = "Refs: ";
3577 bool is_tag = FALSE;
3579 assert(line->type == LINE_COMMIT);
3581 refs = get_refs(commit_id);
3583 if (view == VIEW(REQ_VIEW_DIFF))
3584 goto try_add_describe_ref;
3589 struct ref *ref = refs[refpos];
3590 const char *fmt = ref->tag ? "%s[%s]" :
3591 ref->remote ? "%s<%s>" : "%s%s";
3593 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
3598 } while (refs[refpos++]->next);
3600 if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
3601 try_add_describe_ref:
3602 /* Add <tag>-g<commit_id> "fake" reference. */
3603 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
3610 add_line_text(view, buf, LINE_PP_REFS);
3614 pager_read(struct view *view, char *data)
3621 line = add_line_text(view, data, get_line_type(data));
3625 if (line->type == LINE_COMMIT &&
3626 (view == VIEW(REQ_VIEW_DIFF) ||
3627 view == VIEW(REQ_VIEW_LOG)))
3628 add_pager_refs(view, line);
3634 pager_request(struct view *view, enum request request, struct line *line)
3638 if (request != REQ_ENTER)
3641 if (line->type == LINE_COMMIT &&
3642 (view == VIEW(REQ_VIEW_LOG) ||
3643 view == VIEW(REQ_VIEW_PAGER))) {
3644 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3648 /* Always scroll the view even if it was split. That way
3649 * you can use Enter to scroll through the log view and
3650 * split open each commit diff. */
3651 scroll_view(view, REQ_SCROLL_LINE_DOWN);
3653 /* FIXME: A minor workaround. Scrolling the view will call report("")
3654 * but if we are scrolling a non-current view this won't properly
3655 * update the view title. */
3657 update_view_title(view);
3663 pager_grep(struct view *view, struct line *line)
3665 const char *text[] = { line->data, NULL };
3667 return grep_text(view, text);
3671 pager_select(struct view *view, struct line *line)
3673 if (line->type == LINE_COMMIT) {
3674 char *text = (char *)line->data + STRING_SIZE("commit ");
3676 if (view != VIEW(REQ_VIEW_PAGER))
3677 string_copy_rev(view->ref, text);
3678 string_copy_rev(ref_commit, text);
3682 static struct view_ops pager_ops = {
3693 static const char *log_argv[SIZEOF_ARG] = {
3694 "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3698 log_request(struct view *view, enum request request, struct line *line)
3703 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
3706 return pager_request(view, request, line);
3710 static struct view_ops log_ops = {
3721 static const char *diff_argv[SIZEOF_ARG] = {
3722 "git", "show", "--pretty=fuller", "--no-color", "--root",
3723 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
3726 static struct view_ops diff_ops = {
3742 help_open(struct view *view)
3744 char buf[SIZEOF_STR];
3748 if (view->lines > 0)
3751 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
3753 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
3756 if (req_info[i].request == REQ_NONE)
3759 if (!req_info[i].request) {
3760 add_line_text(view, "", LINE_DEFAULT);
3761 add_line_text(view, req_info[i].help, LINE_DEFAULT);
3765 key = get_key(req_info[i].request);
3767 key = "(no key defined)";
3769 for (bufpos = 0; bufpos <= req_info[i].namelen; bufpos++) {
3770 buf[bufpos] = tolower(req_info[i].name[bufpos]);
3771 if (buf[bufpos] == '_')
3775 add_line_format(view, LINE_DEFAULT, " %-25s %-20s %s",
3776 key, buf, req_info[i].help);
3780 add_line_text(view, "", LINE_DEFAULT);
3781 add_line_text(view, "External commands:", LINE_DEFAULT);
3784 for (i = 0; i < run_requests; i++) {
3785 struct run_request *req = get_run_request(REQ_NONE + i + 1);
3792 key = get_key_name(req->key);
3794 key = "(no key defined)";
3796 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
3797 if (!string_format_from(buf, &bufpos, "%s%s",
3798 argc ? " " : "", req->argv[argc]))
3801 add_line_format(view, LINE_DEFAULT, " %-10s %-14s `%s`",
3802 keymap_table[req->keymap].name, key, buf);
3808 static struct view_ops help_ops = {
3824 struct tree_stack_entry {
3825 struct tree_stack_entry *prev; /* Entry below this in the stack */
3826 unsigned long lineno; /* Line number to restore */
3827 char *name; /* Position of name in opt_path */
3830 /* The top of the path stack. */
3831 static struct tree_stack_entry *tree_stack = NULL;
3832 unsigned long tree_lineno = 0;
3835 pop_tree_stack_entry(void)
3837 struct tree_stack_entry *entry = tree_stack;
3839 tree_lineno = entry->lineno;
3841 tree_stack = entry->prev;
3846 push_tree_stack_entry(const char *name, unsigned long lineno)
3848 struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
3849 size_t pathlen = strlen(opt_path);
3854 entry->prev = tree_stack;
3855 entry->name = opt_path + pathlen;
3858 if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
3859 pop_tree_stack_entry();
3863 /* Move the current line to the first tree entry. */
3865 entry->lineno = lineno;
3868 /* Parse output from git-ls-tree(1):
3870 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
3873 #define SIZEOF_TREE_ATTR \
3874 STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
3876 #define SIZEOF_TREE_MODE \
3877 STRING_SIZE("100644 ")
3879 #define TREE_ID_OFFSET \
3880 STRING_SIZE("100644 blob ")
3883 char id[SIZEOF_REV];
3885 time_t time; /* Date from the author ident. */
3886 const char *author; /* Author of the commit. */
3891 tree_path(const struct line *line)
3893 return ((struct tree_entry *) line->data)->name;
3897 tree_compare_entry(const struct line *line1, const struct line *line2)
3899 if (line1->type != line2->type)
3900 return line1->type == LINE_TREE_DIR ? -1 : 1;
3901 return strcmp(tree_path(line1), tree_path(line2));
3904 static const enum sort_field tree_sort_fields[] = {
3905 ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
3907 static struct sort_state tree_sort_state = SORT_STATE(tree_sort_fields);
3910 tree_compare(const void *l1, const void *l2)
3912 const struct line *line1 = (const struct line *) l1;
3913 const struct line *line2 = (const struct line *) l2;
3914 const struct tree_entry *entry1 = ((const struct line *) l1)->data;
3915 const struct tree_entry *entry2 = ((const struct line *) l2)->data;
3917 if (line1->type == LINE_TREE_HEAD)
3919 if (line2->type == LINE_TREE_HEAD)
3922 switch (get_sort_field(tree_sort_state)) {
3924 return sort_order(tree_sort_state, entry1->time - entry2->time);
3926 case ORDERBY_AUTHOR:
3927 return sort_order(tree_sort_state, strcmp(entry1->author, entry2->author));
3931 return sort_order(tree_sort_state, tree_compare_entry(line1, line2));
3936 static struct line *
3937 tree_entry(struct view *view, enum line_type type, const char *path,
3938 const char *mode, const char *id)
3940 struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
3941 struct line *line = entry ? add_line_data(view, entry, type) : NULL;
3943 if (!entry || !line) {
3948 strncpy(entry->name, path, strlen(path));
3950 entry->mode = strtoul(mode, NULL, 8);
3952 string_copy_rev(entry->id, id);
3958 tree_read_date(struct view *view, char *text, bool *read_date)
3960 static const char *author_name;
3961 static time_t author_time;
3963 if (!text && *read_date) {
3968 char *path = *opt_path ? opt_path : ".";
3969 /* Find next entry to process */
3970 const char *log_file[] = {
3971 "git", "log", "--no-color", "--pretty=raw",
3972 "--cc", "--raw", view->id, "--", path, NULL
3977 tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
3978 report("Tree is empty");
3982 if (!run_io_rd(&io, log_file, FORMAT_NONE)) {
3983 report("Failed to load tree data");
3987 done_io(view->pipe);
3992 } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
3993 parse_author_line(text + STRING_SIZE("author "),
3994 &author_name, &author_time);
3996 } else if (*text == ':') {
3998 size_t annotated = 1;
4001 pos = strchr(text, '\t');
4005 if (*opt_prefix && !strncmp(text, opt_prefix, strlen(opt_prefix)))
4006 text += strlen(opt_prefix);
4007 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
4008 text += strlen(opt_path);
4009 pos = strchr(text, '/');
4013 for (i = 1; i < view->lines; i++) {
4014 struct line *line = &view->line[i];
4015 struct tree_entry *entry = line->data;
4017 annotated += !!entry->author;
4018 if (entry->author || strcmp(entry->name, text))
4021 entry->author = author_name;
4022 entry->time = author_time;
4027 if (annotated == view->lines)
4028 kill_io(view->pipe);
4034 tree_read(struct view *view, char *text)
4036 static bool read_date = FALSE;
4037 struct tree_entry *data;
4038 struct line *entry, *line;
4039 enum line_type type;
4040 size_t textlen = text ? strlen(text) : 0;
4041 char *path = text + SIZEOF_TREE_ATTR;
4043 if (read_date || !text)
4044 return tree_read_date(view, text, &read_date);
4046 if (textlen <= SIZEOF_TREE_ATTR)
4048 if (view->lines == 0 &&
4049 !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
4052 /* Strip the path part ... */
4054 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
4055 size_t striplen = strlen(opt_path);
4057 if (pathlen > striplen)
4058 memmove(path, path + striplen,
4059 pathlen - striplen + 1);
4061 /* Insert "link" to parent directory. */
4062 if (view->lines == 1 &&
4063 !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
4067 type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
4068 entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
4073 /* Skip "Directory ..." and ".." line. */
4074 for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
4075 if (tree_compare_entry(line, entry) <= 0)
4078 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4082 for (; line <= entry; line++)
4083 line->dirty = line->cleareol = 1;
4087 if (tree_lineno > view->lineno) {
4088 view->lineno = tree_lineno;
4096 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4098 struct tree_entry *entry = line->data;
4100 if (line->type == LINE_TREE_HEAD) {
4101 if (draw_text(view, line->type, "Directory path /", TRUE))
4104 if (draw_mode(view, entry->mode))
4107 if (opt_author && draw_author(view, entry->author))
4110 if (opt_date && draw_date(view, entry->author ? &entry->time : NULL))
4113 if (draw_text(view, line->type, entry->name, TRUE))
4121 char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4122 int fd = mkstemp(file);
4125 report("Failed to create temporary file");
4126 else if (!run_io_append(blob_ops.argv, FORMAT_ALL, fd))
4127 report("Failed to save blob data to file");
4129 open_editor(FALSE, file);
4135 tree_request(struct view *view, enum request request, struct line *line)
4137 enum open_flags flags;
4140 case REQ_VIEW_BLAME:
4141 if (line->type != LINE_TREE_FILE) {
4142 report("Blame only supported for files");
4146 string_copy(opt_ref, view->vid);
4150 if (line->type != LINE_TREE_FILE) {
4151 report("Edit only supported for files");
4152 } else if (!is_head_commit(view->vid)) {
4155 open_editor(TRUE, opt_file);
4159 case REQ_TOGGLE_SORT_FIELD:
4160 case REQ_TOGGLE_SORT_ORDER:
4161 sort_view(view, request, &tree_sort_state, tree_compare);
4166 /* quit view if at top of tree */
4167 return REQ_VIEW_CLOSE;
4170 line = &view->line[1];
4180 /* Cleanup the stack if the tree view is at a different tree. */
4181 while (!*opt_path && tree_stack)
4182 pop_tree_stack_entry();
4184 switch (line->type) {
4186 /* Depending on whether it is a subdirectory or parent link
4187 * mangle the path buffer. */
4188 if (line == &view->line[1] && *opt_path) {
4189 pop_tree_stack_entry();
4192 const char *basename = tree_path(line);
4194 push_tree_stack_entry(basename, view->lineno);
4197 /* Trees and subtrees share the same ID, so they are not not
4198 * unique like blobs. */
4199 flags = OPEN_RELOAD;
4200 request = REQ_VIEW_TREE;
4203 case LINE_TREE_FILE:
4204 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4205 request = REQ_VIEW_BLOB;
4212 open_view(view, request, flags);
4213 if (request == REQ_VIEW_TREE)
4214 view->lineno = tree_lineno;
4220 tree_grep(struct view *view, struct line *line)
4222 struct tree_entry *entry = line->data;
4223 const char *text[] = {
4225 opt_author ? entry->author : "",
4226 opt_date ? mkdate(&entry->time) : "",
4230 return grep_text(view, text);
4234 tree_select(struct view *view, struct line *line)
4236 struct tree_entry *entry = line->data;
4238 if (line->type == LINE_TREE_FILE) {
4239 string_copy_rev(ref_blob, entry->id);
4240 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4242 } else if (line->type != LINE_TREE_DIR) {
4246 string_copy_rev(view->ref, entry->id);
4249 static const char *tree_argv[SIZEOF_ARG] = {
4250 "git", "ls-tree", "%(commit)", "%(directory)", NULL
4253 static struct view_ops tree_ops = {
4265 blob_read(struct view *view, char *line)
4269 return add_line_text(view, line, LINE_DEFAULT) != NULL;
4273 blob_request(struct view *view, enum request request, struct line *line)
4280 return pager_request(view, request, line);
4284 static const char *blob_argv[SIZEOF_ARG] = {
4285 "git", "cat-file", "blob", "%(blob)", NULL
4288 static struct view_ops blob_ops = {
4302 * Loading the blame view is a two phase job:
4304 * 1. File content is read either using opt_file from the
4305 * filesystem or using git-cat-file.
4306 * 2. Then blame information is incrementally added by
4307 * reading output from git-blame.
4310 static const char *blame_head_argv[] = {
4311 "git", "blame", "--incremental", "--", "%(file)", NULL
4314 static const char *blame_ref_argv[] = {
4315 "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4318 static const char *blame_cat_file_argv[] = {
4319 "git", "cat-file", "blob", "%(ref):%(file)", NULL
4322 struct blame_commit {
4323 char id[SIZEOF_REV]; /* SHA1 ID. */
4324 char title[128]; /* First line of the commit message. */
4325 const char *author; /* Author of the commit. */
4326 time_t time; /* Date from the author ident. */
4327 char filename[128]; /* Name of file. */
4328 bool has_previous; /* Was a "previous" line detected. */
4332 struct blame_commit *commit;
4333 unsigned long lineno;
4338 blame_open(struct view *view)
4340 if (*opt_ref || !io_open(&view->io, opt_file)) {
4341 if (!run_io_rd(&view->io, blame_cat_file_argv, FORMAT_ALL))
4345 setup_update(view, opt_file);
4346 string_format(view->ref, "%s ...", opt_file);
4351 static struct blame_commit *
4352 get_blame_commit(struct view *view, const char *id)
4356 for (i = 0; i < view->lines; i++) {
4357 struct blame *blame = view->line[i].data;
4362 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4363 return blame->commit;
4367 struct blame_commit *commit = calloc(1, sizeof(*commit));
4370 string_ncopy(commit->id, id, SIZEOF_REV);
4376 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4378 const char *pos = *posref;
4381 pos = strchr(pos + 1, ' ');
4382 if (!pos || !isdigit(pos[1]))
4384 *number = atoi(pos + 1);
4385 if (*number < min || *number > max)
4392 static struct blame_commit *
4393 parse_blame_commit(struct view *view, const char *text, int *blamed)
4395 struct blame_commit *commit;
4396 struct blame *blame;
4397 const char *pos = text + SIZEOF_REV - 2;
4398 size_t orig_lineno = 0;
4402 if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
4405 if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
4406 !parse_number(&pos, &lineno, 1, view->lines) ||
4407 !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4410 commit = get_blame_commit(view, text);
4416 struct line *line = &view->line[lineno + group - 1];
4419 blame->commit = commit;
4420 blame->lineno = orig_lineno + group - 1;
4428 blame_read_file(struct view *view, const char *line, bool *read_file)
4431 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
4434 if (view->lines == 0 && !view->parent)
4435 die("No blame exist for %s", view->vid);
4437 if (view->lines == 0 || !run_io_rd(&io, argv, FORMAT_ALL)) {
4438 report("Failed to load blame data");
4442 done_io(view->pipe);
4448 size_t linelen = strlen(line);
4449 struct blame *blame = malloc(sizeof(*blame) + linelen);
4454 blame->commit = NULL;
4455 strncpy(blame->text, line, linelen);
4456 blame->text[linelen] = 0;
4457 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
4462 match_blame_header(const char *name, char **line)
4464 size_t namelen = strlen(name);
4465 bool matched = !strncmp(name, *line, namelen);
4474 blame_read(struct view *view, char *line)
4476 static struct blame_commit *commit = NULL;
4477 static int blamed = 0;
4478 static bool read_file = TRUE;
4481 return blame_read_file(view, line, &read_file);
4488 string_format(view->ref, "%s", view->vid);
4489 if (view_is_displayed(view)) {
4490 update_view_title(view);
4491 redraw_view_from(view, 0);
4497 commit = parse_blame_commit(view, line, &blamed);
4498 string_format(view->ref, "%s %2d%%", view->vid,
4499 view->lines ? blamed * 100 / view->lines : 0);
4501 } else if (match_blame_header("author ", &line)) {
4502 commit->author = get_author(line);
4504 } else if (match_blame_header("author-time ", &line)) {
4505 commit->time = (time_t) atol(line);
4507 } else if (match_blame_header("author-tz ", &line)) {
4508 parse_timezone(&commit->time, line);
4510 } else if (match_blame_header("summary ", &line)) {
4511 string_ncopy(commit->title, line, strlen(line));
4513 } else if (match_blame_header("previous ", &line)) {
4514 commit->has_previous = TRUE;
4516 } else if (match_blame_header("filename ", &line)) {
4517 string_ncopy(commit->filename, line, strlen(line));
4525 blame_draw(struct view *view, struct line *line, unsigned int lineno)
4527 struct blame *blame = line->data;
4528 time_t *time = NULL;
4529 const char *id = NULL, *author = NULL;
4530 char text[SIZEOF_STR];
4532 if (blame->commit && *blame->commit->filename) {
4533 id = blame->commit->id;
4534 author = blame->commit->author;
4535 time = &blame->commit->time;
4538 if (opt_date && draw_date(view, time))
4541 if (opt_author && draw_author(view, author))
4544 if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
4547 if (draw_lineno(view, lineno))
4550 string_expand(text, sizeof(text), blame->text, opt_tab_size);
4551 draw_text(view, LINE_DEFAULT, text, TRUE);
4556 check_blame_commit(struct blame *blame, bool check_null_id)
4559 report("Commit data not loaded yet");
4560 else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
4561 report("No commit exist for the selected line");
4568 setup_blame_parent_line(struct view *view, struct blame *blame)
4570 const char *diff_tree_argv[] = {
4571 "git", "diff-tree", "-U0", blame->commit->id,
4572 "--", blame->commit->filename, NULL
4575 int parent_lineno = -1;
4576 int blamed_lineno = -1;
4579 if (!run_io(&io, diff_tree_argv, NULL, IO_RD))
4582 while ((line = io_get(&io, '\n', TRUE))) {
4584 char *pos = strchr(line, '+');
4586 parent_lineno = atoi(line + 4);
4588 blamed_lineno = atoi(pos + 1);
4590 } else if (*line == '+' && parent_lineno != -1) {
4591 if (blame->lineno == blamed_lineno - 1 &&
4592 !strcmp(blame->text, line + 1)) {
4593 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
4604 blame_request(struct view *view, enum request request, struct line *line)
4606 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4607 struct blame *blame = line->data;
4610 case REQ_VIEW_BLAME:
4611 if (check_blame_commit(blame, TRUE)) {
4612 string_copy(opt_ref, blame->commit->id);
4613 string_copy(opt_file, blame->commit->filename);
4615 view->lineno = blame->lineno;
4616 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4621 if (check_blame_commit(blame, TRUE) &&
4622 select_commit_parent(blame->commit->id, opt_ref,
4623 blame->commit->filename)) {
4624 string_copy(opt_file, blame->commit->filename);
4625 setup_blame_parent_line(view, blame);
4626 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4631 if (!check_blame_commit(blame, FALSE))
4634 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
4635 !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
4638 if (!strcmp(blame->commit->id, NULL_ID)) {
4639 struct view *diff = VIEW(REQ_VIEW_DIFF);
4640 const char *diff_index_argv[] = {
4641 "git", "diff-index", "--root", "--patch-with-stat",
4642 "-C", "-M", "HEAD", "--", view->vid, NULL
4645 if (!blame->commit->has_previous) {
4646 diff_index_argv[1] = "diff";
4647 diff_index_argv[2] = "--no-color";
4648 diff_index_argv[6] = "--";
4649 diff_index_argv[7] = "/dev/null";
4652 if (!prepare_update(diff, diff_index_argv, NULL, FORMAT_DASH)) {
4653 report("Failed to allocate diff command");
4656 flags |= OPEN_PREPARED;
4659 open_view(view, REQ_VIEW_DIFF, flags);
4660 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
4661 string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
4672 blame_grep(struct view *view, struct line *line)
4674 struct blame *blame = line->data;
4675 struct blame_commit *commit = blame->commit;
4676 const char *text[] = {
4678 commit ? commit->title : "",
4679 commit ? commit->id : "",
4680 commit && opt_author ? commit->author : "",
4681 commit && opt_date ? mkdate(&commit->time) : "",
4685 return grep_text(view, text);
4689 blame_select(struct view *view, struct line *line)
4691 struct blame *blame = line->data;
4692 struct blame_commit *commit = blame->commit;
4697 if (!strcmp(commit->id, NULL_ID))
4698 string_ncopy(ref_commit, "HEAD", 4);
4700 string_copy_rev(ref_commit, commit->id);
4703 static struct view_ops blame_ops = {
4719 const char *author; /* Author of the last commit. */
4720 time_t time; /* Date of the last activity. */
4721 struct ref *ref; /* Name and commit ID information. */
4725 branch_draw(struct view *view, struct line *line, unsigned int lineno)
4727 struct branch *branch = line->data;
4728 enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
4730 if (opt_date && draw_date(view, branch->author ? &branch->time : NULL))
4733 if (opt_author && draw_author(view, branch->author))
4736 draw_text(view, type, branch->ref->name, TRUE);
4741 branch_request(struct view *view, enum request request, struct line *line)
4746 open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
4750 open_view(view, REQ_VIEW_MAIN, OPEN_SPLIT);
4759 branch_read(struct view *view, char *line)
4761 static char id[SIZEOF_REV];
4762 struct branch *reference;
4768 switch (get_line_type(line)) {
4770 string_copy_rev(id, line + STRING_SIZE("commit "));
4774 for (i = 0, reference = NULL; i < view->lines; i++) {
4775 struct branch *branch = view->line[i].data;
4777 if (strcmp(branch->ref->id, id))
4780 view->line[i].dirty = TRUE;
4782 branch->author = reference->author;
4783 branch->time = reference->time;
4787 parse_author_line(line + STRING_SIZE("author "),
4788 &branch->author, &branch->time);
4800 branch_open_visitor(void *data, struct ref *ref)
4802 struct view *view = data;
4803 struct branch *branch;
4805 if (ref->tag || ref->ltag || ref->remote)
4808 branch = calloc(1, sizeof(*branch));
4813 return !!add_line_data(view, branch, LINE_DEFAULT);
4817 branch_open(struct view *view)
4819 const char *branch_log[] = {
4820 "git", "log", "--no-color", "--pretty=raw",
4821 "--simplify-by-decoration", "--all", NULL
4824 if (!run_io_rd(&view->io, branch_log, FORMAT_NONE)) {
4825 report("Failed to load branch data");
4829 setup_update(view, view->id);
4830 foreach_ref(branch_open_visitor, view);
4836 branch_grep(struct view *view, struct line *line)
4838 struct branch *branch = line->data;
4839 const char *text[] = {
4845 return grep_text(view, text);
4849 branch_select(struct view *view, struct line *line)
4851 struct branch *branch = line->data;
4853 string_copy_rev(view->ref, branch->ref->id);
4854 string_copy_rev(ref_commit, branch->ref->id);
4855 string_copy_rev(ref_head, branch->ref->id);
4858 static struct view_ops branch_ops = {
4877 char rev[SIZEOF_REV];
4878 char name[SIZEOF_STR];
4882 char rev[SIZEOF_REV];
4883 char name[SIZEOF_STR];
4887 static char status_onbranch[SIZEOF_STR];
4888 static struct status stage_status;
4889 static enum line_type stage_line_type;
4890 static size_t stage_chunks;
4891 static int *stage_chunk;
4893 DEFINE_ALLOCATOR(realloc_ints, int, 32)
4895 /* This should work even for the "On branch" line. */
4897 status_has_none(struct view *view, struct line *line)
4899 return line < view->line + view->lines && !line[1].data;
4902 /* Get fields from the diff line:
4903 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
4906 status_get_diff(struct status *file, const char *buf, size_t bufsize)
4908 const char *old_mode = buf + 1;
4909 const char *new_mode = buf + 8;
4910 const char *old_rev = buf + 15;
4911 const char *new_rev = buf + 56;
4912 const char *status = buf + 97;
4915 old_mode[-1] != ':' ||
4916 new_mode[-1] != ' ' ||
4917 old_rev[-1] != ' ' ||
4918 new_rev[-1] != ' ' ||
4922 file->status = *status;
4924 string_copy_rev(file->old.rev, old_rev);
4925 string_copy_rev(file->new.rev, new_rev);
4927 file->old.mode = strtoul(old_mode, NULL, 8);
4928 file->new.mode = strtoul(new_mode, NULL, 8);
4930 file->old.name[0] = file->new.name[0] = 0;
4936 status_run(struct view *view, const char *argv[], char status, enum line_type type)
4938 struct status *unmerged = NULL;
4942 if (!run_io(&io, argv, NULL, IO_RD))
4945 add_line_data(view, NULL, type);
4947 while ((buf = io_get(&io, 0, TRUE))) {
4948 struct status *file = unmerged;
4951 file = calloc(1, sizeof(*file));
4952 if (!file || !add_line_data(view, file, type))
4956 /* Parse diff info part. */
4958 file->status = status;
4960 string_copy(file->old.rev, NULL_ID);
4962 } else if (!file->status || file == unmerged) {
4963 if (!status_get_diff(file, buf, strlen(buf)))
4966 buf = io_get(&io, 0, TRUE);
4970 /* Collapse all modified entries that follow an
4971 * associated unmerged entry. */
4972 if (unmerged == file) {
4973 unmerged->status = 'U';
4975 } else if (file->status == 'U') {
4980 /* Grab the old name for rename/copy. */
4981 if (!*file->old.name &&
4982 (file->status == 'R' || file->status == 'C')) {
4983 string_ncopy(file->old.name, buf, strlen(buf));
4985 buf = io_get(&io, 0, TRUE);
4990 /* git-ls-files just delivers a NUL separated list of
4991 * file names similar to the second half of the
4992 * git-diff-* output. */
4993 string_ncopy(file->new.name, buf, strlen(buf));
4994 if (!*file->old.name)
4995 string_copy(file->old.name, file->new.name);
4999 if (io_error(&io)) {
5005 if (!view->line[view->lines - 1].data)
5006 add_line_data(view, NULL, LINE_STAT_NONE);
5012 /* Don't show unmerged entries in the staged section. */
5013 static const char *status_diff_index_argv[] = {
5014 "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5015 "--cached", "-M", "HEAD", NULL
5018 static const char *status_diff_files_argv[] = {
5019 "git", "diff-files", "-z", NULL
5022 static const char *status_list_other_argv[] = {
5023 "git", "ls-files", "-z", "--others", "--exclude-standard", NULL
5026 static const char *status_list_no_head_argv[] = {
5027 "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5030 static const char *update_index_argv[] = {
5031 "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5034 /* Restore the previous line number to stay in the context or select a
5035 * line with something that can be updated. */
5037 status_restore(struct view *view)
5039 if (view->p_lineno >= view->lines)
5040 view->p_lineno = view->lines - 1;
5041 while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
5043 while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
5046 /* If the above fails, always skip the "On branch" line. */
5047 if (view->p_lineno < view->lines)
5048 view->lineno = view->p_lineno;
5052 if (view->lineno < view->offset)
5053 view->offset = view->lineno;
5054 else if (view->offset + view->height <= view->lineno)
5055 view->offset = view->lineno - view->height + 1;
5057 view->p_restore = FALSE;
5061 status_update_onbranch(void)
5063 static const char *paths[][2] = {
5064 { "rebase-apply/rebasing", "Rebasing" },
5065 { "rebase-apply/applying", "Applying mailbox" },
5066 { "rebase-apply/", "Rebasing mailbox" },
5067 { "rebase-merge/interactive", "Interactive rebase" },
5068 { "rebase-merge/", "Rebase merge" },
5069 { "MERGE_HEAD", "Merging" },
5070 { "BISECT_LOG", "Bisecting" },
5071 { "HEAD", "On branch" },
5073 char buf[SIZEOF_STR];
5077 if (is_initial_commit()) {
5078 string_copy(status_onbranch, "Initial commit");
5082 for (i = 0; i < ARRAY_SIZE(paths); i++) {
5083 char *head = opt_head;
5085 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5086 lstat(buf, &stat) < 0)
5092 if (string_format(buf, "%s/rebase-merge/head-name", opt_git_dir) &&
5093 io_open(&io, buf) &&
5094 io_read_buf(&io, buf, sizeof(buf))) {
5096 if (!prefixcmp(head, "refs/heads/"))
5097 head += STRING_SIZE("refs/heads/");
5101 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5102 string_copy(status_onbranch, opt_head);
5106 string_copy(status_onbranch, "Not currently on any branch");
5109 /* First parse staged info using git-diff-index(1), then parse unstaged
5110 * info using git-diff-files(1), and finally untracked files using
5111 * git-ls-files(1). */
5113 status_open(struct view *view)
5117 add_line_data(view, NULL, LINE_STAT_HEAD);
5118 status_update_onbranch();
5120 run_io_bg(update_index_argv);
5122 if (is_initial_commit()) {
5123 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5125 } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5129 if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5130 !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5133 /* Restore the exact position or use the specialized restore
5135 if (!view->p_restore)
5136 status_restore(view);
5141 status_draw(struct view *view, struct line *line, unsigned int lineno)
5143 struct status *status = line->data;
5144 enum line_type type;
5148 switch (line->type) {
5149 case LINE_STAT_STAGED:
5150 type = LINE_STAT_SECTION;
5151 text = "Changes to be committed:";
5154 case LINE_STAT_UNSTAGED:
5155 type = LINE_STAT_SECTION;
5156 text = "Changed but not updated:";
5159 case LINE_STAT_UNTRACKED:
5160 type = LINE_STAT_SECTION;
5161 text = "Untracked files:";
5164 case LINE_STAT_NONE:
5165 type = LINE_DEFAULT;
5166 text = " (no files)";
5169 case LINE_STAT_HEAD:
5170 type = LINE_STAT_HEAD;
5171 text = status_onbranch;
5178 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5180 buf[0] = status->status;
5181 if (draw_text(view, line->type, buf, TRUE))
5183 type = LINE_DEFAULT;
5184 text = status->new.name;
5187 draw_text(view, type, text, TRUE);
5192 status_load_error(struct view *view, struct view *stage, const char *path)
5194 if (displayed_views() == 2 || display[current_view] != view)
5195 maximize_view(view);
5196 report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5201 status_enter(struct view *view, struct line *line)
5203 struct status *status = line->data;
5204 const char *oldpath = status ? status->old.name : NULL;
5205 /* Diffs for unmerged entries are empty when passing the new
5206 * path, so leave it empty. */
5207 const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5209 enum open_flags split;
5210 struct view *stage = VIEW(REQ_VIEW_STAGE);
5212 if (line->type == LINE_STAT_NONE ||
5213 (!status && line[1].type == LINE_STAT_NONE)) {
5214 report("No file to diff");
5218 switch (line->type) {
5219 case LINE_STAT_STAGED:
5220 if (is_initial_commit()) {
5221 const char *no_head_diff_argv[] = {
5222 "git", "diff", "--no-color", "--patch-with-stat",
5223 "--", "/dev/null", newpath, NULL
5226 if (!prepare_update(stage, no_head_diff_argv, opt_cdup, FORMAT_DASH))
5227 return status_load_error(view, stage, newpath);
5229 const char *index_show_argv[] = {
5230 "git", "diff-index", "--root", "--patch-with-stat",
5231 "-C", "-M", "--cached", "HEAD", "--",
5232 oldpath, newpath, NULL
5235 if (!prepare_update(stage, index_show_argv, opt_cdup, FORMAT_DASH))
5236 return status_load_error(view, stage, newpath);
5240 info = "Staged changes to %s";
5242 info = "Staged changes";
5245 case LINE_STAT_UNSTAGED:
5247 const char *files_show_argv[] = {
5248 "git", "diff-files", "--root", "--patch-with-stat",
5249 "-C", "-M", "--", oldpath, newpath, NULL
5252 if (!prepare_update(stage, files_show_argv, opt_cdup, FORMAT_DASH))
5253 return status_load_error(view, stage, newpath);
5255 info = "Unstaged changes to %s";
5257 info = "Unstaged changes";
5260 case LINE_STAT_UNTRACKED:
5262 report("No file to show");
5266 if (!suffixcmp(status->new.name, -1, "/")) {
5267 report("Cannot display a directory");
5271 if (!prepare_update_file(stage, newpath))
5272 return status_load_error(view, stage, newpath);
5273 info = "Untracked file %s";
5276 case LINE_STAT_HEAD:
5280 die("line type %d not handled in switch", line->type);
5283 split = view_is_displayed(view) ? OPEN_SPLIT : 0;
5284 open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5285 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5287 stage_status = *status;
5289 memset(&stage_status, 0, sizeof(stage_status));
5292 stage_line_type = line->type;
5294 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5301 status_exists(struct status *status, enum line_type type)
5303 struct view *view = VIEW(REQ_VIEW_STATUS);
5304 unsigned long lineno;
5306 for (lineno = 0; lineno < view->lines; lineno++) {
5307 struct line *line = &view->line[lineno];
5308 struct status *pos = line->data;
5310 if (line->type != type)
5312 if (!pos && (!status || !status->status) && line[1].data) {
5313 select_view_line(view, lineno);
5316 if (pos && !strcmp(status->new.name, pos->new.name)) {
5317 select_view_line(view, lineno);
5327 status_update_prepare(struct io *io, enum line_type type)
5329 const char *staged_argv[] = {
5330 "git", "update-index", "-z", "--index-info", NULL
5332 const char *others_argv[] = {
5333 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5337 case LINE_STAT_STAGED:
5338 return run_io(io, staged_argv, opt_cdup, IO_WR);
5340 case LINE_STAT_UNSTAGED:
5341 return run_io(io, others_argv, opt_cdup, IO_WR);
5343 case LINE_STAT_UNTRACKED:
5344 return run_io(io, others_argv, NULL, IO_WR);
5347 die("line type %d not handled in switch", type);
5353 status_update_write(struct io *io, struct status *status, enum line_type type)
5355 char buf[SIZEOF_STR];
5359 case LINE_STAT_STAGED:
5360 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
5363 status->old.name, 0))
5367 case LINE_STAT_UNSTAGED:
5368 case LINE_STAT_UNTRACKED:
5369 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
5374 die("line type %d not handled in switch", type);
5377 return io_write(io, buf, bufsize);
5381 status_update_file(struct status *status, enum line_type type)
5386 if (!status_update_prepare(&io, type))
5389 result = status_update_write(&io, status, type);
5390 return done_io(&io) && result;
5394 status_update_files(struct view *view, struct line *line)
5396 char buf[sizeof(view->ref)];
5399 struct line *pos = view->line + view->lines;
5402 int cursor_y, cursor_x;
5404 if (!status_update_prepare(&io, line->type))
5407 for (pos = line; pos < view->line + view->lines && pos->data; pos++)
5410 string_copy(buf, view->ref);
5411 getsyx(cursor_y, cursor_x);
5412 for (file = 0, done = 5; result && file < files; line++, file++) {
5413 int almost_done = file * 100 / files;
5415 if (almost_done > done) {
5417 string_format(view->ref, "updating file %u of %u (%d%% done)",
5419 update_view_title(view);
5420 setsyx(cursor_y, cursor_x);
5423 result = status_update_write(&io, line->data, line->type);
5425 string_copy(view->ref, buf);
5427 return done_io(&io) && result;
5431 status_update(struct view *view)
5433 struct line *line = &view->line[view->lineno];
5435 assert(view->lines);
5438 /* This should work even for the "On branch" line. */
5439 if (line < view->line + view->lines && !line[1].data) {
5440 report("Nothing to update");
5444 if (!status_update_files(view, line + 1)) {
5445 report("Failed to update file status");
5449 } else if (!status_update_file(line->data, line->type)) {
5450 report("Failed to update file status");
5458 status_revert(struct status *status, enum line_type type, bool has_none)
5460 if (!status || type != LINE_STAT_UNSTAGED) {
5461 if (type == LINE_STAT_STAGED) {
5462 report("Cannot revert changes to staged files");
5463 } else if (type == LINE_STAT_UNTRACKED) {
5464 report("Cannot revert changes to untracked files");
5465 } else if (has_none) {
5466 report("Nothing to revert");
5468 report("Cannot revert changes to multiple files");
5473 char mode[10] = "100644";
5474 const char *reset_argv[] = {
5475 "git", "update-index", "--cacheinfo", mode,
5476 status->old.rev, status->old.name, NULL
5478 const char *checkout_argv[] = {
5479 "git", "checkout", "--", status->old.name, NULL
5482 if (!prompt_yesno("Are you sure you want to overwrite any changes?"))
5484 string_format(mode, "%o", status->old.mode);
5485 return (status->status != 'U' || run_io_fg(reset_argv, opt_cdup)) &&
5486 run_io_fg(checkout_argv, opt_cdup);
5491 status_request(struct view *view, enum request request, struct line *line)
5493 struct status *status = line->data;
5496 case REQ_STATUS_UPDATE:
5497 if (!status_update(view))
5501 case REQ_STATUS_REVERT:
5502 if (!status_revert(status, line->type, status_has_none(view, line)))
5506 case REQ_STATUS_MERGE:
5507 if (!status || status->status != 'U') {
5508 report("Merging only possible for files with unmerged status ('U').");
5511 open_mergetool(status->new.name);
5517 if (status->status == 'D') {
5518 report("File has been deleted.");
5522 open_editor(status->status != '?', status->new.name);
5525 case REQ_VIEW_BLAME:
5527 string_copy(opt_file, status->new.name);
5533 /* After returning the status view has been split to
5534 * show the stage view. No further reloading is
5536 return status_enter(view, line);
5539 /* Simply reload the view. */
5546 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
5552 status_select(struct view *view, struct line *line)
5554 struct status *status = line->data;
5555 char file[SIZEOF_STR] = "all files";
5559 if (status && !string_format(file, "'%s'", status->new.name))
5562 if (!status && line[1].type == LINE_STAT_NONE)
5565 switch (line->type) {
5566 case LINE_STAT_STAGED:
5567 text = "Press %s to unstage %s for commit";
5570 case LINE_STAT_UNSTAGED:
5571 text = "Press %s to stage %s for commit";
5574 case LINE_STAT_UNTRACKED:
5575 text = "Press %s to stage %s for addition";
5578 case LINE_STAT_HEAD:
5579 case LINE_STAT_NONE:
5580 text = "Nothing to update";
5584 die("line type %d not handled in switch", line->type);
5587 if (status && status->status == 'U') {
5588 text = "Press %s to resolve conflict in %s";
5589 key = get_key(REQ_STATUS_MERGE);
5592 key = get_key(REQ_STATUS_UPDATE);
5595 string_format(view->ref, text, key, file);
5599 status_grep(struct view *view, struct line *line)
5601 struct status *status = line->data;
5604 const char buf[2] = { status->status, 0 };
5605 const char *text[] = { status->new.name, buf, NULL };
5607 return grep_text(view, text);
5613 static struct view_ops status_ops = {
5626 stage_diff_write(struct io *io, struct line *line, struct line *end)
5628 while (line < end) {
5629 if (!io_write(io, line->data, strlen(line->data)) ||
5630 !io_write(io, "\n", 1))
5633 if (line->type == LINE_DIFF_CHUNK ||
5634 line->type == LINE_DIFF_HEADER)
5641 static struct line *
5642 stage_diff_find(struct view *view, struct line *line, enum line_type type)
5644 for (; view->line < line; line--)
5645 if (line->type == type)
5652 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
5654 const char *apply_argv[SIZEOF_ARG] = {
5655 "git", "apply", "--whitespace=nowarn", NULL
5657 struct line *diff_hdr;
5661 diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
5666 apply_argv[argc++] = "--cached";
5667 if (revert || stage_line_type == LINE_STAT_STAGED)
5668 apply_argv[argc++] = "-R";
5669 apply_argv[argc++] = "-";
5670 apply_argv[argc++] = NULL;
5671 if (!run_io(&io, apply_argv, opt_cdup, IO_WR))
5674 if (!stage_diff_write(&io, diff_hdr, chunk) ||
5675 !stage_diff_write(&io, chunk, view->line + view->lines))
5679 run_io_bg(update_index_argv);
5681 return chunk ? TRUE : FALSE;
5685 stage_update(struct view *view, struct line *line)
5687 struct line *chunk = NULL;
5689 if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
5690 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5693 if (!stage_apply_chunk(view, chunk, FALSE)) {
5694 report("Failed to apply chunk");
5698 } else if (!stage_status.status) {
5699 view = VIEW(REQ_VIEW_STATUS);
5701 for (line = view->line; line < view->line + view->lines; line++)
5702 if (line->type == stage_line_type)
5705 if (!status_update_files(view, line + 1)) {
5706 report("Failed to update files");
5710 } else if (!status_update_file(&stage_status, stage_line_type)) {
5711 report("Failed to update file");
5719 stage_revert(struct view *view, struct line *line)
5721 struct line *chunk = NULL;
5723 if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
5724 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5727 if (!prompt_yesno("Are you sure you want to revert changes?"))
5730 if (!stage_apply_chunk(view, chunk, TRUE)) {
5731 report("Failed to revert chunk");
5737 return status_revert(stage_status.status ? &stage_status : NULL,
5738 stage_line_type, FALSE);
5744 stage_next(struct view *view, struct line *line)
5748 if (!stage_chunks) {
5749 for (line = view->line; line < view->line + view->lines; line++) {
5750 if (line->type != LINE_DIFF_CHUNK)
5753 if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
5754 report("Allocation failure");
5758 stage_chunk[stage_chunks++] = line - view->line;
5762 for (i = 0; i < stage_chunks; i++) {
5763 if (stage_chunk[i] > view->lineno) {
5764 do_scroll_view(view, stage_chunk[i] - view->lineno);
5765 report("Chunk %d of %d", i + 1, stage_chunks);
5770 report("No next chunk found");
5774 stage_request(struct view *view, enum request request, struct line *line)
5777 case REQ_STATUS_UPDATE:
5778 if (!stage_update(view, line))
5782 case REQ_STATUS_REVERT:
5783 if (!stage_revert(view, line))
5787 case REQ_STAGE_NEXT:
5788 if (stage_line_type == LINE_STAT_UNTRACKED) {
5789 report("File is untracked; press %s to add",
5790 get_key(REQ_STATUS_UPDATE));
5793 stage_next(view, line);
5797 if (!stage_status.new.name[0])
5799 if (stage_status.status == 'D') {
5800 report("File has been deleted.");
5804 open_editor(stage_status.status != '?', stage_status.new.name);
5808 /* Reload everything ... */
5811 case REQ_VIEW_BLAME:
5812 if (stage_status.new.name[0]) {
5813 string_copy(opt_file, stage_status.new.name);
5819 return pager_request(view, request, line);
5825 VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
5826 open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
5828 /* Check whether the staged entry still exists, and close the
5829 * stage view if it doesn't. */
5830 if (!status_exists(&stage_status, stage_line_type)) {
5831 status_restore(VIEW(REQ_VIEW_STATUS));
5832 return REQ_VIEW_CLOSE;
5835 if (stage_line_type == LINE_STAT_UNTRACKED) {
5836 if (!suffixcmp(stage_status.new.name, -1, "/")) {
5837 report("Cannot display a directory");
5841 if (!prepare_update_file(view, stage_status.new.name)) {
5842 report("Failed to open file: %s", strerror(errno));
5846 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
5851 static struct view_ops stage_ops = {
5868 char id[SIZEOF_REV]; /* SHA1 ID. */
5869 char title[128]; /* First line of the commit message. */
5870 const char *author; /* Author of the commit. */
5871 time_t time; /* Date from the author ident. */
5872 struct ref **refs; /* Repository references. */
5873 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
5874 size_t graph_size; /* The width of the graph array. */
5875 bool has_parents; /* Rewritten --parents seen. */
5878 /* Size of rev graph with no "padding" columns */
5879 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
5882 struct rev_graph *prev, *next, *parents;
5883 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
5885 struct commit *commit;
5887 unsigned int boundary:1;
5890 /* Parents of the commit being visualized. */
5891 static struct rev_graph graph_parents[4];
5893 /* The current stack of revisions on the graph. */
5894 static struct rev_graph graph_stacks[4] = {
5895 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
5896 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
5897 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
5898 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
5902 graph_parent_is_merge(struct rev_graph *graph)
5904 return graph->parents->size > 1;
5908 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
5910 struct commit *commit = graph->commit;
5912 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
5913 commit->graph[commit->graph_size++] = symbol;
5917 clear_rev_graph(struct rev_graph *graph)
5919 graph->boundary = 0;
5920 graph->size = graph->pos = 0;
5921 graph->commit = NULL;
5922 memset(graph->parents, 0, sizeof(*graph->parents));
5926 done_rev_graph(struct rev_graph *graph)
5928 if (graph_parent_is_merge(graph) &&
5929 graph->pos < graph->size - 1 &&
5930 graph->next->size == graph->size + graph->parents->size - 1) {
5931 size_t i = graph->pos + graph->parents->size - 1;
5933 graph->commit->graph_size = i * 2;
5934 while (i < graph->next->size - 1) {
5935 append_to_rev_graph(graph, ' ');
5936 append_to_rev_graph(graph, '\\');
5941 clear_rev_graph(graph);
5945 push_rev_graph(struct rev_graph *graph, const char *parent)
5949 /* "Collapse" duplicate parents lines.
5951 * FIXME: This needs to also update update the drawn graph but
5952 * for now it just serves as a method for pruning graph lines. */
5953 for (i = 0; i < graph->size; i++)
5954 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
5957 if (graph->size < SIZEOF_REVITEMS) {
5958 string_copy_rev(graph->rev[graph->size++], parent);
5963 get_rev_graph_symbol(struct rev_graph *graph)
5967 if (graph->boundary)
5968 symbol = REVGRAPH_BOUND;
5969 else if (graph->parents->size == 0)
5970 symbol = REVGRAPH_INIT;
5971 else if (graph_parent_is_merge(graph))
5972 symbol = REVGRAPH_MERGE;
5973 else if (graph->pos >= graph->size)
5974 symbol = REVGRAPH_BRANCH;
5976 symbol = REVGRAPH_COMMIT;
5982 draw_rev_graph(struct rev_graph *graph)
5985 chtype separator, line;
5987 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
5988 static struct rev_filler fillers[] = {
5994 chtype symbol = get_rev_graph_symbol(graph);
5995 struct rev_filler *filler;
5998 if (opt_line_graphics)
5999 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
6001 filler = &fillers[DEFAULT];
6003 for (i = 0; i < graph->pos; i++) {
6004 append_to_rev_graph(graph, filler->line);
6005 if (graph_parent_is_merge(graph->prev) &&
6006 graph->prev->pos == i)
6007 filler = &fillers[RSHARP];
6009 append_to_rev_graph(graph, filler->separator);
6012 /* Place the symbol for this revision. */
6013 append_to_rev_graph(graph, symbol);
6015 if (graph->prev->size > graph->size)
6016 filler = &fillers[RDIAG];
6018 filler = &fillers[DEFAULT];
6022 for (; i < graph->size; i++) {
6023 append_to_rev_graph(graph, filler->separator);
6024 append_to_rev_graph(graph, filler->line);
6025 if (graph_parent_is_merge(graph->prev) &&
6026 i < graph->prev->pos + graph->parents->size)
6027 filler = &fillers[RSHARP];
6028 if (graph->prev->size > graph->size)
6029 filler = &fillers[LDIAG];
6032 if (graph->prev->size > graph->size) {
6033 append_to_rev_graph(graph, filler->separator);
6034 if (filler->line != ' ')
6035 append_to_rev_graph(graph, filler->line);
6039 /* Prepare the next rev graph */
6041 prepare_rev_graph(struct rev_graph *graph)
6045 /* First, traverse all lines of revisions up to the active one. */
6046 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6047 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6050 push_rev_graph(graph->next, graph->rev[graph->pos]);
6053 /* Interleave the new revision parent(s). */
6054 for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6055 push_rev_graph(graph->next, graph->parents->rev[i]);
6057 /* Lastly, put any remaining revisions. */
6058 for (i = graph->pos + 1; i < graph->size; i++)
6059 push_rev_graph(graph->next, graph->rev[i]);
6063 update_rev_graph(struct view *view, struct rev_graph *graph)
6065 /* If this is the finalizing update ... */
6067 prepare_rev_graph(graph);
6069 /* Graph visualization needs a one rev look-ahead,
6070 * so the first update doesn't visualize anything. */
6071 if (!graph->prev->commit)
6074 if (view->lines > 2)
6075 view->line[view->lines - 3].dirty = 1;
6076 if (view->lines > 1)
6077 view->line[view->lines - 2].dirty = 1;
6078 draw_rev_graph(graph->prev);
6079 done_rev_graph(graph->prev->prev);
6087 static const char *main_argv[SIZEOF_ARG] = {
6088 "git", "log", "--no-color", "--pretty=raw", "--parents",
6089 "--topo-order", "%(head)", NULL
6093 main_draw(struct view *view, struct line *line, unsigned int lineno)
6095 struct commit *commit = line->data;
6097 if (!commit->author)
6100 if (opt_date && draw_date(view, &commit->time))
6103 if (opt_author && draw_author(view, commit->author))
6106 if (opt_rev_graph && commit->graph_size &&
6107 draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6110 if (opt_show_refs && commit->refs) {
6114 struct ref *ref = commit->refs[i];
6115 enum line_type type;
6118 type = LINE_MAIN_HEAD;
6120 type = LINE_MAIN_LOCAL_TAG;
6122 type = LINE_MAIN_TAG;
6123 else if (ref->tracked)
6124 type = LINE_MAIN_TRACKED;
6125 else if (ref->remote)
6126 type = LINE_MAIN_REMOTE;
6128 type = LINE_MAIN_REF;
6130 if (draw_text(view, type, "[", TRUE) ||
6131 draw_text(view, type, ref->name, TRUE) ||
6132 draw_text(view, type, "]", TRUE))
6135 if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6137 } while (commit->refs[i++]->next);
6140 draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6144 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6146 main_read(struct view *view, char *line)
6148 static struct rev_graph *graph = graph_stacks;
6149 enum line_type type;
6150 struct commit *commit;
6155 if (!view->lines && !view->parent)
6156 die("No revisions match the given arguments.");
6157 if (view->lines > 0) {
6158 commit = view->line[view->lines - 1].data;
6159 view->line[view->lines - 1].dirty = 1;
6160 if (!commit->author) {
6163 graph->commit = NULL;
6166 update_rev_graph(view, graph);
6168 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6169 clear_rev_graph(&graph_stacks[i]);
6173 type = get_line_type(line);
6174 if (type == LINE_COMMIT) {
6175 commit = calloc(1, sizeof(struct commit));
6179 line += STRING_SIZE("commit ");
6181 graph->boundary = 1;
6185 string_copy_rev(commit->id, line);
6186 commit->refs = get_refs(commit->id);
6187 graph->commit = commit;
6188 add_line_data(view, commit, LINE_MAIN_COMMIT);
6190 while ((line = strchr(line, ' '))) {
6192 push_rev_graph(graph->parents, line);
6193 commit->has_parents = TRUE;
6200 commit = view->line[view->lines - 1].data;
6204 if (commit->has_parents)
6206 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6210 parse_author_line(line + STRING_SIZE("author "),
6211 &commit->author, &commit->time);
6212 update_rev_graph(view, graph);
6213 graph = graph->next;
6217 /* Fill in the commit title if it has not already been set. */
6218 if (commit->title[0])
6221 /* Require titles to start with a non-space character at the
6222 * offset used by git log. */
6223 if (strncmp(line, " ", 4))
6226 /* Well, if the title starts with a whitespace character,
6227 * try to be forgiving. Otherwise we end up with no title. */
6228 while (isspace(*line))
6232 /* FIXME: More graceful handling of titles; append "..." to
6233 * shortened titles, etc. */
6235 string_expand(commit->title, sizeof(commit->title), line, 1);
6236 view->line[view->lines - 1].dirty = 1;
6243 main_request(struct view *view, enum request request, struct line *line)
6245 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
6249 open_view(view, REQ_VIEW_DIFF, flags);
6253 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6263 grep_refs(struct ref **refs, regex_t *regex)
6268 if (!opt_show_refs || !refs)
6271 if (regexec(regex, refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6273 } while (refs[i++]->next);
6279 main_grep(struct view *view, struct line *line)
6281 struct commit *commit = line->data;
6282 const char *text[] = {
6284 opt_author ? commit->author : "",
6285 opt_date ? mkdate(&commit->time) : "",
6289 return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6293 main_select(struct view *view, struct line *line)
6295 struct commit *commit = line->data;
6297 string_copy_rev(view->ref, commit->id);
6298 string_copy_rev(ref_commit, view->ref);
6301 static struct view_ops main_ops = {
6314 * Unicode / UTF-8 handling
6316 * NOTE: Much of the following code for dealing with Unicode is derived from
6317 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
6318 * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
6322 unicode_width(unsigned long c)
6325 (c <= 0x115f /* Hangul Jamo */
6328 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
6330 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
6331 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
6332 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
6333 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
6334 || (c >= 0xffe0 && c <= 0xffe6)
6335 || (c >= 0x20000 && c <= 0x2fffd)
6336 || (c >= 0x30000 && c <= 0x3fffd)))
6340 return opt_tab_size;
6345 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
6346 * Illegal bytes are set one. */
6347 static const unsigned char utf8_bytes[256] = {
6348 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,
6349 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,
6350 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,
6351 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,
6352 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,
6353 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,
6354 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,
6355 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,
6358 /* Decode UTF-8 multi-byte representation into a Unicode character. */
6359 static inline unsigned long
6360 utf8_to_unicode(const char *string, size_t length)
6362 unsigned long unicode;
6366 unicode = string[0];
6369 unicode = (string[0] & 0x1f) << 6;
6370 unicode += (string[1] & 0x3f);
6373 unicode = (string[0] & 0x0f) << 12;
6374 unicode += ((string[1] & 0x3f) << 6);
6375 unicode += (string[2] & 0x3f);
6378 unicode = (string[0] & 0x0f) << 18;
6379 unicode += ((string[1] & 0x3f) << 12);
6380 unicode += ((string[2] & 0x3f) << 6);
6381 unicode += (string[3] & 0x3f);
6384 unicode = (string[0] & 0x0f) << 24;
6385 unicode += ((string[1] & 0x3f) << 18);
6386 unicode += ((string[2] & 0x3f) << 12);
6387 unicode += ((string[3] & 0x3f) << 6);
6388 unicode += (string[4] & 0x3f);
6391 unicode = (string[0] & 0x01) << 30;
6392 unicode += ((string[1] & 0x3f) << 24);
6393 unicode += ((string[2] & 0x3f) << 18);
6394 unicode += ((string[3] & 0x3f) << 12);
6395 unicode += ((string[4] & 0x3f) << 6);
6396 unicode += (string[5] & 0x3f);
6399 die("Invalid Unicode length");
6402 /* Invalid characters could return the special 0xfffd value but NUL
6403 * should be just as good. */
6404 return unicode > 0xffff ? 0 : unicode;
6407 /* Calculates how much of string can be shown within the given maximum width
6408 * and sets trimmed parameter to non-zero value if all of string could not be
6409 * shown. If the reserve flag is TRUE, it will reserve at least one
6410 * trailing character, which can be useful when drawing a delimiter.
6412 * Returns the number of bytes to output from string to satisfy max_width. */
6414 utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve)
6416 const char *string = *start;
6417 const char *end = strchr(string, '\0');
6418 unsigned char last_bytes = 0;
6419 size_t last_ucwidth = 0;
6424 while (string < end) {
6425 int c = *(unsigned char *) string;
6426 unsigned char bytes = utf8_bytes[c];
6428 unsigned long unicode;
6430 if (string + bytes > end)
6433 /* Change representation to figure out whether
6434 * it is a single- or double-width character. */
6436 unicode = utf8_to_unicode(string, bytes);
6437 /* FIXME: Graceful handling of invalid Unicode character. */
6441 ucwidth = unicode_width(unicode);
6443 skip -= ucwidth <= skip ? ucwidth : skip;
6447 if (*width > max_width) {
6450 if (reserve && *width == max_width) {
6451 string -= last_bytes;
6452 *width -= last_ucwidth;
6458 last_bytes = ucwidth ? bytes : 0;
6459 last_ucwidth = ucwidth;
6462 return string - *start;
6470 /* Whether or not the curses interface has been initialized. */
6471 static bool cursed = FALSE;
6473 /* Terminal hacks and workarounds. */
6474 static bool use_scroll_redrawwin;
6475 static bool use_scroll_status_wclear;
6477 /* The status window is used for polling keystrokes. */
6478 static WINDOW *status_win;
6480 /* Reading from the prompt? */
6481 static bool input_mode = FALSE;
6483 static bool status_empty = FALSE;
6485 /* Update status and title window. */
6487 report(const char *msg, ...)
6489 struct view *view = display[current_view];
6495 char buf[SIZEOF_STR];
6498 va_start(args, msg);
6499 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6500 buf[sizeof(buf) - 1] = 0;
6501 buf[sizeof(buf) - 2] = '.';
6502 buf[sizeof(buf) - 3] = '.';
6503 buf[sizeof(buf) - 4] = '.';
6509 if (!status_empty || *msg) {
6512 va_start(args, msg);
6514 wmove(status_win, 0, 0);
6515 if (view->has_scrolled && use_scroll_status_wclear)
6518 vwprintw(status_win, msg, args);
6519 status_empty = FALSE;
6521 status_empty = TRUE;
6523 wclrtoeol(status_win);
6524 wnoutrefresh(status_win);
6529 update_view_title(view);
6532 /* Controls when nodelay should be in effect when polling user input. */
6534 set_nonblocking_input(bool loading)
6536 static unsigned int loading_views;
6538 if ((loading == FALSE && loading_views-- == 1) ||
6539 (loading == TRUE && loading_views++ == 0))
6540 nodelay(status_win, loading);
6549 /* Initialize the curses library */
6550 if (isatty(STDIN_FILENO)) {
6551 cursed = !!initscr();
6554 /* Leave stdin and stdout alone when acting as a pager. */
6555 opt_tty = fopen("/dev/tty", "r+");
6557 die("Failed to open /dev/tty");
6558 cursed = !!newterm(NULL, opt_tty, opt_tty);
6562 die("Failed to initialize curses");
6564 nonl(); /* Disable conversion and detect newlines from input. */
6565 cbreak(); /* Take input chars one at a time, no wait for \n */
6566 noecho(); /* Don't echo input */
6567 leaveok(stdscr, FALSE);
6572 getmaxyx(stdscr, y, x);
6573 status_win = newwin(1, 0, y - 1, 0);
6575 die("Failed to create status window");
6577 /* Enable keyboard mapping */
6578 keypad(status_win, TRUE);
6579 wbkgdset(status_win, get_line_attr(LINE_STATUS));
6581 TABSIZE = opt_tab_size;
6582 if (opt_line_graphics) {
6583 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
6586 term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
6587 if (term && !strcmp(term, "gnome-terminal")) {
6588 /* In the gnome-terminal-emulator, the message from
6589 * scrolling up one line when impossible followed by
6590 * scrolling down one line causes corruption of the
6591 * status line. This is fixed by calling wclear. */
6592 use_scroll_status_wclear = TRUE;
6593 use_scroll_redrawwin = FALSE;
6595 } else if (term && !strcmp(term, "xrvt-xpm")) {
6596 /* No problems with full optimizations in xrvt-(unicode)
6598 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
6601 /* When scrolling in (u)xterm the last line in the
6602 * scrolling direction will update slowly. */
6603 use_scroll_redrawwin = TRUE;
6604 use_scroll_status_wclear = FALSE;
6609 get_input(int prompt_position)
6612 int i, key, cursor_y, cursor_x;
6614 if (prompt_position)
6618 foreach_view (view, i) {
6620 if (view_is_displayed(view) && view->has_scrolled &&
6621 use_scroll_redrawwin)
6622 redrawwin(view->win);
6623 view->has_scrolled = FALSE;
6626 /* Update the cursor position. */
6627 if (prompt_position) {
6628 getbegyx(status_win, cursor_y, cursor_x);
6629 cursor_x = prompt_position;
6631 view = display[current_view];
6632 getbegyx(view->win, cursor_y, cursor_x);
6633 cursor_x = view->width - 1;
6634 cursor_y += view->lineno - view->offset;
6636 setsyx(cursor_y, cursor_x);
6638 /* Refresh, accept single keystroke of input */
6640 key = wgetch(status_win);
6642 /* wgetch() with nodelay() enabled returns ERR when
6643 * there's no input. */
6646 } else if (key == KEY_RESIZE) {
6649 getmaxyx(stdscr, height, width);
6651 wresize(status_win, 1, width);
6652 mvwin(status_win, height - 1, 0);
6653 wnoutrefresh(status_win);
6655 redraw_display(TRUE);
6665 prompt_input(const char *prompt, input_handler handler, void *data)
6667 enum input_status status = INPUT_OK;
6668 static char buf[SIZEOF_STR];
6673 while (status == INPUT_OK || status == INPUT_SKIP) {
6676 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
6677 wclrtoeol(status_win);
6679 key = get_input(pos + 1);
6684 status = pos ? INPUT_STOP : INPUT_CANCEL;
6691 status = INPUT_CANCEL;
6695 status = INPUT_CANCEL;
6699 if (pos >= sizeof(buf)) {
6700 report("Input string too long");
6704 status = handler(data, buf, key);
6705 if (status == INPUT_OK)
6706 buf[pos++] = (char) key;
6710 /* Clear the status window */
6711 status_empty = FALSE;
6714 if (status == INPUT_CANCEL)
6722 static enum input_status
6723 prompt_yesno_handler(void *data, char *buf, int c)
6725 if (c == 'y' || c == 'Y')
6727 if (c == 'n' || c == 'N')
6728 return INPUT_CANCEL;
6733 prompt_yesno(const char *prompt)
6735 char prompt2[SIZEOF_STR];
6737 if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
6740 return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
6743 static enum input_status
6744 read_prompt_handler(void *data, char *buf, int c)
6746 return isprint(c) ? INPUT_OK : INPUT_SKIP;
6750 read_prompt(const char *prompt)
6752 return prompt_input(prompt, read_prompt_handler, NULL);
6756 * Repository properties
6759 static struct ref *refs = NULL;
6760 static size_t refs_size = 0;
6762 /* Id <-> ref store */
6763 static struct ref ***id_refs = NULL;
6764 static size_t id_refs_size = 0;
6766 DEFINE_ALLOCATOR(realloc_refs, struct ref, 256)
6767 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
6768 DEFINE_ALLOCATOR(realloc_refs_lists, struct ref **, 8)
6771 compare_refs(const void *ref1_, const void *ref2_)
6773 const struct ref *ref1 = *(const struct ref **)ref1_;
6774 const struct ref *ref2 = *(const struct ref **)ref2_;
6776 if (ref1->tag != ref2->tag)
6777 return ref2->tag - ref1->tag;
6778 if (ref1->ltag != ref2->ltag)
6779 return ref2->ltag - ref2->ltag;
6780 if (ref1->head != ref2->head)
6781 return ref2->head - ref1->head;
6782 if (ref1->tracked != ref2->tracked)
6783 return ref2->tracked - ref1->tracked;
6784 if (ref1->remote != ref2->remote)
6785 return ref2->remote - ref1->remote;
6786 return strcmp(ref1->name, ref2->name);
6790 foreach_ref(bool (*visitor)(void *data, struct ref *ref), void *data)
6794 for (i = 0; i < refs_size; i++)
6795 if (!visitor(data, &refs[i]))
6799 static struct ref **
6800 get_refs(const char *id)
6802 struct ref **ref_list = NULL;
6803 size_t ref_list_size = 0;
6806 for (i = 0; i < id_refs_size; i++)
6807 if (!strcmp(id, id_refs[i][0]->id))
6810 if (!realloc_refs_lists(&id_refs, id_refs_size, 1))
6813 for (i = 0; i < refs_size; i++) {
6814 if (strcmp(id, refs[i].id))
6817 if (!realloc_refs_list(&ref_list, ref_list_size, 1))
6820 ref_list[ref_list_size] = &refs[i];
6821 /* XXX: The properties of the commit chains ensures that we can
6822 * safely modify the shared ref. The repo references will
6823 * always be similar for the same id. */
6824 ref_list[ref_list_size]->next = 1;
6829 qsort(ref_list, ref_list_size, sizeof(*ref_list), compare_refs);
6830 ref_list[ref_list_size - 1]->next = 0;
6831 id_refs[id_refs_size++] = ref_list;
6838 read_ref(char *id, size_t idlen, char *name, size_t namelen)
6843 bool remote = FALSE;
6844 bool tracked = FALSE;
6845 bool check_replace = FALSE;
6848 if (!prefixcmp(name, "refs/tags/")) {
6849 if (!suffixcmp(name, namelen, "^{}")) {
6852 if (refs_size > 0 && refs[refs_size - 1].ltag == TRUE)
6853 check_replace = TRUE;
6859 namelen -= STRING_SIZE("refs/tags/");
6860 name += STRING_SIZE("refs/tags/");
6862 } else if (!prefixcmp(name, "refs/remotes/")) {
6864 namelen -= STRING_SIZE("refs/remotes/");
6865 name += STRING_SIZE("refs/remotes/");
6866 tracked = !strcmp(opt_remote, name);
6868 } else if (!prefixcmp(name, "refs/heads/")) {
6869 namelen -= STRING_SIZE("refs/heads/");
6870 name += STRING_SIZE("refs/heads/");
6871 head = !strncmp(opt_head, name, namelen);
6873 } else if (!strcmp(name, "HEAD")) {
6874 string_ncopy(opt_head_rev, id, idlen);
6878 if (check_replace && !strcmp(name, refs[refs_size - 1].name)) {
6879 /* it's an annotated tag, replace the previous SHA1 with the
6880 * resolved commit id; relies on the fact git-ls-remote lists
6881 * the commit id of an annotated tag right before the commit id
6883 refs[refs_size - 1].ltag = ltag;
6884 string_copy_rev(refs[refs_size - 1].id, id);
6889 if (!realloc_refs(&refs, refs_size, 1))
6892 ref = &refs[refs_size++];
6893 ref->name = malloc(namelen + 1);
6897 strncpy(ref->name, name, namelen);
6898 ref->name[namelen] = 0;
6902 ref->remote = remote;
6903 ref->tracked = tracked;
6904 string_copy_rev(ref->id, id);
6912 const char *head_argv[] = {
6913 "git", "symbolic-ref", "HEAD", NULL
6915 static const char *ls_remote_argv[SIZEOF_ARG] = {
6916 "git", "ls-remote", opt_git_dir, NULL
6918 static bool init = FALSE;
6921 argv_from_env(ls_remote_argv, "TIG_LS_REMOTE");
6928 if (run_io_buf(head_argv, opt_head, sizeof(opt_head)) &&
6929 !prefixcmp(opt_head, "refs/heads/")) {
6930 char *offset = opt_head + STRING_SIZE("refs/heads/");
6932 memmove(opt_head, offset, strlen(offset) + 1);
6935 while (refs_size > 0)
6936 free(refs[--refs_size].name);
6937 while (id_refs_size > 0)
6938 free(id_refs[--id_refs_size]);
6940 return run_io_load(ls_remote_argv, "\t", read_ref);
6944 set_remote_branch(const char *name, const char *value, size_t valuelen)
6946 if (!strcmp(name, ".remote")) {
6947 string_ncopy(opt_remote, value, valuelen);
6949 } else if (*opt_remote && !strcmp(name, ".merge")) {
6950 size_t from = strlen(opt_remote);
6952 if (!prefixcmp(value, "refs/heads/"))
6953 value += STRING_SIZE("refs/heads/");
6955 if (!string_format_from(opt_remote, &from, "/%s", value))
6961 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
6963 const char *argv[SIZEOF_ARG] = { name, "=" };
6964 int argc = 1 + (cmd == option_set_command);
6967 if (!argv_from_string(argv, &argc, value))
6968 config_msg = "Too many option arguments";
6970 error = cmd(argc, argv);
6973 warn("Option 'tig.%s': %s", name, config_msg);
6977 set_environment_variable(const char *name, const char *value)
6979 size_t len = strlen(name) + 1 + strlen(value) + 1;
6980 char *env = malloc(len);
6983 string_nformat(env, len, NULL, "%s=%s", name, value) &&
6991 set_work_tree(const char *value)
6993 char cwd[SIZEOF_STR];
6995 if (!getcwd(cwd, sizeof(cwd)))
6996 die("Failed to get cwd path: %s", strerror(errno));
6997 if (chdir(opt_git_dir) < 0)
6998 die("Failed to chdir(%s): %s", strerror(errno));
6999 if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7000 die("Failed to get git path: %s", strerror(errno));
7002 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7003 if (chdir(value) < 0)
7004 die("Failed to chdir(%s): %s", value, strerror(errno));
7005 if (!getcwd(cwd, sizeof(cwd)))
7006 die("Failed to get cwd path: %s", strerror(errno));
7007 if (!set_environment_variable("GIT_WORK_TREE", cwd))
7008 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7009 if (!set_environment_variable("GIT_DIR", opt_git_dir))
7010 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7011 opt_is_inside_work_tree = TRUE;
7015 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
7017 if (!strcmp(name, "i18n.commitencoding"))
7018 string_ncopy(opt_encoding, value, valuelen);
7020 else if (!strcmp(name, "core.editor"))
7021 string_ncopy(opt_editor, value, valuelen);
7023 else if (!strcmp(name, "core.worktree"))
7024 set_work_tree(value);
7026 else if (!prefixcmp(name, "tig.color."))
7027 set_repo_config_option(name + 10, value, option_color_command);
7029 else if (!prefixcmp(name, "tig.bind."))
7030 set_repo_config_option(name + 9, value, option_bind_command);
7032 else if (!prefixcmp(name, "tig."))
7033 set_repo_config_option(name + 4, value, option_set_command);
7035 else if (*opt_head && !prefixcmp(name, "branch.") &&
7036 !strncmp(name + 7, opt_head, strlen(opt_head)))
7037 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7043 load_git_config(void)
7045 const char *config_list_argv[] = { "git", GIT_CONFIG, "--list", NULL };
7047 return run_io_load(config_list_argv, "=", read_repo_config_option);
7051 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
7053 if (!opt_git_dir[0]) {
7054 string_ncopy(opt_git_dir, name, namelen);
7056 } else if (opt_is_inside_work_tree == -1) {
7057 /* This can be 3 different values depending on the
7058 * version of git being used. If git-rev-parse does not
7059 * understand --is-inside-work-tree it will simply echo
7060 * the option else either "true" or "false" is printed.
7061 * Default to true for the unknown case. */
7062 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7064 } else if (*name == '.') {
7065 string_ncopy(opt_cdup, name, namelen);
7068 string_ncopy(opt_prefix, name, namelen);
7075 load_repo_info(void)
7077 const char *rev_parse_argv[] = {
7078 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7079 "--show-cdup", "--show-prefix", NULL
7082 return run_io_load(rev_parse_argv, "=", read_repo_info);
7090 static const char usage[] =
7091 "tig " TIG_VERSION " (" __DATE__ ")\n"
7093 "Usage: tig [options] [revs] [--] [paths]\n"
7094 " or: tig show [options] [revs] [--] [paths]\n"
7095 " or: tig blame [rev] path\n"
7097 " or: tig < [git command output]\n"
7100 " -v, --version Show version and exit\n"
7101 " -h, --help Show help message and exit";
7103 static void __NORETURN
7106 /* XXX: Restore tty modes and let the OS cleanup the rest! */
7112 static void __NORETURN
7113 die(const char *err, ...)
7119 va_start(args, err);
7120 fputs("tig: ", stderr);
7121 vfprintf(stderr, err, args);
7122 fputs("\n", stderr);
7129 warn(const char *msg, ...)
7133 va_start(args, msg);
7134 fputs("tig warning: ", stderr);
7135 vfprintf(stderr, msg, args);
7136 fputs("\n", stderr);
7141 parse_options(int argc, const char *argv[])
7143 enum request request = REQ_VIEW_MAIN;
7144 const char *subcommand;
7145 bool seen_dashdash = FALSE;
7146 /* XXX: This is vulnerable to the user overriding options
7147 * required for the main view parser. */
7148 const char *custom_argv[SIZEOF_ARG] = {
7149 "git", "log", "--no-color", "--pretty=raw", "--parents",
7150 "--topo-order", NULL
7154 if (!isatty(STDIN_FILENO)) {
7155 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
7156 return REQ_VIEW_PAGER;
7162 subcommand = argv[1];
7163 if (!strcmp(subcommand, "status")) {
7165 warn("ignoring arguments after `%s'", subcommand);
7166 return REQ_VIEW_STATUS;
7168 } else if (!strcmp(subcommand, "blame")) {
7169 if (argc <= 2 || argc > 4)
7170 die("invalid number of options to blame\n\n%s", usage);
7174 string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7178 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7179 return REQ_VIEW_BLAME;
7181 } else if (!strcmp(subcommand, "show")) {
7182 request = REQ_VIEW_DIFF;
7189 custom_argv[1] = subcommand;
7193 for (i = 1 + !!subcommand; i < argc; i++) {
7194 const char *opt = argv[i];
7196 if (seen_dashdash || !strcmp(opt, "--")) {
7197 seen_dashdash = TRUE;
7199 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7200 printf("tig version %s\n", TIG_VERSION);
7203 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7204 printf("%s\n", usage);
7208 custom_argv[j++] = opt;
7209 if (j >= ARRAY_SIZE(custom_argv))
7210 die("command too long");
7213 if (!prepare_update(VIEW(request), custom_argv, NULL, FORMAT_NONE))
7214 die("Failed to format arguments");
7220 main(int argc, const char *argv[])
7222 enum request request = parse_options(argc, argv);
7226 signal(SIGINT, quit);
7227 signal(SIGPIPE, SIG_IGN);
7229 if (setlocale(LC_ALL, "")) {
7230 char *codeset = nl_langinfo(CODESET);
7232 string_ncopy(opt_codeset, codeset, strlen(codeset));
7235 if (load_repo_info() == ERR)
7236 die("Failed to load repo info.");
7238 if (load_options() == ERR)
7239 die("Failed to load user config.");
7241 if (load_git_config() == ERR)
7242 die("Failed to load repo config.");
7244 /* Require a git repository unless when running in pager mode. */
7245 if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7246 die("Not a git repository");
7248 if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
7251 if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
7252 opt_iconv = iconv_open(opt_codeset, opt_encoding);
7253 if (opt_iconv == ICONV_NONE)
7254 die("Failed to initialize character set conversion");
7257 if (load_refs() == ERR)
7258 die("Failed to load refs.");
7260 foreach_view (view, i)
7261 argv_from_env(view->ops->argv, view->cmd_env);
7265 if (request != REQ_NONE)
7266 open_view(NULL, request, OPEN_PREPARED);
7267 request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
7269 while (view_driver(display[current_view], request)) {
7270 int key = get_input(0);
7272 view = display[current_view];
7273 request = get_keybinding(view->keymap, key);
7275 /* Some low-level request handling. This keeps access to
7276 * status_win restricted. */
7280 char *cmd = read_prompt(":");
7282 if (cmd && isdigit(*cmd)) {
7283 int lineno = view->lineno + 1;
7285 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7286 select_view_line(view, lineno - 1);
7289 report("Unable to parse '%s' as a line number", cmd);
7293 struct view *next = VIEW(REQ_VIEW_PAGER);
7294 const char *argv[SIZEOF_ARG] = { "git" };
7297 /* When running random commands, initially show the
7298 * command in the title. However, it maybe later be
7299 * overwritten if a commit line is selected. */
7300 string_ncopy(next->ref, cmd, strlen(cmd));
7302 if (!argv_from_string(argv, &argc, cmd)) {
7303 report("Too many arguments");
7304 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
7305 report("Failed to format command");
7307 open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7315 case REQ_SEARCH_BACK:
7317 const char *prompt = request == REQ_SEARCH ? "/" : "?";
7318 char *search = read_prompt(prompt);
7321 string_ncopy(opt_search, search, strlen(search));
7322 else if (*opt_search)
7323 request = request == REQ_SEARCH ?