1 /* Copyright (c) 2006-2009 Jonas Fonseca <fonseca@diku.dk>
3 * This program is free software; you can redistribute it and/or
4 * modify it under the terms of the GNU General Public License as
5 * published by the Free Software Foundation; either version 2 of
6 * the License, or (at your option) any later version.
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
19 #define TIG_VERSION "unknown-version"
34 #include <sys/types.h>
37 #include <sys/select.h>
48 /* ncurses(3): Must be defined to have extended wide-character functions. */
49 #define _XOPEN_SOURCE_EXTENDED
51 #ifdef HAVE_NCURSESW_NCURSES_H
52 #include <ncursesw/ncurses.h>
54 #ifdef HAVE_NCURSES_NCURSES_H
55 #include <ncurses/ncurses.h>
62 #define __NORETURN __attribute__((__noreturn__))
67 static void __NORETURN die(const char *err, ...);
68 static void warn(const char *msg, ...);
69 static void report(const char *msg, ...);
70 static void set_nonblocking_input(bool loading);
71 static int load_refs(void);
72 static size_t utf8_length(const char **string, size_t col, int *width, size_t max_width, int *trimmed, bool reserve);
74 #define ABS(x) ((x) >= 0 ? (x) : -(x))
75 #define MIN(x, y) ((x) < (y) ? (x) : (y))
77 #define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
78 #define STRING_SIZE(x) (sizeof(x) - 1)
80 #define SIZEOF_STR 1024 /* Default string size. */
81 #define SIZEOF_REF 256 /* Size of symbolic or SHA1 ID. */
82 #define SIZEOF_REV 41 /* Holds a SHA-1 and an ending NUL. */
83 #define SIZEOF_ARG 32 /* Default argument array size. */
87 #define REVGRAPH_INIT 'I'
88 #define REVGRAPH_MERGE 'M'
89 #define REVGRAPH_BRANCH '+'
90 #define REVGRAPH_COMMIT '*'
91 #define REVGRAPH_BOUND '^'
93 #define SIZEOF_REVGRAPH 19 /* Size of revision ancestry graphics. */
95 /* This color name can be used to refer to the default term colors. */
96 #define COLOR_DEFAULT (-1)
98 #define ICONV_NONE ((iconv_t) -1)
100 #define ICONV_CONST /* nothing */
103 /* The format and size of the date column in the main view. */
104 #define DATE_FORMAT "%Y-%m-%d %H:%M"
105 #define DATE_COLS STRING_SIZE("2006-04-29 14:21 ")
107 #define AUTHOR_COLS 20
110 /* The default interval between line numbers. */
111 #define NUMBER_INTERVAL 5
115 #define SCALE_SPLIT_VIEW(height) ((height) * 2 / 3)
117 #define NULL_ID "0000000000000000000000000000000000000000"
119 #define S_ISGITLINK(mode) (((mode) & S_IFMT) == 0160000)
122 #define GIT_CONFIG "config"
125 /* Some ASCII-shorthands fitted into the ncurses namespace. */
127 #define KEY_RETURN '\r'
132 char *name; /* Ref name; tag or head names are shortened. */
133 char id[SIZEOF_REV]; /* Commit SHA1 ID */
134 unsigned int head:1; /* Is it the current HEAD? */
135 unsigned int tag:1; /* Is it a tag? */
136 unsigned int ltag:1; /* If so, is the tag local? */
137 unsigned int remote:1; /* Is it a remote ref? */
138 unsigned int tracked:1; /* Is it the remote for the current HEAD? */
139 unsigned int next:1; /* For ref lists: are there more refs? */
142 static struct ref **get_refs(const char *id);
143 static void foreach_ref(bool (*visitor)(void *data, struct ref *ref), void *data);
146 FORMAT_ALL, /* Perform replacement in all arguments. */
147 FORMAT_DASH, /* Perform replacement up until "--". */
148 FORMAT_NONE /* No replacement should be performed. */
151 static bool format_argv(const char *dst[], const char *src[], enum format_flags flags);
160 typedef enum input_status (*input_handler)(void *data, char *buf, int c);
162 static char *prompt_input(const char *prompt, input_handler handler, void *data);
163 static bool prompt_yesno(const char *prompt);
166 * Allocation helpers ... Entering macro hell to never be seen again.
169 #define DEFINE_ALLOCATOR(name, type, chunk_size) \
171 name(type **mem, size_t size, size_t increase) \
173 size_t num_chunks = (size + chunk_size - 1) / chunk_size; \
174 size_t num_chunks_new = (size + increase + chunk_size - 1) / chunk_size;\
177 if (mem == NULL || num_chunks != num_chunks_new) { \
178 tmp = realloc(tmp, num_chunks_new * chunk_size * sizeof(type)); \
191 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
193 if (srclen > dstlen - 1)
196 strncpy(dst, src, srclen);
200 /* Shorthands for safely copying into a fixed buffer. */
202 #define string_copy(dst, src) \
203 string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
205 #define string_ncopy(dst, src, srclen) \
206 string_ncopy_do(dst, sizeof(dst), src, srclen)
208 #define string_copy_rev(dst, src) \
209 string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
211 #define string_add(dst, from, src) \
212 string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
215 string_expand(char *dst, size_t dstlen, const char *src, int tabsize)
219 for (size = pos = 0; size < dstlen - 1 && src[pos]; pos++) {
220 if (src[pos] == '\t') {
221 size_t expanded = tabsize - (size % tabsize);
223 if (expanded + size >= dstlen - 1)
224 expanded = dstlen - size - 1;
225 memcpy(dst + size, " ", expanded);
228 dst[size++] = src[pos];
236 chomp_string(char *name)
240 while (isspace(*name))
243 namelen = strlen(name) - 1;
244 while (namelen > 0 && isspace(name[namelen]))
251 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
254 size_t pos = bufpos ? *bufpos : 0;
257 pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
263 return pos >= bufsize ? FALSE : TRUE;
266 #define string_format(buf, fmt, args...) \
267 string_nformat(buf, sizeof(buf), NULL, fmt, args)
269 #define string_format_from(buf, from, fmt, args...) \
270 string_nformat(buf, sizeof(buf), from, fmt, args)
273 string_enum_compare(const char *str1, const char *str2, int len)
277 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
279 /* Diff-Header == DIFF_HEADER */
280 for (i = 0; i < len; i++) {
281 if (toupper(str1[i]) == toupper(str2[i]))
284 if (string_enum_sep(str1[i]) &&
285 string_enum_sep(str2[i]))
288 return str1[i] - str2[i];
300 #define ENUM_MAP(name, value) { name, STRING_SIZE(name), value }
303 map_enum_do(const struct enum_map *map, size_t map_size, int *value, const char *name)
305 size_t namelen = strlen(name);
308 for (i = 0; i < map_size; i++)
309 if (namelen == map[i].namelen &&
310 !string_enum_compare(name, map[i].name, namelen)) {
311 *value = map[i].value;
318 #define map_enum(attr, map, name) \
319 map_enum_do(map, ARRAY_SIZE(map), attr, name)
321 #define prefixcmp(str1, str2) \
322 strncmp(str1, str2, STRING_SIZE(str2))
325 suffixcmp(const char *str, int slen, const char *suffix)
327 size_t len = slen >= 0 ? slen : strlen(str);
328 size_t suffixlen = strlen(suffix);
330 return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
335 mkdate(const time_t *time)
337 static char buf[DATE_COLS + 1];
341 return strftime(buf, sizeof(buf), DATE_FORMAT, &tm) ? buf : NULL;
346 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
350 while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
351 bool advance = cmd[valuelen] != 0;
354 argv[(*argc)++] = chomp_string(cmd);
355 cmd = chomp_string(cmd + valuelen + advance);
358 if (*argc < SIZEOF_ARG)
360 return *argc < SIZEOF_ARG;
364 argv_from_env(const char **argv, const char *name)
366 char *env = argv ? getenv(name) : NULL;
371 if (env && !argv_from_string(argv, &argc, env))
372 die("Too many arguments in the `%s` environment variable", name);
377 * Executing external commands.
381 IO_FD, /* File descriptor based IO. */
382 IO_BG, /* Execute command in the background. */
383 IO_FG, /* Execute command with same std{in,out,err}. */
384 IO_RD, /* Read only fork+exec IO. */
385 IO_WR, /* Write only fork+exec IO. */
386 IO_AP, /* Append fork+exec output to file. */
390 enum io_type type; /* The requested type of pipe. */
391 const char *dir; /* Directory from which to execute. */
392 pid_t pid; /* Pipe for reading or writing. */
393 int pipe; /* Pipe end for reading or writing. */
394 int error; /* Error status. */
395 const char *argv[SIZEOF_ARG]; /* Shell command arguments. */
396 char *buf; /* Read buffer. */
397 size_t bufalloc; /* Allocated buffer size. */
398 size_t bufsize; /* Buffer content size. */
399 char *bufpos; /* Current buffer position. */
400 unsigned int eof:1; /* Has end of file been reached. */
404 reset_io(struct io *io)
408 io->buf = io->bufpos = NULL;
409 io->bufalloc = io->bufsize = 0;
415 init_io(struct io *io, const char *dir, enum io_type type)
423 init_io_rd(struct io *io, const char *argv[], const char *dir,
424 enum format_flags flags)
426 init_io(io, dir, IO_RD);
427 return format_argv(io->argv, argv, flags);
431 io_open(struct io *io, const char *name)
433 init_io(io, NULL, IO_FD);
434 io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
437 return io->pipe != -1;
441 kill_io(struct io *io)
443 return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
447 done_io(struct io *io)
458 pid_t waiting = waitpid(pid, &status, 0);
463 report("waitpid failed (%s)", strerror(errno));
467 return waiting == pid &&
468 !WIFSIGNALED(status) &&
470 !WEXITSTATUS(status);
477 start_io(struct io *io)
479 int pipefds[2] = { -1, -1 };
481 if (io->type == IO_FD)
484 if ((io->type == IO_RD || io->type == IO_WR) &&
487 else if (io->type == IO_AP)
488 pipefds[1] = io->pipe;
490 if ((io->pid = fork())) {
491 if (pipefds[!(io->type == IO_WR)] != -1)
492 close(pipefds[!(io->type == IO_WR)]);
494 io->pipe = pipefds[!!(io->type == IO_WR)];
499 if (io->type != IO_FG) {
500 int devnull = open("/dev/null", O_RDWR);
501 int readfd = io->type == IO_WR ? pipefds[0] : devnull;
502 int writefd = (io->type == IO_RD || io->type == IO_AP)
503 ? pipefds[1] : devnull;
505 dup2(readfd, STDIN_FILENO);
506 dup2(writefd, STDOUT_FILENO);
507 dup2(devnull, STDERR_FILENO);
510 if (pipefds[0] != -1)
512 if (pipefds[1] != -1)
516 if (io->dir && *io->dir && chdir(io->dir) == -1)
517 die("Failed to change directory: %s", strerror(errno));
519 execvp(io->argv[0], (char *const*) io->argv);
520 die("Failed to execute program: %s", strerror(errno));
523 if (pipefds[!!(io->type == IO_WR)] != -1)
524 close(pipefds[!!(io->type == IO_WR)]);
529 run_io(struct io *io, const char **argv, const char *dir, enum io_type type)
531 init_io(io, dir, type);
532 if (!format_argv(io->argv, argv, FORMAT_NONE))
538 run_io_do(struct io *io)
540 return start_io(io) && done_io(io);
544 run_io_bg(const char **argv)
548 init_io(&io, NULL, IO_BG);
549 if (!format_argv(io.argv, argv, FORMAT_NONE))
551 return run_io_do(&io);
555 run_io_fg(const char **argv, const char *dir)
559 init_io(&io, dir, IO_FG);
560 if (!format_argv(io.argv, argv, FORMAT_NONE))
562 return run_io_do(&io);
566 run_io_append(const char **argv, enum format_flags flags, int fd)
570 init_io(&io, NULL, IO_AP);
572 if (format_argv(io.argv, argv, flags))
573 return run_io_do(&io);
579 run_io_rd(struct io *io, const char **argv, enum format_flags flags)
581 return init_io_rd(io, argv, NULL, flags) && start_io(io);
585 io_eof(struct io *io)
591 io_error(struct io *io)
597 io_strerror(struct io *io)
599 return strerror(io->error);
603 io_can_read(struct io *io)
605 struct timeval tv = { 0, 500 };
609 FD_SET(io->pipe, &fds);
611 return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
615 io_read(struct io *io, void *buf, size_t bufsize)
618 ssize_t readsize = read(io->pipe, buf, bufsize);
620 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
622 else if (readsize == -1)
624 else if (readsize == 0)
630 DEFINE_ALLOCATOR(realloc_io_buf, char, BUFSIZ)
633 io_get(struct io *io, int c, bool can_read)
639 if (io->bufsize > 0) {
640 eol = memchr(io->bufpos, c, io->bufsize);
642 char *line = io->bufpos;
645 io->bufpos = eol + 1;
646 io->bufsize -= io->bufpos - line;
653 io->bufpos[io->bufsize] = 0;
663 if (io->bufsize > 0 && io->bufpos > io->buf)
664 memmove(io->buf, io->bufpos, io->bufsize);
666 if (io->bufalloc == io->bufsize) {
667 if (!realloc_io_buf(&io->buf, io->bufalloc, BUFSIZ))
669 io->bufalloc += BUFSIZ;
672 io->bufpos = io->buf;
673 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
676 io->bufsize += readsize;
681 io_write(struct io *io, const void *buf, size_t bufsize)
685 while (!io_error(io) && written < bufsize) {
688 size = write(io->pipe, buf + written, bufsize - written);
689 if (size < 0 && (errno == EAGAIN || errno == EINTR))
697 return written == bufsize;
701 io_read_buf(struct io *io, char buf[], size_t bufsize)
703 char *result = io_get(io, '\n', TRUE);
706 result = chomp_string(result);
707 string_ncopy_do(buf, bufsize, result, strlen(result));
710 return done_io(io) && result;
714 run_io_buf(const char **argv, char buf[], size_t bufsize)
718 return run_io_rd(&io, argv, FORMAT_NONE) && io_read_buf(&io, buf, bufsize);
722 io_load(struct io *io, const char *separators,
723 int (*read_property)(char *, size_t, char *, size_t))
731 while (state == OK && (name = io_get(io, '\n', TRUE))) {
736 name = chomp_string(name);
737 namelen = strcspn(name, separators);
741 value = chomp_string(name + namelen + 1);
742 valuelen = strlen(value);
749 state = read_property(name, namelen, value, valuelen);
752 if (state != ERR && io_error(io))
760 run_io_load(const char **argv, const char *separators,
761 int (*read_property)(char *, size_t, char *, size_t))
765 return init_io_rd(&io, argv, NULL, FORMAT_NONE)
766 ? io_load(&io, separators, read_property) : ERR;
775 /* XXX: Keep the view request first and in sync with views[]. */ \
776 REQ_GROUP("View switching") \
777 REQ_(VIEW_MAIN, "Show main view"), \
778 REQ_(VIEW_DIFF, "Show diff view"), \
779 REQ_(VIEW_LOG, "Show log view"), \
780 REQ_(VIEW_TREE, "Show tree view"), \
781 REQ_(VIEW_BLOB, "Show blob view"), \
782 REQ_(VIEW_BLAME, "Show blame view"), \
783 REQ_(VIEW_BRANCH, "Show branch view"), \
784 REQ_(VIEW_HELP, "Show help page"), \
785 REQ_(VIEW_PAGER, "Show pager view"), \
786 REQ_(VIEW_STATUS, "Show status view"), \
787 REQ_(VIEW_STAGE, "Show stage view"), \
789 REQ_GROUP("View manipulation") \
790 REQ_(ENTER, "Enter current line and scroll"), \
791 REQ_(NEXT, "Move to next"), \
792 REQ_(PREVIOUS, "Move to previous"), \
793 REQ_(PARENT, "Move to parent"), \
794 REQ_(VIEW_NEXT, "Move focus to next view"), \
795 REQ_(REFRESH, "Reload and refresh"), \
796 REQ_(MAXIMIZE, "Maximize the current view"), \
797 REQ_(VIEW_CLOSE, "Close the current view"), \
798 REQ_(QUIT, "Close all views and quit"), \
800 REQ_GROUP("View specific requests") \
801 REQ_(STATUS_UPDATE, "Update file status"), \
802 REQ_(STATUS_REVERT, "Revert file changes"), \
803 REQ_(STATUS_MERGE, "Merge file using external tool"), \
804 REQ_(STAGE_NEXT, "Find next chunk to stage"), \
806 REQ_GROUP("Cursor navigation") \
807 REQ_(MOVE_UP, "Move cursor one line up"), \
808 REQ_(MOVE_DOWN, "Move cursor one line down"), \
809 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
810 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
811 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
812 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
814 REQ_GROUP("Scrolling") \
815 REQ_(SCROLL_LEFT, "Scroll two columns left"), \
816 REQ_(SCROLL_RIGHT, "Scroll two columns right"), \
817 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
818 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
819 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
820 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
822 REQ_GROUP("Searching") \
823 REQ_(SEARCH, "Search the view"), \
824 REQ_(SEARCH_BACK, "Search backwards in the view"), \
825 REQ_(FIND_NEXT, "Find next search match"), \
826 REQ_(FIND_PREV, "Find previous search match"), \
828 REQ_GROUP("Option manipulation") \
829 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
830 REQ_(TOGGLE_DATE, "Toggle date display"), \
831 REQ_(TOGGLE_AUTHOR, "Toggle author display"), \
832 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
833 REQ_(TOGGLE_REFS, "Toggle reference display (tags/branches)"), \
834 REQ_(TOGGLE_SORT_ORDER, "Toggle ascending/descending sort order"), \
835 REQ_(TOGGLE_SORT_FIELD, "Toggle field to sort by"), \
838 REQ_(PROMPT, "Bring up the prompt"), \
839 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
840 REQ_(SHOW_VERSION, "Show version information"), \
841 REQ_(STOP_LOADING, "Stop all loading views"), \
842 REQ_(EDIT, "Open in editor"), \
843 REQ_(NONE, "Do nothing")
846 /* User action requests. */
848 #define REQ_GROUP(help)
849 #define REQ_(req, help) REQ_##req
851 /* Offset all requests to avoid conflicts with ncurses getch values. */
852 REQ_OFFSET = KEY_MAX + 1,
859 struct request_info {
860 enum request request;
866 static const struct request_info req_info[] = {
867 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
868 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
875 get_request(const char *name)
877 int namelen = strlen(name);
880 for (i = 0; i < ARRAY_SIZE(req_info); i++)
881 if (req_info[i].namelen == namelen &&
882 !string_enum_compare(req_info[i].name, name, namelen))
883 return req_info[i].request;
893 /* Option and state variables. */
894 static bool opt_date = TRUE;
895 static bool opt_author = TRUE;
896 static bool opt_line_number = FALSE;
897 static bool opt_line_graphics = TRUE;
898 static bool opt_rev_graph = FALSE;
899 static bool opt_show_refs = TRUE;
900 static int opt_num_interval = NUMBER_INTERVAL;
901 static double opt_hscroll = 0.50;
902 static int opt_tab_size = TAB_SIZE;
903 static int opt_author_cols = AUTHOR_COLS-1;
904 static char opt_path[SIZEOF_STR] = "";
905 static char opt_file[SIZEOF_STR] = "";
906 static char opt_ref[SIZEOF_REF] = "";
907 static char opt_head[SIZEOF_REF] = "";
908 static char opt_head_rev[SIZEOF_REV] = "";
909 static char opt_remote[SIZEOF_REF] = "";
910 static char opt_encoding[20] = "UTF-8";
911 static bool opt_utf8 = TRUE;
912 static char opt_codeset[20] = "UTF-8";
913 static iconv_t opt_iconv = ICONV_NONE;
914 static char opt_search[SIZEOF_STR] = "";
915 static char opt_cdup[SIZEOF_STR] = "";
916 static char opt_prefix[SIZEOF_STR] = "";
917 static char opt_git_dir[SIZEOF_STR] = "";
918 static signed char opt_is_inside_work_tree = -1; /* set to TRUE or FALSE */
919 static char opt_editor[SIZEOF_STR] = "";
920 static FILE *opt_tty = NULL;
922 #define is_initial_commit() (!*opt_head_rev)
923 #define is_head_commit(rev) (!strcmp((rev), "HEAD") || !strcmp(opt_head_rev, (rev)))
927 * Line-oriented content detection.
931 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
932 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
933 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
934 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
935 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
936 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
937 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
938 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
939 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
940 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
941 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
942 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
943 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
944 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
945 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
946 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
947 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
948 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
949 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
950 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
951 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
952 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
953 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
954 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
955 LINE(AUTHOR, "author ", COLOR_GREEN, COLOR_DEFAULT, 0), \
956 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
957 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
958 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
959 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
960 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
961 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
962 LINE(DELIMITER, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
963 LINE(DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
964 LINE(MODE, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
965 LINE(LINE_NUMBER, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
966 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
967 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
968 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
969 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
970 LINE(MAIN_LOCAL_TAG,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
971 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
972 LINE(MAIN_TRACKED, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
973 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
974 LINE(MAIN_HEAD, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
975 LINE(MAIN_REVGRAPH,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
976 LINE(TREE_HEAD, "", COLOR_DEFAULT, COLOR_DEFAULT, A_BOLD), \
977 LINE(TREE_DIR, "", COLOR_YELLOW, COLOR_DEFAULT, A_NORMAL), \
978 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
979 LINE(STAT_HEAD, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
980 LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
981 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
982 LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
983 LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
984 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
985 LINE(BLAME_ID, "", COLOR_MAGENTA, COLOR_DEFAULT, 0)
988 #define LINE(type, line, fg, bg, attr) \
996 const char *name; /* Option name. */
997 int namelen; /* Size of option name. */
998 const char *line; /* The start of line to match. */
999 int linelen; /* Size of string to match. */
1000 int fg, bg, attr; /* Color and text attributes for the lines. */
1003 static struct line_info line_info[] = {
1004 #define LINE(type, line, fg, bg, attr) \
1005 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1010 static enum line_type
1011 get_line_type(const char *line)
1013 int linelen = strlen(line);
1014 enum line_type type;
1016 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1017 /* Case insensitive search matches Signed-off-by lines better. */
1018 if (linelen >= line_info[type].linelen &&
1019 !strncasecmp(line_info[type].line, line, line_info[type].linelen))
1022 return LINE_DEFAULT;
1026 get_line_attr(enum line_type type)
1028 assert(type < ARRAY_SIZE(line_info));
1029 return COLOR_PAIR(type) | line_info[type].attr;
1032 static struct line_info *
1033 get_line_info(const char *name)
1035 size_t namelen = strlen(name);
1036 enum line_type type;
1038 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1039 if (namelen == line_info[type].namelen &&
1040 !string_enum_compare(line_info[type].name, name, namelen))
1041 return &line_info[type];
1049 int default_bg = line_info[LINE_DEFAULT].bg;
1050 int default_fg = line_info[LINE_DEFAULT].fg;
1051 enum line_type type;
1055 if (assume_default_colors(default_fg, default_bg) == ERR) {
1056 default_bg = COLOR_BLACK;
1057 default_fg = COLOR_WHITE;
1060 for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1061 struct line_info *info = &line_info[type];
1062 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1063 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1065 init_pair(type, fg, bg);
1070 enum line_type type;
1073 unsigned int selected:1;
1074 unsigned int dirty:1;
1075 unsigned int cleareol:1;
1077 void *data; /* User data */
1087 enum request request;
1090 static const struct keybinding default_keybindings[] = {
1091 /* View switching */
1092 { 'm', REQ_VIEW_MAIN },
1093 { 'd', REQ_VIEW_DIFF },
1094 { 'l', REQ_VIEW_LOG },
1095 { 't', REQ_VIEW_TREE },
1096 { 'f', REQ_VIEW_BLOB },
1097 { 'B', REQ_VIEW_BLAME },
1098 { 'H', REQ_VIEW_BRANCH },
1099 { 'p', REQ_VIEW_PAGER },
1100 { 'h', REQ_VIEW_HELP },
1101 { 'S', REQ_VIEW_STATUS },
1102 { 'c', REQ_VIEW_STAGE },
1104 /* View manipulation */
1105 { 'q', REQ_VIEW_CLOSE },
1106 { KEY_TAB, REQ_VIEW_NEXT },
1107 { KEY_RETURN, REQ_ENTER },
1108 { KEY_UP, REQ_PREVIOUS },
1109 { KEY_DOWN, REQ_NEXT },
1110 { 'R', REQ_REFRESH },
1111 { KEY_F(5), REQ_REFRESH },
1112 { 'O', REQ_MAXIMIZE },
1114 /* Cursor navigation */
1115 { 'k', REQ_MOVE_UP },
1116 { 'j', REQ_MOVE_DOWN },
1117 { KEY_HOME, REQ_MOVE_FIRST_LINE },
1118 { KEY_END, REQ_MOVE_LAST_LINE },
1119 { KEY_NPAGE, REQ_MOVE_PAGE_DOWN },
1120 { ' ', REQ_MOVE_PAGE_DOWN },
1121 { KEY_PPAGE, REQ_MOVE_PAGE_UP },
1122 { 'b', REQ_MOVE_PAGE_UP },
1123 { '-', REQ_MOVE_PAGE_UP },
1126 { KEY_LEFT, REQ_SCROLL_LEFT },
1127 { KEY_RIGHT, REQ_SCROLL_RIGHT },
1128 { KEY_IC, REQ_SCROLL_LINE_UP },
1129 { KEY_DC, REQ_SCROLL_LINE_DOWN },
1130 { 'w', REQ_SCROLL_PAGE_UP },
1131 { 's', REQ_SCROLL_PAGE_DOWN },
1134 { '/', REQ_SEARCH },
1135 { '?', REQ_SEARCH_BACK },
1136 { 'n', REQ_FIND_NEXT },
1137 { 'N', REQ_FIND_PREV },
1141 { 'z', REQ_STOP_LOADING },
1142 { 'v', REQ_SHOW_VERSION },
1143 { 'r', REQ_SCREEN_REDRAW },
1144 { '.', REQ_TOGGLE_LINENO },
1145 { 'D', REQ_TOGGLE_DATE },
1146 { 'A', REQ_TOGGLE_AUTHOR },
1147 { 'g', REQ_TOGGLE_REV_GRAPH },
1148 { 'F', REQ_TOGGLE_REFS },
1149 { 'I', REQ_TOGGLE_SORT_ORDER },
1150 { 'i', REQ_TOGGLE_SORT_FIELD },
1151 { ':', REQ_PROMPT },
1152 { 'u', REQ_STATUS_UPDATE },
1153 { '!', REQ_STATUS_REVERT },
1154 { 'M', REQ_STATUS_MERGE },
1155 { '@', REQ_STAGE_NEXT },
1156 { ',', REQ_PARENT },
1160 #define KEYMAP_INFO \
1175 #define KEYMAP_(name) KEYMAP_##name
1180 static const struct enum_map keymap_table[] = {
1181 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1186 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1188 struct keybinding_table {
1189 struct keybinding *data;
1193 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1196 add_keybinding(enum keymap keymap, enum request request, int key)
1198 struct keybinding_table *table = &keybindings[keymap];
1200 table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1202 die("Failed to allocate keybinding");
1203 table->data[table->size].alias = key;
1204 table->data[table->size++].request = request;
1207 /* Looks for a key binding first in the given map, then in the generic map, and
1208 * lastly in the default keybindings. */
1210 get_keybinding(enum keymap keymap, int key)
1214 for (i = 0; i < keybindings[keymap].size; i++)
1215 if (keybindings[keymap].data[i].alias == key)
1216 return keybindings[keymap].data[i].request;
1218 for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1219 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1220 return keybindings[KEYMAP_GENERIC].data[i].request;
1222 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1223 if (default_keybindings[i].alias == key)
1224 return default_keybindings[i].request;
1226 return (enum request) key;
1235 static const struct key key_table[] = {
1236 { "Enter", KEY_RETURN },
1238 { "Backspace", KEY_BACKSPACE },
1240 { "Escape", KEY_ESC },
1241 { "Left", KEY_LEFT },
1242 { "Right", KEY_RIGHT },
1244 { "Down", KEY_DOWN },
1245 { "Insert", KEY_IC },
1246 { "Delete", KEY_DC },
1248 { "Home", KEY_HOME },
1250 { "PageUp", KEY_PPAGE },
1251 { "PageDown", KEY_NPAGE },
1261 { "F10", KEY_F(10) },
1262 { "F11", KEY_F(11) },
1263 { "F12", KEY_F(12) },
1267 get_key_value(const char *name)
1271 for (i = 0; i < ARRAY_SIZE(key_table); i++)
1272 if (!strcasecmp(key_table[i].name, name))
1273 return key_table[i].value;
1275 if (strlen(name) == 1 && isprint(*name))
1282 get_key_name(int key_value)
1284 static char key_char[] = "'X'";
1285 const char *seq = NULL;
1288 for (key = 0; key < ARRAY_SIZE(key_table); key++)
1289 if (key_table[key].value == key_value)
1290 seq = key_table[key].name;
1294 isprint(key_value)) {
1295 key_char[1] = (char) key_value;
1299 return seq ? seq : "(no key)";
1303 get_key(enum request request)
1305 static char buf[BUFSIZ];
1312 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1313 const struct keybinding *keybinding = &default_keybindings[i];
1315 if (keybinding->request != request)
1318 if (!string_format_from(buf, &pos, "%s%s", sep,
1319 get_key_name(keybinding->alias)))
1320 return "Too many keybindings!";
1327 struct run_request {
1330 const char *argv[SIZEOF_ARG];
1333 static struct run_request *run_request;
1334 static size_t run_requests;
1336 DEFINE_ALLOCATOR(realloc_run_requests, struct run_request, 8)
1339 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1341 struct run_request *req;
1343 if (argc >= ARRAY_SIZE(req->argv) - 1)
1346 if (!realloc_run_requests(&run_request, run_requests, 1))
1349 req = &run_request[run_requests];
1350 req->keymap = keymap;
1352 req->argv[0] = NULL;
1354 if (!format_argv(req->argv, argv, FORMAT_NONE))
1357 return REQ_NONE + ++run_requests;
1360 static struct run_request *
1361 get_run_request(enum request request)
1363 if (request <= REQ_NONE)
1365 return &run_request[request - REQ_NONE - 1];
1369 add_builtin_run_requests(void)
1371 const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1372 const char *gc[] = { "git", "gc", NULL };
1379 { KEYMAP_MAIN, 'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1380 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1384 for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1387 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1388 if (req != REQ_NONE)
1389 add_keybinding(reqs[i].keymap, req, reqs[i].key);
1394 * User config file handling.
1397 static int config_lineno;
1398 static bool config_errors;
1399 static const char *config_msg;
1401 static const struct enum_map color_map[] = {
1402 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1414 static const struct enum_map attr_map[] = {
1415 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1422 ATTR_MAP(UNDERLINE),
1425 #define set_attribute(attr, name) map_enum(attr, attr_map, name)
1427 static int parse_step(double *opt, const char *arg)
1430 if (!strchr(arg, '%'))
1433 /* "Shift down" so 100% and 1 does not conflict. */
1434 *opt = (*opt - 1) / 100;
1437 config_msg = "Step value larger than 100%";
1442 config_msg = "Invalid step value";
1449 parse_int(int *opt, const char *arg, int min, int max)
1451 int value = atoi(arg);
1453 if (min <= value && value <= max) {
1458 config_msg = "Integer value out of bound";
1463 set_color(int *color, const char *name)
1465 if (map_enum(color, color_map, name))
1467 if (!prefixcmp(name, "color"))
1468 return parse_int(color, name + 5, 0, 255) == OK;
1472 /* Wants: object fgcolor bgcolor [attribute] */
1474 option_color_command(int argc, const char *argv[])
1476 struct line_info *info;
1478 if (argc != 3 && argc != 4) {
1479 config_msg = "Wrong number of arguments given to color command";
1483 info = get_line_info(argv[0]);
1485 static const struct enum_map obsolete[] = {
1486 ENUM_MAP("main-delim", LINE_DELIMITER),
1487 ENUM_MAP("main-date", LINE_DATE),
1488 ENUM_MAP("main-author", LINE_AUTHOR),
1492 if (!map_enum(&index, obsolete, argv[0])) {
1493 config_msg = "Unknown color name";
1496 info = &line_info[index];
1499 if (!set_color(&info->fg, argv[1]) ||
1500 !set_color(&info->bg, argv[2])) {
1501 config_msg = "Unknown color";
1505 if (argc == 4 && !set_attribute(&info->attr, argv[3])) {
1506 config_msg = "Unknown attribute";
1513 static int parse_bool(bool *opt, const char *arg)
1515 *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1521 parse_string(char *opt, const char *arg, size_t optsize)
1523 int arglen = strlen(arg);
1528 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1529 config_msg = "Unmatched quotation";
1532 arg += 1; arglen -= 2;
1534 string_ncopy_do(opt, optsize, arg, arglen);
1539 /* Wants: name = value */
1541 option_set_command(int argc, const char *argv[])
1544 config_msg = "Wrong number of arguments given to set command";
1548 if (strcmp(argv[1], "=")) {
1549 config_msg = "No value assigned";
1553 if (!strcmp(argv[0], "show-author"))
1554 return parse_bool(&opt_author, argv[2]);
1556 if (!strcmp(argv[0], "show-date"))
1557 return parse_bool(&opt_date, argv[2]);
1559 if (!strcmp(argv[0], "show-rev-graph"))
1560 return parse_bool(&opt_rev_graph, argv[2]);
1562 if (!strcmp(argv[0], "show-refs"))
1563 return parse_bool(&opt_show_refs, argv[2]);
1565 if (!strcmp(argv[0], "show-line-numbers"))
1566 return parse_bool(&opt_line_number, argv[2]);
1568 if (!strcmp(argv[0], "line-graphics"))
1569 return parse_bool(&opt_line_graphics, argv[2]);
1571 if (!strcmp(argv[0], "line-number-interval"))
1572 return parse_int(&opt_num_interval, argv[2], 1, 1024);
1574 if (!strcmp(argv[0], "author-width"))
1575 return parse_int(&opt_author_cols, argv[2], 0, 1024);
1577 if (!strcmp(argv[0], "horizontal-scroll"))
1578 return parse_step(&opt_hscroll, argv[2]);
1580 if (!strcmp(argv[0], "tab-size"))
1581 return parse_int(&opt_tab_size, argv[2], 1, 1024);
1583 if (!strcmp(argv[0], "commit-encoding"))
1584 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
1586 config_msg = "Unknown variable name";
1590 /* Wants: mode request key */
1592 option_bind_command(int argc, const char *argv[])
1594 enum request request;
1599 config_msg = "Wrong number of arguments given to bind command";
1603 if (set_keymap(&keymap, argv[0]) == ERR) {
1604 config_msg = "Unknown key map";
1608 key = get_key_value(argv[1]);
1610 config_msg = "Unknown key";
1614 request = get_request(argv[2]);
1615 if (request == REQ_NONE) {
1616 static const struct enum_map obsolete[] = {
1617 ENUM_MAP("cherry-pick", REQ_NONE),
1618 ENUM_MAP("screen-resize", REQ_NONE),
1619 ENUM_MAP("tree-parent", REQ_PARENT),
1623 if (map_enum(&alias, obsolete, argv[2])) {
1624 if (alias != REQ_NONE)
1625 add_keybinding(keymap, alias, key);
1626 config_msg = "Obsolete request name";
1630 if (request == REQ_NONE && *argv[2]++ == '!')
1631 request = add_run_request(keymap, key, argc - 2, argv + 2);
1632 if (request == REQ_NONE) {
1633 config_msg = "Unknown request name";
1637 add_keybinding(keymap, request, key);
1643 set_option(const char *opt, char *value)
1645 const char *argv[SIZEOF_ARG];
1648 if (!argv_from_string(argv, &argc, value)) {
1649 config_msg = "Too many option arguments";
1653 if (!strcmp(opt, "color"))
1654 return option_color_command(argc, argv);
1656 if (!strcmp(opt, "set"))
1657 return option_set_command(argc, argv);
1659 if (!strcmp(opt, "bind"))
1660 return option_bind_command(argc, argv);
1662 config_msg = "Unknown option command";
1667 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1672 config_msg = "Internal error";
1674 /* Check for comment markers, since read_properties() will
1675 * only ensure opt and value are split at first " \t". */
1676 optlen = strcspn(opt, "#");
1680 if (opt[optlen] != 0) {
1681 config_msg = "No option value";
1685 /* Look for comment endings in the value. */
1686 size_t len = strcspn(value, "#");
1688 if (len < valuelen) {
1690 value[valuelen] = 0;
1693 status = set_option(opt, value);
1696 if (status == ERR) {
1697 warn("Error on line %d, near '%.*s': %s",
1698 config_lineno, (int) optlen, opt, config_msg);
1699 config_errors = TRUE;
1702 /* Always keep going if errors are encountered. */
1707 load_option_file(const char *path)
1711 /* It's OK that the file doesn't exist. */
1712 if (!io_open(&io, path))
1716 config_errors = FALSE;
1718 if (io_load(&io, " \t", read_option) == ERR ||
1719 config_errors == TRUE)
1720 warn("Errors while loading %s.", path);
1726 const char *home = getenv("HOME");
1727 const char *tigrc_user = getenv("TIGRC_USER");
1728 const char *tigrc_system = getenv("TIGRC_SYSTEM");
1729 char buf[SIZEOF_STR];
1731 add_builtin_run_requests();
1734 tigrc_system = SYSCONFDIR "/tigrc";
1735 load_option_file(tigrc_system);
1738 if (!home || !string_format(buf, "%s/.tigrc", home))
1742 load_option_file(tigrc_user);
1755 /* The display array of active views and the index of the current view. */
1756 static struct view *display[2];
1757 static unsigned int current_view;
1759 #define foreach_displayed_view(view, i) \
1760 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1762 #define displayed_views() (display[1] != NULL ? 2 : 1)
1764 /* Current head and commit ID */
1765 static char ref_blob[SIZEOF_REF] = "";
1766 static char ref_commit[SIZEOF_REF] = "HEAD";
1767 static char ref_head[SIZEOF_REF] = "HEAD";
1770 const char *name; /* View name */
1771 const char *cmd_env; /* Command line set via environment */
1772 const char *id; /* Points to either of ref_{head,commit,blob} */
1774 struct view_ops *ops; /* View operations */
1776 enum keymap keymap; /* What keymap does this view have */
1777 bool git_dir; /* Whether the view requires a git directory. */
1779 char ref[SIZEOF_REF]; /* Hovered commit reference */
1780 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
1782 int height, width; /* The width and height of the main window */
1783 WINDOW *win; /* The main window */
1784 WINDOW *title; /* The title window living below the main window */
1787 unsigned long offset; /* Offset of the window top */
1788 unsigned long yoffset; /* Offset from the window side. */
1789 unsigned long lineno; /* Current line number */
1790 unsigned long p_offset; /* Previous offset of the window top */
1791 unsigned long p_yoffset;/* Previous offset from the window side */
1792 unsigned long p_lineno; /* Previous current line number */
1793 bool p_restore; /* Should the previous position be restored. */
1796 char grep[SIZEOF_STR]; /* Search string */
1797 regex_t *regex; /* Pre-compiled regexp */
1799 /* If non-NULL, points to the view that opened this view. If this view
1800 * is closed tig will switch back to the parent view. */
1801 struct view *parent;
1804 size_t lines; /* Total number of lines */
1805 struct line *line; /* Line index */
1806 unsigned int digits; /* Number of digits in the lines member. */
1809 struct line *curline; /* Line currently being drawn. */
1810 enum line_type curtype; /* Attribute currently used for drawing. */
1811 unsigned long col; /* Column when drawing. */
1812 bool has_scrolled; /* View was scrolled. */
1822 /* What type of content being displayed. Used in the title bar. */
1824 /* Default command arguments. */
1826 /* Open and reads in all view content. */
1827 bool (*open)(struct view *view);
1828 /* Read one line; updates view->line. */
1829 bool (*read)(struct view *view, char *data);
1830 /* Draw one line; @lineno must be < view->height. */
1831 bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
1832 /* Depending on view handle a special requests. */
1833 enum request (*request)(struct view *view, enum request request, struct line *line);
1834 /* Search for regexp in a line. */
1835 bool (*grep)(struct view *view, struct line *line);
1837 void (*select)(struct view *view, struct line *line);
1840 static struct view_ops blame_ops;
1841 static struct view_ops blob_ops;
1842 static struct view_ops diff_ops;
1843 static struct view_ops help_ops;
1844 static struct view_ops log_ops;
1845 static struct view_ops main_ops;
1846 static struct view_ops pager_ops;
1847 static struct view_ops stage_ops;
1848 static struct view_ops status_ops;
1849 static struct view_ops tree_ops;
1850 static struct view_ops branch_ops;
1852 #define VIEW_STR(name, env, ref, ops, map, git) \
1853 { name, #env, ref, ops, map, git }
1855 #define VIEW_(id, name, ops, git, ref) \
1856 VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
1859 static struct view views[] = {
1860 VIEW_(MAIN, "main", &main_ops, TRUE, ref_head),
1861 VIEW_(DIFF, "diff", &diff_ops, TRUE, ref_commit),
1862 VIEW_(LOG, "log", &log_ops, TRUE, ref_head),
1863 VIEW_(TREE, "tree", &tree_ops, TRUE, ref_commit),
1864 VIEW_(BLOB, "blob", &blob_ops, TRUE, ref_blob),
1865 VIEW_(BLAME, "blame", &blame_ops, TRUE, ref_commit),
1866 VIEW_(BRANCH, "branch", &branch_ops, TRUE, ref_head),
1867 VIEW_(HELP, "help", &help_ops, FALSE, ""),
1868 VIEW_(PAGER, "pager", &pager_ops, FALSE, "stdin"),
1869 VIEW_(STATUS, "status", &status_ops, TRUE, ""),
1870 VIEW_(STAGE, "stage", &stage_ops, TRUE, ""),
1873 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
1874 #define VIEW_REQ(view) ((view) - views + REQ_OFFSET + 1)
1876 #define foreach_view(view, i) \
1877 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1879 #define view_is_displayed(view) \
1880 (view == display[0] || view == display[1])
1887 static chtype line_graphics[] = {
1888 /* LINE_GRAPHIC_VLINE: */ '|'
1892 set_view_attr(struct view *view, enum line_type type)
1894 if (!view->curline->selected && view->curtype != type) {
1895 wattrset(view->win, get_line_attr(type));
1896 wchgat(view->win, -1, 0, type, NULL);
1897 view->curtype = type;
1902 draw_chars(struct view *view, enum line_type type, const char *string,
1903 int max_len, bool use_tilde)
1907 int trimmed = FALSE;
1908 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
1914 len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde);
1916 col = len = strlen(string);
1917 if (len > max_len) {
1921 col = len = max_len;
1926 set_view_attr(view, type);
1928 waddnstr(view->win, string, len);
1929 if (trimmed && use_tilde) {
1930 set_view_attr(view, LINE_DELIMITER);
1931 waddch(view->win, '~');
1939 draw_space(struct view *view, enum line_type type, int max, int spaces)
1941 static char space[] = " ";
1944 spaces = MIN(max, spaces);
1946 while (spaces > 0) {
1947 int len = MIN(spaces, sizeof(space) - 1);
1949 col += draw_chars(view, type, space, len, FALSE);
1957 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
1959 view->col += draw_chars(view, type, string, view->width + view->yoffset - view->col, trim);
1960 return view->width + view->yoffset <= view->col;
1964 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
1966 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
1967 int max = view->width + view->yoffset - view->col;
1973 set_view_attr(view, type);
1974 /* Using waddch() instead of waddnstr() ensures that
1975 * they'll be rendered correctly for the cursor line. */
1976 for (i = skip; i < size; i++)
1977 waddch(view->win, graphic[i]);
1980 if (size < max && skip <= size)
1981 waddch(view->win, ' ');
1984 return view->width + view->yoffset <= view->col;
1988 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
1990 int max = MIN(view->width + view->yoffset - view->col, len);
1994 col = draw_chars(view, type, text, max - 1, trim);
1996 col = draw_space(view, type, max - 1, max - 1);
1999 view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
2000 return view->width + view->yoffset <= view->col;
2004 draw_date(struct view *view, time_t *time)
2006 const char *date = mkdate(time);
2008 return draw_field(view, LINE_DATE, date, DATE_COLS, FALSE);
2012 draw_author(struct view *view, const char *author)
2014 bool trim = opt_author_cols == 0 || opt_author_cols > 5 || !author;
2017 static char initials[10];
2020 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@')
2022 memset(initials, 0, sizeof(initials));
2023 for (pos = 0; *author && pos < opt_author_cols - 1; author++, pos++) {
2024 while (is_initial_sep(*author))
2026 strncpy(&initials[pos], author, sizeof(initials) - 1 - pos);
2027 while (*author && !is_initial_sep(author[1]))
2034 return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2038 draw_mode(struct view *view, mode_t mode)
2044 else if (S_ISLNK(mode))
2046 else if (S_ISGITLINK(mode))
2048 else if (S_ISREG(mode) && mode & S_IXUSR)
2050 else if (S_ISREG(mode))
2055 return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2059 draw_lineno(struct view *view, unsigned int lineno)
2062 int digits3 = view->digits < 3 ? 3 : view->digits;
2063 int max = MIN(view->width + view->yoffset - view->col, digits3);
2066 lineno += view->offset + 1;
2067 if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2068 static char fmt[] = "%1ld";
2070 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2071 if (string_format(number, fmt, lineno))
2075 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2077 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2078 return draw_graphic(view, LINE_DEFAULT, &line_graphics[LINE_GRAPHIC_VLINE], 1);
2082 draw_view_line(struct view *view, unsigned int lineno)
2085 bool selected = (view->offset + lineno == view->lineno);
2087 assert(view_is_displayed(view));
2089 if (view->offset + lineno >= view->lines)
2092 line = &view->line[view->offset + lineno];
2094 wmove(view->win, lineno, 0);
2096 wclrtoeol(view->win);
2098 view->curline = line;
2099 view->curtype = LINE_NONE;
2100 line->selected = FALSE;
2101 line->dirty = line->cleareol = 0;
2104 set_view_attr(view, LINE_CURSOR);
2105 line->selected = TRUE;
2106 view->ops->select(view, line);
2109 return view->ops->draw(view, line, lineno);
2113 redraw_view_dirty(struct view *view)
2118 for (lineno = 0; lineno < view->height; lineno++) {
2119 if (view->offset + lineno >= view->lines)
2121 if (!view->line[view->offset + lineno].dirty)
2124 if (!draw_view_line(view, lineno))
2130 wnoutrefresh(view->win);
2134 redraw_view_from(struct view *view, int lineno)
2136 assert(0 <= lineno && lineno < view->height);
2138 for (; lineno < view->height; lineno++) {
2139 if (!draw_view_line(view, lineno))
2143 wnoutrefresh(view->win);
2147 redraw_view(struct view *view)
2150 redraw_view_from(view, 0);
2155 update_view_title(struct view *view)
2157 char buf[SIZEOF_STR];
2158 char state[SIZEOF_STR];
2159 size_t bufpos = 0, statelen = 0;
2161 assert(view_is_displayed(view));
2163 if (view != VIEW(REQ_VIEW_STATUS) && view->lines) {
2164 unsigned int view_lines = view->offset + view->height;
2165 unsigned int lines = view->lines
2166 ? MIN(view_lines, view->lines) * 100 / view->lines
2169 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2178 time_t secs = time(NULL) - view->start_time;
2180 /* Three git seconds are a long time ... */
2182 string_format_from(state, &statelen, " loading %lds", secs);
2185 string_format_from(buf, &bufpos, "[%s]", view->name);
2186 if (*view->ref && bufpos < view->width) {
2187 size_t refsize = strlen(view->ref);
2188 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2190 if (minsize < view->width)
2191 refsize = view->width - minsize + 7;
2192 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2195 if (statelen && bufpos < view->width) {
2196 string_format_from(buf, &bufpos, "%s", state);
2199 if (view == display[current_view])
2200 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2202 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2204 mvwaddnstr(view->title, 0, 0, buf, bufpos);
2205 wclrtoeol(view->title);
2206 wnoutrefresh(view->title);
2210 resize_display(void)
2213 struct view *base = display[0];
2214 struct view *view = display[1] ? display[1] : display[0];
2216 /* Setup window dimensions */
2218 getmaxyx(stdscr, base->height, base->width);
2220 /* Make room for the status window. */
2224 /* Horizontal split. */
2225 view->width = base->width;
2226 view->height = SCALE_SPLIT_VIEW(base->height);
2227 base->height -= view->height;
2229 /* Make room for the title bar. */
2233 /* Make room for the title bar. */
2238 foreach_displayed_view (view, i) {
2240 view->win = newwin(view->height, 0, offset, 0);
2242 die("Failed to create %s view", view->name);
2244 scrollok(view->win, FALSE);
2246 view->title = newwin(1, 0, offset + view->height, 0);
2248 die("Failed to create title window");
2251 wresize(view->win, view->height, view->width);
2252 mvwin(view->win, offset, 0);
2253 mvwin(view->title, offset + view->height, 0);
2256 offset += view->height + 1;
2261 redraw_display(bool clear)
2266 foreach_displayed_view (view, i) {
2270 update_view_title(view);
2275 toggle_view_option(bool *option, const char *help)
2278 redraw_display(FALSE);
2279 report("%sabling %s", *option ? "En" : "Dis", help);
2283 maximize_view(struct view *view)
2285 memset(display, 0, sizeof(display));
2287 display[current_view] = view;
2289 redraw_display(FALSE);
2299 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2301 if (lineno >= view->lines)
2302 lineno = view->lines > 0 ? view->lines - 1 : 0;
2304 if (offset > lineno || offset + view->height <= lineno) {
2305 unsigned long half = view->height / 2;
2308 offset = lineno - half;
2313 if (offset != view->offset || lineno != view->lineno) {
2314 view->offset = offset;
2315 view->lineno = lineno;
2323 apply_step(double step, int value)
2327 value *= step + 0.01;
2328 return value ? value : 1;
2331 /* Scrolling backend */
2333 do_scroll_view(struct view *view, int lines)
2335 bool redraw_current_line = FALSE;
2337 /* The rendering expects the new offset. */
2338 view->offset += lines;
2340 assert(0 <= view->offset && view->offset < view->lines);
2343 /* Move current line into the view. */
2344 if (view->lineno < view->offset) {
2345 view->lineno = view->offset;
2346 redraw_current_line = TRUE;
2347 } else if (view->lineno >= view->offset + view->height) {
2348 view->lineno = view->offset + view->height - 1;
2349 redraw_current_line = TRUE;
2352 assert(view->offset <= view->lineno && view->lineno < view->lines);
2354 /* Redraw the whole screen if scrolling is pointless. */
2355 if (view->height < ABS(lines)) {
2359 int line = lines > 0 ? view->height - lines : 0;
2360 int end = line + ABS(lines);
2362 scrollok(view->win, TRUE);
2363 wscrl(view->win, lines);
2364 scrollok(view->win, FALSE);
2366 while (line < end && draw_view_line(view, line))
2369 if (redraw_current_line)
2370 draw_view_line(view, view->lineno - view->offset);
2371 wnoutrefresh(view->win);
2374 view->has_scrolled = TRUE;
2378 /* Scroll frontend */
2380 scroll_view(struct view *view, enum request request)
2384 assert(view_is_displayed(view));
2387 case REQ_SCROLL_LEFT:
2388 if (view->yoffset == 0) {
2389 report("Cannot scroll beyond the first column");
2392 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2395 view->yoffset -= apply_step(opt_hscroll, view->width);
2396 redraw_view_from(view, 0);
2399 case REQ_SCROLL_RIGHT:
2400 view->yoffset += apply_step(opt_hscroll, view->width);
2404 case REQ_SCROLL_PAGE_DOWN:
2405 lines = view->height;
2406 case REQ_SCROLL_LINE_DOWN:
2407 if (view->offset + lines > view->lines)
2408 lines = view->lines - view->offset;
2410 if (lines == 0 || view->offset + view->height >= view->lines) {
2411 report("Cannot scroll beyond the last line");
2416 case REQ_SCROLL_PAGE_UP:
2417 lines = view->height;
2418 case REQ_SCROLL_LINE_UP:
2419 if (lines > view->offset)
2420 lines = view->offset;
2423 report("Cannot scroll beyond the first line");
2431 die("request %d not handled in switch", request);
2434 do_scroll_view(view, lines);
2439 move_view(struct view *view, enum request request)
2441 int scroll_steps = 0;
2445 case REQ_MOVE_FIRST_LINE:
2446 steps = -view->lineno;
2449 case REQ_MOVE_LAST_LINE:
2450 steps = view->lines - view->lineno - 1;
2453 case REQ_MOVE_PAGE_UP:
2454 steps = view->height > view->lineno
2455 ? -view->lineno : -view->height;
2458 case REQ_MOVE_PAGE_DOWN:
2459 steps = view->lineno + view->height >= view->lines
2460 ? view->lines - view->lineno - 1 : view->height;
2472 die("request %d not handled in switch", request);
2475 if (steps <= 0 && view->lineno == 0) {
2476 report("Cannot move beyond the first line");
2479 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2480 report("Cannot move beyond the last line");
2484 /* Move the current line */
2485 view->lineno += steps;
2486 assert(0 <= view->lineno && view->lineno < view->lines);
2488 /* Check whether the view needs to be scrolled */
2489 if (view->lineno < view->offset ||
2490 view->lineno >= view->offset + view->height) {
2491 scroll_steps = steps;
2492 if (steps < 0 && -steps > view->offset) {
2493 scroll_steps = -view->offset;
2495 } else if (steps > 0) {
2496 if (view->lineno == view->lines - 1 &&
2497 view->lines > view->height) {
2498 scroll_steps = view->lines - view->offset - 1;
2499 if (scroll_steps >= view->height)
2500 scroll_steps -= view->height - 1;
2505 if (!view_is_displayed(view)) {
2506 view->offset += scroll_steps;
2507 assert(0 <= view->offset && view->offset < view->lines);
2508 view->ops->select(view, &view->line[view->lineno]);
2512 /* Repaint the old "current" line if we be scrolling */
2513 if (ABS(steps) < view->height)
2514 draw_view_line(view, view->lineno - steps - view->offset);
2517 do_scroll_view(view, scroll_steps);
2521 /* Draw the current line */
2522 draw_view_line(view, view->lineno - view->offset);
2524 wnoutrefresh(view->win);
2533 static void search_view(struct view *view, enum request request);
2536 grep_text(struct view *view, const char *text[])
2541 for (i = 0; text[i]; i++)
2543 regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
2549 select_view_line(struct view *view, unsigned long lineno)
2551 unsigned long old_lineno = view->lineno;
2552 unsigned long old_offset = view->offset;
2554 if (goto_view_line(view, view->offset, lineno)) {
2555 if (view_is_displayed(view)) {
2556 if (old_offset != view->offset) {
2559 draw_view_line(view, old_lineno - view->offset);
2560 draw_view_line(view, view->lineno - view->offset);
2561 wnoutrefresh(view->win);
2564 view->ops->select(view, &view->line[view->lineno]);
2570 find_next(struct view *view, enum request request)
2572 unsigned long lineno = view->lineno;
2577 report("No previous search");
2579 search_view(view, request);
2589 case REQ_SEARCH_BACK:
2598 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2599 lineno += direction;
2601 /* Note, lineno is unsigned long so will wrap around in which case it
2602 * will become bigger than view->lines. */
2603 for (; lineno < view->lines; lineno += direction) {
2604 if (view->ops->grep(view, &view->line[lineno])) {
2605 select_view_line(view, lineno);
2606 report("Line %ld matches '%s'", lineno + 1, view->grep);
2611 report("No match found for '%s'", view->grep);
2615 search_view(struct view *view, enum request request)
2620 regfree(view->regex);
2623 view->regex = calloc(1, sizeof(*view->regex));
2628 regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2629 if (regex_err != 0) {
2630 char buf[SIZEOF_STR] = "unknown error";
2632 regerror(regex_err, view->regex, buf, sizeof(buf));
2633 report("Search failed: %s", buf);
2637 string_copy(view->grep, opt_search);
2639 find_next(view, request);
2643 * Incremental updating
2647 reset_view(struct view *view)
2651 for (i = 0; i < view->lines; i++)
2652 free(view->line[i].data);
2655 view->p_offset = view->offset;
2656 view->p_yoffset = view->yoffset;
2657 view->p_lineno = view->lineno;
2665 view->update_secs = 0;
2669 free_argv(const char *argv[])
2673 for (argc = 0; argv[argc]; argc++)
2674 free((void *) argv[argc]);
2678 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
2680 char buf[SIZEOF_STR];
2682 bool noreplace = flags == FORMAT_NONE;
2684 free_argv(dst_argv);
2686 for (argc = 0; src_argv[argc]; argc++) {
2687 const char *arg = src_argv[argc];
2691 char *next = strstr(arg, "%(");
2692 int len = next - arg;
2695 if (!next || noreplace) {
2696 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
2701 } else if (!prefixcmp(next, "%(directory)")) {
2704 } else if (!prefixcmp(next, "%(file)")) {
2707 } else if (!prefixcmp(next, "%(ref)")) {
2708 value = *opt_ref ? opt_ref : "HEAD";
2710 } else if (!prefixcmp(next, "%(head)")) {
2713 } else if (!prefixcmp(next, "%(commit)")) {
2716 } else if (!prefixcmp(next, "%(blob)")) {
2720 report("Unknown replacement: `%s`", next);
2724 if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
2727 arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
2730 dst_argv[argc] = strdup(buf);
2731 if (!dst_argv[argc])
2735 dst_argv[argc] = NULL;
2737 return src_argv[argc] == NULL;
2741 restore_view_position(struct view *view)
2743 if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
2746 /* Changing the view position cancels the restoring. */
2747 /* FIXME: Changing back to the first line is not detected. */
2748 if (view->offset != 0 || view->lineno != 0) {
2749 view->p_restore = FALSE;
2753 if (goto_view_line(view, view->p_offset, view->p_lineno) &&
2754 view_is_displayed(view))
2757 view->yoffset = view->p_yoffset;
2758 view->p_restore = FALSE;
2764 end_update(struct view *view, bool force)
2768 while (!view->ops->read(view, NULL))
2771 set_nonblocking_input(FALSE);
2773 kill_io(view->pipe);
2774 done_io(view->pipe);
2779 setup_update(struct view *view, const char *vid)
2781 set_nonblocking_input(TRUE);
2783 string_copy_rev(view->vid, vid);
2784 view->pipe = &view->io;
2785 view->start_time = time(NULL);
2789 prepare_update(struct view *view, const char *argv[], const char *dir,
2790 enum format_flags flags)
2793 end_update(view, TRUE);
2794 return init_io_rd(&view->io, argv, dir, flags);
2798 prepare_update_file(struct view *view, const char *name)
2801 end_update(view, TRUE);
2802 return io_open(&view->io, name);
2806 begin_update(struct view *view, bool refresh)
2809 end_update(view, TRUE);
2812 if (!start_io(&view->io))
2816 if (view == VIEW(REQ_VIEW_TREE) && strcmp(view->vid, view->id))
2819 if (!run_io_rd(&view->io, view->ops->argv, FORMAT_ALL))
2822 /* Put the current ref_* value to the view title ref
2823 * member. This is needed by the blob view. Most other
2824 * views sets it automatically after loading because the
2825 * first line is a commit line. */
2826 string_copy_rev(view->ref, view->id);
2829 setup_update(view, view->id);
2835 update_view(struct view *view)
2837 char out_buffer[BUFSIZ * 2];
2839 /* Clear the view and redraw everything since the tree sorting
2840 * might have rearranged things. */
2841 bool redraw = view->lines == 0;
2842 bool can_read = TRUE;
2847 if (!io_can_read(view->pipe)) {
2848 if (view->lines == 0 && view_is_displayed(view)) {
2849 time_t secs = time(NULL) - view->start_time;
2851 if (secs > 1 && secs > view->update_secs) {
2852 if (view->update_secs == 0)
2854 update_view_title(view);
2855 view->update_secs = secs;
2861 for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
2862 if (opt_iconv != ICONV_NONE) {
2863 ICONV_CONST char *inbuf = line;
2864 size_t inlen = strlen(line) + 1;
2866 char *outbuf = out_buffer;
2867 size_t outlen = sizeof(out_buffer);
2871 ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2872 if (ret != (size_t) -1)
2876 if (!view->ops->read(view, line)) {
2877 report("Allocation failure");
2878 end_update(view, TRUE);
2884 unsigned long lines = view->lines;
2887 for (digits = 0; lines; digits++)
2890 /* Keep the displayed view in sync with line number scaling. */
2891 if (digits != view->digits) {
2892 view->digits = digits;
2893 if (opt_line_number || view == VIEW(REQ_VIEW_BLAME))
2898 if (io_error(view->pipe)) {
2899 report("Failed to read: %s", io_strerror(view->pipe));
2900 end_update(view, TRUE);
2902 } else if (io_eof(view->pipe)) {
2904 end_update(view, FALSE);
2907 if (restore_view_position(view))
2910 if (!view_is_displayed(view))
2914 redraw_view_from(view, 0);
2916 redraw_view_dirty(view);
2918 /* Update the title _after_ the redraw so that if the redraw picks up a
2919 * commit reference in view->ref it'll be available here. */
2920 update_view_title(view);
2924 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
2926 static struct line *
2927 add_line_data(struct view *view, void *data, enum line_type type)
2931 if (!realloc_lines(&view->line, view->lines, 1))
2934 line = &view->line[view->lines++];
2935 memset(line, 0, sizeof(*line));
2943 static struct line *
2944 add_line_text(struct view *view, const char *text, enum line_type type)
2946 char *data = text ? strdup(text) : NULL;
2948 return data ? add_line_data(view, data, type) : NULL;
2951 static struct line *
2952 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
2954 char buf[SIZEOF_STR];
2957 va_start(args, fmt);
2958 if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
2962 return buf[0] ? add_line_text(view, buf, type) : NULL;
2970 OPEN_DEFAULT = 0, /* Use default view switching. */
2971 OPEN_SPLIT = 1, /* Split current view. */
2972 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
2973 OPEN_REFRESH = 16, /* Refresh view using previous command. */
2974 OPEN_PREPARED = 32, /* Open already prepared command. */
2978 open_view(struct view *prev, enum request request, enum open_flags flags)
2980 bool split = !!(flags & OPEN_SPLIT);
2981 bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
2982 bool nomaximize = !!(flags & OPEN_REFRESH);
2983 struct view *view = VIEW(request);
2984 int nviews = displayed_views();
2985 struct view *base_view = display[0];
2987 if (view == prev && nviews == 1 && !reload) {
2988 report("Already in %s view", view->name);
2992 if (view->git_dir && !opt_git_dir[0]) {
2993 report("The %s view is disabled in pager view", view->name);
3000 } else if (!nomaximize) {
3001 /* Maximize the current view. */
3002 memset(display, 0, sizeof(display));
3004 display[current_view] = view;
3007 /* Resize the view when switching between split- and full-screen,
3008 * or when switching between two different full-screen views. */
3009 if (nviews != displayed_views() ||
3010 (nviews == 1 && base_view != display[0]))
3013 if (view->ops->open) {
3015 end_update(view, TRUE);
3016 if (!view->ops->open(view)) {
3017 report("Failed to load %s view", view->name);
3020 restore_view_position(view);
3022 } else if ((reload || strcmp(view->vid, view->id)) &&
3023 !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3024 report("Failed to load %s view", view->name);
3028 if (split && prev->lineno - prev->offset >= prev->height) {
3029 /* Take the title line into account. */
3030 int lines = prev->lineno - prev->offset - prev->height + 1;
3032 /* Scroll the view that was split if the current line is
3033 * outside the new limited view. */
3034 do_scroll_view(prev, lines);
3037 if (prev && view != prev) {
3039 /* "Blur" the previous view. */
3040 update_view_title(prev);
3043 view->parent = prev;
3046 if (view->pipe && view->lines == 0) {
3047 /* Clear the old view and let the incremental updating refill
3050 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3052 } else if (view_is_displayed(view)) {
3059 open_external_viewer(const char *argv[], const char *dir)
3061 def_prog_mode(); /* save current tty modes */
3062 endwin(); /* restore original tty modes */
3063 run_io_fg(argv, dir);
3064 fprintf(stderr, "Press Enter to continue");
3067 redraw_display(TRUE);
3071 open_mergetool(const char *file)
3073 const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3075 open_external_viewer(mergetool_argv, opt_cdup);
3079 open_editor(bool from_root, const char *file)
3081 const char *editor_argv[] = { "vi", file, NULL };
3084 editor = getenv("GIT_EDITOR");
3085 if (!editor && *opt_editor)
3086 editor = opt_editor;
3088 editor = getenv("VISUAL");
3090 editor = getenv("EDITOR");
3094 editor_argv[0] = editor;
3095 open_external_viewer(editor_argv, from_root ? opt_cdup : NULL);
3099 open_run_request(enum request request)
3101 struct run_request *req = get_run_request(request);
3102 const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3105 report("Unknown run request");
3109 if (format_argv(argv, req->argv, FORMAT_ALL))
3110 open_external_viewer(argv, NULL);
3115 * User request switch noodle
3119 view_driver(struct view *view, enum request request)
3123 if (request == REQ_NONE)
3126 if (request > REQ_NONE) {
3127 open_run_request(request);
3128 /* FIXME: When all views can refresh always do this. */
3129 if (view == VIEW(REQ_VIEW_STATUS) ||
3130 view == VIEW(REQ_VIEW_MAIN) ||
3131 view == VIEW(REQ_VIEW_LOG) ||
3132 view == VIEW(REQ_VIEW_BRANCH) ||
3133 view == VIEW(REQ_VIEW_STAGE))
3134 request = REQ_REFRESH;
3139 if (view && view->lines) {
3140 request = view->ops->request(view, request, &view->line[view->lineno]);
3141 if (request == REQ_NONE)
3148 case REQ_MOVE_PAGE_UP:
3149 case REQ_MOVE_PAGE_DOWN:
3150 case REQ_MOVE_FIRST_LINE:
3151 case REQ_MOVE_LAST_LINE:
3152 move_view(view, request);
3155 case REQ_SCROLL_LEFT:
3156 case REQ_SCROLL_RIGHT:
3157 case REQ_SCROLL_LINE_DOWN:
3158 case REQ_SCROLL_LINE_UP:
3159 case REQ_SCROLL_PAGE_DOWN:
3160 case REQ_SCROLL_PAGE_UP:
3161 scroll_view(view, request);
3164 case REQ_VIEW_BLAME:
3166 report("No file chosen, press %s to open tree view",
3167 get_key(REQ_VIEW_TREE));
3170 open_view(view, request, OPEN_DEFAULT);
3175 report("No file chosen, press %s to open tree view",
3176 get_key(REQ_VIEW_TREE));
3179 open_view(view, request, OPEN_DEFAULT);
3182 case REQ_VIEW_PAGER:
3183 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3184 report("No pager content, press %s to run command from prompt",
3185 get_key(REQ_PROMPT));
3188 open_view(view, request, OPEN_DEFAULT);
3191 case REQ_VIEW_STAGE:
3192 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3193 report("No stage content, press %s to open the status view and choose file",
3194 get_key(REQ_VIEW_STATUS));
3197 open_view(view, request, OPEN_DEFAULT);
3200 case REQ_VIEW_STATUS:
3201 if (opt_is_inside_work_tree == FALSE) {
3202 report("The status view requires a working tree");
3205 open_view(view, request, OPEN_DEFAULT);
3213 case REQ_VIEW_BRANCH:
3214 open_view(view, request, OPEN_DEFAULT);
3219 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3221 if ((view == VIEW(REQ_VIEW_DIFF) &&
3222 view->parent == VIEW(REQ_VIEW_MAIN)) ||
3223 (view == VIEW(REQ_VIEW_DIFF) &&
3224 view->parent == VIEW(REQ_VIEW_BLAME)) ||
3225 (view == VIEW(REQ_VIEW_STAGE) &&
3226 view->parent == VIEW(REQ_VIEW_STATUS)) ||
3227 (view == VIEW(REQ_VIEW_BLOB) &&
3228 view->parent == VIEW(REQ_VIEW_TREE)) ||
3229 (view == VIEW(REQ_VIEW_MAIN) &&
3230 view->parent == VIEW(REQ_VIEW_BRANCH))) {
3233 view = view->parent;
3234 line = view->lineno;
3235 move_view(view, request);
3236 if (view_is_displayed(view))
3237 update_view_title(view);
3238 if (line != view->lineno)
3239 view->ops->request(view, REQ_ENTER,
3240 &view->line[view->lineno]);
3243 move_view(view, request);
3249 int nviews = displayed_views();
3250 int next_view = (current_view + 1) % nviews;
3252 if (next_view == current_view) {
3253 report("Only one view is displayed");
3257 current_view = next_view;
3258 /* Blur out the title of the previous view. */
3259 update_view_title(view);
3264 report("Refreshing is not yet supported for the %s view", view->name);
3268 if (displayed_views() == 2)
3269 maximize_view(view);
3272 case REQ_TOGGLE_LINENO:
3273 toggle_view_option(&opt_line_number, "line numbers");
3276 case REQ_TOGGLE_DATE:
3277 toggle_view_option(&opt_date, "date display");
3280 case REQ_TOGGLE_AUTHOR:
3281 toggle_view_option(&opt_author, "author display");
3284 case REQ_TOGGLE_REV_GRAPH:
3285 toggle_view_option(&opt_rev_graph, "revision graph display");
3288 case REQ_TOGGLE_REFS:
3289 toggle_view_option(&opt_show_refs, "reference display");
3292 case REQ_TOGGLE_SORT_FIELD:
3293 case REQ_TOGGLE_SORT_ORDER:
3294 report("Sorting is not yet supported for the %s view", view->name);
3298 case REQ_SEARCH_BACK:
3299 search_view(view, request);
3304 find_next(view, request);
3307 case REQ_STOP_LOADING:
3308 for (i = 0; i < ARRAY_SIZE(views); i++) {
3311 report("Stopped loading the %s view", view->name),
3312 end_update(view, TRUE);
3316 case REQ_SHOW_VERSION:
3317 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3320 case REQ_SCREEN_REDRAW:
3321 redraw_display(TRUE);
3325 report("Nothing to edit");
3329 report("Nothing to enter");
3332 case REQ_VIEW_CLOSE:
3333 /* XXX: Mark closed views by letting view->parent point to the
3334 * view itself. Parents to closed view should never be
3337 view->parent->parent != view->parent) {
3338 maximize_view(view->parent);
3339 view->parent = view;
3347 report("Unknown key, press 'h' for help");
3356 * View backend utilities
3366 const enum sort_field *fields;
3367 size_t size, current;
3371 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3372 #define get_sort_field(state) ((state).fields[(state).current])
3373 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3376 sort_view(struct view *view, enum request request, struct sort_state *state,
3377 int (*compare)(const void *, const void *))
3380 case REQ_TOGGLE_SORT_FIELD:
3381 state->current = (state->current + 1) % state->size;
3384 case REQ_TOGGLE_SORT_ORDER:
3385 state->reverse = !state->reverse;
3388 die("Not a sort request");
3391 qsort(view->line, view->lines, sizeof(*view->line), compare);
3395 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3397 /* Small author cache to reduce memory consumption. It uses binary
3398 * search to lookup or find place to position new entries. No entries
3399 * are ever freed. */
3401 get_author(const char *name)
3403 static const char **authors;
3404 static size_t authors_size;
3405 int from = 0, to = authors_size - 1;
3407 while (from <= to) {
3408 size_t pos = (to + from) / 2;
3409 int cmp = strcmp(name, authors[pos]);
3412 return authors[pos];
3420 if (!realloc_authors(&authors, authors_size, 1))
3422 name = strdup(name);
3426 memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3427 authors[from] = name;
3434 parse_timezone(time_t *time, const char *zone)
3438 tz = ('0' - zone[1]) * 60 * 60 * 10;
3439 tz += ('0' - zone[2]) * 60 * 60;
3440 tz += ('0' - zone[3]) * 60;
3441 tz += ('0' - zone[4]);
3449 /* Parse author lines where the name may be empty:
3450 * author <email@address.tld> 1138474660 +0100
3453 parse_author_line(char *ident, const char **author, time_t *time)
3455 char *nameend = strchr(ident, '<');
3456 char *emailend = strchr(ident, '>');
3458 if (nameend && emailend)
3459 *nameend = *emailend = 0;
3460 ident = chomp_string(ident);
3463 ident = chomp_string(nameend + 1);
3468 *author = get_author(ident);
3470 /* Parse epoch and timezone */
3471 if (emailend && emailend[1] == ' ') {
3472 char *secs = emailend + 2;
3473 char *zone = strchr(secs, ' ');
3475 *time = (time_t) atol(secs);
3477 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
3478 parse_timezone(time, zone + 1);
3482 static enum input_status
3483 select_commit_parent_handler(void *data, char *buf, int c)
3485 size_t parents = *(size_t *) data;
3492 parent = atoi(buf) * 10;
3495 if (parent > parents)
3501 select_commit_parent(const char *id, char rev[SIZEOF_REV], const char *path)
3503 char buf[SIZEOF_STR * 4];
3504 const char *revlist_argv[] = {
3505 "git", "rev-list", "-1", "--parents", id, "--", path, NULL
3509 if (!run_io_buf(revlist_argv, buf, sizeof(buf)) ||
3510 (parents = (strlen(buf) / 40) - 1) < 0) {
3511 report("Failed to get parent information");
3514 } else if (parents == 0) {
3516 report("Path '%s' does not exist in the parent", path);
3518 report("The selected commit has no parents");
3523 char prompt[SIZEOF_STR];
3526 if (!string_format(prompt, "Which parent? [1..%d] ", parents))
3528 result = prompt_input(prompt, select_commit_parent_handler, &parents);
3531 parents = atoi(result);
3534 string_copy_rev(rev, &buf[41 * parents]);
3543 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3545 char text[SIZEOF_STR];
3547 if (opt_line_number && draw_lineno(view, lineno))
3550 string_expand(text, sizeof(text), line->data, opt_tab_size);
3551 draw_text(view, line->type, text, TRUE);
3556 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3558 const char *describe_argv[] = { "git", "describe", commit_id, NULL };
3559 char ref[SIZEOF_STR];
3561 if (!run_io_buf(describe_argv, ref, sizeof(ref)) || !*ref)
3564 /* This is the only fatal call, since it can "corrupt" the buffer. */
3565 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
3572 add_pager_refs(struct view *view, struct line *line)
3574 char buf[SIZEOF_STR];
3575 char *commit_id = (char *)line->data + STRING_SIZE("commit ");
3577 size_t bufpos = 0, refpos = 0;
3578 const char *sep = "Refs: ";
3579 bool is_tag = FALSE;
3581 assert(line->type == LINE_COMMIT);
3583 refs = get_refs(commit_id);
3585 if (view == VIEW(REQ_VIEW_DIFF))
3586 goto try_add_describe_ref;
3591 struct ref *ref = refs[refpos];
3592 const char *fmt = ref->tag ? "%s[%s]" :
3593 ref->remote ? "%s<%s>" : "%s%s";
3595 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
3600 } while (refs[refpos++]->next);
3602 if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
3603 try_add_describe_ref:
3604 /* Add <tag>-g<commit_id> "fake" reference. */
3605 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
3612 add_line_text(view, buf, LINE_PP_REFS);
3616 pager_read(struct view *view, char *data)
3623 line = add_line_text(view, data, get_line_type(data));
3627 if (line->type == LINE_COMMIT &&
3628 (view == VIEW(REQ_VIEW_DIFF) ||
3629 view == VIEW(REQ_VIEW_LOG)))
3630 add_pager_refs(view, line);
3636 pager_request(struct view *view, enum request request, struct line *line)
3640 if (request != REQ_ENTER)
3643 if (line->type == LINE_COMMIT &&
3644 (view == VIEW(REQ_VIEW_LOG) ||
3645 view == VIEW(REQ_VIEW_PAGER))) {
3646 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3650 /* Always scroll the view even if it was split. That way
3651 * you can use Enter to scroll through the log view and
3652 * split open each commit diff. */
3653 scroll_view(view, REQ_SCROLL_LINE_DOWN);
3655 /* FIXME: A minor workaround. Scrolling the view will call report("")
3656 * but if we are scrolling a non-current view this won't properly
3657 * update the view title. */
3659 update_view_title(view);
3665 pager_grep(struct view *view, struct line *line)
3667 const char *text[] = { line->data, NULL };
3669 return grep_text(view, text);
3673 pager_select(struct view *view, struct line *line)
3675 if (line->type == LINE_COMMIT) {
3676 char *text = (char *)line->data + STRING_SIZE("commit ");
3678 if (view != VIEW(REQ_VIEW_PAGER))
3679 string_copy_rev(view->ref, text);
3680 string_copy_rev(ref_commit, text);
3684 static struct view_ops pager_ops = {
3695 static const char *log_argv[SIZEOF_ARG] = {
3696 "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3700 log_request(struct view *view, enum request request, struct line *line)
3705 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
3708 return pager_request(view, request, line);
3712 static struct view_ops log_ops = {
3723 static const char *diff_argv[SIZEOF_ARG] = {
3724 "git", "show", "--pretty=fuller", "--no-color", "--root",
3725 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
3728 static struct view_ops diff_ops = {
3744 help_open(struct view *view)
3746 char buf[SIZEOF_STR];
3750 if (view->lines > 0)
3753 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
3755 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
3758 if (req_info[i].request == REQ_NONE)
3761 if (!req_info[i].request) {
3762 add_line_text(view, "", LINE_DEFAULT);
3763 add_line_text(view, req_info[i].help, LINE_DEFAULT);
3767 key = get_key(req_info[i].request);
3769 key = "(no key defined)";
3771 for (bufpos = 0; bufpos <= req_info[i].namelen; bufpos++) {
3772 buf[bufpos] = tolower(req_info[i].name[bufpos]);
3773 if (buf[bufpos] == '_')
3777 add_line_format(view, LINE_DEFAULT, " %-25s %-20s %s",
3778 key, buf, req_info[i].help);
3782 add_line_text(view, "", LINE_DEFAULT);
3783 add_line_text(view, "External commands:", LINE_DEFAULT);
3786 for (i = 0; i < run_requests; i++) {
3787 struct run_request *req = get_run_request(REQ_NONE + i + 1);
3794 key = get_key_name(req->key);
3796 key = "(no key defined)";
3798 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
3799 if (!string_format_from(buf, &bufpos, "%s%s",
3800 argc ? " " : "", req->argv[argc]))
3803 add_line_format(view, LINE_DEFAULT, " %-10s %-14s `%s`",
3804 keymap_table[req->keymap].name, key, buf);
3810 static struct view_ops help_ops = {
3826 struct tree_stack_entry {
3827 struct tree_stack_entry *prev; /* Entry below this in the stack */
3828 unsigned long lineno; /* Line number to restore */
3829 char *name; /* Position of name in opt_path */
3832 /* The top of the path stack. */
3833 static struct tree_stack_entry *tree_stack = NULL;
3834 unsigned long tree_lineno = 0;
3837 pop_tree_stack_entry(void)
3839 struct tree_stack_entry *entry = tree_stack;
3841 tree_lineno = entry->lineno;
3843 tree_stack = entry->prev;
3848 push_tree_stack_entry(const char *name, unsigned long lineno)
3850 struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
3851 size_t pathlen = strlen(opt_path);
3856 entry->prev = tree_stack;
3857 entry->name = opt_path + pathlen;
3860 if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
3861 pop_tree_stack_entry();
3865 /* Move the current line to the first tree entry. */
3867 entry->lineno = lineno;
3870 /* Parse output from git-ls-tree(1):
3872 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
3875 #define SIZEOF_TREE_ATTR \
3876 STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
3878 #define SIZEOF_TREE_MODE \
3879 STRING_SIZE("100644 ")
3881 #define TREE_ID_OFFSET \
3882 STRING_SIZE("100644 blob ")
3885 char id[SIZEOF_REV];
3887 time_t time; /* Date from the author ident. */
3888 const char *author; /* Author of the commit. */
3893 tree_path(const struct line *line)
3895 return ((struct tree_entry *) line->data)->name;
3899 tree_compare_entry(const struct line *line1, const struct line *line2)
3901 if (line1->type != line2->type)
3902 return line1->type == LINE_TREE_DIR ? -1 : 1;
3903 return strcmp(tree_path(line1), tree_path(line2));
3906 static const enum sort_field tree_sort_fields[] = {
3907 ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
3909 static struct sort_state tree_sort_state = SORT_STATE(tree_sort_fields);
3912 tree_compare(const void *l1, const void *l2)
3914 const struct line *line1 = (const struct line *) l1;
3915 const struct line *line2 = (const struct line *) l2;
3916 const struct tree_entry *entry1 = ((const struct line *) l1)->data;
3917 const struct tree_entry *entry2 = ((const struct line *) l2)->data;
3919 if (line1->type == LINE_TREE_HEAD)
3921 if (line2->type == LINE_TREE_HEAD)
3924 switch (get_sort_field(tree_sort_state)) {
3926 return sort_order(tree_sort_state, entry1->time - entry2->time);
3928 case ORDERBY_AUTHOR:
3929 return sort_order(tree_sort_state, strcmp(entry1->author, entry2->author));
3933 return sort_order(tree_sort_state, tree_compare_entry(line1, line2));
3938 static struct line *
3939 tree_entry(struct view *view, enum line_type type, const char *path,
3940 const char *mode, const char *id)
3942 struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
3943 struct line *line = entry ? add_line_data(view, entry, type) : NULL;
3945 if (!entry || !line) {
3950 strncpy(entry->name, path, strlen(path));
3952 entry->mode = strtoul(mode, NULL, 8);
3954 string_copy_rev(entry->id, id);
3960 tree_read_date(struct view *view, char *text, bool *read_date)
3962 static const char *author_name;
3963 static time_t author_time;
3965 if (!text && *read_date) {
3970 char *path = *opt_path ? opt_path : ".";
3971 /* Find next entry to process */
3972 const char *log_file[] = {
3973 "git", "log", "--no-color", "--pretty=raw",
3974 "--cc", "--raw", view->id, "--", path, NULL
3979 tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
3980 report("Tree is empty");
3984 if (!run_io_rd(&io, log_file, FORMAT_NONE)) {
3985 report("Failed to load tree data");
3989 done_io(view->pipe);
3994 } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
3995 parse_author_line(text + STRING_SIZE("author "),
3996 &author_name, &author_time);
3998 } else if (*text == ':') {
4000 size_t annotated = 1;
4003 pos = strchr(text, '\t');
4007 if (*opt_prefix && !strncmp(text, opt_prefix, strlen(opt_prefix)))
4008 text += strlen(opt_prefix);
4009 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
4010 text += strlen(opt_path);
4011 pos = strchr(text, '/');
4015 for (i = 1; i < view->lines; i++) {
4016 struct line *line = &view->line[i];
4017 struct tree_entry *entry = line->data;
4019 annotated += !!entry->author;
4020 if (entry->author || strcmp(entry->name, text))
4023 entry->author = author_name;
4024 entry->time = author_time;
4029 if (annotated == view->lines)
4030 kill_io(view->pipe);
4036 tree_read(struct view *view, char *text)
4038 static bool read_date = FALSE;
4039 struct tree_entry *data;
4040 struct line *entry, *line;
4041 enum line_type type;
4042 size_t textlen = text ? strlen(text) : 0;
4043 char *path = text + SIZEOF_TREE_ATTR;
4045 if (read_date || !text)
4046 return tree_read_date(view, text, &read_date);
4048 if (textlen <= SIZEOF_TREE_ATTR)
4050 if (view->lines == 0 &&
4051 !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
4054 /* Strip the path part ... */
4056 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
4057 size_t striplen = strlen(opt_path);
4059 if (pathlen > striplen)
4060 memmove(path, path + striplen,
4061 pathlen - striplen + 1);
4063 /* Insert "link" to parent directory. */
4064 if (view->lines == 1 &&
4065 !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
4069 type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
4070 entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
4075 /* Skip "Directory ..." and ".." line. */
4076 for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
4077 if (tree_compare_entry(line, entry) <= 0)
4080 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4084 for (; line <= entry; line++)
4085 line->dirty = line->cleareol = 1;
4089 if (tree_lineno > view->lineno) {
4090 view->lineno = tree_lineno;
4098 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4100 struct tree_entry *entry = line->data;
4102 if (line->type == LINE_TREE_HEAD) {
4103 if (draw_text(view, line->type, "Directory path /", TRUE))
4106 if (draw_mode(view, entry->mode))
4109 if (opt_author && draw_author(view, entry->author))
4112 if (opt_date && draw_date(view, entry->author ? &entry->time : NULL))
4115 if (draw_text(view, line->type, entry->name, TRUE))
4123 char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4124 int fd = mkstemp(file);
4127 report("Failed to create temporary file");
4128 else if (!run_io_append(blob_ops.argv, FORMAT_ALL, fd))
4129 report("Failed to save blob data to file");
4131 open_editor(FALSE, file);
4137 tree_request(struct view *view, enum request request, struct line *line)
4139 enum open_flags flags;
4142 case REQ_VIEW_BLAME:
4143 if (line->type != LINE_TREE_FILE) {
4144 report("Blame only supported for files");
4148 string_copy(opt_ref, view->vid);
4152 if (line->type != LINE_TREE_FILE) {
4153 report("Edit only supported for files");
4154 } else if (!is_head_commit(view->vid)) {
4157 open_editor(TRUE, opt_file);
4161 case REQ_TOGGLE_SORT_FIELD:
4162 case REQ_TOGGLE_SORT_ORDER:
4163 sort_view(view, request, &tree_sort_state, tree_compare);
4168 /* quit view if at top of tree */
4169 return REQ_VIEW_CLOSE;
4172 line = &view->line[1];
4182 /* Cleanup the stack if the tree view is at a different tree. */
4183 while (!*opt_path && tree_stack)
4184 pop_tree_stack_entry();
4186 switch (line->type) {
4188 /* Depending on whether it is a subdirectory or parent link
4189 * mangle the path buffer. */
4190 if (line == &view->line[1] && *opt_path) {
4191 pop_tree_stack_entry();
4194 const char *basename = tree_path(line);
4196 push_tree_stack_entry(basename, view->lineno);
4199 /* Trees and subtrees share the same ID, so they are not not
4200 * unique like blobs. */
4201 flags = OPEN_RELOAD;
4202 request = REQ_VIEW_TREE;
4205 case LINE_TREE_FILE:
4206 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4207 request = REQ_VIEW_BLOB;
4214 open_view(view, request, flags);
4215 if (request == REQ_VIEW_TREE)
4216 view->lineno = tree_lineno;
4222 tree_grep(struct view *view, struct line *line)
4224 struct tree_entry *entry = line->data;
4225 const char *text[] = {
4227 opt_author ? entry->author : "",
4228 opt_date ? mkdate(&entry->time) : "",
4232 return grep_text(view, text);
4236 tree_select(struct view *view, struct line *line)
4238 struct tree_entry *entry = line->data;
4240 if (line->type == LINE_TREE_FILE) {
4241 string_copy_rev(ref_blob, entry->id);
4242 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4244 } else if (line->type != LINE_TREE_DIR) {
4248 string_copy_rev(view->ref, entry->id);
4251 static const char *tree_argv[SIZEOF_ARG] = {
4252 "git", "ls-tree", "%(commit)", "%(directory)", NULL
4255 static struct view_ops tree_ops = {
4267 blob_read(struct view *view, char *line)
4271 return add_line_text(view, line, LINE_DEFAULT) != NULL;
4275 blob_request(struct view *view, enum request request, struct line *line)
4282 return pager_request(view, request, line);
4286 static const char *blob_argv[SIZEOF_ARG] = {
4287 "git", "cat-file", "blob", "%(blob)", NULL
4290 static struct view_ops blob_ops = {
4304 * Loading the blame view is a two phase job:
4306 * 1. File content is read either using opt_file from the
4307 * filesystem or using git-cat-file.
4308 * 2. Then blame information is incrementally added by
4309 * reading output from git-blame.
4312 static const char *blame_head_argv[] = {
4313 "git", "blame", "--incremental", "--", "%(file)", NULL
4316 static const char *blame_ref_argv[] = {
4317 "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4320 static const char *blame_cat_file_argv[] = {
4321 "git", "cat-file", "blob", "%(ref):%(file)", NULL
4324 struct blame_commit {
4325 char id[SIZEOF_REV]; /* SHA1 ID. */
4326 char title[128]; /* First line of the commit message. */
4327 const char *author; /* Author of the commit. */
4328 time_t time; /* Date from the author ident. */
4329 char filename[128]; /* Name of file. */
4330 bool has_previous; /* Was a "previous" line detected. */
4334 struct blame_commit *commit;
4335 unsigned long lineno;
4340 blame_open(struct view *view)
4342 if (*opt_ref || !io_open(&view->io, opt_file)) {
4343 if (!run_io_rd(&view->io, blame_cat_file_argv, FORMAT_ALL))
4347 setup_update(view, opt_file);
4348 string_format(view->ref, "%s ...", opt_file);
4353 static struct blame_commit *
4354 get_blame_commit(struct view *view, const char *id)
4358 for (i = 0; i < view->lines; i++) {
4359 struct blame *blame = view->line[i].data;
4364 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4365 return blame->commit;
4369 struct blame_commit *commit = calloc(1, sizeof(*commit));
4372 string_ncopy(commit->id, id, SIZEOF_REV);
4378 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4380 const char *pos = *posref;
4383 pos = strchr(pos + 1, ' ');
4384 if (!pos || !isdigit(pos[1]))
4386 *number = atoi(pos + 1);
4387 if (*number < min || *number > max)
4394 static struct blame_commit *
4395 parse_blame_commit(struct view *view, const char *text, int *blamed)
4397 struct blame_commit *commit;
4398 struct blame *blame;
4399 const char *pos = text + SIZEOF_REV - 2;
4400 size_t orig_lineno = 0;
4404 if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
4407 if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
4408 !parse_number(&pos, &lineno, 1, view->lines) ||
4409 !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4412 commit = get_blame_commit(view, text);
4418 struct line *line = &view->line[lineno + group - 1];
4421 blame->commit = commit;
4422 blame->lineno = orig_lineno + group - 1;
4430 blame_read_file(struct view *view, const char *line, bool *read_file)
4433 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
4436 if (view->lines == 0 && !view->parent)
4437 die("No blame exist for %s", view->vid);
4439 if (view->lines == 0 || !run_io_rd(&io, argv, FORMAT_ALL)) {
4440 report("Failed to load blame data");
4444 done_io(view->pipe);
4450 size_t linelen = strlen(line);
4451 struct blame *blame = malloc(sizeof(*blame) + linelen);
4456 blame->commit = NULL;
4457 strncpy(blame->text, line, linelen);
4458 blame->text[linelen] = 0;
4459 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
4464 match_blame_header(const char *name, char **line)
4466 size_t namelen = strlen(name);
4467 bool matched = !strncmp(name, *line, namelen);
4476 blame_read(struct view *view, char *line)
4478 static struct blame_commit *commit = NULL;
4479 static int blamed = 0;
4480 static bool read_file = TRUE;
4483 return blame_read_file(view, line, &read_file);
4490 string_format(view->ref, "%s", view->vid);
4491 if (view_is_displayed(view)) {
4492 update_view_title(view);
4493 redraw_view_from(view, 0);
4499 commit = parse_blame_commit(view, line, &blamed);
4500 string_format(view->ref, "%s %2d%%", view->vid,
4501 view->lines ? blamed * 100 / view->lines : 0);
4503 } else if (match_blame_header("author ", &line)) {
4504 commit->author = get_author(line);
4506 } else if (match_blame_header("author-time ", &line)) {
4507 commit->time = (time_t) atol(line);
4509 } else if (match_blame_header("author-tz ", &line)) {
4510 parse_timezone(&commit->time, line);
4512 } else if (match_blame_header("summary ", &line)) {
4513 string_ncopy(commit->title, line, strlen(line));
4515 } else if (match_blame_header("previous ", &line)) {
4516 commit->has_previous = TRUE;
4518 } else if (match_blame_header("filename ", &line)) {
4519 string_ncopy(commit->filename, line, strlen(line));
4527 blame_draw(struct view *view, struct line *line, unsigned int lineno)
4529 struct blame *blame = line->data;
4530 time_t *time = NULL;
4531 const char *id = NULL, *author = NULL;
4532 char text[SIZEOF_STR];
4534 if (blame->commit && *blame->commit->filename) {
4535 id = blame->commit->id;
4536 author = blame->commit->author;
4537 time = &blame->commit->time;
4540 if (opt_date && draw_date(view, time))
4543 if (opt_author && draw_author(view, author))
4546 if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
4549 if (draw_lineno(view, lineno))
4552 string_expand(text, sizeof(text), blame->text, opt_tab_size);
4553 draw_text(view, LINE_DEFAULT, text, TRUE);
4558 check_blame_commit(struct blame *blame, bool check_null_id)
4561 report("Commit data not loaded yet");
4562 else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
4563 report("No commit exist for the selected line");
4570 setup_blame_parent_line(struct view *view, struct blame *blame)
4572 const char *diff_tree_argv[] = {
4573 "git", "diff-tree", "-U0", blame->commit->id,
4574 "--", blame->commit->filename, NULL
4577 int parent_lineno = -1;
4578 int blamed_lineno = -1;
4581 if (!run_io(&io, diff_tree_argv, NULL, IO_RD))
4584 while ((line = io_get(&io, '\n', TRUE))) {
4586 char *pos = strchr(line, '+');
4588 parent_lineno = atoi(line + 4);
4590 blamed_lineno = atoi(pos + 1);
4592 } else if (*line == '+' && parent_lineno != -1) {
4593 if (blame->lineno == blamed_lineno - 1 &&
4594 !strcmp(blame->text, line + 1)) {
4595 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
4606 blame_request(struct view *view, enum request request, struct line *line)
4608 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4609 struct blame *blame = line->data;
4612 case REQ_VIEW_BLAME:
4613 if (check_blame_commit(blame, TRUE)) {
4614 string_copy(opt_ref, blame->commit->id);
4615 string_copy(opt_file, blame->commit->filename);
4617 view->lineno = blame->lineno;
4618 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4623 if (check_blame_commit(blame, TRUE) &&
4624 select_commit_parent(blame->commit->id, opt_ref,
4625 blame->commit->filename)) {
4626 string_copy(opt_file, blame->commit->filename);
4627 setup_blame_parent_line(view, blame);
4628 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4633 if (!check_blame_commit(blame, FALSE))
4636 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
4637 !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
4640 if (!strcmp(blame->commit->id, NULL_ID)) {
4641 struct view *diff = VIEW(REQ_VIEW_DIFF);
4642 const char *diff_index_argv[] = {
4643 "git", "diff-index", "--root", "--patch-with-stat",
4644 "-C", "-M", "HEAD", "--", view->vid, NULL
4647 if (!blame->commit->has_previous) {
4648 diff_index_argv[1] = "diff";
4649 diff_index_argv[2] = "--no-color";
4650 diff_index_argv[6] = "--";
4651 diff_index_argv[7] = "/dev/null";
4654 if (!prepare_update(diff, diff_index_argv, NULL, FORMAT_DASH)) {
4655 report("Failed to allocate diff command");
4658 flags |= OPEN_PREPARED;
4661 open_view(view, REQ_VIEW_DIFF, flags);
4662 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
4663 string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
4674 blame_grep(struct view *view, struct line *line)
4676 struct blame *blame = line->data;
4677 struct blame_commit *commit = blame->commit;
4678 const char *text[] = {
4680 commit ? commit->title : "",
4681 commit ? commit->id : "",
4682 commit && opt_author ? commit->author : "",
4683 commit && opt_date ? mkdate(&commit->time) : "",
4687 return grep_text(view, text);
4691 blame_select(struct view *view, struct line *line)
4693 struct blame *blame = line->data;
4694 struct blame_commit *commit = blame->commit;
4699 if (!strcmp(commit->id, NULL_ID))
4700 string_ncopy(ref_commit, "HEAD", 4);
4702 string_copy_rev(ref_commit, commit->id);
4705 static struct view_ops blame_ops = {
4721 const char *author; /* Author of the last commit. */
4722 time_t time; /* Date of the last activity. */
4723 struct ref *ref; /* Name and commit ID information. */
4726 static const enum sort_field branch_sort_fields[] = {
4727 ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4729 static struct sort_state branch_sort_state = SORT_STATE(branch_sort_fields);
4732 branch_compare(const void *l1, const void *l2)
4734 const struct branch *branch1 = ((const struct line *) l1)->data;
4735 const struct branch *branch2 = ((const struct line *) l2)->data;
4737 switch (get_sort_field(branch_sort_state)) {
4739 return sort_order(branch_sort_state, branch1->time - branch2->time);
4741 case ORDERBY_AUTHOR:
4742 return sort_order(branch_sort_state, strcmp(branch1->author, branch2->author));
4746 return sort_order(branch_sort_state, strcmp(branch1->ref->name, branch2->ref->name));
4751 branch_draw(struct view *view, struct line *line, unsigned int lineno)
4753 struct branch *branch = line->data;
4754 enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
4756 if (opt_date && draw_date(view, branch->author ? &branch->time : NULL))
4759 if (opt_author && draw_author(view, branch->author))
4762 draw_text(view, type, branch->ref->name, TRUE);
4767 branch_request(struct view *view, enum request request, struct line *line)
4772 open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
4775 case REQ_TOGGLE_SORT_FIELD:
4776 case REQ_TOGGLE_SORT_ORDER:
4777 sort_view(view, request, &branch_sort_state, branch_compare);
4781 open_view(view, REQ_VIEW_MAIN, OPEN_SPLIT);
4790 branch_read(struct view *view, char *line)
4792 static char id[SIZEOF_REV];
4793 struct branch *reference;
4799 switch (get_line_type(line)) {
4801 string_copy_rev(id, line + STRING_SIZE("commit "));
4805 for (i = 0, reference = NULL; i < view->lines; i++) {
4806 struct branch *branch = view->line[i].data;
4808 if (strcmp(branch->ref->id, id))
4811 view->line[i].dirty = TRUE;
4813 branch->author = reference->author;
4814 branch->time = reference->time;
4818 parse_author_line(line + STRING_SIZE("author "),
4819 &branch->author, &branch->time);
4831 branch_open_visitor(void *data, struct ref *ref)
4833 struct view *view = data;
4834 struct branch *branch;
4836 if (ref->tag || ref->ltag || ref->remote)
4839 branch = calloc(1, sizeof(*branch));
4844 return !!add_line_data(view, branch, LINE_DEFAULT);
4848 branch_open(struct view *view)
4850 const char *branch_log[] = {
4851 "git", "log", "--no-color", "--pretty=raw",
4852 "--simplify-by-decoration", "--all", NULL
4855 if (!run_io_rd(&view->io, branch_log, FORMAT_NONE)) {
4856 report("Failed to load branch data");
4860 setup_update(view, view->id);
4861 foreach_ref(branch_open_visitor, view);
4867 branch_grep(struct view *view, struct line *line)
4869 struct branch *branch = line->data;
4870 const char *text[] = {
4876 return grep_text(view, text);
4880 branch_select(struct view *view, struct line *line)
4882 struct branch *branch = line->data;
4884 string_copy_rev(view->ref, branch->ref->id);
4885 string_copy_rev(ref_commit, branch->ref->id);
4886 string_copy_rev(ref_head, branch->ref->id);
4889 static struct view_ops branch_ops = {
4908 char rev[SIZEOF_REV];
4909 char name[SIZEOF_STR];
4913 char rev[SIZEOF_REV];
4914 char name[SIZEOF_STR];
4918 static char status_onbranch[SIZEOF_STR];
4919 static struct status stage_status;
4920 static enum line_type stage_line_type;
4921 static size_t stage_chunks;
4922 static int *stage_chunk;
4924 DEFINE_ALLOCATOR(realloc_ints, int, 32)
4926 /* This should work even for the "On branch" line. */
4928 status_has_none(struct view *view, struct line *line)
4930 return line < view->line + view->lines && !line[1].data;
4933 /* Get fields from the diff line:
4934 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
4937 status_get_diff(struct status *file, const char *buf, size_t bufsize)
4939 const char *old_mode = buf + 1;
4940 const char *new_mode = buf + 8;
4941 const char *old_rev = buf + 15;
4942 const char *new_rev = buf + 56;
4943 const char *status = buf + 97;
4946 old_mode[-1] != ':' ||
4947 new_mode[-1] != ' ' ||
4948 old_rev[-1] != ' ' ||
4949 new_rev[-1] != ' ' ||
4953 file->status = *status;
4955 string_copy_rev(file->old.rev, old_rev);
4956 string_copy_rev(file->new.rev, new_rev);
4958 file->old.mode = strtoul(old_mode, NULL, 8);
4959 file->new.mode = strtoul(new_mode, NULL, 8);
4961 file->old.name[0] = file->new.name[0] = 0;
4967 status_run(struct view *view, const char *argv[], char status, enum line_type type)
4969 struct status *unmerged = NULL;
4973 if (!run_io(&io, argv, NULL, IO_RD))
4976 add_line_data(view, NULL, type);
4978 while ((buf = io_get(&io, 0, TRUE))) {
4979 struct status *file = unmerged;
4982 file = calloc(1, sizeof(*file));
4983 if (!file || !add_line_data(view, file, type))
4987 /* Parse diff info part. */
4989 file->status = status;
4991 string_copy(file->old.rev, NULL_ID);
4993 } else if (!file->status || file == unmerged) {
4994 if (!status_get_diff(file, buf, strlen(buf)))
4997 buf = io_get(&io, 0, TRUE);
5001 /* Collapse all modified entries that follow an
5002 * associated unmerged entry. */
5003 if (unmerged == file) {
5004 unmerged->status = 'U';
5006 } else if (file->status == 'U') {
5011 /* Grab the old name for rename/copy. */
5012 if (!*file->old.name &&
5013 (file->status == 'R' || file->status == 'C')) {
5014 string_ncopy(file->old.name, buf, strlen(buf));
5016 buf = io_get(&io, 0, TRUE);
5021 /* git-ls-files just delivers a NUL separated list of
5022 * file names similar to the second half of the
5023 * git-diff-* output. */
5024 string_ncopy(file->new.name, buf, strlen(buf));
5025 if (!*file->old.name)
5026 string_copy(file->old.name, file->new.name);
5030 if (io_error(&io)) {
5036 if (!view->line[view->lines - 1].data)
5037 add_line_data(view, NULL, LINE_STAT_NONE);
5043 /* Don't show unmerged entries in the staged section. */
5044 static const char *status_diff_index_argv[] = {
5045 "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5046 "--cached", "-M", "HEAD", NULL
5049 static const char *status_diff_files_argv[] = {
5050 "git", "diff-files", "-z", NULL
5053 static const char *status_list_other_argv[] = {
5054 "git", "ls-files", "-z", "--others", "--exclude-standard", NULL
5057 static const char *status_list_no_head_argv[] = {
5058 "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5061 static const char *update_index_argv[] = {
5062 "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5065 /* Restore the previous line number to stay in the context or select a
5066 * line with something that can be updated. */
5068 status_restore(struct view *view)
5070 if (view->p_lineno >= view->lines)
5071 view->p_lineno = view->lines - 1;
5072 while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
5074 while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
5077 /* If the above fails, always skip the "On branch" line. */
5078 if (view->p_lineno < view->lines)
5079 view->lineno = view->p_lineno;
5083 if (view->lineno < view->offset)
5084 view->offset = view->lineno;
5085 else if (view->offset + view->height <= view->lineno)
5086 view->offset = view->lineno - view->height + 1;
5088 view->p_restore = FALSE;
5092 status_update_onbranch(void)
5094 static const char *paths[][2] = {
5095 { "rebase-apply/rebasing", "Rebasing" },
5096 { "rebase-apply/applying", "Applying mailbox" },
5097 { "rebase-apply/", "Rebasing mailbox" },
5098 { "rebase-merge/interactive", "Interactive rebase" },
5099 { "rebase-merge/", "Rebase merge" },
5100 { "MERGE_HEAD", "Merging" },
5101 { "BISECT_LOG", "Bisecting" },
5102 { "HEAD", "On branch" },
5104 char buf[SIZEOF_STR];
5108 if (is_initial_commit()) {
5109 string_copy(status_onbranch, "Initial commit");
5113 for (i = 0; i < ARRAY_SIZE(paths); i++) {
5114 char *head = opt_head;
5116 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5117 lstat(buf, &stat) < 0)
5123 if (string_format(buf, "%s/rebase-merge/head-name", opt_git_dir) &&
5124 io_open(&io, buf) &&
5125 io_read_buf(&io, buf, sizeof(buf))) {
5127 if (!prefixcmp(head, "refs/heads/"))
5128 head += STRING_SIZE("refs/heads/");
5132 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5133 string_copy(status_onbranch, opt_head);
5137 string_copy(status_onbranch, "Not currently on any branch");
5140 /* First parse staged info using git-diff-index(1), then parse unstaged
5141 * info using git-diff-files(1), and finally untracked files using
5142 * git-ls-files(1). */
5144 status_open(struct view *view)
5148 add_line_data(view, NULL, LINE_STAT_HEAD);
5149 status_update_onbranch();
5151 run_io_bg(update_index_argv);
5153 if (is_initial_commit()) {
5154 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5156 } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5160 if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5161 !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5164 /* Restore the exact position or use the specialized restore
5166 if (!view->p_restore)
5167 status_restore(view);
5172 status_draw(struct view *view, struct line *line, unsigned int lineno)
5174 struct status *status = line->data;
5175 enum line_type type;
5179 switch (line->type) {
5180 case LINE_STAT_STAGED:
5181 type = LINE_STAT_SECTION;
5182 text = "Changes to be committed:";
5185 case LINE_STAT_UNSTAGED:
5186 type = LINE_STAT_SECTION;
5187 text = "Changed but not updated:";
5190 case LINE_STAT_UNTRACKED:
5191 type = LINE_STAT_SECTION;
5192 text = "Untracked files:";
5195 case LINE_STAT_NONE:
5196 type = LINE_DEFAULT;
5197 text = " (no files)";
5200 case LINE_STAT_HEAD:
5201 type = LINE_STAT_HEAD;
5202 text = status_onbranch;
5209 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5211 buf[0] = status->status;
5212 if (draw_text(view, line->type, buf, TRUE))
5214 type = LINE_DEFAULT;
5215 text = status->new.name;
5218 draw_text(view, type, text, TRUE);
5223 status_load_error(struct view *view, struct view *stage, const char *path)
5225 if (displayed_views() == 2 || display[current_view] != view)
5226 maximize_view(view);
5227 report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5232 status_enter(struct view *view, struct line *line)
5234 struct status *status = line->data;
5235 const char *oldpath = status ? status->old.name : NULL;
5236 /* Diffs for unmerged entries are empty when passing the new
5237 * path, so leave it empty. */
5238 const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5240 enum open_flags split;
5241 struct view *stage = VIEW(REQ_VIEW_STAGE);
5243 if (line->type == LINE_STAT_NONE ||
5244 (!status && line[1].type == LINE_STAT_NONE)) {
5245 report("No file to diff");
5249 switch (line->type) {
5250 case LINE_STAT_STAGED:
5251 if (is_initial_commit()) {
5252 const char *no_head_diff_argv[] = {
5253 "git", "diff", "--no-color", "--patch-with-stat",
5254 "--", "/dev/null", newpath, NULL
5257 if (!prepare_update(stage, no_head_diff_argv, opt_cdup, FORMAT_DASH))
5258 return status_load_error(view, stage, newpath);
5260 const char *index_show_argv[] = {
5261 "git", "diff-index", "--root", "--patch-with-stat",
5262 "-C", "-M", "--cached", "HEAD", "--",
5263 oldpath, newpath, NULL
5266 if (!prepare_update(stage, index_show_argv, opt_cdup, FORMAT_DASH))
5267 return status_load_error(view, stage, newpath);
5271 info = "Staged changes to %s";
5273 info = "Staged changes";
5276 case LINE_STAT_UNSTAGED:
5278 const char *files_show_argv[] = {
5279 "git", "diff-files", "--root", "--patch-with-stat",
5280 "-C", "-M", "--", oldpath, newpath, NULL
5283 if (!prepare_update(stage, files_show_argv, opt_cdup, FORMAT_DASH))
5284 return status_load_error(view, stage, newpath);
5286 info = "Unstaged changes to %s";
5288 info = "Unstaged changes";
5291 case LINE_STAT_UNTRACKED:
5293 report("No file to show");
5297 if (!suffixcmp(status->new.name, -1, "/")) {
5298 report("Cannot display a directory");
5302 if (!prepare_update_file(stage, newpath))
5303 return status_load_error(view, stage, newpath);
5304 info = "Untracked file %s";
5307 case LINE_STAT_HEAD:
5311 die("line type %d not handled in switch", line->type);
5314 split = view_is_displayed(view) ? OPEN_SPLIT : 0;
5315 open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5316 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5318 stage_status = *status;
5320 memset(&stage_status, 0, sizeof(stage_status));
5323 stage_line_type = line->type;
5325 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5332 status_exists(struct status *status, enum line_type type)
5334 struct view *view = VIEW(REQ_VIEW_STATUS);
5335 unsigned long lineno;
5337 for (lineno = 0; lineno < view->lines; lineno++) {
5338 struct line *line = &view->line[lineno];
5339 struct status *pos = line->data;
5341 if (line->type != type)
5343 if (!pos && (!status || !status->status) && line[1].data) {
5344 select_view_line(view, lineno);
5347 if (pos && !strcmp(status->new.name, pos->new.name)) {
5348 select_view_line(view, lineno);
5358 status_update_prepare(struct io *io, enum line_type type)
5360 const char *staged_argv[] = {
5361 "git", "update-index", "-z", "--index-info", NULL
5363 const char *others_argv[] = {
5364 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5368 case LINE_STAT_STAGED:
5369 return run_io(io, staged_argv, opt_cdup, IO_WR);
5371 case LINE_STAT_UNSTAGED:
5372 return run_io(io, others_argv, opt_cdup, IO_WR);
5374 case LINE_STAT_UNTRACKED:
5375 return run_io(io, others_argv, NULL, IO_WR);
5378 die("line type %d not handled in switch", type);
5384 status_update_write(struct io *io, struct status *status, enum line_type type)
5386 char buf[SIZEOF_STR];
5390 case LINE_STAT_STAGED:
5391 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
5394 status->old.name, 0))
5398 case LINE_STAT_UNSTAGED:
5399 case LINE_STAT_UNTRACKED:
5400 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
5405 die("line type %d not handled in switch", type);
5408 return io_write(io, buf, bufsize);
5412 status_update_file(struct status *status, enum line_type type)
5417 if (!status_update_prepare(&io, type))
5420 result = status_update_write(&io, status, type);
5421 return done_io(&io) && result;
5425 status_update_files(struct view *view, struct line *line)
5427 char buf[sizeof(view->ref)];
5430 struct line *pos = view->line + view->lines;
5433 int cursor_y, cursor_x;
5435 if (!status_update_prepare(&io, line->type))
5438 for (pos = line; pos < view->line + view->lines && pos->data; pos++)
5441 string_copy(buf, view->ref);
5442 getsyx(cursor_y, cursor_x);
5443 for (file = 0, done = 5; result && file < files; line++, file++) {
5444 int almost_done = file * 100 / files;
5446 if (almost_done > done) {
5448 string_format(view->ref, "updating file %u of %u (%d%% done)",
5450 update_view_title(view);
5451 setsyx(cursor_y, cursor_x);
5454 result = status_update_write(&io, line->data, line->type);
5456 string_copy(view->ref, buf);
5458 return done_io(&io) && result;
5462 status_update(struct view *view)
5464 struct line *line = &view->line[view->lineno];
5466 assert(view->lines);
5469 /* This should work even for the "On branch" line. */
5470 if (line < view->line + view->lines && !line[1].data) {
5471 report("Nothing to update");
5475 if (!status_update_files(view, line + 1)) {
5476 report("Failed to update file status");
5480 } else if (!status_update_file(line->data, line->type)) {
5481 report("Failed to update file status");
5489 status_revert(struct status *status, enum line_type type, bool has_none)
5491 if (!status || type != LINE_STAT_UNSTAGED) {
5492 if (type == LINE_STAT_STAGED) {
5493 report("Cannot revert changes to staged files");
5494 } else if (type == LINE_STAT_UNTRACKED) {
5495 report("Cannot revert changes to untracked files");
5496 } else if (has_none) {
5497 report("Nothing to revert");
5499 report("Cannot revert changes to multiple files");
5504 char mode[10] = "100644";
5505 const char *reset_argv[] = {
5506 "git", "update-index", "--cacheinfo", mode,
5507 status->old.rev, status->old.name, NULL
5509 const char *checkout_argv[] = {
5510 "git", "checkout", "--", status->old.name, NULL
5513 if (!prompt_yesno("Are you sure you want to overwrite any changes?"))
5515 string_format(mode, "%o", status->old.mode);
5516 return (status->status != 'U' || run_io_fg(reset_argv, opt_cdup)) &&
5517 run_io_fg(checkout_argv, opt_cdup);
5522 status_request(struct view *view, enum request request, struct line *line)
5524 struct status *status = line->data;
5527 case REQ_STATUS_UPDATE:
5528 if (!status_update(view))
5532 case REQ_STATUS_REVERT:
5533 if (!status_revert(status, line->type, status_has_none(view, line)))
5537 case REQ_STATUS_MERGE:
5538 if (!status || status->status != 'U') {
5539 report("Merging only possible for files with unmerged status ('U').");
5542 open_mergetool(status->new.name);
5548 if (status->status == 'D') {
5549 report("File has been deleted.");
5553 open_editor(status->status != '?', status->new.name);
5556 case REQ_VIEW_BLAME:
5558 string_copy(opt_file, status->new.name);
5564 /* After returning the status view has been split to
5565 * show the stage view. No further reloading is
5567 return status_enter(view, line);
5570 /* Simply reload the view. */
5577 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
5583 status_select(struct view *view, struct line *line)
5585 struct status *status = line->data;
5586 char file[SIZEOF_STR] = "all files";
5590 if (status && !string_format(file, "'%s'", status->new.name))
5593 if (!status && line[1].type == LINE_STAT_NONE)
5596 switch (line->type) {
5597 case LINE_STAT_STAGED:
5598 text = "Press %s to unstage %s for commit";
5601 case LINE_STAT_UNSTAGED:
5602 text = "Press %s to stage %s for commit";
5605 case LINE_STAT_UNTRACKED:
5606 text = "Press %s to stage %s for addition";
5609 case LINE_STAT_HEAD:
5610 case LINE_STAT_NONE:
5611 text = "Nothing to update";
5615 die("line type %d not handled in switch", line->type);
5618 if (status && status->status == 'U') {
5619 text = "Press %s to resolve conflict in %s";
5620 key = get_key(REQ_STATUS_MERGE);
5623 key = get_key(REQ_STATUS_UPDATE);
5626 string_format(view->ref, text, key, file);
5630 status_grep(struct view *view, struct line *line)
5632 struct status *status = line->data;
5635 const char buf[2] = { status->status, 0 };
5636 const char *text[] = { status->new.name, buf, NULL };
5638 return grep_text(view, text);
5644 static struct view_ops status_ops = {
5657 stage_diff_write(struct io *io, struct line *line, struct line *end)
5659 while (line < end) {
5660 if (!io_write(io, line->data, strlen(line->data)) ||
5661 !io_write(io, "\n", 1))
5664 if (line->type == LINE_DIFF_CHUNK ||
5665 line->type == LINE_DIFF_HEADER)
5672 static struct line *
5673 stage_diff_find(struct view *view, struct line *line, enum line_type type)
5675 for (; view->line < line; line--)
5676 if (line->type == type)
5683 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
5685 const char *apply_argv[SIZEOF_ARG] = {
5686 "git", "apply", "--whitespace=nowarn", NULL
5688 struct line *diff_hdr;
5692 diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
5697 apply_argv[argc++] = "--cached";
5698 if (revert || stage_line_type == LINE_STAT_STAGED)
5699 apply_argv[argc++] = "-R";
5700 apply_argv[argc++] = "-";
5701 apply_argv[argc++] = NULL;
5702 if (!run_io(&io, apply_argv, opt_cdup, IO_WR))
5705 if (!stage_diff_write(&io, diff_hdr, chunk) ||
5706 !stage_diff_write(&io, chunk, view->line + view->lines))
5710 run_io_bg(update_index_argv);
5712 return chunk ? TRUE : FALSE;
5716 stage_update(struct view *view, struct line *line)
5718 struct line *chunk = NULL;
5720 if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
5721 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5724 if (!stage_apply_chunk(view, chunk, FALSE)) {
5725 report("Failed to apply chunk");
5729 } else if (!stage_status.status) {
5730 view = VIEW(REQ_VIEW_STATUS);
5732 for (line = view->line; line < view->line + view->lines; line++)
5733 if (line->type == stage_line_type)
5736 if (!status_update_files(view, line + 1)) {
5737 report("Failed to update files");
5741 } else if (!status_update_file(&stage_status, stage_line_type)) {
5742 report("Failed to update file");
5750 stage_revert(struct view *view, struct line *line)
5752 struct line *chunk = NULL;
5754 if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
5755 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5758 if (!prompt_yesno("Are you sure you want to revert changes?"))
5761 if (!stage_apply_chunk(view, chunk, TRUE)) {
5762 report("Failed to revert chunk");
5768 return status_revert(stage_status.status ? &stage_status : NULL,
5769 stage_line_type, FALSE);
5775 stage_next(struct view *view, struct line *line)
5779 if (!stage_chunks) {
5780 for (line = view->line; line < view->line + view->lines; line++) {
5781 if (line->type != LINE_DIFF_CHUNK)
5784 if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
5785 report("Allocation failure");
5789 stage_chunk[stage_chunks++] = line - view->line;
5793 for (i = 0; i < stage_chunks; i++) {
5794 if (stage_chunk[i] > view->lineno) {
5795 do_scroll_view(view, stage_chunk[i] - view->lineno);
5796 report("Chunk %d of %d", i + 1, stage_chunks);
5801 report("No next chunk found");
5805 stage_request(struct view *view, enum request request, struct line *line)
5808 case REQ_STATUS_UPDATE:
5809 if (!stage_update(view, line))
5813 case REQ_STATUS_REVERT:
5814 if (!stage_revert(view, line))
5818 case REQ_STAGE_NEXT:
5819 if (stage_line_type == LINE_STAT_UNTRACKED) {
5820 report("File is untracked; press %s to add",
5821 get_key(REQ_STATUS_UPDATE));
5824 stage_next(view, line);
5828 if (!stage_status.new.name[0])
5830 if (stage_status.status == 'D') {
5831 report("File has been deleted.");
5835 open_editor(stage_status.status != '?', stage_status.new.name);
5839 /* Reload everything ... */
5842 case REQ_VIEW_BLAME:
5843 if (stage_status.new.name[0]) {
5844 string_copy(opt_file, stage_status.new.name);
5850 return pager_request(view, request, line);
5856 VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
5857 open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
5859 /* Check whether the staged entry still exists, and close the
5860 * stage view if it doesn't. */
5861 if (!status_exists(&stage_status, stage_line_type)) {
5862 status_restore(VIEW(REQ_VIEW_STATUS));
5863 return REQ_VIEW_CLOSE;
5866 if (stage_line_type == LINE_STAT_UNTRACKED) {
5867 if (!suffixcmp(stage_status.new.name, -1, "/")) {
5868 report("Cannot display a directory");
5872 if (!prepare_update_file(view, stage_status.new.name)) {
5873 report("Failed to open file: %s", strerror(errno));
5877 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
5882 static struct view_ops stage_ops = {
5899 char id[SIZEOF_REV]; /* SHA1 ID. */
5900 char title[128]; /* First line of the commit message. */
5901 const char *author; /* Author of the commit. */
5902 time_t time; /* Date from the author ident. */
5903 struct ref **refs; /* Repository references. */
5904 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
5905 size_t graph_size; /* The width of the graph array. */
5906 bool has_parents; /* Rewritten --parents seen. */
5909 /* Size of rev graph with no "padding" columns */
5910 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
5913 struct rev_graph *prev, *next, *parents;
5914 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
5916 struct commit *commit;
5918 unsigned int boundary:1;
5921 /* Parents of the commit being visualized. */
5922 static struct rev_graph graph_parents[4];
5924 /* The current stack of revisions on the graph. */
5925 static struct rev_graph graph_stacks[4] = {
5926 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
5927 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
5928 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
5929 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
5933 graph_parent_is_merge(struct rev_graph *graph)
5935 return graph->parents->size > 1;
5939 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
5941 struct commit *commit = graph->commit;
5943 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
5944 commit->graph[commit->graph_size++] = symbol;
5948 clear_rev_graph(struct rev_graph *graph)
5950 graph->boundary = 0;
5951 graph->size = graph->pos = 0;
5952 graph->commit = NULL;
5953 memset(graph->parents, 0, sizeof(*graph->parents));
5957 done_rev_graph(struct rev_graph *graph)
5959 if (graph_parent_is_merge(graph) &&
5960 graph->pos < graph->size - 1 &&
5961 graph->next->size == graph->size + graph->parents->size - 1) {
5962 size_t i = graph->pos + graph->parents->size - 1;
5964 graph->commit->graph_size = i * 2;
5965 while (i < graph->next->size - 1) {
5966 append_to_rev_graph(graph, ' ');
5967 append_to_rev_graph(graph, '\\');
5972 clear_rev_graph(graph);
5976 push_rev_graph(struct rev_graph *graph, const char *parent)
5980 /* "Collapse" duplicate parents lines.
5982 * FIXME: This needs to also update update the drawn graph but
5983 * for now it just serves as a method for pruning graph lines. */
5984 for (i = 0; i < graph->size; i++)
5985 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
5988 if (graph->size < SIZEOF_REVITEMS) {
5989 string_copy_rev(graph->rev[graph->size++], parent);
5994 get_rev_graph_symbol(struct rev_graph *graph)
5998 if (graph->boundary)
5999 symbol = REVGRAPH_BOUND;
6000 else if (graph->parents->size == 0)
6001 symbol = REVGRAPH_INIT;
6002 else if (graph_parent_is_merge(graph))
6003 symbol = REVGRAPH_MERGE;
6004 else if (graph->pos >= graph->size)
6005 symbol = REVGRAPH_BRANCH;
6007 symbol = REVGRAPH_COMMIT;
6013 draw_rev_graph(struct rev_graph *graph)
6016 chtype separator, line;
6018 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
6019 static struct rev_filler fillers[] = {
6025 chtype symbol = get_rev_graph_symbol(graph);
6026 struct rev_filler *filler;
6029 if (opt_line_graphics)
6030 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
6032 filler = &fillers[DEFAULT];
6034 for (i = 0; i < graph->pos; i++) {
6035 append_to_rev_graph(graph, filler->line);
6036 if (graph_parent_is_merge(graph->prev) &&
6037 graph->prev->pos == i)
6038 filler = &fillers[RSHARP];
6040 append_to_rev_graph(graph, filler->separator);
6043 /* Place the symbol for this revision. */
6044 append_to_rev_graph(graph, symbol);
6046 if (graph->prev->size > graph->size)
6047 filler = &fillers[RDIAG];
6049 filler = &fillers[DEFAULT];
6053 for (; i < graph->size; i++) {
6054 append_to_rev_graph(graph, filler->separator);
6055 append_to_rev_graph(graph, filler->line);
6056 if (graph_parent_is_merge(graph->prev) &&
6057 i < graph->prev->pos + graph->parents->size)
6058 filler = &fillers[RSHARP];
6059 if (graph->prev->size > graph->size)
6060 filler = &fillers[LDIAG];
6063 if (graph->prev->size > graph->size) {
6064 append_to_rev_graph(graph, filler->separator);
6065 if (filler->line != ' ')
6066 append_to_rev_graph(graph, filler->line);
6070 /* Prepare the next rev graph */
6072 prepare_rev_graph(struct rev_graph *graph)
6076 /* First, traverse all lines of revisions up to the active one. */
6077 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6078 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6081 push_rev_graph(graph->next, graph->rev[graph->pos]);
6084 /* Interleave the new revision parent(s). */
6085 for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6086 push_rev_graph(graph->next, graph->parents->rev[i]);
6088 /* Lastly, put any remaining revisions. */
6089 for (i = graph->pos + 1; i < graph->size; i++)
6090 push_rev_graph(graph->next, graph->rev[i]);
6094 update_rev_graph(struct view *view, struct rev_graph *graph)
6096 /* If this is the finalizing update ... */
6098 prepare_rev_graph(graph);
6100 /* Graph visualization needs a one rev look-ahead,
6101 * so the first update doesn't visualize anything. */
6102 if (!graph->prev->commit)
6105 if (view->lines > 2)
6106 view->line[view->lines - 3].dirty = 1;
6107 if (view->lines > 1)
6108 view->line[view->lines - 2].dirty = 1;
6109 draw_rev_graph(graph->prev);
6110 done_rev_graph(graph->prev->prev);
6118 static const char *main_argv[SIZEOF_ARG] = {
6119 "git", "log", "--no-color", "--pretty=raw", "--parents",
6120 "--topo-order", "%(head)", NULL
6124 main_draw(struct view *view, struct line *line, unsigned int lineno)
6126 struct commit *commit = line->data;
6128 if (!commit->author)
6131 if (opt_date && draw_date(view, &commit->time))
6134 if (opt_author && draw_author(view, commit->author))
6137 if (opt_rev_graph && commit->graph_size &&
6138 draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6141 if (opt_show_refs && commit->refs) {
6145 struct ref *ref = commit->refs[i];
6146 enum line_type type;
6149 type = LINE_MAIN_HEAD;
6151 type = LINE_MAIN_LOCAL_TAG;
6153 type = LINE_MAIN_TAG;
6154 else if (ref->tracked)
6155 type = LINE_MAIN_TRACKED;
6156 else if (ref->remote)
6157 type = LINE_MAIN_REMOTE;
6159 type = LINE_MAIN_REF;
6161 if (draw_text(view, type, "[", TRUE) ||
6162 draw_text(view, type, ref->name, TRUE) ||
6163 draw_text(view, type, "]", TRUE))
6166 if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6168 } while (commit->refs[i++]->next);
6171 draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6175 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6177 main_read(struct view *view, char *line)
6179 static struct rev_graph *graph = graph_stacks;
6180 enum line_type type;
6181 struct commit *commit;
6186 if (!view->lines && !view->parent)
6187 die("No revisions match the given arguments.");
6188 if (view->lines > 0) {
6189 commit = view->line[view->lines - 1].data;
6190 view->line[view->lines - 1].dirty = 1;
6191 if (!commit->author) {
6194 graph->commit = NULL;
6197 update_rev_graph(view, graph);
6199 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6200 clear_rev_graph(&graph_stacks[i]);
6204 type = get_line_type(line);
6205 if (type == LINE_COMMIT) {
6206 commit = calloc(1, sizeof(struct commit));
6210 line += STRING_SIZE("commit ");
6212 graph->boundary = 1;
6216 string_copy_rev(commit->id, line);
6217 commit->refs = get_refs(commit->id);
6218 graph->commit = commit;
6219 add_line_data(view, commit, LINE_MAIN_COMMIT);
6221 while ((line = strchr(line, ' '))) {
6223 push_rev_graph(graph->parents, line);
6224 commit->has_parents = TRUE;
6231 commit = view->line[view->lines - 1].data;
6235 if (commit->has_parents)
6237 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6241 parse_author_line(line + STRING_SIZE("author "),
6242 &commit->author, &commit->time);
6243 update_rev_graph(view, graph);
6244 graph = graph->next;
6248 /* Fill in the commit title if it has not already been set. */
6249 if (commit->title[0])
6252 /* Require titles to start with a non-space character at the
6253 * offset used by git log. */
6254 if (strncmp(line, " ", 4))
6257 /* Well, if the title starts with a whitespace character,
6258 * try to be forgiving. Otherwise we end up with no title. */
6259 while (isspace(*line))
6263 /* FIXME: More graceful handling of titles; append "..." to
6264 * shortened titles, etc. */
6266 string_expand(commit->title, sizeof(commit->title), line, 1);
6267 view->line[view->lines - 1].dirty = 1;
6274 main_request(struct view *view, enum request request, struct line *line)
6276 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
6280 open_view(view, REQ_VIEW_DIFF, flags);
6284 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6294 grep_refs(struct ref **refs, regex_t *regex)
6299 if (!opt_show_refs || !refs)
6302 if (regexec(regex, refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6304 } while (refs[i++]->next);
6310 main_grep(struct view *view, struct line *line)
6312 struct commit *commit = line->data;
6313 const char *text[] = {
6315 opt_author ? commit->author : "",
6316 opt_date ? mkdate(&commit->time) : "",
6320 return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6324 main_select(struct view *view, struct line *line)
6326 struct commit *commit = line->data;
6328 string_copy_rev(view->ref, commit->id);
6329 string_copy_rev(ref_commit, view->ref);
6332 static struct view_ops main_ops = {
6345 * Unicode / UTF-8 handling
6347 * NOTE: Much of the following code for dealing with Unicode is derived from
6348 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
6349 * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
6353 unicode_width(unsigned long c)
6356 (c <= 0x115f /* Hangul Jamo */
6359 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
6361 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
6362 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
6363 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
6364 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
6365 || (c >= 0xffe0 && c <= 0xffe6)
6366 || (c >= 0x20000 && c <= 0x2fffd)
6367 || (c >= 0x30000 && c <= 0x3fffd)))
6371 return opt_tab_size;
6376 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
6377 * Illegal bytes are set one. */
6378 static const unsigned char utf8_bytes[256] = {
6379 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
6380 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
6381 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
6382 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
6383 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
6384 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
6385 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,
6386 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,
6389 /* Decode UTF-8 multi-byte representation into a Unicode character. */
6390 static inline unsigned long
6391 utf8_to_unicode(const char *string, size_t length)
6393 unsigned long unicode;
6397 unicode = string[0];
6400 unicode = (string[0] & 0x1f) << 6;
6401 unicode += (string[1] & 0x3f);
6404 unicode = (string[0] & 0x0f) << 12;
6405 unicode += ((string[1] & 0x3f) << 6);
6406 unicode += (string[2] & 0x3f);
6409 unicode = (string[0] & 0x0f) << 18;
6410 unicode += ((string[1] & 0x3f) << 12);
6411 unicode += ((string[2] & 0x3f) << 6);
6412 unicode += (string[3] & 0x3f);
6415 unicode = (string[0] & 0x0f) << 24;
6416 unicode += ((string[1] & 0x3f) << 18);
6417 unicode += ((string[2] & 0x3f) << 12);
6418 unicode += ((string[3] & 0x3f) << 6);
6419 unicode += (string[4] & 0x3f);
6422 unicode = (string[0] & 0x01) << 30;
6423 unicode += ((string[1] & 0x3f) << 24);
6424 unicode += ((string[2] & 0x3f) << 18);
6425 unicode += ((string[3] & 0x3f) << 12);
6426 unicode += ((string[4] & 0x3f) << 6);
6427 unicode += (string[5] & 0x3f);
6430 die("Invalid Unicode length");
6433 /* Invalid characters could return the special 0xfffd value but NUL
6434 * should be just as good. */
6435 return unicode > 0xffff ? 0 : unicode;
6438 /* Calculates how much of string can be shown within the given maximum width
6439 * and sets trimmed parameter to non-zero value if all of string could not be
6440 * shown. If the reserve flag is TRUE, it will reserve at least one
6441 * trailing character, which can be useful when drawing a delimiter.
6443 * Returns the number of bytes to output from string to satisfy max_width. */
6445 utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve)
6447 const char *string = *start;
6448 const char *end = strchr(string, '\0');
6449 unsigned char last_bytes = 0;
6450 size_t last_ucwidth = 0;
6455 while (string < end) {
6456 int c = *(unsigned char *) string;
6457 unsigned char bytes = utf8_bytes[c];
6459 unsigned long unicode;
6461 if (string + bytes > end)
6464 /* Change representation to figure out whether
6465 * it is a single- or double-width character. */
6467 unicode = utf8_to_unicode(string, bytes);
6468 /* FIXME: Graceful handling of invalid Unicode character. */
6472 ucwidth = unicode_width(unicode);
6474 skip -= ucwidth <= skip ? ucwidth : skip;
6478 if (*width > max_width) {
6481 if (reserve && *width == max_width) {
6482 string -= last_bytes;
6483 *width -= last_ucwidth;
6489 last_bytes = ucwidth ? bytes : 0;
6490 last_ucwidth = ucwidth;
6493 return string - *start;
6501 /* Whether or not the curses interface has been initialized. */
6502 static bool cursed = FALSE;
6504 /* Terminal hacks and workarounds. */
6505 static bool use_scroll_redrawwin;
6506 static bool use_scroll_status_wclear;
6508 /* The status window is used for polling keystrokes. */
6509 static WINDOW *status_win;
6511 /* Reading from the prompt? */
6512 static bool input_mode = FALSE;
6514 static bool status_empty = FALSE;
6516 /* Update status and title window. */
6518 report(const char *msg, ...)
6520 struct view *view = display[current_view];
6526 char buf[SIZEOF_STR];
6529 va_start(args, msg);
6530 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6531 buf[sizeof(buf) - 1] = 0;
6532 buf[sizeof(buf) - 2] = '.';
6533 buf[sizeof(buf) - 3] = '.';
6534 buf[sizeof(buf) - 4] = '.';
6540 if (!status_empty || *msg) {
6543 va_start(args, msg);
6545 wmove(status_win, 0, 0);
6546 if (view->has_scrolled && use_scroll_status_wclear)
6549 vwprintw(status_win, msg, args);
6550 status_empty = FALSE;
6552 status_empty = TRUE;
6554 wclrtoeol(status_win);
6555 wnoutrefresh(status_win);
6560 update_view_title(view);
6563 /* Controls when nodelay should be in effect when polling user input. */
6565 set_nonblocking_input(bool loading)
6567 static unsigned int loading_views;
6569 if ((loading == FALSE && loading_views-- == 1) ||
6570 (loading == TRUE && loading_views++ == 0))
6571 nodelay(status_win, loading);
6580 /* Initialize the curses library */
6581 if (isatty(STDIN_FILENO)) {
6582 cursed = !!initscr();
6585 /* Leave stdin and stdout alone when acting as a pager. */
6586 opt_tty = fopen("/dev/tty", "r+");
6588 die("Failed to open /dev/tty");
6589 cursed = !!newterm(NULL, opt_tty, opt_tty);
6593 die("Failed to initialize curses");
6595 nonl(); /* Disable conversion and detect newlines from input. */
6596 cbreak(); /* Take input chars one at a time, no wait for \n */
6597 noecho(); /* Don't echo input */
6598 leaveok(stdscr, FALSE);
6603 getmaxyx(stdscr, y, x);
6604 status_win = newwin(1, 0, y - 1, 0);
6606 die("Failed to create status window");
6608 /* Enable keyboard mapping */
6609 keypad(status_win, TRUE);
6610 wbkgdset(status_win, get_line_attr(LINE_STATUS));
6612 TABSIZE = opt_tab_size;
6613 if (opt_line_graphics) {
6614 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
6617 term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
6618 if (term && !strcmp(term, "gnome-terminal")) {
6619 /* In the gnome-terminal-emulator, the message from
6620 * scrolling up one line when impossible followed by
6621 * scrolling down one line causes corruption of the
6622 * status line. This is fixed by calling wclear. */
6623 use_scroll_status_wclear = TRUE;
6624 use_scroll_redrawwin = FALSE;
6626 } else if (term && !strcmp(term, "xrvt-xpm")) {
6627 /* No problems with full optimizations in xrvt-(unicode)
6629 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
6632 /* When scrolling in (u)xterm the last line in the
6633 * scrolling direction will update slowly. */
6634 use_scroll_redrawwin = TRUE;
6635 use_scroll_status_wclear = FALSE;
6640 get_input(int prompt_position)
6643 int i, key, cursor_y, cursor_x;
6645 if (prompt_position)
6649 foreach_view (view, i) {
6651 if (view_is_displayed(view) && view->has_scrolled &&
6652 use_scroll_redrawwin)
6653 redrawwin(view->win);
6654 view->has_scrolled = FALSE;
6657 /* Update the cursor position. */
6658 if (prompt_position) {
6659 getbegyx(status_win, cursor_y, cursor_x);
6660 cursor_x = prompt_position;
6662 view = display[current_view];
6663 getbegyx(view->win, cursor_y, cursor_x);
6664 cursor_x = view->width - 1;
6665 cursor_y += view->lineno - view->offset;
6667 setsyx(cursor_y, cursor_x);
6669 /* Refresh, accept single keystroke of input */
6671 key = wgetch(status_win);
6673 /* wgetch() with nodelay() enabled returns ERR when
6674 * there's no input. */
6677 } else if (key == KEY_RESIZE) {
6680 getmaxyx(stdscr, height, width);
6682 wresize(status_win, 1, width);
6683 mvwin(status_win, height - 1, 0);
6684 wnoutrefresh(status_win);
6686 redraw_display(TRUE);
6696 prompt_input(const char *prompt, input_handler handler, void *data)
6698 enum input_status status = INPUT_OK;
6699 static char buf[SIZEOF_STR];
6704 while (status == INPUT_OK || status == INPUT_SKIP) {
6707 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
6708 wclrtoeol(status_win);
6710 key = get_input(pos + 1);
6715 status = pos ? INPUT_STOP : INPUT_CANCEL;
6722 status = INPUT_CANCEL;
6726 status = INPUT_CANCEL;
6730 if (pos >= sizeof(buf)) {
6731 report("Input string too long");
6735 status = handler(data, buf, key);
6736 if (status == INPUT_OK)
6737 buf[pos++] = (char) key;
6741 /* Clear the status window */
6742 status_empty = FALSE;
6745 if (status == INPUT_CANCEL)
6753 static enum input_status
6754 prompt_yesno_handler(void *data, char *buf, int c)
6756 if (c == 'y' || c == 'Y')
6758 if (c == 'n' || c == 'N')
6759 return INPUT_CANCEL;
6764 prompt_yesno(const char *prompt)
6766 char prompt2[SIZEOF_STR];
6768 if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
6771 return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
6774 static enum input_status
6775 read_prompt_handler(void *data, char *buf, int c)
6777 return isprint(c) ? INPUT_OK : INPUT_SKIP;
6781 read_prompt(const char *prompt)
6783 return prompt_input(prompt, read_prompt_handler, NULL);
6787 * Repository properties
6790 static struct ref *refs = NULL;
6791 static size_t refs_size = 0;
6793 /* Id <-> ref store */
6794 static struct ref ***id_refs = NULL;
6795 static size_t id_refs_size = 0;
6797 DEFINE_ALLOCATOR(realloc_refs, struct ref, 256)
6798 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
6799 DEFINE_ALLOCATOR(realloc_refs_lists, struct ref **, 8)
6802 compare_refs(const void *ref1_, const void *ref2_)
6804 const struct ref *ref1 = *(const struct ref **)ref1_;
6805 const struct ref *ref2 = *(const struct ref **)ref2_;
6807 if (ref1->tag != ref2->tag)
6808 return ref2->tag - ref1->tag;
6809 if (ref1->ltag != ref2->ltag)
6810 return ref2->ltag - ref2->ltag;
6811 if (ref1->head != ref2->head)
6812 return ref2->head - ref1->head;
6813 if (ref1->tracked != ref2->tracked)
6814 return ref2->tracked - ref1->tracked;
6815 if (ref1->remote != ref2->remote)
6816 return ref2->remote - ref1->remote;
6817 return strcmp(ref1->name, ref2->name);
6821 foreach_ref(bool (*visitor)(void *data, struct ref *ref), void *data)
6825 for (i = 0; i < refs_size; i++)
6826 if (!visitor(data, &refs[i]))
6830 static struct ref **
6831 get_refs(const char *id)
6833 struct ref **ref_list = NULL;
6834 size_t ref_list_size = 0;
6837 for (i = 0; i < id_refs_size; i++)
6838 if (!strcmp(id, id_refs[i][0]->id))
6841 if (!realloc_refs_lists(&id_refs, id_refs_size, 1))
6844 for (i = 0; i < refs_size; i++) {
6845 if (strcmp(id, refs[i].id))
6848 if (!realloc_refs_list(&ref_list, ref_list_size, 1))
6851 ref_list[ref_list_size] = &refs[i];
6852 /* XXX: The properties of the commit chains ensures that we can
6853 * safely modify the shared ref. The repo references will
6854 * always be similar for the same id. */
6855 ref_list[ref_list_size]->next = 1;
6860 qsort(ref_list, ref_list_size, sizeof(*ref_list), compare_refs);
6861 ref_list[ref_list_size - 1]->next = 0;
6862 id_refs[id_refs_size++] = ref_list;
6869 read_ref(char *id, size_t idlen, char *name, size_t namelen)
6874 bool remote = FALSE;
6875 bool tracked = FALSE;
6876 bool check_replace = FALSE;
6879 if (!prefixcmp(name, "refs/tags/")) {
6880 if (!suffixcmp(name, namelen, "^{}")) {
6883 if (refs_size > 0 && refs[refs_size - 1].ltag == TRUE)
6884 check_replace = TRUE;
6890 namelen -= STRING_SIZE("refs/tags/");
6891 name += STRING_SIZE("refs/tags/");
6893 } else if (!prefixcmp(name, "refs/remotes/")) {
6895 namelen -= STRING_SIZE("refs/remotes/");
6896 name += STRING_SIZE("refs/remotes/");
6897 tracked = !strcmp(opt_remote, name);
6899 } else if (!prefixcmp(name, "refs/heads/")) {
6900 namelen -= STRING_SIZE("refs/heads/");
6901 name += STRING_SIZE("refs/heads/");
6902 head = !strncmp(opt_head, name, namelen);
6904 } else if (!strcmp(name, "HEAD")) {
6905 string_ncopy(opt_head_rev, id, idlen);
6909 if (check_replace && !strcmp(name, refs[refs_size - 1].name)) {
6910 /* it's an annotated tag, replace the previous SHA1 with the
6911 * resolved commit id; relies on the fact git-ls-remote lists
6912 * the commit id of an annotated tag right before the commit id
6914 refs[refs_size - 1].ltag = ltag;
6915 string_copy_rev(refs[refs_size - 1].id, id);
6920 if (!realloc_refs(&refs, refs_size, 1))
6923 ref = &refs[refs_size++];
6924 ref->name = malloc(namelen + 1);
6928 strncpy(ref->name, name, namelen);
6929 ref->name[namelen] = 0;
6933 ref->remote = remote;
6934 ref->tracked = tracked;
6935 string_copy_rev(ref->id, id);
6943 const char *head_argv[] = {
6944 "git", "symbolic-ref", "HEAD", NULL
6946 static const char *ls_remote_argv[SIZEOF_ARG] = {
6947 "git", "ls-remote", opt_git_dir, NULL
6949 static bool init = FALSE;
6952 argv_from_env(ls_remote_argv, "TIG_LS_REMOTE");
6959 if (run_io_buf(head_argv, opt_head, sizeof(opt_head)) &&
6960 !prefixcmp(opt_head, "refs/heads/")) {
6961 char *offset = opt_head + STRING_SIZE("refs/heads/");
6963 memmove(opt_head, offset, strlen(offset) + 1);
6966 while (refs_size > 0)
6967 free(refs[--refs_size].name);
6968 while (id_refs_size > 0)
6969 free(id_refs[--id_refs_size]);
6971 return run_io_load(ls_remote_argv, "\t", read_ref);
6975 set_remote_branch(const char *name, const char *value, size_t valuelen)
6977 if (!strcmp(name, ".remote")) {
6978 string_ncopy(opt_remote, value, valuelen);
6980 } else if (*opt_remote && !strcmp(name, ".merge")) {
6981 size_t from = strlen(opt_remote);
6983 if (!prefixcmp(value, "refs/heads/"))
6984 value += STRING_SIZE("refs/heads/");
6986 if (!string_format_from(opt_remote, &from, "/%s", value))
6992 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
6994 const char *argv[SIZEOF_ARG] = { name, "=" };
6995 int argc = 1 + (cmd == option_set_command);
6998 if (!argv_from_string(argv, &argc, value))
6999 config_msg = "Too many option arguments";
7001 error = cmd(argc, argv);
7004 warn("Option 'tig.%s': %s", name, config_msg);
7008 set_environment_variable(const char *name, const char *value)
7010 size_t len = strlen(name) + 1 + strlen(value) + 1;
7011 char *env = malloc(len);
7014 string_nformat(env, len, NULL, "%s=%s", name, value) &&
7022 set_work_tree(const char *value)
7024 char cwd[SIZEOF_STR];
7026 if (!getcwd(cwd, sizeof(cwd)))
7027 die("Failed to get cwd path: %s", strerror(errno));
7028 if (chdir(opt_git_dir) < 0)
7029 die("Failed to chdir(%s): %s", strerror(errno));
7030 if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7031 die("Failed to get git path: %s", strerror(errno));
7033 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7034 if (chdir(value) < 0)
7035 die("Failed to chdir(%s): %s", value, strerror(errno));
7036 if (!getcwd(cwd, sizeof(cwd)))
7037 die("Failed to get cwd path: %s", strerror(errno));
7038 if (!set_environment_variable("GIT_WORK_TREE", cwd))
7039 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7040 if (!set_environment_variable("GIT_DIR", opt_git_dir))
7041 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7042 opt_is_inside_work_tree = TRUE;
7046 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
7048 if (!strcmp(name, "i18n.commitencoding"))
7049 string_ncopy(opt_encoding, value, valuelen);
7051 else if (!strcmp(name, "core.editor"))
7052 string_ncopy(opt_editor, value, valuelen);
7054 else if (!strcmp(name, "core.worktree"))
7055 set_work_tree(value);
7057 else if (!prefixcmp(name, "tig.color."))
7058 set_repo_config_option(name + 10, value, option_color_command);
7060 else if (!prefixcmp(name, "tig.bind."))
7061 set_repo_config_option(name + 9, value, option_bind_command);
7063 else if (!prefixcmp(name, "tig."))
7064 set_repo_config_option(name + 4, value, option_set_command);
7066 else if (*opt_head && !prefixcmp(name, "branch.") &&
7067 !strncmp(name + 7, opt_head, strlen(opt_head)))
7068 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7074 load_git_config(void)
7076 const char *config_list_argv[] = { "git", GIT_CONFIG, "--list", NULL };
7078 return run_io_load(config_list_argv, "=", read_repo_config_option);
7082 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
7084 if (!opt_git_dir[0]) {
7085 string_ncopy(opt_git_dir, name, namelen);
7087 } else if (opt_is_inside_work_tree == -1) {
7088 /* This can be 3 different values depending on the
7089 * version of git being used. If git-rev-parse does not
7090 * understand --is-inside-work-tree it will simply echo
7091 * the option else either "true" or "false" is printed.
7092 * Default to true for the unknown case. */
7093 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7095 } else if (*name == '.') {
7096 string_ncopy(opt_cdup, name, namelen);
7099 string_ncopy(opt_prefix, name, namelen);
7106 load_repo_info(void)
7108 const char *rev_parse_argv[] = {
7109 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7110 "--show-cdup", "--show-prefix", NULL
7113 return run_io_load(rev_parse_argv, "=", read_repo_info);
7121 static const char usage[] =
7122 "tig " TIG_VERSION " (" __DATE__ ")\n"
7124 "Usage: tig [options] [revs] [--] [paths]\n"
7125 " or: tig show [options] [revs] [--] [paths]\n"
7126 " or: tig blame [rev] path\n"
7128 " or: tig < [git command output]\n"
7131 " -v, --version Show version and exit\n"
7132 " -h, --help Show help message and exit";
7134 static void __NORETURN
7137 /* XXX: Restore tty modes and let the OS cleanup the rest! */
7143 static void __NORETURN
7144 die(const char *err, ...)
7150 va_start(args, err);
7151 fputs("tig: ", stderr);
7152 vfprintf(stderr, err, args);
7153 fputs("\n", stderr);
7160 warn(const char *msg, ...)
7164 va_start(args, msg);
7165 fputs("tig warning: ", stderr);
7166 vfprintf(stderr, msg, args);
7167 fputs("\n", stderr);
7172 parse_options(int argc, const char *argv[])
7174 enum request request = REQ_VIEW_MAIN;
7175 const char *subcommand;
7176 bool seen_dashdash = FALSE;
7177 /* XXX: This is vulnerable to the user overriding options
7178 * required for the main view parser. */
7179 const char *custom_argv[SIZEOF_ARG] = {
7180 "git", "log", "--no-color", "--pretty=raw", "--parents",
7181 "--topo-order", NULL
7185 if (!isatty(STDIN_FILENO)) {
7186 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
7187 return REQ_VIEW_PAGER;
7193 subcommand = argv[1];
7194 if (!strcmp(subcommand, "status")) {
7196 warn("ignoring arguments after `%s'", subcommand);
7197 return REQ_VIEW_STATUS;
7199 } else if (!strcmp(subcommand, "blame")) {
7200 if (argc <= 2 || argc > 4)
7201 die("invalid number of options to blame\n\n%s", usage);
7205 string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7209 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7210 return REQ_VIEW_BLAME;
7212 } else if (!strcmp(subcommand, "show")) {
7213 request = REQ_VIEW_DIFF;
7220 custom_argv[1] = subcommand;
7224 for (i = 1 + !!subcommand; i < argc; i++) {
7225 const char *opt = argv[i];
7227 if (seen_dashdash || !strcmp(opt, "--")) {
7228 seen_dashdash = TRUE;
7230 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7231 printf("tig version %s\n", TIG_VERSION);
7234 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7235 printf("%s\n", usage);
7239 custom_argv[j++] = opt;
7240 if (j >= ARRAY_SIZE(custom_argv))
7241 die("command too long");
7244 if (!prepare_update(VIEW(request), custom_argv, NULL, FORMAT_NONE))
7245 die("Failed to format arguments");
7251 main(int argc, const char *argv[])
7253 enum request request = parse_options(argc, argv);
7257 signal(SIGINT, quit);
7258 signal(SIGPIPE, SIG_IGN);
7260 if (setlocale(LC_ALL, "")) {
7261 char *codeset = nl_langinfo(CODESET);
7263 string_ncopy(opt_codeset, codeset, strlen(codeset));
7266 if (load_repo_info() == ERR)
7267 die("Failed to load repo info.");
7269 if (load_options() == ERR)
7270 die("Failed to load user config.");
7272 if (load_git_config() == ERR)
7273 die("Failed to load repo config.");
7275 /* Require a git repository unless when running in pager mode. */
7276 if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7277 die("Not a git repository");
7279 if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
7282 if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
7283 opt_iconv = iconv_open(opt_codeset, opt_encoding);
7284 if (opt_iconv == ICONV_NONE)
7285 die("Failed to initialize character set conversion");
7288 if (load_refs() == ERR)
7289 die("Failed to load refs.");
7291 foreach_view (view, i)
7292 argv_from_env(view->ops->argv, view->cmd_env);
7296 if (request != REQ_NONE)
7297 open_view(NULL, request, OPEN_PREPARED);
7298 request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
7300 while (view_driver(display[current_view], request)) {
7301 int key = get_input(0);
7303 view = display[current_view];
7304 request = get_keybinding(view->keymap, key);
7306 /* Some low-level request handling. This keeps access to
7307 * status_win restricted. */
7311 char *cmd = read_prompt(":");
7313 if (cmd && isdigit(*cmd)) {
7314 int lineno = view->lineno + 1;
7316 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7317 select_view_line(view, lineno - 1);
7320 report("Unable to parse '%s' as a line number", cmd);
7324 struct view *next = VIEW(REQ_VIEW_PAGER);
7325 const char *argv[SIZEOF_ARG] = { "git" };
7328 /* When running random commands, initially show the
7329 * command in the title. However, it maybe later be
7330 * overwritten if a commit line is selected. */
7331 string_ncopy(next->ref, cmd, strlen(cmd));
7333 if (!argv_from_string(argv, &argc, cmd)) {
7334 report("Too many arguments");
7335 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
7336 report("Failed to format command");
7338 open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7346 case REQ_SEARCH_BACK:
7348 const char *prompt = request == REQ_SEARCH ? "/" : "?";
7349 char *search = read_prompt(prompt);
7352 string_ncopy(opt_search, search, strlen(search));
7353 else if (*opt_search)
7354 request = request == REQ_SEARCH ?