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
112 #define SCROLL_INTERVAL 1
116 #define SCALE_SPLIT_VIEW(height) ((height) * 2 / 3)
118 #define NULL_ID "0000000000000000000000000000000000000000"
121 #define GIT_CONFIG "config"
124 /* Some ASCII-shorthands fitted into the ncurses namespace. */
126 #define KEY_RETURN '\r'
131 char *name; /* Ref name; tag or head names are shortened. */
132 char id[SIZEOF_REV]; /* Commit SHA1 ID */
133 unsigned int head:1; /* Is it the current HEAD? */
134 unsigned int tag:1; /* Is it a tag? */
135 unsigned int ltag:1; /* If so, is the tag local? */
136 unsigned int remote:1; /* Is it a remote ref? */
137 unsigned int tracked:1; /* Is it the remote for the current HEAD? */
138 unsigned int next:1; /* For ref lists: are there more refs? */
141 static struct ref **get_refs(const char *id);
144 FORMAT_ALL, /* Perform replacement in all arguments. */
145 FORMAT_DASH, /* Perform replacement up until "--". */
146 FORMAT_NONE /* No replacement should be performed. */
149 static bool format_argv(const char *dst[], const char *src[], enum format_flags flags);
158 typedef enum input_status (*input_handler)(void *data, char *buf, int c);
160 static char *prompt_input(const char *prompt, input_handler handler, void *data);
161 static bool prompt_yesno(const char *prompt);
168 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
170 if (srclen > dstlen - 1)
173 strncpy(dst, src, srclen);
177 /* Shorthands for safely copying into a fixed buffer. */
179 #define string_copy(dst, src) \
180 string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
182 #define string_ncopy(dst, src, srclen) \
183 string_ncopy_do(dst, sizeof(dst), src, srclen)
185 #define string_copy_rev(dst, src) \
186 string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
188 #define string_add(dst, from, src) \
189 string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
192 string_expand_length(const char *line, int tabsize)
196 for (size = pos = 0; line[pos]; pos++) {
197 if (line[pos] == '\t' && tabsize > 0)
198 size += tabsize - (size % tabsize);
206 string_expand(char *dst, size_t dstlen, const char *src, int tabsize)
210 for (size = pos = 0; size < dstlen - 1 && src[pos]; pos++) {
211 if (src[pos] == '\t') {
212 size_t expanded = tabsize - (size % tabsize);
214 if (expanded + size >= dstlen - 1)
215 expanded = dstlen - size - 1;
216 memcpy(dst + size, " ", expanded);
219 dst[size++] = src[pos];
227 chomp_string(char *name)
231 while (isspace(*name))
234 namelen = strlen(name) - 1;
235 while (namelen > 0 && isspace(name[namelen]))
242 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
245 size_t pos = bufpos ? *bufpos : 0;
248 pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
254 return pos >= bufsize ? FALSE : TRUE;
257 #define string_format(buf, fmt, args...) \
258 string_nformat(buf, sizeof(buf), NULL, fmt, args)
260 #define string_format_from(buf, from, fmt, args...) \
261 string_nformat(buf, sizeof(buf), from, fmt, args)
264 string_enum_compare(const char *str1, const char *str2, int len)
268 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
270 /* Diff-Header == DIFF_HEADER */
271 for (i = 0; i < len; i++) {
272 if (toupper(str1[i]) == toupper(str2[i]))
275 if (string_enum_sep(str1[i]) &&
276 string_enum_sep(str2[i]))
279 return str1[i] - str2[i];
291 #define ENUM_MAP(name, value) { name, STRING_SIZE(name), value }
294 map_enum_do(struct enum_map *map, size_t map_size, int *value, const char *name)
296 size_t namelen = strlen(name);
299 for (i = 0; i < map_size; i++)
300 if (namelen == map[i].namelen &&
301 !string_enum_compare(name, map[i].name, namelen)) {
302 *value = map[i].value;
309 #define map_enum(attr, map, name) \
310 map_enum_do(map, ARRAY_SIZE(map), attr, name)
312 #define prefixcmp(str1, str2) \
313 strncmp(str1, str2, STRING_SIZE(str2))
316 suffixcmp(const char *str, int slen, const char *suffix)
318 size_t len = slen >= 0 ? slen : strlen(str);
319 size_t suffixlen = strlen(suffix);
321 return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
326 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
330 while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
331 bool advance = cmd[valuelen] != 0;
334 argv[(*argc)++] = chomp_string(cmd);
335 cmd = chomp_string(cmd + valuelen + advance);
338 if (*argc < SIZEOF_ARG)
340 return *argc < SIZEOF_ARG;
344 argv_from_env(const char **argv, const char *name)
346 char *env = argv ? getenv(name) : NULL;
351 if (env && !argv_from_string(argv, &argc, env))
352 die("Too many arguments in the `%s` environment variable", name);
357 * Executing external commands.
361 IO_FD, /* File descriptor based IO. */
362 IO_BG, /* Execute command in the background. */
363 IO_FG, /* Execute command with same std{in,out,err}. */
364 IO_RD, /* Read only fork+exec IO. */
365 IO_WR, /* Write only fork+exec IO. */
366 IO_AP, /* Append fork+exec output to file. */
370 enum io_type type; /* The requested type of pipe. */
371 const char *dir; /* Directory from which to execute. */
372 pid_t pid; /* Pipe for reading or writing. */
373 int pipe; /* Pipe end for reading or writing. */
374 int error; /* Error status. */
375 const char *argv[SIZEOF_ARG]; /* Shell command arguments. */
376 char *buf; /* Read buffer. */
377 size_t bufalloc; /* Allocated buffer size. */
378 size_t bufsize; /* Buffer content size. */
379 char *bufpos; /* Current buffer position. */
380 unsigned int eof:1; /* Has end of file been reached. */
384 reset_io(struct io *io)
388 io->buf = io->bufpos = NULL;
389 io->bufalloc = io->bufsize = 0;
395 init_io(struct io *io, const char *dir, enum io_type type)
403 init_io_rd(struct io *io, const char *argv[], const char *dir,
404 enum format_flags flags)
406 init_io(io, dir, IO_RD);
407 return format_argv(io->argv, argv, flags);
411 io_open(struct io *io, const char *name)
413 init_io(io, NULL, IO_FD);
414 io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
415 return io->pipe != -1;
419 kill_io(struct io *io)
421 return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
425 done_io(struct io *io)
436 pid_t waiting = waitpid(pid, &status, 0);
441 report("waitpid failed (%s)", strerror(errno));
445 return waiting == pid &&
446 !WIFSIGNALED(status) &&
448 !WEXITSTATUS(status);
455 start_io(struct io *io)
457 int pipefds[2] = { -1, -1 };
459 if (io->type == IO_FD)
462 if ((io->type == IO_RD || io->type == IO_WR) &&
465 else if (io->type == IO_AP)
466 pipefds[1] = io->pipe;
468 if ((io->pid = fork())) {
469 if (pipefds[!(io->type == IO_WR)] != -1)
470 close(pipefds[!(io->type == IO_WR)]);
472 io->pipe = pipefds[!!(io->type == IO_WR)];
477 if (io->type != IO_FG) {
478 int devnull = open("/dev/null", O_RDWR);
479 int readfd = io->type == IO_WR ? pipefds[0] : devnull;
480 int writefd = (io->type == IO_RD || io->type == IO_AP)
481 ? pipefds[1] : devnull;
483 dup2(readfd, STDIN_FILENO);
484 dup2(writefd, STDOUT_FILENO);
485 dup2(devnull, STDERR_FILENO);
488 if (pipefds[0] != -1)
490 if (pipefds[1] != -1)
494 if (io->dir && *io->dir && chdir(io->dir) == -1)
495 die("Failed to change directory: %s", strerror(errno));
497 execvp(io->argv[0], (char *const*) io->argv);
498 die("Failed to execute program: %s", strerror(errno));
501 if (pipefds[!!(io->type == IO_WR)] != -1)
502 close(pipefds[!!(io->type == IO_WR)]);
507 run_io(struct io *io, const char **argv, const char *dir, enum io_type type)
509 init_io(io, dir, type);
510 if (!format_argv(io->argv, argv, FORMAT_NONE))
516 run_io_do(struct io *io)
518 return start_io(io) && done_io(io);
522 run_io_bg(const char **argv)
526 init_io(&io, NULL, IO_BG);
527 if (!format_argv(io.argv, argv, FORMAT_NONE))
529 return run_io_do(&io);
533 run_io_fg(const char **argv, const char *dir)
537 init_io(&io, dir, IO_FG);
538 if (!format_argv(io.argv, argv, FORMAT_NONE))
540 return run_io_do(&io);
544 run_io_append(const char **argv, enum format_flags flags, int fd)
548 init_io(&io, NULL, IO_AP);
550 if (format_argv(io.argv, argv, flags))
551 return run_io_do(&io);
557 run_io_rd(struct io *io, const char **argv, enum format_flags flags)
559 return init_io_rd(io, argv, NULL, flags) && start_io(io);
563 io_eof(struct io *io)
569 io_error(struct io *io)
575 io_strerror(struct io *io)
577 return strerror(io->error);
581 io_can_read(struct io *io)
583 struct timeval tv = { 0, 500 };
587 FD_SET(io->pipe, &fds);
589 return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
593 io_read(struct io *io, void *buf, size_t bufsize)
596 ssize_t readsize = read(io->pipe, buf, bufsize);
598 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
600 else if (readsize == -1)
602 else if (readsize == 0)
609 io_get(struct io *io, int c, bool can_read)
615 io->buf = io->bufpos = malloc(BUFSIZ);
618 io->bufalloc = BUFSIZ;
623 if (io->bufsize > 0) {
624 eol = memchr(io->bufpos, c, io->bufsize);
626 char *line = io->bufpos;
629 io->bufpos = eol + 1;
630 io->bufsize -= io->bufpos - line;
637 io->bufpos[io->bufsize] = 0;
647 if (io->bufsize > 0 && io->bufpos > io->buf)
648 memmove(io->buf, io->bufpos, io->bufsize);
650 io->bufpos = io->buf;
651 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
654 io->bufsize += readsize;
659 io_write(struct io *io, const void *buf, size_t bufsize)
663 while (!io_error(io) && written < bufsize) {
666 size = write(io->pipe, buf + written, bufsize - written);
667 if (size < 0 && (errno == EAGAIN || errno == EINTR))
675 return written == bufsize;
679 io_read_buf(struct io *io, char buf[], size_t bufsize)
683 io->buf = io->bufpos = buf;
684 io->bufalloc = bufsize;
685 error = !io_get(io, '\n', TRUE) && io_error(io);
688 return done_io(io) || error;
692 run_io_buf(const char **argv, char buf[], size_t bufsize)
696 return run_io_rd(&io, argv, FORMAT_NONE) && io_read_buf(&io, buf, bufsize);
700 io_load(struct io *io, const char *separators,
701 int (*read_property)(char *, size_t, char *, size_t))
709 while (state == OK && (name = io_get(io, '\n', TRUE))) {
714 name = chomp_string(name);
715 namelen = strcspn(name, separators);
719 value = chomp_string(name + namelen + 1);
720 valuelen = strlen(value);
727 state = read_property(name, namelen, value, valuelen);
730 if (state != ERR && io_error(io))
738 run_io_load(const char **argv, const char *separators,
739 int (*read_property)(char *, size_t, char *, size_t))
743 return init_io_rd(&io, argv, NULL, FORMAT_NONE)
744 ? io_load(&io, separators, read_property) : ERR;
753 /* XXX: Keep the view request first and in sync with views[]. */ \
754 REQ_GROUP("View switching") \
755 REQ_(VIEW_MAIN, "Show main view"), \
756 REQ_(VIEW_DIFF, "Show diff view"), \
757 REQ_(VIEW_LOG, "Show log view"), \
758 REQ_(VIEW_TREE, "Show tree view"), \
759 REQ_(VIEW_BLOB, "Show blob view"), \
760 REQ_(VIEW_BLAME, "Show blame view"), \
761 REQ_(VIEW_HELP, "Show help page"), \
762 REQ_(VIEW_PAGER, "Show pager view"), \
763 REQ_(VIEW_STATUS, "Show status view"), \
764 REQ_(VIEW_STAGE, "Show stage view"), \
766 REQ_GROUP("View manipulation") \
767 REQ_(ENTER, "Enter current line and scroll"), \
768 REQ_(NEXT, "Move to next"), \
769 REQ_(PREVIOUS, "Move to previous"), \
770 REQ_(PARENT, "Move to parent"), \
771 REQ_(VIEW_NEXT, "Move focus to next view"), \
772 REQ_(REFRESH, "Reload and refresh"), \
773 REQ_(MAXIMIZE, "Maximize the current view"), \
774 REQ_(VIEW_CLOSE, "Close the current view"), \
775 REQ_(QUIT, "Close all views and quit"), \
777 REQ_GROUP("View specific requests") \
778 REQ_(STATUS_UPDATE, "Update file status"), \
779 REQ_(STATUS_REVERT, "Revert file changes"), \
780 REQ_(STATUS_MERGE, "Merge file using external tool"), \
781 REQ_(STAGE_NEXT, "Find next chunk to stage"), \
783 REQ_GROUP("Cursor navigation") \
784 REQ_(MOVE_UP, "Move cursor one line up"), \
785 REQ_(MOVE_DOWN, "Move cursor one line down"), \
786 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
787 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
788 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
789 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
791 REQ_GROUP("Scrolling") \
792 REQ_(SCROLL_LEFT, "Scroll two columns left"), \
793 REQ_(SCROLL_RIGHT, "Scroll two columns right"), \
794 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
795 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
796 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
797 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
799 REQ_GROUP("Searching") \
800 REQ_(SEARCH, "Search the view"), \
801 REQ_(SEARCH_BACK, "Search backwards in the view"), \
802 REQ_(FIND_NEXT, "Find next search match"), \
803 REQ_(FIND_PREV, "Find previous search match"), \
805 REQ_GROUP("Option manipulation") \
806 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
807 REQ_(TOGGLE_DATE, "Toggle date display"), \
808 REQ_(TOGGLE_AUTHOR, "Toggle author display"), \
809 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
810 REQ_(TOGGLE_REFS, "Toggle reference display (tags/branches)"), \
813 REQ_(PROMPT, "Bring up the prompt"), \
814 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
815 REQ_(SHOW_VERSION, "Show version information"), \
816 REQ_(STOP_LOADING, "Stop all loading views"), \
817 REQ_(EDIT, "Open in editor"), \
818 REQ_(NONE, "Do nothing")
821 /* User action requests. */
823 #define REQ_GROUP(help)
824 #define REQ_(req, help) REQ_##req
826 /* Offset all requests to avoid conflicts with ncurses getch values. */
827 REQ_OFFSET = KEY_MAX + 1,
834 struct request_info {
835 enum request request;
841 static struct request_info req_info[] = {
842 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
843 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
850 get_request(const char *name)
852 int namelen = strlen(name);
855 for (i = 0; i < ARRAY_SIZE(req_info); i++)
856 if (req_info[i].namelen == namelen &&
857 !string_enum_compare(req_info[i].name, name, namelen))
858 return req_info[i].request;
868 /* Option and state variables. */
869 static bool opt_date = TRUE;
870 static bool opt_author = TRUE;
871 static bool opt_line_number = FALSE;
872 static bool opt_line_graphics = TRUE;
873 static bool opt_rev_graph = FALSE;
874 static bool opt_show_refs = TRUE;
875 static int opt_num_interval = NUMBER_INTERVAL;
876 static int opt_tab_size = TAB_SIZE;
877 static int opt_author_cols = AUTHOR_COLS-1;
878 static char opt_path[SIZEOF_STR] = "";
879 static char opt_file[SIZEOF_STR] = "";
880 static char opt_ref[SIZEOF_REF] = "";
881 static char opt_head[SIZEOF_REF] = "";
882 static char opt_head_rev[SIZEOF_REV] = "";
883 static char opt_remote[SIZEOF_REF] = "";
884 static char opt_encoding[20] = "UTF-8";
885 static bool opt_utf8 = TRUE;
886 static char opt_codeset[20] = "UTF-8";
887 static iconv_t opt_iconv = ICONV_NONE;
888 static char opt_search[SIZEOF_STR] = "";
889 static char opt_cdup[SIZEOF_STR] = "";
890 static char opt_prefix[SIZEOF_STR] = "";
891 static char opt_git_dir[SIZEOF_STR] = "";
892 static signed char opt_is_inside_work_tree = -1; /* set to TRUE or FALSE */
893 static char opt_editor[SIZEOF_STR] = "";
894 static FILE *opt_tty = NULL;
896 #define is_initial_commit() (!*opt_head_rev)
897 #define is_head_commit(rev) (!strcmp((rev), "HEAD") || !strcmp(opt_head_rev, (rev)))
901 * Line-oriented content detection.
905 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
906 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
907 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
908 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
909 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
910 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
911 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
912 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
913 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
914 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
915 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
916 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
917 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
918 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
919 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
920 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
921 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
922 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
923 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
924 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
925 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
926 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
927 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
928 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
929 LINE(AUTHOR, "author ", COLOR_GREEN, COLOR_DEFAULT, 0), \
930 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
931 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
932 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
933 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
934 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
935 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
936 LINE(DELIMITER, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
937 LINE(DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
938 LINE(MODE, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
939 LINE(LINE_NUMBER, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
940 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
941 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
942 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
943 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
944 LINE(MAIN_LOCAL_TAG,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
945 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
946 LINE(MAIN_TRACKED, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
947 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
948 LINE(MAIN_HEAD, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
949 LINE(MAIN_REVGRAPH,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
950 LINE(TREE_HEAD, "", COLOR_DEFAULT, COLOR_DEFAULT, A_BOLD), \
951 LINE(TREE_DIR, "", COLOR_YELLOW, COLOR_DEFAULT, A_NORMAL), \
952 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
953 LINE(STAT_HEAD, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
954 LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
955 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
956 LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
957 LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
958 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
959 LINE(BLAME_ID, "", COLOR_MAGENTA, COLOR_DEFAULT, 0)
962 #define LINE(type, line, fg, bg, attr) \
970 const char *name; /* Option name. */
971 int namelen; /* Size of option name. */
972 const char *line; /* The start of line to match. */
973 int linelen; /* Size of string to match. */
974 int fg, bg, attr; /* Color and text attributes for the lines. */
977 static struct line_info line_info[] = {
978 #define LINE(type, line, fg, bg, attr) \
979 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
984 static enum line_type
985 get_line_type(const char *line)
987 int linelen = strlen(line);
990 for (type = 0; type < ARRAY_SIZE(line_info); type++)
991 /* Case insensitive search matches Signed-off-by lines better. */
992 if (linelen >= line_info[type].linelen &&
993 !strncasecmp(line_info[type].line, line, line_info[type].linelen))
1000 get_line_attr(enum line_type type)
1002 assert(type < ARRAY_SIZE(line_info));
1003 return COLOR_PAIR(type) | line_info[type].attr;
1006 static struct line_info *
1007 get_line_info(const char *name)
1009 size_t namelen = strlen(name);
1010 enum line_type type;
1012 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1013 if (namelen == line_info[type].namelen &&
1014 !string_enum_compare(line_info[type].name, name, namelen))
1015 return &line_info[type];
1023 int default_bg = line_info[LINE_DEFAULT].bg;
1024 int default_fg = line_info[LINE_DEFAULT].fg;
1025 enum line_type type;
1029 if (assume_default_colors(default_fg, default_bg) == ERR) {
1030 default_bg = COLOR_BLACK;
1031 default_fg = COLOR_WHITE;
1034 for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1035 struct line_info *info = &line_info[type];
1036 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1037 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1039 init_pair(type, fg, bg);
1044 enum line_type type;
1047 unsigned int selected:1;
1048 unsigned int dirty:1;
1049 unsigned int cleareol:1;
1051 void *data; /* User data */
1061 enum request request;
1064 static struct keybinding default_keybindings[] = {
1065 /* View switching */
1066 { 'm', REQ_VIEW_MAIN },
1067 { 'd', REQ_VIEW_DIFF },
1068 { 'l', REQ_VIEW_LOG },
1069 { 't', REQ_VIEW_TREE },
1070 { 'f', REQ_VIEW_BLOB },
1071 { 'B', REQ_VIEW_BLAME },
1072 { 'p', REQ_VIEW_PAGER },
1073 { 'h', REQ_VIEW_HELP },
1074 { 'S', REQ_VIEW_STATUS },
1075 { 'c', REQ_VIEW_STAGE },
1077 /* View manipulation */
1078 { 'q', REQ_VIEW_CLOSE },
1079 { KEY_TAB, REQ_VIEW_NEXT },
1080 { KEY_RETURN, REQ_ENTER },
1081 { KEY_UP, REQ_PREVIOUS },
1082 { KEY_DOWN, REQ_NEXT },
1083 { 'R', REQ_REFRESH },
1084 { KEY_F(5), REQ_REFRESH },
1085 { 'O', REQ_MAXIMIZE },
1087 /* Cursor navigation */
1088 { 'k', REQ_MOVE_UP },
1089 { 'j', REQ_MOVE_DOWN },
1090 { KEY_HOME, REQ_MOVE_FIRST_LINE },
1091 { KEY_END, REQ_MOVE_LAST_LINE },
1092 { KEY_NPAGE, REQ_MOVE_PAGE_DOWN },
1093 { ' ', REQ_MOVE_PAGE_DOWN },
1094 { KEY_PPAGE, REQ_MOVE_PAGE_UP },
1095 { 'b', REQ_MOVE_PAGE_UP },
1096 { '-', REQ_MOVE_PAGE_UP },
1099 { KEY_LEFT, REQ_SCROLL_LEFT },
1100 { KEY_RIGHT, REQ_SCROLL_RIGHT },
1101 { KEY_IC, REQ_SCROLL_LINE_UP },
1102 { KEY_DC, REQ_SCROLL_LINE_DOWN },
1103 { 'w', REQ_SCROLL_PAGE_UP },
1104 { 's', REQ_SCROLL_PAGE_DOWN },
1107 { '/', REQ_SEARCH },
1108 { '?', REQ_SEARCH_BACK },
1109 { 'n', REQ_FIND_NEXT },
1110 { 'N', REQ_FIND_PREV },
1114 { 'z', REQ_STOP_LOADING },
1115 { 'v', REQ_SHOW_VERSION },
1116 { 'r', REQ_SCREEN_REDRAW },
1117 { '.', REQ_TOGGLE_LINENO },
1118 { 'D', REQ_TOGGLE_DATE },
1119 { 'A', REQ_TOGGLE_AUTHOR },
1120 { 'g', REQ_TOGGLE_REV_GRAPH },
1121 { 'F', REQ_TOGGLE_REFS },
1122 { ':', REQ_PROMPT },
1123 { 'u', REQ_STATUS_UPDATE },
1124 { '!', REQ_STATUS_REVERT },
1125 { 'M', REQ_STATUS_MERGE },
1126 { '@', REQ_STAGE_NEXT },
1127 { ',', REQ_PARENT },
1131 #define KEYMAP_INFO \
1145 #define KEYMAP_(name) KEYMAP_##name
1150 static struct enum_map keymap_table[] = {
1151 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1156 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1158 struct keybinding_table {
1159 struct keybinding *data;
1163 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1166 add_keybinding(enum keymap keymap, enum request request, int key)
1168 struct keybinding_table *table = &keybindings[keymap];
1170 table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1172 die("Failed to allocate keybinding");
1173 table->data[table->size].alias = key;
1174 table->data[table->size++].request = request;
1177 /* Looks for a key binding first in the given map, then in the generic map, and
1178 * lastly in the default keybindings. */
1180 get_keybinding(enum keymap keymap, int key)
1184 for (i = 0; i < keybindings[keymap].size; i++)
1185 if (keybindings[keymap].data[i].alias == key)
1186 return keybindings[keymap].data[i].request;
1188 for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1189 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1190 return keybindings[KEYMAP_GENERIC].data[i].request;
1192 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1193 if (default_keybindings[i].alias == key)
1194 return default_keybindings[i].request;
1196 return (enum request) key;
1205 static struct key key_table[] = {
1206 { "Enter", KEY_RETURN },
1208 { "Backspace", KEY_BACKSPACE },
1210 { "Escape", KEY_ESC },
1211 { "Left", KEY_LEFT },
1212 { "Right", KEY_RIGHT },
1214 { "Down", KEY_DOWN },
1215 { "Insert", KEY_IC },
1216 { "Delete", KEY_DC },
1218 { "Home", KEY_HOME },
1220 { "PageUp", KEY_PPAGE },
1221 { "PageDown", KEY_NPAGE },
1231 { "F10", KEY_F(10) },
1232 { "F11", KEY_F(11) },
1233 { "F12", KEY_F(12) },
1237 get_key_value(const char *name)
1241 for (i = 0; i < ARRAY_SIZE(key_table); i++)
1242 if (!strcasecmp(key_table[i].name, name))
1243 return key_table[i].value;
1245 if (strlen(name) == 1 && isprint(*name))
1252 get_key_name(int key_value)
1254 static char key_char[] = "'X'";
1255 const char *seq = NULL;
1258 for (key = 0; key < ARRAY_SIZE(key_table); key++)
1259 if (key_table[key].value == key_value)
1260 seq = key_table[key].name;
1264 isprint(key_value)) {
1265 key_char[1] = (char) key_value;
1269 return seq ? seq : "(no key)";
1273 get_key(enum request request)
1275 static char buf[BUFSIZ];
1282 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1283 struct keybinding *keybinding = &default_keybindings[i];
1285 if (keybinding->request != request)
1288 if (!string_format_from(buf, &pos, "%s%s", sep,
1289 get_key_name(keybinding->alias)))
1290 return "Too many keybindings!";
1297 struct run_request {
1300 const char *argv[SIZEOF_ARG];
1303 static struct run_request *run_request;
1304 static size_t run_requests;
1307 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1309 struct run_request *req;
1311 if (argc >= ARRAY_SIZE(req->argv) - 1)
1314 req = realloc(run_request, (run_requests + 1) * sizeof(*run_request));
1319 req = &run_request[run_requests];
1320 req->keymap = keymap;
1322 req->argv[0] = NULL;
1324 if (!format_argv(req->argv, argv, FORMAT_NONE))
1327 return REQ_NONE + ++run_requests;
1330 static struct run_request *
1331 get_run_request(enum request request)
1333 if (request <= REQ_NONE)
1335 return &run_request[request - REQ_NONE - 1];
1339 add_builtin_run_requests(void)
1341 const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1342 const char *gc[] = { "git", "gc", NULL };
1349 { KEYMAP_MAIN, 'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1350 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1354 for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1357 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1358 if (req != REQ_NONE)
1359 add_keybinding(reqs[i].keymap, req, reqs[i].key);
1364 * User config file handling.
1367 static int config_lineno;
1368 static bool config_errors;
1369 static const char *config_msg;
1371 static struct enum_map color_map[] = {
1372 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1384 static struct enum_map attr_map[] = {
1385 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1392 ATTR_MAP(UNDERLINE),
1395 #define set_attribute(attr, name) map_enum(attr, attr_map, name)
1398 parse_int(int *opt, const char *arg, int min, int max)
1400 int value = atoi(arg);
1402 if (min <= value && value <= max) {
1407 config_msg = "Integer value out of bound";
1412 set_color(int *color, const char *name)
1414 if (map_enum(color, color_map, name))
1416 if (!prefixcmp(name, "color"))
1417 return parse_int(color, name + 5, 0, 255) == OK;
1421 /* Wants: object fgcolor bgcolor [attribute] */
1423 option_color_command(int argc, const char *argv[])
1425 struct line_info *info;
1427 if (argc != 3 && argc != 4) {
1428 config_msg = "Wrong number of arguments given to color command";
1432 info = get_line_info(argv[0]);
1434 static struct enum_map obsolete[] = {
1435 ENUM_MAP("main-delim", LINE_DELIMITER),
1436 ENUM_MAP("main-date", LINE_DATE),
1437 ENUM_MAP("main-author", LINE_AUTHOR),
1441 if (!map_enum(&index, obsolete, argv[0])) {
1442 config_msg = "Unknown color name";
1445 info = &line_info[index];
1448 if (!set_color(&info->fg, argv[1]) ||
1449 !set_color(&info->bg, argv[2])) {
1450 config_msg = "Unknown color";
1454 if (argc == 4 && !set_attribute(&info->attr, argv[3])) {
1455 config_msg = "Unknown attribute";
1462 static int parse_bool(bool *opt, const char *arg)
1464 *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1470 parse_string(char *opt, const char *arg, size_t optsize)
1472 int arglen = strlen(arg);
1477 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1478 config_msg = "Unmatched quotation";
1481 arg += 1; arglen -= 2;
1483 string_ncopy_do(opt, optsize, arg, strlen(arg));
1488 /* Wants: name = value */
1490 option_set_command(int argc, const char *argv[])
1493 config_msg = "Wrong number of arguments given to set command";
1497 if (strcmp(argv[1], "=")) {
1498 config_msg = "No value assigned";
1502 if (!strcmp(argv[0], "show-author"))
1503 return parse_bool(&opt_author, argv[2]);
1505 if (!strcmp(argv[0], "show-date"))
1506 return parse_bool(&opt_date, argv[2]);
1508 if (!strcmp(argv[0], "show-rev-graph"))
1509 return parse_bool(&opt_rev_graph, argv[2]);
1511 if (!strcmp(argv[0], "show-refs"))
1512 return parse_bool(&opt_show_refs, argv[2]);
1514 if (!strcmp(argv[0], "show-line-numbers"))
1515 return parse_bool(&opt_line_number, argv[2]);
1517 if (!strcmp(argv[0], "line-graphics"))
1518 return parse_bool(&opt_line_graphics, argv[2]);
1520 if (!strcmp(argv[0], "line-number-interval"))
1521 return parse_int(&opt_num_interval, argv[2], 1, 1024);
1523 if (!strcmp(argv[0], "author-width"))
1524 return parse_int(&opt_author_cols, argv[2], 0, 1024);
1526 if (!strcmp(argv[0], "tab-size"))
1527 return parse_int(&opt_tab_size, argv[2], 1, 1024);
1529 if (!strcmp(argv[0], "commit-encoding"))
1530 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
1532 config_msg = "Unknown variable name";
1536 /* Wants: mode request key */
1538 option_bind_command(int argc, const char *argv[])
1540 enum request request;
1545 config_msg = "Wrong number of arguments given to bind command";
1549 if (set_keymap(&keymap, argv[0]) == ERR) {
1550 config_msg = "Unknown key map";
1554 key = get_key_value(argv[1]);
1556 config_msg = "Unknown key";
1560 request = get_request(argv[2]);
1561 if (request == REQ_NONE) {
1562 static struct enum_map obsolete[] = {
1563 ENUM_MAP("cherry-pick", REQ_NONE),
1564 ENUM_MAP("screen-resize", REQ_NONE),
1565 ENUM_MAP("tree-parent", REQ_PARENT),
1569 if (map_enum(&alias, obsolete, argv[2])) {
1570 if (alias != REQ_NONE)
1571 add_keybinding(keymap, alias, key);
1572 config_msg = "Obsolete request name";
1576 if (request == REQ_NONE && *argv[2]++ == '!')
1577 request = add_run_request(keymap, key, argc - 2, argv + 2);
1578 if (request == REQ_NONE) {
1579 config_msg = "Unknown request name";
1583 add_keybinding(keymap, request, key);
1589 set_option(const char *opt, char *value)
1591 const char *argv[SIZEOF_ARG];
1594 if (!argv_from_string(argv, &argc, value)) {
1595 config_msg = "Too many option arguments";
1599 if (!strcmp(opt, "color"))
1600 return option_color_command(argc, argv);
1602 if (!strcmp(opt, "set"))
1603 return option_set_command(argc, argv);
1605 if (!strcmp(opt, "bind"))
1606 return option_bind_command(argc, argv);
1608 config_msg = "Unknown option command";
1613 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1618 config_msg = "Internal error";
1620 /* Check for comment markers, since read_properties() will
1621 * only ensure opt and value are split at first " \t". */
1622 optlen = strcspn(opt, "#");
1626 if (opt[optlen] != 0) {
1627 config_msg = "No option value";
1631 /* Look for comment endings in the value. */
1632 size_t len = strcspn(value, "#");
1634 if (len < valuelen) {
1636 value[valuelen] = 0;
1639 status = set_option(opt, value);
1642 if (status == ERR) {
1643 warn("Error on line %d, near '%.*s': %s",
1644 config_lineno, (int) optlen, opt, config_msg);
1645 config_errors = TRUE;
1648 /* Always keep going if errors are encountered. */
1653 load_option_file(const char *path)
1657 /* It's OK that the file doesn't exist. */
1658 if (!io_open(&io, path))
1662 config_errors = FALSE;
1664 if (io_load(&io, " \t", read_option) == ERR ||
1665 config_errors == TRUE)
1666 warn("Errors while loading %s.", path);
1672 const char *home = getenv("HOME");
1673 const char *tigrc_user = getenv("TIGRC_USER");
1674 const char *tigrc_system = getenv("TIGRC_SYSTEM");
1675 char buf[SIZEOF_STR];
1677 add_builtin_run_requests();
1680 tigrc_system = SYSCONFDIR "/tigrc";
1681 load_option_file(tigrc_system);
1684 if (!home || !string_format(buf, "%s/.tigrc", home))
1688 load_option_file(tigrc_user);
1701 /* The display array of active views and the index of the current view. */
1702 static struct view *display[2];
1703 static unsigned int current_view;
1705 #define foreach_displayed_view(view, i) \
1706 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1708 #define displayed_views() (display[1] != NULL ? 2 : 1)
1710 /* Current head and commit ID */
1711 static char ref_blob[SIZEOF_REF] = "";
1712 static char ref_commit[SIZEOF_REF] = "HEAD";
1713 static char ref_head[SIZEOF_REF] = "HEAD";
1716 const char *name; /* View name */
1717 const char *cmd_env; /* Command line set via environment */
1718 const char *id; /* Points to either of ref_{head,commit,blob} */
1720 struct view_ops *ops; /* View operations */
1722 enum keymap keymap; /* What keymap does this view have */
1723 bool git_dir; /* Whether the view requires a git directory. */
1725 char ref[SIZEOF_REF]; /* Hovered commit reference */
1726 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
1728 int height, width; /* The width and height of the main window */
1729 WINDOW *win; /* The main window */
1730 WINDOW *title; /* The title window living below the main window */
1733 unsigned long offset; /* Offset of the window top */
1734 unsigned long yoffset; /* Offset from the window side. */
1735 unsigned long lineno; /* Current line number */
1736 unsigned long p_offset; /* Previous offset of the window top */
1737 unsigned long p_yoffset;/* Previous offset from the window side */
1738 unsigned long p_lineno; /* Previous current line number */
1739 bool p_restore; /* Should the previous position be restored. */
1742 char grep[SIZEOF_STR]; /* Search string */
1743 regex_t *regex; /* Pre-compiled regexp */
1745 /* If non-NULL, points to the view that opened this view. If this view
1746 * is closed tig will switch back to the parent view. */
1747 struct view *parent;
1750 size_t lines; /* Total number of lines */
1751 struct line *line; /* Line index */
1752 size_t line_alloc; /* Total number of allocated lines */
1753 unsigned int digits; /* Number of digits in the lines member. */
1756 struct line *curline; /* Line currently being drawn. */
1757 enum line_type curtype; /* Attribute currently used for drawing. */
1758 unsigned long col; /* Column when drawing. */
1759 bool has_scrolled; /* View was scrolled. */
1760 bool can_hscroll; /* View can be scrolled horizontally. */
1770 /* What type of content being displayed. Used in the title bar. */
1772 /* Default command arguments. */
1774 /* Open and reads in all view content. */
1775 bool (*open)(struct view *view);
1776 /* Read one line; updates view->line. */
1777 bool (*read)(struct view *view, char *data);
1778 /* Draw one line; @lineno must be < view->height. */
1779 bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
1780 /* Depending on view handle a special requests. */
1781 enum request (*request)(struct view *view, enum request request, struct line *line);
1782 /* Search for regexp in a line. */
1783 bool (*grep)(struct view *view, struct line *line);
1785 void (*select)(struct view *view, struct line *line);
1788 static struct view_ops blame_ops;
1789 static struct view_ops blob_ops;
1790 static struct view_ops diff_ops;
1791 static struct view_ops help_ops;
1792 static struct view_ops log_ops;
1793 static struct view_ops main_ops;
1794 static struct view_ops pager_ops;
1795 static struct view_ops stage_ops;
1796 static struct view_ops status_ops;
1797 static struct view_ops tree_ops;
1799 #define VIEW_STR(name, env, ref, ops, map, git) \
1800 { name, #env, ref, ops, map, git }
1802 #define VIEW_(id, name, ops, git, ref) \
1803 VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
1806 static struct view views[] = {
1807 VIEW_(MAIN, "main", &main_ops, TRUE, ref_head),
1808 VIEW_(DIFF, "diff", &diff_ops, TRUE, ref_commit),
1809 VIEW_(LOG, "log", &log_ops, TRUE, ref_head),
1810 VIEW_(TREE, "tree", &tree_ops, TRUE, ref_commit),
1811 VIEW_(BLOB, "blob", &blob_ops, TRUE, ref_blob),
1812 VIEW_(BLAME, "blame", &blame_ops, TRUE, ref_commit),
1813 VIEW_(HELP, "help", &help_ops, FALSE, ""),
1814 VIEW_(PAGER, "pager", &pager_ops, FALSE, "stdin"),
1815 VIEW_(STATUS, "status", &status_ops, TRUE, ""),
1816 VIEW_(STAGE, "stage", &stage_ops, TRUE, ""),
1819 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
1820 #define VIEW_REQ(view) ((view) - views + REQ_OFFSET + 1)
1822 #define foreach_view(view, i) \
1823 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1825 #define view_is_displayed(view) \
1826 (view == display[0] || view == display[1])
1833 static int line_graphics[] = {
1834 /* LINE_GRAPHIC_VLINE: */ '|'
1838 set_view_attr(struct view *view, enum line_type type)
1840 if (!view->curline->selected && view->curtype != type) {
1841 wattrset(view->win, get_line_attr(type));
1842 wchgat(view->win, -1, 0, type, NULL);
1843 view->curtype = type;
1848 draw_chars(struct view *view, enum line_type type, const char *string,
1849 int max_len, bool use_tilde)
1853 int trimmed = FALSE;
1854 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
1860 len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde);
1862 col = len = strlen(string);
1863 if (len > max_len) {
1867 col = len = max_len;
1872 set_view_attr(view, type);
1874 waddnstr(view->win, string, len);
1875 if (trimmed && use_tilde) {
1876 set_view_attr(view, LINE_DELIMITER);
1877 waddch(view->win, '~');
1881 if (view->col + col >= view->width + view->yoffset)
1882 view->can_hscroll = TRUE;
1888 draw_space(struct view *view, enum line_type type, int max, int spaces)
1890 static char space[] = " ";
1893 spaces = MIN(max, spaces);
1895 while (spaces > 0) {
1896 int len = MIN(spaces, sizeof(space) - 1);
1898 col += draw_chars(view, type, space, spaces, FALSE);
1906 draw_lineno(struct view *view, unsigned int lineno)
1908 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
1910 int digits3 = view->digits < 3 ? 3 : view->digits;
1911 int max_number = MIN(digits3, STRING_SIZE(number));
1912 int max = view->width - view->col;
1915 if (max < max_number)
1918 lineno += view->offset + 1;
1919 if (lineno == 1 || (lineno % opt_num_interval) == 0) {
1920 static char fmt[] = "%1ld";
1922 if (view->digits <= 9)
1923 fmt[1] = '0' + digits3;
1925 if (!string_format(number, fmt, lineno))
1927 col = draw_chars(view, LINE_LINE_NUMBER, number, max_number, TRUE);
1929 col = draw_space(view, LINE_LINE_NUMBER, max_number, max_number);
1932 if (col < max && skip <= col) {
1933 set_view_attr(view, LINE_DEFAULT);
1934 waddch(view->win, line_graphics[LINE_GRAPHIC_VLINE]);
1939 if (col < max && skip <= col)
1940 col = draw_space(view, LINE_DEFAULT, max - col, 1);
1943 return view->width + view->yoffset <= view->col;
1947 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
1949 view->col += draw_chars(view, type, string, view->width + view->yoffset - view->col, trim);
1950 return view->width - view->col <= 0;
1954 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
1956 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
1957 int max = view->width - view->col;
1963 set_view_attr(view, type);
1964 /* Using waddch() instead of waddnstr() ensures that
1965 * they'll be rendered correctly for the cursor line. */
1966 for (i = skip; i < size; i++)
1967 waddch(view->win, graphic[i]);
1970 if (size < max && skip <= size)
1971 waddch(view->win, ' ');
1974 return view->width - view->col <= 0;
1978 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
1980 int max = MIN(view->width - view->col, len);
1984 col = draw_chars(view, type, text, max - 1, trim);
1986 col = draw_space(view, type, max - 1, max - 1);
1989 view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
1990 return view->width + view->yoffset <= view->col;
1994 draw_date(struct view *view, struct tm *time)
1996 char buf[DATE_COLS];
2001 timelen = strftime(buf, sizeof(buf), DATE_FORMAT, time);
2002 date = timelen ? buf : NULL;
2004 return draw_field(view, LINE_DATE, date, DATE_COLS, FALSE);
2008 draw_author(struct view *view, const char *author)
2010 bool trim = opt_author_cols == 0 || opt_author_cols > 5 || !author;
2013 static char initials[10];
2016 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@')
2018 memset(initials, 0, sizeof(initials));
2019 for (pos = 0; *author && pos < opt_author_cols - 1; author++, pos++) {
2020 while (is_initial_sep(*author))
2022 strncpy(&initials[pos], author, sizeof(initials) - 1 - pos);
2023 while (*author && !is_initial_sep(author[1]))
2030 return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2034 draw_mode(struct view *view, mode_t mode)
2036 static const char dir_mode[] = "drwxr-xr-x";
2037 static const char link_mode[] = "lrwxrwxrwx";
2038 static const char exe_mode[] = "-rwxr-xr-x";
2039 static const char file_mode[] = "-rw-r--r--";
2044 else if (S_ISLNK(mode))
2046 else if (mode & S_IXUSR)
2051 return draw_field(view, LINE_MODE, str, sizeof(file_mode), FALSE);
2055 draw_view_line(struct view *view, unsigned int lineno)
2058 bool selected = (view->offset + lineno == view->lineno);
2060 assert(view_is_displayed(view));
2062 if (view->offset + lineno >= view->lines)
2065 line = &view->line[view->offset + lineno];
2067 wmove(view->win, lineno, 0);
2069 wclrtoeol(view->win);
2071 view->curline = line;
2072 view->curtype = LINE_NONE;
2073 line->selected = FALSE;
2074 line->dirty = line->cleareol = 0;
2077 set_view_attr(view, LINE_CURSOR);
2078 line->selected = TRUE;
2079 view->ops->select(view, line);
2082 return view->ops->draw(view, line, lineno);
2086 redraw_view_dirty(struct view *view)
2091 for (lineno = 0; lineno < view->height; lineno++) {
2092 if (view->offset + lineno >= view->lines)
2094 if (!view->line[view->offset + lineno].dirty)
2097 if (!draw_view_line(view, lineno))
2103 wnoutrefresh(view->win);
2107 redraw_view_from(struct view *view, int lineno)
2109 assert(0 <= lineno && lineno < view->height);
2112 view->can_hscroll = FALSE;
2114 for (; lineno < view->height; lineno++) {
2115 if (!draw_view_line(view, lineno))
2119 wnoutrefresh(view->win);
2123 redraw_view(struct view *view)
2126 redraw_view_from(view, 0);
2131 update_view_title(struct view *view)
2133 char buf[SIZEOF_STR];
2134 char state[SIZEOF_STR];
2135 size_t bufpos = 0, statelen = 0;
2137 assert(view_is_displayed(view));
2139 if (view != VIEW(REQ_VIEW_STATUS) && view->lines) {
2140 unsigned int view_lines = view->offset + view->height;
2141 unsigned int lines = view->lines
2142 ? MIN(view_lines, view->lines) * 100 / view->lines
2145 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2154 time_t secs = time(NULL) - view->start_time;
2156 /* Three git seconds are a long time ... */
2158 string_format_from(state, &statelen, " loading %lds", secs);
2161 string_format_from(buf, &bufpos, "[%s]", view->name);
2162 if (*view->ref && bufpos < view->width) {
2163 size_t refsize = strlen(view->ref);
2164 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2166 if (minsize < view->width)
2167 refsize = view->width - minsize + 7;
2168 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2171 if (statelen && bufpos < view->width) {
2172 string_format_from(buf, &bufpos, "%s", state);
2175 if (view == display[current_view])
2176 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2178 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2180 mvwaddnstr(view->title, 0, 0, buf, bufpos);
2181 wclrtoeol(view->title);
2182 wnoutrefresh(view->title);
2186 resize_display(void)
2189 struct view *base = display[0];
2190 struct view *view = display[1] ? display[1] : display[0];
2192 /* Setup window dimensions */
2194 getmaxyx(stdscr, base->height, base->width);
2196 /* Make room for the status window. */
2200 /* Horizontal split. */
2201 view->width = base->width;
2202 view->height = SCALE_SPLIT_VIEW(base->height);
2203 base->height -= view->height;
2205 /* Make room for the title bar. */
2209 /* Make room for the title bar. */
2214 foreach_displayed_view (view, i) {
2216 view->win = newwin(view->height, 0, offset, 0);
2218 die("Failed to create %s view", view->name);
2220 scrollok(view->win, FALSE);
2222 view->title = newwin(1, 0, offset + view->height, 0);
2224 die("Failed to create title window");
2227 wresize(view->win, view->height, view->width);
2228 mvwin(view->win, offset, 0);
2229 mvwin(view->title, offset + view->height, 0);
2232 offset += view->height + 1;
2237 redraw_display(bool clear)
2242 foreach_displayed_view (view, i) {
2246 update_view_title(view);
2251 toggle_view_option(bool *option, const char *help)
2254 redraw_display(FALSE);
2255 report("%sabling %s", *option ? "En" : "Dis", help);
2263 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2265 if (lineno >= view->lines)
2266 lineno = view->lines > 0 ? view->lines - 1 : 0;
2268 if (offset > lineno || offset + view->height <= lineno) {
2269 unsigned long half = view->height / 2;
2272 offset = lineno - half;
2277 if (offset != view->offset || lineno != view->lineno) {
2278 view->offset = offset;
2279 view->lineno = lineno;
2286 /* Scrolling backend */
2288 do_scroll_view(struct view *view, int lines)
2290 bool redraw_current_line = FALSE;
2292 /* The rendering expects the new offset. */
2293 view->offset += lines;
2295 assert(0 <= view->offset && view->offset < view->lines);
2298 /* Move current line into the view. */
2299 if (view->lineno < view->offset) {
2300 view->lineno = view->offset;
2301 redraw_current_line = TRUE;
2302 } else if (view->lineno >= view->offset + view->height) {
2303 view->lineno = view->offset + view->height - 1;
2304 redraw_current_line = TRUE;
2307 assert(view->offset <= view->lineno && view->lineno < view->lines);
2309 /* Redraw the whole screen if scrolling is pointless. */
2310 if (view->height < ABS(lines)) {
2314 int line = lines > 0 ? view->height - lines : 0;
2315 int end = line + ABS(lines);
2317 scrollok(view->win, TRUE);
2318 wscrl(view->win, lines);
2319 scrollok(view->win, FALSE);
2321 while (line < end && draw_view_line(view, line))
2324 if (redraw_current_line)
2325 draw_view_line(view, view->lineno - view->offset);
2326 wnoutrefresh(view->win);
2329 view->has_scrolled = TRUE;
2333 /* Scroll frontend */
2335 scroll_view(struct view *view, enum request request)
2339 assert(view_is_displayed(view));
2342 case REQ_SCROLL_LEFT:
2343 if (view->yoffset == 0) {
2344 report("Cannot scroll beyond the first column");
2347 if (view->yoffset <= SCROLL_INTERVAL)
2350 view->yoffset -= SCROLL_INTERVAL;
2351 redraw_view_from(view, 0);
2354 case REQ_SCROLL_RIGHT:
2355 if (!view->can_hscroll) {
2356 report("Cannot scroll beyond the last column");
2359 view->yoffset += SCROLL_INTERVAL;
2363 case REQ_SCROLL_PAGE_DOWN:
2364 lines = view->height;
2365 case REQ_SCROLL_LINE_DOWN:
2366 if (view->offset + lines > view->lines)
2367 lines = view->lines - view->offset;
2369 if (lines == 0 || view->offset + view->height >= view->lines) {
2370 report("Cannot scroll beyond the last line");
2375 case REQ_SCROLL_PAGE_UP:
2376 lines = view->height;
2377 case REQ_SCROLL_LINE_UP:
2378 if (lines > view->offset)
2379 lines = view->offset;
2382 report("Cannot scroll beyond the first line");
2390 die("request %d not handled in switch", request);
2393 do_scroll_view(view, lines);
2398 move_view(struct view *view, enum request request)
2400 int scroll_steps = 0;
2404 case REQ_MOVE_FIRST_LINE:
2405 steps = -view->lineno;
2408 case REQ_MOVE_LAST_LINE:
2409 steps = view->lines - view->lineno - 1;
2412 case REQ_MOVE_PAGE_UP:
2413 steps = view->height > view->lineno
2414 ? -view->lineno : -view->height;
2417 case REQ_MOVE_PAGE_DOWN:
2418 steps = view->lineno + view->height >= view->lines
2419 ? view->lines - view->lineno - 1 : view->height;
2431 die("request %d not handled in switch", request);
2434 if (steps <= 0 && view->lineno == 0) {
2435 report("Cannot move beyond the first line");
2438 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2439 report("Cannot move beyond the last line");
2443 /* Move the current line */
2444 view->lineno += steps;
2445 assert(0 <= view->lineno && view->lineno < view->lines);
2447 /* Check whether the view needs to be scrolled */
2448 if (view->lineno < view->offset ||
2449 view->lineno >= view->offset + view->height) {
2450 scroll_steps = steps;
2451 if (steps < 0 && -steps > view->offset) {
2452 scroll_steps = -view->offset;
2454 } else if (steps > 0) {
2455 if (view->lineno == view->lines - 1 &&
2456 view->lines > view->height) {
2457 scroll_steps = view->lines - view->offset - 1;
2458 if (scroll_steps >= view->height)
2459 scroll_steps -= view->height - 1;
2464 if (!view_is_displayed(view)) {
2465 view->offset += scroll_steps;
2466 assert(0 <= view->offset && view->offset < view->lines);
2467 view->ops->select(view, &view->line[view->lineno]);
2471 /* Repaint the old "current" line if we be scrolling */
2472 if (ABS(steps) < view->height)
2473 draw_view_line(view, view->lineno - steps - view->offset);
2476 do_scroll_view(view, scroll_steps);
2480 /* Draw the current line */
2481 draw_view_line(view, view->lineno - view->offset);
2483 wnoutrefresh(view->win);
2492 static void search_view(struct view *view, enum request request);
2495 select_view_line(struct view *view, unsigned long lineno)
2497 unsigned long old_lineno = view->lineno;
2498 unsigned long old_offset = view->offset;
2500 if (goto_view_line(view, view->offset, lineno)) {
2501 if (view_is_displayed(view)) {
2502 if (old_offset != view->offset) {
2505 draw_view_line(view, old_lineno - view->offset);
2506 draw_view_line(view, view->lineno - view->offset);
2507 wnoutrefresh(view->win);
2510 view->ops->select(view, &view->line[view->lineno]);
2516 find_next(struct view *view, enum request request)
2518 unsigned long lineno = view->lineno;
2523 report("No previous search");
2525 search_view(view, request);
2535 case REQ_SEARCH_BACK:
2544 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2545 lineno += direction;
2547 /* Note, lineno is unsigned long so will wrap around in which case it
2548 * will become bigger than view->lines. */
2549 for (; lineno < view->lines; lineno += direction) {
2550 if (view->ops->grep(view, &view->line[lineno])) {
2551 select_view_line(view, lineno);
2552 report("Line %ld matches '%s'", lineno + 1, view->grep);
2557 report("No match found for '%s'", view->grep);
2561 search_view(struct view *view, enum request request)
2566 regfree(view->regex);
2569 view->regex = calloc(1, sizeof(*view->regex));
2574 regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2575 if (regex_err != 0) {
2576 char buf[SIZEOF_STR] = "unknown error";
2578 regerror(regex_err, view->regex, buf, sizeof(buf));
2579 report("Search failed: %s", buf);
2583 string_copy(view->grep, opt_search);
2585 find_next(view, request);
2589 * Incremental updating
2593 reset_view(struct view *view)
2597 for (i = 0; i < view->lines; i++)
2598 free(view->line[i].data);
2601 view->p_offset = view->offset;
2602 view->p_yoffset = view->yoffset;
2603 view->p_lineno = view->lineno;
2610 view->line_alloc = 0;
2612 view->update_secs = 0;
2616 free_argv(const char *argv[])
2620 for (argc = 0; argv[argc]; argc++)
2621 free((void *) argv[argc]);
2625 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
2627 char buf[SIZEOF_STR];
2629 bool noreplace = flags == FORMAT_NONE;
2631 free_argv(dst_argv);
2633 for (argc = 0; src_argv[argc]; argc++) {
2634 const char *arg = src_argv[argc];
2638 char *next = strstr(arg, "%(");
2639 int len = next - arg;
2642 if (!next || noreplace) {
2643 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
2648 } else if (!prefixcmp(next, "%(directory)")) {
2651 } else if (!prefixcmp(next, "%(file)")) {
2654 } else if (!prefixcmp(next, "%(ref)")) {
2655 value = *opt_ref ? opt_ref : "HEAD";
2657 } else if (!prefixcmp(next, "%(head)")) {
2660 } else if (!prefixcmp(next, "%(commit)")) {
2663 } else if (!prefixcmp(next, "%(blob)")) {
2667 report("Unknown replacement: `%s`", next);
2671 if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
2674 arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
2677 dst_argv[argc] = strdup(buf);
2678 if (!dst_argv[argc])
2682 dst_argv[argc] = NULL;
2684 return src_argv[argc] == NULL;
2688 restore_view_position(struct view *view)
2690 if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
2693 /* Changing the view position cancels the restoring. */
2694 /* FIXME: Changing back to the first line is not detected. */
2695 if (view->offset != 0 || view->lineno != 0) {
2696 view->p_restore = FALSE;
2700 if (goto_view_line(view, view->p_offset, view->p_lineno) &&
2701 view_is_displayed(view))
2704 view->yoffset = view->p_yoffset;
2705 view->p_restore = FALSE;
2711 end_update(struct view *view, bool force)
2715 while (!view->ops->read(view, NULL))
2718 set_nonblocking_input(FALSE);
2720 kill_io(view->pipe);
2721 done_io(view->pipe);
2726 setup_update(struct view *view, const char *vid)
2728 set_nonblocking_input(TRUE);
2730 string_copy_rev(view->vid, vid);
2731 view->pipe = &view->io;
2732 view->start_time = time(NULL);
2736 prepare_update(struct view *view, const char *argv[], const char *dir,
2737 enum format_flags flags)
2740 end_update(view, TRUE);
2741 return init_io_rd(&view->io, argv, dir, flags);
2745 prepare_update_file(struct view *view, const char *name)
2748 end_update(view, TRUE);
2749 return io_open(&view->io, name);
2753 begin_update(struct view *view, bool refresh)
2756 end_update(view, TRUE);
2759 if (!start_io(&view->io))
2763 if (view == VIEW(REQ_VIEW_TREE) && strcmp(view->vid, view->id))
2766 if (!run_io_rd(&view->io, view->ops->argv, FORMAT_ALL))
2769 /* Put the current ref_* value to the view title ref
2770 * member. This is needed by the blob view. Most other
2771 * views sets it automatically after loading because the
2772 * first line is a commit line. */
2773 string_copy_rev(view->ref, view->id);
2776 setup_update(view, view->id);
2781 #define ITEM_CHUNK_SIZE 256
2783 realloc_items(void *mem, size_t *size, size_t new_size, size_t item_size)
2785 size_t num_chunks = *size / ITEM_CHUNK_SIZE;
2786 size_t num_chunks_new = (new_size + ITEM_CHUNK_SIZE - 1) / ITEM_CHUNK_SIZE;
2788 if (mem == NULL || num_chunks != num_chunks_new) {
2789 *size = num_chunks_new * ITEM_CHUNK_SIZE;
2790 mem = realloc(mem, *size * item_size);
2796 static struct line *
2797 realloc_lines(struct view *view, size_t line_size)
2799 size_t alloc = view->line_alloc;
2800 struct line *tmp = realloc_items(view->line, &alloc, line_size,
2801 sizeof(*view->line));
2807 view->line_alloc = alloc;
2812 update_view(struct view *view)
2814 char out_buffer[BUFSIZ * 2];
2816 /* Clear the view and redraw everything since the tree sorting
2817 * might have rearranged things. */
2818 bool redraw = view->lines == 0;
2819 bool can_read = TRUE;
2824 if (!io_can_read(view->pipe)) {
2825 if (view->lines == 0) {
2826 time_t secs = time(NULL) - view->start_time;
2828 if (secs > 1 && secs > view->update_secs) {
2829 if (view->update_secs == 0)
2831 update_view_title(view);
2832 view->update_secs = secs;
2838 for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
2839 if (opt_iconv != ICONV_NONE) {
2840 ICONV_CONST char *inbuf = line;
2841 size_t inlen = strlen(line) + 1;
2843 char *outbuf = out_buffer;
2844 size_t outlen = sizeof(out_buffer);
2848 ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2849 if (ret != (size_t) -1)
2853 if (!view->ops->read(view, line)) {
2854 report("Allocation failure");
2855 end_update(view, TRUE);
2861 unsigned long lines = view->lines;
2864 for (digits = 0; lines; digits++)
2867 /* Keep the displayed view in sync with line number scaling. */
2868 if (digits != view->digits) {
2869 view->digits = digits;
2870 if (opt_line_number || view == VIEW(REQ_VIEW_BLAME))
2875 if (io_error(view->pipe)) {
2876 report("Failed to read: %s", io_strerror(view->pipe));
2877 end_update(view, TRUE);
2879 } else if (io_eof(view->pipe)) {
2881 end_update(view, FALSE);
2884 if (restore_view_position(view))
2887 if (!view_is_displayed(view))
2891 redraw_view_from(view, 0);
2893 redraw_view_dirty(view);
2895 /* Update the title _after_ the redraw so that if the redraw picks up a
2896 * commit reference in view->ref it'll be available here. */
2897 update_view_title(view);
2901 static struct line *
2902 add_line_data(struct view *view, void *data, enum line_type type)
2906 if (!realloc_lines(view, view->lines + 1))
2909 line = &view->line[view->lines++];
2910 memset(line, 0, sizeof(*line));
2918 static struct line *
2919 add_line_text(struct view *view, const char *text, enum line_type type)
2921 char *data = text ? strdup(text) : NULL;
2923 return data ? add_line_data(view, data, type) : NULL;
2926 static struct line *
2927 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
2929 char buf[SIZEOF_STR];
2932 va_start(args, fmt);
2933 if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
2937 return buf[0] ? add_line_text(view, buf, type) : NULL;
2945 OPEN_DEFAULT = 0, /* Use default view switching. */
2946 OPEN_SPLIT = 1, /* Split current view. */
2947 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
2948 OPEN_REFRESH = 16, /* Refresh view using previous command. */
2949 OPEN_PREPARED = 32, /* Open already prepared command. */
2953 open_view(struct view *prev, enum request request, enum open_flags flags)
2955 bool split = !!(flags & OPEN_SPLIT);
2956 bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
2957 bool nomaximize = !!(flags & OPEN_REFRESH);
2958 struct view *view = VIEW(request);
2959 int nviews = displayed_views();
2960 struct view *base_view = display[0];
2962 if (view == prev && nviews == 1 && !reload) {
2963 report("Already in %s view", view->name);
2967 if (view->git_dir && !opt_git_dir[0]) {
2968 report("The %s view is disabled in pager view", view->name);
2975 } else if (!nomaximize) {
2976 /* Maximize the current view. */
2977 memset(display, 0, sizeof(display));
2979 display[current_view] = view;
2982 /* Resize the view when switching between split- and full-screen,
2983 * or when switching between two different full-screen views. */
2984 if (nviews != displayed_views() ||
2985 (nviews == 1 && base_view != display[0]))
2988 if (view->ops->open) {
2990 end_update(view, TRUE);
2991 if (!view->ops->open(view)) {
2992 report("Failed to load %s view", view->name);
2995 restore_view_position(view);
2997 } else if ((reload || strcmp(view->vid, view->id)) &&
2998 !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
2999 report("Failed to load %s view", view->name);
3003 if (split && prev->lineno - prev->offset >= prev->height) {
3004 /* Take the title line into account. */
3005 int lines = prev->lineno - prev->offset - prev->height + 1;
3007 /* Scroll the view that was split if the current line is
3008 * outside the new limited view. */
3009 do_scroll_view(prev, lines);
3012 if (prev && view != prev) {
3014 /* "Blur" the previous view. */
3015 update_view_title(prev);
3018 view->parent = prev;
3021 if (view->pipe && view->lines == 0) {
3022 /* Clear the old view and let the incremental updating refill
3025 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3027 } else if (view_is_displayed(view)) {
3034 open_external_viewer(const char *argv[], const char *dir)
3036 def_prog_mode(); /* save current tty modes */
3037 endwin(); /* restore original tty modes */
3038 run_io_fg(argv, dir);
3039 fprintf(stderr, "Press Enter to continue");
3042 redraw_display(TRUE);
3046 open_mergetool(const char *file)
3048 const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3050 open_external_viewer(mergetool_argv, opt_cdup);
3054 open_editor(bool from_root, const char *file)
3056 const char *editor_argv[] = { "vi", file, NULL };
3059 editor = getenv("GIT_EDITOR");
3060 if (!editor && *opt_editor)
3061 editor = opt_editor;
3063 editor = getenv("VISUAL");
3065 editor = getenv("EDITOR");
3069 editor_argv[0] = editor;
3070 open_external_viewer(editor_argv, from_root ? opt_cdup : NULL);
3074 open_run_request(enum request request)
3076 struct run_request *req = get_run_request(request);
3077 const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3080 report("Unknown run request");
3084 if (format_argv(argv, req->argv, FORMAT_ALL))
3085 open_external_viewer(argv, NULL);
3090 * User request switch noodle
3094 view_driver(struct view *view, enum request request)
3098 if (request == REQ_NONE) {
3103 if (request > REQ_NONE) {
3104 open_run_request(request);
3105 /* FIXME: When all views can refresh always do this. */
3106 if (view == VIEW(REQ_VIEW_STATUS) ||
3107 view == VIEW(REQ_VIEW_MAIN) ||
3108 view == VIEW(REQ_VIEW_LOG) ||
3109 view == VIEW(REQ_VIEW_STAGE))
3110 request = REQ_REFRESH;
3115 if (view && view->lines) {
3116 request = view->ops->request(view, request, &view->line[view->lineno]);
3117 if (request == REQ_NONE)
3124 case REQ_MOVE_PAGE_UP:
3125 case REQ_MOVE_PAGE_DOWN:
3126 case REQ_MOVE_FIRST_LINE:
3127 case REQ_MOVE_LAST_LINE:
3128 move_view(view, request);
3131 case REQ_SCROLL_LEFT:
3132 case REQ_SCROLL_RIGHT:
3133 case REQ_SCROLL_LINE_DOWN:
3134 case REQ_SCROLL_LINE_UP:
3135 case REQ_SCROLL_PAGE_DOWN:
3136 case REQ_SCROLL_PAGE_UP:
3137 scroll_view(view, request);
3140 case REQ_VIEW_BLAME:
3142 report("No file chosen, press %s to open tree view",
3143 get_key(REQ_VIEW_TREE));
3146 open_view(view, request, OPEN_DEFAULT);
3151 report("No file chosen, press %s to open tree view",
3152 get_key(REQ_VIEW_TREE));
3155 open_view(view, request, OPEN_DEFAULT);
3158 case REQ_VIEW_PAGER:
3159 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3160 report("No pager content, press %s to run command from prompt",
3161 get_key(REQ_PROMPT));
3164 open_view(view, request, OPEN_DEFAULT);
3167 case REQ_VIEW_STAGE:
3168 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3169 report("No stage content, press %s to open the status view and choose file",
3170 get_key(REQ_VIEW_STATUS));
3173 open_view(view, request, OPEN_DEFAULT);
3176 case REQ_VIEW_STATUS:
3177 if (opt_is_inside_work_tree == FALSE) {
3178 report("The status view requires a working tree");
3181 open_view(view, request, OPEN_DEFAULT);
3189 open_view(view, request, OPEN_DEFAULT);
3194 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3196 if ((view == VIEW(REQ_VIEW_DIFF) &&
3197 view->parent == VIEW(REQ_VIEW_MAIN)) ||
3198 (view == VIEW(REQ_VIEW_DIFF) &&
3199 view->parent == VIEW(REQ_VIEW_BLAME)) ||
3200 (view == VIEW(REQ_VIEW_STAGE) &&
3201 view->parent == VIEW(REQ_VIEW_STATUS)) ||
3202 (view == VIEW(REQ_VIEW_BLOB) &&
3203 view->parent == VIEW(REQ_VIEW_TREE))) {
3206 view = view->parent;
3207 line = view->lineno;
3208 move_view(view, request);
3209 if (view_is_displayed(view))
3210 update_view_title(view);
3211 if (line != view->lineno)
3212 view->ops->request(view, REQ_ENTER,
3213 &view->line[view->lineno]);
3216 move_view(view, request);
3222 int nviews = displayed_views();
3223 int next_view = (current_view + 1) % nviews;
3225 if (next_view == current_view) {
3226 report("Only one view is displayed");
3230 current_view = next_view;
3231 /* Blur out the title of the previous view. */
3232 update_view_title(view);
3237 report("Refreshing is not yet supported for the %s view", view->name);
3241 if (displayed_views() == 2)
3242 open_view(view, VIEW_REQ(view), OPEN_DEFAULT);
3245 case REQ_TOGGLE_LINENO:
3246 toggle_view_option(&opt_line_number, "line numbers");
3249 case REQ_TOGGLE_DATE:
3250 toggle_view_option(&opt_date, "date display");
3253 case REQ_TOGGLE_AUTHOR:
3254 toggle_view_option(&opt_author, "author display");
3257 case REQ_TOGGLE_REV_GRAPH:
3258 toggle_view_option(&opt_rev_graph, "revision graph display");
3261 case REQ_TOGGLE_REFS:
3262 toggle_view_option(&opt_show_refs, "reference display");
3266 case REQ_SEARCH_BACK:
3267 search_view(view, request);
3272 find_next(view, request);
3275 case REQ_STOP_LOADING:
3276 for (i = 0; i < ARRAY_SIZE(views); i++) {
3279 report("Stopped loading the %s view", view->name),
3280 end_update(view, TRUE);
3284 case REQ_SHOW_VERSION:
3285 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3288 case REQ_SCREEN_REDRAW:
3289 redraw_display(TRUE);
3293 report("Nothing to edit");
3297 report("Nothing to enter");
3300 case REQ_VIEW_CLOSE:
3301 /* XXX: Mark closed views by letting view->parent point to the
3302 * view itself. Parents to closed view should never be
3305 view->parent->parent != view->parent) {
3306 memset(display, 0, sizeof(display));
3308 display[current_view] = view->parent;
3309 view->parent = view;
3311 redraw_display(FALSE);
3320 report("Unknown key, press 'h' for help");
3329 * View backend utilities
3333 parse_timezone(time_t *time, const char *zone)
3337 tz = ('0' - zone[1]) * 60 * 60 * 10;
3338 tz += ('0' - zone[2]) * 60 * 60;
3339 tz += ('0' - zone[3]) * 60;
3340 tz += ('0' - zone[4]);
3348 /* Parse author lines where the name may be empty:
3349 * author <email@address.tld> 1138474660 +0100
3352 parse_author_line(char *ident, char *author, size_t authorsize, struct tm *tm)
3354 char *nameend = strchr(ident, '<');
3355 char *emailend = strchr(ident, '>');
3357 if (nameend && emailend)
3358 *nameend = *emailend = 0;
3359 ident = chomp_string(ident);
3362 ident = chomp_string(nameend + 1);
3367 string_ncopy_do(author, authorsize, ident, strlen(ident));
3369 /* Parse epoch and timezone */
3370 if (emailend && emailend[1] == ' ') {
3371 char *secs = emailend + 2;
3372 char *zone = strchr(secs, ' ');
3373 time_t time = (time_t) atol(secs);
3375 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
3376 parse_timezone(&time, zone + 1);
3378 gmtime_r(&time, tm);
3382 static enum input_status
3383 select_commit_parent_handler(void *data, char *buf, int c)
3385 size_t parents = *(size_t *) data;
3392 parent = atoi(buf) * 10;
3395 if (parent > parents)
3401 select_commit_parent(const char *id, char rev[SIZEOF_REV])
3403 char buf[SIZEOF_STR * 4];
3404 const char *revlist_argv[] = {
3405 "git", "rev-list", "-1", "--parents", id, NULL
3409 if (!run_io_buf(revlist_argv, buf, sizeof(buf)) ||
3410 !*chomp_string(buf) ||
3411 (parents = (strlen(buf) / 40) - 1) < 0) {
3412 report("Failed to get parent information");
3415 } else if (parents == 0) {
3416 report("The selected commit has no parents");
3421 char prompt[SIZEOF_STR];
3424 if (!string_format(prompt, "Which parent? [1..%d] ", parents))
3426 result = prompt_input(prompt, select_commit_parent_handler, &parents);
3429 parents = atoi(result);
3432 string_copy_rev(rev, &buf[41 * parents]);
3441 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3443 char text[SIZEOF_STR];
3445 if (opt_line_number && draw_lineno(view, lineno))
3448 string_expand(text, sizeof(text), line->data, opt_tab_size);
3449 draw_text(view, line->type, text, TRUE);
3454 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3456 const char *describe_argv[] = { "git", "describe", commit_id, NULL };
3457 char refbuf[SIZEOF_STR];
3460 if (run_io_buf(describe_argv, refbuf, sizeof(refbuf)))
3461 ref = chomp_string(refbuf);
3466 /* This is the only fatal call, since it can "corrupt" the buffer. */
3467 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
3474 add_pager_refs(struct view *view, struct line *line)
3476 char buf[SIZEOF_STR];
3477 char *commit_id = (char *)line->data + STRING_SIZE("commit ");
3479 size_t bufpos = 0, refpos = 0;
3480 const char *sep = "Refs: ";
3481 bool is_tag = FALSE;
3483 assert(line->type == LINE_COMMIT);
3485 refs = get_refs(commit_id);
3487 if (view == VIEW(REQ_VIEW_DIFF))
3488 goto try_add_describe_ref;
3493 struct ref *ref = refs[refpos];
3494 const char *fmt = ref->tag ? "%s[%s]" :
3495 ref->remote ? "%s<%s>" : "%s%s";
3497 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
3502 } while (refs[refpos++]->next);
3504 if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
3505 try_add_describe_ref:
3506 /* Add <tag>-g<commit_id> "fake" reference. */
3507 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
3514 add_line_text(view, buf, LINE_PP_REFS);
3518 pager_read(struct view *view, char *data)
3525 line = add_line_text(view, data, get_line_type(data));
3529 if (line->type == LINE_COMMIT &&
3530 (view == VIEW(REQ_VIEW_DIFF) ||
3531 view == VIEW(REQ_VIEW_LOG)))
3532 add_pager_refs(view, line);
3538 pager_request(struct view *view, enum request request, struct line *line)
3542 if (request != REQ_ENTER)
3545 if (line->type == LINE_COMMIT &&
3546 (view == VIEW(REQ_VIEW_LOG) ||
3547 view == VIEW(REQ_VIEW_PAGER))) {
3548 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3552 /* Always scroll the view even if it was split. That way
3553 * you can use Enter to scroll through the log view and
3554 * split open each commit diff. */
3555 scroll_view(view, REQ_SCROLL_LINE_DOWN);
3557 /* FIXME: A minor workaround. Scrolling the view will call report("")
3558 * but if we are scrolling a non-current view this won't properly
3559 * update the view title. */
3561 update_view_title(view);
3567 pager_grep(struct view *view, struct line *line)
3570 char *text = line->data;
3575 if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
3582 pager_select(struct view *view, struct line *line)
3584 if (line->type == LINE_COMMIT) {
3585 char *text = (char *)line->data + STRING_SIZE("commit ");
3587 if (view != VIEW(REQ_VIEW_PAGER))
3588 string_copy_rev(view->ref, text);
3589 string_copy_rev(ref_commit, text);
3593 static struct view_ops pager_ops = {
3604 static const char *log_argv[SIZEOF_ARG] = {
3605 "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3609 log_request(struct view *view, enum request request, struct line *line)
3614 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
3617 return pager_request(view, request, line);
3621 static struct view_ops log_ops = {
3632 static const char *diff_argv[SIZEOF_ARG] = {
3633 "git", "show", "--pretty=fuller", "--no-color", "--root",
3634 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
3637 static struct view_ops diff_ops = {
3653 help_open(struct view *view)
3655 char buf[SIZEOF_STR];
3659 if (view->lines > 0)
3662 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
3664 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
3667 if (req_info[i].request == REQ_NONE)
3670 if (!req_info[i].request) {
3671 add_line_text(view, "", LINE_DEFAULT);
3672 add_line_text(view, req_info[i].help, LINE_DEFAULT);
3676 key = get_key(req_info[i].request);
3678 key = "(no key defined)";
3680 for (bufpos = 0; bufpos <= req_info[i].namelen; bufpos++) {
3681 buf[bufpos] = tolower(req_info[i].name[bufpos]);
3682 if (buf[bufpos] == '_')
3686 add_line_format(view, LINE_DEFAULT, " %-25s %-20s %s",
3687 key, buf, req_info[i].help);
3691 add_line_text(view, "", LINE_DEFAULT);
3692 add_line_text(view, "External commands:", LINE_DEFAULT);
3695 for (i = 0; i < run_requests; i++) {
3696 struct run_request *req = get_run_request(REQ_NONE + i + 1);
3703 key = get_key_name(req->key);
3705 key = "(no key defined)";
3707 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
3708 if (!string_format_from(buf, &bufpos, "%s%s",
3709 argc ? " " : "", req->argv[argc]))
3712 add_line_format(view, LINE_DEFAULT, " %-10s %-14s `%s`",
3713 keymap_table[req->keymap].name, key, buf);
3719 static struct view_ops help_ops = {
3735 struct tree_stack_entry {
3736 struct tree_stack_entry *prev; /* Entry below this in the stack */
3737 unsigned long lineno; /* Line number to restore */
3738 char *name; /* Position of name in opt_path */
3741 /* The top of the path stack. */
3742 static struct tree_stack_entry *tree_stack = NULL;
3743 unsigned long tree_lineno = 0;
3746 pop_tree_stack_entry(void)
3748 struct tree_stack_entry *entry = tree_stack;
3750 tree_lineno = entry->lineno;
3752 tree_stack = entry->prev;
3757 push_tree_stack_entry(const char *name, unsigned long lineno)
3759 struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
3760 size_t pathlen = strlen(opt_path);
3765 entry->prev = tree_stack;
3766 entry->name = opt_path + pathlen;
3769 if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
3770 pop_tree_stack_entry();
3774 /* Move the current line to the first tree entry. */
3776 entry->lineno = lineno;
3779 /* Parse output from git-ls-tree(1):
3781 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
3784 #define SIZEOF_TREE_ATTR \
3785 STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
3787 #define SIZEOF_TREE_MODE \
3788 STRING_SIZE("100644 ")
3790 #define TREE_ID_OFFSET \
3791 STRING_SIZE("100644 blob ")
3794 char id[SIZEOF_REV];
3796 struct tm time; /* Date from the author ident. */
3797 char author[75]; /* Author of the commit. */
3802 tree_path(struct line *line)
3804 return ((struct tree_entry *) line->data)->name;
3809 tree_compare_entry(struct line *line1, struct line *line2)
3811 if (line1->type != line2->type)
3812 return line1->type == LINE_TREE_DIR ? -1 : 1;
3813 return strcmp(tree_path(line1), tree_path(line2));
3816 static struct line *
3817 tree_entry(struct view *view, enum line_type type, const char *path,
3818 const char *mode, const char *id)
3820 struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
3821 struct line *line = entry ? add_line_data(view, entry, type) : NULL;
3823 if (!entry || !line) {
3828 strncpy(entry->name, path, strlen(path));
3830 entry->mode = strtoul(mode, NULL, 8);
3832 string_copy_rev(entry->id, id);
3838 tree_read_date(struct view *view, char *text, bool *read_date)
3840 static char author_name[SIZEOF_STR];
3841 static struct tm author_time;
3843 if (!text && *read_date) {
3848 char *path = *opt_path ? opt_path : ".";
3849 /* Find next entry to process */
3850 const char *log_file[] = {
3851 "git", "log", "--no-color", "--pretty=raw",
3852 "--cc", "--raw", view->id, "--", path, NULL
3857 tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
3858 report("Tree is empty");
3862 if (!run_io_rd(&io, log_file, FORMAT_NONE)) {
3863 report("Failed to load tree data");
3867 done_io(view->pipe);
3872 } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
3873 parse_author_line(text + STRING_SIZE("author "),
3874 author_name, sizeof(author_name), &author_time);
3876 } else if (*text == ':') {
3878 size_t annotated = 1;
3881 pos = strchr(text, '\t');
3885 if (*opt_prefix && !strncmp(text, opt_prefix, strlen(opt_prefix)))
3886 text += strlen(opt_prefix);
3887 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
3888 text += strlen(opt_path);
3889 pos = strchr(text, '/');
3893 for (i = 1; i < view->lines; i++) {
3894 struct line *line = &view->line[i];
3895 struct tree_entry *entry = line->data;
3897 annotated += !!*entry->author;
3898 if (*entry->author || strcmp(entry->name, text))
3901 string_copy(entry->author, author_name);
3902 memcpy(&entry->time, &author_time, sizeof(entry->time));
3907 if (annotated == view->lines)
3908 kill_io(view->pipe);
3914 tree_read(struct view *view, char *text)
3916 static bool read_date = FALSE;
3917 struct tree_entry *data;
3918 struct line *entry, *line;
3919 enum line_type type;
3920 size_t textlen = text ? strlen(text) : 0;
3921 char *path = text + SIZEOF_TREE_ATTR;
3923 if (read_date || !text)
3924 return tree_read_date(view, text, &read_date);
3926 if (textlen <= SIZEOF_TREE_ATTR)
3928 if (view->lines == 0 &&
3929 !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
3932 /* Strip the path part ... */
3934 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
3935 size_t striplen = strlen(opt_path);
3937 if (pathlen > striplen)
3938 memmove(path, path + striplen,
3939 pathlen - striplen + 1);
3941 /* Insert "link" to parent directory. */
3942 if (view->lines == 1 &&
3943 !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
3947 type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
3948 entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
3953 /* Skip "Directory ..." and ".." line. */
3954 for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
3955 if (tree_compare_entry(line, entry) <= 0)
3958 memmove(line + 1, line, (entry - line) * sizeof(*entry));
3962 for (; line <= entry; line++)
3963 line->dirty = line->cleareol = 1;
3967 if (tree_lineno > view->lineno) {
3968 view->lineno = tree_lineno;
3976 tree_draw(struct view *view, struct line *line, unsigned int lineno)
3978 struct tree_entry *entry = line->data;
3980 if (line->type == LINE_TREE_HEAD) {
3981 if (draw_text(view, line->type, "Directory path /", TRUE))
3984 if (draw_mode(view, entry->mode))
3987 if (opt_author && draw_author(view, entry->author))
3990 if (opt_date && draw_date(view, *entry->author ? &entry->time : NULL))
3993 if (draw_text(view, line->type, entry->name, TRUE))
4001 char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4002 int fd = mkstemp(file);
4005 report("Failed to create temporary file");
4006 else if (!run_io_append(blob_ops.argv, FORMAT_ALL, fd))
4007 report("Failed to save blob data to file");
4009 open_editor(FALSE, file);
4015 tree_request(struct view *view, enum request request, struct line *line)
4017 enum open_flags flags;
4020 case REQ_VIEW_BLAME:
4021 if (line->type != LINE_TREE_FILE) {
4022 report("Blame only supported for files");
4026 string_copy(opt_ref, view->vid);
4030 if (line->type != LINE_TREE_FILE) {
4031 report("Edit only supported for files");
4032 } else if (!is_head_commit(view->vid)) {
4035 open_editor(TRUE, opt_file);
4041 /* quit view if at top of tree */
4042 return REQ_VIEW_CLOSE;
4045 line = &view->line[1];
4055 /* Cleanup the stack if the tree view is at a different tree. */
4056 while (!*opt_path && tree_stack)
4057 pop_tree_stack_entry();
4059 switch (line->type) {
4061 /* Depending on whether it is a subdirectory or parent link
4062 * mangle the path buffer. */
4063 if (line == &view->line[1] && *opt_path) {
4064 pop_tree_stack_entry();
4067 const char *basename = tree_path(line);
4069 push_tree_stack_entry(basename, view->lineno);
4072 /* Trees and subtrees share the same ID, so they are not not
4073 * unique like blobs. */
4074 flags = OPEN_RELOAD;
4075 request = REQ_VIEW_TREE;
4078 case LINE_TREE_FILE:
4079 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4080 request = REQ_VIEW_BLOB;
4087 open_view(view, request, flags);
4088 if (request == REQ_VIEW_TREE)
4089 view->lineno = tree_lineno;
4095 tree_select(struct view *view, struct line *line)
4097 struct tree_entry *entry = line->data;
4099 if (line->type == LINE_TREE_FILE) {
4100 string_copy_rev(ref_blob, entry->id);
4101 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4103 } else if (line->type != LINE_TREE_DIR) {
4107 string_copy_rev(view->ref, entry->id);
4110 static const char *tree_argv[SIZEOF_ARG] = {
4111 "git", "ls-tree", "%(commit)", "%(directory)", NULL
4114 static struct view_ops tree_ops = {
4126 blob_read(struct view *view, char *line)
4130 return add_line_text(view, line, LINE_DEFAULT) != NULL;
4134 blob_request(struct view *view, enum request request, struct line *line)
4141 return pager_request(view, request, line);
4145 static const char *blob_argv[SIZEOF_ARG] = {
4146 "git", "cat-file", "blob", "%(blob)", NULL
4149 static struct view_ops blob_ops = {
4163 * Loading the blame view is a two phase job:
4165 * 1. File content is read either using opt_file from the
4166 * filesystem or using git-cat-file.
4167 * 2. Then blame information is incrementally added by
4168 * reading output from git-blame.
4171 static const char *blame_head_argv[] = {
4172 "git", "blame", "--incremental", "--", "%(file)", NULL
4175 static const char *blame_ref_argv[] = {
4176 "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4179 static const char *blame_cat_file_argv[] = {
4180 "git", "cat-file", "blob", "%(ref):%(file)", NULL
4183 struct blame_commit {
4184 char id[SIZEOF_REV]; /* SHA1 ID. */
4185 char title[128]; /* First line of the commit message. */
4186 char author[75]; /* Author of the commit. */
4187 struct tm time; /* Date from the author ident. */
4188 char filename[128]; /* Name of file. */
4189 bool has_previous; /* Was a "previous" line detected. */
4193 struct blame_commit *commit;
4198 blame_open(struct view *view)
4200 if (*opt_ref || !io_open(&view->io, opt_file)) {
4201 if (!run_io_rd(&view->io, blame_cat_file_argv, FORMAT_ALL))
4205 setup_update(view, opt_file);
4206 string_format(view->ref, "%s ...", opt_file);
4211 static struct blame_commit *
4212 get_blame_commit(struct view *view, const char *id)
4216 for (i = 0; i < view->lines; i++) {
4217 struct blame *blame = view->line[i].data;
4222 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4223 return blame->commit;
4227 struct blame_commit *commit = calloc(1, sizeof(*commit));
4230 string_ncopy(commit->id, id, SIZEOF_REV);
4236 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4238 const char *pos = *posref;
4241 pos = strchr(pos + 1, ' ');
4242 if (!pos || !isdigit(pos[1]))
4244 *number = atoi(pos + 1);
4245 if (*number < min || *number > max)
4252 static struct blame_commit *
4253 parse_blame_commit(struct view *view, const char *text, int *blamed)
4255 struct blame_commit *commit;
4256 struct blame *blame;
4257 const char *pos = text + SIZEOF_REV - 1;
4261 if (strlen(text) <= SIZEOF_REV || *pos != ' ')
4264 if (!parse_number(&pos, &lineno, 1, view->lines) ||
4265 !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4268 commit = get_blame_commit(view, text);
4274 struct line *line = &view->line[lineno + group - 1];
4277 blame->commit = commit;
4285 blame_read_file(struct view *view, const char *line, bool *read_file)
4288 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
4291 if (view->lines == 0 && !view->parent)
4292 die("No blame exist for %s", view->vid);
4294 if (view->lines == 0 || !run_io_rd(&io, argv, FORMAT_ALL)) {
4295 report("Failed to load blame data");
4299 done_io(view->pipe);
4305 size_t linelen = string_expand_length(line, opt_tab_size);
4306 struct blame *blame = malloc(sizeof(*blame) + linelen);
4311 blame->commit = NULL;
4312 string_expand(blame->text, linelen + 1, line, opt_tab_size);
4313 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
4318 match_blame_header(const char *name, char **line)
4320 size_t namelen = strlen(name);
4321 bool matched = !strncmp(name, *line, namelen);
4330 blame_read(struct view *view, char *line)
4332 static struct blame_commit *commit = NULL;
4333 static int blamed = 0;
4334 static time_t author_time;
4335 static bool read_file = TRUE;
4338 return blame_read_file(view, line, &read_file);
4345 string_format(view->ref, "%s", view->vid);
4346 if (view_is_displayed(view)) {
4347 update_view_title(view);
4348 redraw_view_from(view, 0);
4354 commit = parse_blame_commit(view, line, &blamed);
4355 string_format(view->ref, "%s %2d%%", view->vid,
4356 view->lines ? blamed * 100 / view->lines : 0);
4358 } else if (match_blame_header("author ", &line)) {
4359 string_ncopy(commit->author, line, strlen(line));
4361 } else if (match_blame_header("author-time ", &line)) {
4362 author_time = (time_t) atol(line);
4364 } else if (match_blame_header("author-tz ", &line)) {
4365 parse_timezone(&author_time, line);
4366 gmtime_r(&author_time, &commit->time);
4368 } else if (match_blame_header("summary ", &line)) {
4369 string_ncopy(commit->title, line, strlen(line));
4371 } else if (match_blame_header("previous ", &line)) {
4372 commit->has_previous = TRUE;
4374 } else if (match_blame_header("filename ", &line)) {
4375 string_ncopy(commit->filename, line, strlen(line));
4383 blame_draw(struct view *view, struct line *line, unsigned int lineno)
4385 struct blame *blame = line->data;
4386 struct tm *time = NULL;
4387 const char *id = NULL, *author = NULL;
4389 if (blame->commit && *blame->commit->filename) {
4390 id = blame->commit->id;
4391 author = blame->commit->author;
4392 time = &blame->commit->time;
4395 if (opt_date && draw_date(view, time))
4398 if (opt_author && draw_author(view, author))
4401 if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
4404 if (draw_lineno(view, lineno))
4407 draw_text(view, LINE_DEFAULT, blame->text, TRUE);
4412 check_blame_commit(struct blame *blame)
4415 report("Commit data not loaded yet");
4416 else if (!strcmp(blame->commit->id, NULL_ID))
4417 report("No commit exist for the selected line");
4424 blame_request(struct view *view, enum request request, struct line *line)
4426 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4427 struct blame *blame = line->data;
4430 case REQ_VIEW_BLAME:
4431 if (check_blame_commit(blame)) {
4432 string_copy(opt_ref, blame->commit->id);
4433 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4438 if (check_blame_commit(blame) &&
4439 select_commit_parent(blame->commit->id, opt_ref))
4440 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4444 if (!blame->commit) {
4445 report("No commit loaded yet");
4449 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
4450 !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
4453 if (!strcmp(blame->commit->id, NULL_ID)) {
4454 struct view *diff = VIEW(REQ_VIEW_DIFF);
4455 const char *diff_index_argv[] = {
4456 "git", "diff-index", "--root", "--patch-with-stat",
4457 "-C", "-M", "HEAD", "--", view->vid, NULL
4460 if (!blame->commit->has_previous) {
4461 diff_index_argv[1] = "diff";
4462 diff_index_argv[2] = "--no-color";
4463 diff_index_argv[6] = "--";
4464 diff_index_argv[7] = "/dev/null";
4467 if (!prepare_update(diff, diff_index_argv, NULL, FORMAT_DASH)) {
4468 report("Failed to allocate diff command");
4471 flags |= OPEN_PREPARED;
4474 open_view(view, REQ_VIEW_DIFF, flags);
4475 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
4476 string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
4487 blame_grep(struct view *view, struct line *line)
4489 struct blame *blame = line->data;
4490 struct blame_commit *commit = blame->commit;
4493 #define MATCH(text, on) \
4494 (on && *text && regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4497 char buf[DATE_COLS + 1];
4499 if (MATCH(commit->title, 1) ||
4500 MATCH(commit->author, opt_author) ||
4501 MATCH(commit->id, opt_date))
4504 if (strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time) &&
4509 return MATCH(blame->text, 1);
4515 blame_select(struct view *view, struct line *line)
4517 struct blame *blame = line->data;
4518 struct blame_commit *commit = blame->commit;
4523 if (!strcmp(commit->id, NULL_ID))
4524 string_ncopy(ref_commit, "HEAD", 4);
4526 string_copy_rev(ref_commit, commit->id);
4529 static struct view_ops blame_ops = {
4548 char rev[SIZEOF_REV];
4549 char name[SIZEOF_STR];
4553 char rev[SIZEOF_REV];
4554 char name[SIZEOF_STR];
4558 static char status_onbranch[SIZEOF_STR];
4559 static struct status stage_status;
4560 static enum line_type stage_line_type;
4561 static size_t stage_chunks;
4562 static int *stage_chunk;
4564 /* This should work even for the "On branch" line. */
4566 status_has_none(struct view *view, struct line *line)
4568 return line < view->line + view->lines && !line[1].data;
4571 /* Get fields from the diff line:
4572 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
4575 status_get_diff(struct status *file, const char *buf, size_t bufsize)
4577 const char *old_mode = buf + 1;
4578 const char *new_mode = buf + 8;
4579 const char *old_rev = buf + 15;
4580 const char *new_rev = buf + 56;
4581 const char *status = buf + 97;
4584 old_mode[-1] != ':' ||
4585 new_mode[-1] != ' ' ||
4586 old_rev[-1] != ' ' ||
4587 new_rev[-1] != ' ' ||
4591 file->status = *status;
4593 string_copy_rev(file->old.rev, old_rev);
4594 string_copy_rev(file->new.rev, new_rev);
4596 file->old.mode = strtoul(old_mode, NULL, 8);
4597 file->new.mode = strtoul(new_mode, NULL, 8);
4599 file->old.name[0] = file->new.name[0] = 0;
4605 status_run(struct view *view, const char *argv[], char status, enum line_type type)
4607 struct status *unmerged = NULL;
4611 if (!run_io(&io, argv, NULL, IO_RD))
4614 add_line_data(view, NULL, type);
4616 while ((buf = io_get(&io, 0, TRUE))) {
4617 struct status *file = unmerged;
4620 file = calloc(1, sizeof(*file));
4621 if (!file || !add_line_data(view, file, type))
4625 /* Parse diff info part. */
4627 file->status = status;
4629 string_copy(file->old.rev, NULL_ID);
4631 } else if (!file->status || file == unmerged) {
4632 if (!status_get_diff(file, buf, strlen(buf)))
4635 buf = io_get(&io, 0, TRUE);
4639 /* Collapse all modified entries that follow an
4640 * associated unmerged entry. */
4641 if (unmerged == file) {
4642 unmerged->status = 'U';
4644 } else if (file->status == 'U') {
4649 /* Grab the old name for rename/copy. */
4650 if (!*file->old.name &&
4651 (file->status == 'R' || file->status == 'C')) {
4652 string_ncopy(file->old.name, buf, strlen(buf));
4654 buf = io_get(&io, 0, TRUE);
4659 /* git-ls-files just delivers a NUL separated list of
4660 * file names similar to the second half of the
4661 * git-diff-* output. */
4662 string_ncopy(file->new.name, buf, strlen(buf));
4663 if (!*file->old.name)
4664 string_copy(file->old.name, file->new.name);
4668 if (io_error(&io)) {
4674 if (!view->line[view->lines - 1].data)
4675 add_line_data(view, NULL, LINE_STAT_NONE);
4681 /* Don't show unmerged entries in the staged section. */
4682 static const char *status_diff_index_argv[] = {
4683 "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
4684 "--cached", "-M", "HEAD", NULL
4687 static const char *status_diff_files_argv[] = {
4688 "git", "diff-files", "-z", NULL
4691 static const char *status_list_other_argv[] = {
4692 "git", "ls-files", "-z", "--others", "--exclude-standard", NULL
4695 static const char *status_list_no_head_argv[] = {
4696 "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
4699 static const char *update_index_argv[] = {
4700 "git", "update-index", "-q", "--unmerged", "--refresh", NULL
4703 /* Restore the previous line number to stay in the context or select a
4704 * line with something that can be updated. */
4706 status_restore(struct view *view)
4708 if (view->p_lineno >= view->lines)
4709 view->p_lineno = view->lines - 1;
4710 while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
4712 while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
4715 /* If the above fails, always skip the "On branch" line. */
4716 if (view->p_lineno < view->lines)
4717 view->lineno = view->p_lineno;
4721 if (view->lineno < view->offset)
4722 view->offset = view->lineno;
4723 else if (view->offset + view->height <= view->lineno)
4724 view->offset = view->lineno - view->height + 1;
4726 view->p_restore = FALSE;
4730 status_update_onbranch(void)
4732 static const char *paths[][2] = {
4733 { "rebase-apply/rebasing", "Rebasing" },
4734 { "rebase-apply/applying", "Applying mailbox" },
4735 { "rebase-apply/", "Rebasing mailbox" },
4736 { "rebase-merge/interactive", "Interactive rebase" },
4737 { "rebase-merge/", "Rebase merge" },
4738 { "MERGE_HEAD", "Merging" },
4739 { "BISECT_LOG", "Bisecting" },
4740 { "HEAD", "On branch" },
4742 char buf[SIZEOF_STR];
4746 if (is_initial_commit()) {
4747 string_copy(status_onbranch, "Initial commit");
4751 for (i = 0; i < ARRAY_SIZE(paths); i++) {
4752 char *head = opt_head;
4754 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
4755 lstat(buf, &stat) < 0)
4761 if (string_format(buf, "%s/rebase-merge/head-name", opt_git_dir) &&
4762 io_open(&io, buf) &&
4763 io_read_buf(&io, buf, sizeof(buf))) {
4764 head = chomp_string(buf);
4765 if (!prefixcmp(head, "refs/heads/"))
4766 head += STRING_SIZE("refs/heads/");
4770 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
4771 string_copy(status_onbranch, opt_head);
4775 string_copy(status_onbranch, "Not currently on any branch");
4778 /* First parse staged info using git-diff-index(1), then parse unstaged
4779 * info using git-diff-files(1), and finally untracked files using
4780 * git-ls-files(1). */
4782 status_open(struct view *view)
4786 add_line_data(view, NULL, LINE_STAT_HEAD);
4787 status_update_onbranch();
4789 run_io_bg(update_index_argv);
4791 if (is_initial_commit()) {
4792 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
4794 } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
4798 if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
4799 !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
4802 /* Restore the exact position or use the specialized restore
4804 if (!view->p_restore)
4805 status_restore(view);
4810 status_draw(struct view *view, struct line *line, unsigned int lineno)
4812 struct status *status = line->data;
4813 enum line_type type;
4817 switch (line->type) {
4818 case LINE_STAT_STAGED:
4819 type = LINE_STAT_SECTION;
4820 text = "Changes to be committed:";
4823 case LINE_STAT_UNSTAGED:
4824 type = LINE_STAT_SECTION;
4825 text = "Changed but not updated:";
4828 case LINE_STAT_UNTRACKED:
4829 type = LINE_STAT_SECTION;
4830 text = "Untracked files:";
4833 case LINE_STAT_NONE:
4834 type = LINE_DEFAULT;
4835 text = " (no files)";
4838 case LINE_STAT_HEAD:
4839 type = LINE_STAT_HEAD;
4840 text = status_onbranch;
4847 static char buf[] = { '?', ' ', ' ', ' ', 0 };
4849 buf[0] = status->status;
4850 if (draw_text(view, line->type, buf, TRUE))
4852 type = LINE_DEFAULT;
4853 text = status->new.name;
4856 draw_text(view, type, text, TRUE);
4861 status_enter(struct view *view, struct line *line)
4863 struct status *status = line->data;
4864 const char *oldpath = status ? status->old.name : NULL;
4865 /* Diffs for unmerged entries are empty when passing the new
4866 * path, so leave it empty. */
4867 const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
4869 enum open_flags split;
4870 struct view *stage = VIEW(REQ_VIEW_STAGE);
4872 if (line->type == LINE_STAT_NONE ||
4873 (!status && line[1].type == LINE_STAT_NONE)) {
4874 report("No file to diff");
4878 switch (line->type) {
4879 case LINE_STAT_STAGED:
4880 if (is_initial_commit()) {
4881 const char *no_head_diff_argv[] = {
4882 "git", "diff", "--no-color", "--patch-with-stat",
4883 "--", "/dev/null", newpath, NULL
4886 if (!prepare_update(stage, no_head_diff_argv, opt_cdup, FORMAT_DASH))
4889 const char *index_show_argv[] = {
4890 "git", "diff-index", "--root", "--patch-with-stat",
4891 "-C", "-M", "--cached", "HEAD", "--",
4892 oldpath, newpath, NULL
4895 if (!prepare_update(stage, index_show_argv, opt_cdup, FORMAT_DASH))
4900 info = "Staged changes to %s";
4902 info = "Staged changes";
4905 case LINE_STAT_UNSTAGED:
4907 const char *files_show_argv[] = {
4908 "git", "diff-files", "--root", "--patch-with-stat",
4909 "-C", "-M", "--", oldpath, newpath, NULL
4912 if (!prepare_update(stage, files_show_argv, opt_cdup, FORMAT_DASH))
4915 info = "Unstaged changes to %s";
4917 info = "Unstaged changes";
4920 case LINE_STAT_UNTRACKED:
4922 report("No file to show");
4926 if (!suffixcmp(status->new.name, -1, "/")) {
4927 report("Cannot display a directory");
4931 if (!prepare_update_file(stage, newpath))
4933 info = "Untracked file %s";
4936 case LINE_STAT_HEAD:
4940 die("line type %d not handled in switch", line->type);
4943 split = view_is_displayed(view) ? OPEN_SPLIT : 0;
4944 open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
4945 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
4947 stage_status = *status;
4949 memset(&stage_status, 0, sizeof(stage_status));
4952 stage_line_type = line->type;
4954 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
4961 status_exists(struct status *status, enum line_type type)
4963 struct view *view = VIEW(REQ_VIEW_STATUS);
4964 unsigned long lineno;
4966 for (lineno = 0; lineno < view->lines; lineno++) {
4967 struct line *line = &view->line[lineno];
4968 struct status *pos = line->data;
4970 if (line->type != type)
4972 if (!pos && (!status || !status->status) && line[1].data) {
4973 select_view_line(view, lineno);
4976 if (pos && !strcmp(status->new.name, pos->new.name)) {
4977 select_view_line(view, lineno);
4987 status_update_prepare(struct io *io, enum line_type type)
4989 const char *staged_argv[] = {
4990 "git", "update-index", "-z", "--index-info", NULL
4992 const char *others_argv[] = {
4993 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
4997 case LINE_STAT_STAGED:
4998 return run_io(io, staged_argv, opt_cdup, IO_WR);
5000 case LINE_STAT_UNSTAGED:
5001 return run_io(io, others_argv, opt_cdup, IO_WR);
5003 case LINE_STAT_UNTRACKED:
5004 return run_io(io, others_argv, NULL, IO_WR);
5007 die("line type %d not handled in switch", type);
5013 status_update_write(struct io *io, struct status *status, enum line_type type)
5015 char buf[SIZEOF_STR];
5019 case LINE_STAT_STAGED:
5020 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
5023 status->old.name, 0))
5027 case LINE_STAT_UNSTAGED:
5028 case LINE_STAT_UNTRACKED:
5029 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
5034 die("line type %d not handled in switch", type);
5037 return io_write(io, buf, bufsize);
5041 status_update_file(struct status *status, enum line_type type)
5046 if (!status_update_prepare(&io, type))
5049 result = status_update_write(&io, status, type);
5055 status_update_files(struct view *view, struct line *line)
5059 struct line *pos = view->line + view->lines;
5063 if (!status_update_prepare(&io, line->type))
5066 for (pos = line; pos < view->line + view->lines && pos->data; pos++)
5069 for (file = 0, done = 0; result && file < files; line++, file++) {
5070 int almost_done = file * 100 / files;
5072 if (almost_done > done) {
5074 string_format(view->ref, "updating file %u of %u (%d%% done)",
5076 update_view_title(view);
5078 result = status_update_write(&io, line->data, line->type);
5086 status_update(struct view *view)
5088 struct line *line = &view->line[view->lineno];
5090 assert(view->lines);
5093 /* This should work even for the "On branch" line. */
5094 if (line < view->line + view->lines && !line[1].data) {
5095 report("Nothing to update");
5099 if (!status_update_files(view, line + 1)) {
5100 report("Failed to update file status");
5104 } else if (!status_update_file(line->data, line->type)) {
5105 report("Failed to update file status");
5113 status_revert(struct status *status, enum line_type type, bool has_none)
5115 if (!status || type != LINE_STAT_UNSTAGED) {
5116 if (type == LINE_STAT_STAGED) {
5117 report("Cannot revert changes to staged files");
5118 } else if (type == LINE_STAT_UNTRACKED) {
5119 report("Cannot revert changes to untracked files");
5120 } else if (has_none) {
5121 report("Nothing to revert");
5123 report("Cannot revert changes to multiple files");
5128 char mode[10] = "100644";
5129 const char *reset_argv[] = {
5130 "git", "update-index", "--cacheinfo", mode,
5131 status->old.rev, status->old.name, NULL
5133 const char *checkout_argv[] = {
5134 "git", "checkout", "--", status->old.name, NULL
5137 if (!prompt_yesno("Are you sure you want to overwrite any changes?"))
5139 string_format(mode, "%o", status->old.mode);
5140 return (status->status != 'U' || run_io_fg(reset_argv, opt_cdup)) &&
5141 run_io_fg(checkout_argv, opt_cdup);
5146 status_request(struct view *view, enum request request, struct line *line)
5148 struct status *status = line->data;
5151 case REQ_STATUS_UPDATE:
5152 if (!status_update(view))
5156 case REQ_STATUS_REVERT:
5157 if (!status_revert(status, line->type, status_has_none(view, line)))
5161 case REQ_STATUS_MERGE:
5162 if (!status || status->status != 'U') {
5163 report("Merging only possible for files with unmerged status ('U').");
5166 open_mergetool(status->new.name);
5172 if (status->status == 'D') {
5173 report("File has been deleted.");
5177 open_editor(status->status != '?', status->new.name);
5180 case REQ_VIEW_BLAME:
5182 string_copy(opt_file, status->new.name);
5188 /* After returning the status view has been split to
5189 * show the stage view. No further reloading is
5191 status_enter(view, line);
5195 /* Simply reload the view. */
5202 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
5208 status_select(struct view *view, struct line *line)
5210 struct status *status = line->data;
5211 char file[SIZEOF_STR] = "all files";
5215 if (status && !string_format(file, "'%s'", status->new.name))
5218 if (!status && line[1].type == LINE_STAT_NONE)
5221 switch (line->type) {
5222 case LINE_STAT_STAGED:
5223 text = "Press %s to unstage %s for commit";
5226 case LINE_STAT_UNSTAGED:
5227 text = "Press %s to stage %s for commit";
5230 case LINE_STAT_UNTRACKED:
5231 text = "Press %s to stage %s for addition";
5234 case LINE_STAT_HEAD:
5235 case LINE_STAT_NONE:
5236 text = "Nothing to update";
5240 die("line type %d not handled in switch", line->type);
5243 if (status && status->status == 'U') {
5244 text = "Press %s to resolve conflict in %s";
5245 key = get_key(REQ_STATUS_MERGE);
5248 key = get_key(REQ_STATUS_UPDATE);
5251 string_format(view->ref, text, key, file);
5255 status_grep(struct view *view, struct line *line)
5257 struct status *status = line->data;
5258 enum { S_STATUS, S_NAME, S_END } state;
5265 for (state = S_STATUS; state < S_END; state++) {
5269 case S_NAME: text = status->new.name; break;
5271 buf[0] = status->status;
5279 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
5286 static struct view_ops status_ops = {
5299 stage_diff_write(struct io *io, struct line *line, struct line *end)
5301 while (line < end) {
5302 if (!io_write(io, line->data, strlen(line->data)) ||
5303 !io_write(io, "\n", 1))
5306 if (line->type == LINE_DIFF_CHUNK ||
5307 line->type == LINE_DIFF_HEADER)
5314 static struct line *
5315 stage_diff_find(struct view *view, struct line *line, enum line_type type)
5317 for (; view->line < line; line--)
5318 if (line->type == type)
5325 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
5327 const char *apply_argv[SIZEOF_ARG] = {
5328 "git", "apply", "--whitespace=nowarn", NULL
5330 struct line *diff_hdr;
5334 diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
5339 apply_argv[argc++] = "--cached";
5340 if (revert || stage_line_type == LINE_STAT_STAGED)
5341 apply_argv[argc++] = "-R";
5342 apply_argv[argc++] = "-";
5343 apply_argv[argc++] = NULL;
5344 if (!run_io(&io, apply_argv, opt_cdup, IO_WR))
5347 if (!stage_diff_write(&io, diff_hdr, chunk) ||
5348 !stage_diff_write(&io, chunk, view->line + view->lines))
5352 run_io_bg(update_index_argv);
5354 return chunk ? TRUE : FALSE;
5358 stage_update(struct view *view, struct line *line)
5360 struct line *chunk = NULL;
5362 if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
5363 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5366 if (!stage_apply_chunk(view, chunk, FALSE)) {
5367 report("Failed to apply chunk");
5371 } else if (!stage_status.status) {
5372 view = VIEW(REQ_VIEW_STATUS);
5374 for (line = view->line; line < view->line + view->lines; line++)
5375 if (line->type == stage_line_type)
5378 if (!status_update_files(view, line + 1)) {
5379 report("Failed to update files");
5383 } else if (!status_update_file(&stage_status, stage_line_type)) {
5384 report("Failed to update file");
5392 stage_revert(struct view *view, struct line *line)
5394 struct line *chunk = NULL;
5396 if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
5397 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5400 if (!prompt_yesno("Are you sure you want to revert changes?"))
5403 if (!stage_apply_chunk(view, chunk, TRUE)) {
5404 report("Failed to revert chunk");
5410 return status_revert(stage_status.status ? &stage_status : NULL,
5411 stage_line_type, FALSE);
5417 stage_next(struct view *view, struct line *line)
5421 if (!stage_chunks) {
5422 static size_t alloc = 0;
5425 for (line = view->line; line < view->line + view->lines; line++) {
5426 if (line->type != LINE_DIFF_CHUNK)
5429 tmp = realloc_items(stage_chunk, &alloc,
5430 stage_chunks, sizeof(*tmp));
5432 report("Allocation failure");
5437 stage_chunk[stage_chunks++] = line - view->line;
5441 for (i = 0; i < stage_chunks; i++) {
5442 if (stage_chunk[i] > view->lineno) {
5443 do_scroll_view(view, stage_chunk[i] - view->lineno);
5444 report("Chunk %d of %d", i + 1, stage_chunks);
5449 report("No next chunk found");
5453 stage_request(struct view *view, enum request request, struct line *line)
5456 case REQ_STATUS_UPDATE:
5457 if (!stage_update(view, line))
5461 case REQ_STATUS_REVERT:
5462 if (!stage_revert(view, line))
5466 case REQ_STAGE_NEXT:
5467 if (stage_line_type == LINE_STAT_UNTRACKED) {
5468 report("File is untracked; press %s to add",
5469 get_key(REQ_STATUS_UPDATE));
5472 stage_next(view, line);
5476 if (!stage_status.new.name[0])
5478 if (stage_status.status == 'D') {
5479 report("File has been deleted.");
5483 open_editor(stage_status.status != '?', stage_status.new.name);
5487 /* Reload everything ... */
5490 case REQ_VIEW_BLAME:
5491 if (stage_status.new.name[0]) {
5492 string_copy(opt_file, stage_status.new.name);
5498 return pager_request(view, request, line);
5504 VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
5505 open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
5507 /* Check whether the staged entry still exists, and close the
5508 * stage view if it doesn't. */
5509 if (!status_exists(&stage_status, stage_line_type)) {
5510 status_restore(VIEW(REQ_VIEW_STATUS));
5511 return REQ_VIEW_CLOSE;
5514 if (stage_line_type == LINE_STAT_UNTRACKED) {
5515 if (!suffixcmp(stage_status.new.name, -1, "/")) {
5516 report("Cannot display a directory");
5520 if (!prepare_update_file(view, stage_status.new.name)) {
5521 report("Failed to open file: %s", strerror(errno));
5525 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
5530 static struct view_ops stage_ops = {
5547 char id[SIZEOF_REV]; /* SHA1 ID. */
5548 char title[128]; /* First line of the commit message. */
5549 char author[75]; /* Author of the commit. */
5550 struct tm time; /* Date from the author ident. */
5551 struct ref **refs; /* Repository references. */
5552 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
5553 size_t graph_size; /* The width of the graph array. */
5554 bool has_parents; /* Rewritten --parents seen. */
5557 /* Size of rev graph with no "padding" columns */
5558 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
5561 struct rev_graph *prev, *next, *parents;
5562 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
5564 struct commit *commit;
5566 unsigned int boundary:1;
5569 /* Parents of the commit being visualized. */
5570 static struct rev_graph graph_parents[4];
5572 /* The current stack of revisions on the graph. */
5573 static struct rev_graph graph_stacks[4] = {
5574 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
5575 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
5576 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
5577 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
5581 graph_parent_is_merge(struct rev_graph *graph)
5583 return graph->parents->size > 1;
5587 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
5589 struct commit *commit = graph->commit;
5591 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
5592 commit->graph[commit->graph_size++] = symbol;
5596 clear_rev_graph(struct rev_graph *graph)
5598 graph->boundary = 0;
5599 graph->size = graph->pos = 0;
5600 graph->commit = NULL;
5601 memset(graph->parents, 0, sizeof(*graph->parents));
5605 done_rev_graph(struct rev_graph *graph)
5607 if (graph_parent_is_merge(graph) &&
5608 graph->pos < graph->size - 1 &&
5609 graph->next->size == graph->size + graph->parents->size - 1) {
5610 size_t i = graph->pos + graph->parents->size - 1;
5612 graph->commit->graph_size = i * 2;
5613 while (i < graph->next->size - 1) {
5614 append_to_rev_graph(graph, ' ');
5615 append_to_rev_graph(graph, '\\');
5620 clear_rev_graph(graph);
5624 push_rev_graph(struct rev_graph *graph, const char *parent)
5628 /* "Collapse" duplicate parents lines.
5630 * FIXME: This needs to also update update the drawn graph but
5631 * for now it just serves as a method for pruning graph lines. */
5632 for (i = 0; i < graph->size; i++)
5633 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
5636 if (graph->size < SIZEOF_REVITEMS) {
5637 string_copy_rev(graph->rev[graph->size++], parent);
5642 get_rev_graph_symbol(struct rev_graph *graph)
5646 if (graph->boundary)
5647 symbol = REVGRAPH_BOUND;
5648 else if (graph->parents->size == 0)
5649 symbol = REVGRAPH_INIT;
5650 else if (graph_parent_is_merge(graph))
5651 symbol = REVGRAPH_MERGE;
5652 else if (graph->pos >= graph->size)
5653 symbol = REVGRAPH_BRANCH;
5655 symbol = REVGRAPH_COMMIT;
5661 draw_rev_graph(struct rev_graph *graph)
5664 chtype separator, line;
5666 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
5667 static struct rev_filler fillers[] = {
5673 chtype symbol = get_rev_graph_symbol(graph);
5674 struct rev_filler *filler;
5677 if (opt_line_graphics)
5678 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
5680 filler = &fillers[DEFAULT];
5682 for (i = 0; i < graph->pos; i++) {
5683 append_to_rev_graph(graph, filler->line);
5684 if (graph_parent_is_merge(graph->prev) &&
5685 graph->prev->pos == i)
5686 filler = &fillers[RSHARP];
5688 append_to_rev_graph(graph, filler->separator);
5691 /* Place the symbol for this revision. */
5692 append_to_rev_graph(graph, symbol);
5694 if (graph->prev->size > graph->size)
5695 filler = &fillers[RDIAG];
5697 filler = &fillers[DEFAULT];
5701 for (; i < graph->size; i++) {
5702 append_to_rev_graph(graph, filler->separator);
5703 append_to_rev_graph(graph, filler->line);
5704 if (graph_parent_is_merge(graph->prev) &&
5705 i < graph->prev->pos + graph->parents->size)
5706 filler = &fillers[RSHARP];
5707 if (graph->prev->size > graph->size)
5708 filler = &fillers[LDIAG];
5711 if (graph->prev->size > graph->size) {
5712 append_to_rev_graph(graph, filler->separator);
5713 if (filler->line != ' ')
5714 append_to_rev_graph(graph, filler->line);
5718 /* Prepare the next rev graph */
5720 prepare_rev_graph(struct rev_graph *graph)
5724 /* First, traverse all lines of revisions up to the active one. */
5725 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
5726 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
5729 push_rev_graph(graph->next, graph->rev[graph->pos]);
5732 /* Interleave the new revision parent(s). */
5733 for (i = 0; !graph->boundary && i < graph->parents->size; i++)
5734 push_rev_graph(graph->next, graph->parents->rev[i]);
5736 /* Lastly, put any remaining revisions. */
5737 for (i = graph->pos + 1; i < graph->size; i++)
5738 push_rev_graph(graph->next, graph->rev[i]);
5742 update_rev_graph(struct view *view, struct rev_graph *graph)
5744 /* If this is the finalizing update ... */
5746 prepare_rev_graph(graph);
5748 /* Graph visualization needs a one rev look-ahead,
5749 * so the first update doesn't visualize anything. */
5750 if (!graph->prev->commit)
5753 if (view->lines > 2)
5754 view->line[view->lines - 3].dirty = 1;
5755 if (view->lines > 1)
5756 view->line[view->lines - 2].dirty = 1;
5757 draw_rev_graph(graph->prev);
5758 done_rev_graph(graph->prev->prev);
5766 static const char *main_argv[SIZEOF_ARG] = {
5767 "git", "log", "--no-color", "--pretty=raw", "--parents",
5768 "--topo-order", "%(head)", NULL
5772 main_draw(struct view *view, struct line *line, unsigned int lineno)
5774 struct commit *commit = line->data;
5776 if (!*commit->author)
5779 if (opt_date && draw_date(view, &commit->time))
5782 if (opt_author && draw_author(view, commit->author))
5785 if (opt_rev_graph && commit->graph_size &&
5786 draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
5789 if (opt_show_refs && commit->refs) {
5793 enum line_type type;
5795 if (commit->refs[i]->head)
5796 type = LINE_MAIN_HEAD;
5797 else if (commit->refs[i]->ltag)
5798 type = LINE_MAIN_LOCAL_TAG;
5799 else if (commit->refs[i]->tag)
5800 type = LINE_MAIN_TAG;
5801 else if (commit->refs[i]->tracked)
5802 type = LINE_MAIN_TRACKED;
5803 else if (commit->refs[i]->remote)
5804 type = LINE_MAIN_REMOTE;
5806 type = LINE_MAIN_REF;
5808 if (draw_text(view, type, "[", TRUE) ||
5809 draw_text(view, type, commit->refs[i]->name, TRUE) ||
5810 draw_text(view, type, "]", TRUE))
5813 if (draw_text(view, LINE_DEFAULT, " ", TRUE))
5815 } while (commit->refs[i++]->next);
5818 draw_text(view, LINE_DEFAULT, commit->title, TRUE);
5822 /* Reads git log --pretty=raw output and parses it into the commit struct. */
5824 main_read(struct view *view, char *line)
5826 static struct rev_graph *graph = graph_stacks;
5827 enum line_type type;
5828 struct commit *commit;
5833 if (!view->lines && !view->parent)
5834 die("No revisions match the given arguments.");
5835 if (view->lines > 0) {
5836 commit = view->line[view->lines - 1].data;
5837 view->line[view->lines - 1].dirty = 1;
5838 if (!*commit->author) {
5841 graph->commit = NULL;
5844 update_rev_graph(view, graph);
5846 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
5847 clear_rev_graph(&graph_stacks[i]);
5851 type = get_line_type(line);
5852 if (type == LINE_COMMIT) {
5853 commit = calloc(1, sizeof(struct commit));
5857 line += STRING_SIZE("commit ");
5859 graph->boundary = 1;
5863 string_copy_rev(commit->id, line);
5864 commit->refs = get_refs(commit->id);
5865 graph->commit = commit;
5866 add_line_data(view, commit, LINE_MAIN_COMMIT);
5868 while ((line = strchr(line, ' '))) {
5870 push_rev_graph(graph->parents, line);
5871 commit->has_parents = TRUE;
5878 commit = view->line[view->lines - 1].data;
5882 if (commit->has_parents)
5884 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
5888 parse_author_line(line + STRING_SIZE("author "),
5889 commit->author, sizeof(commit->author),
5891 update_rev_graph(view, graph);
5892 graph = graph->next;
5896 /* Fill in the commit title if it has not already been set. */
5897 if (commit->title[0])
5900 /* Require titles to start with a non-space character at the
5901 * offset used by git log. */
5902 if (strncmp(line, " ", 4))
5905 /* Well, if the title starts with a whitespace character,
5906 * try to be forgiving. Otherwise we end up with no title. */
5907 while (isspace(*line))
5911 /* FIXME: More graceful handling of titles; append "..." to
5912 * shortened titles, etc. */
5914 string_expand(commit->title, sizeof(commit->title), line, 1);
5915 view->line[view->lines - 1].dirty = 1;
5922 main_request(struct view *view, enum request request, struct line *line)
5924 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
5928 open_view(view, REQ_VIEW_DIFF, flags);
5932 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
5942 grep_refs(struct ref **refs, regex_t *regex)
5950 if (regexec(regex, refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
5952 } while (refs[i++]->next);
5958 main_grep(struct view *view, struct line *line)
5960 struct commit *commit = line->data;
5961 enum { S_TITLE, S_AUTHOR, S_DATE, S_REFS, S_END } state;
5962 char buf[DATE_COLS + 1];
5965 for (state = S_TITLE; state < S_END; state++) {
5969 case S_TITLE: text = commit->title; break;
5973 text = commit->author;
5978 if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
5985 if (grep_refs(commit->refs, view->regex) == TRUE)
5992 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
6000 main_select(struct view *view, struct line *line)
6002 struct commit *commit = line->data;
6004 string_copy_rev(view->ref, commit->id);
6005 string_copy_rev(ref_commit, view->ref);
6008 static struct view_ops main_ops = {
6021 * Unicode / UTF-8 handling
6023 * NOTE: Much of the following code for dealing with Unicode is derived from
6024 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
6025 * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
6029 unicode_width(unsigned long c)
6032 (c <= 0x115f /* Hangul Jamo */
6035 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
6037 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
6038 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
6039 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
6040 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
6041 || (c >= 0xffe0 && c <= 0xffe6)
6042 || (c >= 0x20000 && c <= 0x2fffd)
6043 || (c >= 0x30000 && c <= 0x3fffd)))
6047 return opt_tab_size;
6052 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
6053 * Illegal bytes are set one. */
6054 static const unsigned char utf8_bytes[256] = {
6055 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,
6056 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,
6057 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,
6058 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,
6059 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,
6060 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,
6061 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,
6062 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,
6065 /* Decode UTF-8 multi-byte representation into a Unicode character. */
6066 static inline unsigned long
6067 utf8_to_unicode(const char *string, size_t length)
6069 unsigned long unicode;
6073 unicode = string[0];
6076 unicode = (string[0] & 0x1f) << 6;
6077 unicode += (string[1] & 0x3f);
6080 unicode = (string[0] & 0x0f) << 12;
6081 unicode += ((string[1] & 0x3f) << 6);
6082 unicode += (string[2] & 0x3f);
6085 unicode = (string[0] & 0x0f) << 18;
6086 unicode += ((string[1] & 0x3f) << 12);
6087 unicode += ((string[2] & 0x3f) << 6);
6088 unicode += (string[3] & 0x3f);
6091 unicode = (string[0] & 0x0f) << 24;
6092 unicode += ((string[1] & 0x3f) << 18);
6093 unicode += ((string[2] & 0x3f) << 12);
6094 unicode += ((string[3] & 0x3f) << 6);
6095 unicode += (string[4] & 0x3f);
6098 unicode = (string[0] & 0x01) << 30;
6099 unicode += ((string[1] & 0x3f) << 24);
6100 unicode += ((string[2] & 0x3f) << 18);
6101 unicode += ((string[3] & 0x3f) << 12);
6102 unicode += ((string[4] & 0x3f) << 6);
6103 unicode += (string[5] & 0x3f);
6106 die("Invalid Unicode length");
6109 /* Invalid characters could return the special 0xfffd value but NUL
6110 * should be just as good. */
6111 return unicode > 0xffff ? 0 : unicode;
6114 /* Calculates how much of string can be shown within the given maximum width
6115 * and sets trimmed parameter to non-zero value if all of string could not be
6116 * shown. If the reserve flag is TRUE, it will reserve at least one
6117 * trailing character, which can be useful when drawing a delimiter.
6119 * Returns the number of bytes to output from string to satisfy max_width. */
6121 utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve)
6123 const char *string = *start;
6124 const char *end = strchr(string, '\0');
6125 unsigned char last_bytes = 0;
6126 size_t last_ucwidth = 0;
6131 while (string < end) {
6132 int c = *(unsigned char *) string;
6133 unsigned char bytes = utf8_bytes[c];
6135 unsigned long unicode;
6137 if (string + bytes > end)
6140 /* Change representation to figure out whether
6141 * it is a single- or double-width character. */
6143 unicode = utf8_to_unicode(string, bytes);
6144 /* FIXME: Graceful handling of invalid Unicode character. */
6148 ucwidth = unicode_width(unicode);
6150 skip -= ucwidth <= skip ? ucwidth : skip;
6154 if (*width > max_width) {
6157 if (reserve && *width == max_width) {
6158 string -= last_bytes;
6159 *width -= last_ucwidth;
6165 last_bytes = ucwidth ? bytes : 0;
6166 last_ucwidth = ucwidth;
6169 return string - *start;
6177 /* Whether or not the curses interface has been initialized. */
6178 static bool cursed = FALSE;
6180 /* Terminal hacks and workarounds. */
6181 static bool use_scroll_redrawwin;
6182 static bool use_scroll_status_wclear;
6184 /* The status window is used for polling keystrokes. */
6185 static WINDOW *status_win;
6187 /* Reading from the prompt? */
6188 static bool input_mode = FALSE;
6190 static bool status_empty = FALSE;
6192 /* Update status and title window. */
6194 report(const char *msg, ...)
6196 struct view *view = display[current_view];
6202 char buf[SIZEOF_STR];
6205 va_start(args, msg);
6206 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6207 buf[sizeof(buf) - 1] = 0;
6208 buf[sizeof(buf) - 2] = '.';
6209 buf[sizeof(buf) - 3] = '.';
6210 buf[sizeof(buf) - 4] = '.';
6216 if (!status_empty || *msg) {
6219 va_start(args, msg);
6221 wmove(status_win, 0, 0);
6222 if (view->has_scrolled && use_scroll_status_wclear)
6225 vwprintw(status_win, msg, args);
6226 status_empty = FALSE;
6228 status_empty = TRUE;
6230 wclrtoeol(status_win);
6231 wnoutrefresh(status_win);
6236 update_view_title(view);
6239 /* Controls when nodelay should be in effect when polling user input. */
6241 set_nonblocking_input(bool loading)
6243 static unsigned int loading_views;
6245 if ((loading == FALSE && loading_views-- == 1) ||
6246 (loading == TRUE && loading_views++ == 0))
6247 nodelay(status_win, loading);
6256 /* Initialize the curses library */
6257 if (isatty(STDIN_FILENO)) {
6258 cursed = !!initscr();
6261 /* Leave stdin and stdout alone when acting as a pager. */
6262 opt_tty = fopen("/dev/tty", "r+");
6264 die("Failed to open /dev/tty");
6265 cursed = !!newterm(NULL, opt_tty, opt_tty);
6269 die("Failed to initialize curses");
6271 nonl(); /* Disable conversion and detect newlines from input. */
6272 cbreak(); /* Take input chars one at a time, no wait for \n */
6273 noecho(); /* Don't echo input */
6274 leaveok(stdscr, FALSE);
6279 getmaxyx(stdscr, y, x);
6280 status_win = newwin(1, 0, y - 1, 0);
6282 die("Failed to create status window");
6284 /* Enable keyboard mapping */
6285 keypad(status_win, TRUE);
6286 wbkgdset(status_win, get_line_attr(LINE_STATUS));
6288 TABSIZE = opt_tab_size;
6289 if (opt_line_graphics) {
6290 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
6293 term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
6294 if (term && !strcmp(term, "gnome-terminal")) {
6295 /* In the gnome-terminal-emulator, the message from
6296 * scrolling up one line when impossible followed by
6297 * scrolling down one line causes corruption of the
6298 * status line. This is fixed by calling wclear. */
6299 use_scroll_status_wclear = TRUE;
6300 use_scroll_redrawwin = FALSE;
6302 } else if (term && !strcmp(term, "xrvt-xpm")) {
6303 /* No problems with full optimizations in xrvt-(unicode)
6305 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
6308 /* When scrolling in (u)xterm the last line in the
6309 * scrolling direction will update slowly. */
6310 use_scroll_redrawwin = TRUE;
6311 use_scroll_status_wclear = FALSE;
6316 get_input(int prompt_position)
6319 int i, key, cursor_y, cursor_x;
6321 if (prompt_position)
6325 foreach_view (view, i) {
6327 if (view_is_displayed(view) && view->has_scrolled &&
6328 use_scroll_redrawwin)
6329 redrawwin(view->win);
6330 view->has_scrolled = FALSE;
6333 /* Update the cursor position. */
6334 if (prompt_position) {
6335 getbegyx(status_win, cursor_y, cursor_x);
6336 cursor_x = prompt_position;
6338 view = display[current_view];
6339 getbegyx(view->win, cursor_y, cursor_x);
6340 cursor_x = view->width - 1;
6341 cursor_y += view->lineno - view->offset;
6343 setsyx(cursor_y, cursor_x);
6345 /* Refresh, accept single keystroke of input */
6347 key = wgetch(status_win);
6349 /* wgetch() with nodelay() enabled returns ERR when
6350 * there's no input. */
6353 } else if (key == KEY_RESIZE) {
6356 getmaxyx(stdscr, height, width);
6358 wresize(status_win, 1, width);
6359 mvwin(status_win, height - 1, 0);
6360 wnoutrefresh(status_win);
6362 redraw_display(TRUE);
6372 prompt_input(const char *prompt, input_handler handler, void *data)
6374 enum input_status status = INPUT_OK;
6375 static char buf[SIZEOF_STR];
6380 while (status == INPUT_OK || status == INPUT_SKIP) {
6383 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
6384 wclrtoeol(status_win);
6386 key = get_input(pos + 1);
6391 status = pos ? INPUT_STOP : INPUT_CANCEL;
6398 status = INPUT_CANCEL;
6402 status = INPUT_CANCEL;
6406 if (pos >= sizeof(buf)) {
6407 report("Input string too long");
6411 status = handler(data, buf, key);
6412 if (status == INPUT_OK)
6413 buf[pos++] = (char) key;
6417 /* Clear the status window */
6418 status_empty = FALSE;
6421 if (status == INPUT_CANCEL)
6429 static enum input_status
6430 prompt_yesno_handler(void *data, char *buf, int c)
6432 if (c == 'y' || c == 'Y')
6434 if (c == 'n' || c == 'N')
6435 return INPUT_CANCEL;
6440 prompt_yesno(const char *prompt)
6442 char prompt2[SIZEOF_STR];
6444 if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
6447 return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
6450 static enum input_status
6451 read_prompt_handler(void *data, char *buf, int c)
6453 return isprint(c) ? INPUT_OK : INPUT_SKIP;
6457 read_prompt(const char *prompt)
6459 return prompt_input(prompt, read_prompt_handler, NULL);
6463 * Repository properties
6466 static struct ref *refs = NULL;
6467 static size_t refs_alloc = 0;
6468 static size_t refs_size = 0;
6470 /* Id <-> ref store */
6471 static struct ref ***id_refs = NULL;
6472 static size_t id_refs_alloc = 0;
6473 static size_t id_refs_size = 0;
6476 compare_refs(const void *ref1_, const void *ref2_)
6478 const struct ref *ref1 = *(const struct ref **)ref1_;
6479 const struct ref *ref2 = *(const struct ref **)ref2_;
6481 if (ref1->tag != ref2->tag)
6482 return ref2->tag - ref1->tag;
6483 if (ref1->ltag != ref2->ltag)
6484 return ref2->ltag - ref2->ltag;
6485 if (ref1->head != ref2->head)
6486 return ref2->head - ref1->head;
6487 if (ref1->tracked != ref2->tracked)
6488 return ref2->tracked - ref1->tracked;
6489 if (ref1->remote != ref2->remote)
6490 return ref2->remote - ref1->remote;
6491 return strcmp(ref1->name, ref2->name);
6494 static struct ref **
6495 get_refs(const char *id)
6497 struct ref ***tmp_id_refs;
6498 struct ref **ref_list = NULL;
6499 size_t ref_list_alloc = 0;
6500 size_t ref_list_size = 0;
6503 for (i = 0; i < id_refs_size; i++)
6504 if (!strcmp(id, id_refs[i][0]->id))
6507 tmp_id_refs = realloc_items(id_refs, &id_refs_alloc, id_refs_size + 1,
6512 id_refs = tmp_id_refs;
6514 for (i = 0; i < refs_size; i++) {
6517 if (strcmp(id, refs[i].id))
6520 tmp = realloc_items(ref_list, &ref_list_alloc,
6521 ref_list_size + 1, sizeof(*ref_list));
6529 ref_list[ref_list_size] = &refs[i];
6530 /* XXX: The properties of the commit chains ensures that we can
6531 * safely modify the shared ref. The repo references will
6532 * always be similar for the same id. */
6533 ref_list[ref_list_size]->next = 1;
6539 qsort(ref_list, ref_list_size, sizeof(*ref_list), compare_refs);
6540 ref_list[ref_list_size - 1]->next = 0;
6541 id_refs[id_refs_size++] = ref_list;
6548 read_ref(char *id, size_t idlen, char *name, size_t namelen)
6553 bool remote = FALSE;
6554 bool tracked = FALSE;
6555 bool check_replace = FALSE;
6558 if (!prefixcmp(name, "refs/tags/")) {
6559 if (!suffixcmp(name, namelen, "^{}")) {
6562 if (refs_size > 0 && refs[refs_size - 1].ltag == TRUE)
6563 check_replace = TRUE;
6569 namelen -= STRING_SIZE("refs/tags/");
6570 name += STRING_SIZE("refs/tags/");
6572 } else if (!prefixcmp(name, "refs/remotes/")) {
6574 namelen -= STRING_SIZE("refs/remotes/");
6575 name += STRING_SIZE("refs/remotes/");
6576 tracked = !strcmp(opt_remote, name);
6578 } else if (!prefixcmp(name, "refs/heads/")) {
6579 namelen -= STRING_SIZE("refs/heads/");
6580 name += STRING_SIZE("refs/heads/");
6581 head = !strncmp(opt_head, name, namelen);
6583 } else if (!strcmp(name, "HEAD")) {
6584 string_ncopy(opt_head_rev, id, idlen);
6588 if (check_replace && !strcmp(name, refs[refs_size - 1].name)) {
6589 /* it's an annotated tag, replace the previous SHA1 with the
6590 * resolved commit id; relies on the fact git-ls-remote lists
6591 * the commit id of an annotated tag right before the commit id
6593 refs[refs_size - 1].ltag = ltag;
6594 string_copy_rev(refs[refs_size - 1].id, id);
6598 refs = realloc_items(refs, &refs_alloc, refs_size + 1, sizeof(*refs));
6602 ref = &refs[refs_size++];
6603 ref->name = malloc(namelen + 1);
6607 strncpy(ref->name, name, namelen);
6608 ref->name[namelen] = 0;
6612 ref->remote = remote;
6613 ref->tracked = tracked;
6614 string_copy_rev(ref->id, id);
6622 static const char *ls_remote_argv[SIZEOF_ARG] = {
6623 "git", "ls-remote", ".", NULL
6625 static bool init = FALSE;
6628 argv_from_env(ls_remote_argv, "TIG_LS_REMOTE");
6635 while (refs_size > 0)
6636 free(refs[--refs_size].name);
6637 while (id_refs_size > 0)
6638 free(id_refs[--id_refs_size]);
6640 return run_io_load(ls_remote_argv, "\t", read_ref);
6644 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
6646 const char *argv[SIZEOF_ARG] = { name, "=" };
6647 int argc = 1 + (cmd == option_set_command);
6650 if (!argv_from_string(argv, &argc, value))
6651 config_msg = "Too many option arguments";
6653 error = cmd(argc, argv);
6656 warn("Option 'tig.%s': %s", name, config_msg);
6660 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
6662 if (!strcmp(name, "i18n.commitencoding"))
6663 string_ncopy(opt_encoding, value, valuelen);
6665 if (!strcmp(name, "core.editor"))
6666 string_ncopy(opt_editor, value, valuelen);
6668 if (!prefixcmp(name, "tig.color."))
6669 set_repo_config_option(name + 10, value, option_color_command);
6671 else if (!prefixcmp(name, "tig.bind."))
6672 set_repo_config_option(name + 9, value, option_bind_command);
6674 else if (!prefixcmp(name, "tig."))
6675 set_repo_config_option(name + 4, value, option_set_command);
6677 /* branch.<head>.remote */
6679 !strncmp(name, "branch.", 7) &&
6680 !strncmp(name + 7, opt_head, strlen(opt_head)) &&
6681 !strcmp(name + 7 + strlen(opt_head), ".remote"))
6682 string_ncopy(opt_remote, value, valuelen);
6684 if (*opt_head && *opt_remote &&
6685 !strncmp(name, "branch.", 7) &&
6686 !strncmp(name + 7, opt_head, strlen(opt_head)) &&
6687 !strcmp(name + 7 + strlen(opt_head), ".merge")) {
6688 size_t from = strlen(opt_remote);
6690 if (!prefixcmp(value, "refs/heads/")) {
6691 value += STRING_SIZE("refs/heads/");
6692 valuelen -= STRING_SIZE("refs/heads/");
6695 if (!string_format_from(opt_remote, &from, "/%s", value))
6703 load_git_config(void)
6705 const char *config_list_argv[] = { "git", GIT_CONFIG, "--list", NULL };
6707 return run_io_load(config_list_argv, "=", read_repo_config_option);
6711 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
6713 if (!opt_git_dir[0]) {
6714 string_ncopy(opt_git_dir, name, namelen);
6716 } else if (opt_is_inside_work_tree == -1) {
6717 /* This can be 3 different values depending on the
6718 * version of git being used. If git-rev-parse does not
6719 * understand --is-inside-work-tree it will simply echo
6720 * the option else either "true" or "false" is printed.
6721 * Default to true for the unknown case. */
6722 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
6724 } else if (*name == '.') {
6725 string_ncopy(opt_cdup, name, namelen);
6728 string_ncopy(opt_prefix, name, namelen);
6735 load_repo_info(void)
6737 const char *head_argv[] = {
6738 "git", "symbolic-ref", "HEAD", NULL
6740 const char *rev_parse_argv[] = {
6741 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
6742 "--show-cdup", "--show-prefix", NULL
6745 if (run_io_buf(head_argv, opt_head, sizeof(opt_head))) {
6746 chomp_string(opt_head);
6747 if (!prefixcmp(opt_head, "refs/heads/")) {
6748 char *offset = opt_head + STRING_SIZE("refs/heads/");
6750 memmove(opt_head, offset, strlen(offset) + 1);
6754 return run_io_load(rev_parse_argv, "=", read_repo_info);
6762 static const char usage[] =
6763 "tig " TIG_VERSION " (" __DATE__ ")\n"
6765 "Usage: tig [options] [revs] [--] [paths]\n"
6766 " or: tig show [options] [revs] [--] [paths]\n"
6767 " or: tig blame [rev] path\n"
6769 " or: tig < [git command output]\n"
6772 " -v, --version Show version and exit\n"
6773 " -h, --help Show help message and exit";
6775 static void __NORETURN
6778 /* XXX: Restore tty modes and let the OS cleanup the rest! */
6784 static void __NORETURN
6785 die(const char *err, ...)
6791 va_start(args, err);
6792 fputs("tig: ", stderr);
6793 vfprintf(stderr, err, args);
6794 fputs("\n", stderr);
6801 warn(const char *msg, ...)
6805 va_start(args, msg);
6806 fputs("tig warning: ", stderr);
6807 vfprintf(stderr, msg, args);
6808 fputs("\n", stderr);
6813 parse_options(int argc, const char *argv[])
6815 enum request request = REQ_VIEW_MAIN;
6816 const char *subcommand;
6817 bool seen_dashdash = FALSE;
6818 /* XXX: This is vulnerable to the user overriding options
6819 * required for the main view parser. */
6820 const char *custom_argv[SIZEOF_ARG] = {
6821 "git", "log", "--no-color", "--pretty=raw", "--parents",
6822 "--topo-order", NULL
6826 if (!isatty(STDIN_FILENO)) {
6827 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
6828 return REQ_VIEW_PAGER;
6834 subcommand = argv[1];
6835 if (!strcmp(subcommand, "status")) {
6837 warn("ignoring arguments after `%s'", subcommand);
6838 return REQ_VIEW_STATUS;
6840 } else if (!strcmp(subcommand, "blame")) {
6841 if (argc <= 2 || argc > 4)
6842 die("invalid number of options to blame\n\n%s", usage);
6846 string_ncopy(opt_ref, argv[i], strlen(argv[i]));
6850 string_ncopy(opt_file, argv[i], strlen(argv[i]));
6851 return REQ_VIEW_BLAME;
6853 } else if (!strcmp(subcommand, "show")) {
6854 request = REQ_VIEW_DIFF;
6861 custom_argv[1] = subcommand;
6865 for (i = 1 + !!subcommand; i < argc; i++) {
6866 const char *opt = argv[i];
6868 if (seen_dashdash || !strcmp(opt, "--")) {
6869 seen_dashdash = TRUE;
6871 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
6872 printf("tig version %s\n", TIG_VERSION);
6875 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
6876 printf("%s\n", usage);
6880 custom_argv[j++] = opt;
6881 if (j >= ARRAY_SIZE(custom_argv))
6882 die("command too long");
6885 if (!prepare_update(VIEW(request), custom_argv, NULL, FORMAT_NONE))
6886 die("Failed to format arguments");
6892 main(int argc, const char *argv[])
6894 enum request request = parse_options(argc, argv);
6898 signal(SIGINT, quit);
6900 if (setlocale(LC_ALL, "")) {
6901 char *codeset = nl_langinfo(CODESET);
6903 string_ncopy(opt_codeset, codeset, strlen(codeset));
6906 if (load_repo_info() == ERR)
6907 die("Failed to load repo info.");
6909 if (load_options() == ERR)
6910 die("Failed to load user config.");
6912 if (load_git_config() == ERR)
6913 die("Failed to load repo config.");
6915 /* Require a git repository unless when running in pager mode. */
6916 if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
6917 die("Not a git repository");
6919 if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
6922 if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
6923 opt_iconv = iconv_open(opt_codeset, opt_encoding);
6924 if (opt_iconv == ICONV_NONE)
6925 die("Failed to initialize character set conversion");
6928 if (load_refs() == ERR)
6929 die("Failed to load refs.");
6931 foreach_view (view, i)
6932 argv_from_env(view->ops->argv, view->cmd_env);
6936 if (request != REQ_NONE)
6937 open_view(NULL, request, OPEN_PREPARED);
6938 request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
6940 while (view_driver(display[current_view], request)) {
6941 int key = get_input(0);
6943 view = display[current_view];
6944 request = get_keybinding(view->keymap, key);
6946 /* Some low-level request handling. This keeps access to
6947 * status_win restricted. */
6951 char *cmd = read_prompt(":");
6953 if (cmd && isdigit(*cmd)) {
6954 int lineno = view->lineno + 1;
6956 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
6957 select_view_line(view, lineno - 1);
6960 report("Unable to parse '%s' as a line number", cmd);
6964 struct view *next = VIEW(REQ_VIEW_PAGER);
6965 const char *argv[SIZEOF_ARG] = { "git" };
6968 /* When running random commands, initially show the
6969 * command in the title. However, it maybe later be
6970 * overwritten if a commit line is selected. */
6971 string_ncopy(next->ref, cmd, strlen(cmd));
6973 if (!argv_from_string(argv, &argc, cmd)) {
6974 report("Too many arguments");
6975 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
6976 report("Failed to format command");
6978 open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
6986 case REQ_SEARCH_BACK:
6988 const char *prompt = request == REQ_SEARCH ? "/" : "?";
6989 char *search = read_prompt(prompt);
6992 string_ncopy(opt_search, search, strlen(search));
6993 else if (*opt_search)
6994 request = request == REQ_SEARCH ?