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 size_t utf8_length(const char **string, size_t col, int *width, size_t max_width, int *trimmed, bool reserve);
73 #define ABS(x) ((x) >= 0 ? (x) : -(x))
74 #define MIN(x, y) ((x) < (y) ? (x) : (y))
76 #define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
77 #define STRING_SIZE(x) (sizeof(x) - 1)
79 #define SIZEOF_STR 1024 /* Default string size. */
80 #define SIZEOF_REF 256 /* Size of symbolic or SHA1 ID. */
81 #define SIZEOF_REV 41 /* Holds a SHA-1 and an ending NUL. */
82 #define SIZEOF_ARG 32 /* Default argument array size. */
86 #define REVGRAPH_INIT 'I'
87 #define REVGRAPH_MERGE 'M'
88 #define REVGRAPH_BRANCH '+'
89 #define REVGRAPH_COMMIT '*'
90 #define REVGRAPH_BOUND '^'
92 #define SIZEOF_REVGRAPH 19 /* Size of revision ancestry graphics. */
94 /* This color name can be used to refer to the default term colors. */
95 #define COLOR_DEFAULT (-1)
97 #define ICONV_NONE ((iconv_t) -1)
99 #define ICONV_CONST /* nothing */
102 /* The format and size of the date column in the main view. */
103 #define DATE_FORMAT "%Y-%m-%d %H:%M"
104 #define DATE_COLS STRING_SIZE("2006-04-29 14:21 ")
106 #define AUTHOR_COLS 20
109 /* The default interval between line numbers. */
110 #define NUMBER_INTERVAL 5
114 #define SCALE_SPLIT_VIEW(height) ((height) * 2 / 3)
116 #define NULL_ID "0000000000000000000000000000000000000000"
118 #define S_ISGITLINK(mode) (((mode) & S_IFMT) == 0160000)
121 #define GIT_CONFIG "config"
124 /* Some ASCII-shorthands fitted into the ncurses namespace. */
126 #define KEY_RETURN '\r'
131 char id[SIZEOF_REV]; /* Commit SHA1 ID */
132 unsigned int head:1; /* Is it the current HEAD? */
133 unsigned int tag:1; /* Is it a tag? */
134 unsigned int ltag:1; /* If so, is the tag local? */
135 unsigned int remote:1; /* Is it a remote ref? */
136 unsigned int tracked:1; /* Is it the remote for the current HEAD? */
137 char name[1]; /* Ref name; tag or head names are shortened. */
141 char id[SIZEOF_REV]; /* Commit SHA1 ID */
142 size_t size; /* Number of refs. */
143 struct ref **refs; /* References for this ID. */
146 static struct ref_list *get_ref_list(const char *id);
147 static void foreach_ref(bool (*visitor)(void *data, struct ref *ref), void *data);
148 static int load_refs(void);
151 FORMAT_ALL, /* Perform replacement in all arguments. */
152 FORMAT_DASH, /* Perform replacement up until "--". */
153 FORMAT_NONE /* No replacement should be performed. */
156 static bool format_argv(const char *dst[], const char *src[], enum format_flags flags);
165 typedef enum input_status (*input_handler)(void *data, char *buf, int c);
167 static char *prompt_input(const char *prompt, input_handler handler, void *data);
168 static bool prompt_yesno(const char *prompt);
171 * Allocation helpers ... Entering macro hell to never be seen again.
174 #define DEFINE_ALLOCATOR(name, type, chunk_size) \
176 name(type **mem, size_t size, size_t increase) \
178 size_t num_chunks = (size + chunk_size - 1) / chunk_size; \
179 size_t num_chunks_new = (size + increase + chunk_size - 1) / chunk_size;\
182 if (mem == NULL || num_chunks != num_chunks_new) { \
183 tmp = realloc(tmp, num_chunks_new * chunk_size * sizeof(type)); \
196 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
198 if (srclen > dstlen - 1)
201 strncpy(dst, src, srclen);
205 /* Shorthands for safely copying into a fixed buffer. */
207 #define string_copy(dst, src) \
208 string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
210 #define string_ncopy(dst, src, srclen) \
211 string_ncopy_do(dst, sizeof(dst), src, srclen)
213 #define string_copy_rev(dst, src) \
214 string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
216 #define string_add(dst, from, src) \
217 string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
220 string_expand(char *dst, size_t dstlen, const char *src, int tabsize)
224 for (size = pos = 0; size < dstlen - 1 && src[pos]; pos++) {
225 if (src[pos] == '\t') {
226 size_t expanded = tabsize - (size % tabsize);
228 if (expanded + size >= dstlen - 1)
229 expanded = dstlen - size - 1;
230 memcpy(dst + size, " ", expanded);
233 dst[size++] = src[pos];
241 chomp_string(char *name)
245 while (isspace(*name))
248 namelen = strlen(name) - 1;
249 while (namelen > 0 && isspace(name[namelen]))
256 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
259 size_t pos = bufpos ? *bufpos : 0;
262 pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
268 return pos >= bufsize ? FALSE : TRUE;
271 #define string_format(buf, fmt, args...) \
272 string_nformat(buf, sizeof(buf), NULL, fmt, args)
274 #define string_format_from(buf, from, fmt, args...) \
275 string_nformat(buf, sizeof(buf), from, fmt, args)
278 string_enum_compare(const char *str1, const char *str2, int len)
282 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
284 /* Diff-Header == DIFF_HEADER */
285 for (i = 0; i < len; i++) {
286 if (toupper(str1[i]) == toupper(str2[i]))
289 if (string_enum_sep(str1[i]) &&
290 string_enum_sep(str2[i]))
293 return str1[i] - str2[i];
305 #define ENUM_MAP(name, value) { name, STRING_SIZE(name), value }
308 map_enum_do(const struct enum_map *map, size_t map_size, int *value, const char *name)
310 size_t namelen = strlen(name);
313 for (i = 0; i < map_size; i++)
314 if (namelen == map[i].namelen &&
315 !string_enum_compare(name, map[i].name, namelen)) {
316 *value = map[i].value;
323 #define map_enum(attr, map, name) \
324 map_enum_do(map, ARRAY_SIZE(map), attr, name)
326 #define prefixcmp(str1, str2) \
327 strncmp(str1, str2, STRING_SIZE(str2))
330 suffixcmp(const char *str, int slen, const char *suffix)
332 size_t len = slen >= 0 ? slen : strlen(str);
333 size_t suffixlen = strlen(suffix);
335 return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
340 mkdate(const time_t *time)
342 static char buf[DATE_COLS + 1];
346 return strftime(buf, sizeof(buf), DATE_FORMAT, &tm) ? buf : NULL;
351 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
355 while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
356 bool advance = cmd[valuelen] != 0;
359 argv[(*argc)++] = chomp_string(cmd);
360 cmd = chomp_string(cmd + valuelen + advance);
363 if (*argc < SIZEOF_ARG)
365 return *argc < SIZEOF_ARG;
369 argv_from_env(const char **argv, const char *name)
371 char *env = argv ? getenv(name) : NULL;
376 if (env && !argv_from_string(argv, &argc, env))
377 die("Too many arguments in the `%s` environment variable", name);
382 * Executing external commands.
386 IO_FD, /* File descriptor based IO. */
387 IO_BG, /* Execute command in the background. */
388 IO_FG, /* Execute command with same std{in,out,err}. */
389 IO_RD, /* Read only fork+exec IO. */
390 IO_WR, /* Write only fork+exec IO. */
391 IO_AP, /* Append fork+exec output to file. */
395 enum io_type type; /* The requested type of pipe. */
396 const char *dir; /* Directory from which to execute. */
397 pid_t pid; /* Pipe for reading or writing. */
398 int pipe; /* Pipe end for reading or writing. */
399 int error; /* Error status. */
400 const char *argv[SIZEOF_ARG]; /* Shell command arguments. */
401 char *buf; /* Read buffer. */
402 size_t bufalloc; /* Allocated buffer size. */
403 size_t bufsize; /* Buffer content size. */
404 char *bufpos; /* Current buffer position. */
405 unsigned int eof:1; /* Has end of file been reached. */
409 reset_io(struct io *io)
413 io->buf = io->bufpos = NULL;
414 io->bufalloc = io->bufsize = 0;
420 init_io(struct io *io, const char *dir, enum io_type type)
428 init_io_rd(struct io *io, const char *argv[], const char *dir,
429 enum format_flags flags)
431 init_io(io, dir, IO_RD);
432 return format_argv(io->argv, argv, flags);
436 io_open(struct io *io, const char *name)
438 init_io(io, NULL, IO_FD);
439 io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
442 return io->pipe != -1;
446 kill_io(struct io *io)
448 return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
452 done_io(struct io *io)
463 pid_t waiting = waitpid(pid, &status, 0);
468 report("waitpid failed (%s)", strerror(errno));
472 return waiting == pid &&
473 !WIFSIGNALED(status) &&
475 !WEXITSTATUS(status);
482 start_io(struct io *io)
484 int pipefds[2] = { -1, -1 };
486 if (io->type == IO_FD)
489 if ((io->type == IO_RD || io->type == IO_WR) &&
492 else if (io->type == IO_AP)
493 pipefds[1] = io->pipe;
495 if ((io->pid = fork())) {
496 if (pipefds[!(io->type == IO_WR)] != -1)
497 close(pipefds[!(io->type == IO_WR)]);
499 io->pipe = pipefds[!!(io->type == IO_WR)];
504 if (io->type != IO_FG) {
505 int devnull = open("/dev/null", O_RDWR);
506 int readfd = io->type == IO_WR ? pipefds[0] : devnull;
507 int writefd = (io->type == IO_RD || io->type == IO_AP)
508 ? pipefds[1] : devnull;
510 dup2(readfd, STDIN_FILENO);
511 dup2(writefd, STDOUT_FILENO);
512 dup2(devnull, STDERR_FILENO);
515 if (pipefds[0] != -1)
517 if (pipefds[1] != -1)
521 if (io->dir && *io->dir && chdir(io->dir) == -1)
522 die("Failed to change directory: %s", strerror(errno));
524 execvp(io->argv[0], (char *const*) io->argv);
525 die("Failed to execute program: %s", strerror(errno));
528 if (pipefds[!!(io->type == IO_WR)] != -1)
529 close(pipefds[!!(io->type == IO_WR)]);
534 run_io(struct io *io, const char **argv, const char *dir, enum io_type type)
536 init_io(io, dir, type);
537 if (!format_argv(io->argv, argv, FORMAT_NONE))
543 run_io_do(struct io *io)
545 return start_io(io) && done_io(io);
549 run_io_bg(const char **argv)
553 init_io(&io, NULL, IO_BG);
554 if (!format_argv(io.argv, argv, FORMAT_NONE))
556 return run_io_do(&io);
560 run_io_fg(const char **argv, const char *dir)
564 init_io(&io, dir, IO_FG);
565 if (!format_argv(io.argv, argv, FORMAT_NONE))
567 return run_io_do(&io);
571 run_io_append(const char **argv, enum format_flags flags, int fd)
575 init_io(&io, NULL, IO_AP);
577 if (format_argv(io.argv, argv, flags))
578 return run_io_do(&io);
584 run_io_rd(struct io *io, const char **argv, enum format_flags flags)
586 return init_io_rd(io, argv, NULL, flags) && start_io(io);
590 io_eof(struct io *io)
596 io_error(struct io *io)
602 io_strerror(struct io *io)
604 return strerror(io->error);
608 io_can_read(struct io *io)
610 struct timeval tv = { 0, 500 };
614 FD_SET(io->pipe, &fds);
616 return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
620 io_read(struct io *io, void *buf, size_t bufsize)
623 ssize_t readsize = read(io->pipe, buf, bufsize);
625 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
627 else if (readsize == -1)
629 else if (readsize == 0)
635 DEFINE_ALLOCATOR(realloc_io_buf, char, BUFSIZ)
638 io_get(struct io *io, int c, bool can_read)
644 if (io->bufsize > 0) {
645 eol = memchr(io->bufpos, c, io->bufsize);
647 char *line = io->bufpos;
650 io->bufpos = eol + 1;
651 io->bufsize -= io->bufpos - line;
658 io->bufpos[io->bufsize] = 0;
668 if (io->bufsize > 0 && io->bufpos > io->buf)
669 memmove(io->buf, io->bufpos, io->bufsize);
671 if (io->bufalloc == io->bufsize) {
672 if (!realloc_io_buf(&io->buf, io->bufalloc, BUFSIZ))
674 io->bufalloc += BUFSIZ;
677 io->bufpos = io->buf;
678 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
681 io->bufsize += readsize;
686 io_write(struct io *io, const void *buf, size_t bufsize)
690 while (!io_error(io) && written < bufsize) {
693 size = write(io->pipe, buf + written, bufsize - written);
694 if (size < 0 && (errno == EAGAIN || errno == EINTR))
702 return written == bufsize;
706 io_read_buf(struct io *io, char buf[], size_t bufsize)
708 char *result = io_get(io, '\n', TRUE);
711 result = chomp_string(result);
712 string_ncopy_do(buf, bufsize, result, strlen(result));
715 return done_io(io) && result;
719 run_io_buf(const char **argv, char buf[], size_t bufsize)
723 return run_io_rd(&io, argv, FORMAT_NONE) && io_read_buf(&io, buf, bufsize);
727 io_load(struct io *io, const char *separators,
728 int (*read_property)(char *, size_t, char *, size_t))
736 while (state == OK && (name = io_get(io, '\n', TRUE))) {
741 name = chomp_string(name);
742 namelen = strcspn(name, separators);
746 value = chomp_string(name + namelen + 1);
747 valuelen = strlen(value);
754 state = read_property(name, namelen, value, valuelen);
757 if (state != ERR && io_error(io))
765 run_io_load(const char **argv, const char *separators,
766 int (*read_property)(char *, size_t, char *, size_t))
770 return init_io_rd(&io, argv, NULL, FORMAT_NONE)
771 ? io_load(&io, separators, read_property) : ERR;
780 /* XXX: Keep the view request first and in sync with views[]. */ \
781 REQ_GROUP("View switching") \
782 REQ_(VIEW_MAIN, "Show main view"), \
783 REQ_(VIEW_DIFF, "Show diff view"), \
784 REQ_(VIEW_LOG, "Show log view"), \
785 REQ_(VIEW_TREE, "Show tree view"), \
786 REQ_(VIEW_BLOB, "Show blob view"), \
787 REQ_(VIEW_BLAME, "Show blame view"), \
788 REQ_(VIEW_BRANCH, "Show branch view"), \
789 REQ_(VIEW_HELP, "Show help page"), \
790 REQ_(VIEW_PAGER, "Show pager view"), \
791 REQ_(VIEW_STATUS, "Show status view"), \
792 REQ_(VIEW_STAGE, "Show stage view"), \
794 REQ_GROUP("View manipulation") \
795 REQ_(ENTER, "Enter current line and scroll"), \
796 REQ_(NEXT, "Move to next"), \
797 REQ_(PREVIOUS, "Move to previous"), \
798 REQ_(PARENT, "Move to parent"), \
799 REQ_(VIEW_NEXT, "Move focus to next view"), \
800 REQ_(REFRESH, "Reload and refresh"), \
801 REQ_(MAXIMIZE, "Maximize the current view"), \
802 REQ_(VIEW_CLOSE, "Close the current view"), \
803 REQ_(QUIT, "Close all views and quit"), \
805 REQ_GROUP("View specific requests") \
806 REQ_(STATUS_UPDATE, "Update file status"), \
807 REQ_(STATUS_REVERT, "Revert file changes"), \
808 REQ_(STATUS_MERGE, "Merge file using external tool"), \
809 REQ_(STAGE_NEXT, "Find next chunk to stage"), \
811 REQ_GROUP("Cursor navigation") \
812 REQ_(MOVE_UP, "Move cursor one line up"), \
813 REQ_(MOVE_DOWN, "Move cursor one line down"), \
814 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
815 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
816 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
817 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
819 REQ_GROUP("Scrolling") \
820 REQ_(SCROLL_LEFT, "Scroll two columns left"), \
821 REQ_(SCROLL_RIGHT, "Scroll two columns right"), \
822 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
823 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
824 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
825 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
827 REQ_GROUP("Searching") \
828 REQ_(SEARCH, "Search the view"), \
829 REQ_(SEARCH_BACK, "Search backwards in the view"), \
830 REQ_(FIND_NEXT, "Find next search match"), \
831 REQ_(FIND_PREV, "Find previous search match"), \
833 REQ_GROUP("Option manipulation") \
834 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
835 REQ_(TOGGLE_DATE, "Toggle date display"), \
836 REQ_(TOGGLE_AUTHOR, "Toggle author display"), \
837 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
838 REQ_(TOGGLE_REFS, "Toggle reference display (tags/branches)"), \
839 REQ_(TOGGLE_SORT_ORDER, "Toggle ascending/descending sort order"), \
840 REQ_(TOGGLE_SORT_FIELD, "Toggle field to sort by"), \
843 REQ_(PROMPT, "Bring up the prompt"), \
844 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
845 REQ_(SHOW_VERSION, "Show version information"), \
846 REQ_(STOP_LOADING, "Stop all loading views"), \
847 REQ_(EDIT, "Open in editor"), \
848 REQ_(NONE, "Do nothing")
851 /* User action requests. */
853 #define REQ_GROUP(help)
854 #define REQ_(req, help) REQ_##req
856 /* Offset all requests to avoid conflicts with ncurses getch values. */
857 REQ_OFFSET = KEY_MAX + 1,
864 struct request_info {
865 enum request request;
871 static const struct request_info req_info[] = {
872 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
873 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
880 get_request(const char *name)
882 int namelen = strlen(name);
885 for (i = 0; i < ARRAY_SIZE(req_info); i++)
886 if (req_info[i].namelen == namelen &&
887 !string_enum_compare(req_info[i].name, name, namelen))
888 return req_info[i].request;
898 /* Option and state variables. */
899 static bool opt_date = TRUE;
900 static bool opt_author = TRUE;
901 static bool opt_line_number = FALSE;
902 static bool opt_line_graphics = TRUE;
903 static bool opt_rev_graph = FALSE;
904 static bool opt_show_refs = TRUE;
905 static int opt_num_interval = NUMBER_INTERVAL;
906 static double opt_hscroll = 0.50;
907 static int opt_tab_size = TAB_SIZE;
908 static int opt_author_cols = AUTHOR_COLS-1;
909 static char opt_path[SIZEOF_STR] = "";
910 static char opt_file[SIZEOF_STR] = "";
911 static char opt_ref[SIZEOF_REF] = "";
912 static char opt_head[SIZEOF_REF] = "";
913 static char opt_head_rev[SIZEOF_REV] = "";
914 static char opt_remote[SIZEOF_REF] = "";
915 static char opt_encoding[20] = "UTF-8";
916 static bool opt_utf8 = TRUE;
917 static char opt_codeset[20] = "UTF-8";
918 static iconv_t opt_iconv = ICONV_NONE;
919 static char opt_search[SIZEOF_STR] = "";
920 static char opt_cdup[SIZEOF_STR] = "";
921 static char opt_prefix[SIZEOF_STR] = "";
922 static char opt_git_dir[SIZEOF_STR] = "";
923 static signed char opt_is_inside_work_tree = -1; /* set to TRUE or FALSE */
924 static char opt_editor[SIZEOF_STR] = "";
925 static FILE *opt_tty = NULL;
927 #define is_initial_commit() (!*opt_head_rev)
928 #define is_head_commit(rev) (!strcmp((rev), "HEAD") || !strcmp(opt_head_rev, (rev)))
932 * Line-oriented content detection.
936 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
937 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
938 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
939 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
940 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
941 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
942 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
943 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
944 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
945 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
946 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
947 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
948 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
949 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
950 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
951 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
952 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
953 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
954 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
955 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
956 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
957 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
958 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
959 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
960 LINE(AUTHOR, "author ", COLOR_GREEN, COLOR_DEFAULT, 0), \
961 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
962 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
963 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
964 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
965 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
966 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
967 LINE(DELIMITER, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
968 LINE(DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
969 LINE(MODE, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
970 LINE(LINE_NUMBER, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
971 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
972 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
973 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
974 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
975 LINE(MAIN_LOCAL_TAG,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
976 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
977 LINE(MAIN_TRACKED, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
978 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
979 LINE(MAIN_HEAD, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
980 LINE(MAIN_REVGRAPH,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
981 LINE(TREE_HEAD, "", COLOR_DEFAULT, COLOR_DEFAULT, A_BOLD), \
982 LINE(TREE_DIR, "", COLOR_YELLOW, COLOR_DEFAULT, A_NORMAL), \
983 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
984 LINE(STAT_HEAD, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
985 LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
986 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
987 LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
988 LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
989 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
990 LINE(BLAME_ID, "", COLOR_MAGENTA, COLOR_DEFAULT, 0)
993 #define LINE(type, line, fg, bg, attr) \
1001 const char *name; /* Option name. */
1002 int namelen; /* Size of option name. */
1003 const char *line; /* The start of line to match. */
1004 int linelen; /* Size of string to match. */
1005 int fg, bg, attr; /* Color and text attributes for the lines. */
1008 static struct line_info line_info[] = {
1009 #define LINE(type, line, fg, bg, attr) \
1010 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1015 static enum line_type
1016 get_line_type(const char *line)
1018 int linelen = strlen(line);
1019 enum line_type type;
1021 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1022 /* Case insensitive search matches Signed-off-by lines better. */
1023 if (linelen >= line_info[type].linelen &&
1024 !strncasecmp(line_info[type].line, line, line_info[type].linelen))
1027 return LINE_DEFAULT;
1031 get_line_attr(enum line_type type)
1033 assert(type < ARRAY_SIZE(line_info));
1034 return COLOR_PAIR(type) | line_info[type].attr;
1037 static struct line_info *
1038 get_line_info(const char *name)
1040 size_t namelen = strlen(name);
1041 enum line_type type;
1043 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1044 if (namelen == line_info[type].namelen &&
1045 !string_enum_compare(line_info[type].name, name, namelen))
1046 return &line_info[type];
1054 int default_bg = line_info[LINE_DEFAULT].bg;
1055 int default_fg = line_info[LINE_DEFAULT].fg;
1056 enum line_type type;
1060 if (assume_default_colors(default_fg, default_bg) == ERR) {
1061 default_bg = COLOR_BLACK;
1062 default_fg = COLOR_WHITE;
1065 for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1066 struct line_info *info = &line_info[type];
1067 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1068 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1070 init_pair(type, fg, bg);
1075 enum line_type type;
1078 unsigned int selected:1;
1079 unsigned int dirty:1;
1080 unsigned int cleareol:1;
1082 void *data; /* User data */
1092 enum request request;
1095 static const struct keybinding default_keybindings[] = {
1096 /* View switching */
1097 { 'm', REQ_VIEW_MAIN },
1098 { 'd', REQ_VIEW_DIFF },
1099 { 'l', REQ_VIEW_LOG },
1100 { 't', REQ_VIEW_TREE },
1101 { 'f', REQ_VIEW_BLOB },
1102 { 'B', REQ_VIEW_BLAME },
1103 { 'H', REQ_VIEW_BRANCH },
1104 { 'p', REQ_VIEW_PAGER },
1105 { 'h', REQ_VIEW_HELP },
1106 { 'S', REQ_VIEW_STATUS },
1107 { 'c', REQ_VIEW_STAGE },
1109 /* View manipulation */
1110 { 'q', REQ_VIEW_CLOSE },
1111 { KEY_TAB, REQ_VIEW_NEXT },
1112 { KEY_RETURN, REQ_ENTER },
1113 { KEY_UP, REQ_PREVIOUS },
1114 { KEY_DOWN, REQ_NEXT },
1115 { 'R', REQ_REFRESH },
1116 { KEY_F(5), REQ_REFRESH },
1117 { 'O', REQ_MAXIMIZE },
1119 /* Cursor navigation */
1120 { 'k', REQ_MOVE_UP },
1121 { 'j', REQ_MOVE_DOWN },
1122 { KEY_HOME, REQ_MOVE_FIRST_LINE },
1123 { KEY_END, REQ_MOVE_LAST_LINE },
1124 { KEY_NPAGE, REQ_MOVE_PAGE_DOWN },
1125 { ' ', REQ_MOVE_PAGE_DOWN },
1126 { KEY_PPAGE, REQ_MOVE_PAGE_UP },
1127 { 'b', REQ_MOVE_PAGE_UP },
1128 { '-', REQ_MOVE_PAGE_UP },
1131 { KEY_LEFT, REQ_SCROLL_LEFT },
1132 { KEY_RIGHT, REQ_SCROLL_RIGHT },
1133 { KEY_IC, REQ_SCROLL_LINE_UP },
1134 { KEY_DC, REQ_SCROLL_LINE_DOWN },
1135 { 'w', REQ_SCROLL_PAGE_UP },
1136 { 's', REQ_SCROLL_PAGE_DOWN },
1139 { '/', REQ_SEARCH },
1140 { '?', REQ_SEARCH_BACK },
1141 { 'n', REQ_FIND_NEXT },
1142 { 'N', REQ_FIND_PREV },
1146 { 'z', REQ_STOP_LOADING },
1147 { 'v', REQ_SHOW_VERSION },
1148 { 'r', REQ_SCREEN_REDRAW },
1149 { '.', REQ_TOGGLE_LINENO },
1150 { 'D', REQ_TOGGLE_DATE },
1151 { 'A', REQ_TOGGLE_AUTHOR },
1152 { 'g', REQ_TOGGLE_REV_GRAPH },
1153 { 'F', REQ_TOGGLE_REFS },
1154 { 'I', REQ_TOGGLE_SORT_ORDER },
1155 { 'i', REQ_TOGGLE_SORT_FIELD },
1156 { ':', REQ_PROMPT },
1157 { 'u', REQ_STATUS_UPDATE },
1158 { '!', REQ_STATUS_REVERT },
1159 { 'M', REQ_STATUS_MERGE },
1160 { '@', REQ_STAGE_NEXT },
1161 { ',', REQ_PARENT },
1165 #define KEYMAP_INFO \
1180 #define KEYMAP_(name) KEYMAP_##name
1185 static const struct enum_map keymap_table[] = {
1186 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1191 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1193 struct keybinding_table {
1194 struct keybinding *data;
1198 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1201 add_keybinding(enum keymap keymap, enum request request, int key)
1203 struct keybinding_table *table = &keybindings[keymap];
1205 table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1207 die("Failed to allocate keybinding");
1208 table->data[table->size].alias = key;
1209 table->data[table->size++].request = request;
1212 /* Looks for a key binding first in the given map, then in the generic map, and
1213 * lastly in the default keybindings. */
1215 get_keybinding(enum keymap keymap, int key)
1219 for (i = 0; i < keybindings[keymap].size; i++)
1220 if (keybindings[keymap].data[i].alias == key)
1221 return keybindings[keymap].data[i].request;
1223 for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1224 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1225 return keybindings[KEYMAP_GENERIC].data[i].request;
1227 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1228 if (default_keybindings[i].alias == key)
1229 return default_keybindings[i].request;
1231 return (enum request) key;
1240 static const struct key key_table[] = {
1241 { "Enter", KEY_RETURN },
1243 { "Backspace", KEY_BACKSPACE },
1245 { "Escape", KEY_ESC },
1246 { "Left", KEY_LEFT },
1247 { "Right", KEY_RIGHT },
1249 { "Down", KEY_DOWN },
1250 { "Insert", KEY_IC },
1251 { "Delete", KEY_DC },
1253 { "Home", KEY_HOME },
1255 { "PageUp", KEY_PPAGE },
1256 { "PageDown", KEY_NPAGE },
1266 { "F10", KEY_F(10) },
1267 { "F11", KEY_F(11) },
1268 { "F12", KEY_F(12) },
1272 get_key_value(const char *name)
1276 for (i = 0; i < ARRAY_SIZE(key_table); i++)
1277 if (!strcasecmp(key_table[i].name, name))
1278 return key_table[i].value;
1280 if (strlen(name) == 1 && isprint(*name))
1287 get_key_name(int key_value)
1289 static char key_char[] = "'X'";
1290 const char *seq = NULL;
1293 for (key = 0; key < ARRAY_SIZE(key_table); key++)
1294 if (key_table[key].value == key_value)
1295 seq = key_table[key].name;
1299 isprint(key_value)) {
1300 key_char[1] = (char) key_value;
1304 return seq ? seq : "(no key)";
1308 get_key(enum request request)
1310 static char buf[BUFSIZ];
1317 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1318 const struct keybinding *keybinding = &default_keybindings[i];
1320 if (keybinding->request != request)
1323 if (!string_format_from(buf, &pos, "%s%s", sep,
1324 get_key_name(keybinding->alias)))
1325 return "Too many keybindings!";
1332 struct run_request {
1335 const char *argv[SIZEOF_ARG];
1338 static struct run_request *run_request;
1339 static size_t run_requests;
1341 DEFINE_ALLOCATOR(realloc_run_requests, struct run_request, 8)
1344 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1346 struct run_request *req;
1348 if (argc >= ARRAY_SIZE(req->argv) - 1)
1351 if (!realloc_run_requests(&run_request, run_requests, 1))
1354 req = &run_request[run_requests];
1355 req->keymap = keymap;
1357 req->argv[0] = NULL;
1359 if (!format_argv(req->argv, argv, FORMAT_NONE))
1362 return REQ_NONE + ++run_requests;
1365 static struct run_request *
1366 get_run_request(enum request request)
1368 if (request <= REQ_NONE)
1370 return &run_request[request - REQ_NONE - 1];
1374 add_builtin_run_requests(void)
1376 const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1377 const char *commit[] = { "git", "commit", NULL };
1378 const char *gc[] = { "git", "gc", NULL };
1385 { KEYMAP_MAIN, 'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1386 { KEYMAP_STATUS, 'C', ARRAY_SIZE(commit) - 1, commit },
1387 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1391 for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1394 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1395 if (req != REQ_NONE)
1396 add_keybinding(reqs[i].keymap, req, reqs[i].key);
1401 * User config file handling.
1404 static int config_lineno;
1405 static bool config_errors;
1406 static const char *config_msg;
1408 static const struct enum_map color_map[] = {
1409 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1421 static const struct enum_map attr_map[] = {
1422 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1429 ATTR_MAP(UNDERLINE),
1432 #define set_attribute(attr, name) map_enum(attr, attr_map, name)
1434 static int parse_step(double *opt, const char *arg)
1437 if (!strchr(arg, '%'))
1440 /* "Shift down" so 100% and 1 does not conflict. */
1441 *opt = (*opt - 1) / 100;
1444 config_msg = "Step value larger than 100%";
1449 config_msg = "Invalid step value";
1456 parse_int(int *opt, const char *arg, int min, int max)
1458 int value = atoi(arg);
1460 if (min <= value && value <= max) {
1465 config_msg = "Integer value out of bound";
1470 set_color(int *color, const char *name)
1472 if (map_enum(color, color_map, name))
1474 if (!prefixcmp(name, "color"))
1475 return parse_int(color, name + 5, 0, 255) == OK;
1479 /* Wants: object fgcolor bgcolor [attribute] */
1481 option_color_command(int argc, const char *argv[])
1483 struct line_info *info;
1485 if (argc != 3 && argc != 4) {
1486 config_msg = "Wrong number of arguments given to color command";
1490 info = get_line_info(argv[0]);
1492 static const struct enum_map obsolete[] = {
1493 ENUM_MAP("main-delim", LINE_DELIMITER),
1494 ENUM_MAP("main-date", LINE_DATE),
1495 ENUM_MAP("main-author", LINE_AUTHOR),
1499 if (!map_enum(&index, obsolete, argv[0])) {
1500 config_msg = "Unknown color name";
1503 info = &line_info[index];
1506 if (!set_color(&info->fg, argv[1]) ||
1507 !set_color(&info->bg, argv[2])) {
1508 config_msg = "Unknown color";
1512 if (argc == 4 && !set_attribute(&info->attr, argv[3])) {
1513 config_msg = "Unknown attribute";
1520 static int parse_bool(bool *opt, const char *arg)
1522 *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1528 parse_string(char *opt, const char *arg, size_t optsize)
1530 int arglen = strlen(arg);
1535 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1536 config_msg = "Unmatched quotation";
1539 arg += 1; arglen -= 2;
1541 string_ncopy_do(opt, optsize, arg, arglen);
1546 /* Wants: name = value */
1548 option_set_command(int argc, const char *argv[])
1551 config_msg = "Wrong number of arguments given to set command";
1555 if (strcmp(argv[1], "=")) {
1556 config_msg = "No value assigned";
1560 if (!strcmp(argv[0], "show-author"))
1561 return parse_bool(&opt_author, argv[2]);
1563 if (!strcmp(argv[0], "show-date"))
1564 return parse_bool(&opt_date, argv[2]);
1566 if (!strcmp(argv[0], "show-rev-graph"))
1567 return parse_bool(&opt_rev_graph, argv[2]);
1569 if (!strcmp(argv[0], "show-refs"))
1570 return parse_bool(&opt_show_refs, argv[2]);
1572 if (!strcmp(argv[0], "show-line-numbers"))
1573 return parse_bool(&opt_line_number, argv[2]);
1575 if (!strcmp(argv[0], "line-graphics"))
1576 return parse_bool(&opt_line_graphics, argv[2]);
1578 if (!strcmp(argv[0], "line-number-interval"))
1579 return parse_int(&opt_num_interval, argv[2], 1, 1024);
1581 if (!strcmp(argv[0], "author-width"))
1582 return parse_int(&opt_author_cols, argv[2], 0, 1024);
1584 if (!strcmp(argv[0], "horizontal-scroll"))
1585 return parse_step(&opt_hscroll, argv[2]);
1587 if (!strcmp(argv[0], "tab-size"))
1588 return parse_int(&opt_tab_size, argv[2], 1, 1024);
1590 if (!strcmp(argv[0], "commit-encoding"))
1591 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
1593 config_msg = "Unknown variable name";
1597 /* Wants: mode request key */
1599 option_bind_command(int argc, const char *argv[])
1601 enum request request;
1606 config_msg = "Wrong number of arguments given to bind command";
1610 if (set_keymap(&keymap, argv[0]) == ERR) {
1611 config_msg = "Unknown key map";
1615 key = get_key_value(argv[1]);
1617 config_msg = "Unknown key";
1621 request = get_request(argv[2]);
1622 if (request == REQ_NONE) {
1623 static const struct enum_map obsolete[] = {
1624 ENUM_MAP("cherry-pick", REQ_NONE),
1625 ENUM_MAP("screen-resize", REQ_NONE),
1626 ENUM_MAP("tree-parent", REQ_PARENT),
1630 if (map_enum(&alias, obsolete, argv[2])) {
1631 if (alias != REQ_NONE)
1632 add_keybinding(keymap, alias, key);
1633 config_msg = "Obsolete request name";
1637 if (request == REQ_NONE && *argv[2]++ == '!')
1638 request = add_run_request(keymap, key, argc - 2, argv + 2);
1639 if (request == REQ_NONE) {
1640 config_msg = "Unknown request name";
1644 add_keybinding(keymap, request, key);
1650 set_option(const char *opt, char *value)
1652 const char *argv[SIZEOF_ARG];
1655 if (!argv_from_string(argv, &argc, value)) {
1656 config_msg = "Too many option arguments";
1660 if (!strcmp(opt, "color"))
1661 return option_color_command(argc, argv);
1663 if (!strcmp(opt, "set"))
1664 return option_set_command(argc, argv);
1666 if (!strcmp(opt, "bind"))
1667 return option_bind_command(argc, argv);
1669 config_msg = "Unknown option command";
1674 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1679 config_msg = "Internal error";
1681 /* Check for comment markers, since read_properties() will
1682 * only ensure opt and value are split at first " \t". */
1683 optlen = strcspn(opt, "#");
1687 if (opt[optlen] != 0) {
1688 config_msg = "No option value";
1692 /* Look for comment endings in the value. */
1693 size_t len = strcspn(value, "#");
1695 if (len < valuelen) {
1697 value[valuelen] = 0;
1700 status = set_option(opt, value);
1703 if (status == ERR) {
1704 warn("Error on line %d, near '%.*s': %s",
1705 config_lineno, (int) optlen, opt, config_msg);
1706 config_errors = TRUE;
1709 /* Always keep going if errors are encountered. */
1714 load_option_file(const char *path)
1718 /* It's OK that the file doesn't exist. */
1719 if (!io_open(&io, path))
1723 config_errors = FALSE;
1725 if (io_load(&io, " \t", read_option) == ERR ||
1726 config_errors == TRUE)
1727 warn("Errors while loading %s.", path);
1733 const char *home = getenv("HOME");
1734 const char *tigrc_user = getenv("TIGRC_USER");
1735 const char *tigrc_system = getenv("TIGRC_SYSTEM");
1736 char buf[SIZEOF_STR];
1738 add_builtin_run_requests();
1741 tigrc_system = SYSCONFDIR "/tigrc";
1742 load_option_file(tigrc_system);
1745 if (!home || !string_format(buf, "%s/.tigrc", home))
1749 load_option_file(tigrc_user);
1762 /* The display array of active views and the index of the current view. */
1763 static struct view *display[2];
1764 static unsigned int current_view;
1766 #define foreach_displayed_view(view, i) \
1767 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1769 #define displayed_views() (display[1] != NULL ? 2 : 1)
1771 /* Current head and commit ID */
1772 static char ref_blob[SIZEOF_REF] = "";
1773 static char ref_commit[SIZEOF_REF] = "HEAD";
1774 static char ref_head[SIZEOF_REF] = "HEAD";
1777 const char *name; /* View name */
1778 const char *cmd_env; /* Command line set via environment */
1779 const char *id; /* Points to either of ref_{head,commit,blob} */
1781 struct view_ops *ops; /* View operations */
1783 enum keymap keymap; /* What keymap does this view have */
1784 bool git_dir; /* Whether the view requires a git directory. */
1786 char ref[SIZEOF_REF]; /* Hovered commit reference */
1787 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
1789 int height, width; /* The width and height of the main window */
1790 WINDOW *win; /* The main window */
1791 WINDOW *title; /* The title window living below the main window */
1794 unsigned long offset; /* Offset of the window top */
1795 unsigned long yoffset; /* Offset from the window side. */
1796 unsigned long lineno; /* Current line number */
1797 unsigned long p_offset; /* Previous offset of the window top */
1798 unsigned long p_yoffset;/* Previous offset from the window side */
1799 unsigned long p_lineno; /* Previous current line number */
1800 bool p_restore; /* Should the previous position be restored. */
1803 char grep[SIZEOF_STR]; /* Search string */
1804 regex_t *regex; /* Pre-compiled regexp */
1806 /* If non-NULL, points to the view that opened this view. If this view
1807 * is closed tig will switch back to the parent view. */
1808 struct view *parent;
1811 size_t lines; /* Total number of lines */
1812 struct line *line; /* Line index */
1813 unsigned int digits; /* Number of digits in the lines member. */
1816 struct line *curline; /* Line currently being drawn. */
1817 enum line_type curtype; /* Attribute currently used for drawing. */
1818 unsigned long col; /* Column when drawing. */
1819 bool has_scrolled; /* View was scrolled. */
1829 /* What type of content being displayed. Used in the title bar. */
1831 /* Default command arguments. */
1833 /* Open and reads in all view content. */
1834 bool (*open)(struct view *view);
1835 /* Read one line; updates view->line. */
1836 bool (*read)(struct view *view, char *data);
1837 /* Draw one line; @lineno must be < view->height. */
1838 bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
1839 /* Depending on view handle a special requests. */
1840 enum request (*request)(struct view *view, enum request request, struct line *line);
1841 /* Search for regexp in a line. */
1842 bool (*grep)(struct view *view, struct line *line);
1844 void (*select)(struct view *view, struct line *line);
1847 static struct view_ops blame_ops;
1848 static struct view_ops blob_ops;
1849 static struct view_ops diff_ops;
1850 static struct view_ops help_ops;
1851 static struct view_ops log_ops;
1852 static struct view_ops main_ops;
1853 static struct view_ops pager_ops;
1854 static struct view_ops stage_ops;
1855 static struct view_ops status_ops;
1856 static struct view_ops tree_ops;
1857 static struct view_ops branch_ops;
1859 #define VIEW_STR(name, env, ref, ops, map, git) \
1860 { name, #env, ref, ops, map, git }
1862 #define VIEW_(id, name, ops, git, ref) \
1863 VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
1866 static struct view views[] = {
1867 VIEW_(MAIN, "main", &main_ops, TRUE, ref_head),
1868 VIEW_(DIFF, "diff", &diff_ops, TRUE, ref_commit),
1869 VIEW_(LOG, "log", &log_ops, TRUE, ref_head),
1870 VIEW_(TREE, "tree", &tree_ops, TRUE, ref_commit),
1871 VIEW_(BLOB, "blob", &blob_ops, TRUE, ref_blob),
1872 VIEW_(BLAME, "blame", &blame_ops, TRUE, ref_commit),
1873 VIEW_(BRANCH, "branch", &branch_ops, TRUE, ref_head),
1874 VIEW_(HELP, "help", &help_ops, FALSE, ""),
1875 VIEW_(PAGER, "pager", &pager_ops, FALSE, "stdin"),
1876 VIEW_(STATUS, "status", &status_ops, TRUE, ""),
1877 VIEW_(STAGE, "stage", &stage_ops, TRUE, ""),
1880 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
1881 #define VIEW_REQ(view) ((view) - views + REQ_OFFSET + 1)
1883 #define foreach_view(view, i) \
1884 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1886 #define view_is_displayed(view) \
1887 (view == display[0] || view == display[1])
1894 static chtype line_graphics[] = {
1895 /* LINE_GRAPHIC_VLINE: */ '|'
1899 set_view_attr(struct view *view, enum line_type type)
1901 if (!view->curline->selected && view->curtype != type) {
1902 wattrset(view->win, get_line_attr(type));
1903 wchgat(view->win, -1, 0, type, NULL);
1904 view->curtype = type;
1909 draw_chars(struct view *view, enum line_type type, const char *string,
1910 int max_len, bool use_tilde)
1914 int trimmed = FALSE;
1915 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
1921 len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde);
1923 col = len = strlen(string);
1924 if (len > max_len) {
1928 col = len = max_len;
1933 set_view_attr(view, type);
1935 waddnstr(view->win, string, len);
1936 if (trimmed && use_tilde) {
1937 set_view_attr(view, LINE_DELIMITER);
1938 waddch(view->win, '~');
1946 draw_space(struct view *view, enum line_type type, int max, int spaces)
1948 static char space[] = " ";
1951 spaces = MIN(max, spaces);
1953 while (spaces > 0) {
1954 int len = MIN(spaces, sizeof(space) - 1);
1956 col += draw_chars(view, type, space, len, FALSE);
1964 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
1966 view->col += draw_chars(view, type, string, view->width + view->yoffset - view->col, trim);
1967 return view->width + view->yoffset <= view->col;
1971 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
1973 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
1974 int max = view->width + view->yoffset - view->col;
1980 set_view_attr(view, type);
1981 /* Using waddch() instead of waddnstr() ensures that
1982 * they'll be rendered correctly for the cursor line. */
1983 for (i = skip; i < size; i++)
1984 waddch(view->win, graphic[i]);
1987 if (size < max && skip <= size)
1988 waddch(view->win, ' ');
1991 return view->width + view->yoffset <= view->col;
1995 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
1997 int max = MIN(view->width + view->yoffset - view->col, len);
2001 col = draw_chars(view, type, text, max - 1, trim);
2003 col = draw_space(view, type, max - 1, max - 1);
2006 view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
2007 return view->width + view->yoffset <= view->col;
2011 draw_date(struct view *view, time_t *time)
2013 const char *date = mkdate(time);
2015 return draw_field(view, LINE_DATE, date, DATE_COLS, FALSE);
2019 draw_author(struct view *view, const char *author)
2021 bool trim = opt_author_cols == 0 || opt_author_cols > 5 || !author;
2024 static char initials[10];
2027 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@')
2029 memset(initials, 0, sizeof(initials));
2030 for (pos = 0; *author && pos < opt_author_cols - 1; author++, pos++) {
2031 while (is_initial_sep(*author))
2033 strncpy(&initials[pos], author, sizeof(initials) - 1 - pos);
2034 while (*author && !is_initial_sep(author[1]))
2041 return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2045 draw_mode(struct view *view, mode_t mode)
2051 else if (S_ISLNK(mode))
2053 else if (S_ISGITLINK(mode))
2055 else if (S_ISREG(mode) && mode & S_IXUSR)
2057 else if (S_ISREG(mode))
2062 return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2066 draw_lineno(struct view *view, unsigned int lineno)
2069 int digits3 = view->digits < 3 ? 3 : view->digits;
2070 int max = MIN(view->width + view->yoffset - view->col, digits3);
2073 lineno += view->offset + 1;
2074 if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2075 static char fmt[] = "%1ld";
2077 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2078 if (string_format(number, fmt, lineno))
2082 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2084 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2085 return draw_graphic(view, LINE_DEFAULT, &line_graphics[LINE_GRAPHIC_VLINE], 1);
2089 draw_view_line(struct view *view, unsigned int lineno)
2092 bool selected = (view->offset + lineno == view->lineno);
2094 assert(view_is_displayed(view));
2096 if (view->offset + lineno >= view->lines)
2099 line = &view->line[view->offset + lineno];
2101 wmove(view->win, lineno, 0);
2103 wclrtoeol(view->win);
2105 view->curline = line;
2106 view->curtype = LINE_NONE;
2107 line->selected = FALSE;
2108 line->dirty = line->cleareol = 0;
2111 set_view_attr(view, LINE_CURSOR);
2112 line->selected = TRUE;
2113 view->ops->select(view, line);
2116 return view->ops->draw(view, line, lineno);
2120 redraw_view_dirty(struct view *view)
2125 for (lineno = 0; lineno < view->height; lineno++) {
2126 if (view->offset + lineno >= view->lines)
2128 if (!view->line[view->offset + lineno].dirty)
2131 if (!draw_view_line(view, lineno))
2137 wnoutrefresh(view->win);
2141 redraw_view_from(struct view *view, int lineno)
2143 assert(0 <= lineno && lineno < view->height);
2145 for (; lineno < view->height; lineno++) {
2146 if (!draw_view_line(view, lineno))
2150 wnoutrefresh(view->win);
2154 redraw_view(struct view *view)
2157 redraw_view_from(view, 0);
2162 update_view_title(struct view *view)
2164 char buf[SIZEOF_STR];
2165 char state[SIZEOF_STR];
2166 size_t bufpos = 0, statelen = 0;
2168 assert(view_is_displayed(view));
2170 if (view != VIEW(REQ_VIEW_STATUS) && view->lines) {
2171 unsigned int view_lines = view->offset + view->height;
2172 unsigned int lines = view->lines
2173 ? MIN(view_lines, view->lines) * 100 / view->lines
2176 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2185 time_t secs = time(NULL) - view->start_time;
2187 /* Three git seconds are a long time ... */
2189 string_format_from(state, &statelen, " loading %lds", secs);
2192 string_format_from(buf, &bufpos, "[%s]", view->name);
2193 if (*view->ref && bufpos < view->width) {
2194 size_t refsize = strlen(view->ref);
2195 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2197 if (minsize < view->width)
2198 refsize = view->width - minsize + 7;
2199 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2202 if (statelen && bufpos < view->width) {
2203 string_format_from(buf, &bufpos, "%s", state);
2206 if (view == display[current_view])
2207 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2209 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2211 mvwaddnstr(view->title, 0, 0, buf, bufpos);
2212 wclrtoeol(view->title);
2213 wnoutrefresh(view->title);
2217 resize_display(void)
2220 struct view *base = display[0];
2221 struct view *view = display[1] ? display[1] : display[0];
2223 /* Setup window dimensions */
2225 getmaxyx(stdscr, base->height, base->width);
2227 /* Make room for the status window. */
2231 /* Horizontal split. */
2232 view->width = base->width;
2233 view->height = SCALE_SPLIT_VIEW(base->height);
2234 base->height -= view->height;
2236 /* Make room for the title bar. */
2240 /* Make room for the title bar. */
2245 foreach_displayed_view (view, i) {
2247 view->win = newwin(view->height, 0, offset, 0);
2249 die("Failed to create %s view", view->name);
2251 scrollok(view->win, FALSE);
2253 view->title = newwin(1, 0, offset + view->height, 0);
2255 die("Failed to create title window");
2258 wresize(view->win, view->height, view->width);
2259 mvwin(view->win, offset, 0);
2260 mvwin(view->title, offset + view->height, 0);
2263 offset += view->height + 1;
2268 redraw_display(bool clear)
2273 foreach_displayed_view (view, i) {
2277 update_view_title(view);
2282 toggle_view_option(bool *option, const char *help)
2285 redraw_display(FALSE);
2286 report("%sabling %s", *option ? "En" : "Dis", help);
2290 maximize_view(struct view *view)
2292 memset(display, 0, sizeof(display));
2294 display[current_view] = view;
2296 redraw_display(FALSE);
2306 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2308 if (lineno >= view->lines)
2309 lineno = view->lines > 0 ? view->lines - 1 : 0;
2311 if (offset > lineno || offset + view->height <= lineno) {
2312 unsigned long half = view->height / 2;
2315 offset = lineno - half;
2320 if (offset != view->offset || lineno != view->lineno) {
2321 view->offset = offset;
2322 view->lineno = lineno;
2330 apply_step(double step, int value)
2334 value *= step + 0.01;
2335 return value ? value : 1;
2338 /* Scrolling backend */
2340 do_scroll_view(struct view *view, int lines)
2342 bool redraw_current_line = FALSE;
2344 /* The rendering expects the new offset. */
2345 view->offset += lines;
2347 assert(0 <= view->offset && view->offset < view->lines);
2350 /* Move current line into the view. */
2351 if (view->lineno < view->offset) {
2352 view->lineno = view->offset;
2353 redraw_current_line = TRUE;
2354 } else if (view->lineno >= view->offset + view->height) {
2355 view->lineno = view->offset + view->height - 1;
2356 redraw_current_line = TRUE;
2359 assert(view->offset <= view->lineno && view->lineno < view->lines);
2361 /* Redraw the whole screen if scrolling is pointless. */
2362 if (view->height < ABS(lines)) {
2366 int line = lines > 0 ? view->height - lines : 0;
2367 int end = line + ABS(lines);
2369 scrollok(view->win, TRUE);
2370 wscrl(view->win, lines);
2371 scrollok(view->win, FALSE);
2373 while (line < end && draw_view_line(view, line))
2376 if (redraw_current_line)
2377 draw_view_line(view, view->lineno - view->offset);
2378 wnoutrefresh(view->win);
2381 view->has_scrolled = TRUE;
2385 /* Scroll frontend */
2387 scroll_view(struct view *view, enum request request)
2391 assert(view_is_displayed(view));
2394 case REQ_SCROLL_LEFT:
2395 if (view->yoffset == 0) {
2396 report("Cannot scroll beyond the first column");
2399 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2402 view->yoffset -= apply_step(opt_hscroll, view->width);
2403 redraw_view_from(view, 0);
2406 case REQ_SCROLL_RIGHT:
2407 view->yoffset += apply_step(opt_hscroll, view->width);
2411 case REQ_SCROLL_PAGE_DOWN:
2412 lines = view->height;
2413 case REQ_SCROLL_LINE_DOWN:
2414 if (view->offset + lines > view->lines)
2415 lines = view->lines - view->offset;
2417 if (lines == 0 || view->offset + view->height >= view->lines) {
2418 report("Cannot scroll beyond the last line");
2423 case REQ_SCROLL_PAGE_UP:
2424 lines = view->height;
2425 case REQ_SCROLL_LINE_UP:
2426 if (lines > view->offset)
2427 lines = view->offset;
2430 report("Cannot scroll beyond the first line");
2438 die("request %d not handled in switch", request);
2441 do_scroll_view(view, lines);
2446 move_view(struct view *view, enum request request)
2448 int scroll_steps = 0;
2452 case REQ_MOVE_FIRST_LINE:
2453 steps = -view->lineno;
2456 case REQ_MOVE_LAST_LINE:
2457 steps = view->lines - view->lineno - 1;
2460 case REQ_MOVE_PAGE_UP:
2461 steps = view->height > view->lineno
2462 ? -view->lineno : -view->height;
2465 case REQ_MOVE_PAGE_DOWN:
2466 steps = view->lineno + view->height >= view->lines
2467 ? view->lines - view->lineno - 1 : view->height;
2479 die("request %d not handled in switch", request);
2482 if (steps <= 0 && view->lineno == 0) {
2483 report("Cannot move beyond the first line");
2486 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2487 report("Cannot move beyond the last line");
2491 /* Move the current line */
2492 view->lineno += steps;
2493 assert(0 <= view->lineno && view->lineno < view->lines);
2495 /* Check whether the view needs to be scrolled */
2496 if (view->lineno < view->offset ||
2497 view->lineno >= view->offset + view->height) {
2498 scroll_steps = steps;
2499 if (steps < 0 && -steps > view->offset) {
2500 scroll_steps = -view->offset;
2502 } else if (steps > 0) {
2503 if (view->lineno == view->lines - 1 &&
2504 view->lines > view->height) {
2505 scroll_steps = view->lines - view->offset - 1;
2506 if (scroll_steps >= view->height)
2507 scroll_steps -= view->height - 1;
2512 if (!view_is_displayed(view)) {
2513 view->offset += scroll_steps;
2514 assert(0 <= view->offset && view->offset < view->lines);
2515 view->ops->select(view, &view->line[view->lineno]);
2519 /* Repaint the old "current" line if we be scrolling */
2520 if (ABS(steps) < view->height)
2521 draw_view_line(view, view->lineno - steps - view->offset);
2524 do_scroll_view(view, scroll_steps);
2528 /* Draw the current line */
2529 draw_view_line(view, view->lineno - view->offset);
2531 wnoutrefresh(view->win);
2540 static void search_view(struct view *view, enum request request);
2543 grep_text(struct view *view, const char *text[])
2548 for (i = 0; text[i]; i++)
2550 regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
2556 select_view_line(struct view *view, unsigned long lineno)
2558 unsigned long old_lineno = view->lineno;
2559 unsigned long old_offset = view->offset;
2561 if (goto_view_line(view, view->offset, lineno)) {
2562 if (view_is_displayed(view)) {
2563 if (old_offset != view->offset) {
2566 draw_view_line(view, old_lineno - view->offset);
2567 draw_view_line(view, view->lineno - view->offset);
2568 wnoutrefresh(view->win);
2571 view->ops->select(view, &view->line[view->lineno]);
2577 find_next(struct view *view, enum request request)
2579 unsigned long lineno = view->lineno;
2584 report("No previous search");
2586 search_view(view, request);
2596 case REQ_SEARCH_BACK:
2605 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2606 lineno += direction;
2608 /* Note, lineno is unsigned long so will wrap around in which case it
2609 * will become bigger than view->lines. */
2610 for (; lineno < view->lines; lineno += direction) {
2611 if (view->ops->grep(view, &view->line[lineno])) {
2612 select_view_line(view, lineno);
2613 report("Line %ld matches '%s'", lineno + 1, view->grep);
2618 report("No match found for '%s'", view->grep);
2622 search_view(struct view *view, enum request request)
2627 regfree(view->regex);
2630 view->regex = calloc(1, sizeof(*view->regex));
2635 regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2636 if (regex_err != 0) {
2637 char buf[SIZEOF_STR] = "unknown error";
2639 regerror(regex_err, view->regex, buf, sizeof(buf));
2640 report("Search failed: %s", buf);
2644 string_copy(view->grep, opt_search);
2646 find_next(view, request);
2650 * Incremental updating
2654 reset_view(struct view *view)
2658 for (i = 0; i < view->lines; i++)
2659 free(view->line[i].data);
2662 view->p_offset = view->offset;
2663 view->p_yoffset = view->yoffset;
2664 view->p_lineno = view->lineno;
2672 view->update_secs = 0;
2676 free_argv(const char *argv[])
2680 for (argc = 0; argv[argc]; argc++)
2681 free((void *) argv[argc]);
2685 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
2687 char buf[SIZEOF_STR];
2689 bool noreplace = flags == FORMAT_NONE;
2691 free_argv(dst_argv);
2693 for (argc = 0; src_argv[argc]; argc++) {
2694 const char *arg = src_argv[argc];
2698 char *next = strstr(arg, "%(");
2699 int len = next - arg;
2702 if (!next || noreplace) {
2703 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
2708 } else if (!prefixcmp(next, "%(directory)")) {
2711 } else if (!prefixcmp(next, "%(file)")) {
2714 } else if (!prefixcmp(next, "%(ref)")) {
2715 value = *opt_ref ? opt_ref : "HEAD";
2717 } else if (!prefixcmp(next, "%(head)")) {
2720 } else if (!prefixcmp(next, "%(commit)")) {
2723 } else if (!prefixcmp(next, "%(blob)")) {
2727 report("Unknown replacement: `%s`", next);
2731 if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
2734 arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
2737 dst_argv[argc] = strdup(buf);
2738 if (!dst_argv[argc])
2742 dst_argv[argc] = NULL;
2744 return src_argv[argc] == NULL;
2748 restore_view_position(struct view *view)
2750 if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
2753 /* Changing the view position cancels the restoring. */
2754 /* FIXME: Changing back to the first line is not detected. */
2755 if (view->offset != 0 || view->lineno != 0) {
2756 view->p_restore = FALSE;
2760 if (goto_view_line(view, view->p_offset, view->p_lineno) &&
2761 view_is_displayed(view))
2764 view->yoffset = view->p_yoffset;
2765 view->p_restore = FALSE;
2771 end_update(struct view *view, bool force)
2775 while (!view->ops->read(view, NULL))
2778 set_nonblocking_input(FALSE);
2780 kill_io(view->pipe);
2781 done_io(view->pipe);
2786 setup_update(struct view *view, const char *vid)
2788 set_nonblocking_input(TRUE);
2790 string_copy_rev(view->vid, vid);
2791 view->pipe = &view->io;
2792 view->start_time = time(NULL);
2796 prepare_update(struct view *view, const char *argv[], const char *dir,
2797 enum format_flags flags)
2800 end_update(view, TRUE);
2801 return init_io_rd(&view->io, argv, dir, flags);
2805 prepare_update_file(struct view *view, const char *name)
2808 end_update(view, TRUE);
2809 return io_open(&view->io, name);
2813 begin_update(struct view *view, bool refresh)
2816 end_update(view, TRUE);
2819 if (!start_io(&view->io))
2823 if (view == VIEW(REQ_VIEW_TREE) && strcmp(view->vid, view->id))
2826 if (!run_io_rd(&view->io, view->ops->argv, FORMAT_ALL))
2829 /* Put the current ref_* value to the view title ref
2830 * member. This is needed by the blob view. Most other
2831 * views sets it automatically after loading because the
2832 * first line is a commit line. */
2833 string_copy_rev(view->ref, view->id);
2836 setup_update(view, view->id);
2842 update_view(struct view *view)
2844 char out_buffer[BUFSIZ * 2];
2846 /* Clear the view and redraw everything since the tree sorting
2847 * might have rearranged things. */
2848 bool redraw = view->lines == 0;
2849 bool can_read = TRUE;
2854 if (!io_can_read(view->pipe)) {
2855 if (view->lines == 0 && view_is_displayed(view)) {
2856 time_t secs = time(NULL) - view->start_time;
2858 if (secs > 1 && secs > view->update_secs) {
2859 if (view->update_secs == 0)
2861 update_view_title(view);
2862 view->update_secs = secs;
2868 for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
2869 if (opt_iconv != ICONV_NONE) {
2870 ICONV_CONST char *inbuf = line;
2871 size_t inlen = strlen(line) + 1;
2873 char *outbuf = out_buffer;
2874 size_t outlen = sizeof(out_buffer);
2878 ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2879 if (ret != (size_t) -1)
2883 if (!view->ops->read(view, line)) {
2884 report("Allocation failure");
2885 end_update(view, TRUE);
2891 unsigned long lines = view->lines;
2894 for (digits = 0; lines; digits++)
2897 /* Keep the displayed view in sync with line number scaling. */
2898 if (digits != view->digits) {
2899 view->digits = digits;
2900 if (opt_line_number || view == VIEW(REQ_VIEW_BLAME))
2905 if (io_error(view->pipe)) {
2906 report("Failed to read: %s", io_strerror(view->pipe));
2907 end_update(view, TRUE);
2909 } else if (io_eof(view->pipe)) {
2911 end_update(view, FALSE);
2914 if (restore_view_position(view))
2917 if (!view_is_displayed(view))
2921 redraw_view_from(view, 0);
2923 redraw_view_dirty(view);
2925 /* Update the title _after_ the redraw so that if the redraw picks up a
2926 * commit reference in view->ref it'll be available here. */
2927 update_view_title(view);
2931 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
2933 static struct line *
2934 add_line_data(struct view *view, void *data, enum line_type type)
2938 if (!realloc_lines(&view->line, view->lines, 1))
2941 line = &view->line[view->lines++];
2942 memset(line, 0, sizeof(*line));
2950 static struct line *
2951 add_line_text(struct view *view, const char *text, enum line_type type)
2953 char *data = text ? strdup(text) : NULL;
2955 return data ? add_line_data(view, data, type) : NULL;
2958 static struct line *
2959 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
2961 char buf[SIZEOF_STR];
2964 va_start(args, fmt);
2965 if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
2969 return buf[0] ? add_line_text(view, buf, type) : NULL;
2977 OPEN_DEFAULT = 0, /* Use default view switching. */
2978 OPEN_SPLIT = 1, /* Split current view. */
2979 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
2980 OPEN_REFRESH = 16, /* Refresh view using previous command. */
2981 OPEN_PREPARED = 32, /* Open already prepared command. */
2985 open_view(struct view *prev, enum request request, enum open_flags flags)
2987 bool split = !!(flags & OPEN_SPLIT);
2988 bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
2989 bool nomaximize = !!(flags & OPEN_REFRESH);
2990 struct view *view = VIEW(request);
2991 int nviews = displayed_views();
2992 struct view *base_view = display[0];
2994 if (view == prev && nviews == 1 && !reload) {
2995 report("Already in %s view", view->name);
2999 if (view->git_dir && !opt_git_dir[0]) {
3000 report("The %s view is disabled in pager view", view->name);
3007 } else if (!nomaximize) {
3008 /* Maximize the current view. */
3009 memset(display, 0, sizeof(display));
3011 display[current_view] = view;
3014 /* Resize the view when switching between split- and full-screen,
3015 * or when switching between two different full-screen views. */
3016 if (nviews != displayed_views() ||
3017 (nviews == 1 && base_view != display[0]))
3020 if (view->ops->open) {
3022 end_update(view, TRUE);
3023 if (!view->ops->open(view)) {
3024 report("Failed to load %s view", view->name);
3027 restore_view_position(view);
3029 } else if ((reload || strcmp(view->vid, view->id)) &&
3030 !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3031 report("Failed to load %s view", view->name);
3035 if (split && prev->lineno - prev->offset >= prev->height) {
3036 /* Take the title line into account. */
3037 int lines = prev->lineno - prev->offset - prev->height + 1;
3039 /* Scroll the view that was split if the current line is
3040 * outside the new limited view. */
3041 do_scroll_view(prev, lines);
3044 if (prev && view != prev) {
3046 /* "Blur" the previous view. */
3047 update_view_title(prev);
3050 view->parent = prev;
3053 if (view->pipe && view->lines == 0) {
3054 /* Clear the old view and let the incremental updating refill
3057 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3059 } else if (view_is_displayed(view)) {
3066 open_external_viewer(const char *argv[], const char *dir)
3068 def_prog_mode(); /* save current tty modes */
3069 endwin(); /* restore original tty modes */
3070 run_io_fg(argv, dir);
3071 fprintf(stderr, "Press Enter to continue");
3074 redraw_display(TRUE);
3078 open_mergetool(const char *file)
3080 const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3082 open_external_viewer(mergetool_argv, opt_cdup);
3086 open_editor(bool from_root, const char *file)
3088 const char *editor_argv[] = { "vi", file, NULL };
3091 editor = getenv("GIT_EDITOR");
3092 if (!editor && *opt_editor)
3093 editor = opt_editor;
3095 editor = getenv("VISUAL");
3097 editor = getenv("EDITOR");
3101 editor_argv[0] = editor;
3102 open_external_viewer(editor_argv, from_root ? opt_cdup : NULL);
3106 open_run_request(enum request request)
3108 struct run_request *req = get_run_request(request);
3109 const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3112 report("Unknown run request");
3116 if (format_argv(argv, req->argv, FORMAT_ALL))
3117 open_external_viewer(argv, NULL);
3122 * User request switch noodle
3126 view_driver(struct view *view, enum request request)
3130 if (request == REQ_NONE)
3133 if (request > REQ_NONE) {
3134 open_run_request(request);
3135 /* FIXME: When all views can refresh always do this. */
3136 if (view == VIEW(REQ_VIEW_STATUS) ||
3137 view == VIEW(REQ_VIEW_MAIN) ||
3138 view == VIEW(REQ_VIEW_LOG) ||
3139 view == VIEW(REQ_VIEW_BRANCH) ||
3140 view == VIEW(REQ_VIEW_STAGE))
3141 request = REQ_REFRESH;
3146 if (view && view->lines) {
3147 request = view->ops->request(view, request, &view->line[view->lineno]);
3148 if (request == REQ_NONE)
3155 case REQ_MOVE_PAGE_UP:
3156 case REQ_MOVE_PAGE_DOWN:
3157 case REQ_MOVE_FIRST_LINE:
3158 case REQ_MOVE_LAST_LINE:
3159 move_view(view, request);
3162 case REQ_SCROLL_LEFT:
3163 case REQ_SCROLL_RIGHT:
3164 case REQ_SCROLL_LINE_DOWN:
3165 case REQ_SCROLL_LINE_UP:
3166 case REQ_SCROLL_PAGE_DOWN:
3167 case REQ_SCROLL_PAGE_UP:
3168 scroll_view(view, request);
3171 case REQ_VIEW_BLAME:
3173 report("No file chosen, press %s to open tree view",
3174 get_key(REQ_VIEW_TREE));
3177 open_view(view, request, OPEN_DEFAULT);
3182 report("No file chosen, press %s to open tree view",
3183 get_key(REQ_VIEW_TREE));
3186 open_view(view, request, OPEN_DEFAULT);
3189 case REQ_VIEW_PAGER:
3190 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3191 report("No pager content, press %s to run command from prompt",
3192 get_key(REQ_PROMPT));
3195 open_view(view, request, OPEN_DEFAULT);
3198 case REQ_VIEW_STAGE:
3199 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3200 report("No stage content, press %s to open the status view and choose file",
3201 get_key(REQ_VIEW_STATUS));
3204 open_view(view, request, OPEN_DEFAULT);
3207 case REQ_VIEW_STATUS:
3208 if (opt_is_inside_work_tree == FALSE) {
3209 report("The status view requires a working tree");
3212 open_view(view, request, OPEN_DEFAULT);
3220 case REQ_VIEW_BRANCH:
3221 open_view(view, request, OPEN_DEFAULT);
3226 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3228 if ((view == VIEW(REQ_VIEW_DIFF) &&
3229 view->parent == VIEW(REQ_VIEW_MAIN)) ||
3230 (view == VIEW(REQ_VIEW_DIFF) &&
3231 view->parent == VIEW(REQ_VIEW_BLAME)) ||
3232 (view == VIEW(REQ_VIEW_STAGE) &&
3233 view->parent == VIEW(REQ_VIEW_STATUS)) ||
3234 (view == VIEW(REQ_VIEW_BLOB) &&
3235 view->parent == VIEW(REQ_VIEW_TREE)) ||
3236 (view == VIEW(REQ_VIEW_MAIN) &&
3237 view->parent == VIEW(REQ_VIEW_BRANCH))) {
3240 view = view->parent;
3241 line = view->lineno;
3242 move_view(view, request);
3243 if (view_is_displayed(view))
3244 update_view_title(view);
3245 if (line != view->lineno)
3246 view->ops->request(view, REQ_ENTER,
3247 &view->line[view->lineno]);
3250 move_view(view, request);
3256 int nviews = displayed_views();
3257 int next_view = (current_view + 1) % nviews;
3259 if (next_view == current_view) {
3260 report("Only one view is displayed");
3264 current_view = next_view;
3265 /* Blur out the title of the previous view. */
3266 update_view_title(view);
3271 report("Refreshing is not yet supported for the %s view", view->name);
3275 if (displayed_views() == 2)
3276 maximize_view(view);
3279 case REQ_TOGGLE_LINENO:
3280 toggle_view_option(&opt_line_number, "line numbers");
3283 case REQ_TOGGLE_DATE:
3284 toggle_view_option(&opt_date, "date display");
3287 case REQ_TOGGLE_AUTHOR:
3288 toggle_view_option(&opt_author, "author display");
3291 case REQ_TOGGLE_REV_GRAPH:
3292 toggle_view_option(&opt_rev_graph, "revision graph display");
3295 case REQ_TOGGLE_REFS:
3296 toggle_view_option(&opt_show_refs, "reference display");
3299 case REQ_TOGGLE_SORT_FIELD:
3300 case REQ_TOGGLE_SORT_ORDER:
3301 report("Sorting is not yet supported for the %s view", view->name);
3305 case REQ_SEARCH_BACK:
3306 search_view(view, request);
3311 find_next(view, request);
3314 case REQ_STOP_LOADING:
3315 for (i = 0; i < ARRAY_SIZE(views); i++) {
3318 report("Stopped loading the %s view", view->name),
3319 end_update(view, TRUE);
3323 case REQ_SHOW_VERSION:
3324 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3327 case REQ_SCREEN_REDRAW:
3328 redraw_display(TRUE);
3332 report("Nothing to edit");
3336 report("Nothing to enter");
3339 case REQ_VIEW_CLOSE:
3340 /* XXX: Mark closed views by letting view->parent point to the
3341 * view itself. Parents to closed view should never be
3344 view->parent->parent != view->parent) {
3345 maximize_view(view->parent);
3346 view->parent = view;
3354 report("Unknown key, press 'h' for help");
3363 * View backend utilities
3373 const enum sort_field *fields;
3374 size_t size, current;
3378 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3379 #define get_sort_field(state) ((state).fields[(state).current])
3380 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3383 sort_view(struct view *view, enum request request, struct sort_state *state,
3384 int (*compare)(const void *, const void *))
3387 case REQ_TOGGLE_SORT_FIELD:
3388 state->current = (state->current + 1) % state->size;
3391 case REQ_TOGGLE_SORT_ORDER:
3392 state->reverse = !state->reverse;
3395 die("Not a sort request");
3398 qsort(view->line, view->lines, sizeof(*view->line), compare);
3402 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3404 /* Small author cache to reduce memory consumption. It uses binary
3405 * search to lookup or find place to position new entries. No entries
3406 * are ever freed. */
3408 get_author(const char *name)
3410 static const char **authors;
3411 static size_t authors_size;
3412 int from = 0, to = authors_size - 1;
3414 while (from <= to) {
3415 size_t pos = (to + from) / 2;
3416 int cmp = strcmp(name, authors[pos]);
3419 return authors[pos];
3427 if (!realloc_authors(&authors, authors_size, 1))
3429 name = strdup(name);
3433 memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3434 authors[from] = name;
3441 parse_timezone(time_t *time, const char *zone)
3445 tz = ('0' - zone[1]) * 60 * 60 * 10;
3446 tz += ('0' - zone[2]) * 60 * 60;
3447 tz += ('0' - zone[3]) * 60;
3448 tz += ('0' - zone[4]);
3456 /* Parse author lines where the name may be empty:
3457 * author <email@address.tld> 1138474660 +0100
3460 parse_author_line(char *ident, const char **author, time_t *time)
3462 char *nameend = strchr(ident, '<');
3463 char *emailend = strchr(ident, '>');
3465 if (nameend && emailend)
3466 *nameend = *emailend = 0;
3467 ident = chomp_string(ident);
3470 ident = chomp_string(nameend + 1);
3475 *author = get_author(ident);
3477 /* Parse epoch and timezone */
3478 if (emailend && emailend[1] == ' ') {
3479 char *secs = emailend + 2;
3480 char *zone = strchr(secs, ' ');
3482 *time = (time_t) atol(secs);
3484 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
3485 parse_timezone(time, zone + 1);
3489 static enum input_status
3490 select_commit_parent_handler(void *data, char *buf, int c)
3492 size_t parents = *(size_t *) data;
3499 parent = atoi(buf) * 10;
3502 if (parent > parents)
3508 select_commit_parent(const char *id, char rev[SIZEOF_REV], const char *path)
3510 char buf[SIZEOF_STR * 4];
3511 const char *revlist_argv[] = {
3512 "git", "rev-list", "-1", "--parents", id, "--", path, NULL
3516 if (!run_io_buf(revlist_argv, buf, sizeof(buf)) ||
3517 (parents = (strlen(buf) / 40) - 1) < 0) {
3518 report("Failed to get parent information");
3521 } else if (parents == 0) {
3523 report("Path '%s' does not exist in the parent", path);
3525 report("The selected commit has no parents");
3530 char prompt[SIZEOF_STR];
3533 if (!string_format(prompt, "Which parent? [1..%d] ", parents))
3535 result = prompt_input(prompt, select_commit_parent_handler, &parents);
3538 parents = atoi(result);
3541 string_copy_rev(rev, &buf[41 * parents]);
3550 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3552 char text[SIZEOF_STR];
3554 if (opt_line_number && draw_lineno(view, lineno))
3557 string_expand(text, sizeof(text), line->data, opt_tab_size);
3558 draw_text(view, line->type, text, TRUE);
3563 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3565 const char *describe_argv[] = { "git", "describe", commit_id, NULL };
3566 char ref[SIZEOF_STR];
3568 if (!run_io_buf(describe_argv, ref, sizeof(ref)) || !*ref)
3571 /* This is the only fatal call, since it can "corrupt" the buffer. */
3572 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
3579 add_pager_refs(struct view *view, struct line *line)
3581 char buf[SIZEOF_STR];
3582 char *commit_id = (char *)line->data + STRING_SIZE("commit ");
3583 struct ref_list *list;
3584 size_t bufpos = 0, i;
3585 const char *sep = "Refs: ";
3586 bool is_tag = FALSE;
3588 assert(line->type == LINE_COMMIT);
3590 list = get_ref_list(commit_id);
3592 if (view == VIEW(REQ_VIEW_DIFF))
3593 goto try_add_describe_ref;
3597 for (i = 0; i < list->size; i++) {
3598 struct ref *ref = list->refs[i];
3599 const char *fmt = ref->tag ? "%s[%s]" :
3600 ref->remote ? "%s<%s>" : "%s%s";
3602 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
3609 if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
3610 try_add_describe_ref:
3611 /* Add <tag>-g<commit_id> "fake" reference. */
3612 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
3619 add_line_text(view, buf, LINE_PP_REFS);
3623 pager_read(struct view *view, char *data)
3630 line = add_line_text(view, data, get_line_type(data));
3634 if (line->type == LINE_COMMIT &&
3635 (view == VIEW(REQ_VIEW_DIFF) ||
3636 view == VIEW(REQ_VIEW_LOG)))
3637 add_pager_refs(view, line);
3643 pager_request(struct view *view, enum request request, struct line *line)
3647 if (request != REQ_ENTER)
3650 if (line->type == LINE_COMMIT &&
3651 (view == VIEW(REQ_VIEW_LOG) ||
3652 view == VIEW(REQ_VIEW_PAGER))) {
3653 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3657 /* Always scroll the view even if it was split. That way
3658 * you can use Enter to scroll through the log view and
3659 * split open each commit diff. */
3660 scroll_view(view, REQ_SCROLL_LINE_DOWN);
3662 /* FIXME: A minor workaround. Scrolling the view will call report("")
3663 * but if we are scrolling a non-current view this won't properly
3664 * update the view title. */
3666 update_view_title(view);
3672 pager_grep(struct view *view, struct line *line)
3674 const char *text[] = { line->data, NULL };
3676 return grep_text(view, text);
3680 pager_select(struct view *view, struct line *line)
3682 if (line->type == LINE_COMMIT) {
3683 char *text = (char *)line->data + STRING_SIZE("commit ");
3685 if (view != VIEW(REQ_VIEW_PAGER))
3686 string_copy_rev(view->ref, text);
3687 string_copy_rev(ref_commit, text);
3691 static struct view_ops pager_ops = {
3702 static const char *log_argv[SIZEOF_ARG] = {
3703 "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3707 log_request(struct view *view, enum request request, struct line *line)
3712 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
3715 return pager_request(view, request, line);
3719 static struct view_ops log_ops = {
3730 static const char *diff_argv[SIZEOF_ARG] = {
3731 "git", "show", "--pretty=fuller", "--no-color", "--root",
3732 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
3735 static struct view_ops diff_ops = {
3751 help_open(struct view *view)
3753 char buf[SIZEOF_STR];
3757 if (view->lines > 0)
3760 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
3762 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
3765 if (req_info[i].request == REQ_NONE)
3768 if (!req_info[i].request) {
3769 add_line_text(view, "", LINE_DEFAULT);
3770 add_line_text(view, req_info[i].help, LINE_DEFAULT);
3774 key = get_key(req_info[i].request);
3776 key = "(no key defined)";
3778 for (bufpos = 0; bufpos <= req_info[i].namelen; bufpos++) {
3779 buf[bufpos] = tolower(req_info[i].name[bufpos]);
3780 if (buf[bufpos] == '_')
3784 add_line_format(view, LINE_DEFAULT, " %-25s %-20s %s",
3785 key, buf, req_info[i].help);
3789 add_line_text(view, "", LINE_DEFAULT);
3790 add_line_text(view, "External commands:", LINE_DEFAULT);
3793 for (i = 0; i < run_requests; i++) {
3794 struct run_request *req = get_run_request(REQ_NONE + i + 1);
3801 key = get_key_name(req->key);
3803 key = "(no key defined)";
3805 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
3806 if (!string_format_from(buf, &bufpos, "%s%s",
3807 argc ? " " : "", req->argv[argc]))
3810 add_line_format(view, LINE_DEFAULT, " %-10s %-14s `%s`",
3811 keymap_table[req->keymap].name, key, buf);
3817 static struct view_ops help_ops = {
3833 struct tree_stack_entry {
3834 struct tree_stack_entry *prev; /* Entry below this in the stack */
3835 unsigned long lineno; /* Line number to restore */
3836 char *name; /* Position of name in opt_path */
3839 /* The top of the path stack. */
3840 static struct tree_stack_entry *tree_stack = NULL;
3841 unsigned long tree_lineno = 0;
3844 pop_tree_stack_entry(void)
3846 struct tree_stack_entry *entry = tree_stack;
3848 tree_lineno = entry->lineno;
3850 tree_stack = entry->prev;
3855 push_tree_stack_entry(const char *name, unsigned long lineno)
3857 struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
3858 size_t pathlen = strlen(opt_path);
3863 entry->prev = tree_stack;
3864 entry->name = opt_path + pathlen;
3867 if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
3868 pop_tree_stack_entry();
3872 /* Move the current line to the first tree entry. */
3874 entry->lineno = lineno;
3877 /* Parse output from git-ls-tree(1):
3879 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
3882 #define SIZEOF_TREE_ATTR \
3883 STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
3885 #define SIZEOF_TREE_MODE \
3886 STRING_SIZE("100644 ")
3888 #define TREE_ID_OFFSET \
3889 STRING_SIZE("100644 blob ")
3892 char id[SIZEOF_REV];
3894 time_t time; /* Date from the author ident. */
3895 const char *author; /* Author of the commit. */
3900 tree_path(const struct line *line)
3902 return ((struct tree_entry *) line->data)->name;
3906 tree_compare_entry(const struct line *line1, const struct line *line2)
3908 if (line1->type != line2->type)
3909 return line1->type == LINE_TREE_DIR ? -1 : 1;
3910 return strcmp(tree_path(line1), tree_path(line2));
3913 static const enum sort_field tree_sort_fields[] = {
3914 ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
3916 static struct sort_state tree_sort_state = SORT_STATE(tree_sort_fields);
3919 tree_compare(const void *l1, const void *l2)
3921 const struct line *line1 = (const struct line *) l1;
3922 const struct line *line2 = (const struct line *) l2;
3923 const struct tree_entry *entry1 = ((const struct line *) l1)->data;
3924 const struct tree_entry *entry2 = ((const struct line *) l2)->data;
3926 if (line1->type == LINE_TREE_HEAD)
3928 if (line2->type == LINE_TREE_HEAD)
3931 switch (get_sort_field(tree_sort_state)) {
3933 return sort_order(tree_sort_state, entry1->time - entry2->time);
3935 case ORDERBY_AUTHOR:
3936 return sort_order(tree_sort_state, strcmp(entry1->author, entry2->author));
3940 return sort_order(tree_sort_state, tree_compare_entry(line1, line2));
3945 static struct line *
3946 tree_entry(struct view *view, enum line_type type, const char *path,
3947 const char *mode, const char *id)
3949 struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
3950 struct line *line = entry ? add_line_data(view, entry, type) : NULL;
3952 if (!entry || !line) {
3957 strncpy(entry->name, path, strlen(path));
3959 entry->mode = strtoul(mode, NULL, 8);
3961 string_copy_rev(entry->id, id);
3967 tree_read_date(struct view *view, char *text, bool *read_date)
3969 static const char *author_name;
3970 static time_t author_time;
3972 if (!text && *read_date) {
3977 char *path = *opt_path ? opt_path : ".";
3978 /* Find next entry to process */
3979 const char *log_file[] = {
3980 "git", "log", "--no-color", "--pretty=raw",
3981 "--cc", "--raw", view->id, "--", path, NULL
3986 tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
3987 report("Tree is empty");
3991 if (!run_io_rd(&io, log_file, FORMAT_NONE)) {
3992 report("Failed to load tree data");
3996 done_io(view->pipe);
4001 } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
4002 parse_author_line(text + STRING_SIZE("author "),
4003 &author_name, &author_time);
4005 } else if (*text == ':') {
4007 size_t annotated = 1;
4010 pos = strchr(text, '\t');
4014 if (*opt_prefix && !strncmp(text, opt_prefix, strlen(opt_prefix)))
4015 text += strlen(opt_prefix);
4016 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
4017 text += strlen(opt_path);
4018 pos = strchr(text, '/');
4022 for (i = 1; i < view->lines; i++) {
4023 struct line *line = &view->line[i];
4024 struct tree_entry *entry = line->data;
4026 annotated += !!entry->author;
4027 if (entry->author || strcmp(entry->name, text))
4030 entry->author = author_name;
4031 entry->time = author_time;
4036 if (annotated == view->lines)
4037 kill_io(view->pipe);
4043 tree_read(struct view *view, char *text)
4045 static bool read_date = FALSE;
4046 struct tree_entry *data;
4047 struct line *entry, *line;
4048 enum line_type type;
4049 size_t textlen = text ? strlen(text) : 0;
4050 char *path = text + SIZEOF_TREE_ATTR;
4052 if (read_date || !text)
4053 return tree_read_date(view, text, &read_date);
4055 if (textlen <= SIZEOF_TREE_ATTR)
4057 if (view->lines == 0 &&
4058 !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
4061 /* Strip the path part ... */
4063 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
4064 size_t striplen = strlen(opt_path);
4066 if (pathlen > striplen)
4067 memmove(path, path + striplen,
4068 pathlen - striplen + 1);
4070 /* Insert "link" to parent directory. */
4071 if (view->lines == 1 &&
4072 !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
4076 type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
4077 entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
4082 /* Skip "Directory ..." and ".." line. */
4083 for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
4084 if (tree_compare_entry(line, entry) <= 0)
4087 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4091 for (; line <= entry; line++)
4092 line->dirty = line->cleareol = 1;
4096 if (tree_lineno > view->lineno) {
4097 view->lineno = tree_lineno;
4105 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4107 struct tree_entry *entry = line->data;
4109 if (line->type == LINE_TREE_HEAD) {
4110 if (draw_text(view, line->type, "Directory path /", TRUE))
4113 if (draw_mode(view, entry->mode))
4116 if (opt_author && draw_author(view, entry->author))
4119 if (opt_date && draw_date(view, entry->author ? &entry->time : NULL))
4122 if (draw_text(view, line->type, entry->name, TRUE))
4130 char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4131 int fd = mkstemp(file);
4134 report("Failed to create temporary file");
4135 else if (!run_io_append(blob_ops.argv, FORMAT_ALL, fd))
4136 report("Failed to save blob data to file");
4138 open_editor(FALSE, file);
4144 tree_request(struct view *view, enum request request, struct line *line)
4146 enum open_flags flags;
4149 case REQ_VIEW_BLAME:
4150 if (line->type != LINE_TREE_FILE) {
4151 report("Blame only supported for files");
4155 string_copy(opt_ref, view->vid);
4159 if (line->type != LINE_TREE_FILE) {
4160 report("Edit only supported for files");
4161 } else if (!is_head_commit(view->vid)) {
4164 open_editor(TRUE, opt_file);
4168 case REQ_TOGGLE_SORT_FIELD:
4169 case REQ_TOGGLE_SORT_ORDER:
4170 sort_view(view, request, &tree_sort_state, tree_compare);
4175 /* quit view if at top of tree */
4176 return REQ_VIEW_CLOSE;
4179 line = &view->line[1];
4189 /* Cleanup the stack if the tree view is at a different tree. */
4190 while (!*opt_path && tree_stack)
4191 pop_tree_stack_entry();
4193 switch (line->type) {
4195 /* Depending on whether it is a subdirectory or parent link
4196 * mangle the path buffer. */
4197 if (line == &view->line[1] && *opt_path) {
4198 pop_tree_stack_entry();
4201 const char *basename = tree_path(line);
4203 push_tree_stack_entry(basename, view->lineno);
4206 /* Trees and subtrees share the same ID, so they are not not
4207 * unique like blobs. */
4208 flags = OPEN_RELOAD;
4209 request = REQ_VIEW_TREE;
4212 case LINE_TREE_FILE:
4213 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4214 request = REQ_VIEW_BLOB;
4221 open_view(view, request, flags);
4222 if (request == REQ_VIEW_TREE)
4223 view->lineno = tree_lineno;
4229 tree_grep(struct view *view, struct line *line)
4231 struct tree_entry *entry = line->data;
4232 const char *text[] = {
4234 opt_author ? entry->author : "",
4235 opt_date ? mkdate(&entry->time) : "",
4239 return grep_text(view, text);
4243 tree_select(struct view *view, struct line *line)
4245 struct tree_entry *entry = line->data;
4247 if (line->type == LINE_TREE_FILE) {
4248 string_copy_rev(ref_blob, entry->id);
4249 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4251 } else if (line->type != LINE_TREE_DIR) {
4255 string_copy_rev(view->ref, entry->id);
4258 static const char *tree_argv[SIZEOF_ARG] = {
4259 "git", "ls-tree", "%(commit)", "%(directory)", NULL
4262 static struct view_ops tree_ops = {
4274 blob_read(struct view *view, char *line)
4278 return add_line_text(view, line, LINE_DEFAULT) != NULL;
4282 blob_request(struct view *view, enum request request, struct line *line)
4289 return pager_request(view, request, line);
4293 static const char *blob_argv[SIZEOF_ARG] = {
4294 "git", "cat-file", "blob", "%(blob)", NULL
4297 static struct view_ops blob_ops = {
4311 * Loading the blame view is a two phase job:
4313 * 1. File content is read either using opt_file from the
4314 * filesystem or using git-cat-file.
4315 * 2. Then blame information is incrementally added by
4316 * reading output from git-blame.
4319 static const char *blame_head_argv[] = {
4320 "git", "blame", "--incremental", "--", "%(file)", NULL
4323 static const char *blame_ref_argv[] = {
4324 "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4327 static const char *blame_cat_file_argv[] = {
4328 "git", "cat-file", "blob", "%(ref):%(file)", NULL
4331 struct blame_commit {
4332 char id[SIZEOF_REV]; /* SHA1 ID. */
4333 char title[128]; /* First line of the commit message. */
4334 const char *author; /* Author of the commit. */
4335 time_t time; /* Date from the author ident. */
4336 char filename[128]; /* Name of file. */
4337 bool has_previous; /* Was a "previous" line detected. */
4341 struct blame_commit *commit;
4342 unsigned long lineno;
4347 blame_open(struct view *view)
4349 if (*opt_ref || !io_open(&view->io, opt_file)) {
4350 if (!run_io_rd(&view->io, blame_cat_file_argv, FORMAT_ALL))
4354 setup_update(view, opt_file);
4355 string_format(view->ref, "%s ...", opt_file);
4360 static struct blame_commit *
4361 get_blame_commit(struct view *view, const char *id)
4365 for (i = 0; i < view->lines; i++) {
4366 struct blame *blame = view->line[i].data;
4371 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4372 return blame->commit;
4376 struct blame_commit *commit = calloc(1, sizeof(*commit));
4379 string_ncopy(commit->id, id, SIZEOF_REV);
4385 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4387 const char *pos = *posref;
4390 pos = strchr(pos + 1, ' ');
4391 if (!pos || !isdigit(pos[1]))
4393 *number = atoi(pos + 1);
4394 if (*number < min || *number > max)
4401 static struct blame_commit *
4402 parse_blame_commit(struct view *view, const char *text, int *blamed)
4404 struct blame_commit *commit;
4405 struct blame *blame;
4406 const char *pos = text + SIZEOF_REV - 2;
4407 size_t orig_lineno = 0;
4411 if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
4414 if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
4415 !parse_number(&pos, &lineno, 1, view->lines) ||
4416 !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4419 commit = get_blame_commit(view, text);
4425 struct line *line = &view->line[lineno + group - 1];
4428 blame->commit = commit;
4429 blame->lineno = orig_lineno + group - 1;
4437 blame_read_file(struct view *view, const char *line, bool *read_file)
4440 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
4443 if (view->lines == 0 && !view->parent)
4444 die("No blame exist for %s", view->vid);
4446 if (view->lines == 0 || !run_io_rd(&io, argv, FORMAT_ALL)) {
4447 report("Failed to load blame data");
4451 done_io(view->pipe);
4457 size_t linelen = strlen(line);
4458 struct blame *blame = malloc(sizeof(*blame) + linelen);
4463 blame->commit = NULL;
4464 strncpy(blame->text, line, linelen);
4465 blame->text[linelen] = 0;
4466 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
4471 match_blame_header(const char *name, char **line)
4473 size_t namelen = strlen(name);
4474 bool matched = !strncmp(name, *line, namelen);
4483 blame_read(struct view *view, char *line)
4485 static struct blame_commit *commit = NULL;
4486 static int blamed = 0;
4487 static bool read_file = TRUE;
4490 return blame_read_file(view, line, &read_file);
4497 string_format(view->ref, "%s", view->vid);
4498 if (view_is_displayed(view)) {
4499 update_view_title(view);
4500 redraw_view_from(view, 0);
4506 commit = parse_blame_commit(view, line, &blamed);
4507 string_format(view->ref, "%s %2d%%", view->vid,
4508 view->lines ? blamed * 100 / view->lines : 0);
4510 } else if (match_blame_header("author ", &line)) {
4511 commit->author = get_author(line);
4513 } else if (match_blame_header("author-time ", &line)) {
4514 commit->time = (time_t) atol(line);
4516 } else if (match_blame_header("author-tz ", &line)) {
4517 parse_timezone(&commit->time, line);
4519 } else if (match_blame_header("summary ", &line)) {
4520 string_ncopy(commit->title, line, strlen(line));
4522 } else if (match_blame_header("previous ", &line)) {
4523 commit->has_previous = TRUE;
4525 } else if (match_blame_header("filename ", &line)) {
4526 string_ncopy(commit->filename, line, strlen(line));
4534 blame_draw(struct view *view, struct line *line, unsigned int lineno)
4536 struct blame *blame = line->data;
4537 time_t *time = NULL;
4538 const char *id = NULL, *author = NULL;
4539 char text[SIZEOF_STR];
4541 if (blame->commit && *blame->commit->filename) {
4542 id = blame->commit->id;
4543 author = blame->commit->author;
4544 time = &blame->commit->time;
4547 if (opt_date && draw_date(view, time))
4550 if (opt_author && draw_author(view, author))
4553 if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
4556 if (draw_lineno(view, lineno))
4559 string_expand(text, sizeof(text), blame->text, opt_tab_size);
4560 draw_text(view, LINE_DEFAULT, text, TRUE);
4565 check_blame_commit(struct blame *blame, bool check_null_id)
4568 report("Commit data not loaded yet");
4569 else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
4570 report("No commit exist for the selected line");
4577 setup_blame_parent_line(struct view *view, struct blame *blame)
4579 const char *diff_tree_argv[] = {
4580 "git", "diff-tree", "-U0", blame->commit->id,
4581 "--", blame->commit->filename, NULL
4584 int parent_lineno = -1;
4585 int blamed_lineno = -1;
4588 if (!run_io(&io, diff_tree_argv, NULL, IO_RD))
4591 while ((line = io_get(&io, '\n', TRUE))) {
4593 char *pos = strchr(line, '+');
4595 parent_lineno = atoi(line + 4);
4597 blamed_lineno = atoi(pos + 1);
4599 } else if (*line == '+' && parent_lineno != -1) {
4600 if (blame->lineno == blamed_lineno - 1 &&
4601 !strcmp(blame->text, line + 1)) {
4602 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
4613 blame_request(struct view *view, enum request request, struct line *line)
4615 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4616 struct blame *blame = line->data;
4619 case REQ_VIEW_BLAME:
4620 if (check_blame_commit(blame, TRUE)) {
4621 string_copy(opt_ref, blame->commit->id);
4622 string_copy(opt_file, blame->commit->filename);
4624 view->lineno = blame->lineno;
4625 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4630 if (check_blame_commit(blame, TRUE) &&
4631 select_commit_parent(blame->commit->id, opt_ref,
4632 blame->commit->filename)) {
4633 string_copy(opt_file, blame->commit->filename);
4634 setup_blame_parent_line(view, blame);
4635 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4640 if (!check_blame_commit(blame, FALSE))
4643 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
4644 !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
4647 if (!strcmp(blame->commit->id, NULL_ID)) {
4648 struct view *diff = VIEW(REQ_VIEW_DIFF);
4649 const char *diff_index_argv[] = {
4650 "git", "diff-index", "--root", "--patch-with-stat",
4651 "-C", "-M", "HEAD", "--", view->vid, NULL
4654 if (!blame->commit->has_previous) {
4655 diff_index_argv[1] = "diff";
4656 diff_index_argv[2] = "--no-color";
4657 diff_index_argv[6] = "--";
4658 diff_index_argv[7] = "/dev/null";
4661 if (!prepare_update(diff, diff_index_argv, NULL, FORMAT_DASH)) {
4662 report("Failed to allocate diff command");
4665 flags |= OPEN_PREPARED;
4668 open_view(view, REQ_VIEW_DIFF, flags);
4669 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
4670 string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
4681 blame_grep(struct view *view, struct line *line)
4683 struct blame *blame = line->data;
4684 struct blame_commit *commit = blame->commit;
4685 const char *text[] = {
4687 commit ? commit->title : "",
4688 commit ? commit->id : "",
4689 commit && opt_author ? commit->author : "",
4690 commit && opt_date ? mkdate(&commit->time) : "",
4694 return grep_text(view, text);
4698 blame_select(struct view *view, struct line *line)
4700 struct blame *blame = line->data;
4701 struct blame_commit *commit = blame->commit;
4706 if (!strcmp(commit->id, NULL_ID))
4707 string_ncopy(ref_commit, "HEAD", 4);
4709 string_copy_rev(ref_commit, commit->id);
4712 static struct view_ops blame_ops = {
4728 const char *author; /* Author of the last commit. */
4729 time_t time; /* Date of the last activity. */
4730 struct ref *ref; /* Name and commit ID information. */
4733 static const enum sort_field branch_sort_fields[] = {
4734 ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4736 static struct sort_state branch_sort_state = SORT_STATE(branch_sort_fields);
4739 branch_compare(const void *l1, const void *l2)
4741 const struct branch *branch1 = ((const struct line *) l1)->data;
4742 const struct branch *branch2 = ((const struct line *) l2)->data;
4744 switch (get_sort_field(branch_sort_state)) {
4746 return sort_order(branch_sort_state, branch1->time - branch2->time);
4748 case ORDERBY_AUTHOR:
4749 return sort_order(branch_sort_state, strcmp(branch1->author, branch2->author));
4753 return sort_order(branch_sort_state, strcmp(branch1->ref->name, branch2->ref->name));
4758 branch_draw(struct view *view, struct line *line, unsigned int lineno)
4760 struct branch *branch = line->data;
4761 enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
4763 if (opt_date && draw_date(view, branch->author ? &branch->time : NULL))
4766 if (opt_author && draw_author(view, branch->author))
4769 draw_text(view, type, branch->ref->name, TRUE);
4774 branch_request(struct view *view, enum request request, struct line *line)
4779 open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
4782 case REQ_TOGGLE_SORT_FIELD:
4783 case REQ_TOGGLE_SORT_ORDER:
4784 sort_view(view, request, &branch_sort_state, branch_compare);
4788 open_view(view, REQ_VIEW_MAIN, OPEN_SPLIT);
4797 branch_read(struct view *view, char *line)
4799 static char id[SIZEOF_REV];
4800 struct branch *reference;
4806 switch (get_line_type(line)) {
4808 string_copy_rev(id, line + STRING_SIZE("commit "));
4812 for (i = 0, reference = NULL; i < view->lines; i++) {
4813 struct branch *branch = view->line[i].data;
4815 if (strcmp(branch->ref->id, id))
4818 view->line[i].dirty = TRUE;
4820 branch->author = reference->author;
4821 branch->time = reference->time;
4825 parse_author_line(line + STRING_SIZE("author "),
4826 &branch->author, &branch->time);
4838 branch_open_visitor(void *data, struct ref *ref)
4840 struct view *view = data;
4841 struct branch *branch;
4843 if (ref->tag || ref->ltag || ref->remote)
4846 branch = calloc(1, sizeof(*branch));
4851 return !!add_line_data(view, branch, LINE_DEFAULT);
4855 branch_open(struct view *view)
4857 const char *branch_log[] = {
4858 "git", "log", "--no-color", "--pretty=raw",
4859 "--simplify-by-decoration", "--all", NULL
4862 if (!run_io_rd(&view->io, branch_log, FORMAT_NONE)) {
4863 report("Failed to load branch data");
4867 setup_update(view, view->id);
4868 foreach_ref(branch_open_visitor, view);
4874 branch_grep(struct view *view, struct line *line)
4876 struct branch *branch = line->data;
4877 const char *text[] = {
4883 return grep_text(view, text);
4887 branch_select(struct view *view, struct line *line)
4889 struct branch *branch = line->data;
4891 string_copy_rev(view->ref, branch->ref->id);
4892 string_copy_rev(ref_commit, branch->ref->id);
4893 string_copy_rev(ref_head, branch->ref->id);
4896 static struct view_ops branch_ops = {
4915 char rev[SIZEOF_REV];
4916 char name[SIZEOF_STR];
4920 char rev[SIZEOF_REV];
4921 char name[SIZEOF_STR];
4925 static char status_onbranch[SIZEOF_STR];
4926 static struct status stage_status;
4927 static enum line_type stage_line_type;
4928 static size_t stage_chunks;
4929 static int *stage_chunk;
4931 DEFINE_ALLOCATOR(realloc_ints, int, 32)
4933 /* This should work even for the "On branch" line. */
4935 status_has_none(struct view *view, struct line *line)
4937 return line < view->line + view->lines && !line[1].data;
4940 /* Get fields from the diff line:
4941 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
4944 status_get_diff(struct status *file, const char *buf, size_t bufsize)
4946 const char *old_mode = buf + 1;
4947 const char *new_mode = buf + 8;
4948 const char *old_rev = buf + 15;
4949 const char *new_rev = buf + 56;
4950 const char *status = buf + 97;
4953 old_mode[-1] != ':' ||
4954 new_mode[-1] != ' ' ||
4955 old_rev[-1] != ' ' ||
4956 new_rev[-1] != ' ' ||
4960 file->status = *status;
4962 string_copy_rev(file->old.rev, old_rev);
4963 string_copy_rev(file->new.rev, new_rev);
4965 file->old.mode = strtoul(old_mode, NULL, 8);
4966 file->new.mode = strtoul(new_mode, NULL, 8);
4968 file->old.name[0] = file->new.name[0] = 0;
4974 status_run(struct view *view, const char *argv[], char status, enum line_type type)
4976 struct status *unmerged = NULL;
4980 if (!run_io(&io, argv, NULL, IO_RD))
4983 add_line_data(view, NULL, type);
4985 while ((buf = io_get(&io, 0, TRUE))) {
4986 struct status *file = unmerged;
4989 file = calloc(1, sizeof(*file));
4990 if (!file || !add_line_data(view, file, type))
4994 /* Parse diff info part. */
4996 file->status = status;
4998 string_copy(file->old.rev, NULL_ID);
5000 } else if (!file->status || file == unmerged) {
5001 if (!status_get_diff(file, buf, strlen(buf)))
5004 buf = io_get(&io, 0, TRUE);
5008 /* Collapse all modified entries that follow an
5009 * associated unmerged entry. */
5010 if (unmerged == file) {
5011 unmerged->status = 'U';
5013 } else if (file->status == 'U') {
5018 /* Grab the old name for rename/copy. */
5019 if (!*file->old.name &&
5020 (file->status == 'R' || file->status == 'C')) {
5021 string_ncopy(file->old.name, buf, strlen(buf));
5023 buf = io_get(&io, 0, TRUE);
5028 /* git-ls-files just delivers a NUL separated list of
5029 * file names similar to the second half of the
5030 * git-diff-* output. */
5031 string_ncopy(file->new.name, buf, strlen(buf));
5032 if (!*file->old.name)
5033 string_copy(file->old.name, file->new.name);
5037 if (io_error(&io)) {
5043 if (!view->line[view->lines - 1].data)
5044 add_line_data(view, NULL, LINE_STAT_NONE);
5050 /* Don't show unmerged entries in the staged section. */
5051 static const char *status_diff_index_argv[] = {
5052 "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5053 "--cached", "-M", "HEAD", NULL
5056 static const char *status_diff_files_argv[] = {
5057 "git", "diff-files", "-z", NULL
5060 static const char *status_list_other_argv[] = {
5061 "git", "ls-files", "-z", "--others", "--exclude-standard", NULL
5064 static const char *status_list_no_head_argv[] = {
5065 "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5068 static const char *update_index_argv[] = {
5069 "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5072 /* Restore the previous line number to stay in the context or select a
5073 * line with something that can be updated. */
5075 status_restore(struct view *view)
5077 if (view->p_lineno >= view->lines)
5078 view->p_lineno = view->lines - 1;
5079 while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
5081 while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
5084 /* If the above fails, always skip the "On branch" line. */
5085 if (view->p_lineno < view->lines)
5086 view->lineno = view->p_lineno;
5090 if (view->lineno < view->offset)
5091 view->offset = view->lineno;
5092 else if (view->offset + view->height <= view->lineno)
5093 view->offset = view->lineno - view->height + 1;
5095 view->p_restore = FALSE;
5099 status_update_onbranch(void)
5101 static const char *paths[][2] = {
5102 { "rebase-apply/rebasing", "Rebasing" },
5103 { "rebase-apply/applying", "Applying mailbox" },
5104 { "rebase-apply/", "Rebasing mailbox" },
5105 { "rebase-merge/interactive", "Interactive rebase" },
5106 { "rebase-merge/", "Rebase merge" },
5107 { "MERGE_HEAD", "Merging" },
5108 { "BISECT_LOG", "Bisecting" },
5109 { "HEAD", "On branch" },
5111 char buf[SIZEOF_STR];
5115 if (is_initial_commit()) {
5116 string_copy(status_onbranch, "Initial commit");
5120 for (i = 0; i < ARRAY_SIZE(paths); i++) {
5121 char *head = opt_head;
5123 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5124 lstat(buf, &stat) < 0)
5130 if (string_format(buf, "%s/rebase-merge/head-name", opt_git_dir) &&
5131 io_open(&io, buf) &&
5132 io_read_buf(&io, buf, sizeof(buf))) {
5134 if (!prefixcmp(head, "refs/heads/"))
5135 head += STRING_SIZE("refs/heads/");
5139 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5140 string_copy(status_onbranch, opt_head);
5144 string_copy(status_onbranch, "Not currently on any branch");
5147 /* First parse staged info using git-diff-index(1), then parse unstaged
5148 * info using git-diff-files(1), and finally untracked files using
5149 * git-ls-files(1). */
5151 status_open(struct view *view)
5155 add_line_data(view, NULL, LINE_STAT_HEAD);
5156 status_update_onbranch();
5158 run_io_bg(update_index_argv);
5160 if (is_initial_commit()) {
5161 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5163 } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5167 if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5168 !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5171 /* Restore the exact position or use the specialized restore
5173 if (!view->p_restore)
5174 status_restore(view);
5179 status_draw(struct view *view, struct line *line, unsigned int lineno)
5181 struct status *status = line->data;
5182 enum line_type type;
5186 switch (line->type) {
5187 case LINE_STAT_STAGED:
5188 type = LINE_STAT_SECTION;
5189 text = "Changes to be committed:";
5192 case LINE_STAT_UNSTAGED:
5193 type = LINE_STAT_SECTION;
5194 text = "Changed but not updated:";
5197 case LINE_STAT_UNTRACKED:
5198 type = LINE_STAT_SECTION;
5199 text = "Untracked files:";
5202 case LINE_STAT_NONE:
5203 type = LINE_DEFAULT;
5204 text = " (no files)";
5207 case LINE_STAT_HEAD:
5208 type = LINE_STAT_HEAD;
5209 text = status_onbranch;
5216 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5218 buf[0] = status->status;
5219 if (draw_text(view, line->type, buf, TRUE))
5221 type = LINE_DEFAULT;
5222 text = status->new.name;
5225 draw_text(view, type, text, TRUE);
5230 status_load_error(struct view *view, struct view *stage, const char *path)
5232 if (displayed_views() == 2 || display[current_view] != view)
5233 maximize_view(view);
5234 report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5239 status_enter(struct view *view, struct line *line)
5241 struct status *status = line->data;
5242 const char *oldpath = status ? status->old.name : NULL;
5243 /* Diffs for unmerged entries are empty when passing the new
5244 * path, so leave it empty. */
5245 const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5247 enum open_flags split;
5248 struct view *stage = VIEW(REQ_VIEW_STAGE);
5250 if (line->type == LINE_STAT_NONE ||
5251 (!status && line[1].type == LINE_STAT_NONE)) {
5252 report("No file to diff");
5256 switch (line->type) {
5257 case LINE_STAT_STAGED:
5258 if (is_initial_commit()) {
5259 const char *no_head_diff_argv[] = {
5260 "git", "diff", "--no-color", "--patch-with-stat",
5261 "--", "/dev/null", newpath, NULL
5264 if (!prepare_update(stage, no_head_diff_argv, opt_cdup, FORMAT_DASH))
5265 return status_load_error(view, stage, newpath);
5267 const char *index_show_argv[] = {
5268 "git", "diff-index", "--root", "--patch-with-stat",
5269 "-C", "-M", "--cached", "HEAD", "--",
5270 oldpath, newpath, NULL
5273 if (!prepare_update(stage, index_show_argv, opt_cdup, FORMAT_DASH))
5274 return status_load_error(view, stage, newpath);
5278 info = "Staged changes to %s";
5280 info = "Staged changes";
5283 case LINE_STAT_UNSTAGED:
5285 const char *files_show_argv[] = {
5286 "git", "diff-files", "--root", "--patch-with-stat",
5287 "-C", "-M", "--", oldpath, newpath, NULL
5290 if (!prepare_update(stage, files_show_argv, opt_cdup, FORMAT_DASH))
5291 return status_load_error(view, stage, newpath);
5293 info = "Unstaged changes to %s";
5295 info = "Unstaged changes";
5298 case LINE_STAT_UNTRACKED:
5300 report("No file to show");
5304 if (!suffixcmp(status->new.name, -1, "/")) {
5305 report("Cannot display a directory");
5309 if (!prepare_update_file(stage, newpath))
5310 return status_load_error(view, stage, newpath);
5311 info = "Untracked file %s";
5314 case LINE_STAT_HEAD:
5318 die("line type %d not handled in switch", line->type);
5321 split = view_is_displayed(view) ? OPEN_SPLIT : 0;
5322 open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5323 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5325 stage_status = *status;
5327 memset(&stage_status, 0, sizeof(stage_status));
5330 stage_line_type = line->type;
5332 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5339 status_exists(struct status *status, enum line_type type)
5341 struct view *view = VIEW(REQ_VIEW_STATUS);
5342 unsigned long lineno;
5344 for (lineno = 0; lineno < view->lines; lineno++) {
5345 struct line *line = &view->line[lineno];
5346 struct status *pos = line->data;
5348 if (line->type != type)
5350 if (!pos && (!status || !status->status) && line[1].data) {
5351 select_view_line(view, lineno);
5354 if (pos && !strcmp(status->new.name, pos->new.name)) {
5355 select_view_line(view, lineno);
5365 status_update_prepare(struct io *io, enum line_type type)
5367 const char *staged_argv[] = {
5368 "git", "update-index", "-z", "--index-info", NULL
5370 const char *others_argv[] = {
5371 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5375 case LINE_STAT_STAGED:
5376 return run_io(io, staged_argv, opt_cdup, IO_WR);
5378 case LINE_STAT_UNSTAGED:
5379 return run_io(io, others_argv, opt_cdup, IO_WR);
5381 case LINE_STAT_UNTRACKED:
5382 return run_io(io, others_argv, NULL, IO_WR);
5385 die("line type %d not handled in switch", type);
5391 status_update_write(struct io *io, struct status *status, enum line_type type)
5393 char buf[SIZEOF_STR];
5397 case LINE_STAT_STAGED:
5398 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
5401 status->old.name, 0))
5405 case LINE_STAT_UNSTAGED:
5406 case LINE_STAT_UNTRACKED:
5407 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
5412 die("line type %d not handled in switch", type);
5415 return io_write(io, buf, bufsize);
5419 status_update_file(struct status *status, enum line_type type)
5424 if (!status_update_prepare(&io, type))
5427 result = status_update_write(&io, status, type);
5428 return done_io(&io) && result;
5432 status_update_files(struct view *view, struct line *line)
5434 char buf[sizeof(view->ref)];
5437 struct line *pos = view->line + view->lines;
5440 int cursor_y, cursor_x;
5442 if (!status_update_prepare(&io, line->type))
5445 for (pos = line; pos < view->line + view->lines && pos->data; pos++)
5448 string_copy(buf, view->ref);
5449 getsyx(cursor_y, cursor_x);
5450 for (file = 0, done = 5; result && file < files; line++, file++) {
5451 int almost_done = file * 100 / files;
5453 if (almost_done > done) {
5455 string_format(view->ref, "updating file %u of %u (%d%% done)",
5457 update_view_title(view);
5458 setsyx(cursor_y, cursor_x);
5461 result = status_update_write(&io, line->data, line->type);
5463 string_copy(view->ref, buf);
5465 return done_io(&io) && result;
5469 status_update(struct view *view)
5471 struct line *line = &view->line[view->lineno];
5473 assert(view->lines);
5476 /* This should work even for the "On branch" line. */
5477 if (line < view->line + view->lines && !line[1].data) {
5478 report("Nothing to update");
5482 if (!status_update_files(view, line + 1)) {
5483 report("Failed to update file status");
5487 } else if (!status_update_file(line->data, line->type)) {
5488 report("Failed to update file status");
5496 status_revert(struct status *status, enum line_type type, bool has_none)
5498 if (!status || type != LINE_STAT_UNSTAGED) {
5499 if (type == LINE_STAT_STAGED) {
5500 report("Cannot revert changes to staged files");
5501 } else if (type == LINE_STAT_UNTRACKED) {
5502 report("Cannot revert changes to untracked files");
5503 } else if (has_none) {
5504 report("Nothing to revert");
5506 report("Cannot revert changes to multiple files");
5511 char mode[10] = "100644";
5512 const char *reset_argv[] = {
5513 "git", "update-index", "--cacheinfo", mode,
5514 status->old.rev, status->old.name, NULL
5516 const char *checkout_argv[] = {
5517 "git", "checkout", "--", status->old.name, NULL
5520 if (!prompt_yesno("Are you sure you want to overwrite any changes?"))
5522 string_format(mode, "%o", status->old.mode);
5523 return (status->status != 'U' || run_io_fg(reset_argv, opt_cdup)) &&
5524 run_io_fg(checkout_argv, opt_cdup);
5529 status_request(struct view *view, enum request request, struct line *line)
5531 struct status *status = line->data;
5534 case REQ_STATUS_UPDATE:
5535 if (!status_update(view))
5539 case REQ_STATUS_REVERT:
5540 if (!status_revert(status, line->type, status_has_none(view, line)))
5544 case REQ_STATUS_MERGE:
5545 if (!status || status->status != 'U') {
5546 report("Merging only possible for files with unmerged status ('U').");
5549 open_mergetool(status->new.name);
5555 if (status->status == 'D') {
5556 report("File has been deleted.");
5560 open_editor(status->status != '?', status->new.name);
5563 case REQ_VIEW_BLAME:
5565 string_copy(opt_file, status->new.name);
5571 /* After returning the status view has been split to
5572 * show the stage view. No further reloading is
5574 return status_enter(view, line);
5577 /* Simply reload the view. */
5584 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
5590 status_select(struct view *view, struct line *line)
5592 struct status *status = line->data;
5593 char file[SIZEOF_STR] = "all files";
5597 if (status && !string_format(file, "'%s'", status->new.name))
5600 if (!status && line[1].type == LINE_STAT_NONE)
5603 switch (line->type) {
5604 case LINE_STAT_STAGED:
5605 text = "Press %s to unstage %s for commit";
5608 case LINE_STAT_UNSTAGED:
5609 text = "Press %s to stage %s for commit";
5612 case LINE_STAT_UNTRACKED:
5613 text = "Press %s to stage %s for addition";
5616 case LINE_STAT_HEAD:
5617 case LINE_STAT_NONE:
5618 text = "Nothing to update";
5622 die("line type %d not handled in switch", line->type);
5625 if (status && status->status == 'U') {
5626 text = "Press %s to resolve conflict in %s";
5627 key = get_key(REQ_STATUS_MERGE);
5630 key = get_key(REQ_STATUS_UPDATE);
5633 string_format(view->ref, text, key, file);
5637 status_grep(struct view *view, struct line *line)
5639 struct status *status = line->data;
5642 const char buf[2] = { status->status, 0 };
5643 const char *text[] = { status->new.name, buf, NULL };
5645 return grep_text(view, text);
5651 static struct view_ops status_ops = {
5664 stage_diff_write(struct io *io, struct line *line, struct line *end)
5666 while (line < end) {
5667 if (!io_write(io, line->data, strlen(line->data)) ||
5668 !io_write(io, "\n", 1))
5671 if (line->type == LINE_DIFF_CHUNK ||
5672 line->type == LINE_DIFF_HEADER)
5679 static struct line *
5680 stage_diff_find(struct view *view, struct line *line, enum line_type type)
5682 for (; view->line < line; line--)
5683 if (line->type == type)
5690 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
5692 const char *apply_argv[SIZEOF_ARG] = {
5693 "git", "apply", "--whitespace=nowarn", NULL
5695 struct line *diff_hdr;
5699 diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
5704 apply_argv[argc++] = "--cached";
5705 if (revert || stage_line_type == LINE_STAT_STAGED)
5706 apply_argv[argc++] = "-R";
5707 apply_argv[argc++] = "-";
5708 apply_argv[argc++] = NULL;
5709 if (!run_io(&io, apply_argv, opt_cdup, IO_WR))
5712 if (!stage_diff_write(&io, diff_hdr, chunk) ||
5713 !stage_diff_write(&io, chunk, view->line + view->lines))
5717 run_io_bg(update_index_argv);
5719 return chunk ? TRUE : FALSE;
5723 stage_update(struct view *view, struct line *line)
5725 struct line *chunk = NULL;
5727 if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
5728 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5731 if (!stage_apply_chunk(view, chunk, FALSE)) {
5732 report("Failed to apply chunk");
5736 } else if (!stage_status.status) {
5737 view = VIEW(REQ_VIEW_STATUS);
5739 for (line = view->line; line < view->line + view->lines; line++)
5740 if (line->type == stage_line_type)
5743 if (!status_update_files(view, line + 1)) {
5744 report("Failed to update files");
5748 } else if (!status_update_file(&stage_status, stage_line_type)) {
5749 report("Failed to update file");
5757 stage_revert(struct view *view, struct line *line)
5759 struct line *chunk = NULL;
5761 if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
5762 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5765 if (!prompt_yesno("Are you sure you want to revert changes?"))
5768 if (!stage_apply_chunk(view, chunk, TRUE)) {
5769 report("Failed to revert chunk");
5775 return status_revert(stage_status.status ? &stage_status : NULL,
5776 stage_line_type, FALSE);
5782 stage_next(struct view *view, struct line *line)
5786 if (!stage_chunks) {
5787 for (line = view->line; line < view->line + view->lines; line++) {
5788 if (line->type != LINE_DIFF_CHUNK)
5791 if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
5792 report("Allocation failure");
5796 stage_chunk[stage_chunks++] = line - view->line;
5800 for (i = 0; i < stage_chunks; i++) {
5801 if (stage_chunk[i] > view->lineno) {
5802 do_scroll_view(view, stage_chunk[i] - view->lineno);
5803 report("Chunk %d of %d", i + 1, stage_chunks);
5808 report("No next chunk found");
5812 stage_request(struct view *view, enum request request, struct line *line)
5815 case REQ_STATUS_UPDATE:
5816 if (!stage_update(view, line))
5820 case REQ_STATUS_REVERT:
5821 if (!stage_revert(view, line))
5825 case REQ_STAGE_NEXT:
5826 if (stage_line_type == LINE_STAT_UNTRACKED) {
5827 report("File is untracked; press %s to add",
5828 get_key(REQ_STATUS_UPDATE));
5831 stage_next(view, line);
5835 if (!stage_status.new.name[0])
5837 if (stage_status.status == 'D') {
5838 report("File has been deleted.");
5842 open_editor(stage_status.status != '?', stage_status.new.name);
5846 /* Reload everything ... */
5849 case REQ_VIEW_BLAME:
5850 if (stage_status.new.name[0]) {
5851 string_copy(opt_file, stage_status.new.name);
5857 return pager_request(view, request, line);
5863 VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
5864 open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
5866 /* Check whether the staged entry still exists, and close the
5867 * stage view if it doesn't. */
5868 if (!status_exists(&stage_status, stage_line_type)) {
5869 status_restore(VIEW(REQ_VIEW_STATUS));
5870 return REQ_VIEW_CLOSE;
5873 if (stage_line_type == LINE_STAT_UNTRACKED) {
5874 if (!suffixcmp(stage_status.new.name, -1, "/")) {
5875 report("Cannot display a directory");
5879 if (!prepare_update_file(view, stage_status.new.name)) {
5880 report("Failed to open file: %s", strerror(errno));
5884 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
5889 static struct view_ops stage_ops = {
5906 char id[SIZEOF_REV]; /* SHA1 ID. */
5907 char title[128]; /* First line of the commit message. */
5908 const char *author; /* Author of the commit. */
5909 time_t time; /* Date from the author ident. */
5910 struct ref_list *refs; /* Repository references. */
5911 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
5912 size_t graph_size; /* The width of the graph array. */
5913 bool has_parents; /* Rewritten --parents seen. */
5916 /* Size of rev graph with no "padding" columns */
5917 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
5920 struct rev_graph *prev, *next, *parents;
5921 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
5923 struct commit *commit;
5925 unsigned int boundary:1;
5928 /* Parents of the commit being visualized. */
5929 static struct rev_graph graph_parents[4];
5931 /* The current stack of revisions on the graph. */
5932 static struct rev_graph graph_stacks[4] = {
5933 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
5934 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
5935 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
5936 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
5940 graph_parent_is_merge(struct rev_graph *graph)
5942 return graph->parents->size > 1;
5946 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
5948 struct commit *commit = graph->commit;
5950 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
5951 commit->graph[commit->graph_size++] = symbol;
5955 clear_rev_graph(struct rev_graph *graph)
5957 graph->boundary = 0;
5958 graph->size = graph->pos = 0;
5959 graph->commit = NULL;
5960 memset(graph->parents, 0, sizeof(*graph->parents));
5964 done_rev_graph(struct rev_graph *graph)
5966 if (graph_parent_is_merge(graph) &&
5967 graph->pos < graph->size - 1 &&
5968 graph->next->size == graph->size + graph->parents->size - 1) {
5969 size_t i = graph->pos + graph->parents->size - 1;
5971 graph->commit->graph_size = i * 2;
5972 while (i < graph->next->size - 1) {
5973 append_to_rev_graph(graph, ' ');
5974 append_to_rev_graph(graph, '\\');
5979 clear_rev_graph(graph);
5983 push_rev_graph(struct rev_graph *graph, const char *parent)
5987 /* "Collapse" duplicate parents lines.
5989 * FIXME: This needs to also update update the drawn graph but
5990 * for now it just serves as a method for pruning graph lines. */
5991 for (i = 0; i < graph->size; i++)
5992 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
5995 if (graph->size < SIZEOF_REVITEMS) {
5996 string_copy_rev(graph->rev[graph->size++], parent);
6001 get_rev_graph_symbol(struct rev_graph *graph)
6005 if (graph->boundary)
6006 symbol = REVGRAPH_BOUND;
6007 else if (graph->parents->size == 0)
6008 symbol = REVGRAPH_INIT;
6009 else if (graph_parent_is_merge(graph))
6010 symbol = REVGRAPH_MERGE;
6011 else if (graph->pos >= graph->size)
6012 symbol = REVGRAPH_BRANCH;
6014 symbol = REVGRAPH_COMMIT;
6020 draw_rev_graph(struct rev_graph *graph)
6023 chtype separator, line;
6025 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
6026 static struct rev_filler fillers[] = {
6032 chtype symbol = get_rev_graph_symbol(graph);
6033 struct rev_filler *filler;
6036 if (opt_line_graphics)
6037 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
6039 filler = &fillers[DEFAULT];
6041 for (i = 0; i < graph->pos; i++) {
6042 append_to_rev_graph(graph, filler->line);
6043 if (graph_parent_is_merge(graph->prev) &&
6044 graph->prev->pos == i)
6045 filler = &fillers[RSHARP];
6047 append_to_rev_graph(graph, filler->separator);
6050 /* Place the symbol for this revision. */
6051 append_to_rev_graph(graph, symbol);
6053 if (graph->prev->size > graph->size)
6054 filler = &fillers[RDIAG];
6056 filler = &fillers[DEFAULT];
6060 for (; i < graph->size; i++) {
6061 append_to_rev_graph(graph, filler->separator);
6062 append_to_rev_graph(graph, filler->line);
6063 if (graph_parent_is_merge(graph->prev) &&
6064 i < graph->prev->pos + graph->parents->size)
6065 filler = &fillers[RSHARP];
6066 if (graph->prev->size > graph->size)
6067 filler = &fillers[LDIAG];
6070 if (graph->prev->size > graph->size) {
6071 append_to_rev_graph(graph, filler->separator);
6072 if (filler->line != ' ')
6073 append_to_rev_graph(graph, filler->line);
6077 /* Prepare the next rev graph */
6079 prepare_rev_graph(struct rev_graph *graph)
6083 /* First, traverse all lines of revisions up to the active one. */
6084 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6085 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6088 push_rev_graph(graph->next, graph->rev[graph->pos]);
6091 /* Interleave the new revision parent(s). */
6092 for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6093 push_rev_graph(graph->next, graph->parents->rev[i]);
6095 /* Lastly, put any remaining revisions. */
6096 for (i = graph->pos + 1; i < graph->size; i++)
6097 push_rev_graph(graph->next, graph->rev[i]);
6101 update_rev_graph(struct view *view, struct rev_graph *graph)
6103 /* If this is the finalizing update ... */
6105 prepare_rev_graph(graph);
6107 /* Graph visualization needs a one rev look-ahead,
6108 * so the first update doesn't visualize anything. */
6109 if (!graph->prev->commit)
6112 if (view->lines > 2)
6113 view->line[view->lines - 3].dirty = 1;
6114 if (view->lines > 1)
6115 view->line[view->lines - 2].dirty = 1;
6116 draw_rev_graph(graph->prev);
6117 done_rev_graph(graph->prev->prev);
6125 static const char *main_argv[SIZEOF_ARG] = {
6126 "git", "log", "--no-color", "--pretty=raw", "--parents",
6127 "--topo-order", "%(head)", NULL
6131 main_draw(struct view *view, struct line *line, unsigned int lineno)
6133 struct commit *commit = line->data;
6135 if (!commit->author)
6138 if (opt_date && draw_date(view, &commit->time))
6141 if (opt_author && draw_author(view, commit->author))
6144 if (opt_rev_graph && commit->graph_size &&
6145 draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6148 if (opt_show_refs && commit->refs) {
6151 for (i = 0; i < commit->refs->size; i++) {
6152 struct ref *ref = commit->refs->refs[i];
6153 enum line_type type;
6156 type = LINE_MAIN_HEAD;
6158 type = LINE_MAIN_LOCAL_TAG;
6160 type = LINE_MAIN_TAG;
6161 else if (ref->tracked)
6162 type = LINE_MAIN_TRACKED;
6163 else if (ref->remote)
6164 type = LINE_MAIN_REMOTE;
6166 type = LINE_MAIN_REF;
6168 if (draw_text(view, type, "[", TRUE) ||
6169 draw_text(view, type, ref->name, TRUE) ||
6170 draw_text(view, type, "]", TRUE))
6173 if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6178 draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6182 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6184 main_read(struct view *view, char *line)
6186 static struct rev_graph *graph = graph_stacks;
6187 enum line_type type;
6188 struct commit *commit;
6193 if (!view->lines && !view->parent)
6194 die("No revisions match the given arguments.");
6195 if (view->lines > 0) {
6196 commit = view->line[view->lines - 1].data;
6197 view->line[view->lines - 1].dirty = 1;
6198 if (!commit->author) {
6201 graph->commit = NULL;
6204 update_rev_graph(view, graph);
6206 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6207 clear_rev_graph(&graph_stacks[i]);
6211 type = get_line_type(line);
6212 if (type == LINE_COMMIT) {
6213 commit = calloc(1, sizeof(struct commit));
6217 line += STRING_SIZE("commit ");
6219 graph->boundary = 1;
6223 string_copy_rev(commit->id, line);
6224 commit->refs = get_ref_list(commit->id);
6225 graph->commit = commit;
6226 add_line_data(view, commit, LINE_MAIN_COMMIT);
6228 while ((line = strchr(line, ' '))) {
6230 push_rev_graph(graph->parents, line);
6231 commit->has_parents = TRUE;
6238 commit = view->line[view->lines - 1].data;
6242 if (commit->has_parents)
6244 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6248 parse_author_line(line + STRING_SIZE("author "),
6249 &commit->author, &commit->time);
6250 update_rev_graph(view, graph);
6251 graph = graph->next;
6255 /* Fill in the commit title if it has not already been set. */
6256 if (commit->title[0])
6259 /* Require titles to start with a non-space character at the
6260 * offset used by git log. */
6261 if (strncmp(line, " ", 4))
6264 /* Well, if the title starts with a whitespace character,
6265 * try to be forgiving. Otherwise we end up with no title. */
6266 while (isspace(*line))
6270 /* FIXME: More graceful handling of titles; append "..." to
6271 * shortened titles, etc. */
6273 string_expand(commit->title, sizeof(commit->title), line, 1);
6274 view->line[view->lines - 1].dirty = 1;
6281 main_request(struct view *view, enum request request, struct line *line)
6283 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
6287 open_view(view, REQ_VIEW_DIFF, flags);
6291 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6301 grep_refs(struct ref_list *list, regex_t *regex)
6306 if (!opt_show_refs || !list)
6309 for (i = 0; i < list->size; i++) {
6310 if (regexec(regex, list->refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6318 main_grep(struct view *view, struct line *line)
6320 struct commit *commit = line->data;
6321 const char *text[] = {
6323 opt_author ? commit->author : "",
6324 opt_date ? mkdate(&commit->time) : "",
6328 return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6332 main_select(struct view *view, struct line *line)
6334 struct commit *commit = line->data;
6336 string_copy_rev(view->ref, commit->id);
6337 string_copy_rev(ref_commit, view->ref);
6340 static struct view_ops main_ops = {
6353 * Unicode / UTF-8 handling
6355 * NOTE: Much of the following code for dealing with Unicode is derived from
6356 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
6357 * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
6361 unicode_width(unsigned long c)
6364 (c <= 0x115f /* Hangul Jamo */
6367 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
6369 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
6370 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
6371 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
6372 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
6373 || (c >= 0xffe0 && c <= 0xffe6)
6374 || (c >= 0x20000 && c <= 0x2fffd)
6375 || (c >= 0x30000 && c <= 0x3fffd)))
6379 return opt_tab_size;
6384 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
6385 * Illegal bytes are set one. */
6386 static const unsigned char utf8_bytes[256] = {
6387 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,
6388 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,
6389 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,
6390 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,
6391 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,
6392 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,
6393 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,
6394 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,
6397 /* Decode UTF-8 multi-byte representation into a Unicode character. */
6398 static inline unsigned long
6399 utf8_to_unicode(const char *string, size_t length)
6401 unsigned long unicode;
6405 unicode = string[0];
6408 unicode = (string[0] & 0x1f) << 6;
6409 unicode += (string[1] & 0x3f);
6412 unicode = (string[0] & 0x0f) << 12;
6413 unicode += ((string[1] & 0x3f) << 6);
6414 unicode += (string[2] & 0x3f);
6417 unicode = (string[0] & 0x0f) << 18;
6418 unicode += ((string[1] & 0x3f) << 12);
6419 unicode += ((string[2] & 0x3f) << 6);
6420 unicode += (string[3] & 0x3f);
6423 unicode = (string[0] & 0x0f) << 24;
6424 unicode += ((string[1] & 0x3f) << 18);
6425 unicode += ((string[2] & 0x3f) << 12);
6426 unicode += ((string[3] & 0x3f) << 6);
6427 unicode += (string[4] & 0x3f);
6430 unicode = (string[0] & 0x01) << 30;
6431 unicode += ((string[1] & 0x3f) << 24);
6432 unicode += ((string[2] & 0x3f) << 18);
6433 unicode += ((string[3] & 0x3f) << 12);
6434 unicode += ((string[4] & 0x3f) << 6);
6435 unicode += (string[5] & 0x3f);
6438 die("Invalid Unicode length");
6441 /* Invalid characters could return the special 0xfffd value but NUL
6442 * should be just as good. */
6443 return unicode > 0xffff ? 0 : unicode;
6446 /* Calculates how much of string can be shown within the given maximum width
6447 * and sets trimmed parameter to non-zero value if all of string could not be
6448 * shown. If the reserve flag is TRUE, it will reserve at least one
6449 * trailing character, which can be useful when drawing a delimiter.
6451 * Returns the number of bytes to output from string to satisfy max_width. */
6453 utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve)
6455 const char *string = *start;
6456 const char *end = strchr(string, '\0');
6457 unsigned char last_bytes = 0;
6458 size_t last_ucwidth = 0;
6463 while (string < end) {
6464 int c = *(unsigned char *) string;
6465 unsigned char bytes = utf8_bytes[c];
6467 unsigned long unicode;
6469 if (string + bytes > end)
6472 /* Change representation to figure out whether
6473 * it is a single- or double-width character. */
6475 unicode = utf8_to_unicode(string, bytes);
6476 /* FIXME: Graceful handling of invalid Unicode character. */
6480 ucwidth = unicode_width(unicode);
6482 skip -= ucwidth <= skip ? ucwidth : skip;
6486 if (*width > max_width) {
6489 if (reserve && *width == max_width) {
6490 string -= last_bytes;
6491 *width -= last_ucwidth;
6497 last_bytes = ucwidth ? bytes : 0;
6498 last_ucwidth = ucwidth;
6501 return string - *start;
6509 /* Whether or not the curses interface has been initialized. */
6510 static bool cursed = FALSE;
6512 /* Terminal hacks and workarounds. */
6513 static bool use_scroll_redrawwin;
6514 static bool use_scroll_status_wclear;
6516 /* The status window is used for polling keystrokes. */
6517 static WINDOW *status_win;
6519 /* Reading from the prompt? */
6520 static bool input_mode = FALSE;
6522 static bool status_empty = FALSE;
6524 /* Update status and title window. */
6526 report(const char *msg, ...)
6528 struct view *view = display[current_view];
6534 char buf[SIZEOF_STR];
6537 va_start(args, msg);
6538 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6539 buf[sizeof(buf) - 1] = 0;
6540 buf[sizeof(buf) - 2] = '.';
6541 buf[sizeof(buf) - 3] = '.';
6542 buf[sizeof(buf) - 4] = '.';
6548 if (!status_empty || *msg) {
6551 va_start(args, msg);
6553 wmove(status_win, 0, 0);
6554 if (view->has_scrolled && use_scroll_status_wclear)
6557 vwprintw(status_win, msg, args);
6558 status_empty = FALSE;
6560 status_empty = TRUE;
6562 wclrtoeol(status_win);
6563 wnoutrefresh(status_win);
6568 update_view_title(view);
6571 /* Controls when nodelay should be in effect when polling user input. */
6573 set_nonblocking_input(bool loading)
6575 static unsigned int loading_views;
6577 if ((loading == FALSE && loading_views-- == 1) ||
6578 (loading == TRUE && loading_views++ == 0))
6579 nodelay(status_win, loading);
6588 /* Initialize the curses library */
6589 if (isatty(STDIN_FILENO)) {
6590 cursed = !!initscr();
6593 /* Leave stdin and stdout alone when acting as a pager. */
6594 opt_tty = fopen("/dev/tty", "r+");
6596 die("Failed to open /dev/tty");
6597 cursed = !!newterm(NULL, opt_tty, opt_tty);
6601 die("Failed to initialize curses");
6603 nonl(); /* Disable conversion and detect newlines from input. */
6604 cbreak(); /* Take input chars one at a time, no wait for \n */
6605 noecho(); /* Don't echo input */
6606 leaveok(stdscr, FALSE);
6611 getmaxyx(stdscr, y, x);
6612 status_win = newwin(1, 0, y - 1, 0);
6614 die("Failed to create status window");
6616 /* Enable keyboard mapping */
6617 keypad(status_win, TRUE);
6618 wbkgdset(status_win, get_line_attr(LINE_STATUS));
6620 TABSIZE = opt_tab_size;
6621 if (opt_line_graphics) {
6622 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
6625 term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
6626 if (term && !strcmp(term, "gnome-terminal")) {
6627 /* In the gnome-terminal-emulator, the message from
6628 * scrolling up one line when impossible followed by
6629 * scrolling down one line causes corruption of the
6630 * status line. This is fixed by calling wclear. */
6631 use_scroll_status_wclear = TRUE;
6632 use_scroll_redrawwin = FALSE;
6634 } else if (term && !strcmp(term, "xrvt-xpm")) {
6635 /* No problems with full optimizations in xrvt-(unicode)
6637 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
6640 /* When scrolling in (u)xterm the last line in the
6641 * scrolling direction will update slowly. */
6642 use_scroll_redrawwin = TRUE;
6643 use_scroll_status_wclear = FALSE;
6648 get_input(int prompt_position)
6651 int i, key, cursor_y, cursor_x;
6653 if (prompt_position)
6657 foreach_view (view, i) {
6659 if (view_is_displayed(view) && view->has_scrolled &&
6660 use_scroll_redrawwin)
6661 redrawwin(view->win);
6662 view->has_scrolled = FALSE;
6665 /* Update the cursor position. */
6666 if (prompt_position) {
6667 getbegyx(status_win, cursor_y, cursor_x);
6668 cursor_x = prompt_position;
6670 view = display[current_view];
6671 getbegyx(view->win, cursor_y, cursor_x);
6672 cursor_x = view->width - 1;
6673 cursor_y += view->lineno - view->offset;
6675 setsyx(cursor_y, cursor_x);
6677 /* Refresh, accept single keystroke of input */
6679 key = wgetch(status_win);
6681 /* wgetch() with nodelay() enabled returns ERR when
6682 * there's no input. */
6685 } else if (key == KEY_RESIZE) {
6688 getmaxyx(stdscr, height, width);
6690 wresize(status_win, 1, width);
6691 mvwin(status_win, height - 1, 0);
6692 wnoutrefresh(status_win);
6694 redraw_display(TRUE);
6704 prompt_input(const char *prompt, input_handler handler, void *data)
6706 enum input_status status = INPUT_OK;
6707 static char buf[SIZEOF_STR];
6712 while (status == INPUT_OK || status == INPUT_SKIP) {
6715 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
6716 wclrtoeol(status_win);
6718 key = get_input(pos + 1);
6723 status = pos ? INPUT_STOP : INPUT_CANCEL;
6730 status = INPUT_CANCEL;
6734 status = INPUT_CANCEL;
6738 if (pos >= sizeof(buf)) {
6739 report("Input string too long");
6743 status = handler(data, buf, key);
6744 if (status == INPUT_OK)
6745 buf[pos++] = (char) key;
6749 /* Clear the status window */
6750 status_empty = FALSE;
6753 if (status == INPUT_CANCEL)
6761 static enum input_status
6762 prompt_yesno_handler(void *data, char *buf, int c)
6764 if (c == 'y' || c == 'Y')
6766 if (c == 'n' || c == 'N')
6767 return INPUT_CANCEL;
6772 prompt_yesno(const char *prompt)
6774 char prompt2[SIZEOF_STR];
6776 if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
6779 return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
6782 static enum input_status
6783 read_prompt_handler(void *data, char *buf, int c)
6785 return isprint(c) ? INPUT_OK : INPUT_SKIP;
6789 read_prompt(const char *prompt)
6791 return prompt_input(prompt, read_prompt_handler, NULL);
6795 * Repository properties
6798 static struct ref **refs = NULL;
6799 static size_t refs_size = 0;
6801 static struct ref_list **ref_lists = NULL;
6802 static size_t ref_lists_size = 0;
6804 DEFINE_ALLOCATOR(realloc_refs, struct ref *, 256)
6805 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
6806 DEFINE_ALLOCATOR(realloc_ref_lists, struct ref_list *, 8)
6809 compare_refs(const void *ref1_, const void *ref2_)
6811 const struct ref *ref1 = *(const struct ref **)ref1_;
6812 const struct ref *ref2 = *(const struct ref **)ref2_;
6814 if (ref1->tag != ref2->tag)
6815 return ref2->tag - ref1->tag;
6816 if (ref1->ltag != ref2->ltag)
6817 return ref2->ltag - ref2->ltag;
6818 if (ref1->head != ref2->head)
6819 return ref2->head - ref1->head;
6820 if (ref1->tracked != ref2->tracked)
6821 return ref2->tracked - ref1->tracked;
6822 if (ref1->remote != ref2->remote)
6823 return ref2->remote - ref1->remote;
6824 return strcmp(ref1->name, ref2->name);
6828 foreach_ref(bool (*visitor)(void *data, struct ref *ref), void *data)
6832 for (i = 0; i < refs_size; i++)
6833 if (!visitor(data, refs[i]))
6837 static struct ref_list *
6838 get_ref_list(const char *id)
6840 struct ref_list *list;
6843 for (i = 0; i < ref_lists_size; i++)
6844 if (!strcmp(id, ref_lists[i]->id))
6845 return ref_lists[i];
6847 if (!realloc_ref_lists(&ref_lists, ref_lists_size, 1))
6849 list = calloc(1, sizeof(*list));
6853 for (i = 0; i < refs_size; i++) {
6854 if (!strcmp(id, refs[i]->id) &&
6855 realloc_refs_list(&list->refs, list->size, 1))
6856 list->refs[list->size++] = refs[i];
6864 qsort(list->refs, list->size, sizeof(*list->refs), compare_refs);
6865 ref_lists[ref_lists_size++] = list;
6870 read_ref(char *id, size_t idlen, char *name, size_t namelen)
6872 struct ref *ref = NULL;
6875 bool remote = FALSE;
6876 bool tracked = FALSE;
6878 int from = 0, to = refs_size - 1;
6880 if (!prefixcmp(name, "refs/tags/")) {
6881 if (!suffixcmp(name, namelen, "^{}")) {
6889 namelen -= STRING_SIZE("refs/tags/");
6890 name += STRING_SIZE("refs/tags/");
6892 } else if (!prefixcmp(name, "refs/remotes/")) {
6894 namelen -= STRING_SIZE("refs/remotes/");
6895 name += STRING_SIZE("refs/remotes/");
6896 tracked = !strcmp(opt_remote, name);
6898 } else if (!prefixcmp(name, "refs/heads/")) {
6899 namelen -= STRING_SIZE("refs/heads/");
6900 name += STRING_SIZE("refs/heads/");
6901 head = !strncmp(opt_head, name, namelen);
6903 } else if (!strcmp(name, "HEAD")) {
6904 string_ncopy(opt_head_rev, id, idlen);
6908 /* If we are reloading or it's an annotated tag, replace the
6909 * previous SHA1 with the resolved commit id; relies on the fact
6910 * git-ls-remote lists the commit id of an annotated tag right
6911 * before the commit id it points to. */
6912 while (from <= to) {
6913 size_t pos = (to + from) / 2;
6914 int cmp = strcmp(name, refs[pos]->name);
6928 if (!realloc_refs(&refs, refs_size, 1))
6930 ref = calloc(1, sizeof(*ref) + namelen);
6933 memmove(refs + from + 1, refs + from,
6934 (refs_size - from) * sizeof(*refs));
6936 strncpy(ref->name, name, namelen);
6943 ref->remote = remote;
6944 ref->tracked = tracked;
6945 string_copy_rev(ref->id, id);
6953 const char *head_argv[] = {
6954 "git", "symbolic-ref", "HEAD", NULL
6956 static const char *ls_remote_argv[SIZEOF_ARG] = {
6957 "git", "ls-remote", opt_git_dir, NULL
6959 static bool init = FALSE;
6963 argv_from_env(ls_remote_argv, "TIG_LS_REMOTE");
6970 if (run_io_buf(head_argv, opt_head, sizeof(opt_head)) &&
6971 !prefixcmp(opt_head, "refs/heads/")) {
6972 char *offset = opt_head + STRING_SIZE("refs/heads/");
6974 memmove(opt_head, offset, strlen(offset) + 1);
6977 for (i = 0; i < refs_size; i++)
6980 if (run_io_load(ls_remote_argv, "\t", read_ref) == ERR)
6983 /* Update the ref lists to reflect changes. */
6984 for (i = 0; i < ref_lists_size; i++) {
6985 struct ref_list *list = ref_lists[i];
6988 for (old = new = 0; old < list->size; old++)
6989 if (!strcmp(list->id, list->refs[old]->id))
6990 list->refs[new++] = list->refs[old];
6998 set_remote_branch(const char *name, const char *value, size_t valuelen)
7000 if (!strcmp(name, ".remote")) {
7001 string_ncopy(opt_remote, value, valuelen);
7003 } else if (*opt_remote && !strcmp(name, ".merge")) {
7004 size_t from = strlen(opt_remote);
7006 if (!prefixcmp(value, "refs/heads/"))
7007 value += STRING_SIZE("refs/heads/");
7009 if (!string_format_from(opt_remote, &from, "/%s", value))
7015 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
7017 const char *argv[SIZEOF_ARG] = { name, "=" };
7018 int argc = 1 + (cmd == option_set_command);
7021 if (!argv_from_string(argv, &argc, value))
7022 config_msg = "Too many option arguments";
7024 error = cmd(argc, argv);
7027 warn("Option 'tig.%s': %s", name, config_msg);
7031 set_environment_variable(const char *name, const char *value)
7033 size_t len = strlen(name) + 1 + strlen(value) + 1;
7034 char *env = malloc(len);
7037 string_nformat(env, len, NULL, "%s=%s", name, value) &&
7045 set_work_tree(const char *value)
7047 char cwd[SIZEOF_STR];
7049 if (!getcwd(cwd, sizeof(cwd)))
7050 die("Failed to get cwd path: %s", strerror(errno));
7051 if (chdir(opt_git_dir) < 0)
7052 die("Failed to chdir(%s): %s", strerror(errno));
7053 if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7054 die("Failed to get git path: %s", strerror(errno));
7056 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7057 if (chdir(value) < 0)
7058 die("Failed to chdir(%s): %s", value, strerror(errno));
7059 if (!getcwd(cwd, sizeof(cwd)))
7060 die("Failed to get cwd path: %s", strerror(errno));
7061 if (!set_environment_variable("GIT_WORK_TREE", cwd))
7062 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7063 if (!set_environment_variable("GIT_DIR", opt_git_dir))
7064 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7065 opt_is_inside_work_tree = TRUE;
7069 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
7071 if (!strcmp(name, "i18n.commitencoding"))
7072 string_ncopy(opt_encoding, value, valuelen);
7074 else if (!strcmp(name, "core.editor"))
7075 string_ncopy(opt_editor, value, valuelen);
7077 else if (!strcmp(name, "core.worktree"))
7078 set_work_tree(value);
7080 else if (!prefixcmp(name, "tig.color."))
7081 set_repo_config_option(name + 10, value, option_color_command);
7083 else if (!prefixcmp(name, "tig.bind."))
7084 set_repo_config_option(name + 9, value, option_bind_command);
7086 else if (!prefixcmp(name, "tig."))
7087 set_repo_config_option(name + 4, value, option_set_command);
7089 else if (*opt_head && !prefixcmp(name, "branch.") &&
7090 !strncmp(name + 7, opt_head, strlen(opt_head)))
7091 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7097 load_git_config(void)
7099 const char *config_list_argv[] = { "git", GIT_CONFIG, "--list", NULL };
7101 return run_io_load(config_list_argv, "=", read_repo_config_option);
7105 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
7107 if (!opt_git_dir[0]) {
7108 string_ncopy(opt_git_dir, name, namelen);
7110 } else if (opt_is_inside_work_tree == -1) {
7111 /* This can be 3 different values depending on the
7112 * version of git being used. If git-rev-parse does not
7113 * understand --is-inside-work-tree it will simply echo
7114 * the option else either "true" or "false" is printed.
7115 * Default to true for the unknown case. */
7116 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7118 } else if (*name == '.') {
7119 string_ncopy(opt_cdup, name, namelen);
7122 string_ncopy(opt_prefix, name, namelen);
7129 load_repo_info(void)
7131 const char *rev_parse_argv[] = {
7132 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7133 "--show-cdup", "--show-prefix", NULL
7136 return run_io_load(rev_parse_argv, "=", read_repo_info);
7144 static const char usage[] =
7145 "tig " TIG_VERSION " (" __DATE__ ")\n"
7147 "Usage: tig [options] [revs] [--] [paths]\n"
7148 " or: tig show [options] [revs] [--] [paths]\n"
7149 " or: tig blame [rev] path\n"
7151 " or: tig < [git command output]\n"
7154 " -v, --version Show version and exit\n"
7155 " -h, --help Show help message and exit";
7157 static void __NORETURN
7160 /* XXX: Restore tty modes and let the OS cleanup the rest! */
7166 static void __NORETURN
7167 die(const char *err, ...)
7173 va_start(args, err);
7174 fputs("tig: ", stderr);
7175 vfprintf(stderr, err, args);
7176 fputs("\n", stderr);
7183 warn(const char *msg, ...)
7187 va_start(args, msg);
7188 fputs("tig warning: ", stderr);
7189 vfprintf(stderr, msg, args);
7190 fputs("\n", stderr);
7195 parse_options(int argc, const char *argv[])
7197 enum request request = REQ_VIEW_MAIN;
7198 const char *subcommand;
7199 bool seen_dashdash = FALSE;
7200 /* XXX: This is vulnerable to the user overriding options
7201 * required for the main view parser. */
7202 const char *custom_argv[SIZEOF_ARG] = {
7203 "git", "log", "--no-color", "--pretty=raw", "--parents",
7204 "--topo-order", NULL
7208 if (!isatty(STDIN_FILENO)) {
7209 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
7210 return REQ_VIEW_PAGER;
7216 subcommand = argv[1];
7217 if (!strcmp(subcommand, "status")) {
7219 warn("ignoring arguments after `%s'", subcommand);
7220 return REQ_VIEW_STATUS;
7222 } else if (!strcmp(subcommand, "blame")) {
7223 if (argc <= 2 || argc > 4)
7224 die("invalid number of options to blame\n\n%s", usage);
7228 string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7232 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7233 return REQ_VIEW_BLAME;
7235 } else if (!strcmp(subcommand, "show")) {
7236 request = REQ_VIEW_DIFF;
7243 custom_argv[1] = subcommand;
7247 for (i = 1 + !!subcommand; i < argc; i++) {
7248 const char *opt = argv[i];
7250 if (seen_dashdash || !strcmp(opt, "--")) {
7251 seen_dashdash = TRUE;
7253 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7254 printf("tig version %s\n", TIG_VERSION);
7257 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7258 printf("%s\n", usage);
7262 custom_argv[j++] = opt;
7263 if (j >= ARRAY_SIZE(custom_argv))
7264 die("command too long");
7267 if (!prepare_update(VIEW(request), custom_argv, NULL, FORMAT_NONE))
7268 die("Failed to format arguments");
7274 main(int argc, const char *argv[])
7276 enum request request = parse_options(argc, argv);
7280 signal(SIGINT, quit);
7281 signal(SIGPIPE, SIG_IGN);
7283 if (setlocale(LC_ALL, "")) {
7284 char *codeset = nl_langinfo(CODESET);
7286 string_ncopy(opt_codeset, codeset, strlen(codeset));
7289 if (load_repo_info() == ERR)
7290 die("Failed to load repo info.");
7292 if (load_options() == ERR)
7293 die("Failed to load user config.");
7295 if (load_git_config() == ERR)
7296 die("Failed to load repo config.");
7298 /* Require a git repository unless when running in pager mode. */
7299 if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7300 die("Not a git repository");
7302 if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
7305 if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
7306 opt_iconv = iconv_open(opt_codeset, opt_encoding);
7307 if (opt_iconv == ICONV_NONE)
7308 die("Failed to initialize character set conversion");
7311 if (load_refs() == ERR)
7312 die("Failed to load refs.");
7314 foreach_view (view, i)
7315 argv_from_env(view->ops->argv, view->cmd_env);
7319 if (request != REQ_NONE)
7320 open_view(NULL, request, OPEN_PREPARED);
7321 request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
7323 while (view_driver(display[current_view], request)) {
7324 int key = get_input(0);
7326 view = display[current_view];
7327 request = get_keybinding(view->keymap, key);
7329 /* Some low-level request handling. This keeps access to
7330 * status_win restricted. */
7334 char *cmd = read_prompt(":");
7336 if (cmd && isdigit(*cmd)) {
7337 int lineno = view->lineno + 1;
7339 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7340 select_view_line(view, lineno - 1);
7343 report("Unable to parse '%s' as a line number", cmd);
7347 struct view *next = VIEW(REQ_VIEW_PAGER);
7348 const char *argv[SIZEOF_ARG] = { "git" };
7351 /* When running random commands, initially show the
7352 * command in the title. However, it maybe later be
7353 * overwritten if a commit line is selected. */
7354 string_ncopy(next->ref, cmd, strlen(cmd));
7356 if (!argv_from_string(argv, &argc, cmd)) {
7357 report("Too many arguments");
7358 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
7359 report("Failed to format command");
7361 open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7369 case REQ_SEARCH_BACK:
7371 const char *prompt = request == REQ_SEARCH ? "/" : "?";
7372 char *search = read_prompt(prompt);
7375 string_ncopy(opt_search, search, strlen(search));
7376 else if (*opt_search)
7377 request = request == REQ_SEARCH ?