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(char *dst, size_t dstlen, const char *src, int tabsize)
196 for (size = pos = 0; size < dstlen - 1 && src[pos]; pos++) {
197 if (src[pos] == '\t') {
198 size_t expanded = tabsize - (size % tabsize);
200 if (expanded + size >= dstlen - 1)
201 expanded = dstlen - size - 1;
202 memcpy(dst + size, " ", expanded);
205 dst[size++] = src[pos];
213 chomp_string(char *name)
217 while (isspace(*name))
220 namelen = strlen(name) - 1;
221 while (namelen > 0 && isspace(name[namelen]))
228 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
231 size_t pos = bufpos ? *bufpos : 0;
234 pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
240 return pos >= bufsize ? FALSE : TRUE;
243 #define string_format(buf, fmt, args...) \
244 string_nformat(buf, sizeof(buf), NULL, fmt, args)
246 #define string_format_from(buf, from, fmt, args...) \
247 string_nformat(buf, sizeof(buf), from, fmt, args)
250 string_enum_compare(const char *str1, const char *str2, int len)
254 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
256 /* Diff-Header == DIFF_HEADER */
257 for (i = 0; i < len; i++) {
258 if (toupper(str1[i]) == toupper(str2[i]))
261 if (string_enum_sep(str1[i]) &&
262 string_enum_sep(str2[i]))
265 return str1[i] - str2[i];
277 #define ENUM_MAP(name, value) { name, STRING_SIZE(name), value }
280 map_enum_do(const struct enum_map *map, size_t map_size, int *value, const char *name)
282 size_t namelen = strlen(name);
285 for (i = 0; i < map_size; i++)
286 if (namelen == map[i].namelen &&
287 !string_enum_compare(name, map[i].name, namelen)) {
288 *value = map[i].value;
295 #define map_enum(attr, map, name) \
296 map_enum_do(map, ARRAY_SIZE(map), attr, name)
298 #define prefixcmp(str1, str2) \
299 strncmp(str1, str2, STRING_SIZE(str2))
302 suffixcmp(const char *str, int slen, const char *suffix)
304 size_t len = slen >= 0 ? slen : strlen(str);
305 size_t suffixlen = strlen(suffix);
307 return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
312 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
316 while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
317 bool advance = cmd[valuelen] != 0;
320 argv[(*argc)++] = chomp_string(cmd);
321 cmd = chomp_string(cmd + valuelen + advance);
324 if (*argc < SIZEOF_ARG)
326 return *argc < SIZEOF_ARG;
330 argv_from_env(const char **argv, const char *name)
332 char *env = argv ? getenv(name) : NULL;
337 if (env && !argv_from_string(argv, &argc, env))
338 die("Too many arguments in the `%s` environment variable", name);
343 * Executing external commands.
347 IO_FD, /* File descriptor based IO. */
348 IO_BG, /* Execute command in the background. */
349 IO_FG, /* Execute command with same std{in,out,err}. */
350 IO_RD, /* Read only fork+exec IO. */
351 IO_WR, /* Write only fork+exec IO. */
352 IO_AP, /* Append fork+exec output to file. */
356 enum io_type type; /* The requested type of pipe. */
357 const char *dir; /* Directory from which to execute. */
358 pid_t pid; /* Pipe for reading or writing. */
359 int pipe; /* Pipe end for reading or writing. */
360 int error; /* Error status. */
361 const char *argv[SIZEOF_ARG]; /* Shell command arguments. */
362 char *buf; /* Read buffer. */
363 size_t bufalloc; /* Allocated buffer size. */
364 size_t bufsize; /* Buffer content size. */
365 char *bufpos; /* Current buffer position. */
366 unsigned int eof:1; /* Has end of file been reached. */
370 reset_io(struct io *io)
374 io->buf = io->bufpos = NULL;
375 io->bufalloc = io->bufsize = 0;
381 init_io(struct io *io, const char *dir, enum io_type type)
389 init_io_rd(struct io *io, const char *argv[], const char *dir,
390 enum format_flags flags)
392 init_io(io, dir, IO_RD);
393 return format_argv(io->argv, argv, flags);
397 io_open(struct io *io, const char *name)
399 init_io(io, NULL, IO_FD);
400 io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
401 return io->pipe != -1;
405 kill_io(struct io *io)
407 return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
411 done_io(struct io *io)
422 pid_t waiting = waitpid(pid, &status, 0);
427 report("waitpid failed (%s)", strerror(errno));
431 return waiting == pid &&
432 !WIFSIGNALED(status) &&
434 !WEXITSTATUS(status);
441 start_io(struct io *io)
443 int pipefds[2] = { -1, -1 };
445 if (io->type == IO_FD)
448 if ((io->type == IO_RD || io->type == IO_WR) &&
451 else if (io->type == IO_AP)
452 pipefds[1] = io->pipe;
454 if ((io->pid = fork())) {
455 if (pipefds[!(io->type == IO_WR)] != -1)
456 close(pipefds[!(io->type == IO_WR)]);
458 io->pipe = pipefds[!!(io->type == IO_WR)];
463 if (io->type != IO_FG) {
464 int devnull = open("/dev/null", O_RDWR);
465 int readfd = io->type == IO_WR ? pipefds[0] : devnull;
466 int writefd = (io->type == IO_RD || io->type == IO_AP)
467 ? pipefds[1] : devnull;
469 dup2(readfd, STDIN_FILENO);
470 dup2(writefd, STDOUT_FILENO);
471 dup2(devnull, STDERR_FILENO);
474 if (pipefds[0] != -1)
476 if (pipefds[1] != -1)
480 if (io->dir && *io->dir && chdir(io->dir) == -1)
481 die("Failed to change directory: %s", strerror(errno));
483 execvp(io->argv[0], (char *const*) io->argv);
484 die("Failed to execute program: %s", strerror(errno));
487 if (pipefds[!!(io->type == IO_WR)] != -1)
488 close(pipefds[!!(io->type == IO_WR)]);
493 run_io(struct io *io, const char **argv, const char *dir, enum io_type type)
495 init_io(io, dir, type);
496 if (!format_argv(io->argv, argv, FORMAT_NONE))
502 run_io_do(struct io *io)
504 return start_io(io) && done_io(io);
508 run_io_bg(const char **argv)
512 init_io(&io, NULL, IO_BG);
513 if (!format_argv(io.argv, argv, FORMAT_NONE))
515 return run_io_do(&io);
519 run_io_fg(const char **argv, const char *dir)
523 init_io(&io, dir, IO_FG);
524 if (!format_argv(io.argv, argv, FORMAT_NONE))
526 return run_io_do(&io);
530 run_io_append(const char **argv, enum format_flags flags, int fd)
534 init_io(&io, NULL, IO_AP);
536 if (format_argv(io.argv, argv, flags))
537 return run_io_do(&io);
543 run_io_rd(struct io *io, const char **argv, enum format_flags flags)
545 return init_io_rd(io, argv, NULL, flags) && start_io(io);
549 io_eof(struct io *io)
555 io_error(struct io *io)
561 io_strerror(struct io *io)
563 return strerror(io->error);
567 io_can_read(struct io *io)
569 struct timeval tv = { 0, 500 };
573 FD_SET(io->pipe, &fds);
575 return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
579 io_read(struct io *io, void *buf, size_t bufsize)
582 ssize_t readsize = read(io->pipe, buf, bufsize);
584 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
586 else if (readsize == -1)
588 else if (readsize == 0)
595 io_get(struct io *io, int c, bool can_read)
601 io->buf = io->bufpos = malloc(BUFSIZ);
604 io->bufalloc = BUFSIZ;
609 if (io->bufsize > 0) {
610 eol = memchr(io->bufpos, c, io->bufsize);
612 char *line = io->bufpos;
615 io->bufpos = eol + 1;
616 io->bufsize -= io->bufpos - line;
623 io->bufpos[io->bufsize] = 0;
633 if (io->bufsize > 0 && io->bufpos > io->buf)
634 memmove(io->buf, io->bufpos, io->bufsize);
636 io->bufpos = io->buf;
637 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
640 io->bufsize += readsize;
645 io_write(struct io *io, const void *buf, size_t bufsize)
649 while (!io_error(io) && written < bufsize) {
652 size = write(io->pipe, buf + written, bufsize - written);
653 if (size < 0 && (errno == EAGAIN || errno == EINTR))
661 return written == bufsize;
665 io_read_buf(struct io *io, char buf[], size_t bufsize)
669 io->buf = io->bufpos = buf;
670 io->bufalloc = bufsize;
671 error = !io_get(io, '\n', TRUE) && io_error(io);
674 return done_io(io) || error;
678 run_io_buf(const char **argv, char buf[], size_t bufsize)
682 return run_io_rd(&io, argv, FORMAT_NONE) && io_read_buf(&io, buf, bufsize);
686 io_load(struct io *io, const char *separators,
687 int (*read_property)(char *, size_t, char *, size_t))
695 while (state == OK && (name = io_get(io, '\n', TRUE))) {
700 name = chomp_string(name);
701 namelen = strcspn(name, separators);
705 value = chomp_string(name + namelen + 1);
706 valuelen = strlen(value);
713 state = read_property(name, namelen, value, valuelen);
716 if (state != ERR && io_error(io))
724 run_io_load(const char **argv, const char *separators,
725 int (*read_property)(char *, size_t, char *, size_t))
729 return init_io_rd(&io, argv, NULL, FORMAT_NONE)
730 ? io_load(&io, separators, read_property) : ERR;
739 /* XXX: Keep the view request first and in sync with views[]. */ \
740 REQ_GROUP("View switching") \
741 REQ_(VIEW_MAIN, "Show main view"), \
742 REQ_(VIEW_DIFF, "Show diff view"), \
743 REQ_(VIEW_LOG, "Show log view"), \
744 REQ_(VIEW_TREE, "Show tree view"), \
745 REQ_(VIEW_BLOB, "Show blob view"), \
746 REQ_(VIEW_BLAME, "Show blame view"), \
747 REQ_(VIEW_HELP, "Show help page"), \
748 REQ_(VIEW_PAGER, "Show pager view"), \
749 REQ_(VIEW_STATUS, "Show status view"), \
750 REQ_(VIEW_STAGE, "Show stage view"), \
752 REQ_GROUP("View manipulation") \
753 REQ_(ENTER, "Enter current line and scroll"), \
754 REQ_(NEXT, "Move to next"), \
755 REQ_(PREVIOUS, "Move to previous"), \
756 REQ_(PARENT, "Move to parent"), \
757 REQ_(VIEW_NEXT, "Move focus to next view"), \
758 REQ_(REFRESH, "Reload and refresh"), \
759 REQ_(MAXIMIZE, "Maximize the current view"), \
760 REQ_(VIEW_CLOSE, "Close the current view"), \
761 REQ_(QUIT, "Close all views and quit"), \
763 REQ_GROUP("View specific requests") \
764 REQ_(STATUS_UPDATE, "Update file status"), \
765 REQ_(STATUS_REVERT, "Revert file changes"), \
766 REQ_(STATUS_MERGE, "Merge file using external tool"), \
767 REQ_(STAGE_NEXT, "Find next chunk to stage"), \
769 REQ_GROUP("Cursor navigation") \
770 REQ_(MOVE_UP, "Move cursor one line up"), \
771 REQ_(MOVE_DOWN, "Move cursor one line down"), \
772 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
773 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
774 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
775 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
777 REQ_GROUP("Scrolling") \
778 REQ_(SCROLL_LEFT, "Scroll two columns left"), \
779 REQ_(SCROLL_RIGHT, "Scroll two columns right"), \
780 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
781 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
782 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
783 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
785 REQ_GROUP("Searching") \
786 REQ_(SEARCH, "Search the view"), \
787 REQ_(SEARCH_BACK, "Search backwards in the view"), \
788 REQ_(FIND_NEXT, "Find next search match"), \
789 REQ_(FIND_PREV, "Find previous search match"), \
791 REQ_GROUP("Option manipulation") \
792 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
793 REQ_(TOGGLE_DATE, "Toggle date display"), \
794 REQ_(TOGGLE_AUTHOR, "Toggle author display"), \
795 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
796 REQ_(TOGGLE_REFS, "Toggle reference display (tags/branches)"), \
799 REQ_(PROMPT, "Bring up the prompt"), \
800 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
801 REQ_(SHOW_VERSION, "Show version information"), \
802 REQ_(STOP_LOADING, "Stop all loading views"), \
803 REQ_(EDIT, "Open in editor"), \
804 REQ_(NONE, "Do nothing")
807 /* User action requests. */
809 #define REQ_GROUP(help)
810 #define REQ_(req, help) REQ_##req
812 /* Offset all requests to avoid conflicts with ncurses getch values. */
813 REQ_OFFSET = KEY_MAX + 1,
820 struct request_info {
821 enum request request;
827 static const struct request_info req_info[] = {
828 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
829 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
836 get_request(const char *name)
838 int namelen = strlen(name);
841 for (i = 0; i < ARRAY_SIZE(req_info); i++)
842 if (req_info[i].namelen == namelen &&
843 !string_enum_compare(req_info[i].name, name, namelen))
844 return req_info[i].request;
854 /* Option and state variables. */
855 static bool opt_date = TRUE;
856 static bool opt_author = TRUE;
857 static bool opt_line_number = FALSE;
858 static bool opt_line_graphics = TRUE;
859 static bool opt_rev_graph = FALSE;
860 static bool opt_show_refs = TRUE;
861 static int opt_num_interval = NUMBER_INTERVAL;
862 static int opt_tab_size = TAB_SIZE;
863 static int opt_author_cols = AUTHOR_COLS-1;
864 static char opt_path[SIZEOF_STR] = "";
865 static char opt_file[SIZEOF_STR] = "";
866 static char opt_ref[SIZEOF_REF] = "";
867 static char opt_head[SIZEOF_REF] = "";
868 static char opt_head_rev[SIZEOF_REV] = "";
869 static char opt_remote[SIZEOF_REF] = "";
870 static char opt_encoding[20] = "UTF-8";
871 static bool opt_utf8 = TRUE;
872 static char opt_codeset[20] = "UTF-8";
873 static iconv_t opt_iconv = ICONV_NONE;
874 static char opt_search[SIZEOF_STR] = "";
875 static char opt_cdup[SIZEOF_STR] = "";
876 static char opt_prefix[SIZEOF_STR] = "";
877 static char opt_git_dir[SIZEOF_STR] = "";
878 static signed char opt_is_inside_work_tree = -1; /* set to TRUE or FALSE */
879 static char opt_editor[SIZEOF_STR] = "";
880 static FILE *opt_tty = NULL;
882 #define is_initial_commit() (!*opt_head_rev)
883 #define is_head_commit(rev) (!strcmp((rev), "HEAD") || !strcmp(opt_head_rev, (rev)))
887 * Line-oriented content detection.
891 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
892 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
893 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
894 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
895 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
896 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
897 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
898 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
899 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
900 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
901 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
902 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
903 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
904 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
905 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
906 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
907 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
908 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
909 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
910 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
911 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
912 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
913 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
914 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
915 LINE(AUTHOR, "author ", COLOR_GREEN, COLOR_DEFAULT, 0), \
916 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
917 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
918 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
919 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
920 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
921 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
922 LINE(DELIMITER, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
923 LINE(DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
924 LINE(MODE, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
925 LINE(LINE_NUMBER, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
926 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
927 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
928 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
929 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
930 LINE(MAIN_LOCAL_TAG,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
931 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
932 LINE(MAIN_TRACKED, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
933 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
934 LINE(MAIN_HEAD, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
935 LINE(MAIN_REVGRAPH,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
936 LINE(TREE_HEAD, "", COLOR_DEFAULT, COLOR_DEFAULT, A_BOLD), \
937 LINE(TREE_DIR, "", COLOR_YELLOW, COLOR_DEFAULT, A_NORMAL), \
938 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
939 LINE(STAT_HEAD, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
940 LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
941 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
942 LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
943 LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
944 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
945 LINE(BLAME_ID, "", COLOR_MAGENTA, COLOR_DEFAULT, 0)
948 #define LINE(type, line, fg, bg, attr) \
956 const char *name; /* Option name. */
957 int namelen; /* Size of option name. */
958 const char *line; /* The start of line to match. */
959 int linelen; /* Size of string to match. */
960 int fg, bg, attr; /* Color and text attributes for the lines. */
963 static struct line_info line_info[] = {
964 #define LINE(type, line, fg, bg, attr) \
965 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
970 static enum line_type
971 get_line_type(const char *line)
973 int linelen = strlen(line);
976 for (type = 0; type < ARRAY_SIZE(line_info); type++)
977 /* Case insensitive search matches Signed-off-by lines better. */
978 if (linelen >= line_info[type].linelen &&
979 !strncasecmp(line_info[type].line, line, line_info[type].linelen))
986 get_line_attr(enum line_type type)
988 assert(type < ARRAY_SIZE(line_info));
989 return COLOR_PAIR(type) | line_info[type].attr;
992 static struct line_info *
993 get_line_info(const char *name)
995 size_t namelen = strlen(name);
998 for (type = 0; type < ARRAY_SIZE(line_info); type++)
999 if (namelen == line_info[type].namelen &&
1000 !string_enum_compare(line_info[type].name, name, namelen))
1001 return &line_info[type];
1009 int default_bg = line_info[LINE_DEFAULT].bg;
1010 int default_fg = line_info[LINE_DEFAULT].fg;
1011 enum line_type type;
1015 if (assume_default_colors(default_fg, default_bg) == ERR) {
1016 default_bg = COLOR_BLACK;
1017 default_fg = COLOR_WHITE;
1020 for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1021 struct line_info *info = &line_info[type];
1022 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1023 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1025 init_pair(type, fg, bg);
1030 enum line_type type;
1033 unsigned int selected:1;
1034 unsigned int dirty:1;
1035 unsigned int cleareol:1;
1037 void *data; /* User data */
1047 enum request request;
1050 static const struct keybinding default_keybindings[] = {
1051 /* View switching */
1052 { 'm', REQ_VIEW_MAIN },
1053 { 'd', REQ_VIEW_DIFF },
1054 { 'l', REQ_VIEW_LOG },
1055 { 't', REQ_VIEW_TREE },
1056 { 'f', REQ_VIEW_BLOB },
1057 { 'B', REQ_VIEW_BLAME },
1058 { 'p', REQ_VIEW_PAGER },
1059 { 'h', REQ_VIEW_HELP },
1060 { 'S', REQ_VIEW_STATUS },
1061 { 'c', REQ_VIEW_STAGE },
1063 /* View manipulation */
1064 { 'q', REQ_VIEW_CLOSE },
1065 { KEY_TAB, REQ_VIEW_NEXT },
1066 { KEY_RETURN, REQ_ENTER },
1067 { KEY_UP, REQ_PREVIOUS },
1068 { KEY_DOWN, REQ_NEXT },
1069 { 'R', REQ_REFRESH },
1070 { KEY_F(5), REQ_REFRESH },
1071 { 'O', REQ_MAXIMIZE },
1073 /* Cursor navigation */
1074 { 'k', REQ_MOVE_UP },
1075 { 'j', REQ_MOVE_DOWN },
1076 { KEY_HOME, REQ_MOVE_FIRST_LINE },
1077 { KEY_END, REQ_MOVE_LAST_LINE },
1078 { KEY_NPAGE, REQ_MOVE_PAGE_DOWN },
1079 { ' ', REQ_MOVE_PAGE_DOWN },
1080 { KEY_PPAGE, REQ_MOVE_PAGE_UP },
1081 { 'b', REQ_MOVE_PAGE_UP },
1082 { '-', REQ_MOVE_PAGE_UP },
1085 { KEY_LEFT, REQ_SCROLL_LEFT },
1086 { KEY_RIGHT, REQ_SCROLL_RIGHT },
1087 { KEY_IC, REQ_SCROLL_LINE_UP },
1088 { KEY_DC, REQ_SCROLL_LINE_DOWN },
1089 { 'w', REQ_SCROLL_PAGE_UP },
1090 { 's', REQ_SCROLL_PAGE_DOWN },
1093 { '/', REQ_SEARCH },
1094 { '?', REQ_SEARCH_BACK },
1095 { 'n', REQ_FIND_NEXT },
1096 { 'N', REQ_FIND_PREV },
1100 { 'z', REQ_STOP_LOADING },
1101 { 'v', REQ_SHOW_VERSION },
1102 { 'r', REQ_SCREEN_REDRAW },
1103 { '.', REQ_TOGGLE_LINENO },
1104 { 'D', REQ_TOGGLE_DATE },
1105 { 'A', REQ_TOGGLE_AUTHOR },
1106 { 'g', REQ_TOGGLE_REV_GRAPH },
1107 { 'F', REQ_TOGGLE_REFS },
1108 { ':', REQ_PROMPT },
1109 { 'u', REQ_STATUS_UPDATE },
1110 { '!', REQ_STATUS_REVERT },
1111 { 'M', REQ_STATUS_MERGE },
1112 { '@', REQ_STAGE_NEXT },
1113 { ',', REQ_PARENT },
1117 #define KEYMAP_INFO \
1131 #define KEYMAP_(name) KEYMAP_##name
1136 static const struct enum_map keymap_table[] = {
1137 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1142 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1144 struct keybinding_table {
1145 struct keybinding *data;
1149 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1152 add_keybinding(enum keymap keymap, enum request request, int key)
1154 struct keybinding_table *table = &keybindings[keymap];
1156 table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1158 die("Failed to allocate keybinding");
1159 table->data[table->size].alias = key;
1160 table->data[table->size++].request = request;
1163 /* Looks for a key binding first in the given map, then in the generic map, and
1164 * lastly in the default keybindings. */
1166 get_keybinding(enum keymap keymap, int key)
1170 for (i = 0; i < keybindings[keymap].size; i++)
1171 if (keybindings[keymap].data[i].alias == key)
1172 return keybindings[keymap].data[i].request;
1174 for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1175 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1176 return keybindings[KEYMAP_GENERIC].data[i].request;
1178 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1179 if (default_keybindings[i].alias == key)
1180 return default_keybindings[i].request;
1182 return (enum request) key;
1191 static const struct key key_table[] = {
1192 { "Enter", KEY_RETURN },
1194 { "Backspace", KEY_BACKSPACE },
1196 { "Escape", KEY_ESC },
1197 { "Left", KEY_LEFT },
1198 { "Right", KEY_RIGHT },
1200 { "Down", KEY_DOWN },
1201 { "Insert", KEY_IC },
1202 { "Delete", KEY_DC },
1204 { "Home", KEY_HOME },
1206 { "PageUp", KEY_PPAGE },
1207 { "PageDown", KEY_NPAGE },
1217 { "F10", KEY_F(10) },
1218 { "F11", KEY_F(11) },
1219 { "F12", KEY_F(12) },
1223 get_key_value(const char *name)
1227 for (i = 0; i < ARRAY_SIZE(key_table); i++)
1228 if (!strcasecmp(key_table[i].name, name))
1229 return key_table[i].value;
1231 if (strlen(name) == 1 && isprint(*name))
1238 get_key_name(int key_value)
1240 static char key_char[] = "'X'";
1241 const char *seq = NULL;
1244 for (key = 0; key < ARRAY_SIZE(key_table); key++)
1245 if (key_table[key].value == key_value)
1246 seq = key_table[key].name;
1250 isprint(key_value)) {
1251 key_char[1] = (char) key_value;
1255 return seq ? seq : "(no key)";
1259 get_key(enum request request)
1261 static char buf[BUFSIZ];
1268 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1269 const struct keybinding *keybinding = &default_keybindings[i];
1271 if (keybinding->request != request)
1274 if (!string_format_from(buf, &pos, "%s%s", sep,
1275 get_key_name(keybinding->alias)))
1276 return "Too many keybindings!";
1283 struct run_request {
1286 const char *argv[SIZEOF_ARG];
1289 static struct run_request *run_request;
1290 static size_t run_requests;
1293 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1295 struct run_request *req;
1297 if (argc >= ARRAY_SIZE(req->argv) - 1)
1300 req = realloc(run_request, (run_requests + 1) * sizeof(*run_request));
1305 req = &run_request[run_requests];
1306 req->keymap = keymap;
1308 req->argv[0] = NULL;
1310 if (!format_argv(req->argv, argv, FORMAT_NONE))
1313 return REQ_NONE + ++run_requests;
1316 static struct run_request *
1317 get_run_request(enum request request)
1319 if (request <= REQ_NONE)
1321 return &run_request[request - REQ_NONE - 1];
1325 add_builtin_run_requests(void)
1327 const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1328 const char *gc[] = { "git", "gc", NULL };
1335 { KEYMAP_MAIN, 'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1336 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1340 for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1343 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1344 if (req != REQ_NONE)
1345 add_keybinding(reqs[i].keymap, req, reqs[i].key);
1350 * User config file handling.
1353 static int config_lineno;
1354 static bool config_errors;
1355 static const char *config_msg;
1357 static const struct enum_map color_map[] = {
1358 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1370 static const struct enum_map attr_map[] = {
1371 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1378 ATTR_MAP(UNDERLINE),
1381 #define set_attribute(attr, name) map_enum(attr, attr_map, name)
1384 parse_int(int *opt, const char *arg, int min, int max)
1386 int value = atoi(arg);
1388 if (min <= value && value <= max) {
1393 config_msg = "Integer value out of bound";
1398 set_color(int *color, const char *name)
1400 if (map_enum(color, color_map, name))
1402 if (!prefixcmp(name, "color"))
1403 return parse_int(color, name + 5, 0, 255) == OK;
1407 /* Wants: object fgcolor bgcolor [attribute] */
1409 option_color_command(int argc, const char *argv[])
1411 struct line_info *info;
1413 if (argc != 3 && argc != 4) {
1414 config_msg = "Wrong number of arguments given to color command";
1418 info = get_line_info(argv[0]);
1420 static const struct enum_map obsolete[] = {
1421 ENUM_MAP("main-delim", LINE_DELIMITER),
1422 ENUM_MAP("main-date", LINE_DATE),
1423 ENUM_MAP("main-author", LINE_AUTHOR),
1427 if (!map_enum(&index, obsolete, argv[0])) {
1428 config_msg = "Unknown color name";
1431 info = &line_info[index];
1434 if (!set_color(&info->fg, argv[1]) ||
1435 !set_color(&info->bg, argv[2])) {
1436 config_msg = "Unknown color";
1440 if (argc == 4 && !set_attribute(&info->attr, argv[3])) {
1441 config_msg = "Unknown attribute";
1448 static int parse_bool(bool *opt, const char *arg)
1450 *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1456 parse_string(char *opt, const char *arg, size_t optsize)
1458 int arglen = strlen(arg);
1463 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1464 config_msg = "Unmatched quotation";
1467 arg += 1; arglen -= 2;
1469 string_ncopy_do(opt, optsize, arg, strlen(arg));
1474 /* Wants: name = value */
1476 option_set_command(int argc, const char *argv[])
1479 config_msg = "Wrong number of arguments given to set command";
1483 if (strcmp(argv[1], "=")) {
1484 config_msg = "No value assigned";
1488 if (!strcmp(argv[0], "show-author"))
1489 return parse_bool(&opt_author, argv[2]);
1491 if (!strcmp(argv[0], "show-date"))
1492 return parse_bool(&opt_date, argv[2]);
1494 if (!strcmp(argv[0], "show-rev-graph"))
1495 return parse_bool(&opt_rev_graph, argv[2]);
1497 if (!strcmp(argv[0], "show-refs"))
1498 return parse_bool(&opt_show_refs, argv[2]);
1500 if (!strcmp(argv[0], "show-line-numbers"))
1501 return parse_bool(&opt_line_number, argv[2]);
1503 if (!strcmp(argv[0], "line-graphics"))
1504 return parse_bool(&opt_line_graphics, argv[2]);
1506 if (!strcmp(argv[0], "line-number-interval"))
1507 return parse_int(&opt_num_interval, argv[2], 1, 1024);
1509 if (!strcmp(argv[0], "author-width"))
1510 return parse_int(&opt_author_cols, argv[2], 0, 1024);
1512 if (!strcmp(argv[0], "tab-size"))
1513 return parse_int(&opt_tab_size, argv[2], 1, 1024);
1515 if (!strcmp(argv[0], "commit-encoding"))
1516 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
1518 config_msg = "Unknown variable name";
1522 /* Wants: mode request key */
1524 option_bind_command(int argc, const char *argv[])
1526 enum request request;
1531 config_msg = "Wrong number of arguments given to bind command";
1535 if (set_keymap(&keymap, argv[0]) == ERR) {
1536 config_msg = "Unknown key map";
1540 key = get_key_value(argv[1]);
1542 config_msg = "Unknown key";
1546 request = get_request(argv[2]);
1547 if (request == REQ_NONE) {
1548 static const struct enum_map obsolete[] = {
1549 ENUM_MAP("cherry-pick", REQ_NONE),
1550 ENUM_MAP("screen-resize", REQ_NONE),
1551 ENUM_MAP("tree-parent", REQ_PARENT),
1555 if (map_enum(&alias, obsolete, argv[2])) {
1556 if (alias != REQ_NONE)
1557 add_keybinding(keymap, alias, key);
1558 config_msg = "Obsolete request name";
1562 if (request == REQ_NONE && *argv[2]++ == '!')
1563 request = add_run_request(keymap, key, argc - 2, argv + 2);
1564 if (request == REQ_NONE) {
1565 config_msg = "Unknown request name";
1569 add_keybinding(keymap, request, key);
1575 set_option(const char *opt, char *value)
1577 const char *argv[SIZEOF_ARG];
1580 if (!argv_from_string(argv, &argc, value)) {
1581 config_msg = "Too many option arguments";
1585 if (!strcmp(opt, "color"))
1586 return option_color_command(argc, argv);
1588 if (!strcmp(opt, "set"))
1589 return option_set_command(argc, argv);
1591 if (!strcmp(opt, "bind"))
1592 return option_bind_command(argc, argv);
1594 config_msg = "Unknown option command";
1599 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1604 config_msg = "Internal error";
1606 /* Check for comment markers, since read_properties() will
1607 * only ensure opt and value are split at first " \t". */
1608 optlen = strcspn(opt, "#");
1612 if (opt[optlen] != 0) {
1613 config_msg = "No option value";
1617 /* Look for comment endings in the value. */
1618 size_t len = strcspn(value, "#");
1620 if (len < valuelen) {
1622 value[valuelen] = 0;
1625 status = set_option(opt, value);
1628 if (status == ERR) {
1629 warn("Error on line %d, near '%.*s': %s",
1630 config_lineno, (int) optlen, opt, config_msg);
1631 config_errors = TRUE;
1634 /* Always keep going if errors are encountered. */
1639 load_option_file(const char *path)
1643 /* It's OK that the file doesn't exist. */
1644 if (!io_open(&io, path))
1648 config_errors = FALSE;
1650 if (io_load(&io, " \t", read_option) == ERR ||
1651 config_errors == TRUE)
1652 warn("Errors while loading %s.", path);
1658 const char *home = getenv("HOME");
1659 const char *tigrc_user = getenv("TIGRC_USER");
1660 const char *tigrc_system = getenv("TIGRC_SYSTEM");
1661 char buf[SIZEOF_STR];
1663 add_builtin_run_requests();
1666 tigrc_system = SYSCONFDIR "/tigrc";
1667 load_option_file(tigrc_system);
1670 if (!home || !string_format(buf, "%s/.tigrc", home))
1674 load_option_file(tigrc_user);
1687 /* The display array of active views and the index of the current view. */
1688 static struct view *display[2];
1689 static unsigned int current_view;
1691 #define foreach_displayed_view(view, i) \
1692 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1694 #define displayed_views() (display[1] != NULL ? 2 : 1)
1696 /* Current head and commit ID */
1697 static char ref_blob[SIZEOF_REF] = "";
1698 static char ref_commit[SIZEOF_REF] = "HEAD";
1699 static char ref_head[SIZEOF_REF] = "HEAD";
1702 const char *name; /* View name */
1703 const char *cmd_env; /* Command line set via environment */
1704 const char *id; /* Points to either of ref_{head,commit,blob} */
1706 struct view_ops *ops; /* View operations */
1708 enum keymap keymap; /* What keymap does this view have */
1709 bool git_dir; /* Whether the view requires a git directory. */
1711 char ref[SIZEOF_REF]; /* Hovered commit reference */
1712 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
1714 int height, width; /* The width and height of the main window */
1715 WINDOW *win; /* The main window */
1716 WINDOW *title; /* The title window living below the main window */
1719 unsigned long offset; /* Offset of the window top */
1720 unsigned long yoffset; /* Offset from the window side. */
1721 unsigned long lineno; /* Current line number */
1722 unsigned long p_offset; /* Previous offset of the window top */
1723 unsigned long p_yoffset;/* Previous offset from the window side */
1724 unsigned long p_lineno; /* Previous current line number */
1725 bool p_restore; /* Should the previous position be restored. */
1728 char grep[SIZEOF_STR]; /* Search string */
1729 regex_t *regex; /* Pre-compiled regexp */
1731 /* If non-NULL, points to the view that opened this view. If this view
1732 * is closed tig will switch back to the parent view. */
1733 struct view *parent;
1736 size_t lines; /* Total number of lines */
1737 struct line *line; /* Line index */
1738 size_t line_alloc; /* Total number of allocated lines */
1739 unsigned int digits; /* Number of digits in the lines member. */
1742 struct line *curline; /* Line currently being drawn. */
1743 enum line_type curtype; /* Attribute currently used for drawing. */
1744 unsigned long col; /* Column when drawing. */
1745 bool has_scrolled; /* View was scrolled. */
1746 bool can_hscroll; /* View can be scrolled horizontally. */
1756 /* What type of content being displayed. Used in the title bar. */
1758 /* Default command arguments. */
1760 /* Open and reads in all view content. */
1761 bool (*open)(struct view *view);
1762 /* Read one line; updates view->line. */
1763 bool (*read)(struct view *view, char *data);
1764 /* Draw one line; @lineno must be < view->height. */
1765 bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
1766 /* Depending on view handle a special requests. */
1767 enum request (*request)(struct view *view, enum request request, struct line *line);
1768 /* Search for regexp in a line. */
1769 bool (*grep)(struct view *view, struct line *line);
1771 void (*select)(struct view *view, struct line *line);
1774 static struct view_ops blame_ops;
1775 static struct view_ops blob_ops;
1776 static struct view_ops diff_ops;
1777 static struct view_ops help_ops;
1778 static struct view_ops log_ops;
1779 static struct view_ops main_ops;
1780 static struct view_ops pager_ops;
1781 static struct view_ops stage_ops;
1782 static struct view_ops status_ops;
1783 static struct view_ops tree_ops;
1785 #define VIEW_STR(name, env, ref, ops, map, git) \
1786 { name, #env, ref, ops, map, git }
1788 #define VIEW_(id, name, ops, git, ref) \
1789 VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
1792 static struct view views[] = {
1793 VIEW_(MAIN, "main", &main_ops, TRUE, ref_head),
1794 VIEW_(DIFF, "diff", &diff_ops, TRUE, ref_commit),
1795 VIEW_(LOG, "log", &log_ops, TRUE, ref_head),
1796 VIEW_(TREE, "tree", &tree_ops, TRUE, ref_commit),
1797 VIEW_(BLOB, "blob", &blob_ops, TRUE, ref_blob),
1798 VIEW_(BLAME, "blame", &blame_ops, TRUE, ref_commit),
1799 VIEW_(HELP, "help", &help_ops, FALSE, ""),
1800 VIEW_(PAGER, "pager", &pager_ops, FALSE, "stdin"),
1801 VIEW_(STATUS, "status", &status_ops, TRUE, ""),
1802 VIEW_(STAGE, "stage", &stage_ops, TRUE, ""),
1805 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
1806 #define VIEW_REQ(view) ((view) - views + REQ_OFFSET + 1)
1808 #define foreach_view(view, i) \
1809 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1811 #define view_is_displayed(view) \
1812 (view == display[0] || view == display[1])
1819 static int line_graphics[] = {
1820 /* LINE_GRAPHIC_VLINE: */ '|'
1824 set_view_attr(struct view *view, enum line_type type)
1826 if (!view->curline->selected && view->curtype != type) {
1827 wattrset(view->win, get_line_attr(type));
1828 wchgat(view->win, -1, 0, type, NULL);
1829 view->curtype = type;
1834 draw_chars(struct view *view, enum line_type type, const char *string,
1835 int max_len, bool use_tilde)
1839 int trimmed = FALSE;
1840 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
1846 len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde);
1848 col = len = strlen(string);
1849 if (len > max_len) {
1853 col = len = max_len;
1858 set_view_attr(view, type);
1860 waddnstr(view->win, string, len);
1861 if (trimmed && use_tilde) {
1862 set_view_attr(view, LINE_DELIMITER);
1863 waddch(view->win, '~');
1867 if (view->col + col >= view->width + view->yoffset)
1868 view->can_hscroll = TRUE;
1874 draw_space(struct view *view, enum line_type type, int max, int spaces)
1876 static char space[] = " ";
1879 spaces = MIN(max, spaces);
1881 while (spaces > 0) {
1882 int len = MIN(spaces, sizeof(space) - 1);
1884 col += draw_chars(view, type, space, spaces, FALSE);
1892 draw_lineno(struct view *view, unsigned int lineno)
1894 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
1896 int digits3 = view->digits < 3 ? 3 : view->digits;
1897 int max_number = MIN(digits3, STRING_SIZE(number));
1898 int max = view->width - view->col;
1901 if (max < max_number)
1904 lineno += view->offset + 1;
1905 if (lineno == 1 || (lineno % opt_num_interval) == 0) {
1906 static char fmt[] = "%1ld";
1908 if (view->digits <= 9)
1909 fmt[1] = '0' + digits3;
1911 if (!string_format(number, fmt, lineno))
1913 col = draw_chars(view, LINE_LINE_NUMBER, number, max_number, TRUE);
1915 col = draw_space(view, LINE_LINE_NUMBER, max_number, max_number);
1918 if (col < max && skip <= col) {
1919 set_view_attr(view, LINE_DEFAULT);
1920 waddch(view->win, line_graphics[LINE_GRAPHIC_VLINE]);
1925 if (col < max && skip <= col)
1926 col = draw_space(view, LINE_DEFAULT, max - col, 1);
1929 return view->width + view->yoffset <= view->col;
1933 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
1935 view->col += draw_chars(view, type, string, view->width + view->yoffset - view->col, trim);
1936 return view->width - view->col <= 0;
1940 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
1942 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
1943 int max = view->width - view->col;
1949 set_view_attr(view, type);
1950 /* Using waddch() instead of waddnstr() ensures that
1951 * they'll be rendered correctly for the cursor line. */
1952 for (i = skip; i < size; i++)
1953 waddch(view->win, graphic[i]);
1956 if (size < max && skip <= size)
1957 waddch(view->win, ' ');
1960 return view->width - view->col <= 0;
1964 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
1966 int max = MIN(view->width - view->col, len);
1970 col = draw_chars(view, type, text, max - 1, trim);
1972 col = draw_space(view, type, max - 1, max - 1);
1975 view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
1976 return view->width + view->yoffset <= view->col;
1980 draw_date(struct view *view, struct tm *time)
1982 char buf[DATE_COLS];
1987 timelen = strftime(buf, sizeof(buf), DATE_FORMAT, time);
1988 date = timelen ? buf : NULL;
1990 return draw_field(view, LINE_DATE, date, DATE_COLS, FALSE);
1994 draw_author(struct view *view, const char *author)
1996 bool trim = opt_author_cols == 0 || opt_author_cols > 5 || !author;
1999 static char initials[10];
2002 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@')
2004 memset(initials, 0, sizeof(initials));
2005 for (pos = 0; *author && pos < opt_author_cols - 1; author++, pos++) {
2006 while (is_initial_sep(*author))
2008 strncpy(&initials[pos], author, sizeof(initials) - 1 - pos);
2009 while (*author && !is_initial_sep(author[1]))
2016 return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2020 draw_mode(struct view *view, mode_t mode)
2022 static const char dir_mode[] = "drwxr-xr-x";
2023 static const char link_mode[] = "lrwxrwxrwx";
2024 static const char exe_mode[] = "-rwxr-xr-x";
2025 static const char file_mode[] = "-rw-r--r--";
2030 else if (S_ISLNK(mode))
2032 else if (mode & S_IXUSR)
2037 return draw_field(view, LINE_MODE, str, sizeof(file_mode), FALSE);
2041 draw_view_line(struct view *view, unsigned int lineno)
2044 bool selected = (view->offset + lineno == view->lineno);
2046 assert(view_is_displayed(view));
2048 if (view->offset + lineno >= view->lines)
2051 line = &view->line[view->offset + lineno];
2053 wmove(view->win, lineno, 0);
2055 wclrtoeol(view->win);
2057 view->curline = line;
2058 view->curtype = LINE_NONE;
2059 line->selected = FALSE;
2060 line->dirty = line->cleareol = 0;
2063 set_view_attr(view, LINE_CURSOR);
2064 line->selected = TRUE;
2065 view->ops->select(view, line);
2068 return view->ops->draw(view, line, lineno);
2072 redraw_view_dirty(struct view *view)
2077 for (lineno = 0; lineno < view->height; lineno++) {
2078 if (view->offset + lineno >= view->lines)
2080 if (!view->line[view->offset + lineno].dirty)
2083 if (!draw_view_line(view, lineno))
2089 wnoutrefresh(view->win);
2093 redraw_view_from(struct view *view, int lineno)
2095 assert(0 <= lineno && lineno < view->height);
2098 view->can_hscroll = FALSE;
2100 for (; lineno < view->height; lineno++) {
2101 if (!draw_view_line(view, lineno))
2105 wnoutrefresh(view->win);
2109 redraw_view(struct view *view)
2112 redraw_view_from(view, 0);
2117 update_view_title(struct view *view)
2119 char buf[SIZEOF_STR];
2120 char state[SIZEOF_STR];
2121 size_t bufpos = 0, statelen = 0;
2123 assert(view_is_displayed(view));
2125 if (view != VIEW(REQ_VIEW_STATUS) && view->lines) {
2126 unsigned int view_lines = view->offset + view->height;
2127 unsigned int lines = view->lines
2128 ? MIN(view_lines, view->lines) * 100 / view->lines
2131 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2140 time_t secs = time(NULL) - view->start_time;
2142 /* Three git seconds are a long time ... */
2144 string_format_from(state, &statelen, " loading %lds", secs);
2147 string_format_from(buf, &bufpos, "[%s]", view->name);
2148 if (*view->ref && bufpos < view->width) {
2149 size_t refsize = strlen(view->ref);
2150 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2152 if (minsize < view->width)
2153 refsize = view->width - minsize + 7;
2154 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2157 if (statelen && bufpos < view->width) {
2158 string_format_from(buf, &bufpos, "%s", state);
2161 if (view == display[current_view])
2162 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2164 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2166 mvwaddnstr(view->title, 0, 0, buf, bufpos);
2167 wclrtoeol(view->title);
2168 wnoutrefresh(view->title);
2172 resize_display(void)
2175 struct view *base = display[0];
2176 struct view *view = display[1] ? display[1] : display[0];
2178 /* Setup window dimensions */
2180 getmaxyx(stdscr, base->height, base->width);
2182 /* Make room for the status window. */
2186 /* Horizontal split. */
2187 view->width = base->width;
2188 view->height = SCALE_SPLIT_VIEW(base->height);
2189 base->height -= view->height;
2191 /* Make room for the title bar. */
2195 /* Make room for the title bar. */
2200 foreach_displayed_view (view, i) {
2202 view->win = newwin(view->height, 0, offset, 0);
2204 die("Failed to create %s view", view->name);
2206 scrollok(view->win, FALSE);
2208 view->title = newwin(1, 0, offset + view->height, 0);
2210 die("Failed to create title window");
2213 wresize(view->win, view->height, view->width);
2214 mvwin(view->win, offset, 0);
2215 mvwin(view->title, offset + view->height, 0);
2218 offset += view->height + 1;
2223 redraw_display(bool clear)
2228 foreach_displayed_view (view, i) {
2232 update_view_title(view);
2237 toggle_view_option(bool *option, const char *help)
2240 redraw_display(FALSE);
2241 report("%sabling %s", *option ? "En" : "Dis", help);
2245 maximize_view(struct view *view)
2247 memset(display, 0, sizeof(display));
2249 display[current_view] = view;
2251 redraw_display(FALSE);
2261 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2263 if (lineno >= view->lines)
2264 lineno = view->lines > 0 ? view->lines - 1 : 0;
2266 if (offset > lineno || offset + view->height <= lineno) {
2267 unsigned long half = view->height / 2;
2270 offset = lineno - half;
2275 if (offset != view->offset || lineno != view->lineno) {
2276 view->offset = offset;
2277 view->lineno = lineno;
2284 /* Scrolling backend */
2286 do_scroll_view(struct view *view, int lines)
2288 bool redraw_current_line = FALSE;
2290 /* The rendering expects the new offset. */
2291 view->offset += lines;
2293 assert(0 <= view->offset && view->offset < view->lines);
2296 /* Move current line into the view. */
2297 if (view->lineno < view->offset) {
2298 view->lineno = view->offset;
2299 redraw_current_line = TRUE;
2300 } else if (view->lineno >= view->offset + view->height) {
2301 view->lineno = view->offset + view->height - 1;
2302 redraw_current_line = TRUE;
2305 assert(view->offset <= view->lineno && view->lineno < view->lines);
2307 /* Redraw the whole screen if scrolling is pointless. */
2308 if (view->height < ABS(lines)) {
2312 int line = lines > 0 ? view->height - lines : 0;
2313 int end = line + ABS(lines);
2315 scrollok(view->win, TRUE);
2316 wscrl(view->win, lines);
2317 scrollok(view->win, FALSE);
2319 while (line < end && draw_view_line(view, line))
2322 if (redraw_current_line)
2323 draw_view_line(view, view->lineno - view->offset);
2324 wnoutrefresh(view->win);
2327 view->has_scrolled = TRUE;
2331 /* Scroll frontend */
2333 scroll_view(struct view *view, enum request request)
2337 assert(view_is_displayed(view));
2340 case REQ_SCROLL_LEFT:
2341 if (view->yoffset == 0) {
2342 report("Cannot scroll beyond the first column");
2345 if (view->yoffset <= SCROLL_INTERVAL)
2348 view->yoffset -= SCROLL_INTERVAL;
2349 redraw_view_from(view, 0);
2352 case REQ_SCROLL_RIGHT:
2353 if (!view->can_hscroll) {
2354 report("Cannot scroll beyond the last column");
2357 view->yoffset += SCROLL_INTERVAL;
2361 case REQ_SCROLL_PAGE_DOWN:
2362 lines = view->height;
2363 case REQ_SCROLL_LINE_DOWN:
2364 if (view->offset + lines > view->lines)
2365 lines = view->lines - view->offset;
2367 if (lines == 0 || view->offset + view->height >= view->lines) {
2368 report("Cannot scroll beyond the last line");
2373 case REQ_SCROLL_PAGE_UP:
2374 lines = view->height;
2375 case REQ_SCROLL_LINE_UP:
2376 if (lines > view->offset)
2377 lines = view->offset;
2380 report("Cannot scroll beyond the first line");
2388 die("request %d not handled in switch", request);
2391 do_scroll_view(view, lines);
2396 move_view(struct view *view, enum request request)
2398 int scroll_steps = 0;
2402 case REQ_MOVE_FIRST_LINE:
2403 steps = -view->lineno;
2406 case REQ_MOVE_LAST_LINE:
2407 steps = view->lines - view->lineno - 1;
2410 case REQ_MOVE_PAGE_UP:
2411 steps = view->height > view->lineno
2412 ? -view->lineno : -view->height;
2415 case REQ_MOVE_PAGE_DOWN:
2416 steps = view->lineno + view->height >= view->lines
2417 ? view->lines - view->lineno - 1 : view->height;
2429 die("request %d not handled in switch", request);
2432 if (steps <= 0 && view->lineno == 0) {
2433 report("Cannot move beyond the first line");
2436 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2437 report("Cannot move beyond the last line");
2441 /* Move the current line */
2442 view->lineno += steps;
2443 assert(0 <= view->lineno && view->lineno < view->lines);
2445 /* Check whether the view needs to be scrolled */
2446 if (view->lineno < view->offset ||
2447 view->lineno >= view->offset + view->height) {
2448 scroll_steps = steps;
2449 if (steps < 0 && -steps > view->offset) {
2450 scroll_steps = -view->offset;
2452 } else if (steps > 0) {
2453 if (view->lineno == view->lines - 1 &&
2454 view->lines > view->height) {
2455 scroll_steps = view->lines - view->offset - 1;
2456 if (scroll_steps >= view->height)
2457 scroll_steps -= view->height - 1;
2462 if (!view_is_displayed(view)) {
2463 view->offset += scroll_steps;
2464 assert(0 <= view->offset && view->offset < view->lines);
2465 view->ops->select(view, &view->line[view->lineno]);
2469 /* Repaint the old "current" line if we be scrolling */
2470 if (ABS(steps) < view->height)
2471 draw_view_line(view, view->lineno - steps - view->offset);
2474 do_scroll_view(view, scroll_steps);
2478 /* Draw the current line */
2479 draw_view_line(view, view->lineno - view->offset);
2481 wnoutrefresh(view->win);
2490 static void search_view(struct view *view, enum request request);
2493 select_view_line(struct view *view, unsigned long lineno)
2495 unsigned long old_lineno = view->lineno;
2496 unsigned long old_offset = view->offset;
2498 if (goto_view_line(view, view->offset, lineno)) {
2499 if (view_is_displayed(view)) {
2500 if (old_offset != view->offset) {
2503 draw_view_line(view, old_lineno - view->offset);
2504 draw_view_line(view, view->lineno - view->offset);
2505 wnoutrefresh(view->win);
2508 view->ops->select(view, &view->line[view->lineno]);
2514 find_next(struct view *view, enum request request)
2516 unsigned long lineno = view->lineno;
2521 report("No previous search");
2523 search_view(view, request);
2533 case REQ_SEARCH_BACK:
2542 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2543 lineno += direction;
2545 /* Note, lineno is unsigned long so will wrap around in which case it
2546 * will become bigger than view->lines. */
2547 for (; lineno < view->lines; lineno += direction) {
2548 if (view->ops->grep(view, &view->line[lineno])) {
2549 select_view_line(view, lineno);
2550 report("Line %ld matches '%s'", lineno + 1, view->grep);
2555 report("No match found for '%s'", view->grep);
2559 search_view(struct view *view, enum request request)
2564 regfree(view->regex);
2567 view->regex = calloc(1, sizeof(*view->regex));
2572 regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2573 if (regex_err != 0) {
2574 char buf[SIZEOF_STR] = "unknown error";
2576 regerror(regex_err, view->regex, buf, sizeof(buf));
2577 report("Search failed: %s", buf);
2581 string_copy(view->grep, opt_search);
2583 find_next(view, request);
2587 * Incremental updating
2591 reset_view(struct view *view)
2595 for (i = 0; i < view->lines; i++)
2596 free(view->line[i].data);
2599 view->p_offset = view->offset;
2600 view->p_yoffset = view->yoffset;
2601 view->p_lineno = view->lineno;
2608 view->line_alloc = 0;
2610 view->update_secs = 0;
2614 free_argv(const char *argv[])
2618 for (argc = 0; argv[argc]; argc++)
2619 free((void *) argv[argc]);
2623 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
2625 char buf[SIZEOF_STR];
2627 bool noreplace = flags == FORMAT_NONE;
2629 free_argv(dst_argv);
2631 for (argc = 0; src_argv[argc]; argc++) {
2632 const char *arg = src_argv[argc];
2636 char *next = strstr(arg, "%(");
2637 int len = next - arg;
2640 if (!next || noreplace) {
2641 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
2646 } else if (!prefixcmp(next, "%(directory)")) {
2649 } else if (!prefixcmp(next, "%(file)")) {
2652 } else if (!prefixcmp(next, "%(ref)")) {
2653 value = *opt_ref ? opt_ref : "HEAD";
2655 } else if (!prefixcmp(next, "%(head)")) {
2658 } else if (!prefixcmp(next, "%(commit)")) {
2661 } else if (!prefixcmp(next, "%(blob)")) {
2665 report("Unknown replacement: `%s`", next);
2669 if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
2672 arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
2675 dst_argv[argc] = strdup(buf);
2676 if (!dst_argv[argc])
2680 dst_argv[argc] = NULL;
2682 return src_argv[argc] == NULL;
2686 restore_view_position(struct view *view)
2688 if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
2691 /* Changing the view position cancels the restoring. */
2692 /* FIXME: Changing back to the first line is not detected. */
2693 if (view->offset != 0 || view->lineno != 0) {
2694 view->p_restore = FALSE;
2698 if (goto_view_line(view, view->p_offset, view->p_lineno) &&
2699 view_is_displayed(view))
2702 view->yoffset = view->p_yoffset;
2703 view->p_restore = FALSE;
2709 end_update(struct view *view, bool force)
2713 while (!view->ops->read(view, NULL))
2716 set_nonblocking_input(FALSE);
2718 kill_io(view->pipe);
2719 done_io(view->pipe);
2724 setup_update(struct view *view, const char *vid)
2726 set_nonblocking_input(TRUE);
2728 string_copy_rev(view->vid, vid);
2729 view->pipe = &view->io;
2730 view->start_time = time(NULL);
2734 prepare_update(struct view *view, const char *argv[], const char *dir,
2735 enum format_flags flags)
2738 end_update(view, TRUE);
2739 return init_io_rd(&view->io, argv, dir, flags);
2743 prepare_update_file(struct view *view, const char *name)
2746 end_update(view, TRUE);
2747 return io_open(&view->io, name);
2751 begin_update(struct view *view, bool refresh)
2754 end_update(view, TRUE);
2757 if (!start_io(&view->io))
2761 if (view == VIEW(REQ_VIEW_TREE) && strcmp(view->vid, view->id))
2764 if (!run_io_rd(&view->io, view->ops->argv, FORMAT_ALL))
2767 /* Put the current ref_* value to the view title ref
2768 * member. This is needed by the blob view. Most other
2769 * views sets it automatically after loading because the
2770 * first line is a commit line. */
2771 string_copy_rev(view->ref, view->id);
2774 setup_update(view, view->id);
2779 #define ITEM_CHUNK_SIZE 256
2781 realloc_items(void *mem, size_t *size, size_t new_size, size_t item_size)
2783 size_t num_chunks = *size / ITEM_CHUNK_SIZE;
2784 size_t num_chunks_new = (new_size + ITEM_CHUNK_SIZE - 1) / ITEM_CHUNK_SIZE;
2786 if (mem == NULL || num_chunks != num_chunks_new) {
2787 *size = num_chunks_new * ITEM_CHUNK_SIZE;
2788 mem = realloc(mem, *size * item_size);
2794 static struct line *
2795 realloc_lines(struct view *view, size_t line_size)
2797 size_t alloc = view->line_alloc;
2798 struct line *tmp = realloc_items(view->line, &alloc, line_size,
2799 sizeof(*view->line));
2805 view->line_alloc = alloc;
2810 update_view(struct view *view)
2812 char out_buffer[BUFSIZ * 2];
2814 /* Clear the view and redraw everything since the tree sorting
2815 * might have rearranged things. */
2816 bool redraw = view->lines == 0;
2817 bool can_read = TRUE;
2822 if (!io_can_read(view->pipe)) {
2823 if (view->lines == 0) {
2824 time_t secs = time(NULL) - view->start_time;
2826 if (secs > 1 && secs > view->update_secs) {
2827 if (view->update_secs == 0)
2829 update_view_title(view);
2830 view->update_secs = secs;
2836 for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
2837 if (opt_iconv != ICONV_NONE) {
2838 ICONV_CONST char *inbuf = line;
2839 size_t inlen = strlen(line) + 1;
2841 char *outbuf = out_buffer;
2842 size_t outlen = sizeof(out_buffer);
2846 ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2847 if (ret != (size_t) -1)
2851 if (!view->ops->read(view, line)) {
2852 report("Allocation failure");
2853 end_update(view, TRUE);
2859 unsigned long lines = view->lines;
2862 for (digits = 0; lines; digits++)
2865 /* Keep the displayed view in sync with line number scaling. */
2866 if (digits != view->digits) {
2867 view->digits = digits;
2868 if (opt_line_number || view == VIEW(REQ_VIEW_BLAME))
2873 if (io_error(view->pipe)) {
2874 report("Failed to read: %s", io_strerror(view->pipe));
2875 end_update(view, TRUE);
2877 } else if (io_eof(view->pipe)) {
2879 end_update(view, FALSE);
2882 if (restore_view_position(view))
2885 if (!view_is_displayed(view))
2889 redraw_view_from(view, 0);
2891 redraw_view_dirty(view);
2893 /* Update the title _after_ the redraw so that if the redraw picks up a
2894 * commit reference in view->ref it'll be available here. */
2895 update_view_title(view);
2899 static struct line *
2900 add_line_data(struct view *view, void *data, enum line_type type)
2904 if (!realloc_lines(view, view->lines + 1))
2907 line = &view->line[view->lines++];
2908 memset(line, 0, sizeof(*line));
2916 static struct line *
2917 add_line_text(struct view *view, const char *text, enum line_type type)
2919 char *data = text ? strdup(text) : NULL;
2921 return data ? add_line_data(view, data, type) : NULL;
2924 static struct line *
2925 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
2927 char buf[SIZEOF_STR];
2930 va_start(args, fmt);
2931 if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
2935 return buf[0] ? add_line_text(view, buf, type) : NULL;
2943 OPEN_DEFAULT = 0, /* Use default view switching. */
2944 OPEN_SPLIT = 1, /* Split current view. */
2945 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
2946 OPEN_REFRESH = 16, /* Refresh view using previous command. */
2947 OPEN_PREPARED = 32, /* Open already prepared command. */
2951 open_view(struct view *prev, enum request request, enum open_flags flags)
2953 bool split = !!(flags & OPEN_SPLIT);
2954 bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
2955 bool nomaximize = !!(flags & OPEN_REFRESH);
2956 struct view *view = VIEW(request);
2957 int nviews = displayed_views();
2958 struct view *base_view = display[0];
2960 if (view == prev && nviews == 1 && !reload) {
2961 report("Already in %s view", view->name);
2965 if (view->git_dir && !opt_git_dir[0]) {
2966 report("The %s view is disabled in pager view", view->name);
2973 } else if (!nomaximize) {
2974 /* Maximize the current view. */
2975 memset(display, 0, sizeof(display));
2977 display[current_view] = view;
2980 /* Resize the view when switching between split- and full-screen,
2981 * or when switching between two different full-screen views. */
2982 if (nviews != displayed_views() ||
2983 (nviews == 1 && base_view != display[0]))
2986 if (view->ops->open) {
2988 end_update(view, TRUE);
2989 if (!view->ops->open(view)) {
2990 report("Failed to load %s view", view->name);
2993 restore_view_position(view);
2995 } else if ((reload || strcmp(view->vid, view->id)) &&
2996 !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
2997 report("Failed to load %s view", view->name);
3001 if (split && prev->lineno - prev->offset >= prev->height) {
3002 /* Take the title line into account. */
3003 int lines = prev->lineno - prev->offset - prev->height + 1;
3005 /* Scroll the view that was split if the current line is
3006 * outside the new limited view. */
3007 do_scroll_view(prev, lines);
3010 if (prev && view != prev) {
3012 /* "Blur" the previous view. */
3013 update_view_title(prev);
3016 view->parent = prev;
3019 if (view->pipe && view->lines == 0) {
3020 /* Clear the old view and let the incremental updating refill
3023 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3025 } else if (view_is_displayed(view)) {
3032 open_external_viewer(const char *argv[], const char *dir)
3034 def_prog_mode(); /* save current tty modes */
3035 endwin(); /* restore original tty modes */
3036 run_io_fg(argv, dir);
3037 fprintf(stderr, "Press Enter to continue");
3040 redraw_display(TRUE);
3044 open_mergetool(const char *file)
3046 const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3048 open_external_viewer(mergetool_argv, opt_cdup);
3052 open_editor(bool from_root, const char *file)
3054 const char *editor_argv[] = { "vi", file, NULL };
3057 editor = getenv("GIT_EDITOR");
3058 if (!editor && *opt_editor)
3059 editor = opt_editor;
3061 editor = getenv("VISUAL");
3063 editor = getenv("EDITOR");
3067 editor_argv[0] = editor;
3068 open_external_viewer(editor_argv, from_root ? opt_cdup : NULL);
3072 open_run_request(enum request request)
3074 struct run_request *req = get_run_request(request);
3075 const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3078 report("Unknown run request");
3082 if (format_argv(argv, req->argv, FORMAT_ALL))
3083 open_external_viewer(argv, NULL);
3088 * User request switch noodle
3092 view_driver(struct view *view, enum request request)
3096 if (request == REQ_NONE) {
3101 if (request > REQ_NONE) {
3102 open_run_request(request);
3103 /* FIXME: When all views can refresh always do this. */
3104 if (view == VIEW(REQ_VIEW_STATUS) ||
3105 view == VIEW(REQ_VIEW_MAIN) ||
3106 view == VIEW(REQ_VIEW_LOG) ||
3107 view == VIEW(REQ_VIEW_STAGE))
3108 request = REQ_REFRESH;
3113 if (view && view->lines) {
3114 request = view->ops->request(view, request, &view->line[view->lineno]);
3115 if (request == REQ_NONE)
3122 case REQ_MOVE_PAGE_UP:
3123 case REQ_MOVE_PAGE_DOWN:
3124 case REQ_MOVE_FIRST_LINE:
3125 case REQ_MOVE_LAST_LINE:
3126 move_view(view, request);
3129 case REQ_SCROLL_LEFT:
3130 case REQ_SCROLL_RIGHT:
3131 case REQ_SCROLL_LINE_DOWN:
3132 case REQ_SCROLL_LINE_UP:
3133 case REQ_SCROLL_PAGE_DOWN:
3134 case REQ_SCROLL_PAGE_UP:
3135 scroll_view(view, request);
3138 case REQ_VIEW_BLAME:
3140 report("No file chosen, press %s to open tree view",
3141 get_key(REQ_VIEW_TREE));
3144 open_view(view, request, OPEN_DEFAULT);
3149 report("No file chosen, press %s to open tree view",
3150 get_key(REQ_VIEW_TREE));
3153 open_view(view, request, OPEN_DEFAULT);
3156 case REQ_VIEW_PAGER:
3157 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3158 report("No pager content, press %s to run command from prompt",
3159 get_key(REQ_PROMPT));
3162 open_view(view, request, OPEN_DEFAULT);
3165 case REQ_VIEW_STAGE:
3166 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3167 report("No stage content, press %s to open the status view and choose file",
3168 get_key(REQ_VIEW_STATUS));
3171 open_view(view, request, OPEN_DEFAULT);
3174 case REQ_VIEW_STATUS:
3175 if (opt_is_inside_work_tree == FALSE) {
3176 report("The status view requires a working tree");
3179 open_view(view, request, OPEN_DEFAULT);
3187 open_view(view, request, OPEN_DEFAULT);
3192 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3194 if ((view == VIEW(REQ_VIEW_DIFF) &&
3195 view->parent == VIEW(REQ_VIEW_MAIN)) ||
3196 (view == VIEW(REQ_VIEW_DIFF) &&
3197 view->parent == VIEW(REQ_VIEW_BLAME)) ||
3198 (view == VIEW(REQ_VIEW_STAGE) &&
3199 view->parent == VIEW(REQ_VIEW_STATUS)) ||
3200 (view == VIEW(REQ_VIEW_BLOB) &&
3201 view->parent == VIEW(REQ_VIEW_TREE))) {
3204 view = view->parent;
3205 line = view->lineno;
3206 move_view(view, request);
3207 if (view_is_displayed(view))
3208 update_view_title(view);
3209 if (line != view->lineno)
3210 view->ops->request(view, REQ_ENTER,
3211 &view->line[view->lineno]);
3214 move_view(view, request);
3220 int nviews = displayed_views();
3221 int next_view = (current_view + 1) % nviews;
3223 if (next_view == current_view) {
3224 report("Only one view is displayed");
3228 current_view = next_view;
3229 /* Blur out the title of the previous view. */
3230 update_view_title(view);
3235 report("Refreshing is not yet supported for the %s view", view->name);
3239 if (displayed_views() == 2)
3240 maximize_view(view);
3243 case REQ_TOGGLE_LINENO:
3244 toggle_view_option(&opt_line_number, "line numbers");
3247 case REQ_TOGGLE_DATE:
3248 toggle_view_option(&opt_date, "date display");
3251 case REQ_TOGGLE_AUTHOR:
3252 toggle_view_option(&opt_author, "author display");
3255 case REQ_TOGGLE_REV_GRAPH:
3256 toggle_view_option(&opt_rev_graph, "revision graph display");
3259 case REQ_TOGGLE_REFS:
3260 toggle_view_option(&opt_show_refs, "reference display");
3264 case REQ_SEARCH_BACK:
3265 search_view(view, request);
3270 find_next(view, request);
3273 case REQ_STOP_LOADING:
3274 for (i = 0; i < ARRAY_SIZE(views); i++) {
3277 report("Stopped loading the %s view", view->name),
3278 end_update(view, TRUE);
3282 case REQ_SHOW_VERSION:
3283 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3286 case REQ_SCREEN_REDRAW:
3287 redraw_display(TRUE);
3291 report("Nothing to edit");
3295 report("Nothing to enter");
3298 case REQ_VIEW_CLOSE:
3299 /* XXX: Mark closed views by letting view->parent point to the
3300 * view itself. Parents to closed view should never be
3303 view->parent->parent != view->parent) {
3304 maximize_view(view->parent);
3305 view->parent = view;
3313 report("Unknown key, press 'h' for help");
3322 * View backend utilities
3326 parse_timezone(time_t *time, const char *zone)
3330 tz = ('0' - zone[1]) * 60 * 60 * 10;
3331 tz += ('0' - zone[2]) * 60 * 60;
3332 tz += ('0' - zone[3]) * 60;
3333 tz += ('0' - zone[4]);
3341 /* Parse author lines where the name may be empty:
3342 * author <email@address.tld> 1138474660 +0100
3345 parse_author_line(char *ident, char *author, size_t authorsize, struct tm *tm)
3347 char *nameend = strchr(ident, '<');
3348 char *emailend = strchr(ident, '>');
3350 if (nameend && emailend)
3351 *nameend = *emailend = 0;
3352 ident = chomp_string(ident);
3355 ident = chomp_string(nameend + 1);
3360 string_ncopy_do(author, authorsize, ident, strlen(ident));
3362 /* Parse epoch and timezone */
3363 if (emailend && emailend[1] == ' ') {
3364 char *secs = emailend + 2;
3365 char *zone = strchr(secs, ' ');
3366 time_t time = (time_t) atol(secs);
3368 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
3369 parse_timezone(&time, zone + 1);
3371 gmtime_r(&time, tm);
3375 static enum input_status
3376 select_commit_parent_handler(void *data, char *buf, int c)
3378 size_t parents = *(size_t *) data;
3385 parent = atoi(buf) * 10;
3388 if (parent > parents)
3394 select_commit_parent(const char *id, char rev[SIZEOF_REV], const char *path)
3396 char buf[SIZEOF_STR * 4];
3397 const char *revlist_argv[] = {
3398 "git", "rev-list", "-1", "--parents", id, "--", path, NULL
3402 if (!run_io_buf(revlist_argv, buf, sizeof(buf)) ||
3403 !*chomp_string(buf) ||
3404 (parents = (strlen(buf) / 40) - 1) < 0) {
3405 report("Failed to get parent information");
3408 } else if (parents == 0) {
3410 report("Path '%s' does not exist in the parent", path);
3412 report("The selected commit has no parents");
3417 char prompt[SIZEOF_STR];
3420 if (!string_format(prompt, "Which parent? [1..%d] ", parents))
3422 result = prompt_input(prompt, select_commit_parent_handler, &parents);
3425 parents = atoi(result);
3428 string_copy_rev(rev, &buf[41 * parents]);
3437 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3439 char text[SIZEOF_STR];
3441 if (opt_line_number && draw_lineno(view, lineno))
3444 string_expand(text, sizeof(text), line->data, opt_tab_size);
3445 draw_text(view, line->type, text, TRUE);
3450 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3452 const char *describe_argv[] = { "git", "describe", commit_id, NULL };
3453 char refbuf[SIZEOF_STR];
3456 if (run_io_buf(describe_argv, refbuf, sizeof(refbuf)))
3457 ref = chomp_string(refbuf);
3462 /* This is the only fatal call, since it can "corrupt" the buffer. */
3463 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
3470 add_pager_refs(struct view *view, struct line *line)
3472 char buf[SIZEOF_STR];
3473 char *commit_id = (char *)line->data + STRING_SIZE("commit ");
3475 size_t bufpos = 0, refpos = 0;
3476 const char *sep = "Refs: ";
3477 bool is_tag = FALSE;
3479 assert(line->type == LINE_COMMIT);
3481 refs = get_refs(commit_id);
3483 if (view == VIEW(REQ_VIEW_DIFF))
3484 goto try_add_describe_ref;
3489 struct ref *ref = refs[refpos];
3490 const char *fmt = ref->tag ? "%s[%s]" :
3491 ref->remote ? "%s<%s>" : "%s%s";
3493 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
3498 } while (refs[refpos++]->next);
3500 if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
3501 try_add_describe_ref:
3502 /* Add <tag>-g<commit_id> "fake" reference. */
3503 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
3510 add_line_text(view, buf, LINE_PP_REFS);
3514 pager_read(struct view *view, char *data)
3521 line = add_line_text(view, data, get_line_type(data));
3525 if (line->type == LINE_COMMIT &&
3526 (view == VIEW(REQ_VIEW_DIFF) ||
3527 view == VIEW(REQ_VIEW_LOG)))
3528 add_pager_refs(view, line);
3534 pager_request(struct view *view, enum request request, struct line *line)
3538 if (request != REQ_ENTER)
3541 if (line->type == LINE_COMMIT &&
3542 (view == VIEW(REQ_VIEW_LOG) ||
3543 view == VIEW(REQ_VIEW_PAGER))) {
3544 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3548 /* Always scroll the view even if it was split. That way
3549 * you can use Enter to scroll through the log view and
3550 * split open each commit diff. */
3551 scroll_view(view, REQ_SCROLL_LINE_DOWN);
3553 /* FIXME: A minor workaround. Scrolling the view will call report("")
3554 * but if we are scrolling a non-current view this won't properly
3555 * update the view title. */
3557 update_view_title(view);
3563 pager_grep(struct view *view, struct line *line)
3566 char *text = line->data;
3571 if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
3578 pager_select(struct view *view, struct line *line)
3580 if (line->type == LINE_COMMIT) {
3581 char *text = (char *)line->data + STRING_SIZE("commit ");
3583 if (view != VIEW(REQ_VIEW_PAGER))
3584 string_copy_rev(view->ref, text);
3585 string_copy_rev(ref_commit, text);
3589 static struct view_ops pager_ops = {
3600 static const char *log_argv[SIZEOF_ARG] = {
3601 "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3605 log_request(struct view *view, enum request request, struct line *line)
3610 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
3613 return pager_request(view, request, line);
3617 static struct view_ops log_ops = {
3628 static const char *diff_argv[SIZEOF_ARG] = {
3629 "git", "show", "--pretty=fuller", "--no-color", "--root",
3630 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
3633 static struct view_ops diff_ops = {
3649 help_open(struct view *view)
3651 char buf[SIZEOF_STR];
3655 if (view->lines > 0)
3658 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
3660 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
3663 if (req_info[i].request == REQ_NONE)
3666 if (!req_info[i].request) {
3667 add_line_text(view, "", LINE_DEFAULT);
3668 add_line_text(view, req_info[i].help, LINE_DEFAULT);
3672 key = get_key(req_info[i].request);
3674 key = "(no key defined)";
3676 for (bufpos = 0; bufpos <= req_info[i].namelen; bufpos++) {
3677 buf[bufpos] = tolower(req_info[i].name[bufpos]);
3678 if (buf[bufpos] == '_')
3682 add_line_format(view, LINE_DEFAULT, " %-25s %-20s %s",
3683 key, buf, req_info[i].help);
3687 add_line_text(view, "", LINE_DEFAULT);
3688 add_line_text(view, "External commands:", LINE_DEFAULT);
3691 for (i = 0; i < run_requests; i++) {
3692 struct run_request *req = get_run_request(REQ_NONE + i + 1);
3699 key = get_key_name(req->key);
3701 key = "(no key defined)";
3703 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
3704 if (!string_format_from(buf, &bufpos, "%s%s",
3705 argc ? " " : "", req->argv[argc]))
3708 add_line_format(view, LINE_DEFAULT, " %-10s %-14s `%s`",
3709 keymap_table[req->keymap].name, key, buf);
3715 static struct view_ops help_ops = {
3731 struct tree_stack_entry {
3732 struct tree_stack_entry *prev; /* Entry below this in the stack */
3733 unsigned long lineno; /* Line number to restore */
3734 char *name; /* Position of name in opt_path */
3737 /* The top of the path stack. */
3738 static struct tree_stack_entry *tree_stack = NULL;
3739 unsigned long tree_lineno = 0;
3742 pop_tree_stack_entry(void)
3744 struct tree_stack_entry *entry = tree_stack;
3746 tree_lineno = entry->lineno;
3748 tree_stack = entry->prev;
3753 push_tree_stack_entry(const char *name, unsigned long lineno)
3755 struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
3756 size_t pathlen = strlen(opt_path);
3761 entry->prev = tree_stack;
3762 entry->name = opt_path + pathlen;
3765 if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
3766 pop_tree_stack_entry();
3770 /* Move the current line to the first tree entry. */
3772 entry->lineno = lineno;
3775 /* Parse output from git-ls-tree(1):
3777 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
3780 #define SIZEOF_TREE_ATTR \
3781 STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
3783 #define SIZEOF_TREE_MODE \
3784 STRING_SIZE("100644 ")
3786 #define TREE_ID_OFFSET \
3787 STRING_SIZE("100644 blob ")
3790 char id[SIZEOF_REV];
3792 struct tm time; /* Date from the author ident. */
3793 char author[75]; /* Author of the commit. */
3798 tree_path(struct line *line)
3800 return ((struct tree_entry *) line->data)->name;
3805 tree_compare_entry(struct line *line1, struct line *line2)
3807 if (line1->type != line2->type)
3808 return line1->type == LINE_TREE_DIR ? -1 : 1;
3809 return strcmp(tree_path(line1), tree_path(line2));
3812 static struct line *
3813 tree_entry(struct view *view, enum line_type type, const char *path,
3814 const char *mode, const char *id)
3816 struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
3817 struct line *line = entry ? add_line_data(view, entry, type) : NULL;
3819 if (!entry || !line) {
3824 strncpy(entry->name, path, strlen(path));
3826 entry->mode = strtoul(mode, NULL, 8);
3828 string_copy_rev(entry->id, id);
3834 tree_read_date(struct view *view, char *text, bool *read_date)
3836 static char author_name[SIZEOF_STR];
3837 static struct tm author_time;
3839 if (!text && *read_date) {
3844 char *path = *opt_path ? opt_path : ".";
3845 /* Find next entry to process */
3846 const char *log_file[] = {
3847 "git", "log", "--no-color", "--pretty=raw",
3848 "--cc", "--raw", view->id, "--", path, NULL
3853 tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
3854 report("Tree is empty");
3858 if (!run_io_rd(&io, log_file, FORMAT_NONE)) {
3859 report("Failed to load tree data");
3863 done_io(view->pipe);
3868 } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
3869 parse_author_line(text + STRING_SIZE("author "),
3870 author_name, sizeof(author_name), &author_time);
3872 } else if (*text == ':') {
3874 size_t annotated = 1;
3877 pos = strchr(text, '\t');
3881 if (*opt_prefix && !strncmp(text, opt_prefix, strlen(opt_prefix)))
3882 text += strlen(opt_prefix);
3883 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
3884 text += strlen(opt_path);
3885 pos = strchr(text, '/');
3889 for (i = 1; i < view->lines; i++) {
3890 struct line *line = &view->line[i];
3891 struct tree_entry *entry = line->data;
3893 annotated += !!*entry->author;
3894 if (*entry->author || strcmp(entry->name, text))
3897 string_copy(entry->author, author_name);
3898 memcpy(&entry->time, &author_time, sizeof(entry->time));
3903 if (annotated == view->lines)
3904 kill_io(view->pipe);
3910 tree_read(struct view *view, char *text)
3912 static bool read_date = FALSE;
3913 struct tree_entry *data;
3914 struct line *entry, *line;
3915 enum line_type type;
3916 size_t textlen = text ? strlen(text) : 0;
3917 char *path = text + SIZEOF_TREE_ATTR;
3919 if (read_date || !text)
3920 return tree_read_date(view, text, &read_date);
3922 if (textlen <= SIZEOF_TREE_ATTR)
3924 if (view->lines == 0 &&
3925 !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
3928 /* Strip the path part ... */
3930 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
3931 size_t striplen = strlen(opt_path);
3933 if (pathlen > striplen)
3934 memmove(path, path + striplen,
3935 pathlen - striplen + 1);
3937 /* Insert "link" to parent directory. */
3938 if (view->lines == 1 &&
3939 !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
3943 type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
3944 entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
3949 /* Skip "Directory ..." and ".." line. */
3950 for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
3951 if (tree_compare_entry(line, entry) <= 0)
3954 memmove(line + 1, line, (entry - line) * sizeof(*entry));
3958 for (; line <= entry; line++)
3959 line->dirty = line->cleareol = 1;
3963 if (tree_lineno > view->lineno) {
3964 view->lineno = tree_lineno;
3972 tree_draw(struct view *view, struct line *line, unsigned int lineno)
3974 struct tree_entry *entry = line->data;
3976 if (line->type == LINE_TREE_HEAD) {
3977 if (draw_text(view, line->type, "Directory path /", TRUE))
3980 if (draw_mode(view, entry->mode))
3983 if (opt_author && draw_author(view, entry->author))
3986 if (opt_date && draw_date(view, *entry->author ? &entry->time : NULL))
3989 if (draw_text(view, line->type, entry->name, TRUE))
3997 char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
3998 int fd = mkstemp(file);
4001 report("Failed to create temporary file");
4002 else if (!run_io_append(blob_ops.argv, FORMAT_ALL, fd))
4003 report("Failed to save blob data to file");
4005 open_editor(FALSE, file);
4011 tree_request(struct view *view, enum request request, struct line *line)
4013 enum open_flags flags;
4016 case REQ_VIEW_BLAME:
4017 if (line->type != LINE_TREE_FILE) {
4018 report("Blame only supported for files");
4022 string_copy(opt_ref, view->vid);
4026 if (line->type != LINE_TREE_FILE) {
4027 report("Edit only supported for files");
4028 } else if (!is_head_commit(view->vid)) {
4031 open_editor(TRUE, opt_file);
4037 /* quit view if at top of tree */
4038 return REQ_VIEW_CLOSE;
4041 line = &view->line[1];
4051 /* Cleanup the stack if the tree view is at a different tree. */
4052 while (!*opt_path && tree_stack)
4053 pop_tree_stack_entry();
4055 switch (line->type) {
4057 /* Depending on whether it is a subdirectory or parent link
4058 * mangle the path buffer. */
4059 if (line == &view->line[1] && *opt_path) {
4060 pop_tree_stack_entry();
4063 const char *basename = tree_path(line);
4065 push_tree_stack_entry(basename, view->lineno);
4068 /* Trees and subtrees share the same ID, so they are not not
4069 * unique like blobs. */
4070 flags = OPEN_RELOAD;
4071 request = REQ_VIEW_TREE;
4074 case LINE_TREE_FILE:
4075 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4076 request = REQ_VIEW_BLOB;
4083 open_view(view, request, flags);
4084 if (request == REQ_VIEW_TREE)
4085 view->lineno = tree_lineno;
4091 tree_select(struct view *view, struct line *line)
4093 struct tree_entry *entry = line->data;
4095 if (line->type == LINE_TREE_FILE) {
4096 string_copy_rev(ref_blob, entry->id);
4097 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4099 } else if (line->type != LINE_TREE_DIR) {
4103 string_copy_rev(view->ref, entry->id);
4106 static const char *tree_argv[SIZEOF_ARG] = {
4107 "git", "ls-tree", "%(commit)", "%(directory)", NULL
4110 static struct view_ops tree_ops = {
4122 blob_read(struct view *view, char *line)
4126 return add_line_text(view, line, LINE_DEFAULT) != NULL;
4130 blob_request(struct view *view, enum request request, struct line *line)
4137 return pager_request(view, request, line);
4141 static const char *blob_argv[SIZEOF_ARG] = {
4142 "git", "cat-file", "blob", "%(blob)", NULL
4145 static struct view_ops blob_ops = {
4159 * Loading the blame view is a two phase job:
4161 * 1. File content is read either using opt_file from the
4162 * filesystem or using git-cat-file.
4163 * 2. Then blame information is incrementally added by
4164 * reading output from git-blame.
4167 static const char *blame_head_argv[] = {
4168 "git", "blame", "--incremental", "--", "%(file)", NULL
4171 static const char *blame_ref_argv[] = {
4172 "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4175 static const char *blame_cat_file_argv[] = {
4176 "git", "cat-file", "blob", "%(ref):%(file)", NULL
4179 struct blame_commit {
4180 char id[SIZEOF_REV]; /* SHA1 ID. */
4181 char title[128]; /* First line of the commit message. */
4182 char author[75]; /* Author of the commit. */
4183 struct tm time; /* Date from the author ident. */
4184 char filename[128]; /* Name of file. */
4185 bool has_previous; /* Was a "previous" line detected. */
4189 struct blame_commit *commit;
4190 unsigned long lineno;
4195 blame_open(struct view *view)
4197 if (*opt_ref || !io_open(&view->io, opt_file)) {
4198 if (!run_io_rd(&view->io, blame_cat_file_argv, FORMAT_ALL))
4202 setup_update(view, opt_file);
4203 string_format(view->ref, "%s ...", opt_file);
4208 static struct blame_commit *
4209 get_blame_commit(struct view *view, const char *id)
4213 for (i = 0; i < view->lines; i++) {
4214 struct blame *blame = view->line[i].data;
4219 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4220 return blame->commit;
4224 struct blame_commit *commit = calloc(1, sizeof(*commit));
4227 string_ncopy(commit->id, id, SIZEOF_REV);
4233 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4235 const char *pos = *posref;
4238 pos = strchr(pos + 1, ' ');
4239 if (!pos || !isdigit(pos[1]))
4241 *number = atoi(pos + 1);
4242 if (*number < min || *number > max)
4249 static struct blame_commit *
4250 parse_blame_commit(struct view *view, const char *text, int *blamed)
4252 struct blame_commit *commit;
4253 struct blame *blame;
4254 const char *pos = text + SIZEOF_REV - 2;
4255 size_t orig_lineno = 0;
4259 if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
4262 if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
4263 !parse_number(&pos, &lineno, 1, view->lines) ||
4264 !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4267 commit = get_blame_commit(view, text);
4273 struct line *line = &view->line[lineno + group - 1];
4276 blame->commit = commit;
4277 blame->lineno = orig_lineno + group - 1;
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 = strlen(line);
4306 struct blame *blame = malloc(sizeof(*blame) + linelen);
4311 blame->commit = NULL;
4312 strncpy(blame->text, line, linelen);
4313 blame->text[linelen] = 0;
4314 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
4319 match_blame_header(const char *name, char **line)
4321 size_t namelen = strlen(name);
4322 bool matched = !strncmp(name, *line, namelen);
4331 blame_read(struct view *view, char *line)
4333 static struct blame_commit *commit = NULL;
4334 static int blamed = 0;
4335 static time_t author_time;
4336 static bool read_file = TRUE;
4339 return blame_read_file(view, line, &read_file);
4346 string_format(view->ref, "%s", view->vid);
4347 if (view_is_displayed(view)) {
4348 update_view_title(view);
4349 redraw_view_from(view, 0);
4355 commit = parse_blame_commit(view, line, &blamed);
4356 string_format(view->ref, "%s %2d%%", view->vid,
4357 view->lines ? blamed * 100 / view->lines : 0);
4359 } else if (match_blame_header("author ", &line)) {
4360 string_ncopy(commit->author, line, strlen(line));
4362 } else if (match_blame_header("author-time ", &line)) {
4363 author_time = (time_t) atol(line);
4365 } else if (match_blame_header("author-tz ", &line)) {
4366 parse_timezone(&author_time, line);
4367 gmtime_r(&author_time, &commit->time);
4369 } else if (match_blame_header("summary ", &line)) {
4370 string_ncopy(commit->title, line, strlen(line));
4372 } else if (match_blame_header("previous ", &line)) {
4373 commit->has_previous = TRUE;
4375 } else if (match_blame_header("filename ", &line)) {
4376 string_ncopy(commit->filename, line, strlen(line));
4384 blame_draw(struct view *view, struct line *line, unsigned int lineno)
4386 struct blame *blame = line->data;
4387 struct tm *time = NULL;
4388 const char *id = NULL, *author = NULL;
4389 char text[SIZEOF_STR];
4391 if (blame->commit && *blame->commit->filename) {
4392 id = blame->commit->id;
4393 author = blame->commit->author;
4394 time = &blame->commit->time;
4397 if (opt_date && draw_date(view, time))
4400 if (opt_author && draw_author(view, author))
4403 if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
4406 if (draw_lineno(view, lineno))
4409 string_expand(text, sizeof(text), blame->text, opt_tab_size);
4410 draw_text(view, LINE_DEFAULT, text, TRUE);
4415 check_blame_commit(struct blame *blame, bool check_null_id)
4418 report("Commit data not loaded yet");
4419 else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
4420 report("No commit exist for the selected line");
4427 setup_blame_parent_line(struct view *view, struct blame *blame)
4429 const char *diff_tree_argv[] = {
4430 "git", "diff-tree", "-U0", blame->commit->id,
4431 "--", blame->commit->filename, NULL
4434 int parent_lineno = -1;
4435 int blamed_lineno = -1;
4438 if (!run_io(&io, diff_tree_argv, NULL, IO_RD))
4441 while ((line = io_get(&io, '\n', TRUE))) {
4443 char *pos = strchr(line, '+');
4445 parent_lineno = atoi(line + 4);
4447 blamed_lineno = atoi(pos + 1);
4449 } else if (*line == '+' && parent_lineno != -1) {
4450 if (blame->lineno == blamed_lineno - 1 &&
4451 !strcmp(blame->text, line + 1)) {
4452 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
4463 blame_request(struct view *view, enum request request, struct line *line)
4465 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4466 struct blame *blame = line->data;
4469 case REQ_VIEW_BLAME:
4470 if (check_blame_commit(blame, TRUE)) {
4471 string_copy(opt_ref, blame->commit->id);
4472 string_copy(opt_file, blame->commit->filename);
4474 view->lineno = blame->lineno;
4475 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4480 if (check_blame_commit(blame, TRUE) &&
4481 select_commit_parent(blame->commit->id, opt_ref,
4482 blame->commit->filename)) {
4483 string_copy(opt_file, blame->commit->filename);
4484 setup_blame_parent_line(view, blame);
4485 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4490 if (!check_blame_commit(blame, FALSE))
4493 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
4494 !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
4497 if (!strcmp(blame->commit->id, NULL_ID)) {
4498 struct view *diff = VIEW(REQ_VIEW_DIFF);
4499 const char *diff_index_argv[] = {
4500 "git", "diff-index", "--root", "--patch-with-stat",
4501 "-C", "-M", "HEAD", "--", view->vid, NULL
4504 if (!blame->commit->has_previous) {
4505 diff_index_argv[1] = "diff";
4506 diff_index_argv[2] = "--no-color";
4507 diff_index_argv[6] = "--";
4508 diff_index_argv[7] = "/dev/null";
4511 if (!prepare_update(diff, diff_index_argv, NULL, FORMAT_DASH)) {
4512 report("Failed to allocate diff command");
4515 flags |= OPEN_PREPARED;
4518 open_view(view, REQ_VIEW_DIFF, flags);
4519 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
4520 string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
4531 blame_grep(struct view *view, struct line *line)
4533 struct blame *blame = line->data;
4534 struct blame_commit *commit = blame->commit;
4537 #define MATCH(text, on) \
4538 (on && *text && regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4541 char buf[DATE_COLS + 1];
4543 if (MATCH(commit->title, 1) ||
4544 MATCH(commit->author, opt_author) ||
4545 MATCH(commit->id, opt_date))
4548 if (strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time) &&
4553 return MATCH(blame->text, 1);
4559 blame_select(struct view *view, struct line *line)
4561 struct blame *blame = line->data;
4562 struct blame_commit *commit = blame->commit;
4567 if (!strcmp(commit->id, NULL_ID))
4568 string_ncopy(ref_commit, "HEAD", 4);
4570 string_copy_rev(ref_commit, commit->id);
4573 static struct view_ops blame_ops = {
4592 char rev[SIZEOF_REV];
4593 char name[SIZEOF_STR];
4597 char rev[SIZEOF_REV];
4598 char name[SIZEOF_STR];
4602 static char status_onbranch[SIZEOF_STR];
4603 static struct status stage_status;
4604 static enum line_type stage_line_type;
4605 static size_t stage_chunks;
4606 static int *stage_chunk;
4608 /* This should work even for the "On branch" line. */
4610 status_has_none(struct view *view, struct line *line)
4612 return line < view->line + view->lines && !line[1].data;
4615 /* Get fields from the diff line:
4616 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
4619 status_get_diff(struct status *file, const char *buf, size_t bufsize)
4621 const char *old_mode = buf + 1;
4622 const char *new_mode = buf + 8;
4623 const char *old_rev = buf + 15;
4624 const char *new_rev = buf + 56;
4625 const char *status = buf + 97;
4628 old_mode[-1] != ':' ||
4629 new_mode[-1] != ' ' ||
4630 old_rev[-1] != ' ' ||
4631 new_rev[-1] != ' ' ||
4635 file->status = *status;
4637 string_copy_rev(file->old.rev, old_rev);
4638 string_copy_rev(file->new.rev, new_rev);
4640 file->old.mode = strtoul(old_mode, NULL, 8);
4641 file->new.mode = strtoul(new_mode, NULL, 8);
4643 file->old.name[0] = file->new.name[0] = 0;
4649 status_run(struct view *view, const char *argv[], char status, enum line_type type)
4651 struct status *unmerged = NULL;
4655 if (!run_io(&io, argv, NULL, IO_RD))
4658 add_line_data(view, NULL, type);
4660 while ((buf = io_get(&io, 0, TRUE))) {
4661 struct status *file = unmerged;
4664 file = calloc(1, sizeof(*file));
4665 if (!file || !add_line_data(view, file, type))
4669 /* Parse diff info part. */
4671 file->status = status;
4673 string_copy(file->old.rev, NULL_ID);
4675 } else if (!file->status || file == unmerged) {
4676 if (!status_get_diff(file, buf, strlen(buf)))
4679 buf = io_get(&io, 0, TRUE);
4683 /* Collapse all modified entries that follow an
4684 * associated unmerged entry. */
4685 if (unmerged == file) {
4686 unmerged->status = 'U';
4688 } else if (file->status == 'U') {
4693 /* Grab the old name for rename/copy. */
4694 if (!*file->old.name &&
4695 (file->status == 'R' || file->status == 'C')) {
4696 string_ncopy(file->old.name, buf, strlen(buf));
4698 buf = io_get(&io, 0, TRUE);
4703 /* git-ls-files just delivers a NUL separated list of
4704 * file names similar to the second half of the
4705 * git-diff-* output. */
4706 string_ncopy(file->new.name, buf, strlen(buf));
4707 if (!*file->old.name)
4708 string_copy(file->old.name, file->new.name);
4712 if (io_error(&io)) {
4718 if (!view->line[view->lines - 1].data)
4719 add_line_data(view, NULL, LINE_STAT_NONE);
4725 /* Don't show unmerged entries in the staged section. */
4726 static const char *status_diff_index_argv[] = {
4727 "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
4728 "--cached", "-M", "HEAD", NULL
4731 static const char *status_diff_files_argv[] = {
4732 "git", "diff-files", "-z", NULL
4735 static const char *status_list_other_argv[] = {
4736 "git", "ls-files", "-z", "--others", "--exclude-standard", NULL
4739 static const char *status_list_no_head_argv[] = {
4740 "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
4743 static const char *update_index_argv[] = {
4744 "git", "update-index", "-q", "--unmerged", "--refresh", NULL
4747 /* Restore the previous line number to stay in the context or select a
4748 * line with something that can be updated. */
4750 status_restore(struct view *view)
4752 if (view->p_lineno >= view->lines)
4753 view->p_lineno = view->lines - 1;
4754 while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
4756 while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
4759 /* If the above fails, always skip the "On branch" line. */
4760 if (view->p_lineno < view->lines)
4761 view->lineno = view->p_lineno;
4765 if (view->lineno < view->offset)
4766 view->offset = view->lineno;
4767 else if (view->offset + view->height <= view->lineno)
4768 view->offset = view->lineno - view->height + 1;
4770 view->p_restore = FALSE;
4774 status_update_onbranch(void)
4776 static const char *paths[][2] = {
4777 { "rebase-apply/rebasing", "Rebasing" },
4778 { "rebase-apply/applying", "Applying mailbox" },
4779 { "rebase-apply/", "Rebasing mailbox" },
4780 { "rebase-merge/interactive", "Interactive rebase" },
4781 { "rebase-merge/", "Rebase merge" },
4782 { "MERGE_HEAD", "Merging" },
4783 { "BISECT_LOG", "Bisecting" },
4784 { "HEAD", "On branch" },
4786 char buf[SIZEOF_STR];
4790 if (is_initial_commit()) {
4791 string_copy(status_onbranch, "Initial commit");
4795 for (i = 0; i < ARRAY_SIZE(paths); i++) {
4796 char *head = opt_head;
4798 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
4799 lstat(buf, &stat) < 0)
4805 if (string_format(buf, "%s/rebase-merge/head-name", opt_git_dir) &&
4806 io_open(&io, buf) &&
4807 io_read_buf(&io, buf, sizeof(buf))) {
4808 head = chomp_string(buf);
4809 if (!prefixcmp(head, "refs/heads/"))
4810 head += STRING_SIZE("refs/heads/");
4814 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
4815 string_copy(status_onbranch, opt_head);
4819 string_copy(status_onbranch, "Not currently on any branch");
4822 /* First parse staged info using git-diff-index(1), then parse unstaged
4823 * info using git-diff-files(1), and finally untracked files using
4824 * git-ls-files(1). */
4826 status_open(struct view *view)
4830 add_line_data(view, NULL, LINE_STAT_HEAD);
4831 status_update_onbranch();
4833 run_io_bg(update_index_argv);
4835 if (is_initial_commit()) {
4836 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
4838 } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
4842 if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
4843 !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
4846 /* Restore the exact position or use the specialized restore
4848 if (!view->p_restore)
4849 status_restore(view);
4854 status_draw(struct view *view, struct line *line, unsigned int lineno)
4856 struct status *status = line->data;
4857 enum line_type type;
4861 switch (line->type) {
4862 case LINE_STAT_STAGED:
4863 type = LINE_STAT_SECTION;
4864 text = "Changes to be committed:";
4867 case LINE_STAT_UNSTAGED:
4868 type = LINE_STAT_SECTION;
4869 text = "Changed but not updated:";
4872 case LINE_STAT_UNTRACKED:
4873 type = LINE_STAT_SECTION;
4874 text = "Untracked files:";
4877 case LINE_STAT_NONE:
4878 type = LINE_DEFAULT;
4879 text = " (no files)";
4882 case LINE_STAT_HEAD:
4883 type = LINE_STAT_HEAD;
4884 text = status_onbranch;
4891 static char buf[] = { '?', ' ', ' ', ' ', 0 };
4893 buf[0] = status->status;
4894 if (draw_text(view, line->type, buf, TRUE))
4896 type = LINE_DEFAULT;
4897 text = status->new.name;
4900 draw_text(view, type, text, TRUE);
4905 status_enter(struct view *view, struct line *line)
4907 struct status *status = line->data;
4908 const char *oldpath = status ? status->old.name : NULL;
4909 /* Diffs for unmerged entries are empty when passing the new
4910 * path, so leave it empty. */
4911 const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
4913 enum open_flags split;
4914 struct view *stage = VIEW(REQ_VIEW_STAGE);
4916 if (line->type == LINE_STAT_NONE ||
4917 (!status && line[1].type == LINE_STAT_NONE)) {
4918 report("No file to diff");
4922 switch (line->type) {
4923 case LINE_STAT_STAGED:
4924 if (is_initial_commit()) {
4925 const char *no_head_diff_argv[] = {
4926 "git", "diff", "--no-color", "--patch-with-stat",
4927 "--", "/dev/null", newpath, NULL
4930 if (!prepare_update(stage, no_head_diff_argv, opt_cdup, FORMAT_DASH))
4933 const char *index_show_argv[] = {
4934 "git", "diff-index", "--root", "--patch-with-stat",
4935 "-C", "-M", "--cached", "HEAD", "--",
4936 oldpath, newpath, NULL
4939 if (!prepare_update(stage, index_show_argv, opt_cdup, FORMAT_DASH))
4944 info = "Staged changes to %s";
4946 info = "Staged changes";
4949 case LINE_STAT_UNSTAGED:
4951 const char *files_show_argv[] = {
4952 "git", "diff-files", "--root", "--patch-with-stat",
4953 "-C", "-M", "--", oldpath, newpath, NULL
4956 if (!prepare_update(stage, files_show_argv, opt_cdup, FORMAT_DASH))
4959 info = "Unstaged changes to %s";
4961 info = "Unstaged changes";
4964 case LINE_STAT_UNTRACKED:
4966 report("No file to show");
4970 if (!suffixcmp(status->new.name, -1, "/")) {
4971 report("Cannot display a directory");
4975 if (!prepare_update_file(stage, newpath))
4977 info = "Untracked file %s";
4980 case LINE_STAT_HEAD:
4984 die("line type %d not handled in switch", line->type);
4987 split = view_is_displayed(view) ? OPEN_SPLIT : 0;
4988 open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
4989 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
4991 stage_status = *status;
4993 memset(&stage_status, 0, sizeof(stage_status));
4996 stage_line_type = line->type;
4998 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5005 status_exists(struct status *status, enum line_type type)
5007 struct view *view = VIEW(REQ_VIEW_STATUS);
5008 unsigned long lineno;
5010 for (lineno = 0; lineno < view->lines; lineno++) {
5011 struct line *line = &view->line[lineno];
5012 struct status *pos = line->data;
5014 if (line->type != type)
5016 if (!pos && (!status || !status->status) && line[1].data) {
5017 select_view_line(view, lineno);
5020 if (pos && !strcmp(status->new.name, pos->new.name)) {
5021 select_view_line(view, lineno);
5031 status_update_prepare(struct io *io, enum line_type type)
5033 const char *staged_argv[] = {
5034 "git", "update-index", "-z", "--index-info", NULL
5036 const char *others_argv[] = {
5037 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5041 case LINE_STAT_STAGED:
5042 return run_io(io, staged_argv, opt_cdup, IO_WR);
5044 case LINE_STAT_UNSTAGED:
5045 return run_io(io, others_argv, opt_cdup, IO_WR);
5047 case LINE_STAT_UNTRACKED:
5048 return run_io(io, others_argv, NULL, IO_WR);
5051 die("line type %d not handled in switch", type);
5057 status_update_write(struct io *io, struct status *status, enum line_type type)
5059 char buf[SIZEOF_STR];
5063 case LINE_STAT_STAGED:
5064 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
5067 status->old.name, 0))
5071 case LINE_STAT_UNSTAGED:
5072 case LINE_STAT_UNTRACKED:
5073 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
5078 die("line type %d not handled in switch", type);
5081 return io_write(io, buf, bufsize);
5085 status_update_file(struct status *status, enum line_type type)
5090 if (!status_update_prepare(&io, type))
5093 result = status_update_write(&io, status, type);
5099 status_update_files(struct view *view, struct line *line)
5103 struct line *pos = view->line + view->lines;
5107 if (!status_update_prepare(&io, line->type))
5110 for (pos = line; pos < view->line + view->lines && pos->data; pos++)
5113 for (file = 0, done = 0; result && file < files; line++, file++) {
5114 int almost_done = file * 100 / files;
5116 if (almost_done > done) {
5118 string_format(view->ref, "updating file %u of %u (%d%% done)",
5120 update_view_title(view);
5122 result = status_update_write(&io, line->data, line->type);
5130 status_update(struct view *view)
5132 struct line *line = &view->line[view->lineno];
5134 assert(view->lines);
5137 /* This should work even for the "On branch" line. */
5138 if (line < view->line + view->lines && !line[1].data) {
5139 report("Nothing to update");
5143 if (!status_update_files(view, line + 1)) {
5144 report("Failed to update file status");
5148 } else if (!status_update_file(line->data, line->type)) {
5149 report("Failed to update file status");
5157 status_revert(struct status *status, enum line_type type, bool has_none)
5159 if (!status || type != LINE_STAT_UNSTAGED) {
5160 if (type == LINE_STAT_STAGED) {
5161 report("Cannot revert changes to staged files");
5162 } else if (type == LINE_STAT_UNTRACKED) {
5163 report("Cannot revert changes to untracked files");
5164 } else if (has_none) {
5165 report("Nothing to revert");
5167 report("Cannot revert changes to multiple files");
5172 char mode[10] = "100644";
5173 const char *reset_argv[] = {
5174 "git", "update-index", "--cacheinfo", mode,
5175 status->old.rev, status->old.name, NULL
5177 const char *checkout_argv[] = {
5178 "git", "checkout", "--", status->old.name, NULL
5181 if (!prompt_yesno("Are you sure you want to overwrite any changes?"))
5183 string_format(mode, "%o", status->old.mode);
5184 return (status->status != 'U' || run_io_fg(reset_argv, opt_cdup)) &&
5185 run_io_fg(checkout_argv, opt_cdup);
5190 status_request(struct view *view, enum request request, struct line *line)
5192 struct status *status = line->data;
5195 case REQ_STATUS_UPDATE:
5196 if (!status_update(view))
5200 case REQ_STATUS_REVERT:
5201 if (!status_revert(status, line->type, status_has_none(view, line)))
5205 case REQ_STATUS_MERGE:
5206 if (!status || status->status != 'U') {
5207 report("Merging only possible for files with unmerged status ('U').");
5210 open_mergetool(status->new.name);
5216 if (status->status == 'D') {
5217 report("File has been deleted.");
5221 open_editor(status->status != '?', status->new.name);
5224 case REQ_VIEW_BLAME:
5226 string_copy(opt_file, status->new.name);
5232 /* After returning the status view has been split to
5233 * show the stage view. No further reloading is
5235 status_enter(view, line);
5239 /* Simply reload the view. */
5246 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
5252 status_select(struct view *view, struct line *line)
5254 struct status *status = line->data;
5255 char file[SIZEOF_STR] = "all files";
5259 if (status && !string_format(file, "'%s'", status->new.name))
5262 if (!status && line[1].type == LINE_STAT_NONE)
5265 switch (line->type) {
5266 case LINE_STAT_STAGED:
5267 text = "Press %s to unstage %s for commit";
5270 case LINE_STAT_UNSTAGED:
5271 text = "Press %s to stage %s for commit";
5274 case LINE_STAT_UNTRACKED:
5275 text = "Press %s to stage %s for addition";
5278 case LINE_STAT_HEAD:
5279 case LINE_STAT_NONE:
5280 text = "Nothing to update";
5284 die("line type %d not handled in switch", line->type);
5287 if (status && status->status == 'U') {
5288 text = "Press %s to resolve conflict in %s";
5289 key = get_key(REQ_STATUS_MERGE);
5292 key = get_key(REQ_STATUS_UPDATE);
5295 string_format(view->ref, text, key, file);
5299 status_grep(struct view *view, struct line *line)
5301 struct status *status = line->data;
5302 enum { S_STATUS, S_NAME, S_END } state;
5309 for (state = S_STATUS; state < S_END; state++) {
5313 case S_NAME: text = status->new.name; break;
5315 buf[0] = status->status;
5323 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
5330 static struct view_ops status_ops = {
5343 stage_diff_write(struct io *io, struct line *line, struct line *end)
5345 while (line < end) {
5346 if (!io_write(io, line->data, strlen(line->data)) ||
5347 !io_write(io, "\n", 1))
5350 if (line->type == LINE_DIFF_CHUNK ||
5351 line->type == LINE_DIFF_HEADER)
5358 static struct line *
5359 stage_diff_find(struct view *view, struct line *line, enum line_type type)
5361 for (; view->line < line; line--)
5362 if (line->type == type)
5369 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
5371 const char *apply_argv[SIZEOF_ARG] = {
5372 "git", "apply", "--whitespace=nowarn", NULL
5374 struct line *diff_hdr;
5378 diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
5383 apply_argv[argc++] = "--cached";
5384 if (revert || stage_line_type == LINE_STAT_STAGED)
5385 apply_argv[argc++] = "-R";
5386 apply_argv[argc++] = "-";
5387 apply_argv[argc++] = NULL;
5388 if (!run_io(&io, apply_argv, opt_cdup, IO_WR))
5391 if (!stage_diff_write(&io, diff_hdr, chunk) ||
5392 !stage_diff_write(&io, chunk, view->line + view->lines))
5396 run_io_bg(update_index_argv);
5398 return chunk ? TRUE : FALSE;
5402 stage_update(struct view *view, struct line *line)
5404 struct line *chunk = NULL;
5406 if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
5407 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5410 if (!stage_apply_chunk(view, chunk, FALSE)) {
5411 report("Failed to apply chunk");
5415 } else if (!stage_status.status) {
5416 view = VIEW(REQ_VIEW_STATUS);
5418 for (line = view->line; line < view->line + view->lines; line++)
5419 if (line->type == stage_line_type)
5422 if (!status_update_files(view, line + 1)) {
5423 report("Failed to update files");
5427 } else if (!status_update_file(&stage_status, stage_line_type)) {
5428 report("Failed to update file");
5436 stage_revert(struct view *view, struct line *line)
5438 struct line *chunk = NULL;
5440 if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
5441 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5444 if (!prompt_yesno("Are you sure you want to revert changes?"))
5447 if (!stage_apply_chunk(view, chunk, TRUE)) {
5448 report("Failed to revert chunk");
5454 return status_revert(stage_status.status ? &stage_status : NULL,
5455 stage_line_type, FALSE);
5461 stage_next(struct view *view, struct line *line)
5465 if (!stage_chunks) {
5466 static size_t alloc = 0;
5469 for (line = view->line; line < view->line + view->lines; line++) {
5470 if (line->type != LINE_DIFF_CHUNK)
5473 tmp = realloc_items(stage_chunk, &alloc,
5474 stage_chunks, sizeof(*tmp));
5476 report("Allocation failure");
5481 stage_chunk[stage_chunks++] = line - view->line;
5485 for (i = 0; i < stage_chunks; i++) {
5486 if (stage_chunk[i] > view->lineno) {
5487 do_scroll_view(view, stage_chunk[i] - view->lineno);
5488 report("Chunk %d of %d", i + 1, stage_chunks);
5493 report("No next chunk found");
5497 stage_request(struct view *view, enum request request, struct line *line)
5500 case REQ_STATUS_UPDATE:
5501 if (!stage_update(view, line))
5505 case REQ_STATUS_REVERT:
5506 if (!stage_revert(view, line))
5510 case REQ_STAGE_NEXT:
5511 if (stage_line_type == LINE_STAT_UNTRACKED) {
5512 report("File is untracked; press %s to add",
5513 get_key(REQ_STATUS_UPDATE));
5516 stage_next(view, line);
5520 if (!stage_status.new.name[0])
5522 if (stage_status.status == 'D') {
5523 report("File has been deleted.");
5527 open_editor(stage_status.status != '?', stage_status.new.name);
5531 /* Reload everything ... */
5534 case REQ_VIEW_BLAME:
5535 if (stage_status.new.name[0]) {
5536 string_copy(opt_file, stage_status.new.name);
5542 return pager_request(view, request, line);
5548 VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
5549 open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
5551 /* Check whether the staged entry still exists, and close the
5552 * stage view if it doesn't. */
5553 if (!status_exists(&stage_status, stage_line_type)) {
5554 status_restore(VIEW(REQ_VIEW_STATUS));
5555 return REQ_VIEW_CLOSE;
5558 if (stage_line_type == LINE_STAT_UNTRACKED) {
5559 if (!suffixcmp(stage_status.new.name, -1, "/")) {
5560 report("Cannot display a directory");
5564 if (!prepare_update_file(view, stage_status.new.name)) {
5565 report("Failed to open file: %s", strerror(errno));
5569 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
5574 static struct view_ops stage_ops = {
5591 char id[SIZEOF_REV]; /* SHA1 ID. */
5592 char title[128]; /* First line of the commit message. */
5593 char author[75]; /* Author of the commit. */
5594 struct tm time; /* Date from the author ident. */
5595 struct ref **refs; /* Repository references. */
5596 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
5597 size_t graph_size; /* The width of the graph array. */
5598 bool has_parents; /* Rewritten --parents seen. */
5601 /* Size of rev graph with no "padding" columns */
5602 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
5605 struct rev_graph *prev, *next, *parents;
5606 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
5608 struct commit *commit;
5610 unsigned int boundary:1;
5613 /* Parents of the commit being visualized. */
5614 static struct rev_graph graph_parents[4];
5616 /* The current stack of revisions on the graph. */
5617 static struct rev_graph graph_stacks[4] = {
5618 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
5619 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
5620 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
5621 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
5625 graph_parent_is_merge(struct rev_graph *graph)
5627 return graph->parents->size > 1;
5631 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
5633 struct commit *commit = graph->commit;
5635 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
5636 commit->graph[commit->graph_size++] = symbol;
5640 clear_rev_graph(struct rev_graph *graph)
5642 graph->boundary = 0;
5643 graph->size = graph->pos = 0;
5644 graph->commit = NULL;
5645 memset(graph->parents, 0, sizeof(*graph->parents));
5649 done_rev_graph(struct rev_graph *graph)
5651 if (graph_parent_is_merge(graph) &&
5652 graph->pos < graph->size - 1 &&
5653 graph->next->size == graph->size + graph->parents->size - 1) {
5654 size_t i = graph->pos + graph->parents->size - 1;
5656 graph->commit->graph_size = i * 2;
5657 while (i < graph->next->size - 1) {
5658 append_to_rev_graph(graph, ' ');
5659 append_to_rev_graph(graph, '\\');
5664 clear_rev_graph(graph);
5668 push_rev_graph(struct rev_graph *graph, const char *parent)
5672 /* "Collapse" duplicate parents lines.
5674 * FIXME: This needs to also update update the drawn graph but
5675 * for now it just serves as a method for pruning graph lines. */
5676 for (i = 0; i < graph->size; i++)
5677 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
5680 if (graph->size < SIZEOF_REVITEMS) {
5681 string_copy_rev(graph->rev[graph->size++], parent);
5686 get_rev_graph_symbol(struct rev_graph *graph)
5690 if (graph->boundary)
5691 symbol = REVGRAPH_BOUND;
5692 else if (graph->parents->size == 0)
5693 symbol = REVGRAPH_INIT;
5694 else if (graph_parent_is_merge(graph))
5695 symbol = REVGRAPH_MERGE;
5696 else if (graph->pos >= graph->size)
5697 symbol = REVGRAPH_BRANCH;
5699 symbol = REVGRAPH_COMMIT;
5705 draw_rev_graph(struct rev_graph *graph)
5708 chtype separator, line;
5710 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
5711 static struct rev_filler fillers[] = {
5717 chtype symbol = get_rev_graph_symbol(graph);
5718 struct rev_filler *filler;
5721 if (opt_line_graphics)
5722 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
5724 filler = &fillers[DEFAULT];
5726 for (i = 0; i < graph->pos; i++) {
5727 append_to_rev_graph(graph, filler->line);
5728 if (graph_parent_is_merge(graph->prev) &&
5729 graph->prev->pos == i)
5730 filler = &fillers[RSHARP];
5732 append_to_rev_graph(graph, filler->separator);
5735 /* Place the symbol for this revision. */
5736 append_to_rev_graph(graph, symbol);
5738 if (graph->prev->size > graph->size)
5739 filler = &fillers[RDIAG];
5741 filler = &fillers[DEFAULT];
5745 for (; i < graph->size; i++) {
5746 append_to_rev_graph(graph, filler->separator);
5747 append_to_rev_graph(graph, filler->line);
5748 if (graph_parent_is_merge(graph->prev) &&
5749 i < graph->prev->pos + graph->parents->size)
5750 filler = &fillers[RSHARP];
5751 if (graph->prev->size > graph->size)
5752 filler = &fillers[LDIAG];
5755 if (graph->prev->size > graph->size) {
5756 append_to_rev_graph(graph, filler->separator);
5757 if (filler->line != ' ')
5758 append_to_rev_graph(graph, filler->line);
5762 /* Prepare the next rev graph */
5764 prepare_rev_graph(struct rev_graph *graph)
5768 /* First, traverse all lines of revisions up to the active one. */
5769 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
5770 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
5773 push_rev_graph(graph->next, graph->rev[graph->pos]);
5776 /* Interleave the new revision parent(s). */
5777 for (i = 0; !graph->boundary && i < graph->parents->size; i++)
5778 push_rev_graph(graph->next, graph->parents->rev[i]);
5780 /* Lastly, put any remaining revisions. */
5781 for (i = graph->pos + 1; i < graph->size; i++)
5782 push_rev_graph(graph->next, graph->rev[i]);
5786 update_rev_graph(struct view *view, struct rev_graph *graph)
5788 /* If this is the finalizing update ... */
5790 prepare_rev_graph(graph);
5792 /* Graph visualization needs a one rev look-ahead,
5793 * so the first update doesn't visualize anything. */
5794 if (!graph->prev->commit)
5797 if (view->lines > 2)
5798 view->line[view->lines - 3].dirty = 1;
5799 if (view->lines > 1)
5800 view->line[view->lines - 2].dirty = 1;
5801 draw_rev_graph(graph->prev);
5802 done_rev_graph(graph->prev->prev);
5810 static const char *main_argv[SIZEOF_ARG] = {
5811 "git", "log", "--no-color", "--pretty=raw", "--parents",
5812 "--topo-order", "%(head)", NULL
5816 main_draw(struct view *view, struct line *line, unsigned int lineno)
5818 struct commit *commit = line->data;
5820 if (!*commit->author)
5823 if (opt_date && draw_date(view, &commit->time))
5826 if (opt_author && draw_author(view, commit->author))
5829 if (opt_rev_graph && commit->graph_size &&
5830 draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
5833 if (opt_show_refs && commit->refs) {
5837 enum line_type type;
5839 if (commit->refs[i]->head)
5840 type = LINE_MAIN_HEAD;
5841 else if (commit->refs[i]->ltag)
5842 type = LINE_MAIN_LOCAL_TAG;
5843 else if (commit->refs[i]->tag)
5844 type = LINE_MAIN_TAG;
5845 else if (commit->refs[i]->tracked)
5846 type = LINE_MAIN_TRACKED;
5847 else if (commit->refs[i]->remote)
5848 type = LINE_MAIN_REMOTE;
5850 type = LINE_MAIN_REF;
5852 if (draw_text(view, type, "[", TRUE) ||
5853 draw_text(view, type, commit->refs[i]->name, TRUE) ||
5854 draw_text(view, type, "]", TRUE))
5857 if (draw_text(view, LINE_DEFAULT, " ", TRUE))
5859 } while (commit->refs[i++]->next);
5862 draw_text(view, LINE_DEFAULT, commit->title, TRUE);
5866 /* Reads git log --pretty=raw output and parses it into the commit struct. */
5868 main_read(struct view *view, char *line)
5870 static struct rev_graph *graph = graph_stacks;
5871 enum line_type type;
5872 struct commit *commit;
5877 if (!view->lines && !view->parent)
5878 die("No revisions match the given arguments.");
5879 if (view->lines > 0) {
5880 commit = view->line[view->lines - 1].data;
5881 view->line[view->lines - 1].dirty = 1;
5882 if (!*commit->author) {
5885 graph->commit = NULL;
5888 update_rev_graph(view, graph);
5890 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
5891 clear_rev_graph(&graph_stacks[i]);
5895 type = get_line_type(line);
5896 if (type == LINE_COMMIT) {
5897 commit = calloc(1, sizeof(struct commit));
5901 line += STRING_SIZE("commit ");
5903 graph->boundary = 1;
5907 string_copy_rev(commit->id, line);
5908 commit->refs = get_refs(commit->id);
5909 graph->commit = commit;
5910 add_line_data(view, commit, LINE_MAIN_COMMIT);
5912 while ((line = strchr(line, ' '))) {
5914 push_rev_graph(graph->parents, line);
5915 commit->has_parents = TRUE;
5922 commit = view->line[view->lines - 1].data;
5926 if (commit->has_parents)
5928 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
5932 parse_author_line(line + STRING_SIZE("author "),
5933 commit->author, sizeof(commit->author),
5935 update_rev_graph(view, graph);
5936 graph = graph->next;
5940 /* Fill in the commit title if it has not already been set. */
5941 if (commit->title[0])
5944 /* Require titles to start with a non-space character at the
5945 * offset used by git log. */
5946 if (strncmp(line, " ", 4))
5949 /* Well, if the title starts with a whitespace character,
5950 * try to be forgiving. Otherwise we end up with no title. */
5951 while (isspace(*line))
5955 /* FIXME: More graceful handling of titles; append "..." to
5956 * shortened titles, etc. */
5958 string_expand(commit->title, sizeof(commit->title), line, 1);
5959 view->line[view->lines - 1].dirty = 1;
5966 main_request(struct view *view, enum request request, struct line *line)
5968 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
5972 open_view(view, REQ_VIEW_DIFF, flags);
5976 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
5986 grep_refs(struct ref **refs, regex_t *regex)
5994 if (regexec(regex, refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
5996 } while (refs[i++]->next);
6002 main_grep(struct view *view, struct line *line)
6004 struct commit *commit = line->data;
6005 enum { S_TITLE, S_AUTHOR, S_DATE, S_REFS, S_END } state;
6006 char buf[DATE_COLS + 1];
6009 for (state = S_TITLE; state < S_END; state++) {
6013 case S_TITLE: text = commit->title; break;
6017 text = commit->author;
6022 if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
6029 if (grep_refs(commit->refs, view->regex) == TRUE)
6036 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
6044 main_select(struct view *view, struct line *line)
6046 struct commit *commit = line->data;
6048 string_copy_rev(view->ref, commit->id);
6049 string_copy_rev(ref_commit, view->ref);
6052 static struct view_ops main_ops = {
6065 * Unicode / UTF-8 handling
6067 * NOTE: Much of the following code for dealing with Unicode is derived from
6068 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
6069 * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
6073 unicode_width(unsigned long c)
6076 (c <= 0x115f /* Hangul Jamo */
6079 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
6081 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
6082 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
6083 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
6084 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
6085 || (c >= 0xffe0 && c <= 0xffe6)
6086 || (c >= 0x20000 && c <= 0x2fffd)
6087 || (c >= 0x30000 && c <= 0x3fffd)))
6091 return opt_tab_size;
6096 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
6097 * Illegal bytes are set one. */
6098 static const unsigned char utf8_bytes[256] = {
6099 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,
6100 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,
6101 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,
6102 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,
6103 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,
6104 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,
6105 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,
6106 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,
6109 /* Decode UTF-8 multi-byte representation into a Unicode character. */
6110 static inline unsigned long
6111 utf8_to_unicode(const char *string, size_t length)
6113 unsigned long unicode;
6117 unicode = string[0];
6120 unicode = (string[0] & 0x1f) << 6;
6121 unicode += (string[1] & 0x3f);
6124 unicode = (string[0] & 0x0f) << 12;
6125 unicode += ((string[1] & 0x3f) << 6);
6126 unicode += (string[2] & 0x3f);
6129 unicode = (string[0] & 0x0f) << 18;
6130 unicode += ((string[1] & 0x3f) << 12);
6131 unicode += ((string[2] & 0x3f) << 6);
6132 unicode += (string[3] & 0x3f);
6135 unicode = (string[0] & 0x0f) << 24;
6136 unicode += ((string[1] & 0x3f) << 18);
6137 unicode += ((string[2] & 0x3f) << 12);
6138 unicode += ((string[3] & 0x3f) << 6);
6139 unicode += (string[4] & 0x3f);
6142 unicode = (string[0] & 0x01) << 30;
6143 unicode += ((string[1] & 0x3f) << 24);
6144 unicode += ((string[2] & 0x3f) << 18);
6145 unicode += ((string[3] & 0x3f) << 12);
6146 unicode += ((string[4] & 0x3f) << 6);
6147 unicode += (string[5] & 0x3f);
6150 die("Invalid Unicode length");
6153 /* Invalid characters could return the special 0xfffd value but NUL
6154 * should be just as good. */
6155 return unicode > 0xffff ? 0 : unicode;
6158 /* Calculates how much of string can be shown within the given maximum width
6159 * and sets trimmed parameter to non-zero value if all of string could not be
6160 * shown. If the reserve flag is TRUE, it will reserve at least one
6161 * trailing character, which can be useful when drawing a delimiter.
6163 * Returns the number of bytes to output from string to satisfy max_width. */
6165 utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve)
6167 const char *string = *start;
6168 const char *end = strchr(string, '\0');
6169 unsigned char last_bytes = 0;
6170 size_t last_ucwidth = 0;
6175 while (string < end) {
6176 int c = *(unsigned char *) string;
6177 unsigned char bytes = utf8_bytes[c];
6179 unsigned long unicode;
6181 if (string + bytes > end)
6184 /* Change representation to figure out whether
6185 * it is a single- or double-width character. */
6187 unicode = utf8_to_unicode(string, bytes);
6188 /* FIXME: Graceful handling of invalid Unicode character. */
6192 ucwidth = unicode_width(unicode);
6194 skip -= ucwidth <= skip ? ucwidth : skip;
6198 if (*width > max_width) {
6201 if (reserve && *width == max_width) {
6202 string -= last_bytes;
6203 *width -= last_ucwidth;
6209 last_bytes = ucwidth ? bytes : 0;
6210 last_ucwidth = ucwidth;
6213 return string - *start;
6221 /* Whether or not the curses interface has been initialized. */
6222 static bool cursed = FALSE;
6224 /* Terminal hacks and workarounds. */
6225 static bool use_scroll_redrawwin;
6226 static bool use_scroll_status_wclear;
6228 /* The status window is used for polling keystrokes. */
6229 static WINDOW *status_win;
6231 /* Reading from the prompt? */
6232 static bool input_mode = FALSE;
6234 static bool status_empty = FALSE;
6236 /* Update status and title window. */
6238 report(const char *msg, ...)
6240 struct view *view = display[current_view];
6246 char buf[SIZEOF_STR];
6249 va_start(args, msg);
6250 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6251 buf[sizeof(buf) - 1] = 0;
6252 buf[sizeof(buf) - 2] = '.';
6253 buf[sizeof(buf) - 3] = '.';
6254 buf[sizeof(buf) - 4] = '.';
6260 if (!status_empty || *msg) {
6263 va_start(args, msg);
6265 wmove(status_win, 0, 0);
6266 if (view->has_scrolled && use_scroll_status_wclear)
6269 vwprintw(status_win, msg, args);
6270 status_empty = FALSE;
6272 status_empty = TRUE;
6274 wclrtoeol(status_win);
6275 wnoutrefresh(status_win);
6280 update_view_title(view);
6283 /* Controls when nodelay should be in effect when polling user input. */
6285 set_nonblocking_input(bool loading)
6287 static unsigned int loading_views;
6289 if ((loading == FALSE && loading_views-- == 1) ||
6290 (loading == TRUE && loading_views++ == 0))
6291 nodelay(status_win, loading);
6300 /* Initialize the curses library */
6301 if (isatty(STDIN_FILENO)) {
6302 cursed = !!initscr();
6305 /* Leave stdin and stdout alone when acting as a pager. */
6306 opt_tty = fopen("/dev/tty", "r+");
6308 die("Failed to open /dev/tty");
6309 cursed = !!newterm(NULL, opt_tty, opt_tty);
6313 die("Failed to initialize curses");
6315 nonl(); /* Disable conversion and detect newlines from input. */
6316 cbreak(); /* Take input chars one at a time, no wait for \n */
6317 noecho(); /* Don't echo input */
6318 leaveok(stdscr, FALSE);
6323 getmaxyx(stdscr, y, x);
6324 status_win = newwin(1, 0, y - 1, 0);
6326 die("Failed to create status window");
6328 /* Enable keyboard mapping */
6329 keypad(status_win, TRUE);
6330 wbkgdset(status_win, get_line_attr(LINE_STATUS));
6332 TABSIZE = opt_tab_size;
6333 if (opt_line_graphics) {
6334 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
6337 term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
6338 if (term && !strcmp(term, "gnome-terminal")) {
6339 /* In the gnome-terminal-emulator, the message from
6340 * scrolling up one line when impossible followed by
6341 * scrolling down one line causes corruption of the
6342 * status line. This is fixed by calling wclear. */
6343 use_scroll_status_wclear = TRUE;
6344 use_scroll_redrawwin = FALSE;
6346 } else if (term && !strcmp(term, "xrvt-xpm")) {
6347 /* No problems with full optimizations in xrvt-(unicode)
6349 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
6352 /* When scrolling in (u)xterm the last line in the
6353 * scrolling direction will update slowly. */
6354 use_scroll_redrawwin = TRUE;
6355 use_scroll_status_wclear = FALSE;
6360 get_input(int prompt_position)
6363 int i, key, cursor_y, cursor_x;
6365 if (prompt_position)
6369 foreach_view (view, i) {
6371 if (view_is_displayed(view) && view->has_scrolled &&
6372 use_scroll_redrawwin)
6373 redrawwin(view->win);
6374 view->has_scrolled = FALSE;
6377 /* Update the cursor position. */
6378 if (prompt_position) {
6379 getbegyx(status_win, cursor_y, cursor_x);
6380 cursor_x = prompt_position;
6382 view = display[current_view];
6383 getbegyx(view->win, cursor_y, cursor_x);
6384 cursor_x = view->width - 1;
6385 cursor_y += view->lineno - view->offset;
6387 setsyx(cursor_y, cursor_x);
6389 /* Refresh, accept single keystroke of input */
6391 key = wgetch(status_win);
6393 /* wgetch() with nodelay() enabled returns ERR when
6394 * there's no input. */
6397 } else if (key == KEY_RESIZE) {
6400 getmaxyx(stdscr, height, width);
6402 wresize(status_win, 1, width);
6403 mvwin(status_win, height - 1, 0);
6404 wnoutrefresh(status_win);
6406 redraw_display(TRUE);
6416 prompt_input(const char *prompt, input_handler handler, void *data)
6418 enum input_status status = INPUT_OK;
6419 static char buf[SIZEOF_STR];
6424 while (status == INPUT_OK || status == INPUT_SKIP) {
6427 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
6428 wclrtoeol(status_win);
6430 key = get_input(pos + 1);
6435 status = pos ? INPUT_STOP : INPUT_CANCEL;
6442 status = INPUT_CANCEL;
6446 status = INPUT_CANCEL;
6450 if (pos >= sizeof(buf)) {
6451 report("Input string too long");
6455 status = handler(data, buf, key);
6456 if (status == INPUT_OK)
6457 buf[pos++] = (char) key;
6461 /* Clear the status window */
6462 status_empty = FALSE;
6465 if (status == INPUT_CANCEL)
6473 static enum input_status
6474 prompt_yesno_handler(void *data, char *buf, int c)
6476 if (c == 'y' || c == 'Y')
6478 if (c == 'n' || c == 'N')
6479 return INPUT_CANCEL;
6484 prompt_yesno(const char *prompt)
6486 char prompt2[SIZEOF_STR];
6488 if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
6491 return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
6494 static enum input_status
6495 read_prompt_handler(void *data, char *buf, int c)
6497 return isprint(c) ? INPUT_OK : INPUT_SKIP;
6501 read_prompt(const char *prompt)
6503 return prompt_input(prompt, read_prompt_handler, NULL);
6507 * Repository properties
6510 static struct ref *refs = NULL;
6511 static size_t refs_alloc = 0;
6512 static size_t refs_size = 0;
6514 /* Id <-> ref store */
6515 static struct ref ***id_refs = NULL;
6516 static size_t id_refs_alloc = 0;
6517 static size_t id_refs_size = 0;
6520 compare_refs(const void *ref1_, const void *ref2_)
6522 const struct ref *ref1 = *(const struct ref **)ref1_;
6523 const struct ref *ref2 = *(const struct ref **)ref2_;
6525 if (ref1->tag != ref2->tag)
6526 return ref2->tag - ref1->tag;
6527 if (ref1->ltag != ref2->ltag)
6528 return ref2->ltag - ref2->ltag;
6529 if (ref1->head != ref2->head)
6530 return ref2->head - ref1->head;
6531 if (ref1->tracked != ref2->tracked)
6532 return ref2->tracked - ref1->tracked;
6533 if (ref1->remote != ref2->remote)
6534 return ref2->remote - ref1->remote;
6535 return strcmp(ref1->name, ref2->name);
6538 static struct ref **
6539 get_refs(const char *id)
6541 struct ref ***tmp_id_refs;
6542 struct ref **ref_list = NULL;
6543 size_t ref_list_alloc = 0;
6544 size_t ref_list_size = 0;
6547 for (i = 0; i < id_refs_size; i++)
6548 if (!strcmp(id, id_refs[i][0]->id))
6551 tmp_id_refs = realloc_items(id_refs, &id_refs_alloc, id_refs_size + 1,
6556 id_refs = tmp_id_refs;
6558 for (i = 0; i < refs_size; i++) {
6561 if (strcmp(id, refs[i].id))
6564 tmp = realloc_items(ref_list, &ref_list_alloc,
6565 ref_list_size + 1, sizeof(*ref_list));
6573 ref_list[ref_list_size] = &refs[i];
6574 /* XXX: The properties of the commit chains ensures that we can
6575 * safely modify the shared ref. The repo references will
6576 * always be similar for the same id. */
6577 ref_list[ref_list_size]->next = 1;
6583 qsort(ref_list, ref_list_size, sizeof(*ref_list), compare_refs);
6584 ref_list[ref_list_size - 1]->next = 0;
6585 id_refs[id_refs_size++] = ref_list;
6592 read_ref(char *id, size_t idlen, char *name, size_t namelen)
6597 bool remote = FALSE;
6598 bool tracked = FALSE;
6599 bool check_replace = FALSE;
6602 if (!prefixcmp(name, "refs/tags/")) {
6603 if (!suffixcmp(name, namelen, "^{}")) {
6606 if (refs_size > 0 && refs[refs_size - 1].ltag == TRUE)
6607 check_replace = TRUE;
6613 namelen -= STRING_SIZE("refs/tags/");
6614 name += STRING_SIZE("refs/tags/");
6616 } else if (!prefixcmp(name, "refs/remotes/")) {
6618 namelen -= STRING_SIZE("refs/remotes/");
6619 name += STRING_SIZE("refs/remotes/");
6620 tracked = !strcmp(opt_remote, name);
6622 } else if (!prefixcmp(name, "refs/heads/")) {
6623 namelen -= STRING_SIZE("refs/heads/");
6624 name += STRING_SIZE("refs/heads/");
6625 head = !strncmp(opt_head, name, namelen);
6627 } else if (!strcmp(name, "HEAD")) {
6628 string_ncopy(opt_head_rev, id, idlen);
6632 if (check_replace && !strcmp(name, refs[refs_size - 1].name)) {
6633 /* it's an annotated tag, replace the previous SHA1 with the
6634 * resolved commit id; relies on the fact git-ls-remote lists
6635 * the commit id of an annotated tag right before the commit id
6637 refs[refs_size - 1].ltag = ltag;
6638 string_copy_rev(refs[refs_size - 1].id, id);
6642 refs = realloc_items(refs, &refs_alloc, refs_size + 1, sizeof(*refs));
6646 ref = &refs[refs_size++];
6647 ref->name = malloc(namelen + 1);
6651 strncpy(ref->name, name, namelen);
6652 ref->name[namelen] = 0;
6656 ref->remote = remote;
6657 ref->tracked = tracked;
6658 string_copy_rev(ref->id, id);
6666 static const char *ls_remote_argv[SIZEOF_ARG] = {
6667 "git", "ls-remote", opt_git_dir, NULL
6669 static bool init = FALSE;
6672 argv_from_env(ls_remote_argv, "TIG_LS_REMOTE");
6679 while (refs_size > 0)
6680 free(refs[--refs_size].name);
6681 while (id_refs_size > 0)
6682 free(id_refs[--id_refs_size]);
6684 return run_io_load(ls_remote_argv, "\t", read_ref);
6688 set_remote_branch(const char *name, const char *value, size_t valuelen)
6690 if (!strcmp(name, ".remote")) {
6691 string_ncopy(opt_remote, value, valuelen);
6693 } else if (*opt_remote && !strcmp(name, ".merge")) {
6694 size_t from = strlen(opt_remote);
6696 if (!prefixcmp(value, "refs/heads/"))
6697 value += STRING_SIZE("refs/heads/");
6699 if (!string_format_from(opt_remote, &from, "/%s", value))
6705 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
6707 const char *argv[SIZEOF_ARG] = { name, "=" };
6708 int argc = 1 + (cmd == option_set_command);
6711 if (!argv_from_string(argv, &argc, value))
6712 config_msg = "Too many option arguments";
6714 error = cmd(argc, argv);
6717 warn("Option 'tig.%s': %s", name, config_msg);
6721 set_work_tree(const char *value)
6723 char cwd[SIZEOF_STR];
6725 if (!getcwd(cwd, sizeof(cwd)))
6726 die("Failed to get cwd path: %s", strerror(errno));
6727 if (chdir(opt_git_dir) < 0)
6728 die("Failed to chdir(%s): %s", strerror(errno));
6729 if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
6730 die("Failed to get git path: %s", strerror(errno));
6732 die("Failed to chdir(%s): %s", cwd, strerror(errno));
6733 if (chdir(value) < 0)
6734 die("Failed to chdir(%s): %s", value, strerror(errno));
6735 if (!getcwd(cwd, sizeof(cwd)))
6736 die("Failed to get cwd path: %s", strerror(errno));
6737 if (setenv("GIT_WORK_TREE", cwd, TRUE) < 0)
6738 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
6739 if (setenv("GIT_DIR", opt_git_dir, TRUE) < 0)
6740 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
6741 opt_is_inside_work_tree = TRUE;
6745 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
6747 if (!strcmp(name, "i18n.commitencoding"))
6748 string_ncopy(opt_encoding, value, valuelen);
6750 else if (!strcmp(name, "core.editor"))
6751 string_ncopy(opt_editor, value, valuelen);
6753 else if (!strcmp(name, "core.worktree"))
6754 set_work_tree(value);
6756 else if (!prefixcmp(name, "tig.color."))
6757 set_repo_config_option(name + 10, value, option_color_command);
6759 else if (!prefixcmp(name, "tig.bind."))
6760 set_repo_config_option(name + 9, value, option_bind_command);
6762 else if (!prefixcmp(name, "tig."))
6763 set_repo_config_option(name + 4, value, option_set_command);
6765 else if (*opt_head && !prefixcmp(name, "branch.") &&
6766 !strncmp(name + 7, opt_head, strlen(opt_head)))
6767 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
6773 load_git_config(void)
6775 const char *config_list_argv[] = { "git", GIT_CONFIG, "--list", NULL };
6777 return run_io_load(config_list_argv, "=", read_repo_config_option);
6781 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
6783 if (!opt_git_dir[0]) {
6784 string_ncopy(opt_git_dir, name, namelen);
6786 } else if (opt_is_inside_work_tree == -1) {
6787 /* This can be 3 different values depending on the
6788 * version of git being used. If git-rev-parse does not
6789 * understand --is-inside-work-tree it will simply echo
6790 * the option else either "true" or "false" is printed.
6791 * Default to true for the unknown case. */
6792 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
6794 } else if (*name == '.') {
6795 string_ncopy(opt_cdup, name, namelen);
6798 string_ncopy(opt_prefix, name, namelen);
6805 load_repo_info(void)
6807 const char *head_argv[] = {
6808 "git", "symbolic-ref", "HEAD", NULL
6810 const char *rev_parse_argv[] = {
6811 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
6812 "--show-cdup", "--show-prefix", NULL
6815 if (run_io_buf(head_argv, opt_head, sizeof(opt_head))) {
6816 chomp_string(opt_head);
6817 if (!prefixcmp(opt_head, "refs/heads/")) {
6818 char *offset = opt_head + STRING_SIZE("refs/heads/");
6820 memmove(opt_head, offset, strlen(offset) + 1);
6824 return run_io_load(rev_parse_argv, "=", read_repo_info);
6832 static const char usage[] =
6833 "tig " TIG_VERSION " (" __DATE__ ")\n"
6835 "Usage: tig [options] [revs] [--] [paths]\n"
6836 " or: tig show [options] [revs] [--] [paths]\n"
6837 " or: tig blame [rev] path\n"
6839 " or: tig < [git command output]\n"
6842 " -v, --version Show version and exit\n"
6843 " -h, --help Show help message and exit";
6845 static void __NORETURN
6848 /* XXX: Restore tty modes and let the OS cleanup the rest! */
6854 static void __NORETURN
6855 die(const char *err, ...)
6861 va_start(args, err);
6862 fputs("tig: ", stderr);
6863 vfprintf(stderr, err, args);
6864 fputs("\n", stderr);
6871 warn(const char *msg, ...)
6875 va_start(args, msg);
6876 fputs("tig warning: ", stderr);
6877 vfprintf(stderr, msg, args);
6878 fputs("\n", stderr);
6883 parse_options(int argc, const char *argv[])
6885 enum request request = REQ_VIEW_MAIN;
6886 const char *subcommand;
6887 bool seen_dashdash = FALSE;
6888 /* XXX: This is vulnerable to the user overriding options
6889 * required for the main view parser. */
6890 const char *custom_argv[SIZEOF_ARG] = {
6891 "git", "log", "--no-color", "--pretty=raw", "--parents",
6892 "--topo-order", NULL
6896 if (!isatty(STDIN_FILENO)) {
6897 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
6898 return REQ_VIEW_PAGER;
6904 subcommand = argv[1];
6905 if (!strcmp(subcommand, "status")) {
6907 warn("ignoring arguments after `%s'", subcommand);
6908 return REQ_VIEW_STATUS;
6910 } else if (!strcmp(subcommand, "blame")) {
6911 if (argc <= 2 || argc > 4)
6912 die("invalid number of options to blame\n\n%s", usage);
6916 string_ncopy(opt_ref, argv[i], strlen(argv[i]));
6920 string_ncopy(opt_file, argv[i], strlen(argv[i]));
6921 return REQ_VIEW_BLAME;
6923 } else if (!strcmp(subcommand, "show")) {
6924 request = REQ_VIEW_DIFF;
6931 custom_argv[1] = subcommand;
6935 for (i = 1 + !!subcommand; i < argc; i++) {
6936 const char *opt = argv[i];
6938 if (seen_dashdash || !strcmp(opt, "--")) {
6939 seen_dashdash = TRUE;
6941 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
6942 printf("tig version %s\n", TIG_VERSION);
6945 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
6946 printf("%s\n", usage);
6950 custom_argv[j++] = opt;
6951 if (j >= ARRAY_SIZE(custom_argv))
6952 die("command too long");
6955 if (!prepare_update(VIEW(request), custom_argv, NULL, FORMAT_NONE))
6956 die("Failed to format arguments");
6962 main(int argc, const char *argv[])
6964 enum request request = parse_options(argc, argv);
6968 signal(SIGINT, quit);
6970 if (setlocale(LC_ALL, "")) {
6971 char *codeset = nl_langinfo(CODESET);
6973 string_ncopy(opt_codeset, codeset, strlen(codeset));
6976 if (load_repo_info() == ERR)
6977 die("Failed to load repo info.");
6979 if (load_options() == ERR)
6980 die("Failed to load user config.");
6982 if (load_git_config() == ERR)
6983 die("Failed to load repo config.");
6985 /* Require a git repository unless when running in pager mode. */
6986 if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
6987 die("Not a git repository");
6989 if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
6992 if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
6993 opt_iconv = iconv_open(opt_codeset, opt_encoding);
6994 if (opt_iconv == ICONV_NONE)
6995 die("Failed to initialize character set conversion");
6998 if (load_refs() == ERR)
6999 die("Failed to load refs.");
7001 foreach_view (view, i)
7002 argv_from_env(view->ops->argv, view->cmd_env);
7006 if (request != REQ_NONE)
7007 open_view(NULL, request, OPEN_PREPARED);
7008 request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
7010 while (view_driver(display[current_view], request)) {
7011 int key = get_input(0);
7013 view = display[current_view];
7014 request = get_keybinding(view->keymap, key);
7016 /* Some low-level request handling. This keeps access to
7017 * status_win restricted. */
7021 char *cmd = read_prompt(":");
7023 if (cmd && isdigit(*cmd)) {
7024 int lineno = view->lineno + 1;
7026 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7027 select_view_line(view, lineno - 1);
7030 report("Unable to parse '%s' as a line number", cmd);
7034 struct view *next = VIEW(REQ_VIEW_PAGER);
7035 const char *argv[SIZEOF_ARG] = { "git" };
7038 /* When running random commands, initially show the
7039 * command in the title. However, it maybe later be
7040 * overwritten if a commit line is selected. */
7041 string_ncopy(next->ref, cmd, strlen(cmd));
7043 if (!argv_from_string(argv, &argc, cmd)) {
7044 report("Too many arguments");
7045 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
7046 report("Failed to format command");
7048 open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7056 case REQ_SEARCH_BACK:
7058 const char *prompt = request == REQ_SEARCH ? "/" : "?";
7059 char *search = read_prompt(prompt);
7062 string_ncopy(opt_search, search, strlen(search));
7063 else if (*opt_search)
7064 request = request == REQ_SEARCH ?