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. */
4724 static const enum sort_field branch_sort_fields[] = {
4725 ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4727 static struct sort_state branch_sort_state = SORT_STATE(branch_sort_fields);
4730 branch_compare(const void *l1, const void *l2)
4732 const struct branch *branch1 = ((const struct line *) l1)->data;
4733 const struct branch *branch2 = ((const struct line *) l2)->data;
4735 switch (get_sort_field(branch_sort_state)) {
4737 return sort_order(branch_sort_state, branch1->time - branch2->time);
4739 case ORDERBY_AUTHOR:
4740 return sort_order(branch_sort_state, strcmp(branch1->author, branch2->author));
4744 return sort_order(branch_sort_state, strcmp(branch1->ref->name, branch2->ref->name));
4749 branch_draw(struct view *view, struct line *line, unsigned int lineno)
4751 struct branch *branch = line->data;
4752 enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
4754 if (opt_date && draw_date(view, branch->author ? &branch->time : NULL))
4757 if (opt_author && draw_author(view, branch->author))
4760 draw_text(view, type, branch->ref->name, TRUE);
4765 branch_request(struct view *view, enum request request, struct line *line)
4770 open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
4773 case REQ_TOGGLE_SORT_FIELD:
4774 case REQ_TOGGLE_SORT_ORDER:
4775 sort_view(view, request, &branch_sort_state, branch_compare);
4779 open_view(view, REQ_VIEW_MAIN, OPEN_SPLIT);
4788 branch_read(struct view *view, char *line)
4790 static char id[SIZEOF_REV];
4791 struct branch *reference;
4797 switch (get_line_type(line)) {
4799 string_copy_rev(id, line + STRING_SIZE("commit "));
4803 for (i = 0, reference = NULL; i < view->lines; i++) {
4804 struct branch *branch = view->line[i].data;
4806 if (strcmp(branch->ref->id, id))
4809 view->line[i].dirty = TRUE;
4811 branch->author = reference->author;
4812 branch->time = reference->time;
4816 parse_author_line(line + STRING_SIZE("author "),
4817 &branch->author, &branch->time);
4829 branch_open_visitor(void *data, struct ref *ref)
4831 struct view *view = data;
4832 struct branch *branch;
4834 if (ref->tag || ref->ltag || ref->remote)
4837 branch = calloc(1, sizeof(*branch));
4842 return !!add_line_data(view, branch, LINE_DEFAULT);
4846 branch_open(struct view *view)
4848 const char *branch_log[] = {
4849 "git", "log", "--no-color", "--pretty=raw",
4850 "--simplify-by-decoration", "--all", NULL
4853 if (!run_io_rd(&view->io, branch_log, FORMAT_NONE)) {
4854 report("Failed to load branch data");
4858 setup_update(view, view->id);
4859 foreach_ref(branch_open_visitor, view);
4865 branch_grep(struct view *view, struct line *line)
4867 struct branch *branch = line->data;
4868 const char *text[] = {
4874 return grep_text(view, text);
4878 branch_select(struct view *view, struct line *line)
4880 struct branch *branch = line->data;
4882 string_copy_rev(view->ref, branch->ref->id);
4883 string_copy_rev(ref_commit, branch->ref->id);
4884 string_copy_rev(ref_head, branch->ref->id);
4887 static struct view_ops branch_ops = {
4906 char rev[SIZEOF_REV];
4907 char name[SIZEOF_STR];
4911 char rev[SIZEOF_REV];
4912 char name[SIZEOF_STR];
4916 static char status_onbranch[SIZEOF_STR];
4917 static struct status stage_status;
4918 static enum line_type stage_line_type;
4919 static size_t stage_chunks;
4920 static int *stage_chunk;
4922 DEFINE_ALLOCATOR(realloc_ints, int, 32)
4924 /* This should work even for the "On branch" line. */
4926 status_has_none(struct view *view, struct line *line)
4928 return line < view->line + view->lines && !line[1].data;
4931 /* Get fields from the diff line:
4932 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
4935 status_get_diff(struct status *file, const char *buf, size_t bufsize)
4937 const char *old_mode = buf + 1;
4938 const char *new_mode = buf + 8;
4939 const char *old_rev = buf + 15;
4940 const char *new_rev = buf + 56;
4941 const char *status = buf + 97;
4944 old_mode[-1] != ':' ||
4945 new_mode[-1] != ' ' ||
4946 old_rev[-1] != ' ' ||
4947 new_rev[-1] != ' ' ||
4951 file->status = *status;
4953 string_copy_rev(file->old.rev, old_rev);
4954 string_copy_rev(file->new.rev, new_rev);
4956 file->old.mode = strtoul(old_mode, NULL, 8);
4957 file->new.mode = strtoul(new_mode, NULL, 8);
4959 file->old.name[0] = file->new.name[0] = 0;
4965 status_run(struct view *view, const char *argv[], char status, enum line_type type)
4967 struct status *unmerged = NULL;
4971 if (!run_io(&io, argv, NULL, IO_RD))
4974 add_line_data(view, NULL, type);
4976 while ((buf = io_get(&io, 0, TRUE))) {
4977 struct status *file = unmerged;
4980 file = calloc(1, sizeof(*file));
4981 if (!file || !add_line_data(view, file, type))
4985 /* Parse diff info part. */
4987 file->status = status;
4989 string_copy(file->old.rev, NULL_ID);
4991 } else if (!file->status || file == unmerged) {
4992 if (!status_get_diff(file, buf, strlen(buf)))
4995 buf = io_get(&io, 0, TRUE);
4999 /* Collapse all modified entries that follow an
5000 * associated unmerged entry. */
5001 if (unmerged == file) {
5002 unmerged->status = 'U';
5004 } else if (file->status == 'U') {
5009 /* Grab the old name for rename/copy. */
5010 if (!*file->old.name &&
5011 (file->status == 'R' || file->status == 'C')) {
5012 string_ncopy(file->old.name, buf, strlen(buf));
5014 buf = io_get(&io, 0, TRUE);
5019 /* git-ls-files just delivers a NUL separated list of
5020 * file names similar to the second half of the
5021 * git-diff-* output. */
5022 string_ncopy(file->new.name, buf, strlen(buf));
5023 if (!*file->old.name)
5024 string_copy(file->old.name, file->new.name);
5028 if (io_error(&io)) {
5034 if (!view->line[view->lines - 1].data)
5035 add_line_data(view, NULL, LINE_STAT_NONE);
5041 /* Don't show unmerged entries in the staged section. */
5042 static const char *status_diff_index_argv[] = {
5043 "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5044 "--cached", "-M", "HEAD", NULL
5047 static const char *status_diff_files_argv[] = {
5048 "git", "diff-files", "-z", NULL
5051 static const char *status_list_other_argv[] = {
5052 "git", "ls-files", "-z", "--others", "--exclude-standard", NULL
5055 static const char *status_list_no_head_argv[] = {
5056 "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5059 static const char *update_index_argv[] = {
5060 "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5063 /* Restore the previous line number to stay in the context or select a
5064 * line with something that can be updated. */
5066 status_restore(struct view *view)
5068 if (view->p_lineno >= view->lines)
5069 view->p_lineno = view->lines - 1;
5070 while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
5072 while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
5075 /* If the above fails, always skip the "On branch" line. */
5076 if (view->p_lineno < view->lines)
5077 view->lineno = view->p_lineno;
5081 if (view->lineno < view->offset)
5082 view->offset = view->lineno;
5083 else if (view->offset + view->height <= view->lineno)
5084 view->offset = view->lineno - view->height + 1;
5086 view->p_restore = FALSE;
5090 status_update_onbranch(void)
5092 static const char *paths[][2] = {
5093 { "rebase-apply/rebasing", "Rebasing" },
5094 { "rebase-apply/applying", "Applying mailbox" },
5095 { "rebase-apply/", "Rebasing mailbox" },
5096 { "rebase-merge/interactive", "Interactive rebase" },
5097 { "rebase-merge/", "Rebase merge" },
5098 { "MERGE_HEAD", "Merging" },
5099 { "BISECT_LOG", "Bisecting" },
5100 { "HEAD", "On branch" },
5102 char buf[SIZEOF_STR];
5106 if (is_initial_commit()) {
5107 string_copy(status_onbranch, "Initial commit");
5111 for (i = 0; i < ARRAY_SIZE(paths); i++) {
5112 char *head = opt_head;
5114 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5115 lstat(buf, &stat) < 0)
5121 if (string_format(buf, "%s/rebase-merge/head-name", opt_git_dir) &&
5122 io_open(&io, buf) &&
5123 io_read_buf(&io, buf, sizeof(buf))) {
5125 if (!prefixcmp(head, "refs/heads/"))
5126 head += STRING_SIZE("refs/heads/");
5130 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5131 string_copy(status_onbranch, opt_head);
5135 string_copy(status_onbranch, "Not currently on any branch");
5138 /* First parse staged info using git-diff-index(1), then parse unstaged
5139 * info using git-diff-files(1), and finally untracked files using
5140 * git-ls-files(1). */
5142 status_open(struct view *view)
5146 add_line_data(view, NULL, LINE_STAT_HEAD);
5147 status_update_onbranch();
5149 run_io_bg(update_index_argv);
5151 if (is_initial_commit()) {
5152 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5154 } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5158 if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5159 !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5162 /* Restore the exact position or use the specialized restore
5164 if (!view->p_restore)
5165 status_restore(view);
5170 status_draw(struct view *view, struct line *line, unsigned int lineno)
5172 struct status *status = line->data;
5173 enum line_type type;
5177 switch (line->type) {
5178 case LINE_STAT_STAGED:
5179 type = LINE_STAT_SECTION;
5180 text = "Changes to be committed:";
5183 case LINE_STAT_UNSTAGED:
5184 type = LINE_STAT_SECTION;
5185 text = "Changed but not updated:";
5188 case LINE_STAT_UNTRACKED:
5189 type = LINE_STAT_SECTION;
5190 text = "Untracked files:";
5193 case LINE_STAT_NONE:
5194 type = LINE_DEFAULT;
5195 text = " (no files)";
5198 case LINE_STAT_HEAD:
5199 type = LINE_STAT_HEAD;
5200 text = status_onbranch;
5207 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5209 buf[0] = status->status;
5210 if (draw_text(view, line->type, buf, TRUE))
5212 type = LINE_DEFAULT;
5213 text = status->new.name;
5216 draw_text(view, type, text, TRUE);
5221 status_load_error(struct view *view, struct view *stage, const char *path)
5223 if (displayed_views() == 2 || display[current_view] != view)
5224 maximize_view(view);
5225 report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5230 status_enter(struct view *view, struct line *line)
5232 struct status *status = line->data;
5233 const char *oldpath = status ? status->old.name : NULL;
5234 /* Diffs for unmerged entries are empty when passing the new
5235 * path, so leave it empty. */
5236 const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5238 enum open_flags split;
5239 struct view *stage = VIEW(REQ_VIEW_STAGE);
5241 if (line->type == LINE_STAT_NONE ||
5242 (!status && line[1].type == LINE_STAT_NONE)) {
5243 report("No file to diff");
5247 switch (line->type) {
5248 case LINE_STAT_STAGED:
5249 if (is_initial_commit()) {
5250 const char *no_head_diff_argv[] = {
5251 "git", "diff", "--no-color", "--patch-with-stat",
5252 "--", "/dev/null", newpath, NULL
5255 if (!prepare_update(stage, no_head_diff_argv, opt_cdup, FORMAT_DASH))
5256 return status_load_error(view, stage, newpath);
5258 const char *index_show_argv[] = {
5259 "git", "diff-index", "--root", "--patch-with-stat",
5260 "-C", "-M", "--cached", "HEAD", "--",
5261 oldpath, newpath, NULL
5264 if (!prepare_update(stage, index_show_argv, opt_cdup, FORMAT_DASH))
5265 return status_load_error(view, stage, newpath);
5269 info = "Staged changes to %s";
5271 info = "Staged changes";
5274 case LINE_STAT_UNSTAGED:
5276 const char *files_show_argv[] = {
5277 "git", "diff-files", "--root", "--patch-with-stat",
5278 "-C", "-M", "--", oldpath, newpath, NULL
5281 if (!prepare_update(stage, files_show_argv, opt_cdup, FORMAT_DASH))
5282 return status_load_error(view, stage, newpath);
5284 info = "Unstaged changes to %s";
5286 info = "Unstaged changes";
5289 case LINE_STAT_UNTRACKED:
5291 report("No file to show");
5295 if (!suffixcmp(status->new.name, -1, "/")) {
5296 report("Cannot display a directory");
5300 if (!prepare_update_file(stage, newpath))
5301 return status_load_error(view, stage, newpath);
5302 info = "Untracked file %s";
5305 case LINE_STAT_HEAD:
5309 die("line type %d not handled in switch", line->type);
5312 split = view_is_displayed(view) ? OPEN_SPLIT : 0;
5313 open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5314 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5316 stage_status = *status;
5318 memset(&stage_status, 0, sizeof(stage_status));
5321 stage_line_type = line->type;
5323 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5330 status_exists(struct status *status, enum line_type type)
5332 struct view *view = VIEW(REQ_VIEW_STATUS);
5333 unsigned long lineno;
5335 for (lineno = 0; lineno < view->lines; lineno++) {
5336 struct line *line = &view->line[lineno];
5337 struct status *pos = line->data;
5339 if (line->type != type)
5341 if (!pos && (!status || !status->status) && line[1].data) {
5342 select_view_line(view, lineno);
5345 if (pos && !strcmp(status->new.name, pos->new.name)) {
5346 select_view_line(view, lineno);
5356 status_update_prepare(struct io *io, enum line_type type)
5358 const char *staged_argv[] = {
5359 "git", "update-index", "-z", "--index-info", NULL
5361 const char *others_argv[] = {
5362 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5366 case LINE_STAT_STAGED:
5367 return run_io(io, staged_argv, opt_cdup, IO_WR);
5369 case LINE_STAT_UNSTAGED:
5370 return run_io(io, others_argv, opt_cdup, IO_WR);
5372 case LINE_STAT_UNTRACKED:
5373 return run_io(io, others_argv, NULL, IO_WR);
5376 die("line type %d not handled in switch", type);
5382 status_update_write(struct io *io, struct status *status, enum line_type type)
5384 char buf[SIZEOF_STR];
5388 case LINE_STAT_STAGED:
5389 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
5392 status->old.name, 0))
5396 case LINE_STAT_UNSTAGED:
5397 case LINE_STAT_UNTRACKED:
5398 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
5403 die("line type %d not handled in switch", type);
5406 return io_write(io, buf, bufsize);
5410 status_update_file(struct status *status, enum line_type type)
5415 if (!status_update_prepare(&io, type))
5418 result = status_update_write(&io, status, type);
5419 return done_io(&io) && result;
5423 status_update_files(struct view *view, struct line *line)
5425 char buf[sizeof(view->ref)];
5428 struct line *pos = view->line + view->lines;
5431 int cursor_y, cursor_x;
5433 if (!status_update_prepare(&io, line->type))
5436 for (pos = line; pos < view->line + view->lines && pos->data; pos++)
5439 string_copy(buf, view->ref);
5440 getsyx(cursor_y, cursor_x);
5441 for (file = 0, done = 5; result && file < files; line++, file++) {
5442 int almost_done = file * 100 / files;
5444 if (almost_done > done) {
5446 string_format(view->ref, "updating file %u of %u (%d%% done)",
5448 update_view_title(view);
5449 setsyx(cursor_y, cursor_x);
5452 result = status_update_write(&io, line->data, line->type);
5454 string_copy(view->ref, buf);
5456 return done_io(&io) && result;
5460 status_update(struct view *view)
5462 struct line *line = &view->line[view->lineno];
5464 assert(view->lines);
5467 /* This should work even for the "On branch" line. */
5468 if (line < view->line + view->lines && !line[1].data) {
5469 report("Nothing to update");
5473 if (!status_update_files(view, line + 1)) {
5474 report("Failed to update file status");
5478 } else if (!status_update_file(line->data, line->type)) {
5479 report("Failed to update file status");
5487 status_revert(struct status *status, enum line_type type, bool has_none)
5489 if (!status || type != LINE_STAT_UNSTAGED) {
5490 if (type == LINE_STAT_STAGED) {
5491 report("Cannot revert changes to staged files");
5492 } else if (type == LINE_STAT_UNTRACKED) {
5493 report("Cannot revert changes to untracked files");
5494 } else if (has_none) {
5495 report("Nothing to revert");
5497 report("Cannot revert changes to multiple files");
5502 char mode[10] = "100644";
5503 const char *reset_argv[] = {
5504 "git", "update-index", "--cacheinfo", mode,
5505 status->old.rev, status->old.name, NULL
5507 const char *checkout_argv[] = {
5508 "git", "checkout", "--", status->old.name, NULL
5511 if (!prompt_yesno("Are you sure you want to overwrite any changes?"))
5513 string_format(mode, "%o", status->old.mode);
5514 return (status->status != 'U' || run_io_fg(reset_argv, opt_cdup)) &&
5515 run_io_fg(checkout_argv, opt_cdup);
5520 status_request(struct view *view, enum request request, struct line *line)
5522 struct status *status = line->data;
5525 case REQ_STATUS_UPDATE:
5526 if (!status_update(view))
5530 case REQ_STATUS_REVERT:
5531 if (!status_revert(status, line->type, status_has_none(view, line)))
5535 case REQ_STATUS_MERGE:
5536 if (!status || status->status != 'U') {
5537 report("Merging only possible for files with unmerged status ('U').");
5540 open_mergetool(status->new.name);
5546 if (status->status == 'D') {
5547 report("File has been deleted.");
5551 open_editor(status->status != '?', status->new.name);
5554 case REQ_VIEW_BLAME:
5556 string_copy(opt_file, status->new.name);
5562 /* After returning the status view has been split to
5563 * show the stage view. No further reloading is
5565 return status_enter(view, line);
5568 /* Simply reload the view. */
5575 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
5581 status_select(struct view *view, struct line *line)
5583 struct status *status = line->data;
5584 char file[SIZEOF_STR] = "all files";
5588 if (status && !string_format(file, "'%s'", status->new.name))
5591 if (!status && line[1].type == LINE_STAT_NONE)
5594 switch (line->type) {
5595 case LINE_STAT_STAGED:
5596 text = "Press %s to unstage %s for commit";
5599 case LINE_STAT_UNSTAGED:
5600 text = "Press %s to stage %s for commit";
5603 case LINE_STAT_UNTRACKED:
5604 text = "Press %s to stage %s for addition";
5607 case LINE_STAT_HEAD:
5608 case LINE_STAT_NONE:
5609 text = "Nothing to update";
5613 die("line type %d not handled in switch", line->type);
5616 if (status && status->status == 'U') {
5617 text = "Press %s to resolve conflict in %s";
5618 key = get_key(REQ_STATUS_MERGE);
5621 key = get_key(REQ_STATUS_UPDATE);
5624 string_format(view->ref, text, key, file);
5628 status_grep(struct view *view, struct line *line)
5630 struct status *status = line->data;
5633 const char buf[2] = { status->status, 0 };
5634 const char *text[] = { status->new.name, buf, NULL };
5636 return grep_text(view, text);
5642 static struct view_ops status_ops = {
5655 stage_diff_write(struct io *io, struct line *line, struct line *end)
5657 while (line < end) {
5658 if (!io_write(io, line->data, strlen(line->data)) ||
5659 !io_write(io, "\n", 1))
5662 if (line->type == LINE_DIFF_CHUNK ||
5663 line->type == LINE_DIFF_HEADER)
5670 static struct line *
5671 stage_diff_find(struct view *view, struct line *line, enum line_type type)
5673 for (; view->line < line; line--)
5674 if (line->type == type)
5681 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
5683 const char *apply_argv[SIZEOF_ARG] = {
5684 "git", "apply", "--whitespace=nowarn", NULL
5686 struct line *diff_hdr;
5690 diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
5695 apply_argv[argc++] = "--cached";
5696 if (revert || stage_line_type == LINE_STAT_STAGED)
5697 apply_argv[argc++] = "-R";
5698 apply_argv[argc++] = "-";
5699 apply_argv[argc++] = NULL;
5700 if (!run_io(&io, apply_argv, opt_cdup, IO_WR))
5703 if (!stage_diff_write(&io, diff_hdr, chunk) ||
5704 !stage_diff_write(&io, chunk, view->line + view->lines))
5708 run_io_bg(update_index_argv);
5710 return chunk ? TRUE : FALSE;
5714 stage_update(struct view *view, struct line *line)
5716 struct line *chunk = NULL;
5718 if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
5719 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5722 if (!stage_apply_chunk(view, chunk, FALSE)) {
5723 report("Failed to apply chunk");
5727 } else if (!stage_status.status) {
5728 view = VIEW(REQ_VIEW_STATUS);
5730 for (line = view->line; line < view->line + view->lines; line++)
5731 if (line->type == stage_line_type)
5734 if (!status_update_files(view, line + 1)) {
5735 report("Failed to update files");
5739 } else if (!status_update_file(&stage_status, stage_line_type)) {
5740 report("Failed to update file");
5748 stage_revert(struct view *view, struct line *line)
5750 struct line *chunk = NULL;
5752 if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
5753 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5756 if (!prompt_yesno("Are you sure you want to revert changes?"))
5759 if (!stage_apply_chunk(view, chunk, TRUE)) {
5760 report("Failed to revert chunk");
5766 return status_revert(stage_status.status ? &stage_status : NULL,
5767 stage_line_type, FALSE);
5773 stage_next(struct view *view, struct line *line)
5777 if (!stage_chunks) {
5778 for (line = view->line; line < view->line + view->lines; line++) {
5779 if (line->type != LINE_DIFF_CHUNK)
5782 if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
5783 report("Allocation failure");
5787 stage_chunk[stage_chunks++] = line - view->line;
5791 for (i = 0; i < stage_chunks; i++) {
5792 if (stage_chunk[i] > view->lineno) {
5793 do_scroll_view(view, stage_chunk[i] - view->lineno);
5794 report("Chunk %d of %d", i + 1, stage_chunks);
5799 report("No next chunk found");
5803 stage_request(struct view *view, enum request request, struct line *line)
5806 case REQ_STATUS_UPDATE:
5807 if (!stage_update(view, line))
5811 case REQ_STATUS_REVERT:
5812 if (!stage_revert(view, line))
5816 case REQ_STAGE_NEXT:
5817 if (stage_line_type == LINE_STAT_UNTRACKED) {
5818 report("File is untracked; press %s to add",
5819 get_key(REQ_STATUS_UPDATE));
5822 stage_next(view, line);
5826 if (!stage_status.new.name[0])
5828 if (stage_status.status == 'D') {
5829 report("File has been deleted.");
5833 open_editor(stage_status.status != '?', stage_status.new.name);
5837 /* Reload everything ... */
5840 case REQ_VIEW_BLAME:
5841 if (stage_status.new.name[0]) {
5842 string_copy(opt_file, stage_status.new.name);
5848 return pager_request(view, request, line);
5854 VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
5855 open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
5857 /* Check whether the staged entry still exists, and close the
5858 * stage view if it doesn't. */
5859 if (!status_exists(&stage_status, stage_line_type)) {
5860 status_restore(VIEW(REQ_VIEW_STATUS));
5861 return REQ_VIEW_CLOSE;
5864 if (stage_line_type == LINE_STAT_UNTRACKED) {
5865 if (!suffixcmp(stage_status.new.name, -1, "/")) {
5866 report("Cannot display a directory");
5870 if (!prepare_update_file(view, stage_status.new.name)) {
5871 report("Failed to open file: %s", strerror(errno));
5875 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
5880 static struct view_ops stage_ops = {
5897 char id[SIZEOF_REV]; /* SHA1 ID. */
5898 char title[128]; /* First line of the commit message. */
5899 const char *author; /* Author of the commit. */
5900 time_t time; /* Date from the author ident. */
5901 struct ref **refs; /* Repository references. */
5902 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
5903 size_t graph_size; /* The width of the graph array. */
5904 bool has_parents; /* Rewritten --parents seen. */
5907 /* Size of rev graph with no "padding" columns */
5908 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
5911 struct rev_graph *prev, *next, *parents;
5912 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
5914 struct commit *commit;
5916 unsigned int boundary:1;
5919 /* Parents of the commit being visualized. */
5920 static struct rev_graph graph_parents[4];
5922 /* The current stack of revisions on the graph. */
5923 static struct rev_graph graph_stacks[4] = {
5924 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
5925 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
5926 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
5927 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
5931 graph_parent_is_merge(struct rev_graph *graph)
5933 return graph->parents->size > 1;
5937 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
5939 struct commit *commit = graph->commit;
5941 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
5942 commit->graph[commit->graph_size++] = symbol;
5946 clear_rev_graph(struct rev_graph *graph)
5948 graph->boundary = 0;
5949 graph->size = graph->pos = 0;
5950 graph->commit = NULL;
5951 memset(graph->parents, 0, sizeof(*graph->parents));
5955 done_rev_graph(struct rev_graph *graph)
5957 if (graph_parent_is_merge(graph) &&
5958 graph->pos < graph->size - 1 &&
5959 graph->next->size == graph->size + graph->parents->size - 1) {
5960 size_t i = graph->pos + graph->parents->size - 1;
5962 graph->commit->graph_size = i * 2;
5963 while (i < graph->next->size - 1) {
5964 append_to_rev_graph(graph, ' ');
5965 append_to_rev_graph(graph, '\\');
5970 clear_rev_graph(graph);
5974 push_rev_graph(struct rev_graph *graph, const char *parent)
5978 /* "Collapse" duplicate parents lines.
5980 * FIXME: This needs to also update update the drawn graph but
5981 * for now it just serves as a method for pruning graph lines. */
5982 for (i = 0; i < graph->size; i++)
5983 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
5986 if (graph->size < SIZEOF_REVITEMS) {
5987 string_copy_rev(graph->rev[graph->size++], parent);
5992 get_rev_graph_symbol(struct rev_graph *graph)
5996 if (graph->boundary)
5997 symbol = REVGRAPH_BOUND;
5998 else if (graph->parents->size == 0)
5999 symbol = REVGRAPH_INIT;
6000 else if (graph_parent_is_merge(graph))
6001 symbol = REVGRAPH_MERGE;
6002 else if (graph->pos >= graph->size)
6003 symbol = REVGRAPH_BRANCH;
6005 symbol = REVGRAPH_COMMIT;
6011 draw_rev_graph(struct rev_graph *graph)
6014 chtype separator, line;
6016 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
6017 static struct rev_filler fillers[] = {
6023 chtype symbol = get_rev_graph_symbol(graph);
6024 struct rev_filler *filler;
6027 if (opt_line_graphics)
6028 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
6030 filler = &fillers[DEFAULT];
6032 for (i = 0; i < graph->pos; i++) {
6033 append_to_rev_graph(graph, filler->line);
6034 if (graph_parent_is_merge(graph->prev) &&
6035 graph->prev->pos == i)
6036 filler = &fillers[RSHARP];
6038 append_to_rev_graph(graph, filler->separator);
6041 /* Place the symbol for this revision. */
6042 append_to_rev_graph(graph, symbol);
6044 if (graph->prev->size > graph->size)
6045 filler = &fillers[RDIAG];
6047 filler = &fillers[DEFAULT];
6051 for (; i < graph->size; i++) {
6052 append_to_rev_graph(graph, filler->separator);
6053 append_to_rev_graph(graph, filler->line);
6054 if (graph_parent_is_merge(graph->prev) &&
6055 i < graph->prev->pos + graph->parents->size)
6056 filler = &fillers[RSHARP];
6057 if (graph->prev->size > graph->size)
6058 filler = &fillers[LDIAG];
6061 if (graph->prev->size > graph->size) {
6062 append_to_rev_graph(graph, filler->separator);
6063 if (filler->line != ' ')
6064 append_to_rev_graph(graph, filler->line);
6068 /* Prepare the next rev graph */
6070 prepare_rev_graph(struct rev_graph *graph)
6074 /* First, traverse all lines of revisions up to the active one. */
6075 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6076 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6079 push_rev_graph(graph->next, graph->rev[graph->pos]);
6082 /* Interleave the new revision parent(s). */
6083 for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6084 push_rev_graph(graph->next, graph->parents->rev[i]);
6086 /* Lastly, put any remaining revisions. */
6087 for (i = graph->pos + 1; i < graph->size; i++)
6088 push_rev_graph(graph->next, graph->rev[i]);
6092 update_rev_graph(struct view *view, struct rev_graph *graph)
6094 /* If this is the finalizing update ... */
6096 prepare_rev_graph(graph);
6098 /* Graph visualization needs a one rev look-ahead,
6099 * so the first update doesn't visualize anything. */
6100 if (!graph->prev->commit)
6103 if (view->lines > 2)
6104 view->line[view->lines - 3].dirty = 1;
6105 if (view->lines > 1)
6106 view->line[view->lines - 2].dirty = 1;
6107 draw_rev_graph(graph->prev);
6108 done_rev_graph(graph->prev->prev);
6116 static const char *main_argv[SIZEOF_ARG] = {
6117 "git", "log", "--no-color", "--pretty=raw", "--parents",
6118 "--topo-order", "%(head)", NULL
6122 main_draw(struct view *view, struct line *line, unsigned int lineno)
6124 struct commit *commit = line->data;
6126 if (!commit->author)
6129 if (opt_date && draw_date(view, &commit->time))
6132 if (opt_author && draw_author(view, commit->author))
6135 if (opt_rev_graph && commit->graph_size &&
6136 draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6139 if (opt_show_refs && commit->refs) {
6143 struct ref *ref = commit->refs[i];
6144 enum line_type type;
6147 type = LINE_MAIN_HEAD;
6149 type = LINE_MAIN_LOCAL_TAG;
6151 type = LINE_MAIN_TAG;
6152 else if (ref->tracked)
6153 type = LINE_MAIN_TRACKED;
6154 else if (ref->remote)
6155 type = LINE_MAIN_REMOTE;
6157 type = LINE_MAIN_REF;
6159 if (draw_text(view, type, "[", TRUE) ||
6160 draw_text(view, type, ref->name, TRUE) ||
6161 draw_text(view, type, "]", TRUE))
6164 if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6166 } while (commit->refs[i++]->next);
6169 draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6173 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6175 main_read(struct view *view, char *line)
6177 static struct rev_graph *graph = graph_stacks;
6178 enum line_type type;
6179 struct commit *commit;
6184 if (!view->lines && !view->parent)
6185 die("No revisions match the given arguments.");
6186 if (view->lines > 0) {
6187 commit = view->line[view->lines - 1].data;
6188 view->line[view->lines - 1].dirty = 1;
6189 if (!commit->author) {
6192 graph->commit = NULL;
6195 update_rev_graph(view, graph);
6197 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6198 clear_rev_graph(&graph_stacks[i]);
6202 type = get_line_type(line);
6203 if (type == LINE_COMMIT) {
6204 commit = calloc(1, sizeof(struct commit));
6208 line += STRING_SIZE("commit ");
6210 graph->boundary = 1;
6214 string_copy_rev(commit->id, line);
6215 commit->refs = get_refs(commit->id);
6216 graph->commit = commit;
6217 add_line_data(view, commit, LINE_MAIN_COMMIT);
6219 while ((line = strchr(line, ' '))) {
6221 push_rev_graph(graph->parents, line);
6222 commit->has_parents = TRUE;
6229 commit = view->line[view->lines - 1].data;
6233 if (commit->has_parents)
6235 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6239 parse_author_line(line + STRING_SIZE("author "),
6240 &commit->author, &commit->time);
6241 update_rev_graph(view, graph);
6242 graph = graph->next;
6246 /* Fill in the commit title if it has not already been set. */
6247 if (commit->title[0])
6250 /* Require titles to start with a non-space character at the
6251 * offset used by git log. */
6252 if (strncmp(line, " ", 4))
6255 /* Well, if the title starts with a whitespace character,
6256 * try to be forgiving. Otherwise we end up with no title. */
6257 while (isspace(*line))
6261 /* FIXME: More graceful handling of titles; append "..." to
6262 * shortened titles, etc. */
6264 string_expand(commit->title, sizeof(commit->title), line, 1);
6265 view->line[view->lines - 1].dirty = 1;
6272 main_request(struct view *view, enum request request, struct line *line)
6274 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
6278 open_view(view, REQ_VIEW_DIFF, flags);
6282 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6292 grep_refs(struct ref **refs, regex_t *regex)
6297 if (!opt_show_refs || !refs)
6300 if (regexec(regex, refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6302 } while (refs[i++]->next);
6308 main_grep(struct view *view, struct line *line)
6310 struct commit *commit = line->data;
6311 const char *text[] = {
6313 opt_author ? commit->author : "",
6314 opt_date ? mkdate(&commit->time) : "",
6318 return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6322 main_select(struct view *view, struct line *line)
6324 struct commit *commit = line->data;
6326 string_copy_rev(view->ref, commit->id);
6327 string_copy_rev(ref_commit, view->ref);
6330 static struct view_ops main_ops = {
6343 * Unicode / UTF-8 handling
6345 * NOTE: Much of the following code for dealing with Unicode is derived from
6346 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
6347 * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
6351 unicode_width(unsigned long c)
6354 (c <= 0x115f /* Hangul Jamo */
6357 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
6359 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
6360 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
6361 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
6362 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
6363 || (c >= 0xffe0 && c <= 0xffe6)
6364 || (c >= 0x20000 && c <= 0x2fffd)
6365 || (c >= 0x30000 && c <= 0x3fffd)))
6369 return opt_tab_size;
6374 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
6375 * Illegal bytes are set one. */
6376 static const unsigned char utf8_bytes[256] = {
6377 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,
6378 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,
6379 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,
6380 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,
6381 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
6382 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
6383 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,
6384 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,
6387 /* Decode UTF-8 multi-byte representation into a Unicode character. */
6388 static inline unsigned long
6389 utf8_to_unicode(const char *string, size_t length)
6391 unsigned long unicode;
6395 unicode = string[0];
6398 unicode = (string[0] & 0x1f) << 6;
6399 unicode += (string[1] & 0x3f);
6402 unicode = (string[0] & 0x0f) << 12;
6403 unicode += ((string[1] & 0x3f) << 6);
6404 unicode += (string[2] & 0x3f);
6407 unicode = (string[0] & 0x0f) << 18;
6408 unicode += ((string[1] & 0x3f) << 12);
6409 unicode += ((string[2] & 0x3f) << 6);
6410 unicode += (string[3] & 0x3f);
6413 unicode = (string[0] & 0x0f) << 24;
6414 unicode += ((string[1] & 0x3f) << 18);
6415 unicode += ((string[2] & 0x3f) << 12);
6416 unicode += ((string[3] & 0x3f) << 6);
6417 unicode += (string[4] & 0x3f);
6420 unicode = (string[0] & 0x01) << 30;
6421 unicode += ((string[1] & 0x3f) << 24);
6422 unicode += ((string[2] & 0x3f) << 18);
6423 unicode += ((string[3] & 0x3f) << 12);
6424 unicode += ((string[4] & 0x3f) << 6);
6425 unicode += (string[5] & 0x3f);
6428 die("Invalid Unicode length");
6431 /* Invalid characters could return the special 0xfffd value but NUL
6432 * should be just as good. */
6433 return unicode > 0xffff ? 0 : unicode;
6436 /* Calculates how much of string can be shown within the given maximum width
6437 * and sets trimmed parameter to non-zero value if all of string could not be
6438 * shown. If the reserve flag is TRUE, it will reserve at least one
6439 * trailing character, which can be useful when drawing a delimiter.
6441 * Returns the number of bytes to output from string to satisfy max_width. */
6443 utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve)
6445 const char *string = *start;
6446 const char *end = strchr(string, '\0');
6447 unsigned char last_bytes = 0;
6448 size_t last_ucwidth = 0;
6453 while (string < end) {
6454 int c = *(unsigned char *) string;
6455 unsigned char bytes = utf8_bytes[c];
6457 unsigned long unicode;
6459 if (string + bytes > end)
6462 /* Change representation to figure out whether
6463 * it is a single- or double-width character. */
6465 unicode = utf8_to_unicode(string, bytes);
6466 /* FIXME: Graceful handling of invalid Unicode character. */
6470 ucwidth = unicode_width(unicode);
6472 skip -= ucwidth <= skip ? ucwidth : skip;
6476 if (*width > max_width) {
6479 if (reserve && *width == max_width) {
6480 string -= last_bytes;
6481 *width -= last_ucwidth;
6487 last_bytes = ucwidth ? bytes : 0;
6488 last_ucwidth = ucwidth;
6491 return string - *start;
6499 /* Whether or not the curses interface has been initialized. */
6500 static bool cursed = FALSE;
6502 /* Terminal hacks and workarounds. */
6503 static bool use_scroll_redrawwin;
6504 static bool use_scroll_status_wclear;
6506 /* The status window is used for polling keystrokes. */
6507 static WINDOW *status_win;
6509 /* Reading from the prompt? */
6510 static bool input_mode = FALSE;
6512 static bool status_empty = FALSE;
6514 /* Update status and title window. */
6516 report(const char *msg, ...)
6518 struct view *view = display[current_view];
6524 char buf[SIZEOF_STR];
6527 va_start(args, msg);
6528 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6529 buf[sizeof(buf) - 1] = 0;
6530 buf[sizeof(buf) - 2] = '.';
6531 buf[sizeof(buf) - 3] = '.';
6532 buf[sizeof(buf) - 4] = '.';
6538 if (!status_empty || *msg) {
6541 va_start(args, msg);
6543 wmove(status_win, 0, 0);
6544 if (view->has_scrolled && use_scroll_status_wclear)
6547 vwprintw(status_win, msg, args);
6548 status_empty = FALSE;
6550 status_empty = TRUE;
6552 wclrtoeol(status_win);
6553 wnoutrefresh(status_win);
6558 update_view_title(view);
6561 /* Controls when nodelay should be in effect when polling user input. */
6563 set_nonblocking_input(bool loading)
6565 static unsigned int loading_views;
6567 if ((loading == FALSE && loading_views-- == 1) ||
6568 (loading == TRUE && loading_views++ == 0))
6569 nodelay(status_win, loading);
6578 /* Initialize the curses library */
6579 if (isatty(STDIN_FILENO)) {
6580 cursed = !!initscr();
6583 /* Leave stdin and stdout alone when acting as a pager. */
6584 opt_tty = fopen("/dev/tty", "r+");
6586 die("Failed to open /dev/tty");
6587 cursed = !!newterm(NULL, opt_tty, opt_tty);
6591 die("Failed to initialize curses");
6593 nonl(); /* Disable conversion and detect newlines from input. */
6594 cbreak(); /* Take input chars one at a time, no wait for \n */
6595 noecho(); /* Don't echo input */
6596 leaveok(stdscr, FALSE);
6601 getmaxyx(stdscr, y, x);
6602 status_win = newwin(1, 0, y - 1, 0);
6604 die("Failed to create status window");
6606 /* Enable keyboard mapping */
6607 keypad(status_win, TRUE);
6608 wbkgdset(status_win, get_line_attr(LINE_STATUS));
6610 TABSIZE = opt_tab_size;
6611 if (opt_line_graphics) {
6612 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
6615 term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
6616 if (term && !strcmp(term, "gnome-terminal")) {
6617 /* In the gnome-terminal-emulator, the message from
6618 * scrolling up one line when impossible followed by
6619 * scrolling down one line causes corruption of the
6620 * status line. This is fixed by calling wclear. */
6621 use_scroll_status_wclear = TRUE;
6622 use_scroll_redrawwin = FALSE;
6624 } else if (term && !strcmp(term, "xrvt-xpm")) {
6625 /* No problems with full optimizations in xrvt-(unicode)
6627 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
6630 /* When scrolling in (u)xterm the last line in the
6631 * scrolling direction will update slowly. */
6632 use_scroll_redrawwin = TRUE;
6633 use_scroll_status_wclear = FALSE;
6638 get_input(int prompt_position)
6641 int i, key, cursor_y, cursor_x;
6643 if (prompt_position)
6647 foreach_view (view, i) {
6649 if (view_is_displayed(view) && view->has_scrolled &&
6650 use_scroll_redrawwin)
6651 redrawwin(view->win);
6652 view->has_scrolled = FALSE;
6655 /* Update the cursor position. */
6656 if (prompt_position) {
6657 getbegyx(status_win, cursor_y, cursor_x);
6658 cursor_x = prompt_position;
6660 view = display[current_view];
6661 getbegyx(view->win, cursor_y, cursor_x);
6662 cursor_x = view->width - 1;
6663 cursor_y += view->lineno - view->offset;
6665 setsyx(cursor_y, cursor_x);
6667 /* Refresh, accept single keystroke of input */
6669 key = wgetch(status_win);
6671 /* wgetch() with nodelay() enabled returns ERR when
6672 * there's no input. */
6675 } else if (key == KEY_RESIZE) {
6678 getmaxyx(stdscr, height, width);
6680 wresize(status_win, 1, width);
6681 mvwin(status_win, height - 1, 0);
6682 wnoutrefresh(status_win);
6684 redraw_display(TRUE);
6694 prompt_input(const char *prompt, input_handler handler, void *data)
6696 enum input_status status = INPUT_OK;
6697 static char buf[SIZEOF_STR];
6702 while (status == INPUT_OK || status == INPUT_SKIP) {
6705 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
6706 wclrtoeol(status_win);
6708 key = get_input(pos + 1);
6713 status = pos ? INPUT_STOP : INPUT_CANCEL;
6720 status = INPUT_CANCEL;
6724 status = INPUT_CANCEL;
6728 if (pos >= sizeof(buf)) {
6729 report("Input string too long");
6733 status = handler(data, buf, key);
6734 if (status == INPUT_OK)
6735 buf[pos++] = (char) key;
6739 /* Clear the status window */
6740 status_empty = FALSE;
6743 if (status == INPUT_CANCEL)
6751 static enum input_status
6752 prompt_yesno_handler(void *data, char *buf, int c)
6754 if (c == 'y' || c == 'Y')
6756 if (c == 'n' || c == 'N')
6757 return INPUT_CANCEL;
6762 prompt_yesno(const char *prompt)
6764 char prompt2[SIZEOF_STR];
6766 if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
6769 return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
6772 static enum input_status
6773 read_prompt_handler(void *data, char *buf, int c)
6775 return isprint(c) ? INPUT_OK : INPUT_SKIP;
6779 read_prompt(const char *prompt)
6781 return prompt_input(prompt, read_prompt_handler, NULL);
6785 * Repository properties
6788 static struct ref *refs = NULL;
6789 static size_t refs_size = 0;
6791 /* Id <-> ref store */
6792 static struct ref ***id_refs = NULL;
6793 static size_t id_refs_size = 0;
6795 DEFINE_ALLOCATOR(realloc_refs, struct ref, 256)
6796 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
6797 DEFINE_ALLOCATOR(realloc_refs_lists, struct ref **, 8)
6800 compare_refs(const void *ref1_, const void *ref2_)
6802 const struct ref *ref1 = *(const struct ref **)ref1_;
6803 const struct ref *ref2 = *(const struct ref **)ref2_;
6805 if (ref1->tag != ref2->tag)
6806 return ref2->tag - ref1->tag;
6807 if (ref1->ltag != ref2->ltag)
6808 return ref2->ltag - ref2->ltag;
6809 if (ref1->head != ref2->head)
6810 return ref2->head - ref1->head;
6811 if (ref1->tracked != ref2->tracked)
6812 return ref2->tracked - ref1->tracked;
6813 if (ref1->remote != ref2->remote)
6814 return ref2->remote - ref1->remote;
6815 return strcmp(ref1->name, ref2->name);
6819 foreach_ref(bool (*visitor)(void *data, struct ref *ref), void *data)
6823 for (i = 0; i < refs_size; i++)
6824 if (!visitor(data, &refs[i]))
6828 static struct ref **
6829 get_refs(const char *id)
6831 struct ref **ref_list = NULL;
6832 size_t ref_list_size = 0;
6835 for (i = 0; i < id_refs_size; i++)
6836 if (!strcmp(id, id_refs[i][0]->id))
6839 if (!realloc_refs_lists(&id_refs, id_refs_size, 1))
6842 for (i = 0; i < refs_size; i++) {
6843 if (strcmp(id, refs[i].id))
6846 if (!realloc_refs_list(&ref_list, ref_list_size, 1))
6849 ref_list[ref_list_size] = &refs[i];
6850 /* XXX: The properties of the commit chains ensures that we can
6851 * safely modify the shared ref. The repo references will
6852 * always be similar for the same id. */
6853 ref_list[ref_list_size]->next = 1;
6858 qsort(ref_list, ref_list_size, sizeof(*ref_list), compare_refs);
6859 ref_list[ref_list_size - 1]->next = 0;
6860 id_refs[id_refs_size++] = ref_list;
6867 read_ref(char *id, size_t idlen, char *name, size_t namelen)
6872 bool remote = FALSE;
6873 bool tracked = FALSE;
6874 bool check_replace = FALSE;
6877 if (!prefixcmp(name, "refs/tags/")) {
6878 if (!suffixcmp(name, namelen, "^{}")) {
6881 if (refs_size > 0 && refs[refs_size - 1].ltag == TRUE)
6882 check_replace = TRUE;
6888 namelen -= STRING_SIZE("refs/tags/");
6889 name += STRING_SIZE("refs/tags/");
6891 } else if (!prefixcmp(name, "refs/remotes/")) {
6893 namelen -= STRING_SIZE("refs/remotes/");
6894 name += STRING_SIZE("refs/remotes/");
6895 tracked = !strcmp(opt_remote, name);
6897 } else if (!prefixcmp(name, "refs/heads/")) {
6898 namelen -= STRING_SIZE("refs/heads/");
6899 name += STRING_SIZE("refs/heads/");
6900 head = !strncmp(opt_head, name, namelen);
6902 } else if (!strcmp(name, "HEAD")) {
6903 string_ncopy(opt_head_rev, id, idlen);
6907 if (check_replace && !strcmp(name, refs[refs_size - 1].name)) {
6908 /* it's an annotated tag, replace the previous SHA1 with the
6909 * resolved commit id; relies on the fact git-ls-remote lists
6910 * the commit id of an annotated tag right before the commit id
6912 refs[refs_size - 1].ltag = ltag;
6913 string_copy_rev(refs[refs_size - 1].id, id);
6918 if (!realloc_refs(&refs, refs_size, 1))
6921 ref = &refs[refs_size++];
6922 ref->name = malloc(namelen + 1);
6926 strncpy(ref->name, name, namelen);
6927 ref->name[namelen] = 0;
6931 ref->remote = remote;
6932 ref->tracked = tracked;
6933 string_copy_rev(ref->id, id);
6941 const char *head_argv[] = {
6942 "git", "symbolic-ref", "HEAD", NULL
6944 static const char *ls_remote_argv[SIZEOF_ARG] = {
6945 "git", "ls-remote", opt_git_dir, NULL
6947 static bool init = FALSE;
6950 argv_from_env(ls_remote_argv, "TIG_LS_REMOTE");
6957 if (run_io_buf(head_argv, opt_head, sizeof(opt_head)) &&
6958 !prefixcmp(opt_head, "refs/heads/")) {
6959 char *offset = opt_head + STRING_SIZE("refs/heads/");
6961 memmove(opt_head, offset, strlen(offset) + 1);
6964 while (refs_size > 0)
6965 free(refs[--refs_size].name);
6966 while (id_refs_size > 0)
6967 free(id_refs[--id_refs_size]);
6969 return run_io_load(ls_remote_argv, "\t", read_ref);
6973 set_remote_branch(const char *name, const char *value, size_t valuelen)
6975 if (!strcmp(name, ".remote")) {
6976 string_ncopy(opt_remote, value, valuelen);
6978 } else if (*opt_remote && !strcmp(name, ".merge")) {
6979 size_t from = strlen(opt_remote);
6981 if (!prefixcmp(value, "refs/heads/"))
6982 value += STRING_SIZE("refs/heads/");
6984 if (!string_format_from(opt_remote, &from, "/%s", value))
6990 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
6992 const char *argv[SIZEOF_ARG] = { name, "=" };
6993 int argc = 1 + (cmd == option_set_command);
6996 if (!argv_from_string(argv, &argc, value))
6997 config_msg = "Too many option arguments";
6999 error = cmd(argc, argv);
7002 warn("Option 'tig.%s': %s", name, config_msg);
7006 set_environment_variable(const char *name, const char *value)
7008 size_t len = strlen(name) + 1 + strlen(value) + 1;
7009 char *env = malloc(len);
7012 string_nformat(env, len, NULL, "%s=%s", name, value) &&
7020 set_work_tree(const char *value)
7022 char cwd[SIZEOF_STR];
7024 if (!getcwd(cwd, sizeof(cwd)))
7025 die("Failed to get cwd path: %s", strerror(errno));
7026 if (chdir(opt_git_dir) < 0)
7027 die("Failed to chdir(%s): %s", strerror(errno));
7028 if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7029 die("Failed to get git path: %s", strerror(errno));
7031 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7032 if (chdir(value) < 0)
7033 die("Failed to chdir(%s): %s", value, strerror(errno));
7034 if (!getcwd(cwd, sizeof(cwd)))
7035 die("Failed to get cwd path: %s", strerror(errno));
7036 if (!set_environment_variable("GIT_WORK_TREE", cwd))
7037 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7038 if (!set_environment_variable("GIT_DIR", opt_git_dir))
7039 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7040 opt_is_inside_work_tree = TRUE;
7044 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
7046 if (!strcmp(name, "i18n.commitencoding"))
7047 string_ncopy(opt_encoding, value, valuelen);
7049 else if (!strcmp(name, "core.editor"))
7050 string_ncopy(opt_editor, value, valuelen);
7052 else if (!strcmp(name, "core.worktree"))
7053 set_work_tree(value);
7055 else if (!prefixcmp(name, "tig.color."))
7056 set_repo_config_option(name + 10, value, option_color_command);
7058 else if (!prefixcmp(name, "tig.bind."))
7059 set_repo_config_option(name + 9, value, option_bind_command);
7061 else if (!prefixcmp(name, "tig."))
7062 set_repo_config_option(name + 4, value, option_set_command);
7064 else if (*opt_head && !prefixcmp(name, "branch.") &&
7065 !strncmp(name + 7, opt_head, strlen(opt_head)))
7066 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7072 load_git_config(void)
7074 const char *config_list_argv[] = { "git", GIT_CONFIG, "--list", NULL };
7076 return run_io_load(config_list_argv, "=", read_repo_config_option);
7080 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
7082 if (!opt_git_dir[0]) {
7083 string_ncopy(opt_git_dir, name, namelen);
7085 } else if (opt_is_inside_work_tree == -1) {
7086 /* This can be 3 different values depending on the
7087 * version of git being used. If git-rev-parse does not
7088 * understand --is-inside-work-tree it will simply echo
7089 * the option else either "true" or "false" is printed.
7090 * Default to true for the unknown case. */
7091 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7093 } else if (*name == '.') {
7094 string_ncopy(opt_cdup, name, namelen);
7097 string_ncopy(opt_prefix, name, namelen);
7104 load_repo_info(void)
7106 const char *rev_parse_argv[] = {
7107 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7108 "--show-cdup", "--show-prefix", NULL
7111 return run_io_load(rev_parse_argv, "=", read_repo_info);
7119 static const char usage[] =
7120 "tig " TIG_VERSION " (" __DATE__ ")\n"
7122 "Usage: tig [options] [revs] [--] [paths]\n"
7123 " or: tig show [options] [revs] [--] [paths]\n"
7124 " or: tig blame [rev] path\n"
7126 " or: tig < [git command output]\n"
7129 " -v, --version Show version and exit\n"
7130 " -h, --help Show help message and exit";
7132 static void __NORETURN
7135 /* XXX: Restore tty modes and let the OS cleanup the rest! */
7141 static void __NORETURN
7142 die(const char *err, ...)
7148 va_start(args, err);
7149 fputs("tig: ", stderr);
7150 vfprintf(stderr, err, args);
7151 fputs("\n", stderr);
7158 warn(const char *msg, ...)
7162 va_start(args, msg);
7163 fputs("tig warning: ", stderr);
7164 vfprintf(stderr, msg, args);
7165 fputs("\n", stderr);
7170 parse_options(int argc, const char *argv[])
7172 enum request request = REQ_VIEW_MAIN;
7173 const char *subcommand;
7174 bool seen_dashdash = FALSE;
7175 /* XXX: This is vulnerable to the user overriding options
7176 * required for the main view parser. */
7177 const char *custom_argv[SIZEOF_ARG] = {
7178 "git", "log", "--no-color", "--pretty=raw", "--parents",
7179 "--topo-order", NULL
7183 if (!isatty(STDIN_FILENO)) {
7184 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
7185 return REQ_VIEW_PAGER;
7191 subcommand = argv[1];
7192 if (!strcmp(subcommand, "status")) {
7194 warn("ignoring arguments after `%s'", subcommand);
7195 return REQ_VIEW_STATUS;
7197 } else if (!strcmp(subcommand, "blame")) {
7198 if (argc <= 2 || argc > 4)
7199 die("invalid number of options to blame\n\n%s", usage);
7203 string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7207 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7208 return REQ_VIEW_BLAME;
7210 } else if (!strcmp(subcommand, "show")) {
7211 request = REQ_VIEW_DIFF;
7218 custom_argv[1] = subcommand;
7222 for (i = 1 + !!subcommand; i < argc; i++) {
7223 const char *opt = argv[i];
7225 if (seen_dashdash || !strcmp(opt, "--")) {
7226 seen_dashdash = TRUE;
7228 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7229 printf("tig version %s\n", TIG_VERSION);
7232 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7233 printf("%s\n", usage);
7237 custom_argv[j++] = opt;
7238 if (j >= ARRAY_SIZE(custom_argv))
7239 die("command too long");
7242 if (!prepare_update(VIEW(request), custom_argv, NULL, FORMAT_NONE))
7243 die("Failed to format arguments");
7249 main(int argc, const char *argv[])
7251 enum request request = parse_options(argc, argv);
7255 signal(SIGINT, quit);
7256 signal(SIGPIPE, SIG_IGN);
7258 if (setlocale(LC_ALL, "")) {
7259 char *codeset = nl_langinfo(CODESET);
7261 string_ncopy(opt_codeset, codeset, strlen(codeset));
7264 if (load_repo_info() == ERR)
7265 die("Failed to load repo info.");
7267 if (load_options() == ERR)
7268 die("Failed to load user config.");
7270 if (load_git_config() == ERR)
7271 die("Failed to load repo config.");
7273 /* Require a git repository unless when running in pager mode. */
7274 if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7275 die("Not a git repository");
7277 if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
7280 if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
7281 opt_iconv = iconv_open(opt_codeset, opt_encoding);
7282 if (opt_iconv == ICONV_NONE)
7283 die("Failed to initialize character set conversion");
7286 if (load_refs() == ERR)
7287 die("Failed to load refs.");
7289 foreach_view (view, i)
7290 argv_from_env(view->ops->argv, view->cmd_env);
7294 if (request != REQ_NONE)
7295 open_view(NULL, request, OPEN_PREPARED);
7296 request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
7298 while (view_driver(display[current_view], request)) {
7299 int key = get_input(0);
7301 view = display[current_view];
7302 request = get_keybinding(view->keymap, key);
7304 /* Some low-level request handling. This keeps access to
7305 * status_win restricted. */
7309 char *cmd = read_prompt(":");
7311 if (cmd && isdigit(*cmd)) {
7312 int lineno = view->lineno + 1;
7314 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7315 select_view_line(view, lineno - 1);
7318 report("Unable to parse '%s' as a line number", cmd);
7322 struct view *next = VIEW(REQ_VIEW_PAGER);
7323 const char *argv[SIZEOF_ARG] = { "git" };
7326 /* When running random commands, initially show the
7327 * command in the title. However, it maybe later be
7328 * overwritten if a commit line is selected. */
7329 string_ncopy(next->ref, cmd, strlen(cmd));
7331 if (!argv_from_string(argv, &argc, cmd)) {
7332 report("Too many arguments");
7333 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
7334 report("Failed to format command");
7336 open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7344 case REQ_SEARCH_BACK:
7346 const char *prompt = request == REQ_SEARCH ? "/" : "?";
7347 char *search = read_prompt(prompt);
7350 string_ncopy(opt_search, search, strlen(search));
7351 else if (*opt_search)
7352 request = request == REQ_SEARCH ?