1 /* Copyright (c) 2006-2009 Jonas Fonseca <fonseca@diku.dk>
3 * This program is free software; you can redistribute it and/or
4 * modify it under the terms of the GNU General Public License as
5 * published by the Free Software Foundation; either version 2 of
6 * the License, or (at your option) any later version.
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
19 #define TIG_VERSION "unknown-version"
34 #include <sys/types.h>
37 #include <sys/select.h>
48 /* ncurses(3): Must be defined to have extended wide-character functions. */
49 #define _XOPEN_SOURCE_EXTENDED
51 #ifdef HAVE_NCURSESW_NCURSES_H
52 #include <ncursesw/ncurses.h>
54 #ifdef HAVE_NCURSES_NCURSES_H
55 #include <ncurses/ncurses.h>
62 #define __NORETURN __attribute__((__noreturn__))
67 static void __NORETURN die(const char *err, ...);
68 static void warn(const char *msg, ...);
69 static void report(const char *msg, ...);
70 static void set_nonblocking_input(bool loading);
71 static int load_refs(void);
72 static size_t utf8_length(const char **string, size_t col, int *width, size_t max_width, int *trimmed, bool reserve);
74 #define ABS(x) ((x) >= 0 ? (x) : -(x))
75 #define MIN(x, y) ((x) < (y) ? (x) : (y))
77 #define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
78 #define STRING_SIZE(x) (sizeof(x) - 1)
80 #define SIZEOF_STR 1024 /* Default string size. */
81 #define SIZEOF_REF 256 /* Size of symbolic or SHA1 ID. */
82 #define SIZEOF_REV 41 /* Holds a SHA-1 and an ending NUL. */
83 #define SIZEOF_ARG 32 /* Default argument array size. */
87 #define REVGRAPH_INIT 'I'
88 #define REVGRAPH_MERGE 'M'
89 #define REVGRAPH_BRANCH '+'
90 #define REVGRAPH_COMMIT '*'
91 #define REVGRAPH_BOUND '^'
93 #define SIZEOF_REVGRAPH 19 /* Size of revision ancestry graphics. */
95 /* This color name can be used to refer to the default term colors. */
96 #define COLOR_DEFAULT (-1)
98 #define ICONV_NONE ((iconv_t) -1)
100 #define ICONV_CONST /* nothing */
103 /* The format and size of the date column in the main view. */
104 #define DATE_FORMAT "%Y-%m-%d %H:%M"
105 #define DATE_COLS STRING_SIZE("2006-04-29 14:21 ")
107 #define AUTHOR_COLS 20
110 /* The default interval between line numbers. */
111 #define NUMBER_INTERVAL 5
112 #define SCROLL_INTERVAL 1
116 #define SCALE_SPLIT_VIEW(height) ((height) * 2 / 3)
118 #define NULL_ID "0000000000000000000000000000000000000000"
121 #define GIT_CONFIG "config"
124 /* Some ASCII-shorthands fitted into the ncurses namespace. */
126 #define KEY_RETURN '\r'
131 char *name; /* Ref name; tag or head names are shortened. */
132 char id[SIZEOF_REV]; /* Commit SHA1 ID */
133 unsigned int head:1; /* Is it the current HEAD? */
134 unsigned int tag:1; /* Is it a tag? */
135 unsigned int ltag:1; /* If so, is the tag local? */
136 unsigned int remote:1; /* Is it a remote ref? */
137 unsigned int tracked:1; /* Is it the remote for the current HEAD? */
138 unsigned int next:1; /* For ref lists: are there more refs? */
141 static struct ref **get_refs(const char *id);
144 FORMAT_ALL, /* Perform replacement in all arguments. */
145 FORMAT_DASH, /* Perform replacement up until "--". */
146 FORMAT_NONE /* No replacement should be performed. */
149 static bool format_argv(const char *dst[], const char *src[], enum format_flags flags);
158 typedef enum input_status (*input_handler)(void *data, char *buf, int c);
160 static char *prompt_input(const char *prompt, input_handler handler, void *data);
161 static bool prompt_yesno(const char *prompt);
168 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
170 if (srclen > dstlen - 1)
173 strncpy(dst, src, srclen);
177 /* Shorthands for safely copying into a fixed buffer. */
179 #define string_copy(dst, src) \
180 string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
182 #define string_ncopy(dst, src, srclen) \
183 string_ncopy_do(dst, sizeof(dst), src, srclen)
185 #define string_copy_rev(dst, src) \
186 string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
188 #define string_add(dst, from, src) \
189 string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
192 string_expand_length(const char *line, int tabsize)
196 for (pos = 0; line[pos]; pos++) {
197 if (line[pos] == '\t' && tabsize > 0)
198 size += tabsize - (size % tabsize);
206 string_expand(char *dst, size_t dstlen, const char *src, int tabsize)
210 for (size = pos = 0; size < dstlen - 1 && src[pos]; pos++) {
211 if (src[pos] == '\t') {
212 size_t expanded = tabsize - (size % tabsize);
214 if (expanded + size >= dstlen - 1)
215 expanded = dstlen - size - 1;
216 memcpy(dst + size, " ", expanded);
219 dst[size++] = src[pos];
227 chomp_string(char *name)
231 while (isspace(*name))
234 namelen = strlen(name) - 1;
235 while (namelen > 0 && isspace(name[namelen]))
242 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
245 size_t pos = bufpos ? *bufpos : 0;
248 pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
254 return pos >= bufsize ? FALSE : TRUE;
257 #define string_format(buf, fmt, args...) \
258 string_nformat(buf, sizeof(buf), NULL, fmt, args)
260 #define string_format_from(buf, from, fmt, args...) \
261 string_nformat(buf, sizeof(buf), from, fmt, args)
264 string_enum_compare(const char *str1, const char *str2, int len)
268 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
270 /* Diff-Header == DIFF_HEADER */
271 for (i = 0; i < len; i++) {
272 if (toupper(str1[i]) == toupper(str2[i]))
275 if (string_enum_sep(str1[i]) &&
276 string_enum_sep(str2[i]))
279 return str1[i] - str2[i];
291 #define ENUM_MAP(name, value) { name, STRING_SIZE(name), value }
294 map_enum_do(struct enum_map *map, size_t map_size, int *value, const char *name)
296 size_t namelen = strlen(name);
299 for (i = 0; i < map_size; i++)
300 if (namelen == map[i].namelen &&
301 !string_enum_compare(name, map[i].name, namelen)) {
302 *value = map[i].value;
309 #define map_enum(attr, map, name) \
310 map_enum_do(map, ARRAY_SIZE(map), attr, name)
312 #define prefixcmp(str1, str2) \
313 strncmp(str1, str2, STRING_SIZE(str2))
316 suffixcmp(const char *str, int slen, const char *suffix)
318 size_t len = slen >= 0 ? slen : strlen(str);
319 size_t suffixlen = strlen(suffix);
321 return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
326 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
330 while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
331 bool advance = cmd[valuelen] != 0;
334 argv[(*argc)++] = chomp_string(cmd);
335 cmd = chomp_string(cmd + valuelen + advance);
338 if (*argc < SIZEOF_ARG)
340 return *argc < SIZEOF_ARG;
344 argv_from_env(const char **argv, const char *name)
346 char *env = argv ? getenv(name) : NULL;
351 if (env && !argv_from_string(argv, &argc, env))
352 die("Too many arguments in the `%s` environment variable", name);
357 * Executing external commands.
361 IO_FD, /* File descriptor based IO. */
362 IO_BG, /* Execute command in the background. */
363 IO_FG, /* Execute command with same std{in,out,err}. */
364 IO_RD, /* Read only fork+exec IO. */
365 IO_WR, /* Write only fork+exec IO. */
366 IO_AP, /* Append fork+exec output to file. */
370 enum io_type type; /* The requested type of pipe. */
371 const char *dir; /* Directory from which to execute. */
372 pid_t pid; /* Pipe for reading or writing. */
373 int pipe; /* Pipe end for reading or writing. */
374 int error; /* Error status. */
375 const char *argv[SIZEOF_ARG]; /* Shell command arguments. */
376 char *buf; /* Read buffer. */
377 size_t bufalloc; /* Allocated buffer size. */
378 size_t bufsize; /* Buffer content size. */
379 char *bufpos; /* Current buffer position. */
380 unsigned int eof:1; /* Has end of file been reached. */
384 reset_io(struct io *io)
388 io->buf = io->bufpos = NULL;
389 io->bufalloc = io->bufsize = 0;
395 init_io(struct io *io, const char *dir, enum io_type type)
403 init_io_rd(struct io *io, const char *argv[], const char *dir,
404 enum format_flags flags)
406 init_io(io, dir, IO_RD);
407 return format_argv(io->argv, argv, flags);
411 io_open(struct io *io, const char *name)
413 init_io(io, NULL, IO_FD);
414 io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
415 return io->pipe != -1;
419 kill_io(struct io *io)
421 return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
425 done_io(struct io *io)
436 pid_t waiting = waitpid(pid, &status, 0);
441 report("waitpid failed (%s)", strerror(errno));
445 return waiting == pid &&
446 !WIFSIGNALED(status) &&
448 !WEXITSTATUS(status);
455 start_io(struct io *io)
457 int pipefds[2] = { -1, -1 };
459 if (io->type == IO_FD)
462 if ((io->type == IO_RD || io->type == IO_WR) &&
465 else if (io->type == IO_AP)
466 pipefds[1] = io->pipe;
468 if ((io->pid = fork())) {
469 if (pipefds[!(io->type == IO_WR)] != -1)
470 close(pipefds[!(io->type == IO_WR)]);
472 io->pipe = pipefds[!!(io->type == IO_WR)];
477 if (io->type != IO_FG) {
478 int devnull = open("/dev/null", O_RDWR);
479 int readfd = io->type == IO_WR ? pipefds[0] : devnull;
480 int writefd = (io->type == IO_RD || io->type == IO_AP)
481 ? pipefds[1] : devnull;
483 dup2(readfd, STDIN_FILENO);
484 dup2(writefd, STDOUT_FILENO);
485 dup2(devnull, STDERR_FILENO);
488 if (pipefds[0] != -1)
490 if (pipefds[1] != -1)
494 if (io->dir && *io->dir && chdir(io->dir) == -1)
495 die("Failed to change directory: %s", strerror(errno));
497 execvp(io->argv[0], (char *const*) io->argv);
498 die("Failed to execute program: %s", strerror(errno));
501 if (pipefds[!!(io->type == IO_WR)] != -1)
502 close(pipefds[!!(io->type == IO_WR)]);
507 run_io(struct io *io, const char **argv, const char *dir, enum io_type type)
509 init_io(io, dir, type);
510 if (!format_argv(io->argv, argv, FORMAT_NONE))
516 run_io_do(struct io *io)
518 return start_io(io) && done_io(io);
522 run_io_bg(const char **argv)
526 init_io(&io, NULL, IO_BG);
527 if (!format_argv(io.argv, argv, FORMAT_NONE))
529 return run_io_do(&io);
533 run_io_fg(const char **argv, const char *dir)
537 init_io(&io, dir, IO_FG);
538 if (!format_argv(io.argv, argv, FORMAT_NONE))
540 return run_io_do(&io);
544 run_io_append(const char **argv, enum format_flags flags, int fd)
548 init_io(&io, NULL, IO_AP);
550 if (format_argv(io.argv, argv, flags))
551 return run_io_do(&io);
557 run_io_rd(struct io *io, const char **argv, enum format_flags flags)
559 return init_io_rd(io, argv, NULL, flags) && start_io(io);
563 io_eof(struct io *io)
569 io_error(struct io *io)
575 io_strerror(struct io *io)
577 return strerror(io->error);
581 io_can_read(struct io *io)
583 struct timeval tv = { 0, 500 };
587 FD_SET(io->pipe, &fds);
589 return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
593 io_read(struct io *io, void *buf, size_t bufsize)
596 ssize_t readsize = read(io->pipe, buf, bufsize);
598 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
600 else if (readsize == -1)
602 else if (readsize == 0)
609 io_get(struct io *io, int c, bool can_read)
615 io->buf = io->bufpos = malloc(BUFSIZ);
618 io->bufalloc = BUFSIZ;
623 if (io->bufsize > 0) {
624 eol = memchr(io->bufpos, c, io->bufsize);
626 char *line = io->bufpos;
629 io->bufpos = eol + 1;
630 io->bufsize -= io->bufpos - line;
637 io->bufpos[io->bufsize] = 0;
647 if (io->bufsize > 0 && io->bufpos > io->buf)
648 memmove(io->buf, io->bufpos, io->bufsize);
650 io->bufpos = io->buf;
651 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
654 io->bufsize += readsize;
659 io_write(struct io *io, const void *buf, size_t bufsize)
663 while (!io_error(io) && written < bufsize) {
666 size = write(io->pipe, buf + written, bufsize - written);
667 if (size < 0 && (errno == EAGAIN || errno == EINTR))
675 return written == bufsize;
679 run_io_buf(const char **argv, char buf[], size_t bufsize)
684 if (!run_io_rd(&io, argv, FORMAT_NONE))
687 io.buf = io.bufpos = buf;
688 io.bufalloc = bufsize;
689 error = !io_get(&io, '\n', TRUE) && io_error(&io);
692 return done_io(&io) || error;
696 io_load(struct io *io, const char *separators,
697 int (*read_property)(char *, size_t, char *, size_t))
705 while (state == OK && (name = io_get(io, '\n', TRUE))) {
710 name = chomp_string(name);
711 namelen = strcspn(name, separators);
715 value = chomp_string(name + namelen + 1);
716 valuelen = strlen(value);
723 state = read_property(name, namelen, value, valuelen);
726 if (state != ERR && io_error(io))
734 run_io_load(const char **argv, const char *separators,
735 int (*read_property)(char *, size_t, char *, size_t))
739 return init_io_rd(&io, argv, NULL, FORMAT_NONE)
740 ? io_load(&io, separators, read_property) : ERR;
749 /* XXX: Keep the view request first and in sync with views[]. */ \
750 REQ_GROUP("View switching") \
751 REQ_(VIEW_MAIN, "Show main view"), \
752 REQ_(VIEW_DIFF, "Show diff view"), \
753 REQ_(VIEW_LOG, "Show log view"), \
754 REQ_(VIEW_TREE, "Show tree view"), \
755 REQ_(VIEW_BLOB, "Show blob view"), \
756 REQ_(VIEW_BLAME, "Show blame view"), \
757 REQ_(VIEW_HELP, "Show help page"), \
758 REQ_(VIEW_PAGER, "Show pager view"), \
759 REQ_(VIEW_STATUS, "Show status view"), \
760 REQ_(VIEW_STAGE, "Show stage view"), \
762 REQ_GROUP("View manipulation") \
763 REQ_(ENTER, "Enter current line and scroll"), \
764 REQ_(NEXT, "Move to next"), \
765 REQ_(PREVIOUS, "Move to previous"), \
766 REQ_(PARENT, "Move to parent"), \
767 REQ_(VIEW_NEXT, "Move focus to next view"), \
768 REQ_(REFRESH, "Reload and refresh"), \
769 REQ_(MAXIMIZE, "Maximize the current view"), \
770 REQ_(VIEW_CLOSE, "Close the current view"), \
771 REQ_(QUIT, "Close all views and quit"), \
773 REQ_GROUP("View specific requests") \
774 REQ_(STATUS_UPDATE, "Update file status"), \
775 REQ_(STATUS_REVERT, "Revert file changes"), \
776 REQ_(STATUS_MERGE, "Merge file using external tool"), \
777 REQ_(STAGE_NEXT, "Find next chunk to stage"), \
779 REQ_GROUP("Cursor navigation") \
780 REQ_(MOVE_UP, "Move cursor one line up"), \
781 REQ_(MOVE_DOWN, "Move cursor one line down"), \
782 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
783 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
784 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
785 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
787 REQ_GROUP("Scrolling") \
788 REQ_(SCROLL_LEFT, "Scroll two columns left"), \
789 REQ_(SCROLL_RIGHT, "Scroll two columns right"), \
790 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
791 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
792 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
793 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
795 REQ_GROUP("Searching") \
796 REQ_(SEARCH, "Search the view"), \
797 REQ_(SEARCH_BACK, "Search backwards in the view"), \
798 REQ_(FIND_NEXT, "Find next search match"), \
799 REQ_(FIND_PREV, "Find previous search match"), \
801 REQ_GROUP("Option manipulation") \
802 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
803 REQ_(TOGGLE_DATE, "Toggle date display"), \
804 REQ_(TOGGLE_AUTHOR, "Toggle author display"), \
805 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
806 REQ_(TOGGLE_REFS, "Toggle reference display (tags/branches)"), \
809 REQ_(PROMPT, "Bring up the prompt"), \
810 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
811 REQ_(SHOW_VERSION, "Show version information"), \
812 REQ_(STOP_LOADING, "Stop all loading views"), \
813 REQ_(EDIT, "Open in editor"), \
814 REQ_(NONE, "Do nothing")
817 /* User action requests. */
819 #define REQ_GROUP(help)
820 #define REQ_(req, help) REQ_##req
822 /* Offset all requests to avoid conflicts with ncurses getch values. */
823 REQ_OFFSET = KEY_MAX + 1,
830 struct request_info {
831 enum request request;
837 static struct request_info req_info[] = {
838 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
839 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
846 get_request(const char *name)
848 int namelen = strlen(name);
851 for (i = 0; i < ARRAY_SIZE(req_info); i++)
852 if (req_info[i].namelen == namelen &&
853 !string_enum_compare(req_info[i].name, name, namelen))
854 return req_info[i].request;
864 /* Option and state variables. */
865 static bool opt_date = TRUE;
866 static bool opt_author = TRUE;
867 static bool opt_line_number = FALSE;
868 static bool opt_line_graphics = TRUE;
869 static bool opt_rev_graph = FALSE;
870 static bool opt_show_refs = TRUE;
871 static int opt_num_interval = NUMBER_INTERVAL;
872 static int opt_tab_size = TAB_SIZE;
873 static int opt_author_cols = AUTHOR_COLS-1;
874 static char opt_path[SIZEOF_STR] = "";
875 static char opt_file[SIZEOF_STR] = "";
876 static char opt_ref[SIZEOF_REF] = "";
877 static char opt_head[SIZEOF_REF] = "";
878 static char opt_head_rev[SIZEOF_REV] = "";
879 static char opt_remote[SIZEOF_REF] = "";
880 static char opt_encoding[20] = "UTF-8";
881 static bool opt_utf8 = TRUE;
882 static char opt_codeset[20] = "UTF-8";
883 static iconv_t opt_iconv = ICONV_NONE;
884 static char opt_search[SIZEOF_STR] = "";
885 static char opt_cdup[SIZEOF_STR] = "";
886 static char opt_prefix[SIZEOF_STR] = "";
887 static char opt_git_dir[SIZEOF_STR] = "";
888 static signed char opt_is_inside_work_tree = -1; /* set to TRUE or FALSE */
889 static char opt_editor[SIZEOF_STR] = "";
890 static FILE *opt_tty = NULL;
892 #define is_initial_commit() (!*opt_head_rev)
893 #define is_head_commit(rev) (!strcmp((rev), "HEAD") || !strcmp(opt_head_rev, (rev)))
897 * Line-oriented content detection.
901 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
902 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
903 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
904 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
905 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
906 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
907 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
908 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
909 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
910 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
911 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
912 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
913 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
914 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
915 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
916 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
917 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
918 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
919 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
920 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
921 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
922 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
923 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
924 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
925 LINE(AUTHOR, "author ", COLOR_GREEN, COLOR_DEFAULT, 0), \
926 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
927 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
928 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
929 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
930 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
931 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
932 LINE(DELIMITER, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
933 LINE(DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
934 LINE(MODE, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
935 LINE(LINE_NUMBER, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
936 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
937 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
938 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
939 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
940 LINE(MAIN_LOCAL_TAG,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
941 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
942 LINE(MAIN_TRACKED, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
943 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
944 LINE(MAIN_HEAD, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
945 LINE(MAIN_REVGRAPH,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
946 LINE(TREE_HEAD, "", COLOR_DEFAULT, COLOR_DEFAULT, A_BOLD), \
947 LINE(TREE_DIR, "", COLOR_YELLOW, COLOR_DEFAULT, A_NORMAL), \
948 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
949 LINE(STAT_HEAD, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
950 LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
951 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
952 LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
953 LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
954 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
955 LINE(BLAME_ID, "", COLOR_MAGENTA, COLOR_DEFAULT, 0)
958 #define LINE(type, line, fg, bg, attr) \
966 const char *name; /* Option name. */
967 int namelen; /* Size of option name. */
968 const char *line; /* The start of line to match. */
969 int linelen; /* Size of string to match. */
970 int fg, bg, attr; /* Color and text attributes for the lines. */
973 static struct line_info line_info[] = {
974 #define LINE(type, line, fg, bg, attr) \
975 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
980 static enum line_type
981 get_line_type(const char *line)
983 int linelen = strlen(line);
986 for (type = 0; type < ARRAY_SIZE(line_info); type++)
987 /* Case insensitive search matches Signed-off-by lines better. */
988 if (linelen >= line_info[type].linelen &&
989 !strncasecmp(line_info[type].line, line, line_info[type].linelen))
996 get_line_attr(enum line_type type)
998 assert(type < ARRAY_SIZE(line_info));
999 return COLOR_PAIR(type) | line_info[type].attr;
1002 static struct line_info *
1003 get_line_info(const char *name)
1005 size_t namelen = strlen(name);
1006 enum line_type type;
1008 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1009 if (namelen == line_info[type].namelen &&
1010 !string_enum_compare(line_info[type].name, name, namelen))
1011 return &line_info[type];
1019 int default_bg = line_info[LINE_DEFAULT].bg;
1020 int default_fg = line_info[LINE_DEFAULT].fg;
1021 enum line_type type;
1025 if (assume_default_colors(default_fg, default_bg) == ERR) {
1026 default_bg = COLOR_BLACK;
1027 default_fg = COLOR_WHITE;
1030 for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1031 struct line_info *info = &line_info[type];
1032 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1033 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1035 init_pair(type, fg, bg);
1040 enum line_type type;
1043 unsigned int selected:1;
1044 unsigned int dirty:1;
1045 unsigned int cleareol:1;
1047 void *data; /* User data */
1057 enum request request;
1060 static struct keybinding default_keybindings[] = {
1061 /* View switching */
1062 { 'm', REQ_VIEW_MAIN },
1063 { 'd', REQ_VIEW_DIFF },
1064 { 'l', REQ_VIEW_LOG },
1065 { 't', REQ_VIEW_TREE },
1066 { 'f', REQ_VIEW_BLOB },
1067 { 'B', REQ_VIEW_BLAME },
1068 { 'p', REQ_VIEW_PAGER },
1069 { 'h', REQ_VIEW_HELP },
1070 { 'S', REQ_VIEW_STATUS },
1071 { 'c', REQ_VIEW_STAGE },
1073 /* View manipulation */
1074 { 'q', REQ_VIEW_CLOSE },
1075 { KEY_TAB, REQ_VIEW_NEXT },
1076 { KEY_RETURN, REQ_ENTER },
1077 { KEY_UP, REQ_PREVIOUS },
1078 { KEY_DOWN, REQ_NEXT },
1079 { 'R', REQ_REFRESH },
1080 { KEY_F(5), REQ_REFRESH },
1081 { 'O', REQ_MAXIMIZE },
1083 /* Cursor navigation */
1084 { 'k', REQ_MOVE_UP },
1085 { 'j', REQ_MOVE_DOWN },
1086 { KEY_HOME, REQ_MOVE_FIRST_LINE },
1087 { KEY_END, REQ_MOVE_LAST_LINE },
1088 { KEY_NPAGE, REQ_MOVE_PAGE_DOWN },
1089 { ' ', REQ_MOVE_PAGE_DOWN },
1090 { KEY_PPAGE, REQ_MOVE_PAGE_UP },
1091 { 'b', REQ_MOVE_PAGE_UP },
1092 { '-', REQ_MOVE_PAGE_UP },
1095 { KEY_LEFT, REQ_SCROLL_LEFT },
1096 { KEY_RIGHT, REQ_SCROLL_RIGHT },
1097 { KEY_IC, REQ_SCROLL_LINE_UP },
1098 { KEY_DC, REQ_SCROLL_LINE_DOWN },
1099 { 'w', REQ_SCROLL_PAGE_UP },
1100 { 's', REQ_SCROLL_PAGE_DOWN },
1103 { '/', REQ_SEARCH },
1104 { '?', REQ_SEARCH_BACK },
1105 { 'n', REQ_FIND_NEXT },
1106 { 'N', REQ_FIND_PREV },
1110 { 'z', REQ_STOP_LOADING },
1111 { 'v', REQ_SHOW_VERSION },
1112 { 'r', REQ_SCREEN_REDRAW },
1113 { '.', REQ_TOGGLE_LINENO },
1114 { 'D', REQ_TOGGLE_DATE },
1115 { 'A', REQ_TOGGLE_AUTHOR },
1116 { 'g', REQ_TOGGLE_REV_GRAPH },
1117 { 'F', REQ_TOGGLE_REFS },
1118 { ':', REQ_PROMPT },
1119 { 'u', REQ_STATUS_UPDATE },
1120 { '!', REQ_STATUS_REVERT },
1121 { 'M', REQ_STATUS_MERGE },
1122 { '@', REQ_STAGE_NEXT },
1123 { ',', REQ_PARENT },
1127 #define KEYMAP_INFO \
1141 #define KEYMAP_(name) KEYMAP_##name
1146 static struct enum_map keymap_table[] = {
1147 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1152 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1154 struct keybinding_table {
1155 struct keybinding *data;
1159 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1162 add_keybinding(enum keymap keymap, enum request request, int key)
1164 struct keybinding_table *table = &keybindings[keymap];
1166 table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1168 die("Failed to allocate keybinding");
1169 table->data[table->size].alias = key;
1170 table->data[table->size++].request = request;
1173 /* Looks for a key binding first in the given map, then in the generic map, and
1174 * lastly in the default keybindings. */
1176 get_keybinding(enum keymap keymap, int key)
1180 for (i = 0; i < keybindings[keymap].size; i++)
1181 if (keybindings[keymap].data[i].alias == key)
1182 return keybindings[keymap].data[i].request;
1184 for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1185 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1186 return keybindings[KEYMAP_GENERIC].data[i].request;
1188 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1189 if (default_keybindings[i].alias == key)
1190 return default_keybindings[i].request;
1192 return (enum request) key;
1201 static struct key key_table[] = {
1202 { "Enter", KEY_RETURN },
1204 { "Backspace", KEY_BACKSPACE },
1206 { "Escape", KEY_ESC },
1207 { "Left", KEY_LEFT },
1208 { "Right", KEY_RIGHT },
1210 { "Down", KEY_DOWN },
1211 { "Insert", KEY_IC },
1212 { "Delete", KEY_DC },
1214 { "Home", KEY_HOME },
1216 { "PageUp", KEY_PPAGE },
1217 { "PageDown", KEY_NPAGE },
1227 { "F10", KEY_F(10) },
1228 { "F11", KEY_F(11) },
1229 { "F12", KEY_F(12) },
1233 get_key_value(const char *name)
1237 for (i = 0; i < ARRAY_SIZE(key_table); i++)
1238 if (!strcasecmp(key_table[i].name, name))
1239 return key_table[i].value;
1241 if (strlen(name) == 1 && isprint(*name))
1248 get_key_name(int key_value)
1250 static char key_char[] = "'X'";
1251 const char *seq = NULL;
1254 for (key = 0; key < ARRAY_SIZE(key_table); key++)
1255 if (key_table[key].value == key_value)
1256 seq = key_table[key].name;
1260 isprint(key_value)) {
1261 key_char[1] = (char) key_value;
1265 return seq ? seq : "(no key)";
1269 get_key(enum request request)
1271 static char buf[BUFSIZ];
1278 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1279 struct keybinding *keybinding = &default_keybindings[i];
1281 if (keybinding->request != request)
1284 if (!string_format_from(buf, &pos, "%s%s", sep,
1285 get_key_name(keybinding->alias)))
1286 return "Too many keybindings!";
1293 struct run_request {
1296 const char *argv[SIZEOF_ARG];
1299 static struct run_request *run_request;
1300 static size_t run_requests;
1303 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1305 struct run_request *req;
1307 if (argc >= ARRAY_SIZE(req->argv) - 1)
1310 req = realloc(run_request, (run_requests + 1) * sizeof(*run_request));
1315 req = &run_request[run_requests];
1316 req->keymap = keymap;
1318 req->argv[0] = NULL;
1320 if (!format_argv(req->argv, argv, FORMAT_NONE))
1323 return REQ_NONE + ++run_requests;
1326 static struct run_request *
1327 get_run_request(enum request request)
1329 if (request <= REQ_NONE)
1331 return &run_request[request - REQ_NONE - 1];
1335 add_builtin_run_requests(void)
1337 const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1338 const char *gc[] = { "git", "gc", NULL };
1345 { KEYMAP_MAIN, 'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1346 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1350 for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1353 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1354 if (req != REQ_NONE)
1355 add_keybinding(reqs[i].keymap, req, reqs[i].key);
1360 * User config file handling.
1363 static struct enum_map color_map[] = {
1364 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1376 static struct enum_map attr_map[] = {
1377 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1384 ATTR_MAP(UNDERLINE),
1387 #define set_color(color, name) map_enum(color, color_map, name)
1388 #define set_attribute(attr, name) map_enum(attr, attr_map, name)
1390 static int config_lineno;
1391 static bool config_errors;
1392 static const char *config_msg;
1394 /* Wants: object fgcolor bgcolor [attribute] */
1396 option_color_command(int argc, const char *argv[])
1398 struct line_info *info;
1400 if (argc != 3 && argc != 4) {
1401 config_msg = "Wrong number of arguments given to color command";
1405 info = get_line_info(argv[0]);
1407 static struct enum_map obsolete[] = {
1408 ENUM_MAP("main-delim", LINE_DELIMITER),
1409 ENUM_MAP("main-date", LINE_DATE),
1410 ENUM_MAP("main-author", LINE_AUTHOR),
1414 if (!map_enum(&index, obsolete, argv[0])) {
1415 config_msg = "Unknown color name";
1418 info = &line_info[index];
1421 if (!set_color(&info->fg, argv[1]) ||
1422 !set_color(&info->bg, argv[2])) {
1423 config_msg = "Unknown color";
1427 if (argc == 4 && !set_attribute(&info->attr, argv[3])) {
1428 config_msg = "Unknown attribute";
1435 static int parse_bool(bool *opt, const char *arg)
1437 *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1443 parse_int(int *opt, const char *arg, int min, int max)
1445 int value = atoi(arg);
1447 if (min <= value && value <= max)
1453 parse_string(char *opt, const char *arg, size_t optsize)
1455 int arglen = strlen(arg);
1460 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1461 config_msg = "Unmatched quotation";
1464 arg += 1; arglen -= 2;
1466 string_ncopy_do(opt, optsize, arg, strlen(arg));
1471 /* Wants: name = value */
1473 option_set_command(int argc, const char *argv[])
1476 config_msg = "Wrong number of arguments given to set command";
1480 if (strcmp(argv[1], "=")) {
1481 config_msg = "No value assigned";
1485 if (!strcmp(argv[0], "show-author"))
1486 return parse_bool(&opt_author, argv[2]);
1488 if (!strcmp(argv[0], "show-date"))
1489 return parse_bool(&opt_date, argv[2]);
1491 if (!strcmp(argv[0], "show-rev-graph"))
1492 return parse_bool(&opt_rev_graph, argv[2]);
1494 if (!strcmp(argv[0], "show-refs"))
1495 return parse_bool(&opt_show_refs, argv[2]);
1497 if (!strcmp(argv[0], "show-line-numbers"))
1498 return parse_bool(&opt_line_number, argv[2]);
1500 if (!strcmp(argv[0], "line-graphics"))
1501 return parse_bool(&opt_line_graphics, argv[2]);
1503 if (!strcmp(argv[0], "line-number-interval"))
1504 return parse_int(&opt_num_interval, argv[2], 1, 1024);
1506 if (!strcmp(argv[0], "author-width"))
1507 return parse_int(&opt_author_cols, argv[2], 0, 1024);
1509 if (!strcmp(argv[0], "tab-size"))
1510 return parse_int(&opt_tab_size, argv[2], 1, 1024);
1512 if (!strcmp(argv[0], "commit-encoding"))
1513 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
1515 config_msg = "Unknown variable name";
1519 /* Wants: mode request key */
1521 option_bind_command(int argc, const char *argv[])
1523 enum request request;
1528 config_msg = "Wrong number of arguments given to bind command";
1532 if (set_keymap(&keymap, argv[0]) == ERR) {
1533 config_msg = "Unknown key map";
1537 key = get_key_value(argv[1]);
1539 config_msg = "Unknown key";
1543 request = get_request(argv[2]);
1544 if (request == REQ_NONE) {
1545 static struct enum_map obsolete[] = {
1546 ENUM_MAP("cherry-pick", REQ_NONE),
1547 ENUM_MAP("screen-resize", REQ_NONE),
1548 ENUM_MAP("tree-parent", REQ_PARENT),
1552 if (map_enum(&alias, obsolete, argv[2])) {
1553 if (alias != REQ_NONE)
1554 add_keybinding(keymap, alias, key);
1555 config_msg = "Obsolete request name";
1559 if (request == REQ_NONE && *argv[2]++ == '!')
1560 request = add_run_request(keymap, key, argc - 2, argv + 2);
1561 if (request == REQ_NONE) {
1562 config_msg = "Unknown request name";
1566 add_keybinding(keymap, request, key);
1572 set_option(const char *opt, char *value)
1574 const char *argv[SIZEOF_ARG];
1577 if (!argv_from_string(argv, &argc, value)) {
1578 config_msg = "Too many option arguments";
1582 if (!strcmp(opt, "color"))
1583 return option_color_command(argc, argv);
1585 if (!strcmp(opt, "set"))
1586 return option_set_command(argc, argv);
1588 if (!strcmp(opt, "bind"))
1589 return option_bind_command(argc, argv);
1591 config_msg = "Unknown option command";
1596 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1601 config_msg = "Internal error";
1603 /* Check for comment markers, since read_properties() will
1604 * only ensure opt and value are split at first " \t". */
1605 optlen = strcspn(opt, "#");
1609 if (opt[optlen] != 0) {
1610 config_msg = "No option value";
1614 /* Look for comment endings in the value. */
1615 size_t len = strcspn(value, "#");
1617 if (len < valuelen) {
1619 value[valuelen] = 0;
1622 status = set_option(opt, value);
1625 if (status == ERR) {
1626 warn("Error on line %d, near '%.*s': %s",
1627 config_lineno, (int) optlen, opt, config_msg);
1628 config_errors = TRUE;
1631 /* Always keep going if errors are encountered. */
1636 load_option_file(const char *path)
1640 /* It's OK that the file doesn't exist. */
1641 if (!io_open(&io, path))
1645 config_errors = FALSE;
1647 if (io_load(&io, " \t", read_option) == ERR ||
1648 config_errors == TRUE)
1649 warn("Errors while loading %s.", path);
1655 const char *home = getenv("HOME");
1656 const char *tigrc_user = getenv("TIGRC_USER");
1657 const char *tigrc_system = getenv("TIGRC_SYSTEM");
1658 char buf[SIZEOF_STR];
1660 add_builtin_run_requests();
1663 tigrc_system = SYSCONFDIR "/tigrc";
1664 load_option_file(tigrc_system);
1667 if (!home || !string_format(buf, "%s/.tigrc", home))
1671 load_option_file(tigrc_user);
1684 /* The display array of active views and the index of the current view. */
1685 static struct view *display[2];
1686 static unsigned int current_view;
1688 #define foreach_displayed_view(view, i) \
1689 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1691 #define displayed_views() (display[1] != NULL ? 2 : 1)
1693 /* Current head and commit ID */
1694 static char ref_blob[SIZEOF_REF] = "";
1695 static char ref_commit[SIZEOF_REF] = "HEAD";
1696 static char ref_head[SIZEOF_REF] = "HEAD";
1699 const char *name; /* View name */
1700 const char *cmd_env; /* Command line set via environment */
1701 const char *id; /* Points to either of ref_{head,commit,blob} */
1703 struct view_ops *ops; /* View operations */
1705 enum keymap keymap; /* What keymap does this view have */
1706 bool git_dir; /* Whether the view requires a git directory. */
1708 char ref[SIZEOF_REF]; /* Hovered commit reference */
1709 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
1711 int height, width; /* The width and height of the main window */
1712 WINDOW *win; /* The main window */
1713 WINDOW *title; /* The title window living below the main window */
1716 unsigned long offset; /* Offset of the window top */
1717 unsigned long yoffset; /* Offset from the window side. */
1718 unsigned long lineno; /* Current line number */
1719 unsigned long p_offset; /* Previous offset of the window top */
1720 unsigned long p_yoffset;/* Previous offset from the window side */
1721 unsigned long p_lineno; /* Previous current line number */
1722 bool p_restore; /* Should the previous position be restored. */
1725 char grep[SIZEOF_STR]; /* Search string */
1726 regex_t *regex; /* Pre-compiled regexp */
1728 /* If non-NULL, points to the view that opened this view. If this view
1729 * is closed tig will switch back to the parent view. */
1730 struct view *parent;
1733 size_t lines; /* Total number of lines */
1734 struct line *line; /* Line index */
1735 size_t line_alloc; /* Total number of allocated lines */
1736 unsigned int digits; /* Number of digits in the lines member. */
1739 struct line *curline; /* Line currently being drawn. */
1740 enum line_type curtype; /* Attribute currently used for drawing. */
1741 unsigned long col; /* Column when drawing. */
1742 bool has_scrolled; /* View was scrolled. */
1743 bool can_hscroll; /* View can be scrolled horizontally. */
1753 /* What type of content being displayed. Used in the title bar. */
1755 /* Default command arguments. */
1757 /* Open and reads in all view content. */
1758 bool (*open)(struct view *view);
1759 /* Read one line; updates view->line. */
1760 bool (*read)(struct view *view, char *data);
1761 /* Draw one line; @lineno must be < view->height. */
1762 bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
1763 /* Depending on view handle a special requests. */
1764 enum request (*request)(struct view *view, enum request request, struct line *line);
1765 /* Search for regexp in a line. */
1766 bool (*grep)(struct view *view, struct line *line);
1768 void (*select)(struct view *view, struct line *line);
1771 static struct view_ops blame_ops;
1772 static struct view_ops blob_ops;
1773 static struct view_ops diff_ops;
1774 static struct view_ops help_ops;
1775 static struct view_ops log_ops;
1776 static struct view_ops main_ops;
1777 static struct view_ops pager_ops;
1778 static struct view_ops stage_ops;
1779 static struct view_ops status_ops;
1780 static struct view_ops tree_ops;
1782 #define VIEW_STR(name, env, ref, ops, map, git) \
1783 { name, #env, ref, ops, map, git }
1785 #define VIEW_(id, name, ops, git, ref) \
1786 VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
1789 static struct view views[] = {
1790 VIEW_(MAIN, "main", &main_ops, TRUE, ref_head),
1791 VIEW_(DIFF, "diff", &diff_ops, TRUE, ref_commit),
1792 VIEW_(LOG, "log", &log_ops, TRUE, ref_head),
1793 VIEW_(TREE, "tree", &tree_ops, TRUE, ref_commit),
1794 VIEW_(BLOB, "blob", &blob_ops, TRUE, ref_blob),
1795 VIEW_(BLAME, "blame", &blame_ops, TRUE, ref_commit),
1796 VIEW_(HELP, "help", &help_ops, FALSE, ""),
1797 VIEW_(PAGER, "pager", &pager_ops, FALSE, "stdin"),
1798 VIEW_(STATUS, "status", &status_ops, TRUE, ""),
1799 VIEW_(STAGE, "stage", &stage_ops, TRUE, ""),
1802 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
1803 #define VIEW_REQ(view) ((view) - views + REQ_OFFSET + 1)
1805 #define foreach_view(view, i) \
1806 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1808 #define view_is_displayed(view) \
1809 (view == display[0] || view == display[1])
1816 static int line_graphics[] = {
1817 /* LINE_GRAPHIC_VLINE: */ '|'
1821 set_view_attr(struct view *view, enum line_type type)
1823 if (!view->curline->selected && view->curtype != type) {
1824 wattrset(view->win, get_line_attr(type));
1825 wchgat(view->win, -1, 0, type, NULL);
1826 view->curtype = type;
1831 draw_chars(struct view *view, enum line_type type, const char *string,
1832 int max_len, bool use_tilde)
1836 int trimmed = FALSE;
1837 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
1843 len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde);
1845 col = len = strlen(string);
1846 if (len > max_len) {
1850 col = len = max_len;
1855 set_view_attr(view, type);
1857 waddnstr(view->win, string, len);
1858 if (trimmed && use_tilde) {
1859 set_view_attr(view, LINE_DELIMITER);
1860 waddch(view->win, '~');
1864 if (view->col + col >= view->width + view->yoffset)
1865 view->can_hscroll = TRUE;
1871 draw_space(struct view *view, enum line_type type, int max, int spaces)
1873 static char space[] = " ";
1876 spaces = MIN(max, spaces);
1878 while (spaces > 0) {
1879 int len = MIN(spaces, sizeof(space) - 1);
1881 col += draw_chars(view, type, space, spaces, FALSE);
1889 draw_lineno(struct view *view, unsigned int lineno)
1891 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
1893 int digits3 = view->digits < 3 ? 3 : view->digits;
1894 int max_number = MIN(digits3, STRING_SIZE(number));
1895 int max = view->width - view->col;
1898 if (max < max_number)
1901 lineno += view->offset + 1;
1902 if (lineno == 1 || (lineno % opt_num_interval) == 0) {
1903 static char fmt[] = "%1ld";
1905 if (view->digits <= 9)
1906 fmt[1] = '0' + digits3;
1908 if (!string_format(number, fmt, lineno))
1910 col = draw_chars(view, LINE_LINE_NUMBER, number, max_number, TRUE);
1912 col = draw_space(view, LINE_LINE_NUMBER, max_number, max_number);
1915 if (col < max && skip <= col) {
1916 set_view_attr(view, LINE_DEFAULT);
1917 waddch(view->win, line_graphics[LINE_GRAPHIC_VLINE]);
1922 if (col < max && skip <= col)
1923 col = draw_space(view, LINE_DEFAULT, max - col, 1);
1926 return view->width + view->yoffset <= view->col;
1930 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
1932 view->col += draw_chars(view, type, string, view->width + view->yoffset - view->col, trim);
1933 return view->width - view->col <= 0;
1937 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
1939 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
1940 int max = view->width - view->col;
1946 set_view_attr(view, type);
1947 /* Using waddch() instead of waddnstr() ensures that
1948 * they'll be rendered correctly for the cursor line. */
1949 for (i = skip; i < size; i++)
1950 waddch(view->win, graphic[i]);
1953 if (size < max && skip <= size)
1954 waddch(view->win, ' ');
1957 return view->width - view->col <= 0;
1961 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
1963 int max = MIN(view->width - view->col, len);
1967 col = draw_chars(view, type, text, max - 1, trim);
1969 col = draw_space(view, type, max - 1, max - 1);
1972 view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
1973 return view->width + view->yoffset <= view->col;
1977 draw_date(struct view *view, struct tm *time)
1979 char buf[DATE_COLS];
1984 timelen = strftime(buf, sizeof(buf), DATE_FORMAT, time);
1985 date = timelen ? buf : NULL;
1987 return draw_field(view, LINE_DATE, date, DATE_COLS, FALSE);
1991 draw_author(struct view *view, const char *author)
1993 bool trim = opt_author_cols == 0 || opt_author_cols > 5 || !author;
1996 static char initials[10];
1999 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@')
2001 memset(initials, 0, sizeof(initials));
2002 for (pos = 0; *author && pos < opt_author_cols - 1; author++, pos++) {
2003 while (is_initial_sep(*author))
2005 strncpy(&initials[pos], author, sizeof(initials) - 1 - pos);
2006 while (*author && !is_initial_sep(author[1]))
2013 return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2017 draw_mode(struct view *view, mode_t mode)
2019 static const char dir_mode[] = "drwxr-xr-x";
2020 static const char link_mode[] = "lrwxrwxrwx";
2021 static const char exe_mode[] = "-rwxr-xr-x";
2022 static const char file_mode[] = "-rw-r--r--";
2027 else if (S_ISLNK(mode))
2029 else if (mode & S_IXUSR)
2034 return draw_field(view, LINE_MODE, str, sizeof(file_mode), FALSE);
2038 draw_view_line(struct view *view, unsigned int lineno)
2041 bool selected = (view->offset + lineno == view->lineno);
2043 assert(view_is_displayed(view));
2045 if (view->offset + lineno >= view->lines)
2048 line = &view->line[view->offset + lineno];
2050 wmove(view->win, lineno, 0);
2052 wclrtoeol(view->win);
2054 view->curline = line;
2055 view->curtype = LINE_NONE;
2056 line->selected = FALSE;
2057 line->dirty = line->cleareol = 0;
2060 set_view_attr(view, LINE_CURSOR);
2061 line->selected = TRUE;
2062 view->ops->select(view, line);
2065 return view->ops->draw(view, line, lineno);
2069 redraw_view_dirty(struct view *view)
2074 for (lineno = 0; lineno < view->height; lineno++) {
2075 if (view->offset + lineno >= view->lines)
2077 if (!view->line[view->offset + lineno].dirty)
2080 if (!draw_view_line(view, lineno))
2086 wnoutrefresh(view->win);
2090 redraw_view_from(struct view *view, int lineno)
2092 assert(0 <= lineno && lineno < view->height);
2095 view->can_hscroll = FALSE;
2097 for (; lineno < view->height; lineno++) {
2098 if (!draw_view_line(view, lineno))
2102 wnoutrefresh(view->win);
2106 redraw_view(struct view *view)
2109 redraw_view_from(view, 0);
2114 update_view_title(struct view *view)
2116 char buf[SIZEOF_STR];
2117 char state[SIZEOF_STR];
2118 size_t bufpos = 0, statelen = 0;
2120 assert(view_is_displayed(view));
2122 if (view != VIEW(REQ_VIEW_STATUS) && view->lines) {
2123 unsigned int view_lines = view->offset + view->height;
2124 unsigned int lines = view->lines
2125 ? MIN(view_lines, view->lines) * 100 / view->lines
2128 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2137 time_t secs = time(NULL) - view->start_time;
2139 /* Three git seconds are a long time ... */
2141 string_format_from(state, &statelen, " loading %lds", secs);
2144 string_format_from(buf, &bufpos, "[%s]", view->name);
2145 if (*view->ref && bufpos < view->width) {
2146 size_t refsize = strlen(view->ref);
2147 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2149 if (minsize < view->width)
2150 refsize = view->width - minsize + 7;
2151 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2154 if (statelen && bufpos < view->width) {
2155 string_format_from(buf, &bufpos, "%s", state);
2158 if (view == display[current_view])
2159 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2161 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2163 mvwaddnstr(view->title, 0, 0, buf, bufpos);
2164 wclrtoeol(view->title);
2165 wnoutrefresh(view->title);
2169 resize_display(void)
2172 struct view *base = display[0];
2173 struct view *view = display[1] ? display[1] : display[0];
2175 /* Setup window dimensions */
2177 getmaxyx(stdscr, base->height, base->width);
2179 /* Make room for the status window. */
2183 /* Horizontal split. */
2184 view->width = base->width;
2185 view->height = SCALE_SPLIT_VIEW(base->height);
2186 base->height -= view->height;
2188 /* Make room for the title bar. */
2192 /* Make room for the title bar. */
2197 foreach_displayed_view (view, i) {
2199 view->win = newwin(view->height, 0, offset, 0);
2201 die("Failed to create %s view", view->name);
2203 scrollok(view->win, FALSE);
2205 view->title = newwin(1, 0, offset + view->height, 0);
2207 die("Failed to create title window");
2210 wresize(view->win, view->height, view->width);
2211 mvwin(view->win, offset, 0);
2212 mvwin(view->title, offset + view->height, 0);
2215 offset += view->height + 1;
2220 redraw_display(bool clear)
2225 foreach_displayed_view (view, i) {
2229 update_view_title(view);
2234 toggle_view_option(bool *option, const char *help)
2237 redraw_display(FALSE);
2238 report("%sabling %s", *option ? "En" : "Dis", help);
2245 /* Scrolling backend */
2247 do_scroll_view(struct view *view, int lines)
2249 bool redraw_current_line = FALSE;
2251 /* The rendering expects the new offset. */
2252 view->offset += lines;
2254 assert(0 <= view->offset && view->offset < view->lines);
2257 /* Move current line into the view. */
2258 if (view->lineno < view->offset) {
2259 view->lineno = view->offset;
2260 redraw_current_line = TRUE;
2261 } else if (view->lineno >= view->offset + view->height) {
2262 view->lineno = view->offset + view->height - 1;
2263 redraw_current_line = TRUE;
2266 assert(view->offset <= view->lineno && view->lineno < view->lines);
2268 /* Redraw the whole screen if scrolling is pointless. */
2269 if (view->height < ABS(lines)) {
2273 int line = lines > 0 ? view->height - lines : 0;
2274 int end = line + ABS(lines);
2276 scrollok(view->win, TRUE);
2277 wscrl(view->win, lines);
2278 scrollok(view->win, FALSE);
2280 while (line < end && draw_view_line(view, line))
2283 if (redraw_current_line)
2284 draw_view_line(view, view->lineno - view->offset);
2285 wnoutrefresh(view->win);
2288 view->has_scrolled = TRUE;
2292 /* Scroll frontend */
2294 scroll_view(struct view *view, enum request request)
2298 assert(view_is_displayed(view));
2301 case REQ_SCROLL_LEFT:
2302 if (view->yoffset == 0) {
2303 report("Cannot scroll beyond the first column");
2306 if (view->yoffset <= SCROLL_INTERVAL)
2309 view->yoffset -= SCROLL_INTERVAL;
2310 redraw_view_from(view, 0);
2313 case REQ_SCROLL_RIGHT:
2314 if (!view->can_hscroll) {
2315 report("Cannot scroll beyond the last column");
2318 view->yoffset += SCROLL_INTERVAL;
2322 case REQ_SCROLL_PAGE_DOWN:
2323 lines = view->height;
2324 case REQ_SCROLL_LINE_DOWN:
2325 if (view->offset + lines > view->lines)
2326 lines = view->lines - view->offset;
2328 if (lines == 0 || view->offset + view->height >= view->lines) {
2329 report("Cannot scroll beyond the last line");
2334 case REQ_SCROLL_PAGE_UP:
2335 lines = view->height;
2336 case REQ_SCROLL_LINE_UP:
2337 if (lines > view->offset)
2338 lines = view->offset;
2341 report("Cannot scroll beyond the first line");
2349 die("request %d not handled in switch", request);
2352 do_scroll_view(view, lines);
2357 move_view(struct view *view, enum request request)
2359 int scroll_steps = 0;
2363 case REQ_MOVE_FIRST_LINE:
2364 steps = -view->lineno;
2367 case REQ_MOVE_LAST_LINE:
2368 steps = view->lines - view->lineno - 1;
2371 case REQ_MOVE_PAGE_UP:
2372 steps = view->height > view->lineno
2373 ? -view->lineno : -view->height;
2376 case REQ_MOVE_PAGE_DOWN:
2377 steps = view->lineno + view->height >= view->lines
2378 ? view->lines - view->lineno - 1 : view->height;
2390 die("request %d not handled in switch", request);
2393 if (steps <= 0 && view->lineno == 0) {
2394 report("Cannot move beyond the first line");
2397 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2398 report("Cannot move beyond the last line");
2402 /* Move the current line */
2403 view->lineno += steps;
2404 assert(0 <= view->lineno && view->lineno < view->lines);
2406 /* Check whether the view needs to be scrolled */
2407 if (view->lineno < view->offset ||
2408 view->lineno >= view->offset + view->height) {
2409 scroll_steps = steps;
2410 if (steps < 0 && -steps > view->offset) {
2411 scroll_steps = -view->offset;
2413 } else if (steps > 0) {
2414 if (view->lineno == view->lines - 1 &&
2415 view->lines > view->height) {
2416 scroll_steps = view->lines - view->offset - 1;
2417 if (scroll_steps >= view->height)
2418 scroll_steps -= view->height - 1;
2423 if (!view_is_displayed(view)) {
2424 view->offset += scroll_steps;
2425 assert(0 <= view->offset && view->offset < view->lines);
2426 view->ops->select(view, &view->line[view->lineno]);
2430 /* Repaint the old "current" line if we be scrolling */
2431 if (ABS(steps) < view->height)
2432 draw_view_line(view, view->lineno - steps - view->offset);
2435 do_scroll_view(view, scroll_steps);
2439 /* Draw the current line */
2440 draw_view_line(view, view->lineno - view->offset);
2442 wnoutrefresh(view->win);
2451 static void search_view(struct view *view, enum request request);
2454 select_view_line(struct view *view, unsigned long lineno)
2456 if (lineno - view->offset >= view->height) {
2457 view->offset = lineno;
2458 view->lineno = lineno;
2459 if (view_is_displayed(view))
2463 unsigned long old_lineno = view->lineno - view->offset;
2465 view->lineno = lineno;
2466 if (view_is_displayed(view)) {
2467 draw_view_line(view, old_lineno);
2468 draw_view_line(view, view->lineno - view->offset);
2469 wnoutrefresh(view->win);
2471 view->ops->select(view, &view->line[view->lineno]);
2477 find_next(struct view *view, enum request request)
2479 unsigned long lineno = view->lineno;
2484 report("No previous search");
2486 search_view(view, request);
2496 case REQ_SEARCH_BACK:
2505 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2506 lineno += direction;
2508 /* Note, lineno is unsigned long so will wrap around in which case it
2509 * will become bigger than view->lines. */
2510 for (; lineno < view->lines; lineno += direction) {
2511 if (view->ops->grep(view, &view->line[lineno])) {
2512 select_view_line(view, lineno);
2513 report("Line %ld matches '%s'", lineno + 1, view->grep);
2518 report("No match found for '%s'", view->grep);
2522 search_view(struct view *view, enum request request)
2527 regfree(view->regex);
2530 view->regex = calloc(1, sizeof(*view->regex));
2535 regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2536 if (regex_err != 0) {
2537 char buf[SIZEOF_STR] = "unknown error";
2539 regerror(regex_err, view->regex, buf, sizeof(buf));
2540 report("Search failed: %s", buf);
2544 string_copy(view->grep, opt_search);
2546 find_next(view, request);
2550 * Incremental updating
2554 reset_view(struct view *view)
2558 for (i = 0; i < view->lines; i++)
2559 free(view->line[i].data);
2562 view->p_offset = view->offset;
2563 view->p_yoffset = view->yoffset;
2564 view->p_lineno = view->lineno;
2571 view->line_alloc = 0;
2573 view->update_secs = 0;
2577 free_argv(const char *argv[])
2581 for (argc = 0; argv[argc]; argc++)
2582 free((void *) argv[argc]);
2586 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
2588 char buf[SIZEOF_STR];
2590 bool noreplace = flags == FORMAT_NONE;
2592 free_argv(dst_argv);
2594 for (argc = 0; src_argv[argc]; argc++) {
2595 const char *arg = src_argv[argc];
2599 char *next = strstr(arg, "%(");
2600 int len = next - arg;
2603 if (!next || noreplace) {
2604 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
2609 } else if (!prefixcmp(next, "%(directory)")) {
2612 } else if (!prefixcmp(next, "%(file)")) {
2615 } else if (!prefixcmp(next, "%(ref)")) {
2616 value = *opt_ref ? opt_ref : "HEAD";
2618 } else if (!prefixcmp(next, "%(head)")) {
2621 } else if (!prefixcmp(next, "%(commit)")) {
2624 } else if (!prefixcmp(next, "%(blob)")) {
2628 report("Unknown replacement: `%s`", next);
2632 if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
2635 arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
2638 dst_argv[argc] = strdup(buf);
2639 if (!dst_argv[argc])
2643 dst_argv[argc] = NULL;
2645 return src_argv[argc] == NULL;
2649 restore_view_position(struct view *view)
2651 if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
2654 /* Changing the view position cancels the restoring. */
2655 /* FIXME: Changing back to the first line is not detected. */
2656 if (view->offset != 0 || view->lineno != 0) {
2657 view->p_restore = FALSE;
2661 if (view->p_lineno >= view->lines) {
2662 view->p_lineno = view->lines > 0 ? view->lines - 1 : 0;
2663 if (view->p_offset >= view->p_lineno) {
2664 unsigned long half = view->height / 2;
2666 if (view->p_lineno > half)
2667 view->p_offset = view->p_lineno - half;
2673 if (view_is_displayed(view) &&
2674 view->offset != view->p_offset &&
2675 view->lineno != view->p_lineno)
2678 view->offset = view->p_offset;
2679 view->yoffset = view->p_yoffset;
2680 view->lineno = view->p_lineno;
2681 view->p_restore = FALSE;
2687 end_update(struct view *view, bool force)
2691 while (!view->ops->read(view, NULL))
2694 set_nonblocking_input(FALSE);
2696 kill_io(view->pipe);
2697 done_io(view->pipe);
2702 setup_update(struct view *view, const char *vid)
2704 set_nonblocking_input(TRUE);
2706 string_copy_rev(view->vid, vid);
2707 view->pipe = &view->io;
2708 view->start_time = time(NULL);
2712 prepare_update(struct view *view, const char *argv[], const char *dir,
2713 enum format_flags flags)
2716 end_update(view, TRUE);
2717 return init_io_rd(&view->io, argv, dir, flags);
2721 prepare_update_file(struct view *view, const char *name)
2724 end_update(view, TRUE);
2725 return io_open(&view->io, name);
2729 begin_update(struct view *view, bool refresh)
2732 end_update(view, TRUE);
2735 if (!start_io(&view->io))
2739 if (view == VIEW(REQ_VIEW_TREE) && strcmp(view->vid, view->id))
2742 if (!run_io_rd(&view->io, view->ops->argv, FORMAT_ALL))
2745 /* Put the current ref_* value to the view title ref
2746 * member. This is needed by the blob view. Most other
2747 * views sets it automatically after loading because the
2748 * first line is a commit line. */
2749 string_copy_rev(view->ref, view->id);
2752 setup_update(view, view->id);
2757 #define ITEM_CHUNK_SIZE 256
2759 realloc_items(void *mem, size_t *size, size_t new_size, size_t item_size)
2761 size_t num_chunks = *size / ITEM_CHUNK_SIZE;
2762 size_t num_chunks_new = (new_size + ITEM_CHUNK_SIZE - 1) / ITEM_CHUNK_SIZE;
2764 if (mem == NULL || num_chunks != num_chunks_new) {
2765 *size = num_chunks_new * ITEM_CHUNK_SIZE;
2766 mem = realloc(mem, *size * item_size);
2772 static struct line *
2773 realloc_lines(struct view *view, size_t line_size)
2775 size_t alloc = view->line_alloc;
2776 struct line *tmp = realloc_items(view->line, &alloc, line_size,
2777 sizeof(*view->line));
2783 view->line_alloc = alloc;
2788 update_view(struct view *view)
2790 char out_buffer[BUFSIZ * 2];
2792 /* Clear the view and redraw everything since the tree sorting
2793 * might have rearranged things. */
2794 bool redraw = view->lines == 0;
2795 bool can_read = TRUE;
2800 if (!io_can_read(view->pipe)) {
2801 if (view->lines == 0) {
2802 time_t secs = time(NULL) - view->start_time;
2804 if (secs > 1 && secs > view->update_secs) {
2805 if (view->update_secs == 0)
2807 update_view_title(view);
2808 view->update_secs = secs;
2814 for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
2815 if (opt_iconv != ICONV_NONE) {
2816 ICONV_CONST char *inbuf = line;
2817 size_t inlen = strlen(line) + 1;
2819 char *outbuf = out_buffer;
2820 size_t outlen = sizeof(out_buffer);
2824 ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2825 if (ret != (size_t) -1)
2829 if (!view->ops->read(view, line)) {
2830 report("Allocation failure");
2831 end_update(view, TRUE);
2837 unsigned long lines = view->lines;
2840 for (digits = 0; lines; digits++)
2843 /* Keep the displayed view in sync with line number scaling. */
2844 if (digits != view->digits) {
2845 view->digits = digits;
2846 if (opt_line_number || view == VIEW(REQ_VIEW_BLAME))
2851 if (io_error(view->pipe)) {
2852 report("Failed to read: %s", io_strerror(view->pipe));
2853 end_update(view, TRUE);
2855 } else if (io_eof(view->pipe)) {
2857 end_update(view, FALSE);
2860 if (restore_view_position(view))
2863 if (!view_is_displayed(view))
2867 redraw_view_from(view, 0);
2869 redraw_view_dirty(view);
2871 /* Update the title _after_ the redraw so that if the redraw picks up a
2872 * commit reference in view->ref it'll be available here. */
2873 update_view_title(view);
2877 static struct line *
2878 add_line_data(struct view *view, void *data, enum line_type type)
2882 if (!realloc_lines(view, view->lines + 1))
2885 line = &view->line[view->lines++];
2886 memset(line, 0, sizeof(*line));
2894 static struct line *
2895 add_line_text(struct view *view, const char *text, enum line_type type)
2897 char *data = text ? strdup(text) : NULL;
2899 return data ? add_line_data(view, data, type) : NULL;
2902 static struct line *
2903 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
2905 char buf[SIZEOF_STR];
2908 va_start(args, fmt);
2909 if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
2913 return buf[0] ? add_line_text(view, buf, type) : NULL;
2921 OPEN_DEFAULT = 0, /* Use default view switching. */
2922 OPEN_SPLIT = 1, /* Split current view. */
2923 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
2924 OPEN_REFRESH = 16, /* Refresh view using previous command. */
2925 OPEN_PREPARED = 32, /* Open already prepared command. */
2929 open_view(struct view *prev, enum request request, enum open_flags flags)
2931 bool split = !!(flags & OPEN_SPLIT);
2932 bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
2933 bool nomaximize = !!(flags & OPEN_REFRESH);
2934 struct view *view = VIEW(request);
2935 int nviews = displayed_views();
2936 struct view *base_view = display[0];
2938 if (view == prev && nviews == 1 && !reload) {
2939 report("Already in %s view", view->name);
2943 if (view->git_dir && !opt_git_dir[0]) {
2944 report("The %s view is disabled in pager view", view->name);
2951 } else if (!nomaximize) {
2952 /* Maximize the current view. */
2953 memset(display, 0, sizeof(display));
2955 display[current_view] = view;
2958 /* Resize the view when switching between split- and full-screen,
2959 * or when switching between two different full-screen views. */
2960 if (nviews != displayed_views() ||
2961 (nviews == 1 && base_view != display[0]))
2964 if (view->ops->open) {
2966 end_update(view, TRUE);
2967 if (!view->ops->open(view)) {
2968 report("Failed to load %s view", view->name);
2971 restore_view_position(view);
2973 } else if ((reload || strcmp(view->vid, view->id)) &&
2974 !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
2975 report("Failed to load %s view", view->name);
2979 if (split && prev->lineno - prev->offset >= prev->height) {
2980 /* Take the title line into account. */
2981 int lines = prev->lineno - prev->offset - prev->height + 1;
2983 /* Scroll the view that was split if the current line is
2984 * outside the new limited view. */
2985 do_scroll_view(prev, lines);
2988 if (prev && view != prev) {
2990 /* "Blur" the previous view. */
2991 update_view_title(prev);
2994 view->parent = prev;
2997 if (view->pipe && view->lines == 0) {
2998 /* Clear the old view and let the incremental updating refill
3001 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3003 } else if (view_is_displayed(view)) {
3010 open_external_viewer(const char *argv[], const char *dir)
3012 def_prog_mode(); /* save current tty modes */
3013 endwin(); /* restore original tty modes */
3014 run_io_fg(argv, dir);
3015 fprintf(stderr, "Press Enter to continue");
3018 redraw_display(TRUE);
3022 open_mergetool(const char *file)
3024 const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3026 open_external_viewer(mergetool_argv, opt_cdup);
3030 open_editor(bool from_root, const char *file)
3032 const char *editor_argv[] = { "vi", file, NULL };
3035 editor = getenv("GIT_EDITOR");
3036 if (!editor && *opt_editor)
3037 editor = opt_editor;
3039 editor = getenv("VISUAL");
3041 editor = getenv("EDITOR");
3045 editor_argv[0] = editor;
3046 open_external_viewer(editor_argv, from_root ? opt_cdup : NULL);
3050 open_run_request(enum request request)
3052 struct run_request *req = get_run_request(request);
3053 const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3056 report("Unknown run request");
3060 if (format_argv(argv, req->argv, FORMAT_ALL))
3061 open_external_viewer(argv, NULL);
3066 * User request switch noodle
3070 view_driver(struct view *view, enum request request)
3074 if (request == REQ_NONE) {
3079 if (request > REQ_NONE) {
3080 open_run_request(request);
3081 /* FIXME: When all views can refresh always do this. */
3082 if (view == VIEW(REQ_VIEW_STATUS) ||
3083 view == VIEW(REQ_VIEW_MAIN) ||
3084 view == VIEW(REQ_VIEW_LOG) ||
3085 view == VIEW(REQ_VIEW_STAGE))
3086 request = REQ_REFRESH;
3091 if (view && view->lines) {
3092 request = view->ops->request(view, request, &view->line[view->lineno]);
3093 if (request == REQ_NONE)
3100 case REQ_MOVE_PAGE_UP:
3101 case REQ_MOVE_PAGE_DOWN:
3102 case REQ_MOVE_FIRST_LINE:
3103 case REQ_MOVE_LAST_LINE:
3104 move_view(view, request);
3107 case REQ_SCROLL_LEFT:
3108 case REQ_SCROLL_RIGHT:
3109 case REQ_SCROLL_LINE_DOWN:
3110 case REQ_SCROLL_LINE_UP:
3111 case REQ_SCROLL_PAGE_DOWN:
3112 case REQ_SCROLL_PAGE_UP:
3113 scroll_view(view, request);
3116 case REQ_VIEW_BLAME:
3118 report("No file chosen, press %s to open tree view",
3119 get_key(REQ_VIEW_TREE));
3122 open_view(view, request, OPEN_DEFAULT);
3127 report("No file chosen, press %s to open tree view",
3128 get_key(REQ_VIEW_TREE));
3131 open_view(view, request, OPEN_DEFAULT);
3134 case REQ_VIEW_PAGER:
3135 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3136 report("No pager content, press %s to run command from prompt",
3137 get_key(REQ_PROMPT));
3140 open_view(view, request, OPEN_DEFAULT);
3143 case REQ_VIEW_STAGE:
3144 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3145 report("No stage content, press %s to open the status view and choose file",
3146 get_key(REQ_VIEW_STATUS));
3149 open_view(view, request, OPEN_DEFAULT);
3152 case REQ_VIEW_STATUS:
3153 if (opt_is_inside_work_tree == FALSE) {
3154 report("The status view requires a working tree");
3157 open_view(view, request, OPEN_DEFAULT);
3165 open_view(view, request, OPEN_DEFAULT);
3170 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3172 if ((view == VIEW(REQ_VIEW_DIFF) &&
3173 view->parent == VIEW(REQ_VIEW_MAIN)) ||
3174 (view == VIEW(REQ_VIEW_DIFF) &&
3175 view->parent == VIEW(REQ_VIEW_BLAME)) ||
3176 (view == VIEW(REQ_VIEW_STAGE) &&
3177 view->parent == VIEW(REQ_VIEW_STATUS)) ||
3178 (view == VIEW(REQ_VIEW_BLOB) &&
3179 view->parent == VIEW(REQ_VIEW_TREE))) {
3182 view = view->parent;
3183 line = view->lineno;
3184 move_view(view, request);
3185 if (view_is_displayed(view))
3186 update_view_title(view);
3187 if (line != view->lineno)
3188 view->ops->request(view, REQ_ENTER,
3189 &view->line[view->lineno]);
3192 move_view(view, request);
3198 int nviews = displayed_views();
3199 int next_view = (current_view + 1) % nviews;
3201 if (next_view == current_view) {
3202 report("Only one view is displayed");
3206 current_view = next_view;
3207 /* Blur out the title of the previous view. */
3208 update_view_title(view);
3213 report("Refreshing is not yet supported for the %s view", view->name);
3217 if (displayed_views() == 2)
3218 open_view(view, VIEW_REQ(view), OPEN_DEFAULT);
3221 case REQ_TOGGLE_LINENO:
3222 toggle_view_option(&opt_line_number, "line numbers");
3225 case REQ_TOGGLE_DATE:
3226 toggle_view_option(&opt_date, "date display");
3229 case REQ_TOGGLE_AUTHOR:
3230 toggle_view_option(&opt_author, "author display");
3233 case REQ_TOGGLE_REV_GRAPH:
3234 toggle_view_option(&opt_rev_graph, "revision graph display");
3237 case REQ_TOGGLE_REFS:
3238 toggle_view_option(&opt_show_refs, "reference display");
3242 case REQ_SEARCH_BACK:
3243 search_view(view, request);
3248 find_next(view, request);
3251 case REQ_STOP_LOADING:
3252 for (i = 0; i < ARRAY_SIZE(views); i++) {
3255 report("Stopped loading the %s view", view->name),
3256 end_update(view, TRUE);
3260 case REQ_SHOW_VERSION:
3261 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3264 case REQ_SCREEN_REDRAW:
3265 redraw_display(TRUE);
3269 report("Nothing to edit");
3273 report("Nothing to enter");
3276 case REQ_VIEW_CLOSE:
3277 /* XXX: Mark closed views by letting view->parent point to the
3278 * view itself. Parents to closed view should never be
3281 view->parent->parent != view->parent) {
3282 memset(display, 0, sizeof(display));
3284 display[current_view] = view->parent;
3285 view->parent = view;
3287 redraw_display(FALSE);
3296 report("Unknown key, press 'h' for help");
3305 * View backend utilities
3309 parse_timezone(time_t *time, const char *zone)
3313 tz = ('0' - zone[1]) * 60 * 60 * 10;
3314 tz += ('0' - zone[2]) * 60 * 60;
3315 tz += ('0' - zone[3]) * 60;
3316 tz += ('0' - zone[4]);
3324 /* Parse author lines where the name may be empty:
3325 * author <email@address.tld> 1138474660 +0100
3328 parse_author_line(char *ident, char *author, size_t authorsize, struct tm *tm)
3330 char *nameend = strchr(ident, '<');
3331 char *emailend = strchr(ident, '>');
3333 if (nameend && emailend)
3334 *nameend = *emailend = 0;
3335 ident = chomp_string(ident);
3338 ident = chomp_string(nameend + 1);
3343 string_ncopy_do(author, authorsize, ident, strlen(ident));
3345 /* Parse epoch and timezone */
3346 if (emailend && emailend[1] == ' ') {
3347 char *secs = emailend + 2;
3348 char *zone = strchr(secs, ' ');
3349 time_t time = (time_t) atol(secs);
3351 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
3352 parse_timezone(&time, zone + 1);
3354 gmtime_r(&time, tm);
3358 static enum input_status
3359 select_commit_parent_handler(void *data, char *buf, int c)
3361 size_t parents = *(size_t *) data;
3368 parent = atoi(buf) * 10;
3371 if (parent > parents)
3377 select_commit_parent(const char *id, char rev[SIZEOF_REV])
3379 char buf[SIZEOF_STR * 4];
3380 const char *revlist_argv[] = {
3381 "git", "rev-list", "-1", "--parents", id, NULL
3385 if (!run_io_buf(revlist_argv, buf, sizeof(buf)) ||
3386 !*chomp_string(buf) ||
3387 (parents = (strlen(buf) / 40) - 1) < 0) {
3388 report("Failed to get parent information");
3391 } else if (parents == 0) {
3392 report("The selected commit has no parents");
3397 char prompt[SIZEOF_STR];
3400 if (!string_format(prompt, "Which parent? [1..%d] ", parents))
3402 result = prompt_input(prompt, select_commit_parent_handler, &parents);
3405 parents = atoi(result);
3408 string_copy_rev(rev, &buf[41 * parents]);
3417 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3419 char text[SIZEOF_STR];
3421 if (opt_line_number && draw_lineno(view, lineno))
3424 string_expand(text, sizeof(text), line->data, opt_tab_size);
3425 draw_text(view, line->type, text, TRUE);
3430 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3432 const char *describe_argv[] = { "git", "describe", commit_id, NULL };
3433 char refbuf[SIZEOF_STR];
3436 if (run_io_buf(describe_argv, refbuf, sizeof(refbuf)))
3437 ref = chomp_string(refbuf);
3442 /* This is the only fatal call, since it can "corrupt" the buffer. */
3443 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
3450 add_pager_refs(struct view *view, struct line *line)
3452 char buf[SIZEOF_STR];
3453 char *commit_id = (char *)line->data + STRING_SIZE("commit ");
3455 size_t bufpos = 0, refpos = 0;
3456 const char *sep = "Refs: ";
3457 bool is_tag = FALSE;
3459 assert(line->type == LINE_COMMIT);
3461 refs = get_refs(commit_id);
3463 if (view == VIEW(REQ_VIEW_DIFF))
3464 goto try_add_describe_ref;
3469 struct ref *ref = refs[refpos];
3470 const char *fmt = ref->tag ? "%s[%s]" :
3471 ref->remote ? "%s<%s>" : "%s%s";
3473 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
3478 } while (refs[refpos++]->next);
3480 if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
3481 try_add_describe_ref:
3482 /* Add <tag>-g<commit_id> "fake" reference. */
3483 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
3490 add_line_text(view, buf, LINE_PP_REFS);
3494 pager_read(struct view *view, char *data)
3501 line = add_line_text(view, data, get_line_type(data));
3505 if (line->type == LINE_COMMIT &&
3506 (view == VIEW(REQ_VIEW_DIFF) ||
3507 view == VIEW(REQ_VIEW_LOG)))
3508 add_pager_refs(view, line);
3514 pager_request(struct view *view, enum request request, struct line *line)
3518 if (request != REQ_ENTER)
3521 if (line->type == LINE_COMMIT &&
3522 (view == VIEW(REQ_VIEW_LOG) ||
3523 view == VIEW(REQ_VIEW_PAGER))) {
3524 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3528 /* Always scroll the view even if it was split. That way
3529 * you can use Enter to scroll through the log view and
3530 * split open each commit diff. */
3531 scroll_view(view, REQ_SCROLL_LINE_DOWN);
3533 /* FIXME: A minor workaround. Scrolling the view will call report("")
3534 * but if we are scrolling a non-current view this won't properly
3535 * update the view title. */
3537 update_view_title(view);
3543 pager_grep(struct view *view, struct line *line)
3546 char *text = line->data;
3551 if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
3558 pager_select(struct view *view, struct line *line)
3560 if (line->type == LINE_COMMIT) {
3561 char *text = (char *)line->data + STRING_SIZE("commit ");
3563 if (view != VIEW(REQ_VIEW_PAGER))
3564 string_copy_rev(view->ref, text);
3565 string_copy_rev(ref_commit, text);
3569 static struct view_ops pager_ops = {
3580 static const char *log_argv[SIZEOF_ARG] = {
3581 "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3585 log_request(struct view *view, enum request request, struct line *line)
3590 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
3593 return pager_request(view, request, line);
3597 static struct view_ops log_ops = {
3608 static const char *diff_argv[SIZEOF_ARG] = {
3609 "git", "show", "--pretty=fuller", "--no-color", "--root",
3610 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
3613 static struct view_ops diff_ops = {
3629 help_open(struct view *view)
3631 char buf[SIZEOF_STR];
3635 if (view->lines > 0)
3638 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
3640 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
3643 if (req_info[i].request == REQ_NONE)
3646 if (!req_info[i].request) {
3647 add_line_text(view, "", LINE_DEFAULT);
3648 add_line_text(view, req_info[i].help, LINE_DEFAULT);
3652 key = get_key(req_info[i].request);
3654 key = "(no key defined)";
3656 for (bufpos = 0; bufpos <= req_info[i].namelen; bufpos++) {
3657 buf[bufpos] = tolower(req_info[i].name[bufpos]);
3658 if (buf[bufpos] == '_')
3662 add_line_format(view, LINE_DEFAULT, " %-25s %-20s %s",
3663 key, buf, req_info[i].help);
3667 add_line_text(view, "", LINE_DEFAULT);
3668 add_line_text(view, "External commands:", LINE_DEFAULT);
3671 for (i = 0; i < run_requests; i++) {
3672 struct run_request *req = get_run_request(REQ_NONE + i + 1);
3679 key = get_key_name(req->key);
3681 key = "(no key defined)";
3683 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
3684 if (!string_format_from(buf, &bufpos, "%s%s",
3685 argc ? " " : "", req->argv[argc]))
3688 add_line_format(view, LINE_DEFAULT, " %-10s %-14s `%s`",
3689 keymap_table[req->keymap].name, key, buf);
3695 static struct view_ops help_ops = {
3711 struct tree_stack_entry {
3712 struct tree_stack_entry *prev; /* Entry below this in the stack */
3713 unsigned long lineno; /* Line number to restore */
3714 char *name; /* Position of name in opt_path */
3717 /* The top of the path stack. */
3718 static struct tree_stack_entry *tree_stack = NULL;
3719 unsigned long tree_lineno = 0;
3722 pop_tree_stack_entry(void)
3724 struct tree_stack_entry *entry = tree_stack;
3726 tree_lineno = entry->lineno;
3728 tree_stack = entry->prev;
3733 push_tree_stack_entry(const char *name, unsigned long lineno)
3735 struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
3736 size_t pathlen = strlen(opt_path);
3741 entry->prev = tree_stack;
3742 entry->name = opt_path + pathlen;
3745 if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
3746 pop_tree_stack_entry();
3750 /* Move the current line to the first tree entry. */
3752 entry->lineno = lineno;
3755 /* Parse output from git-ls-tree(1):
3757 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
3760 #define SIZEOF_TREE_ATTR \
3761 STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
3763 #define SIZEOF_TREE_MODE \
3764 STRING_SIZE("100644 ")
3766 #define TREE_ID_OFFSET \
3767 STRING_SIZE("100644 blob ")
3770 char id[SIZEOF_REV];
3772 struct tm time; /* Date from the author ident. */
3773 char author[75]; /* Author of the commit. */
3778 tree_path(struct line *line)
3780 return ((struct tree_entry *) line->data)->name;
3785 tree_compare_entry(struct line *line1, struct line *line2)
3787 if (line1->type != line2->type)
3788 return line1->type == LINE_TREE_DIR ? -1 : 1;
3789 return strcmp(tree_path(line1), tree_path(line2));
3792 static struct line *
3793 tree_entry(struct view *view, enum line_type type, const char *path,
3794 const char *mode, const char *id)
3796 struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
3797 struct line *line = entry ? add_line_data(view, entry, type) : NULL;
3799 if (!entry || !line) {
3804 strncpy(entry->name, path, strlen(path));
3806 entry->mode = strtoul(mode, NULL, 8);
3808 string_copy_rev(entry->id, id);
3814 tree_read_date(struct view *view, char *text, bool *read_date)
3816 static char author_name[SIZEOF_STR];
3817 static struct tm author_time;
3819 if (!text && *read_date) {
3824 char *path = *opt_path ? opt_path : ".";
3825 /* Find next entry to process */
3826 const char *log_file[] = {
3827 "git", "log", "--no-color", "--pretty=raw",
3828 "--cc", "--raw", view->id, "--", path, NULL
3833 tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
3834 report("Tree is empty");
3838 if (!run_io_rd(&io, log_file, FORMAT_NONE)) {
3839 report("Failed to load tree data");
3843 done_io(view->pipe);
3848 } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
3849 parse_author_line(text + STRING_SIZE("author "),
3850 author_name, sizeof(author_name), &author_time);
3852 } else if (*text == ':') {
3854 size_t annotated = 1;
3857 pos = strchr(text, '\t');
3861 if (*opt_prefix && !strncmp(text, opt_prefix, strlen(opt_prefix)))
3862 text += strlen(opt_prefix);
3863 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
3864 text += strlen(opt_path);
3865 pos = strchr(text, '/');
3869 for (i = 1; i < view->lines; i++) {
3870 struct line *line = &view->line[i];
3871 struct tree_entry *entry = line->data;
3873 annotated += !!*entry->author;
3874 if (*entry->author || strcmp(entry->name, text))
3877 string_copy(entry->author, author_name);
3878 memcpy(&entry->time, &author_time, sizeof(entry->time));
3883 if (annotated == view->lines)
3884 kill_io(view->pipe);
3890 tree_read(struct view *view, char *text)
3892 static bool read_date = FALSE;
3893 struct tree_entry *data;
3894 struct line *entry, *line;
3895 enum line_type type;
3896 size_t textlen = text ? strlen(text) : 0;
3897 char *path = text + SIZEOF_TREE_ATTR;
3899 if (read_date || !text)
3900 return tree_read_date(view, text, &read_date);
3902 if (textlen <= SIZEOF_TREE_ATTR)
3904 if (view->lines == 0 &&
3905 !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
3908 /* Strip the path part ... */
3910 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
3911 size_t striplen = strlen(opt_path);
3913 if (pathlen > striplen)
3914 memmove(path, path + striplen,
3915 pathlen - striplen + 1);
3917 /* Insert "link" to parent directory. */
3918 if (view->lines == 1 &&
3919 !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
3923 type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
3924 entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
3929 /* Skip "Directory ..." and ".." line. */
3930 for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
3931 if (tree_compare_entry(line, entry) <= 0)
3934 memmove(line + 1, line, (entry - line) * sizeof(*entry));
3938 for (; line <= entry; line++)
3939 line->dirty = line->cleareol = 1;
3943 if (tree_lineno > view->lineno) {
3944 view->lineno = tree_lineno;
3952 tree_draw(struct view *view, struct line *line, unsigned int lineno)
3954 struct tree_entry *entry = line->data;
3956 if (line->type == LINE_TREE_HEAD) {
3957 if (draw_text(view, line->type, "Directory path /", TRUE))
3960 if (draw_mode(view, entry->mode))
3963 if (opt_author && draw_author(view, entry->author))
3966 if (opt_date && draw_date(view, *entry->author ? &entry->time : NULL))
3969 if (draw_text(view, line->type, entry->name, TRUE))
3977 char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
3978 int fd = mkstemp(file);
3981 report("Failed to create temporary file");
3982 else if (!run_io_append(blob_ops.argv, FORMAT_ALL, fd))
3983 report("Failed to save blob data to file");
3985 open_editor(FALSE, file);
3991 tree_request(struct view *view, enum request request, struct line *line)
3993 enum open_flags flags;
3996 case REQ_VIEW_BLAME:
3997 if (line->type != LINE_TREE_FILE) {
3998 report("Blame only supported for files");
4002 string_copy(opt_ref, view->vid);
4006 if (line->type != LINE_TREE_FILE) {
4007 report("Edit only supported for files");
4008 } else if (!is_head_commit(view->vid)) {
4011 open_editor(TRUE, opt_file);
4017 /* quit view if at top of tree */
4018 return REQ_VIEW_CLOSE;
4021 line = &view->line[1];
4031 /* Cleanup the stack if the tree view is at a different tree. */
4032 while (!*opt_path && tree_stack)
4033 pop_tree_stack_entry();
4035 switch (line->type) {
4037 /* Depending on whether it is a subdirectory or parent link
4038 * mangle the path buffer. */
4039 if (line == &view->line[1] && *opt_path) {
4040 pop_tree_stack_entry();
4043 const char *basename = tree_path(line);
4045 push_tree_stack_entry(basename, view->lineno);
4048 /* Trees and subtrees share the same ID, so they are not not
4049 * unique like blobs. */
4050 flags = OPEN_RELOAD;
4051 request = REQ_VIEW_TREE;
4054 case LINE_TREE_FILE:
4055 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4056 request = REQ_VIEW_BLOB;
4063 open_view(view, request, flags);
4064 if (request == REQ_VIEW_TREE)
4065 view->lineno = tree_lineno;
4071 tree_select(struct view *view, struct line *line)
4073 struct tree_entry *entry = line->data;
4075 if (line->type == LINE_TREE_FILE) {
4076 string_copy_rev(ref_blob, entry->id);
4077 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4079 } else if (line->type != LINE_TREE_DIR) {
4083 string_copy_rev(view->ref, entry->id);
4086 static const char *tree_argv[SIZEOF_ARG] = {
4087 "git", "ls-tree", "%(commit)", "%(directory)", NULL
4090 static struct view_ops tree_ops = {
4102 blob_read(struct view *view, char *line)
4106 return add_line_text(view, line, LINE_DEFAULT) != NULL;
4110 blob_request(struct view *view, enum request request, struct line *line)
4117 return pager_request(view, request, line);
4121 static const char *blob_argv[SIZEOF_ARG] = {
4122 "git", "cat-file", "blob", "%(blob)", NULL
4125 static struct view_ops blob_ops = {
4139 * Loading the blame view is a two phase job:
4141 * 1. File content is read either using opt_file from the
4142 * filesystem or using git-cat-file.
4143 * 2. Then blame information is incrementally added by
4144 * reading output from git-blame.
4147 static const char *blame_head_argv[] = {
4148 "git", "blame", "--incremental", "--", "%(file)", NULL
4151 static const char *blame_ref_argv[] = {
4152 "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4155 static const char *blame_cat_file_argv[] = {
4156 "git", "cat-file", "blob", "%(ref):%(file)", NULL
4159 struct blame_commit {
4160 char id[SIZEOF_REV]; /* SHA1 ID. */
4161 char title[128]; /* First line of the commit message. */
4162 char author[75]; /* Author of the commit. */
4163 struct tm time; /* Date from the author ident. */
4164 char filename[128]; /* Name of file. */
4165 bool has_previous; /* Was a "previous" line detected. */
4169 struct blame_commit *commit;
4174 blame_open(struct view *view)
4176 if (*opt_ref || !io_open(&view->io, opt_file)) {
4177 if (!run_io_rd(&view->io, blame_cat_file_argv, FORMAT_ALL))
4181 setup_update(view, opt_file);
4182 string_format(view->ref, "%s ...", opt_file);
4187 static struct blame_commit *
4188 get_blame_commit(struct view *view, const char *id)
4192 for (i = 0; i < view->lines; i++) {
4193 struct blame *blame = view->line[i].data;
4198 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4199 return blame->commit;
4203 struct blame_commit *commit = calloc(1, sizeof(*commit));
4206 string_ncopy(commit->id, id, SIZEOF_REV);
4212 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4214 const char *pos = *posref;
4217 pos = strchr(pos + 1, ' ');
4218 if (!pos || !isdigit(pos[1]))
4220 *number = atoi(pos + 1);
4221 if (*number < min || *number > max)
4228 static struct blame_commit *
4229 parse_blame_commit(struct view *view, const char *text, int *blamed)
4231 struct blame_commit *commit;
4232 struct blame *blame;
4233 const char *pos = text + SIZEOF_REV - 1;
4237 if (strlen(text) <= SIZEOF_REV || *pos != ' ')
4240 if (!parse_number(&pos, &lineno, 1, view->lines) ||
4241 !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4244 commit = get_blame_commit(view, text);
4250 struct line *line = &view->line[lineno + group - 1];
4253 blame->commit = commit;
4261 blame_read_file(struct view *view, const char *line, bool *read_file)
4264 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
4267 if (view->lines == 0 && !view->parent)
4268 die("No blame exist for %s", view->vid);
4270 if (view->lines == 0 || !run_io_rd(&io, argv, FORMAT_ALL)) {
4271 report("Failed to load blame data");
4275 done_io(view->pipe);
4281 size_t linelen = string_expand_length(line, opt_tab_size);
4282 struct blame *blame = malloc(sizeof(*blame) + linelen);
4287 blame->commit = NULL;
4288 string_expand(blame->text, linelen + 1, line, opt_tab_size);
4289 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
4294 match_blame_header(const char *name, char **line)
4296 size_t namelen = strlen(name);
4297 bool matched = !strncmp(name, *line, namelen);
4306 blame_read(struct view *view, char *line)
4308 static struct blame_commit *commit = NULL;
4309 static int blamed = 0;
4310 static time_t author_time;
4311 static bool read_file = TRUE;
4314 return blame_read_file(view, line, &read_file);
4321 string_format(view->ref, "%s", view->vid);
4322 if (view_is_displayed(view)) {
4323 update_view_title(view);
4324 redraw_view_from(view, 0);
4330 commit = parse_blame_commit(view, line, &blamed);
4331 string_format(view->ref, "%s %2d%%", view->vid,
4332 view->lines ? blamed * 100 / view->lines : 0);
4334 } else if (match_blame_header("author ", &line)) {
4335 string_ncopy(commit->author, line, strlen(line));
4337 } else if (match_blame_header("author-time ", &line)) {
4338 author_time = (time_t) atol(line);
4340 } else if (match_blame_header("author-tz ", &line)) {
4341 parse_timezone(&author_time, line);
4342 gmtime_r(&author_time, &commit->time);
4344 } else if (match_blame_header("summary ", &line)) {
4345 string_ncopy(commit->title, line, strlen(line));
4347 } else if (match_blame_header("previous ", &line)) {
4348 commit->has_previous = TRUE;
4350 } else if (match_blame_header("filename ", &line)) {
4351 string_ncopy(commit->filename, line, strlen(line));
4359 blame_draw(struct view *view, struct line *line, unsigned int lineno)
4361 struct blame *blame = line->data;
4362 struct tm *time = NULL;
4363 const char *id = NULL, *author = NULL;
4365 if (blame->commit && *blame->commit->filename) {
4366 id = blame->commit->id;
4367 author = blame->commit->author;
4368 time = &blame->commit->time;
4371 if (opt_date && draw_date(view, time))
4374 if (opt_author && draw_author(view, author))
4377 if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
4380 if (draw_lineno(view, lineno))
4383 draw_text(view, LINE_DEFAULT, blame->text, TRUE);
4388 check_blame_commit(struct blame *blame)
4391 report("Commit data not loaded yet");
4392 else if (!strcmp(blame->commit->id, NULL_ID))
4393 report("No commit exist for the selected line");
4400 blame_request(struct view *view, enum request request, struct line *line)
4402 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4403 struct blame *blame = line->data;
4406 case REQ_VIEW_BLAME:
4407 if (check_blame_commit(blame)) {
4408 string_copy(opt_ref, blame->commit->id);
4409 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4414 if (check_blame_commit(blame) &&
4415 select_commit_parent(blame->commit->id, opt_ref))
4416 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4420 if (!blame->commit) {
4421 report("No commit loaded yet");
4425 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
4426 !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
4429 if (!strcmp(blame->commit->id, NULL_ID)) {
4430 struct view *diff = VIEW(REQ_VIEW_DIFF);
4431 const char *diff_index_argv[] = {
4432 "git", "diff-index", "--root", "--patch-with-stat",
4433 "-C", "-M", "HEAD", "--", view->vid, NULL
4436 if (!blame->commit->has_previous) {
4437 diff_index_argv[1] = "diff";
4438 diff_index_argv[2] = "--no-color";
4439 diff_index_argv[6] = "--";
4440 diff_index_argv[7] = "/dev/null";
4443 if (!prepare_update(diff, diff_index_argv, NULL, FORMAT_DASH)) {
4444 report("Failed to allocate diff command");
4447 flags |= OPEN_PREPARED;
4450 open_view(view, REQ_VIEW_DIFF, flags);
4451 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
4452 string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
4463 blame_grep(struct view *view, struct line *line)
4465 struct blame *blame = line->data;
4466 struct blame_commit *commit = blame->commit;
4469 #define MATCH(text, on) \
4470 (on && *text && regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4473 char buf[DATE_COLS + 1];
4475 if (MATCH(commit->title, 1) ||
4476 MATCH(commit->author, opt_author) ||
4477 MATCH(commit->id, opt_date))
4480 if (strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time) &&
4485 return MATCH(blame->text, 1);
4491 blame_select(struct view *view, struct line *line)
4493 struct blame *blame = line->data;
4494 struct blame_commit *commit = blame->commit;
4499 if (!strcmp(commit->id, NULL_ID))
4500 string_ncopy(ref_commit, "HEAD", 4);
4502 string_copy_rev(ref_commit, commit->id);
4505 static struct view_ops blame_ops = {
4524 char rev[SIZEOF_REV];
4525 char name[SIZEOF_STR];
4529 char rev[SIZEOF_REV];
4530 char name[SIZEOF_STR];
4534 static char status_onbranch[SIZEOF_STR];
4535 static struct status stage_status;
4536 static enum line_type stage_line_type;
4537 static size_t stage_chunks;
4538 static int *stage_chunk;
4540 /* This should work even for the "On branch" line. */
4542 status_has_none(struct view *view, struct line *line)
4544 return line < view->line + view->lines && !line[1].data;
4547 /* Get fields from the diff line:
4548 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
4551 status_get_diff(struct status *file, const char *buf, size_t bufsize)
4553 const char *old_mode = buf + 1;
4554 const char *new_mode = buf + 8;
4555 const char *old_rev = buf + 15;
4556 const char *new_rev = buf + 56;
4557 const char *status = buf + 97;
4560 old_mode[-1] != ':' ||
4561 new_mode[-1] != ' ' ||
4562 old_rev[-1] != ' ' ||
4563 new_rev[-1] != ' ' ||
4567 file->status = *status;
4569 string_copy_rev(file->old.rev, old_rev);
4570 string_copy_rev(file->new.rev, new_rev);
4572 file->old.mode = strtoul(old_mode, NULL, 8);
4573 file->new.mode = strtoul(new_mode, NULL, 8);
4575 file->old.name[0] = file->new.name[0] = 0;
4581 status_run(struct view *view, const char *argv[], char status, enum line_type type)
4583 struct status *unmerged = NULL;
4587 if (!run_io(&io, argv, NULL, IO_RD))
4590 add_line_data(view, NULL, type);
4592 while ((buf = io_get(&io, 0, TRUE))) {
4593 struct status *file = unmerged;
4596 file = calloc(1, sizeof(*file));
4597 if (!file || !add_line_data(view, file, type))
4601 /* Parse diff info part. */
4603 file->status = status;
4605 string_copy(file->old.rev, NULL_ID);
4607 } else if (!file->status || file == unmerged) {
4608 if (!status_get_diff(file, buf, strlen(buf)))
4611 buf = io_get(&io, 0, TRUE);
4615 /* Collapse all modified entries that follow an
4616 * associated unmerged entry. */
4617 if (unmerged == file) {
4618 unmerged->status = 'U';
4620 } else if (file->status == 'U') {
4625 /* Grab the old name for rename/copy. */
4626 if (!*file->old.name &&
4627 (file->status == 'R' || file->status == 'C')) {
4628 string_ncopy(file->old.name, buf, strlen(buf));
4630 buf = io_get(&io, 0, TRUE);
4635 /* git-ls-files just delivers a NUL separated list of
4636 * file names similar to the second half of the
4637 * git-diff-* output. */
4638 string_ncopy(file->new.name, buf, strlen(buf));
4639 if (!*file->old.name)
4640 string_copy(file->old.name, file->new.name);
4644 if (io_error(&io)) {
4650 if (!view->line[view->lines - 1].data)
4651 add_line_data(view, NULL, LINE_STAT_NONE);
4657 /* Don't show unmerged entries in the staged section. */
4658 static const char *status_diff_index_argv[] = {
4659 "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
4660 "--cached", "-M", "HEAD", NULL
4663 static const char *status_diff_files_argv[] = {
4664 "git", "diff-files", "-z", NULL
4667 static const char *status_list_other_argv[] = {
4668 "git", "ls-files", "-z", "--others", "--exclude-standard", NULL
4671 static const char *status_list_no_head_argv[] = {
4672 "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
4675 static const char *update_index_argv[] = {
4676 "git", "update-index", "-q", "--unmerged", "--refresh", NULL
4679 /* Restore the previous line number to stay in the context or select a
4680 * line with something that can be updated. */
4682 status_restore(struct view *view)
4684 if (view->p_lineno >= view->lines)
4685 view->p_lineno = view->lines - 1;
4686 while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
4688 while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
4691 /* If the above fails, always skip the "On branch" line. */
4692 if (view->p_lineno < view->lines)
4693 view->lineno = view->p_lineno;
4697 if (view->lineno < view->offset)
4698 view->offset = view->lineno;
4699 else if (view->offset + view->height <= view->lineno)
4700 view->offset = view->lineno - view->height + 1;
4702 view->p_restore = FALSE;
4705 /* First parse staged info using git-diff-index(1), then parse unstaged
4706 * info using git-diff-files(1), and finally untracked files using
4707 * git-ls-files(1). */
4709 status_open(struct view *view)
4713 add_line_data(view, NULL, LINE_STAT_HEAD);
4714 if (is_initial_commit())
4715 string_copy(status_onbranch, "Initial commit");
4716 else if (!*opt_head)
4717 string_copy(status_onbranch, "Not currently on any branch");
4718 else if (!string_format(status_onbranch, "On branch %s", opt_head))
4721 run_io_bg(update_index_argv);
4723 if (is_initial_commit()) {
4724 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
4726 } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
4730 if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
4731 !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
4734 /* Restore the exact position or use the specialized restore
4736 if (!view->p_restore)
4737 status_restore(view);
4742 status_draw(struct view *view, struct line *line, unsigned int lineno)
4744 struct status *status = line->data;
4745 enum line_type type;
4749 switch (line->type) {
4750 case LINE_STAT_STAGED:
4751 type = LINE_STAT_SECTION;
4752 text = "Changes to be committed:";
4755 case LINE_STAT_UNSTAGED:
4756 type = LINE_STAT_SECTION;
4757 text = "Changed but not updated:";
4760 case LINE_STAT_UNTRACKED:
4761 type = LINE_STAT_SECTION;
4762 text = "Untracked files:";
4765 case LINE_STAT_NONE:
4766 type = LINE_DEFAULT;
4767 text = " (no files)";
4770 case LINE_STAT_HEAD:
4771 type = LINE_STAT_HEAD;
4772 text = status_onbranch;
4779 static char buf[] = { '?', ' ', ' ', ' ', 0 };
4781 buf[0] = status->status;
4782 if (draw_text(view, line->type, buf, TRUE))
4784 type = LINE_DEFAULT;
4785 text = status->new.name;
4788 draw_text(view, type, text, TRUE);
4793 status_enter(struct view *view, struct line *line)
4795 struct status *status = line->data;
4796 const char *oldpath = status ? status->old.name : NULL;
4797 /* Diffs for unmerged entries are empty when passing the new
4798 * path, so leave it empty. */
4799 const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
4801 enum open_flags split;
4802 struct view *stage = VIEW(REQ_VIEW_STAGE);
4804 if (line->type == LINE_STAT_NONE ||
4805 (!status && line[1].type == LINE_STAT_NONE)) {
4806 report("No file to diff");
4810 switch (line->type) {
4811 case LINE_STAT_STAGED:
4812 if (is_initial_commit()) {
4813 const char *no_head_diff_argv[] = {
4814 "git", "diff", "--no-color", "--patch-with-stat",
4815 "--", "/dev/null", newpath, NULL
4818 if (!prepare_update(stage, no_head_diff_argv, opt_cdup, FORMAT_DASH))
4821 const char *index_show_argv[] = {
4822 "git", "diff-index", "--root", "--patch-with-stat",
4823 "-C", "-M", "--cached", "HEAD", "--",
4824 oldpath, newpath, NULL
4827 if (!prepare_update(stage, index_show_argv, opt_cdup, FORMAT_DASH))
4832 info = "Staged changes to %s";
4834 info = "Staged changes";
4837 case LINE_STAT_UNSTAGED:
4839 const char *files_show_argv[] = {
4840 "git", "diff-files", "--root", "--patch-with-stat",
4841 "-C", "-M", "--", oldpath, newpath, NULL
4844 if (!prepare_update(stage, files_show_argv, opt_cdup, FORMAT_DASH))
4847 info = "Unstaged changes to %s";
4849 info = "Unstaged changes";
4852 case LINE_STAT_UNTRACKED:
4854 report("No file to show");
4858 if (!suffixcmp(status->new.name, -1, "/")) {
4859 report("Cannot display a directory");
4863 if (!prepare_update_file(stage, newpath))
4865 info = "Untracked file %s";
4868 case LINE_STAT_HEAD:
4872 die("line type %d not handled in switch", line->type);
4875 split = view_is_displayed(view) ? OPEN_SPLIT : 0;
4876 open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
4877 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
4879 stage_status = *status;
4881 memset(&stage_status, 0, sizeof(stage_status));
4884 stage_line_type = line->type;
4886 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
4893 status_exists(struct status *status, enum line_type type)
4895 struct view *view = VIEW(REQ_VIEW_STATUS);
4896 unsigned long lineno;
4898 for (lineno = 0; lineno < view->lines; lineno++) {
4899 struct line *line = &view->line[lineno];
4900 struct status *pos = line->data;
4902 if (line->type != type)
4904 if (!pos && (!status || !status->status) && line[1].data) {
4905 select_view_line(view, lineno);
4908 if (pos && !strcmp(status->new.name, pos->new.name)) {
4909 select_view_line(view, lineno);
4919 status_update_prepare(struct io *io, enum line_type type)
4921 const char *staged_argv[] = {
4922 "git", "update-index", "-z", "--index-info", NULL
4924 const char *others_argv[] = {
4925 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
4929 case LINE_STAT_STAGED:
4930 return run_io(io, staged_argv, opt_cdup, IO_WR);
4932 case LINE_STAT_UNSTAGED:
4933 return run_io(io, others_argv, opt_cdup, IO_WR);
4935 case LINE_STAT_UNTRACKED:
4936 return run_io(io, others_argv, NULL, IO_WR);
4939 die("line type %d not handled in switch", type);
4945 status_update_write(struct io *io, struct status *status, enum line_type type)
4947 char buf[SIZEOF_STR];
4951 case LINE_STAT_STAGED:
4952 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
4955 status->old.name, 0))
4959 case LINE_STAT_UNSTAGED:
4960 case LINE_STAT_UNTRACKED:
4961 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
4966 die("line type %d not handled in switch", type);
4969 return io_write(io, buf, bufsize);
4973 status_update_file(struct status *status, enum line_type type)
4978 if (!status_update_prepare(&io, type))
4981 result = status_update_write(&io, status, type);
4987 status_update_files(struct view *view, struct line *line)
4991 struct line *pos = view->line + view->lines;
4995 if (!status_update_prepare(&io, line->type))
4998 for (pos = line; pos < view->line + view->lines && pos->data; pos++)
5001 for (file = 0, done = 0; result && file < files; line++, file++) {
5002 int almost_done = file * 100 / files;
5004 if (almost_done > done) {
5006 string_format(view->ref, "updating file %u of %u (%d%% done)",
5008 update_view_title(view);
5010 result = status_update_write(&io, line->data, line->type);
5018 status_update(struct view *view)
5020 struct line *line = &view->line[view->lineno];
5022 assert(view->lines);
5025 /* This should work even for the "On branch" line. */
5026 if (line < view->line + view->lines && !line[1].data) {
5027 report("Nothing to update");
5031 if (!status_update_files(view, line + 1)) {
5032 report("Failed to update file status");
5036 } else if (!status_update_file(line->data, line->type)) {
5037 report("Failed to update file status");
5045 status_revert(struct status *status, enum line_type type, bool has_none)
5047 if (!status || type != LINE_STAT_UNSTAGED) {
5048 if (type == LINE_STAT_STAGED) {
5049 report("Cannot revert changes to staged files");
5050 } else if (type == LINE_STAT_UNTRACKED) {
5051 report("Cannot revert changes to untracked files");
5052 } else if (has_none) {
5053 report("Nothing to revert");
5055 report("Cannot revert changes to multiple files");
5060 char mode[10] = "100644";
5061 const char *reset_argv[] = {
5062 "git", "update-index", "--cacheinfo", mode,
5063 status->old.rev, status->old.name, NULL
5065 const char *checkout_argv[] = {
5066 "git", "checkout", "--", status->old.name, NULL
5069 if (!prompt_yesno("Are you sure you want to overwrite any changes?"))
5071 string_format(mode, "%o", status->old.mode);
5072 return (status->status != 'U' || run_io_fg(reset_argv, opt_cdup)) &&
5073 run_io_fg(checkout_argv, opt_cdup);
5078 status_request(struct view *view, enum request request, struct line *line)
5080 struct status *status = line->data;
5083 case REQ_STATUS_UPDATE:
5084 if (!status_update(view))
5088 case REQ_STATUS_REVERT:
5089 if (!status_revert(status, line->type, status_has_none(view, line)))
5093 case REQ_STATUS_MERGE:
5094 if (!status || status->status != 'U') {
5095 report("Merging only possible for files with unmerged status ('U').");
5098 open_mergetool(status->new.name);
5104 if (status->status == 'D') {
5105 report("File has been deleted.");
5109 open_editor(status->status != '?', status->new.name);
5112 case REQ_VIEW_BLAME:
5114 string_copy(opt_file, status->new.name);
5120 /* After returning the status view has been split to
5121 * show the stage view. No further reloading is
5123 status_enter(view, line);
5127 /* Simply reload the view. */
5134 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
5140 status_select(struct view *view, struct line *line)
5142 struct status *status = line->data;
5143 char file[SIZEOF_STR] = "all files";
5147 if (status && !string_format(file, "'%s'", status->new.name))
5150 if (!status && line[1].type == LINE_STAT_NONE)
5153 switch (line->type) {
5154 case LINE_STAT_STAGED:
5155 text = "Press %s to unstage %s for commit";
5158 case LINE_STAT_UNSTAGED:
5159 text = "Press %s to stage %s for commit";
5162 case LINE_STAT_UNTRACKED:
5163 text = "Press %s to stage %s for addition";
5166 case LINE_STAT_HEAD:
5167 case LINE_STAT_NONE:
5168 text = "Nothing to update";
5172 die("line type %d not handled in switch", line->type);
5175 if (status && status->status == 'U') {
5176 text = "Press %s to resolve conflict in %s";
5177 key = get_key(REQ_STATUS_MERGE);
5180 key = get_key(REQ_STATUS_UPDATE);
5183 string_format(view->ref, text, key, file);
5187 status_grep(struct view *view, struct line *line)
5189 struct status *status = line->data;
5190 enum { S_STATUS, S_NAME, S_END } state;
5197 for (state = S_STATUS; state < S_END; state++) {
5201 case S_NAME: text = status->new.name; break;
5203 buf[0] = status->status;
5211 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
5218 static struct view_ops status_ops = {
5231 stage_diff_write(struct io *io, struct line *line, struct line *end)
5233 while (line < end) {
5234 if (!io_write(io, line->data, strlen(line->data)) ||
5235 !io_write(io, "\n", 1))
5238 if (line->type == LINE_DIFF_CHUNK ||
5239 line->type == LINE_DIFF_HEADER)
5246 static struct line *
5247 stage_diff_find(struct view *view, struct line *line, enum line_type type)
5249 for (; view->line < line; line--)
5250 if (line->type == type)
5257 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
5259 const char *apply_argv[SIZEOF_ARG] = {
5260 "git", "apply", "--whitespace=nowarn", NULL
5262 struct line *diff_hdr;
5266 diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
5271 apply_argv[argc++] = "--cached";
5272 if (revert || stage_line_type == LINE_STAT_STAGED)
5273 apply_argv[argc++] = "-R";
5274 apply_argv[argc++] = "-";
5275 apply_argv[argc++] = NULL;
5276 if (!run_io(&io, apply_argv, opt_cdup, IO_WR))
5279 if (!stage_diff_write(&io, diff_hdr, chunk) ||
5280 !stage_diff_write(&io, chunk, view->line + view->lines))
5284 run_io_bg(update_index_argv);
5286 return chunk ? TRUE : FALSE;
5290 stage_update(struct view *view, struct line *line)
5292 struct line *chunk = NULL;
5294 if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
5295 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5298 if (!stage_apply_chunk(view, chunk, FALSE)) {
5299 report("Failed to apply chunk");
5303 } else if (!stage_status.status) {
5304 view = VIEW(REQ_VIEW_STATUS);
5306 for (line = view->line; line < view->line + view->lines; line++)
5307 if (line->type == stage_line_type)
5310 if (!status_update_files(view, line + 1)) {
5311 report("Failed to update files");
5315 } else if (!status_update_file(&stage_status, stage_line_type)) {
5316 report("Failed to update file");
5324 stage_revert(struct view *view, struct line *line)
5326 struct line *chunk = NULL;
5328 if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
5329 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5332 if (!prompt_yesno("Are you sure you want to revert changes?"))
5335 if (!stage_apply_chunk(view, chunk, TRUE)) {
5336 report("Failed to revert chunk");
5342 return status_revert(stage_status.status ? &stage_status : NULL,
5343 stage_line_type, FALSE);
5349 stage_next(struct view *view, struct line *line)
5353 if (!stage_chunks) {
5354 static size_t alloc = 0;
5357 for (line = view->line; line < view->line + view->lines; line++) {
5358 if (line->type != LINE_DIFF_CHUNK)
5361 tmp = realloc_items(stage_chunk, &alloc,
5362 stage_chunks, sizeof(*tmp));
5364 report("Allocation failure");
5369 stage_chunk[stage_chunks++] = line - view->line;
5373 for (i = 0; i < stage_chunks; i++) {
5374 if (stage_chunk[i] > view->lineno) {
5375 do_scroll_view(view, stage_chunk[i] - view->lineno);
5376 report("Chunk %d of %d", i + 1, stage_chunks);
5381 report("No next chunk found");
5385 stage_request(struct view *view, enum request request, struct line *line)
5388 case REQ_STATUS_UPDATE:
5389 if (!stage_update(view, line))
5393 case REQ_STATUS_REVERT:
5394 if (!stage_revert(view, line))
5398 case REQ_STAGE_NEXT:
5399 if (stage_line_type == LINE_STAT_UNTRACKED) {
5400 report("File is untracked; press %s to add",
5401 get_key(REQ_STATUS_UPDATE));
5404 stage_next(view, line);
5408 if (!stage_status.new.name[0])
5410 if (stage_status.status == 'D') {
5411 report("File has been deleted.");
5415 open_editor(stage_status.status != '?', stage_status.new.name);
5419 /* Reload everything ... */
5422 case REQ_VIEW_BLAME:
5423 if (stage_status.new.name[0]) {
5424 string_copy(opt_file, stage_status.new.name);
5430 return pager_request(view, request, line);
5436 VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
5437 open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
5439 /* Check whether the staged entry still exists, and close the
5440 * stage view if it doesn't. */
5441 if (!status_exists(&stage_status, stage_line_type)) {
5442 status_restore(VIEW(REQ_VIEW_STATUS));
5443 return REQ_VIEW_CLOSE;
5446 if (stage_line_type == LINE_STAT_UNTRACKED) {
5447 if (!suffixcmp(stage_status.new.name, -1, "/")) {
5448 report("Cannot display a directory");
5452 if (!prepare_update_file(view, stage_status.new.name)) {
5453 report("Failed to open file: %s", strerror(errno));
5457 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
5462 static struct view_ops stage_ops = {
5479 char id[SIZEOF_REV]; /* SHA1 ID. */
5480 char title[128]; /* First line of the commit message. */
5481 char author[75]; /* Author of the commit. */
5482 struct tm time; /* Date from the author ident. */
5483 struct ref **refs; /* Repository references. */
5484 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
5485 size_t graph_size; /* The width of the graph array. */
5486 bool has_parents; /* Rewritten --parents seen. */
5489 /* Size of rev graph with no "padding" columns */
5490 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
5493 struct rev_graph *prev, *next, *parents;
5494 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
5496 struct commit *commit;
5498 unsigned int boundary:1;
5501 /* Parents of the commit being visualized. */
5502 static struct rev_graph graph_parents[4];
5504 /* The current stack of revisions on the graph. */
5505 static struct rev_graph graph_stacks[4] = {
5506 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
5507 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
5508 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
5509 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
5513 graph_parent_is_merge(struct rev_graph *graph)
5515 return graph->parents->size > 1;
5519 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
5521 struct commit *commit = graph->commit;
5523 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
5524 commit->graph[commit->graph_size++] = symbol;
5528 clear_rev_graph(struct rev_graph *graph)
5530 graph->boundary = 0;
5531 graph->size = graph->pos = 0;
5532 graph->commit = NULL;
5533 memset(graph->parents, 0, sizeof(*graph->parents));
5537 done_rev_graph(struct rev_graph *graph)
5539 if (graph_parent_is_merge(graph) &&
5540 graph->pos < graph->size - 1 &&
5541 graph->next->size == graph->size + graph->parents->size - 1) {
5542 size_t i = graph->pos + graph->parents->size - 1;
5544 graph->commit->graph_size = i * 2;
5545 while (i < graph->next->size - 1) {
5546 append_to_rev_graph(graph, ' ');
5547 append_to_rev_graph(graph, '\\');
5552 clear_rev_graph(graph);
5556 push_rev_graph(struct rev_graph *graph, const char *parent)
5560 /* "Collapse" duplicate parents lines.
5562 * FIXME: This needs to also update update the drawn graph but
5563 * for now it just serves as a method for pruning graph lines. */
5564 for (i = 0; i < graph->size; i++)
5565 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
5568 if (graph->size < SIZEOF_REVITEMS) {
5569 string_copy_rev(graph->rev[graph->size++], parent);
5574 get_rev_graph_symbol(struct rev_graph *graph)
5578 if (graph->boundary)
5579 symbol = REVGRAPH_BOUND;
5580 else if (graph->parents->size == 0)
5581 symbol = REVGRAPH_INIT;
5582 else if (graph_parent_is_merge(graph))
5583 symbol = REVGRAPH_MERGE;
5584 else if (graph->pos >= graph->size)
5585 symbol = REVGRAPH_BRANCH;
5587 symbol = REVGRAPH_COMMIT;
5593 draw_rev_graph(struct rev_graph *graph)
5596 chtype separator, line;
5598 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
5599 static struct rev_filler fillers[] = {
5605 chtype symbol = get_rev_graph_symbol(graph);
5606 struct rev_filler *filler;
5609 if (opt_line_graphics)
5610 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
5612 filler = &fillers[DEFAULT];
5614 for (i = 0; i < graph->pos; i++) {
5615 append_to_rev_graph(graph, filler->line);
5616 if (graph_parent_is_merge(graph->prev) &&
5617 graph->prev->pos == i)
5618 filler = &fillers[RSHARP];
5620 append_to_rev_graph(graph, filler->separator);
5623 /* Place the symbol for this revision. */
5624 append_to_rev_graph(graph, symbol);
5626 if (graph->prev->size > graph->size)
5627 filler = &fillers[RDIAG];
5629 filler = &fillers[DEFAULT];
5633 for (; i < graph->size; i++) {
5634 append_to_rev_graph(graph, filler->separator);
5635 append_to_rev_graph(graph, filler->line);
5636 if (graph_parent_is_merge(graph->prev) &&
5637 i < graph->prev->pos + graph->parents->size)
5638 filler = &fillers[RSHARP];
5639 if (graph->prev->size > graph->size)
5640 filler = &fillers[LDIAG];
5643 if (graph->prev->size > graph->size) {
5644 append_to_rev_graph(graph, filler->separator);
5645 if (filler->line != ' ')
5646 append_to_rev_graph(graph, filler->line);
5650 /* Prepare the next rev graph */
5652 prepare_rev_graph(struct rev_graph *graph)
5656 /* First, traverse all lines of revisions up to the active one. */
5657 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
5658 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
5661 push_rev_graph(graph->next, graph->rev[graph->pos]);
5664 /* Interleave the new revision parent(s). */
5665 for (i = 0; !graph->boundary && i < graph->parents->size; i++)
5666 push_rev_graph(graph->next, graph->parents->rev[i]);
5668 /* Lastly, put any remaining revisions. */
5669 for (i = graph->pos + 1; i < graph->size; i++)
5670 push_rev_graph(graph->next, graph->rev[i]);
5674 update_rev_graph(struct view *view, struct rev_graph *graph)
5676 /* If this is the finalizing update ... */
5678 prepare_rev_graph(graph);
5680 /* Graph visualization needs a one rev look-ahead,
5681 * so the first update doesn't visualize anything. */
5682 if (!graph->prev->commit)
5685 if (view->lines > 2)
5686 view->line[view->lines - 3].dirty = 1;
5687 if (view->lines > 1)
5688 view->line[view->lines - 2].dirty = 1;
5689 draw_rev_graph(graph->prev);
5690 done_rev_graph(graph->prev->prev);
5698 static const char *main_argv[SIZEOF_ARG] = {
5699 "git", "log", "--no-color", "--pretty=raw", "--parents",
5700 "--topo-order", "%(head)", NULL
5704 main_draw(struct view *view, struct line *line, unsigned int lineno)
5706 struct commit *commit = line->data;
5708 if (!*commit->author)
5711 if (opt_date && draw_date(view, &commit->time))
5714 if (opt_author && draw_author(view, commit->author))
5717 if (opt_rev_graph && commit->graph_size &&
5718 draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
5721 if (opt_show_refs && commit->refs) {
5725 enum line_type type;
5727 if (commit->refs[i]->head)
5728 type = LINE_MAIN_HEAD;
5729 else if (commit->refs[i]->ltag)
5730 type = LINE_MAIN_LOCAL_TAG;
5731 else if (commit->refs[i]->tag)
5732 type = LINE_MAIN_TAG;
5733 else if (commit->refs[i]->tracked)
5734 type = LINE_MAIN_TRACKED;
5735 else if (commit->refs[i]->remote)
5736 type = LINE_MAIN_REMOTE;
5738 type = LINE_MAIN_REF;
5740 if (draw_text(view, type, "[", TRUE) ||
5741 draw_text(view, type, commit->refs[i]->name, TRUE) ||
5742 draw_text(view, type, "]", TRUE))
5745 if (draw_text(view, LINE_DEFAULT, " ", TRUE))
5747 } while (commit->refs[i++]->next);
5750 draw_text(view, LINE_DEFAULT, commit->title, TRUE);
5754 /* Reads git log --pretty=raw output and parses it into the commit struct. */
5756 main_read(struct view *view, char *line)
5758 static struct rev_graph *graph = graph_stacks;
5759 enum line_type type;
5760 struct commit *commit;
5765 if (!view->lines && !view->parent)
5766 die("No revisions match the given arguments.");
5767 if (view->lines > 0) {
5768 commit = view->line[view->lines - 1].data;
5769 view->line[view->lines - 1].dirty = 1;
5770 if (!*commit->author) {
5773 graph->commit = NULL;
5776 update_rev_graph(view, graph);
5778 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
5779 clear_rev_graph(&graph_stacks[i]);
5783 type = get_line_type(line);
5784 if (type == LINE_COMMIT) {
5785 commit = calloc(1, sizeof(struct commit));
5789 line += STRING_SIZE("commit ");
5791 graph->boundary = 1;
5795 string_copy_rev(commit->id, line);
5796 commit->refs = get_refs(commit->id);
5797 graph->commit = commit;
5798 add_line_data(view, commit, LINE_MAIN_COMMIT);
5800 while ((line = strchr(line, ' '))) {
5802 push_rev_graph(graph->parents, line);
5803 commit->has_parents = TRUE;
5810 commit = view->line[view->lines - 1].data;
5814 if (commit->has_parents)
5816 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
5820 parse_author_line(line + STRING_SIZE("author "),
5821 commit->author, sizeof(commit->author),
5823 update_rev_graph(view, graph);
5824 graph = graph->next;
5828 /* Fill in the commit title if it has not already been set. */
5829 if (commit->title[0])
5832 /* Require titles to start with a non-space character at the
5833 * offset used by git log. */
5834 if (strncmp(line, " ", 4))
5837 /* Well, if the title starts with a whitespace character,
5838 * try to be forgiving. Otherwise we end up with no title. */
5839 while (isspace(*line))
5843 /* FIXME: More graceful handling of titles; append "..." to
5844 * shortened titles, etc. */
5846 string_expand(commit->title, sizeof(commit->title), line, 1);
5847 view->line[view->lines - 1].dirty = 1;
5854 main_request(struct view *view, enum request request, struct line *line)
5856 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
5860 open_view(view, REQ_VIEW_DIFF, flags);
5864 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
5874 grep_refs(struct ref **refs, regex_t *regex)
5882 if (regexec(regex, refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
5884 } while (refs[i++]->next);
5890 main_grep(struct view *view, struct line *line)
5892 struct commit *commit = line->data;
5893 enum { S_TITLE, S_AUTHOR, S_DATE, S_REFS, S_END } state;
5894 char buf[DATE_COLS + 1];
5897 for (state = S_TITLE; state < S_END; state++) {
5901 case S_TITLE: text = commit->title; break;
5905 text = commit->author;
5910 if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
5917 if (grep_refs(commit->refs, view->regex) == TRUE)
5924 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
5932 main_select(struct view *view, struct line *line)
5934 struct commit *commit = line->data;
5936 string_copy_rev(view->ref, commit->id);
5937 string_copy_rev(ref_commit, view->ref);
5940 static struct view_ops main_ops = {
5953 * Unicode / UTF-8 handling
5955 * NOTE: Much of the following code for dealing with Unicode is derived from
5956 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
5957 * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
5961 unicode_width(unsigned long c)
5964 (c <= 0x115f /* Hangul Jamo */
5967 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
5969 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
5970 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
5971 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
5972 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
5973 || (c >= 0xffe0 && c <= 0xffe6)
5974 || (c >= 0x20000 && c <= 0x2fffd)
5975 || (c >= 0x30000 && c <= 0x3fffd)))
5979 return opt_tab_size;
5984 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
5985 * Illegal bytes are set one. */
5986 static const unsigned char utf8_bytes[256] = {
5987 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,
5988 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,
5989 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,
5990 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,
5991 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,
5992 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,
5993 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,
5994 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,
5997 /* Decode UTF-8 multi-byte representation into a Unicode character. */
5998 static inline unsigned long
5999 utf8_to_unicode(const char *string, size_t length)
6001 unsigned long unicode;
6005 unicode = string[0];
6008 unicode = (string[0] & 0x1f) << 6;
6009 unicode += (string[1] & 0x3f);
6012 unicode = (string[0] & 0x0f) << 12;
6013 unicode += ((string[1] & 0x3f) << 6);
6014 unicode += (string[2] & 0x3f);
6017 unicode = (string[0] & 0x0f) << 18;
6018 unicode += ((string[1] & 0x3f) << 12);
6019 unicode += ((string[2] & 0x3f) << 6);
6020 unicode += (string[3] & 0x3f);
6023 unicode = (string[0] & 0x0f) << 24;
6024 unicode += ((string[1] & 0x3f) << 18);
6025 unicode += ((string[2] & 0x3f) << 12);
6026 unicode += ((string[3] & 0x3f) << 6);
6027 unicode += (string[4] & 0x3f);
6030 unicode = (string[0] & 0x01) << 30;
6031 unicode += ((string[1] & 0x3f) << 24);
6032 unicode += ((string[2] & 0x3f) << 18);
6033 unicode += ((string[3] & 0x3f) << 12);
6034 unicode += ((string[4] & 0x3f) << 6);
6035 unicode += (string[5] & 0x3f);
6038 die("Invalid Unicode length");
6041 /* Invalid characters could return the special 0xfffd value but NUL
6042 * should be just as good. */
6043 return unicode > 0xffff ? 0 : unicode;
6046 /* Calculates how much of string can be shown within the given maximum width
6047 * and sets trimmed parameter to non-zero value if all of string could not be
6048 * shown. If the reserve flag is TRUE, it will reserve at least one
6049 * trailing character, which can be useful when drawing a delimiter.
6051 * Returns the number of bytes to output from string to satisfy max_width. */
6053 utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve)
6055 const char *string = *start;
6056 const char *end = strchr(string, '\0');
6057 unsigned char last_bytes = 0;
6058 size_t last_ucwidth = 0;
6063 while (string < end) {
6064 int c = *(unsigned char *) string;
6065 unsigned char bytes = utf8_bytes[c];
6067 unsigned long unicode;
6069 if (string + bytes > end)
6072 /* Change representation to figure out whether
6073 * it is a single- or double-width character. */
6075 unicode = utf8_to_unicode(string, bytes);
6076 /* FIXME: Graceful handling of invalid Unicode character. */
6080 ucwidth = unicode_width(unicode);
6082 skip -= ucwidth <= skip ? ucwidth : skip;
6086 if (*width > max_width) {
6089 if (reserve && *width == max_width) {
6090 string -= last_bytes;
6091 *width -= last_ucwidth;
6097 last_bytes = ucwidth ? bytes : 0;
6098 last_ucwidth = ucwidth;
6101 return string - *start;
6109 /* Whether or not the curses interface has been initialized. */
6110 static bool cursed = FALSE;
6112 /* Terminal hacks and workarounds. */
6113 static bool use_scroll_redrawwin;
6114 static bool use_scroll_status_wclear;
6116 /* The status window is used for polling keystrokes. */
6117 static WINDOW *status_win;
6119 /* Reading from the prompt? */
6120 static bool input_mode = FALSE;
6122 static bool status_empty = FALSE;
6124 /* Update status and title window. */
6126 report(const char *msg, ...)
6128 struct view *view = display[current_view];
6134 char buf[SIZEOF_STR];
6137 va_start(args, msg);
6138 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6139 buf[sizeof(buf) - 1] = 0;
6140 buf[sizeof(buf) - 2] = '.';
6141 buf[sizeof(buf) - 3] = '.';
6142 buf[sizeof(buf) - 4] = '.';
6148 if (!status_empty || *msg) {
6151 va_start(args, msg);
6153 wmove(status_win, 0, 0);
6154 if (view->has_scrolled && use_scroll_status_wclear)
6157 vwprintw(status_win, msg, args);
6158 status_empty = FALSE;
6160 status_empty = TRUE;
6162 wclrtoeol(status_win);
6163 wnoutrefresh(status_win);
6168 update_view_title(view);
6171 /* Controls when nodelay should be in effect when polling user input. */
6173 set_nonblocking_input(bool loading)
6175 static unsigned int loading_views;
6177 if ((loading == FALSE && loading_views-- == 1) ||
6178 (loading == TRUE && loading_views++ == 0))
6179 nodelay(status_win, loading);
6188 /* Initialize the curses library */
6189 if (isatty(STDIN_FILENO)) {
6190 cursed = !!initscr();
6193 /* Leave stdin and stdout alone when acting as a pager. */
6194 opt_tty = fopen("/dev/tty", "r+");
6196 die("Failed to open /dev/tty");
6197 cursed = !!newterm(NULL, opt_tty, opt_tty);
6201 die("Failed to initialize curses");
6203 nonl(); /* Disable conversion and detect newlines from input. */
6204 cbreak(); /* Take input chars one at a time, no wait for \n */
6205 noecho(); /* Don't echo input */
6206 leaveok(stdscr, FALSE);
6211 getmaxyx(stdscr, y, x);
6212 status_win = newwin(1, 0, y - 1, 0);
6214 die("Failed to create status window");
6216 /* Enable keyboard mapping */
6217 keypad(status_win, TRUE);
6218 wbkgdset(status_win, get_line_attr(LINE_STATUS));
6220 TABSIZE = opt_tab_size;
6221 if (opt_line_graphics) {
6222 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
6225 term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
6226 if (term && !strcmp(term, "gnome-terminal")) {
6227 /* In the gnome-terminal-emulator, the message from
6228 * scrolling up one line when impossible followed by
6229 * scrolling down one line causes corruption of the
6230 * status line. This is fixed by calling wclear. */
6231 use_scroll_status_wclear = TRUE;
6232 use_scroll_redrawwin = FALSE;
6234 } else if (term && !strcmp(term, "xrvt-xpm")) {
6235 /* No problems with full optimizations in xrvt-(unicode)
6237 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
6240 /* When scrolling in (u)xterm the last line in the
6241 * scrolling direction will update slowly. */
6242 use_scroll_redrawwin = TRUE;
6243 use_scroll_status_wclear = FALSE;
6248 get_input(int prompt_position)
6251 int i, key, cursor_y, cursor_x;
6253 if (prompt_position)
6257 foreach_view (view, i) {
6259 if (view_is_displayed(view) && view->has_scrolled &&
6260 use_scroll_redrawwin)
6261 redrawwin(view->win);
6262 view->has_scrolled = FALSE;
6265 /* Update the cursor position. */
6266 if (prompt_position) {
6267 getbegyx(status_win, cursor_y, cursor_x);
6268 cursor_x = prompt_position;
6270 view = display[current_view];
6271 getbegyx(view->win, cursor_y, cursor_x);
6272 cursor_x = view->width - 1;
6273 cursor_y += view->lineno - view->offset;
6275 setsyx(cursor_y, cursor_x);
6277 /* Refresh, accept single keystroke of input */
6279 key = wgetch(status_win);
6281 /* wgetch() with nodelay() enabled returns ERR when
6282 * there's no input. */
6285 } else if (key == KEY_RESIZE) {
6288 getmaxyx(stdscr, height, width);
6290 wresize(status_win, 1, width);
6291 mvwin(status_win, height - 1, 0);
6292 wnoutrefresh(status_win);
6294 redraw_display(TRUE);
6304 prompt_input(const char *prompt, input_handler handler, void *data)
6306 enum input_status status = INPUT_OK;
6307 static char buf[SIZEOF_STR];
6312 while (status == INPUT_OK || status == INPUT_SKIP) {
6315 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
6316 wclrtoeol(status_win);
6318 key = get_input(pos + 1);
6323 status = pos ? INPUT_STOP : INPUT_CANCEL;
6330 status = INPUT_CANCEL;
6334 status = INPUT_CANCEL;
6338 if (pos >= sizeof(buf)) {
6339 report("Input string too long");
6343 status = handler(data, buf, key);
6344 if (status == INPUT_OK)
6345 buf[pos++] = (char) key;
6349 /* Clear the status window */
6350 status_empty = FALSE;
6353 if (status == INPUT_CANCEL)
6361 static enum input_status
6362 prompt_yesno_handler(void *data, char *buf, int c)
6364 if (c == 'y' || c == 'Y')
6366 if (c == 'n' || c == 'N')
6367 return INPUT_CANCEL;
6372 prompt_yesno(const char *prompt)
6374 char prompt2[SIZEOF_STR];
6376 if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
6379 return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
6382 static enum input_status
6383 read_prompt_handler(void *data, char *buf, int c)
6385 return isprint(c) ? INPUT_OK : INPUT_SKIP;
6389 read_prompt(const char *prompt)
6391 return prompt_input(prompt, read_prompt_handler, NULL);
6395 * Repository properties
6398 static struct ref *refs = NULL;
6399 static size_t refs_alloc = 0;
6400 static size_t refs_size = 0;
6402 /* Id <-> ref store */
6403 static struct ref ***id_refs = NULL;
6404 static size_t id_refs_alloc = 0;
6405 static size_t id_refs_size = 0;
6408 compare_refs(const void *ref1_, const void *ref2_)
6410 const struct ref *ref1 = *(const struct ref **)ref1_;
6411 const struct ref *ref2 = *(const struct ref **)ref2_;
6413 if (ref1->tag != ref2->tag)
6414 return ref2->tag - ref1->tag;
6415 if (ref1->ltag != ref2->ltag)
6416 return ref2->ltag - ref2->ltag;
6417 if (ref1->head != ref2->head)
6418 return ref2->head - ref1->head;
6419 if (ref1->tracked != ref2->tracked)
6420 return ref2->tracked - ref1->tracked;
6421 if (ref1->remote != ref2->remote)
6422 return ref2->remote - ref1->remote;
6423 return strcmp(ref1->name, ref2->name);
6426 static struct ref **
6427 get_refs(const char *id)
6429 struct ref ***tmp_id_refs;
6430 struct ref **ref_list = NULL;
6431 size_t ref_list_alloc = 0;
6432 size_t ref_list_size = 0;
6435 for (i = 0; i < id_refs_size; i++)
6436 if (!strcmp(id, id_refs[i][0]->id))
6439 tmp_id_refs = realloc_items(id_refs, &id_refs_alloc, id_refs_size + 1,
6444 id_refs = tmp_id_refs;
6446 for (i = 0; i < refs_size; i++) {
6449 if (strcmp(id, refs[i].id))
6452 tmp = realloc_items(ref_list, &ref_list_alloc,
6453 ref_list_size + 1, sizeof(*ref_list));
6461 ref_list[ref_list_size] = &refs[i];
6462 /* XXX: The properties of the commit chains ensures that we can
6463 * safely modify the shared ref. The repo references will
6464 * always be similar for the same id. */
6465 ref_list[ref_list_size]->next = 1;
6471 qsort(ref_list, ref_list_size, sizeof(*ref_list), compare_refs);
6472 ref_list[ref_list_size - 1]->next = 0;
6473 id_refs[id_refs_size++] = ref_list;
6480 read_ref(char *id, size_t idlen, char *name, size_t namelen)
6485 bool remote = FALSE;
6486 bool tracked = FALSE;
6487 bool check_replace = FALSE;
6490 if (!prefixcmp(name, "refs/tags/")) {
6491 if (!suffixcmp(name, namelen, "^{}")) {
6494 if (refs_size > 0 && refs[refs_size - 1].ltag == TRUE)
6495 check_replace = TRUE;
6501 namelen -= STRING_SIZE("refs/tags/");
6502 name += STRING_SIZE("refs/tags/");
6504 } else if (!prefixcmp(name, "refs/remotes/")) {
6506 namelen -= STRING_SIZE("refs/remotes/");
6507 name += STRING_SIZE("refs/remotes/");
6508 tracked = !strcmp(opt_remote, name);
6510 } else if (!prefixcmp(name, "refs/heads/")) {
6511 namelen -= STRING_SIZE("refs/heads/");
6512 name += STRING_SIZE("refs/heads/");
6513 head = !strncmp(opt_head, name, namelen);
6515 } else if (!strcmp(name, "HEAD")) {
6516 string_ncopy(opt_head_rev, id, idlen);
6520 if (check_replace && !strcmp(name, refs[refs_size - 1].name)) {
6521 /* it's an annotated tag, replace the previous SHA1 with the
6522 * resolved commit id; relies on the fact git-ls-remote lists
6523 * the commit id of an annotated tag right before the commit id
6525 refs[refs_size - 1].ltag = ltag;
6526 string_copy_rev(refs[refs_size - 1].id, id);
6530 refs = realloc_items(refs, &refs_alloc, refs_size + 1, sizeof(*refs));
6534 ref = &refs[refs_size++];
6535 ref->name = malloc(namelen + 1);
6539 strncpy(ref->name, name, namelen);
6540 ref->name[namelen] = 0;
6544 ref->remote = remote;
6545 ref->tracked = tracked;
6546 string_copy_rev(ref->id, id);
6554 static const char *ls_remote_argv[SIZEOF_ARG] = {
6555 "git", "ls-remote", ".", NULL
6557 static bool init = FALSE;
6560 argv_from_env(ls_remote_argv, "TIG_LS_REMOTE");
6567 while (refs_size > 0)
6568 free(refs[--refs_size].name);
6569 while (id_refs_size > 0)
6570 free(id_refs[--id_refs_size]);
6572 return run_io_load(ls_remote_argv, "\t", read_ref);
6576 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
6578 const char *argv[SIZEOF_ARG] = { name, "=" };
6579 int argc = 1 + (cmd == option_set_command);
6582 if (!argv_from_string(argv, &argc, value))
6583 config_msg = "Too many option arguments";
6585 error = cmd(argc, argv);
6588 warn("Option 'tig.%s': %s", name, config_msg);
6592 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
6594 if (!strcmp(name, "i18n.commitencoding"))
6595 string_ncopy(opt_encoding, value, valuelen);
6597 if (!strcmp(name, "core.editor"))
6598 string_ncopy(opt_editor, value, valuelen);
6600 if (!prefixcmp(name, "tig.color."))
6601 set_repo_config_option(name + 10, value, option_color_command);
6603 else if (!prefixcmp(name, "tig.bind."))
6604 set_repo_config_option(name + 9, value, option_bind_command);
6606 else if (!prefixcmp(name, "tig."))
6607 set_repo_config_option(name + 4, value, option_set_command);
6609 /* branch.<head>.remote */
6611 !strncmp(name, "branch.", 7) &&
6612 !strncmp(name + 7, opt_head, strlen(opt_head)) &&
6613 !strcmp(name + 7 + strlen(opt_head), ".remote"))
6614 string_ncopy(opt_remote, value, valuelen);
6616 if (*opt_head && *opt_remote &&
6617 !strncmp(name, "branch.", 7) &&
6618 !strncmp(name + 7, opt_head, strlen(opt_head)) &&
6619 !strcmp(name + 7 + strlen(opt_head), ".merge")) {
6620 size_t from = strlen(opt_remote);
6622 if (!prefixcmp(value, "refs/heads/")) {
6623 value += STRING_SIZE("refs/heads/");
6624 valuelen -= STRING_SIZE("refs/heads/");
6627 if (!string_format_from(opt_remote, &from, "/%s", value))
6635 load_git_config(void)
6637 const char *config_list_argv[] = { "git", GIT_CONFIG, "--list", NULL };
6639 return run_io_load(config_list_argv, "=", read_repo_config_option);
6643 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
6645 if (!opt_git_dir[0]) {
6646 string_ncopy(opt_git_dir, name, namelen);
6648 } else if (opt_is_inside_work_tree == -1) {
6649 /* This can be 3 different values depending on the
6650 * version of git being used. If git-rev-parse does not
6651 * understand --is-inside-work-tree it will simply echo
6652 * the option else either "true" or "false" is printed.
6653 * Default to true for the unknown case. */
6654 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
6656 } else if (*name == '.') {
6657 string_ncopy(opt_cdup, name, namelen);
6660 string_ncopy(opt_prefix, name, namelen);
6667 load_repo_info(void)
6669 const char *head_argv[] = {
6670 "git", "symbolic-ref", "HEAD", NULL
6672 const char *rev_parse_argv[] = {
6673 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
6674 "--show-cdup", "--show-prefix", NULL
6677 if (run_io_buf(head_argv, opt_head, sizeof(opt_head))) {
6678 chomp_string(opt_head);
6679 if (!prefixcmp(opt_head, "refs/heads/")) {
6680 char *offset = opt_head + STRING_SIZE("refs/heads/");
6682 memmove(opt_head, offset, strlen(offset) + 1);
6686 return run_io_load(rev_parse_argv, "=", read_repo_info);
6694 static const char usage[] =
6695 "tig " TIG_VERSION " (" __DATE__ ")\n"
6697 "Usage: tig [options] [revs] [--] [paths]\n"
6698 " or: tig show [options] [revs] [--] [paths]\n"
6699 " or: tig blame [rev] path\n"
6701 " or: tig < [git command output]\n"
6704 " -v, --version Show version and exit\n"
6705 " -h, --help Show help message and exit";
6707 static void __NORETURN
6710 /* XXX: Restore tty modes and let the OS cleanup the rest! */
6716 static void __NORETURN
6717 die(const char *err, ...)
6723 va_start(args, err);
6724 fputs("tig: ", stderr);
6725 vfprintf(stderr, err, args);
6726 fputs("\n", stderr);
6733 warn(const char *msg, ...)
6737 va_start(args, msg);
6738 fputs("tig warning: ", stderr);
6739 vfprintf(stderr, msg, args);
6740 fputs("\n", stderr);
6745 parse_options(int argc, const char *argv[])
6747 enum request request = REQ_VIEW_MAIN;
6748 const char *subcommand;
6749 bool seen_dashdash = FALSE;
6750 /* XXX: This is vulnerable to the user overriding options
6751 * required for the main view parser. */
6752 const char *custom_argv[SIZEOF_ARG] = {
6753 "git", "log", "--no-color", "--pretty=raw", "--parents",
6754 "--topo-order", NULL
6758 if (!isatty(STDIN_FILENO)) {
6759 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
6760 return REQ_VIEW_PAGER;
6766 subcommand = argv[1];
6767 if (!strcmp(subcommand, "status")) {
6769 warn("ignoring arguments after `%s'", subcommand);
6770 return REQ_VIEW_STATUS;
6772 } else if (!strcmp(subcommand, "blame")) {
6773 if (argc <= 2 || argc > 4)
6774 die("invalid number of options to blame\n\n%s", usage);
6778 string_ncopy(opt_ref, argv[i], strlen(argv[i]));
6782 string_ncopy(opt_file, argv[i], strlen(argv[i]));
6783 return REQ_VIEW_BLAME;
6785 } else if (!strcmp(subcommand, "show")) {
6786 request = REQ_VIEW_DIFF;
6793 custom_argv[1] = subcommand;
6797 for (i = 1 + !!subcommand; i < argc; i++) {
6798 const char *opt = argv[i];
6800 if (seen_dashdash || !strcmp(opt, "--")) {
6801 seen_dashdash = TRUE;
6803 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
6804 printf("tig version %s\n", TIG_VERSION);
6807 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
6808 printf("%s\n", usage);
6812 custom_argv[j++] = opt;
6813 if (j >= ARRAY_SIZE(custom_argv))
6814 die("command too long");
6817 if (!prepare_update(VIEW(request), custom_argv, NULL, FORMAT_NONE))
6818 die("Failed to format arguments");
6824 main(int argc, const char *argv[])
6826 enum request request = parse_options(argc, argv);
6830 signal(SIGINT, quit);
6832 if (setlocale(LC_ALL, "")) {
6833 char *codeset = nl_langinfo(CODESET);
6835 string_ncopy(opt_codeset, codeset, strlen(codeset));
6838 if (load_repo_info() == ERR)
6839 die("Failed to load repo info.");
6841 if (load_options() == ERR)
6842 die("Failed to load user config.");
6844 if (load_git_config() == ERR)
6845 die("Failed to load repo config.");
6847 /* Require a git repository unless when running in pager mode. */
6848 if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
6849 die("Not a git repository");
6851 if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
6854 if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
6855 opt_iconv = iconv_open(opt_codeset, opt_encoding);
6856 if (opt_iconv == ICONV_NONE)
6857 die("Failed to initialize character set conversion");
6860 if (load_refs() == ERR)
6861 die("Failed to load refs.");
6863 foreach_view (view, i)
6864 argv_from_env(view->ops->argv, view->cmd_env);
6868 if (request != REQ_NONE)
6869 open_view(NULL, request, OPEN_PREPARED);
6870 request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
6872 while (view_driver(display[current_view], request)) {
6873 int key = get_input(0);
6875 view = display[current_view];
6876 request = get_keybinding(view->keymap, key);
6878 /* Some low-level request handling. This keeps access to
6879 * status_win restricted. */
6883 char *cmd = read_prompt(":");
6886 struct view *next = VIEW(REQ_VIEW_PAGER);
6887 const char *argv[SIZEOF_ARG] = { "git" };
6890 /* When running random commands, initially show the
6891 * command in the title. However, it maybe later be
6892 * overwritten if a commit line is selected. */
6893 string_ncopy(next->ref, cmd, strlen(cmd));
6895 if (!argv_from_string(argv, &argc, cmd)) {
6896 report("Too many arguments");
6897 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
6898 report("Failed to format command");
6900 open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
6908 case REQ_SEARCH_BACK:
6910 const char *prompt = request == REQ_SEARCH ? "/" : "?";
6911 char *search = read_prompt(prompt);
6914 string_ncopy(opt_search, search, strlen(search));
6915 else if (*opt_search)
6916 request = request == REQ_SEARCH ?