1 /* Copyright (c) 2006-2009 Jonas Fonseca <fonseca@diku.dk>
3 * This program is free software; you can redistribute it and/or
4 * modify it under the terms of the GNU General Public License as
5 * published by the Free Software Foundation; either version 2 of
6 * the License, or (at your option) any later version.
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
19 #define TIG_VERSION "unknown-version"
34 #include <sys/types.h>
37 #include <sys/select.h>
48 /* ncurses(3): Must be defined to have extended wide-character functions. */
49 #define _XOPEN_SOURCE_EXTENDED
51 #ifdef HAVE_NCURSESW_NCURSES_H
52 #include <ncursesw/ncurses.h>
54 #ifdef HAVE_NCURSES_NCURSES_H
55 #include <ncurses/ncurses.h>
62 #define __NORETURN __attribute__((__noreturn__))
67 static void __NORETURN die(const char *err, ...);
68 static void warn(const char *msg, ...);
69 static void report(const char *msg, ...);
70 static void set_nonblocking_input(bool loading);
71 static size_t utf8_length(const char *string, int *width, size_t max_width, int *trimmed, bool reserve);
72 static bool prompt_yesno(const char *prompt);
73 static int load_refs(void);
75 #define ABS(x) ((x) >= 0 ? (x) : -(x))
76 #define MIN(x, y) ((x) < (y) ? (x) : (y))
78 #define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
79 #define STRING_SIZE(x) (sizeof(x) - 1)
81 #define SIZEOF_STR 1024 /* Default string size. */
82 #define SIZEOF_REF 256 /* Size of symbolic or SHA1 ID. */
83 #define SIZEOF_REV 41 /* Holds a SHA-1 and an ending NUL. */
84 #define SIZEOF_ARG 32 /* Default argument array size. */
88 #define REVGRAPH_INIT 'I'
89 #define REVGRAPH_MERGE 'M'
90 #define REVGRAPH_BRANCH '+'
91 #define REVGRAPH_COMMIT '*'
92 #define REVGRAPH_BOUND '^'
94 #define SIZEOF_REVGRAPH 19 /* Size of revision ancestry graphics. */
96 /* This color name can be used to refer to the default term colors. */
97 #define COLOR_DEFAULT (-1)
99 #define ICONV_NONE ((iconv_t) -1)
101 #define ICONV_CONST /* nothing */
104 /* The format and size of the date column in the main view. */
105 #define DATE_FORMAT "%Y-%m-%d %H:%M"
106 #define DATE_COLS STRING_SIZE("2006-04-29 14:21 ")
108 #define AUTHOR_COLS 20
111 /* The default interval between line numbers. */
112 #define NUMBER_INTERVAL 5
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 set_from_int_map(struct int_map *map, size_t map_size,
159 int *value, const char *name, int namelen)
164 for (i = 0; i < map_size; i++)
165 if (namelen == map[i].namelen &&
166 !strncasecmp(name, map[i].name, namelen)) {
167 *value = map[i].value;
180 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
182 if (srclen > dstlen - 1)
185 strncpy(dst, src, srclen);
189 /* Shorthands for safely copying into a fixed buffer. */
191 #define string_copy(dst, src) \
192 string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
194 #define string_ncopy(dst, src, srclen) \
195 string_ncopy_do(dst, sizeof(dst), src, srclen)
197 #define string_copy_rev(dst, src) \
198 string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
200 #define string_add(dst, from, src) \
201 string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
204 chomp_string(char *name)
208 while (isspace(*name))
211 namelen = strlen(name) - 1;
212 while (namelen > 0 && isspace(name[namelen]))
219 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
222 size_t pos = bufpos ? *bufpos : 0;
225 pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
231 return pos >= bufsize ? FALSE : TRUE;
234 #define string_format(buf, fmt, args...) \
235 string_nformat(buf, sizeof(buf), NULL, fmt, args)
237 #define string_format_from(buf, from, fmt, args...) \
238 string_nformat(buf, sizeof(buf), from, fmt, args)
241 string_enum_compare(const char *str1, const char *str2, int len)
245 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
247 /* Diff-Header == DIFF_HEADER */
248 for (i = 0; i < len; i++) {
249 if (toupper(str1[i]) == toupper(str2[i]))
252 if (string_enum_sep(str1[i]) &&
253 string_enum_sep(str2[i]))
256 return str1[i] - str2[i];
262 #define prefixcmp(str1, str2) \
263 strncmp(str1, str2, STRING_SIZE(str2))
266 suffixcmp(const char *str, int slen, const char *suffix)
268 size_t len = slen >= 0 ? slen : strlen(str);
269 size_t suffixlen = strlen(suffix);
271 return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
276 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
280 while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
281 bool advance = cmd[valuelen] != 0;
284 argv[(*argc)++] = chomp_string(cmd);
285 cmd += valuelen + advance;
288 if (*argc < SIZEOF_ARG)
290 return *argc < SIZEOF_ARG;
294 argv_from_env(const char **argv, const char *name)
296 char *env = argv ? getenv(name) : NULL;
301 if (env && !argv_from_string(argv, &argc, env))
302 die("Too many arguments in the `%s` environment variable", name);
307 * Executing external commands.
311 IO_FD, /* File descriptor based IO. */
312 IO_BG, /* Execute command in the background. */
313 IO_FG, /* Execute command with same std{in,out,err}. */
314 IO_RD, /* Read only fork+exec IO. */
315 IO_WR, /* Write only fork+exec IO. */
316 IO_AP, /* Append fork+exec output to file. */
320 enum io_type type; /* The requested type of pipe. */
321 const char *dir; /* Directory from which to execute. */
322 pid_t pid; /* Pipe for reading or writing. */
323 int pipe; /* Pipe end for reading or writing. */
324 int error; /* Error status. */
325 const char *argv[SIZEOF_ARG]; /* Shell command arguments. */
326 char *buf; /* Read buffer. */
327 size_t bufalloc; /* Allocated buffer size. */
328 size_t bufsize; /* Buffer content size. */
329 char *bufpos; /* Current buffer position. */
330 unsigned int eof:1; /* Has end of file been reached. */
334 reset_io(struct io *io)
338 io->buf = io->bufpos = NULL;
339 io->bufalloc = io->bufsize = 0;
345 init_io(struct io *io, const char *dir, enum io_type type)
353 init_io_rd(struct io *io, const char *argv[], const char *dir,
354 enum format_flags flags)
356 init_io(io, dir, IO_RD);
357 return format_argv(io->argv, argv, flags);
361 io_open(struct io *io, const char *name)
363 init_io(io, NULL, IO_FD);
364 io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
365 return io->pipe != -1;
369 kill_io(struct io *io)
371 return kill(io->pid, SIGKILL) != -1;
375 done_io(struct io *io)
386 pid_t waiting = waitpid(pid, &status, 0);
391 report("waitpid failed (%s)", strerror(errno));
395 return waiting == pid &&
396 !WIFSIGNALED(status) &&
398 !WEXITSTATUS(status);
405 start_io(struct io *io)
407 int pipefds[2] = { -1, -1 };
409 if (io->type == IO_FD)
412 if ((io->type == IO_RD || io->type == IO_WR) &&
415 else if (io->type == IO_AP)
416 pipefds[1] = io->pipe;
418 if ((io->pid = fork())) {
419 if (pipefds[!(io->type == IO_WR)] != -1)
420 close(pipefds[!(io->type == IO_WR)]);
422 io->pipe = pipefds[!!(io->type == IO_WR)];
427 if (io->type != IO_FG) {
428 int devnull = open("/dev/null", O_RDWR);
429 int readfd = io->type == IO_WR ? pipefds[0] : devnull;
430 int writefd = (io->type == IO_RD || io->type == IO_AP)
431 ? pipefds[1] : devnull;
433 dup2(readfd, STDIN_FILENO);
434 dup2(writefd, STDOUT_FILENO);
435 dup2(devnull, STDERR_FILENO);
438 if (pipefds[0] != -1)
440 if (pipefds[1] != -1)
444 if (io->dir && *io->dir && chdir(io->dir) == -1)
445 die("Failed to change directory: %s", strerror(errno));
447 execvp(io->argv[0], (char *const*) io->argv);
448 die("Failed to execute program: %s", strerror(errno));
451 if (pipefds[!!(io->type == IO_WR)] != -1)
452 close(pipefds[!!(io->type == IO_WR)]);
457 run_io(struct io *io, const char **argv, const char *dir, enum io_type type)
459 init_io(io, dir, type);
460 if (!format_argv(io->argv, argv, FORMAT_NONE))
466 run_io_do(struct io *io)
468 return start_io(io) && done_io(io);
472 run_io_bg(const char **argv)
476 init_io(&io, NULL, IO_BG);
477 if (!format_argv(io.argv, argv, FORMAT_NONE))
479 return run_io_do(&io);
483 run_io_fg(const char **argv, const char *dir)
487 init_io(&io, dir, IO_FG);
488 if (!format_argv(io.argv, argv, FORMAT_NONE))
490 return run_io_do(&io);
494 run_io_append(const char **argv, enum format_flags flags, int fd)
498 init_io(&io, NULL, IO_AP);
500 if (format_argv(io.argv, argv, flags))
501 return run_io_do(&io);
507 run_io_rd(struct io *io, const char **argv, enum format_flags flags)
509 return init_io_rd(io, argv, NULL, flags) && start_io(io);
513 io_eof(struct io *io)
519 io_error(struct io *io)
525 io_strerror(struct io *io)
527 return strerror(io->error);
531 io_can_read(struct io *io)
533 struct timeval tv = { 0, 500 };
537 FD_SET(io->pipe, &fds);
539 return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
543 io_read(struct io *io, void *buf, size_t bufsize)
546 ssize_t readsize = read(io->pipe, buf, bufsize);
548 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
550 else if (readsize == -1)
552 else if (readsize == 0)
559 io_get(struct io *io, int c, bool can_read)
565 io->buf = io->bufpos = malloc(BUFSIZ);
568 io->bufalloc = BUFSIZ;
573 if (io->bufsize > 0) {
574 eol = memchr(io->bufpos, c, io->bufsize);
576 char *line = io->bufpos;
579 io->bufpos = eol + 1;
580 io->bufsize -= io->bufpos - line;
587 io->bufpos[io->bufsize] = 0;
597 if (io->bufsize > 0 && io->bufpos > io->buf)
598 memmove(io->buf, io->bufpos, io->bufsize);
600 io->bufpos = io->buf;
601 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
604 io->bufsize += readsize;
609 io_write(struct io *io, const void *buf, size_t bufsize)
613 while (!io_error(io) && written < bufsize) {
616 size = write(io->pipe, buf + written, bufsize - written);
617 if (size < 0 && (errno == EAGAIN || errno == EINTR))
625 return written == bufsize;
629 run_io_buf(const char **argv, char buf[], size_t bufsize)
634 if (!run_io_rd(&io, argv, FORMAT_NONE))
637 io.buf = io.bufpos = buf;
638 io.bufalloc = bufsize;
639 error = !io_get(&io, '\n', TRUE) && io_error(&io);
642 return done_io(&io) || error;
645 static int read_properties(struct io *io, const char *separators, int (*read)(char *, size_t, char *, size_t));
652 /* XXX: Keep the view request first and in sync with views[]. */ \
653 REQ_GROUP("View switching") \
654 REQ_(VIEW_MAIN, "Show main view"), \
655 REQ_(VIEW_DIFF, "Show diff view"), \
656 REQ_(VIEW_LOG, "Show log view"), \
657 REQ_(VIEW_TREE, "Show tree view"), \
658 REQ_(VIEW_BLOB, "Show blob view"), \
659 REQ_(VIEW_BLAME, "Show blame view"), \
660 REQ_(VIEW_HELP, "Show help page"), \
661 REQ_(VIEW_PAGER, "Show pager view"), \
662 REQ_(VIEW_STATUS, "Show status view"), \
663 REQ_(VIEW_STAGE, "Show stage view"), \
665 REQ_GROUP("View manipulation") \
666 REQ_(ENTER, "Enter current line and scroll"), \
667 REQ_(NEXT, "Move to next"), \
668 REQ_(PREVIOUS, "Move to previous"), \
669 REQ_(VIEW_NEXT, "Move focus to next view"), \
670 REQ_(REFRESH, "Reload and refresh"), \
671 REQ_(MAXIMIZE, "Maximize the current view"), \
672 REQ_(VIEW_CLOSE, "Close the current view"), \
673 REQ_(QUIT, "Close all views and quit"), \
675 REQ_GROUP("View specific requests") \
676 REQ_(STATUS_UPDATE, "Update file status"), \
677 REQ_(STATUS_REVERT, "Revert file changes"), \
678 REQ_(STATUS_MERGE, "Merge file using external tool"), \
679 REQ_(STAGE_NEXT, "Find next chunk to stage"), \
680 REQ_(TREE_PARENT, "Switch to parent directory in tree view"), \
682 REQ_GROUP("Cursor navigation") \
683 REQ_(MOVE_UP, "Move cursor one line up"), \
684 REQ_(MOVE_DOWN, "Move cursor one line down"), \
685 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
686 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
687 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
688 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
690 REQ_GROUP("Scrolling") \
691 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
692 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
693 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
694 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
696 REQ_GROUP("Searching") \
697 REQ_(SEARCH, "Search the view"), \
698 REQ_(SEARCH_BACK, "Search backwards in the view"), \
699 REQ_(FIND_NEXT, "Find next search match"), \
700 REQ_(FIND_PREV, "Find previous search match"), \
702 REQ_GROUP("Option manipulation") \
703 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
704 REQ_(TOGGLE_DATE, "Toggle date display"), \
705 REQ_(TOGGLE_AUTHOR, "Toggle author display"), \
706 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
707 REQ_(TOGGLE_REFS, "Toggle reference display (tags/branches)"), \
710 REQ_(PROMPT, "Bring up the prompt"), \
711 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
712 REQ_(SCREEN_RESIZE, "Resize the screen"), \
713 REQ_(SHOW_VERSION, "Show version information"), \
714 REQ_(STOP_LOADING, "Stop all loading views"), \
715 REQ_(EDIT, "Open in editor"), \
716 REQ_(NONE, "Do nothing")
719 /* User action requests. */
721 #define REQ_GROUP(help)
722 #define REQ_(req, help) REQ_##req
724 /* Offset all requests to avoid conflicts with ncurses getch values. */
725 REQ_OFFSET = KEY_MAX + 1,
732 struct request_info {
733 enum request request;
739 static struct request_info req_info[] = {
740 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
741 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
748 get_request(const char *name)
750 int namelen = strlen(name);
753 for (i = 0; i < ARRAY_SIZE(req_info); i++)
754 if (req_info[i].namelen == namelen &&
755 !string_enum_compare(req_info[i].name, name, namelen))
756 return req_info[i].request;
766 static const char usage[] =
767 "tig " TIG_VERSION " (" __DATE__ ")\n"
769 "Usage: tig [options] [revs] [--] [paths]\n"
770 " or: tig show [options] [revs] [--] [paths]\n"
771 " or: tig blame [rev] path\n"
773 " or: tig < [git command output]\n"
776 " -v, --version Show version and exit\n"
777 " -h, --help Show help message and exit";
779 /* Option and state variables. */
780 static bool opt_date = TRUE;
781 static bool opt_author = TRUE;
782 static bool opt_line_number = FALSE;
783 static bool opt_line_graphics = TRUE;
784 static bool opt_rev_graph = FALSE;
785 static bool opt_show_refs = TRUE;
786 static int opt_num_interval = NUMBER_INTERVAL;
787 static int opt_tab_size = TAB_SIZE;
788 static int opt_author_cols = AUTHOR_COLS-1;
789 static char opt_path[SIZEOF_STR] = "";
790 static char opt_file[SIZEOF_STR] = "";
791 static char opt_ref[SIZEOF_REF] = "";
792 static char opt_head[SIZEOF_REF] = "";
793 static char opt_head_rev[SIZEOF_REV] = "";
794 static char opt_remote[SIZEOF_REF] = "";
795 static char opt_encoding[20] = "UTF-8";
796 static bool opt_utf8 = TRUE;
797 static char opt_codeset[20] = "UTF-8";
798 static iconv_t opt_iconv = ICONV_NONE;
799 static char opt_search[SIZEOF_STR] = "";
800 static char opt_cdup[SIZEOF_STR] = "";
801 static char opt_git_dir[SIZEOF_STR] = "";
802 static signed char opt_is_inside_work_tree = -1; /* set to TRUE or FALSE */
803 static char opt_editor[SIZEOF_STR] = "";
804 static FILE *opt_tty = NULL;
806 #define is_initial_commit() (!*opt_head_rev)
807 #define is_head_commit(rev) (!strcmp((rev), "HEAD") || !strcmp(opt_head_rev, (rev)))
810 parse_options(int argc, const char *argv[], const char ***run_argv)
812 enum request request = REQ_VIEW_MAIN;
813 const char *subcommand;
814 bool seen_dashdash = FALSE;
815 /* XXX: This is vulnerable to the user overriding options
816 * required for the main view parser. */
817 static const char *custom_argv[SIZEOF_ARG] = {
818 "git", "log", "--no-color", "--pretty=raw", "--parents",
823 if (!isatty(STDIN_FILENO))
824 return REQ_VIEW_PAGER;
827 return REQ_VIEW_MAIN;
829 subcommand = argv[1];
830 if (!strcmp(subcommand, "status") || !strcmp(subcommand, "-S")) {
831 if (!strcmp(subcommand, "-S"))
832 warn("`-S' has been deprecated; use `tig status' instead");
834 warn("ignoring arguments after `%s'", subcommand);
835 return REQ_VIEW_STATUS;
837 } else if (!strcmp(subcommand, "blame")) {
838 if (argc <= 2 || argc > 4)
839 die("invalid number of options to blame\n\n%s", usage);
843 string_ncopy(opt_ref, argv[i], strlen(argv[i]));
847 string_ncopy(opt_file, argv[i], strlen(argv[i]));
848 return REQ_VIEW_BLAME;
850 } else if (!strcmp(subcommand, "show")) {
851 request = REQ_VIEW_DIFF;
853 } else if (!strcmp(subcommand, "log") || !strcmp(subcommand, "diff")) {
854 request = subcommand[0] == 'l' ? REQ_VIEW_LOG : REQ_VIEW_DIFF;
855 warn("`tig %s' has been deprecated", subcommand);
862 custom_argv[1] = subcommand;
866 for (i = 1 + !!subcommand; i < argc; i++) {
867 const char *opt = argv[i];
869 if (seen_dashdash || !strcmp(opt, "--")) {
870 seen_dashdash = TRUE;
872 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
873 printf("tig version %s\n", TIG_VERSION);
876 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
877 printf("%s\n", usage);
881 custom_argv[j++] = opt;
882 if (j >= ARRAY_SIZE(custom_argv))
883 die("command too long");
886 custom_argv[j] = NULL;
887 *run_argv = custom_argv;
894 * Line-oriented content detection.
898 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
899 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
900 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
901 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
902 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
903 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
904 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
905 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
906 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
907 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
908 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
909 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
910 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
911 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
912 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
913 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
914 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
915 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
916 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
917 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
918 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
919 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
920 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
921 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
922 LINE(AUTHOR, "author ", COLOR_CYAN, COLOR_DEFAULT, 0), \
923 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
924 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
925 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
926 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
927 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
928 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
929 LINE(DELIMITER, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
930 LINE(DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
931 LINE(LINE_NUMBER, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
932 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
933 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
934 LINE(MAIN_AUTHOR, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
935 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
936 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
937 LINE(MAIN_LOCAL_TAG,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
938 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
939 LINE(MAIN_TRACKED, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
940 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
941 LINE(MAIN_HEAD, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
942 LINE(MAIN_REVGRAPH,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
943 LINE(TREE_DIR, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
944 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
945 LINE(STAT_HEAD, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
946 LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
947 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
948 LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
949 LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
950 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
951 LINE(BLAME_ID, "", COLOR_MAGENTA, COLOR_DEFAULT, 0)
954 #define LINE(type, line, fg, bg, attr) \
962 const char *name; /* Option name. */
963 int namelen; /* Size of option name. */
964 const char *line; /* The start of line to match. */
965 int linelen; /* Size of string to match. */
966 int fg, bg, attr; /* Color and text attributes for the lines. */
969 static struct line_info line_info[] = {
970 #define LINE(type, line, fg, bg, attr) \
971 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
976 static enum line_type
977 get_line_type(const char *line)
979 int linelen = strlen(line);
982 for (type = 0; type < ARRAY_SIZE(line_info); type++)
983 /* Case insensitive search matches Signed-off-by lines better. */
984 if (linelen >= line_info[type].linelen &&
985 !strncasecmp(line_info[type].line, line, line_info[type].linelen))
992 get_line_attr(enum line_type type)
994 assert(type < ARRAY_SIZE(line_info));
995 return COLOR_PAIR(type) | line_info[type].attr;
998 static struct line_info *
999 get_line_info(const char *name)
1001 size_t namelen = strlen(name);
1002 enum line_type type;
1004 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1005 if (namelen == line_info[type].namelen &&
1006 !string_enum_compare(line_info[type].name, name, namelen))
1007 return &line_info[type];
1015 int default_bg = line_info[LINE_DEFAULT].bg;
1016 int default_fg = line_info[LINE_DEFAULT].fg;
1017 enum line_type type;
1021 if (assume_default_colors(default_fg, default_bg) == ERR) {
1022 default_bg = COLOR_BLACK;
1023 default_fg = COLOR_WHITE;
1026 for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1027 struct line_info *info = &line_info[type];
1028 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1029 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1031 init_pair(type, fg, bg);
1036 enum line_type type;
1039 unsigned int selected:1;
1040 unsigned int dirty:1;
1041 unsigned int cleareol:1;
1043 void *data; /* User data */
1053 enum request request;
1056 static struct keybinding default_keybindings[] = {
1057 /* View switching */
1058 { 'm', REQ_VIEW_MAIN },
1059 { 'd', REQ_VIEW_DIFF },
1060 { 'l', REQ_VIEW_LOG },
1061 { 't', REQ_VIEW_TREE },
1062 { 'f', REQ_VIEW_BLOB },
1063 { 'B', REQ_VIEW_BLAME },
1064 { 'p', REQ_VIEW_PAGER },
1065 { 'h', REQ_VIEW_HELP },
1066 { 'S', REQ_VIEW_STATUS },
1067 { 'c', REQ_VIEW_STAGE },
1069 /* View manipulation */
1070 { 'q', REQ_VIEW_CLOSE },
1071 { KEY_TAB, REQ_VIEW_NEXT },
1072 { KEY_RETURN, REQ_ENTER },
1073 { KEY_UP, REQ_PREVIOUS },
1074 { KEY_DOWN, REQ_NEXT },
1075 { 'R', REQ_REFRESH },
1076 { KEY_F(5), REQ_REFRESH },
1077 { 'O', REQ_MAXIMIZE },
1079 /* Cursor navigation */
1080 { 'k', REQ_MOVE_UP },
1081 { 'j', REQ_MOVE_DOWN },
1082 { KEY_HOME, REQ_MOVE_FIRST_LINE },
1083 { KEY_END, REQ_MOVE_LAST_LINE },
1084 { KEY_NPAGE, REQ_MOVE_PAGE_DOWN },
1085 { ' ', REQ_MOVE_PAGE_DOWN },
1086 { KEY_PPAGE, REQ_MOVE_PAGE_UP },
1087 { 'b', REQ_MOVE_PAGE_UP },
1088 { '-', REQ_MOVE_PAGE_UP },
1091 { KEY_IC, REQ_SCROLL_LINE_UP },
1092 { KEY_DC, REQ_SCROLL_LINE_DOWN },
1093 { 'w', REQ_SCROLL_PAGE_UP },
1094 { 's', REQ_SCROLL_PAGE_DOWN },
1097 { '/', REQ_SEARCH },
1098 { '?', REQ_SEARCH_BACK },
1099 { 'n', REQ_FIND_NEXT },
1100 { 'N', REQ_FIND_PREV },
1104 { 'z', REQ_STOP_LOADING },
1105 { 'v', REQ_SHOW_VERSION },
1106 { 'r', REQ_SCREEN_REDRAW },
1107 { '.', REQ_TOGGLE_LINENO },
1108 { 'D', REQ_TOGGLE_DATE },
1109 { 'A', REQ_TOGGLE_AUTHOR },
1110 { 'g', REQ_TOGGLE_REV_GRAPH },
1111 { 'F', REQ_TOGGLE_REFS },
1112 { ':', REQ_PROMPT },
1113 { 'u', REQ_STATUS_UPDATE },
1114 { '!', REQ_STATUS_REVERT },
1115 { 'M', REQ_STATUS_MERGE },
1116 { '@', REQ_STAGE_NEXT },
1117 { ',', REQ_TREE_PARENT },
1120 /* Using the ncurses SIGWINCH handler. */
1121 { KEY_RESIZE, REQ_SCREEN_RESIZE },
1124 #define KEYMAP_INFO \
1138 #define KEYMAP_(name) KEYMAP_##name
1143 static struct int_map keymap_table[] = {
1144 #define KEYMAP_(name) { #name, STRING_SIZE(#name), KEYMAP_##name }
1149 #define set_keymap(map, name) \
1150 set_from_int_map(keymap_table, ARRAY_SIZE(keymap_table), map, name, strlen(name))
1152 struct keybinding_table {
1153 struct keybinding *data;
1157 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1160 add_keybinding(enum keymap keymap, enum request request, int key)
1162 struct keybinding_table *table = &keybindings[keymap];
1164 table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1166 die("Failed to allocate keybinding");
1167 table->data[table->size].alias = key;
1168 table->data[table->size++].request = request;
1171 /* Looks for a key binding first in the given map, then in the generic map, and
1172 * lastly in the default keybindings. */
1174 get_keybinding(enum keymap keymap, int key)
1178 for (i = 0; i < keybindings[keymap].size; i++)
1179 if (keybindings[keymap].data[i].alias == key)
1180 return keybindings[keymap].data[i].request;
1182 for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1183 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1184 return keybindings[KEYMAP_GENERIC].data[i].request;
1186 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1187 if (default_keybindings[i].alias == key)
1188 return default_keybindings[i].request;
1190 return (enum request) key;
1199 static struct key key_table[] = {
1200 { "Enter", KEY_RETURN },
1202 { "Backspace", KEY_BACKSPACE },
1204 { "Escape", KEY_ESC },
1205 { "Left", KEY_LEFT },
1206 { "Right", KEY_RIGHT },
1208 { "Down", KEY_DOWN },
1209 { "Insert", KEY_IC },
1210 { "Delete", KEY_DC },
1212 { "Home", KEY_HOME },
1214 { "PageUp", KEY_PPAGE },
1215 { "PageDown", KEY_NPAGE },
1225 { "F10", KEY_F(10) },
1226 { "F11", KEY_F(11) },
1227 { "F12", KEY_F(12) },
1231 get_key_value(const char *name)
1235 for (i = 0; i < ARRAY_SIZE(key_table); i++)
1236 if (!strcasecmp(key_table[i].name, name))
1237 return key_table[i].value;
1239 if (strlen(name) == 1 && isprint(*name))
1246 get_key_name(int key_value)
1248 static char key_char[] = "'X'";
1249 const char *seq = NULL;
1252 for (key = 0; key < ARRAY_SIZE(key_table); key++)
1253 if (key_table[key].value == key_value)
1254 seq = key_table[key].name;
1258 isprint(key_value)) {
1259 key_char[1] = (char) key_value;
1263 return seq ? seq : "(no key)";
1267 get_key(enum request request)
1269 static char buf[BUFSIZ];
1276 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1277 struct keybinding *keybinding = &default_keybindings[i];
1279 if (keybinding->request != request)
1282 if (!string_format_from(buf, &pos, "%s%s", sep,
1283 get_key_name(keybinding->alias)))
1284 return "Too many keybindings!";
1291 struct run_request {
1294 const char *argv[SIZEOF_ARG];
1297 static struct run_request *run_request;
1298 static size_t run_requests;
1301 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1303 struct run_request *req;
1305 if (argc >= ARRAY_SIZE(req->argv) - 1)
1308 req = realloc(run_request, (run_requests + 1) * sizeof(*run_request));
1313 req = &run_request[run_requests];
1314 req->keymap = keymap;
1316 req->argv[0] = NULL;
1318 if (!format_argv(req->argv, argv, FORMAT_NONE))
1321 return REQ_NONE + ++run_requests;
1324 static struct run_request *
1325 get_run_request(enum request request)
1327 if (request <= REQ_NONE)
1329 return &run_request[request - REQ_NONE - 1];
1333 add_builtin_run_requests(void)
1335 const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1336 const char *gc[] = { "git", "gc", NULL };
1343 { KEYMAP_MAIN, 'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1344 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1348 for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1351 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1352 if (req != REQ_NONE)
1353 add_keybinding(reqs[i].keymap, req, reqs[i].key);
1358 * User config file handling.
1361 static struct int_map color_map[] = {
1362 #define COLOR_MAP(name) { #name, STRING_SIZE(#name), COLOR_##name }
1374 #define set_color(color, name) \
1375 set_from_int_map(color_map, ARRAY_SIZE(color_map), color, name, strlen(name))
1377 static struct int_map attr_map[] = {
1378 #define ATTR_MAP(name) { #name, STRING_SIZE(#name), A_##name }
1385 ATTR_MAP(UNDERLINE),
1388 #define set_attribute(attr, name) \
1389 set_from_int_map(attr_map, ARRAY_SIZE(attr_map), attr, name, strlen(name))
1391 static int config_lineno;
1392 static bool config_errors;
1393 static const char *config_msg;
1395 /* Wants: object fgcolor bgcolor [attr] */
1397 option_color_command(int argc, const char *argv[])
1399 struct line_info *info;
1401 if (argc != 3 && argc != 4) {
1402 config_msg = "Wrong number of arguments given to color command";
1406 info = get_line_info(argv[0]);
1408 if (!string_enum_compare(argv[0], "main-delim", strlen("main-delim"))) {
1409 info = get_line_info("delimiter");
1411 } else if (!string_enum_compare(argv[0], "main-date", strlen("main-date"))) {
1412 info = get_line_info("date");
1415 config_msg = "Unknown color name";
1420 if (set_color(&info->fg, argv[1]) == ERR ||
1421 set_color(&info->bg, argv[2]) == ERR) {
1422 config_msg = "Unknown color";
1426 if (argc == 4 && set_attribute(&info->attr, argv[3]) == ERR) {
1427 config_msg = "Unknown attribute";
1434 static bool parse_bool(const char *s)
1436 return (!strcmp(s, "1") || !strcmp(s, "true") ||
1437 !strcmp(s, "yes")) ? TRUE : FALSE;
1441 parse_int(const char *s, int default_value, int min, int max)
1443 int value = atoi(s);
1445 return (value < min || value > max) ? default_value : value;
1448 /* Wants: name = value */
1450 option_set_command(int argc, const char *argv[])
1453 config_msg = "Wrong number of arguments given to set command";
1457 if (strcmp(argv[1], "=")) {
1458 config_msg = "No value assigned";
1462 if (!strcmp(argv[0], "show-author")) {
1463 opt_author = parse_bool(argv[2]);
1467 if (!strcmp(argv[0], "show-date")) {
1468 opt_date = parse_bool(argv[2]);
1472 if (!strcmp(argv[0], "show-rev-graph")) {
1473 opt_rev_graph = parse_bool(argv[2]);
1477 if (!strcmp(argv[0], "show-refs")) {
1478 opt_show_refs = parse_bool(argv[2]);
1482 if (!strcmp(argv[0], "show-line-numbers")) {
1483 opt_line_number = parse_bool(argv[2]);
1487 if (!strcmp(argv[0], "line-graphics")) {
1488 opt_line_graphics = parse_bool(argv[2]);
1492 if (!strcmp(argv[0], "line-number-interval")) {
1493 opt_num_interval = parse_int(argv[2], opt_num_interval, 1, 1024);
1497 if (!strcmp(argv[0], "author-width")) {
1498 opt_author_cols = parse_int(argv[2], opt_author_cols, 0, 1024);
1502 if (!strcmp(argv[0], "tab-size")) {
1503 opt_tab_size = parse_int(argv[2], opt_tab_size, 1, 1024);
1507 if (!strcmp(argv[0], "commit-encoding")) {
1508 const char *arg = argv[2];
1509 int arglen = strlen(arg);
1514 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1515 config_msg = "Unmatched quotation";
1518 arg += 1; arglen -= 2;
1520 string_ncopy(opt_encoding, arg, strlen(arg));
1525 config_msg = "Unknown variable name";
1529 /* Wants: mode request key */
1531 option_bind_command(int argc, const char *argv[])
1533 enum request request;
1538 config_msg = "Wrong number of arguments given to bind command";
1542 if (set_keymap(&keymap, argv[0]) == ERR) {
1543 config_msg = "Unknown key map";
1547 key = get_key_value(argv[1]);
1549 config_msg = "Unknown key";
1553 request = get_request(argv[2]);
1554 if (request == REQ_NONE) {
1555 const char *obsolete[] = { "cherry-pick" };
1556 size_t namelen = strlen(argv[2]);
1559 for (i = 0; i < ARRAY_SIZE(obsolete); i++) {
1560 if (namelen == strlen(obsolete[i]) &&
1561 !string_enum_compare(obsolete[i], argv[2], namelen)) {
1562 config_msg = "Obsolete request name";
1567 if (request == REQ_NONE && *argv[2]++ == '!')
1568 request = add_run_request(keymap, key, argc - 2, argv + 2);
1569 if (request == REQ_NONE) {
1570 config_msg = "Unknown request name";
1574 add_keybinding(keymap, request, key);
1580 set_option(const char *opt, char *value)
1582 const char *argv[SIZEOF_ARG];
1585 if (!argv_from_string(argv, &argc, value)) {
1586 config_msg = "Too many option arguments";
1590 if (!strcmp(opt, "color"))
1591 return option_color_command(argc, argv);
1593 if (!strcmp(opt, "set"))
1594 return option_set_command(argc, argv);
1596 if (!strcmp(opt, "bind"))
1597 return option_bind_command(argc, argv);
1599 config_msg = "Unknown option command";
1604 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1609 config_msg = "Internal error";
1611 /* Check for comment markers, since read_properties() will
1612 * only ensure opt and value are split at first " \t". */
1613 optlen = strcspn(opt, "#");
1617 if (opt[optlen] != 0) {
1618 config_msg = "No option value";
1622 /* Look for comment endings in the value. */
1623 size_t len = strcspn(value, "#");
1625 if (len < valuelen) {
1627 value[valuelen] = 0;
1630 status = set_option(opt, value);
1633 if (status == ERR) {
1634 fprintf(stderr, "Error on line %d, near '%.*s': %s\n",
1635 config_lineno, (int) optlen, opt, config_msg);
1636 config_errors = TRUE;
1639 /* Always keep going if errors are encountered. */
1644 load_option_file(const char *path)
1648 /* It's ok that the file doesn't exist. */
1649 if (!io_open(&io, path))
1653 config_errors = FALSE;
1655 if (read_properties(&io, " \t", read_option) == ERR ||
1656 config_errors == TRUE)
1657 fprintf(stderr, "Errors while loading %s.\n", path);
1663 const char *home = getenv("HOME");
1664 const char *tigrc_user = getenv("TIGRC_USER");
1665 const char *tigrc_system = getenv("TIGRC_SYSTEM");
1666 char buf[SIZEOF_STR];
1668 add_builtin_run_requests();
1670 if (!tigrc_system) {
1671 if (!string_format(buf, "%s/tigrc", SYSCONFDIR))
1675 load_option_file(tigrc_system);
1678 if (!home || !string_format(buf, "%s/.tigrc", home))
1682 load_option_file(tigrc_user);
1695 /* The display array of active views and the index of the current view. */
1696 static struct view *display[2];
1697 static unsigned int current_view;
1699 /* Reading from the prompt? */
1700 static bool input_mode = FALSE;
1702 #define foreach_displayed_view(view, i) \
1703 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1705 #define displayed_views() (display[1] != NULL ? 2 : 1)
1707 /* Current head and commit ID */
1708 static char ref_blob[SIZEOF_REF] = "";
1709 static char ref_commit[SIZEOF_REF] = "HEAD";
1710 static char ref_head[SIZEOF_REF] = "HEAD";
1713 const char *name; /* View name */
1714 const char *cmd_env; /* Command line set via environment */
1715 const char *id; /* Points to either of ref_{head,commit,blob} */
1717 struct view_ops *ops; /* View operations */
1719 enum keymap keymap; /* What keymap does this view have */
1720 bool git_dir; /* Whether the view requires a git directory. */
1722 char ref[SIZEOF_REF]; /* Hovered commit reference */
1723 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
1725 int height, width; /* The width and height of the main window */
1726 WINDOW *win; /* The main window */
1727 WINDOW *title; /* The title window living below the main window */
1730 unsigned long offset; /* Offset of the window top */
1731 unsigned long lineno; /* Current line number */
1734 char grep[SIZEOF_STR]; /* Search string */
1735 regex_t *regex; /* Pre-compiled regex */
1737 /* If non-NULL, points to the view that opened this view. If this view
1738 * is closed tig will switch back to the parent view. */
1739 struct view *parent;
1742 size_t lines; /* Total number of lines */
1743 struct line *line; /* Line index */
1744 size_t line_alloc; /* Total number of allocated lines */
1745 unsigned int digits; /* Number of digits in the lines member. */
1748 struct line *curline; /* Line currently being drawn. */
1749 enum line_type curtype; /* Attribute currently used for drawing. */
1750 unsigned long col; /* Column when drawing. */
1760 /* What type of content being displayed. Used in the title bar. */
1762 /* Default command arguments. */
1764 /* Open and reads in all view content. */
1765 bool (*open)(struct view *view);
1766 /* Read one line; updates view->line. */
1767 bool (*read)(struct view *view, char *data);
1768 /* Draw one line; @lineno must be < view->height. */
1769 bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
1770 /* Depending on view handle a special requests. */
1771 enum request (*request)(struct view *view, enum request request, struct line *line);
1772 /* Search for regex in a line. */
1773 bool (*grep)(struct view *view, struct line *line);
1775 void (*select)(struct view *view, struct line *line);
1778 static struct view_ops blame_ops;
1779 static struct view_ops blob_ops;
1780 static struct view_ops diff_ops;
1781 static struct view_ops help_ops;
1782 static struct view_ops log_ops;
1783 static struct view_ops main_ops;
1784 static struct view_ops pager_ops;
1785 static struct view_ops stage_ops;
1786 static struct view_ops status_ops;
1787 static struct view_ops tree_ops;
1789 #define VIEW_STR(name, env, ref, ops, map, git) \
1790 { name, #env, ref, ops, map, git }
1792 #define VIEW_(id, name, ops, git, ref) \
1793 VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
1796 static struct view views[] = {
1797 VIEW_(MAIN, "main", &main_ops, TRUE, ref_head),
1798 VIEW_(DIFF, "diff", &diff_ops, TRUE, ref_commit),
1799 VIEW_(LOG, "log", &log_ops, TRUE, ref_head),
1800 VIEW_(TREE, "tree", &tree_ops, TRUE, ref_commit),
1801 VIEW_(BLOB, "blob", &blob_ops, TRUE, ref_blob),
1802 VIEW_(BLAME, "blame", &blame_ops, TRUE, ref_commit),
1803 VIEW_(HELP, "help", &help_ops, FALSE, ""),
1804 VIEW_(PAGER, "pager", &pager_ops, FALSE, "stdin"),
1805 VIEW_(STATUS, "status", &status_ops, TRUE, ""),
1806 VIEW_(STAGE, "stage", &stage_ops, TRUE, ""),
1809 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
1810 #define VIEW_REQ(view) ((view) - views + REQ_OFFSET + 1)
1812 #define foreach_view(view, i) \
1813 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1815 #define view_is_displayed(view) \
1816 (view == display[0] || view == display[1])
1823 static int line_graphics[] = {
1824 /* LINE_GRAPHIC_VLINE: */ '|'
1828 set_view_attr(struct view *view, enum line_type type)
1830 if (!view->curline->selected && view->curtype != type) {
1831 wattrset(view->win, get_line_attr(type));
1832 wchgat(view->win, -1, 0, type, NULL);
1833 view->curtype = type;
1838 draw_chars(struct view *view, enum line_type type, const char *string,
1839 int max_len, bool use_tilde)
1843 int trimmed = FALSE;
1849 len = utf8_length(string, &col, max_len, &trimmed, use_tilde);
1851 col = len = strlen(string);
1852 if (len > max_len) {
1856 col = len = max_len;
1861 set_view_attr(view, type);
1862 waddnstr(view->win, string, len);
1863 if (trimmed && use_tilde) {
1864 set_view_attr(view, LINE_DELIMITER);
1865 waddch(view->win, '~');
1873 draw_space(struct view *view, enum line_type type, int max, int spaces)
1875 static char space[] = " ";
1878 spaces = MIN(max, spaces);
1880 while (spaces > 0) {
1881 int len = MIN(spaces, sizeof(space) - 1);
1883 col += draw_chars(view, type, space, spaces, FALSE);
1891 draw_lineno(struct view *view, unsigned int lineno)
1894 int digits3 = view->digits < 3 ? 3 : view->digits;
1895 int max_number = MIN(digits3, STRING_SIZE(number));
1896 int max = view->width - view->col;
1899 if (max < max_number)
1902 lineno += view->offset + 1;
1903 if (lineno == 1 || (lineno % opt_num_interval) == 0) {
1904 static char fmt[] = "%1ld";
1906 if (view->digits <= 9)
1907 fmt[1] = '0' + digits3;
1909 if (!string_format(number, fmt, lineno))
1911 col = draw_chars(view, LINE_LINE_NUMBER, number, max_number, TRUE);
1913 col = draw_space(view, LINE_LINE_NUMBER, max_number, max_number);
1917 set_view_attr(view, LINE_DEFAULT);
1918 waddch(view->win, line_graphics[LINE_GRAPHIC_VLINE]);
1923 col += draw_space(view, LINE_DEFAULT, max - col, 1);
1926 return view->width - view->col <= 0;
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->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 int max = view->width - view->col;
1945 set_view_attr(view, type);
1946 /* Using waddch() instead of waddnstr() ensures that
1947 * they'll be rendered correctly for the cursor line. */
1948 for (i = 0; i < size; i++)
1949 waddch(view->win, graphic[i]);
1953 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);
1971 view->col += col + draw_space(view, LINE_DEFAULT, max - col, max - col);
1972 return view->width - view->col <= 0;
1976 draw_date(struct view *view, struct tm *time)
1978 char buf[DATE_COLS];
1983 timelen = strftime(buf, sizeof(buf), DATE_FORMAT, time);
1984 date = timelen ? buf : NULL;
1986 return draw_field(view, LINE_DATE, date, DATE_COLS, FALSE);
1990 draw_view_line(struct view *view, unsigned int lineno)
1993 bool selected = (view->offset + lineno == view->lineno);
1996 assert(view_is_displayed(view));
1998 if (view->offset + lineno >= view->lines)
2001 line = &view->line[view->offset + lineno];
2003 wmove(view->win, lineno, 0);
2005 wclrtoeol(view->win);
2007 view->curline = line;
2008 view->curtype = LINE_NONE;
2009 line->selected = FALSE;
2010 line->dirty = line->cleareol = 0;
2013 set_view_attr(view, LINE_CURSOR);
2014 line->selected = TRUE;
2015 view->ops->select(view, line);
2018 scrollok(view->win, FALSE);
2019 draw_ok = view->ops->draw(view, line, lineno);
2020 scrollok(view->win, TRUE);
2026 redraw_view_dirty(struct view *view)
2031 for (lineno = 0; lineno < view->height; lineno++) {
2032 if (view->offset + lineno >= view->lines)
2034 if (!view->line[view->offset + lineno].dirty)
2037 if (!draw_view_line(view, lineno))
2043 redrawwin(view->win);
2045 wnoutrefresh(view->win);
2047 wrefresh(view->win);
2051 redraw_view_from(struct view *view, int lineno)
2053 assert(0 <= lineno && lineno < view->height);
2055 for (; lineno < view->height; lineno++) {
2056 if (!draw_view_line(view, lineno))
2060 redrawwin(view->win);
2062 wnoutrefresh(view->win);
2064 wrefresh(view->win);
2068 redraw_view(struct view *view)
2071 redraw_view_from(view, 0);
2076 update_view_title(struct view *view)
2078 char buf[SIZEOF_STR];
2079 char state[SIZEOF_STR];
2080 size_t bufpos = 0, statelen = 0;
2082 assert(view_is_displayed(view));
2084 if (view != VIEW(REQ_VIEW_STATUS) && view->lines) {
2085 unsigned int view_lines = view->offset + view->height;
2086 unsigned int lines = view->lines
2087 ? MIN(view_lines, view->lines) * 100 / view->lines
2090 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2099 time_t secs = time(NULL) - view->start_time;
2101 /* Three git seconds are a long time ... */
2103 string_format_from(state, &statelen, " loading %lds", secs);
2106 string_format_from(buf, &bufpos, "[%s]", view->name);
2107 if (*view->ref && bufpos < view->width) {
2108 size_t refsize = strlen(view->ref);
2109 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2111 if (minsize < view->width)
2112 refsize = view->width - minsize + 7;
2113 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2116 if (statelen && bufpos < view->width) {
2117 string_format_from(buf, &bufpos, "%s", state);
2120 if (view == display[current_view])
2121 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2123 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2125 mvwaddnstr(view->title, 0, 0, buf, bufpos);
2126 wclrtoeol(view->title);
2127 wmove(view->title, 0, view->width - 1);
2130 wnoutrefresh(view->title);
2132 wrefresh(view->title);
2136 resize_display(void)
2139 struct view *base = display[0];
2140 struct view *view = display[1] ? display[1] : display[0];
2142 /* Setup window dimensions */
2144 getmaxyx(stdscr, base->height, base->width);
2146 /* Make room for the status window. */
2150 /* Horizontal split. */
2151 view->width = base->width;
2152 view->height = SCALE_SPLIT_VIEW(base->height);
2153 base->height -= view->height;
2155 /* Make room for the title bar. */
2159 /* Make room for the title bar. */
2164 foreach_displayed_view (view, i) {
2166 view->win = newwin(view->height, 0, offset, 0);
2168 die("Failed to create %s view", view->name);
2170 scrollok(view->win, TRUE);
2172 view->title = newwin(1, 0, offset + view->height, 0);
2174 die("Failed to create title window");
2177 wresize(view->win, view->height, view->width);
2178 mvwin(view->win, offset, 0);
2179 mvwin(view->title, offset + view->height, 0);
2182 offset += view->height + 1;
2187 redraw_display(bool clear)
2192 foreach_displayed_view (view, i) {
2196 update_view_title(view);
2201 update_display_cursor(struct view *view)
2203 /* Move the cursor to the right-most column of the cursor line.
2205 * XXX: This could turn out to be a bit expensive, but it ensures that
2206 * the cursor does not jump around. */
2208 wmove(view->win, view->lineno - view->offset, view->width - 1);
2209 wrefresh(view->win);
2214 toggle_view_option(bool *option, const char *help)
2217 redraw_display(FALSE);
2218 report("%sabling %s", *option ? "En" : "Dis", help);
2225 /* Scrolling backend */
2227 do_scroll_view(struct view *view, int lines)
2229 bool redraw_current_line = FALSE;
2231 /* The rendering expects the new offset. */
2232 view->offset += lines;
2234 assert(0 <= view->offset && view->offset < view->lines);
2237 /* Move current line into the view. */
2238 if (view->lineno < view->offset) {
2239 view->lineno = view->offset;
2240 redraw_current_line = TRUE;
2241 } else if (view->lineno >= view->offset + view->height) {
2242 view->lineno = view->offset + view->height - 1;
2243 redraw_current_line = TRUE;
2246 assert(view->offset <= view->lineno && view->lineno < view->lines);
2248 /* Redraw the whole screen if scrolling is pointless. */
2249 if (view->height < ABS(lines)) {
2253 int line = lines > 0 ? view->height - lines : 0;
2254 int end = line + ABS(lines);
2256 wscrl(view->win, lines);
2258 for (; line < end; line++) {
2259 if (!draw_view_line(view, line))
2263 if (redraw_current_line)
2264 draw_view_line(view, view->lineno - view->offset);
2267 redrawwin(view->win);
2268 wrefresh(view->win);
2272 /* Scroll frontend */
2274 scroll_view(struct view *view, enum request request)
2278 assert(view_is_displayed(view));
2281 case REQ_SCROLL_PAGE_DOWN:
2282 lines = view->height;
2283 case REQ_SCROLL_LINE_DOWN:
2284 if (view->offset + lines > view->lines)
2285 lines = view->lines - view->offset;
2287 if (lines == 0 || view->offset + view->height >= view->lines) {
2288 report("Cannot scroll beyond the last line");
2293 case REQ_SCROLL_PAGE_UP:
2294 lines = view->height;
2295 case REQ_SCROLL_LINE_UP:
2296 if (lines > view->offset)
2297 lines = view->offset;
2300 report("Cannot scroll beyond the first line");
2308 die("request %d not handled in switch", request);
2311 do_scroll_view(view, lines);
2316 move_view(struct view *view, enum request request)
2318 int scroll_steps = 0;
2322 case REQ_MOVE_FIRST_LINE:
2323 steps = -view->lineno;
2326 case REQ_MOVE_LAST_LINE:
2327 steps = view->lines - view->lineno - 1;
2330 case REQ_MOVE_PAGE_UP:
2331 steps = view->height > view->lineno
2332 ? -view->lineno : -view->height;
2335 case REQ_MOVE_PAGE_DOWN:
2336 steps = view->lineno + view->height >= view->lines
2337 ? view->lines - view->lineno - 1 : view->height;
2349 die("request %d not handled in switch", request);
2352 if (steps <= 0 && view->lineno == 0) {
2353 report("Cannot move beyond the first line");
2356 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2357 report("Cannot move beyond the last line");
2361 /* Move the current line */
2362 view->lineno += steps;
2363 assert(0 <= view->lineno && view->lineno < view->lines);
2365 /* Check whether the view needs to be scrolled */
2366 if (view->lineno < view->offset ||
2367 view->lineno >= view->offset + view->height) {
2368 scroll_steps = steps;
2369 if (steps < 0 && -steps > view->offset) {
2370 scroll_steps = -view->offset;
2372 } else if (steps > 0) {
2373 if (view->lineno == view->lines - 1 &&
2374 view->lines > view->height) {
2375 scroll_steps = view->lines - view->offset - 1;
2376 if (scroll_steps >= view->height)
2377 scroll_steps -= view->height - 1;
2382 if (!view_is_displayed(view)) {
2383 view->offset += scroll_steps;
2384 assert(0 <= view->offset && view->offset < view->lines);
2385 view->ops->select(view, &view->line[view->lineno]);
2389 /* Repaint the old "current" line if we be scrolling */
2390 if (ABS(steps) < view->height)
2391 draw_view_line(view, view->lineno - steps - view->offset);
2394 do_scroll_view(view, scroll_steps);
2398 /* Draw the current line */
2399 draw_view_line(view, view->lineno - view->offset);
2401 redrawwin(view->win);
2402 wrefresh(view->win);
2411 static void search_view(struct view *view, enum request request);
2414 find_next_line(struct view *view, unsigned long lineno, struct line *line)
2416 assert(view_is_displayed(view));
2418 if (!view->ops->grep(view, line))
2421 if (lineno - view->offset >= view->height) {
2422 view->offset = lineno;
2423 view->lineno = lineno;
2427 unsigned long old_lineno = view->lineno - view->offset;
2429 view->lineno = lineno;
2430 draw_view_line(view, old_lineno);
2432 draw_view_line(view, view->lineno - view->offset);
2433 redrawwin(view->win);
2434 wrefresh(view->win);
2437 report("Line %ld matches '%s'", lineno + 1, view->grep);
2442 find_next(struct view *view, enum request request)
2444 unsigned long lineno = view->lineno;
2449 report("No previous search");
2451 search_view(view, request);
2461 case REQ_SEARCH_BACK:
2470 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2471 lineno += direction;
2473 /* Note, lineno is unsigned long so will wrap around in which case it
2474 * will become bigger than view->lines. */
2475 for (; lineno < view->lines; lineno += direction) {
2476 struct line *line = &view->line[lineno];
2478 if (find_next_line(view, lineno, line))
2482 report("No match found for '%s'", view->grep);
2486 search_view(struct view *view, enum request request)
2491 regfree(view->regex);
2494 view->regex = calloc(1, sizeof(*view->regex));
2499 regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2500 if (regex_err != 0) {
2501 char buf[SIZEOF_STR] = "unknown error";
2503 regerror(regex_err, view->regex, buf, sizeof(buf));
2504 report("Search failed: %s", buf);
2508 string_copy(view->grep, opt_search);
2510 find_next(view, request);
2514 * Incremental updating
2518 reset_view(struct view *view)
2522 for (i = 0; i < view->lines; i++)
2523 free(view->line[i].data);
2530 view->line_alloc = 0;
2532 view->update_secs = 0;
2536 free_argv(const char *argv[])
2540 for (argc = 0; argv[argc]; argc++)
2541 free((void *) argv[argc]);
2545 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
2547 char buf[SIZEOF_STR];
2549 bool noreplace = flags == FORMAT_NONE;
2551 free_argv(dst_argv);
2553 for (argc = 0; src_argv[argc]; argc++) {
2554 const char *arg = src_argv[argc];
2558 char *next = strstr(arg, "%(");
2559 int len = next - arg;
2562 if (!next || noreplace) {
2563 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
2568 } else if (!prefixcmp(next, "%(directory)")) {
2571 } else if (!prefixcmp(next, "%(file)")) {
2574 } else if (!prefixcmp(next, "%(ref)")) {
2575 value = *opt_ref ? opt_ref : "HEAD";
2577 } else if (!prefixcmp(next, "%(head)")) {
2580 } else if (!prefixcmp(next, "%(commit)")) {
2583 } else if (!prefixcmp(next, "%(blob)")) {
2587 report("Unknown replacement: `%s`", next);
2591 if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
2594 arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
2597 dst_argv[argc] = strdup(buf);
2598 if (!dst_argv[argc])
2602 dst_argv[argc] = NULL;
2604 return src_argv[argc] == NULL;
2608 end_update(struct view *view, bool force)
2612 while (!view->ops->read(view, NULL))
2615 set_nonblocking_input(FALSE);
2617 kill_io(view->pipe);
2618 done_io(view->pipe);
2623 setup_update(struct view *view, const char *vid)
2625 set_nonblocking_input(TRUE);
2627 string_copy_rev(view->vid, vid);
2628 view->pipe = &view->io;
2629 view->start_time = time(NULL);
2633 prepare_update(struct view *view, const char *argv[], const char *dir,
2634 enum format_flags flags)
2637 end_update(view, TRUE);
2638 return init_io_rd(&view->io, argv, dir, flags);
2642 prepare_update_file(struct view *view, const char *name)
2645 end_update(view, TRUE);
2646 return io_open(&view->io, name);
2650 begin_update(struct view *view, bool refresh)
2653 end_update(view, TRUE);
2656 if (!start_io(&view->io))
2660 if (view == VIEW(REQ_VIEW_TREE) && strcmp(view->vid, view->id))
2663 if (!run_io_rd(&view->io, view->ops->argv, FORMAT_ALL))
2666 /* Put the current ref_* value to the view title ref
2667 * member. This is needed by the blob view. Most other
2668 * views sets it automatically after loading because the
2669 * first line is a commit line. */
2670 string_copy_rev(view->ref, view->id);
2673 setup_update(view, view->id);
2678 #define ITEM_CHUNK_SIZE 256
2680 realloc_items(void *mem, size_t *size, size_t new_size, size_t item_size)
2682 size_t num_chunks = *size / ITEM_CHUNK_SIZE;
2683 size_t num_chunks_new = (new_size + ITEM_CHUNK_SIZE - 1) / ITEM_CHUNK_SIZE;
2685 if (mem == NULL || num_chunks != num_chunks_new) {
2686 *size = num_chunks_new * ITEM_CHUNK_SIZE;
2687 mem = realloc(mem, *size * item_size);
2693 static struct line *
2694 realloc_lines(struct view *view, size_t line_size)
2696 size_t alloc = view->line_alloc;
2697 struct line *tmp = realloc_items(view->line, &alloc, line_size,
2698 sizeof(*view->line));
2704 view->line_alloc = alloc;
2709 update_view(struct view *view)
2711 char out_buffer[BUFSIZ * 2];
2713 /* Clear the view and redraw everything since the tree sorting
2714 * might have rearranged things. */
2715 bool redraw = view->lines == 0;
2716 bool can_read = TRUE;
2721 if (!io_can_read(view->pipe)) {
2722 if (view->lines == 0) {
2723 time_t secs = time(NULL) - view->start_time;
2725 if (secs > view->update_secs) {
2726 if (view->update_secs == 0)
2728 update_view_title(view);
2729 view->update_secs = secs;
2735 for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
2736 if (opt_iconv != ICONV_NONE) {
2737 ICONV_CONST char *inbuf = line;
2738 size_t inlen = strlen(line) + 1;
2740 char *outbuf = out_buffer;
2741 size_t outlen = sizeof(out_buffer);
2745 ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2746 if (ret != (size_t) -1)
2750 if (!view->ops->read(view, line))
2755 unsigned long lines = view->lines;
2758 for (digits = 0; lines; digits++)
2761 /* Keep the displayed view in sync with line number scaling. */
2762 if (digits != view->digits) {
2763 view->digits = digits;
2764 if (opt_line_number || view == VIEW(REQ_VIEW_BLAME))
2769 if (io_error(view->pipe)) {
2770 report("Failed to read: %s", io_strerror(view->pipe));
2771 end_update(view, TRUE);
2773 } else if (io_eof(view->pipe)) {
2775 end_update(view, FALSE);
2778 if (!view_is_displayed(view))
2782 redraw_view_from(view, 0);
2784 redraw_view_dirty(view);
2786 /* Update the title _after_ the redraw so that if the redraw picks up a
2787 * commit reference in view->ref it'll be available here. */
2788 update_view_title(view);
2792 report("Allocation failure");
2793 end_update(view, TRUE);
2797 static struct line *
2798 add_line_data(struct view *view, void *data, enum line_type type)
2802 if (!realloc_lines(view, view->lines + 1))
2805 line = &view->line[view->lines++];
2806 memset(line, 0, sizeof(*line));
2814 static struct line *
2815 add_line_text(struct view *view, const char *text, enum line_type type)
2817 char *data = text ? strdup(text) : NULL;
2819 return data ? add_line_data(view, data, type) : NULL;
2822 static struct line *
2823 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
2825 char buf[SIZEOF_STR];
2828 va_start(args, fmt);
2829 if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
2833 return buf[0] ? add_line_text(view, buf, type) : NULL;
2841 OPEN_DEFAULT = 0, /* Use default view switching. */
2842 OPEN_SPLIT = 1, /* Split current view. */
2843 OPEN_BACKGROUNDED = 2, /* Backgrounded. */
2844 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
2845 OPEN_NOMAXIMIZE = 8, /* Do not maximize the current view. */
2846 OPEN_REFRESH = 16, /* Refresh view using previous command. */
2847 OPEN_PREPARED = 32, /* Open already prepared command. */
2851 open_view(struct view *prev, enum request request, enum open_flags flags)
2853 bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
2854 bool split = !!(flags & OPEN_SPLIT);
2855 bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
2856 bool nomaximize = !!(flags & (OPEN_NOMAXIMIZE | OPEN_REFRESH));
2857 struct view *view = VIEW(request);
2858 int nviews = displayed_views();
2859 struct view *base_view = display[0];
2861 if (view == prev && nviews == 1 && !reload) {
2862 report("Already in %s view", view->name);
2866 if (view->git_dir && !opt_git_dir[0]) {
2867 report("The %s view is disabled in pager view", view->name);
2875 } else if (!nomaximize) {
2876 /* Maximize the current view. */
2877 memset(display, 0, sizeof(display));
2879 display[current_view] = view;
2882 /* Resize the view when switching between split- and full-screen,
2883 * or when switching between two different full-screen views. */
2884 if (nviews != displayed_views() ||
2885 (nviews == 1 && base_view != display[0]))
2888 if (view->ops->open) {
2889 if (!view->ops->open(view)) {
2890 report("Failed to load %s view", view->name);
2894 } else if ((reload || strcmp(view->vid, view->id)) &&
2895 !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
2896 report("Failed to load %s view", view->name);
2900 if (split && prev->lineno - prev->offset >= prev->height) {
2901 /* Take the title line into account. */
2902 int lines = prev->lineno - prev->offset - prev->height + 1;
2904 /* Scroll the view that was split if the current line is
2905 * outside the new limited view. */
2906 do_scroll_view(prev, lines);
2909 if (prev && view != prev) {
2910 if (split && !backgrounded) {
2911 /* "Blur" the previous view. */
2912 update_view_title(prev);
2915 view->parent = prev;
2918 if (view->pipe && view->lines == 0) {
2919 /* Clear the old view and let the incremental updating refill
2923 } else if (view_is_displayed(view)) {
2928 /* If the view is backgrounded the above calls to report()
2929 * won't redraw the view title. */
2931 update_view_title(view);
2935 open_external_viewer(const char *argv[], const char *dir)
2937 def_prog_mode(); /* save current tty modes */
2938 endwin(); /* restore original tty modes */
2939 run_io_fg(argv, dir);
2940 fprintf(stderr, "Press Enter to continue");
2943 redraw_display(TRUE);
2947 open_mergetool(const char *file)
2949 const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
2951 open_external_viewer(mergetool_argv, opt_cdup);
2955 open_editor(bool from_root, const char *file)
2957 const char *editor_argv[] = { "vi", file, NULL };
2960 editor = getenv("GIT_EDITOR");
2961 if (!editor && *opt_editor)
2962 editor = opt_editor;
2964 editor = getenv("VISUAL");
2966 editor = getenv("EDITOR");
2970 editor_argv[0] = editor;
2971 open_external_viewer(editor_argv, from_root ? opt_cdup : NULL);
2975 open_run_request(enum request request)
2977 struct run_request *req = get_run_request(request);
2978 const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
2981 report("Unknown run request");
2985 if (format_argv(argv, req->argv, FORMAT_ALL))
2986 open_external_viewer(argv, NULL);
2991 * User request switch noodle
2995 view_driver(struct view *view, enum request request)
2999 if (request == REQ_NONE) {
3004 if (request > REQ_NONE) {
3005 open_run_request(request);
3006 /* FIXME: When all views can refresh always do this. */
3007 if (view == VIEW(REQ_VIEW_STATUS) ||
3008 view == VIEW(REQ_VIEW_MAIN) ||
3009 view == VIEW(REQ_VIEW_LOG) ||
3010 view == VIEW(REQ_VIEW_STAGE))
3011 request = REQ_REFRESH;
3016 if (view && view->lines) {
3017 request = view->ops->request(view, request, &view->line[view->lineno]);
3018 if (request == REQ_NONE)
3025 case REQ_MOVE_PAGE_UP:
3026 case REQ_MOVE_PAGE_DOWN:
3027 case REQ_MOVE_FIRST_LINE:
3028 case REQ_MOVE_LAST_LINE:
3029 move_view(view, request);
3032 case REQ_SCROLL_LINE_DOWN:
3033 case REQ_SCROLL_LINE_UP:
3034 case REQ_SCROLL_PAGE_DOWN:
3035 case REQ_SCROLL_PAGE_UP:
3036 scroll_view(view, request);
3039 case REQ_VIEW_BLAME:
3041 report("No file chosen, press %s to open tree view",
3042 get_key(REQ_VIEW_TREE));
3045 open_view(view, request, OPEN_DEFAULT);
3050 report("No file chosen, press %s to open tree view",
3051 get_key(REQ_VIEW_TREE));
3054 open_view(view, request, OPEN_DEFAULT);
3057 case REQ_VIEW_PAGER:
3058 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3059 report("No pager content, press %s to run command from prompt",
3060 get_key(REQ_PROMPT));
3063 open_view(view, request, OPEN_DEFAULT);
3066 case REQ_VIEW_STAGE:
3067 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3068 report("No stage content, press %s to open the status view and choose file",
3069 get_key(REQ_VIEW_STATUS));
3072 open_view(view, request, OPEN_DEFAULT);
3075 case REQ_VIEW_STATUS:
3076 if (opt_is_inside_work_tree == FALSE) {
3077 report("The status view requires a working tree");
3080 open_view(view, request, OPEN_DEFAULT);
3088 open_view(view, request, OPEN_DEFAULT);
3093 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3095 if ((view == VIEW(REQ_VIEW_DIFF) &&
3096 view->parent == VIEW(REQ_VIEW_MAIN)) ||
3097 (view == VIEW(REQ_VIEW_DIFF) &&
3098 view->parent == VIEW(REQ_VIEW_BLAME)) ||
3099 (view == VIEW(REQ_VIEW_STAGE) &&
3100 view->parent == VIEW(REQ_VIEW_STATUS)) ||
3101 (view == VIEW(REQ_VIEW_BLOB) &&
3102 view->parent == VIEW(REQ_VIEW_TREE))) {
3105 view = view->parent;
3106 line = view->lineno;
3107 move_view(view, request);
3108 if (view_is_displayed(view))
3109 update_view_title(view);
3110 if (line != view->lineno)
3111 view->ops->request(view, REQ_ENTER,
3112 &view->line[view->lineno]);
3115 move_view(view, request);
3121 int nviews = displayed_views();
3122 int next_view = (current_view + 1) % nviews;
3124 if (next_view == current_view) {
3125 report("Only one view is displayed");
3129 current_view = next_view;
3130 /* Blur out the title of the previous view. */
3131 update_view_title(view);
3136 report("Refreshing is not yet supported for the %s view", view->name);
3140 if (displayed_views() == 2)
3141 open_view(view, VIEW_REQ(view), OPEN_DEFAULT);
3144 case REQ_TOGGLE_LINENO:
3145 toggle_view_option(&opt_line_number, "line numbers");
3148 case REQ_TOGGLE_DATE:
3149 toggle_view_option(&opt_date, "date display");
3152 case REQ_TOGGLE_AUTHOR:
3153 toggle_view_option(&opt_author, "author display");
3156 case REQ_TOGGLE_REV_GRAPH:
3157 toggle_view_option(&opt_rev_graph, "revision graph display");
3160 case REQ_TOGGLE_REFS:
3161 toggle_view_option(&opt_show_refs, "reference display");
3165 case REQ_SEARCH_BACK:
3166 search_view(view, request);
3171 find_next(view, request);
3174 case REQ_STOP_LOADING:
3175 for (i = 0; i < ARRAY_SIZE(views); i++) {
3178 report("Stopped loading the %s view", view->name),
3179 end_update(view, TRUE);
3183 case REQ_SHOW_VERSION:
3184 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3187 case REQ_SCREEN_RESIZE:
3190 case REQ_SCREEN_REDRAW:
3191 redraw_display(TRUE);
3195 report("Nothing to edit");
3199 report("Nothing to enter");
3202 case REQ_VIEW_CLOSE:
3203 /* XXX: Mark closed views by letting view->parent point to the
3204 * view itself. Parents to closed view should never be
3207 view->parent->parent != view->parent) {
3208 memset(display, 0, sizeof(display));
3210 display[current_view] = view->parent;
3211 view->parent = view;
3213 redraw_display(FALSE);
3222 report("Unknown key, press 'h' for help");
3235 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3237 char *text = line->data;
3239 if (opt_line_number && draw_lineno(view, lineno))
3242 draw_text(view, line->type, text, TRUE);
3247 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3249 const char *describe_argv[] = { "git", "describe", commit_id, NULL };
3250 char refbuf[SIZEOF_STR];
3253 if (run_io_buf(describe_argv, refbuf, sizeof(refbuf)))
3254 ref = chomp_string(refbuf);
3259 /* This is the only fatal call, since it can "corrupt" the buffer. */
3260 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
3267 add_pager_refs(struct view *view, struct line *line)
3269 char buf[SIZEOF_STR];
3270 char *commit_id = (char *)line->data + STRING_SIZE("commit ");
3272 size_t bufpos = 0, refpos = 0;
3273 const char *sep = "Refs: ";
3274 bool is_tag = FALSE;
3276 assert(line->type == LINE_COMMIT);
3278 refs = get_refs(commit_id);
3280 if (view == VIEW(REQ_VIEW_DIFF))
3281 goto try_add_describe_ref;
3286 struct ref *ref = refs[refpos];
3287 const char *fmt = ref->tag ? "%s[%s]" :
3288 ref->remote ? "%s<%s>" : "%s%s";
3290 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
3295 } while (refs[refpos++]->next);
3297 if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
3298 try_add_describe_ref:
3299 /* Add <tag>-g<commit_id> "fake" reference. */
3300 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
3307 add_line_text(view, buf, LINE_PP_REFS);
3311 pager_read(struct view *view, char *data)
3318 line = add_line_text(view, data, get_line_type(data));
3322 if (line->type == LINE_COMMIT &&
3323 (view == VIEW(REQ_VIEW_DIFF) ||
3324 view == VIEW(REQ_VIEW_LOG)))
3325 add_pager_refs(view, line);
3331 pager_request(struct view *view, enum request request, struct line *line)
3335 if (request != REQ_ENTER)
3338 if (line->type == LINE_COMMIT &&
3339 (view == VIEW(REQ_VIEW_LOG) ||
3340 view == VIEW(REQ_VIEW_PAGER))) {
3341 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3345 /* Always scroll the view even if it was split. That way
3346 * you can use Enter to scroll through the log view and
3347 * split open each commit diff. */
3348 scroll_view(view, REQ_SCROLL_LINE_DOWN);
3350 /* FIXME: A minor workaround. Scrolling the view will call report("")
3351 * but if we are scrolling a non-current view this won't properly
3352 * update the view title. */
3354 update_view_title(view);
3360 pager_grep(struct view *view, struct line *line)
3363 char *text = line->data;
3368 if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
3375 pager_select(struct view *view, struct line *line)
3377 if (line->type == LINE_COMMIT) {
3378 char *text = (char *)line->data + STRING_SIZE("commit ");
3380 if (view != VIEW(REQ_VIEW_PAGER))
3381 string_copy_rev(view->ref, text);
3382 string_copy_rev(ref_commit, text);
3386 static struct view_ops pager_ops = {
3397 static const char *log_argv[SIZEOF_ARG] = {
3398 "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3402 log_request(struct view *view, enum request request, struct line *line)
3407 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
3410 return pager_request(view, request, line);
3414 static struct view_ops log_ops = {
3425 static const char *diff_argv[SIZEOF_ARG] = {
3426 "git", "show", "--pretty=fuller", "--no-color", "--root",
3427 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
3430 static struct view_ops diff_ops = {
3446 help_open(struct view *view)
3448 int lines = ARRAY_SIZE(req_info) + 2;
3451 if (view->lines > 0)
3454 for (i = 0; i < ARRAY_SIZE(req_info); i++)
3455 if (!req_info[i].request)
3458 lines += run_requests + 1;
3460 view->line = calloc(lines, sizeof(*view->line));
3464 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
3466 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
3469 if (req_info[i].request == REQ_NONE)
3472 if (!req_info[i].request) {
3473 add_line_text(view, "", LINE_DEFAULT);
3474 add_line_text(view, req_info[i].help, LINE_DEFAULT);
3478 key = get_key(req_info[i].request);
3480 key = "(no key defined)";
3482 add_line_format(view, LINE_DEFAULT, " %-25s %s",
3483 key, req_info[i].help);
3487 add_line_text(view, "", LINE_DEFAULT);
3488 add_line_text(view, "External commands:", LINE_DEFAULT);
3491 for (i = 0; i < run_requests; i++) {
3492 struct run_request *req = get_run_request(REQ_NONE + i + 1);
3494 char cmd[SIZEOF_STR];
3501 key = get_key_name(req->key);
3503 key = "(no key defined)";
3505 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
3506 if (!string_format_from(cmd, &bufpos, "%s%s",
3507 argc ? " " : "", req->argv[argc]))
3510 add_line_format(view, LINE_DEFAULT, " %-10s %-14s `%s`",
3511 keymap_table[req->keymap].name, key, cmd);
3517 static struct view_ops help_ops = {
3533 struct tree_stack_entry {
3534 struct tree_stack_entry *prev; /* Entry below this in the stack */
3535 unsigned long lineno; /* Line number to restore */
3536 char *name; /* Position of name in opt_path */
3539 /* The top of the path stack. */
3540 static struct tree_stack_entry *tree_stack = NULL;
3541 unsigned long tree_lineno = 0;
3544 pop_tree_stack_entry(void)
3546 struct tree_stack_entry *entry = tree_stack;
3548 tree_lineno = entry->lineno;
3550 tree_stack = entry->prev;
3555 push_tree_stack_entry(const char *name, unsigned long lineno)
3557 struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
3558 size_t pathlen = strlen(opt_path);
3563 entry->prev = tree_stack;
3564 entry->name = opt_path + pathlen;
3567 if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
3568 pop_tree_stack_entry();
3572 /* Move the current line to the first tree entry. */
3574 entry->lineno = lineno;
3577 /* Parse output from git-ls-tree(1):
3579 * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile
3580 * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README
3581 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
3582 * 100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38 web.conf
3585 #define SIZEOF_TREE_ATTR \
3586 STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t")
3588 #define TREE_UP_FORMAT "040000 tree %s\t.."
3591 tree_path(struct line *line)
3593 const char *path = line->data;
3595 return path + SIZEOF_TREE_ATTR;
3599 tree_compare_entry(struct line *line1, struct line *line2)
3601 if (line1->type != line2->type)
3602 return line1->type == LINE_TREE_DIR ? -1 : 1;
3603 return strcmp(tree_path(line1), tree_path(line2));
3607 tree_read(struct view *view, char *text)
3609 size_t textlen = text ? strlen(text) : 0;
3610 struct line *entry, *line;
3611 enum line_type type;
3615 if (textlen <= SIZEOF_TREE_ATTR)
3618 type = text[STRING_SIZE("100644 ")] == 't'
3619 ? LINE_TREE_DIR : LINE_TREE_FILE;
3621 if (view->lines == 0 &&
3622 !add_line_format(view, LINE_DEFAULT, "Directory path /%s", opt_path))
3625 /* Strip the path part ... */
3627 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
3628 size_t striplen = strlen(opt_path);
3629 char *path = text + SIZEOF_TREE_ATTR;
3631 if (pathlen > striplen)
3632 memmove(path, path + striplen,
3633 pathlen - striplen + 1);
3635 /* Insert "link" to parent directory. */
3636 if (view->lines == 1 &&
3637 !add_line_format(view, LINE_TREE_DIR, TREE_UP_FORMAT, view->ref))
3641 entry = add_line_text(view, text, type);
3646 /* Skip "Directory ..." and ".." line. */
3647 for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
3648 if (tree_compare_entry(line, entry) <= 0)
3651 memmove(line + 1, line, (entry - line) * sizeof(*entry));
3655 for (; line <= entry; line++)
3656 line->dirty = line->cleareol = 1;
3660 if (tree_lineno > view->lineno) {
3661 view->lineno = tree_lineno;
3671 char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
3672 int fd = mkstemp(file);
3675 report("Failed to create temporary file");
3676 else if (!run_io_append(blob_ops.argv, FORMAT_ALL, fd))
3677 report("Failed to save blob data to file");
3679 open_editor(FALSE, file);
3685 tree_request(struct view *view, enum request request, struct line *line)
3687 enum open_flags flags;
3690 case REQ_VIEW_BLAME:
3691 if (line->type != LINE_TREE_FILE) {
3692 report("Blame only supported for files");
3696 string_copy(opt_ref, view->vid);
3700 if (line->type != LINE_TREE_FILE) {
3701 report("Edit only supported for files");
3702 } else if (!is_head_commit(view->vid)) {
3705 open_editor(TRUE, opt_file);
3709 case REQ_TREE_PARENT:
3711 /* quit view if at top of tree */
3712 return REQ_VIEW_CLOSE;
3715 line = &view->line[1];
3725 /* Cleanup the stack if the tree view is at a different tree. */
3726 while (!*opt_path && tree_stack)
3727 pop_tree_stack_entry();
3729 switch (line->type) {
3731 /* Depending on whether it is a subdir or parent (updir?) link
3732 * mangle the path buffer. */
3733 if (line == &view->line[1] && *opt_path) {
3734 pop_tree_stack_entry();
3737 const char *basename = tree_path(line);
3739 push_tree_stack_entry(basename, view->lineno);
3742 /* Trees and subtrees share the same ID, so they are not not
3743 * unique like blobs. */
3744 flags = OPEN_RELOAD;
3745 request = REQ_VIEW_TREE;
3748 case LINE_TREE_FILE:
3749 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
3750 request = REQ_VIEW_BLOB;
3757 open_view(view, request, flags);
3758 if (request == REQ_VIEW_TREE) {
3759 view->lineno = tree_lineno;
3766 tree_select(struct view *view, struct line *line)
3768 char *text = (char *)line->data + STRING_SIZE("100644 blob ");
3770 if (line->type == LINE_TREE_FILE) {
3771 string_copy_rev(ref_blob, text);
3772 string_format(opt_file, "%s%s", opt_path, tree_path(line));
3774 } else if (line->type != LINE_TREE_DIR) {
3778 string_copy_rev(view->ref, text);
3781 static const char *tree_argv[SIZEOF_ARG] = {
3782 "git", "ls-tree", "%(commit)", "%(directory)", NULL
3785 static struct view_ops tree_ops = {
3797 blob_read(struct view *view, char *line)
3801 return add_line_text(view, line, LINE_DEFAULT) != NULL;
3805 blob_request(struct view *view, enum request request, struct line *line)
3812 return pager_request(view, request, line);
3816 static const char *blob_argv[SIZEOF_ARG] = {
3817 "git", "cat-file", "blob", "%(blob)", NULL
3820 static struct view_ops blob_ops = {
3834 * Loading the blame view is a two phase job:
3836 * 1. File content is read either using opt_file from the
3837 * filesystem or using git-cat-file.
3838 * 2. Then blame information is incrementally added by
3839 * reading output from git-blame.
3842 static const char *blame_head_argv[] = {
3843 "git", "blame", "--incremental", "--", "%(file)", NULL
3846 static const char *blame_ref_argv[] = {
3847 "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
3850 static const char *blame_cat_file_argv[] = {
3851 "git", "cat-file", "blob", "%(ref):%(file)", NULL
3854 struct blame_commit {
3855 char id[SIZEOF_REV]; /* SHA1 ID. */
3856 char title[128]; /* First line of the commit message. */
3857 char author[75]; /* Author of the commit. */
3858 struct tm time; /* Date from the author ident. */
3859 char filename[128]; /* Name of file. */
3863 struct blame_commit *commit;
3868 blame_open(struct view *view)
3870 if (*opt_ref || !io_open(&view->io, opt_file)) {
3871 if (!run_io_rd(&view->io, blame_cat_file_argv, FORMAT_ALL))
3875 setup_update(view, opt_file);
3876 string_format(view->ref, "%s ...", opt_file);
3881 static struct blame_commit *
3882 get_blame_commit(struct view *view, const char *id)
3886 for (i = 0; i < view->lines; i++) {
3887 struct blame *blame = view->line[i].data;
3892 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
3893 return blame->commit;
3897 struct blame_commit *commit = calloc(1, sizeof(*commit));
3900 string_ncopy(commit->id, id, SIZEOF_REV);
3906 parse_number(const char **posref, size_t *number, size_t min, size_t max)
3908 const char *pos = *posref;
3911 pos = strchr(pos + 1, ' ');
3912 if (!pos || !isdigit(pos[1]))
3914 *number = atoi(pos + 1);
3915 if (*number < min || *number > max)
3922 static struct blame_commit *
3923 parse_blame_commit(struct view *view, const char *text, int *blamed)
3925 struct blame_commit *commit;
3926 struct blame *blame;
3927 const char *pos = text + SIZEOF_REV - 1;
3931 if (strlen(text) <= SIZEOF_REV || *pos != ' ')
3934 if (!parse_number(&pos, &lineno, 1, view->lines) ||
3935 !parse_number(&pos, &group, 1, view->lines - lineno + 1))
3938 commit = get_blame_commit(view, text);
3944 struct line *line = &view->line[lineno + group - 1];
3947 blame->commit = commit;
3955 blame_read_file(struct view *view, const char *line, bool *read_file)
3958 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
3961 if (view->lines == 0 && !view->parent)
3962 die("No blame exist for %s", view->vid);
3964 if (view->lines == 0 || !run_io_rd(&io, argv, FORMAT_ALL)) {
3965 report("Failed to load blame data");
3969 done_io(view->pipe);
3975 size_t linelen = strlen(line);
3976 struct blame *blame = malloc(sizeof(*blame) + linelen);
3978 blame->commit = NULL;
3979 strncpy(blame->text, line, linelen);
3980 blame->text[linelen] = 0;
3981 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
3986 match_blame_header(const char *name, char **line)
3988 size_t namelen = strlen(name);
3989 bool matched = !strncmp(name, *line, namelen);
3998 blame_read(struct view *view, char *line)
4000 static struct blame_commit *commit = NULL;
4001 static int blamed = 0;
4002 static time_t author_time;
4003 static bool read_file = TRUE;
4006 return blame_read_file(view, line, &read_file);
4013 string_format(view->ref, "%s", view->vid);
4014 if (view_is_displayed(view)) {
4015 update_view_title(view);
4016 redraw_view_from(view, 0);
4022 commit = parse_blame_commit(view, line, &blamed);
4023 string_format(view->ref, "%s %2d%%", view->vid,
4024 blamed * 100 / view->lines);
4026 } else if (match_blame_header("author ", &line)) {
4027 string_ncopy(commit->author, line, strlen(line));
4029 } else if (match_blame_header("author-time ", &line)) {
4030 author_time = (time_t) atol(line);
4032 } else if (match_blame_header("author-tz ", &line)) {
4035 tz = ('0' - line[1]) * 60 * 60 * 10;
4036 tz += ('0' - line[2]) * 60 * 60;
4037 tz += ('0' - line[3]) * 60;
4038 tz += ('0' - line[4]) * 60;
4044 gmtime_r(&author_time, &commit->time);
4046 } else if (match_blame_header("summary ", &line)) {
4047 string_ncopy(commit->title, line, strlen(line));
4049 } else if (match_blame_header("filename ", &line)) {
4050 string_ncopy(commit->filename, line, strlen(line));
4058 blame_draw(struct view *view, struct line *line, unsigned int lineno)
4060 struct blame *blame = line->data;
4061 struct tm *time = NULL;
4062 const char *id = NULL, *author = NULL;
4064 if (blame->commit && *blame->commit->filename) {
4065 id = blame->commit->id;
4066 author = blame->commit->author;
4067 time = &blame->commit->time;
4070 if (opt_date && draw_date(view, time))
4074 draw_field(view, LINE_MAIN_AUTHOR, author, opt_author_cols, TRUE))
4077 if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
4080 if (draw_lineno(view, lineno))
4083 draw_text(view, LINE_DEFAULT, blame->text, TRUE);
4088 check_blame_commit(struct blame *blame)
4091 report("Commit data not loaded yet");
4092 else if (!strcmp(blame->commit->id, NULL_ID))
4093 report("No commit exist for the selected line");
4100 blame_request(struct view *view, enum request request, struct line *line)
4102 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4103 struct blame *blame = line->data;
4106 case REQ_VIEW_BLAME:
4107 if (check_blame_commit(blame)) {
4108 string_copy(opt_ref, blame->commit->id);
4109 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4114 if (!blame->commit) {
4115 report("No commit loaded yet");
4119 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
4120 !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
4123 if (!strcmp(blame->commit->id, NULL_ID)) {
4124 struct view *diff = VIEW(REQ_VIEW_DIFF);
4125 const char *diff_index_argv[] = {
4126 "git", "diff-index", "--root", "--cached",
4127 "--patch-with-stat", "-C", "-M",
4128 "HEAD", "--", view->vid, NULL
4131 if (!prepare_update(diff, diff_index_argv, NULL, FORMAT_DASH)) {
4132 report("Failed to allocate diff command");
4135 flags |= OPEN_PREPARED;
4138 open_view(view, REQ_VIEW_DIFF, flags);
4149 blame_grep(struct view *view, struct line *line)
4151 struct blame *blame = line->data;
4152 struct blame_commit *commit = blame->commit;
4155 #define MATCH(text, on) \
4156 (on && *text && regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4159 char buf[DATE_COLS + 1];
4161 if (MATCH(commit->title, 1) ||
4162 MATCH(commit->author, opt_author) ||
4163 MATCH(commit->id, opt_date))
4166 if (strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time) &&
4171 return MATCH(blame->text, 1);
4177 blame_select(struct view *view, struct line *line)
4179 struct blame *blame = line->data;
4180 struct blame_commit *commit = blame->commit;
4185 if (!strcmp(commit->id, NULL_ID))
4186 string_ncopy(ref_commit, "HEAD", 4);
4188 string_copy_rev(ref_commit, commit->id);
4191 static struct view_ops blame_ops = {
4210 char rev[SIZEOF_REV];
4211 char name[SIZEOF_STR];
4215 char rev[SIZEOF_REV];
4216 char name[SIZEOF_STR];
4220 static char status_onbranch[SIZEOF_STR];
4221 static struct status stage_status;
4222 static enum line_type stage_line_type;
4223 static size_t stage_chunks;
4224 static int *stage_chunk;
4226 /* This should work even for the "On branch" line. */
4228 status_has_none(struct view *view, struct line *line)
4230 return line < view->line + view->lines && !line[1].data;
4233 /* Get fields from the diff line:
4234 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
4237 status_get_diff(struct status *file, const char *buf, size_t bufsize)
4239 const char *old_mode = buf + 1;
4240 const char *new_mode = buf + 8;
4241 const char *old_rev = buf + 15;
4242 const char *new_rev = buf + 56;
4243 const char *status = buf + 97;
4246 old_mode[-1] != ':' ||
4247 new_mode[-1] != ' ' ||
4248 old_rev[-1] != ' ' ||
4249 new_rev[-1] != ' ' ||
4253 file->status = *status;
4255 string_copy_rev(file->old.rev, old_rev);
4256 string_copy_rev(file->new.rev, new_rev);
4258 file->old.mode = strtoul(old_mode, NULL, 8);
4259 file->new.mode = strtoul(new_mode, NULL, 8);
4261 file->old.name[0] = file->new.name[0] = 0;
4267 status_run(struct view *view, const char *argv[], char status, enum line_type type)
4269 struct status *file = NULL;
4270 struct status *unmerged = NULL;
4274 if (!run_io(&io, argv, NULL, IO_RD))
4277 add_line_data(view, NULL, type);
4279 while ((buf = io_get(&io, 0, TRUE))) {
4281 file = calloc(1, sizeof(*file));
4282 if (!file || !add_line_data(view, file, type))
4286 /* Parse diff info part. */
4288 file->status = status;
4290 string_copy(file->old.rev, NULL_ID);
4292 } else if (!file->status) {
4293 if (!status_get_diff(file, buf, strlen(buf)))
4296 buf = io_get(&io, 0, TRUE);
4300 /* Collapse all 'M'odified entries that follow a
4301 * associated 'U'nmerged entry. */
4302 if (file->status == 'U') {
4305 } else if (unmerged) {
4306 int collapse = !strcmp(buf, unmerged->new.name);
4317 /* Grab the old name for rename/copy. */
4318 if (!*file->old.name &&
4319 (file->status == 'R' || file->status == 'C')) {
4320 string_ncopy(file->old.name, buf, strlen(buf));
4322 buf = io_get(&io, 0, TRUE);
4327 /* git-ls-files just delivers a NUL separated list of
4328 * file names similar to the second half of the
4329 * git-diff-* output. */
4330 string_ncopy(file->new.name, buf, strlen(buf));
4331 if (!*file->old.name)
4332 string_copy(file->old.name, file->new.name);
4336 if (io_error(&io)) {
4342 if (!view->line[view->lines - 1].data)
4343 add_line_data(view, NULL, LINE_STAT_NONE);
4349 /* Don't show unmerged entries in the staged section. */
4350 static const char *status_diff_index_argv[] = {
4351 "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
4352 "--cached", "-M", "HEAD", NULL
4355 static const char *status_diff_files_argv[] = {
4356 "git", "diff-files", "-z", NULL
4359 static const char *status_list_other_argv[] = {
4360 "git", "ls-files", "-z", "--others", "--exclude-standard", NULL
4363 static const char *status_list_no_head_argv[] = {
4364 "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
4367 static const char *update_index_argv[] = {
4368 "git", "update-index", "-q", "--unmerged", "--refresh", NULL
4371 /* First parse staged info using git-diff-index(1), then parse unstaged
4372 * info using git-diff-files(1), and finally untracked files using
4373 * git-ls-files(1). */
4375 status_open(struct view *view)
4377 unsigned long prev_lineno = view->lineno;
4381 add_line_data(view, NULL, LINE_STAT_HEAD);
4382 if (is_initial_commit())
4383 string_copy(status_onbranch, "Initial commit");
4384 else if (!*opt_head)
4385 string_copy(status_onbranch, "Not currently on any branch");
4386 else if (!string_format(status_onbranch, "On branch %s", opt_head))
4389 run_io_bg(update_index_argv);
4391 if (is_initial_commit()) {
4392 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
4394 } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
4398 if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
4399 !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
4402 /* If all went well restore the previous line number to stay in
4403 * the context or select a line with something that can be
4405 if (prev_lineno >= view->lines)
4406 prev_lineno = view->lines - 1;
4407 while (prev_lineno < view->lines && !view->line[prev_lineno].data)
4409 while (prev_lineno > 0 && !view->line[prev_lineno].data)
4412 /* If the above fails, always skip the "On branch" line. */
4413 if (prev_lineno < view->lines)
4414 view->lineno = prev_lineno;
4418 if (view->lineno < view->offset)
4419 view->offset = view->lineno;
4420 else if (view->offset + view->height <= view->lineno)
4421 view->offset = view->lineno - view->height + 1;
4427 status_draw(struct view *view, struct line *line, unsigned int lineno)
4429 struct status *status = line->data;
4430 enum line_type type;
4434 switch (line->type) {
4435 case LINE_STAT_STAGED:
4436 type = LINE_STAT_SECTION;
4437 text = "Changes to be committed:";
4440 case LINE_STAT_UNSTAGED:
4441 type = LINE_STAT_SECTION;
4442 text = "Changed but not updated:";
4445 case LINE_STAT_UNTRACKED:
4446 type = LINE_STAT_SECTION;
4447 text = "Untracked files:";
4450 case LINE_STAT_NONE:
4451 type = LINE_DEFAULT;
4452 text = " (no files)";
4455 case LINE_STAT_HEAD:
4456 type = LINE_STAT_HEAD;
4457 text = status_onbranch;
4464 static char buf[] = { '?', ' ', ' ', ' ', 0 };
4466 buf[0] = status->status;
4467 if (draw_text(view, line->type, buf, TRUE))
4469 type = LINE_DEFAULT;
4470 text = status->new.name;
4473 draw_text(view, type, text, TRUE);
4478 status_enter(struct view *view, struct line *line)
4480 struct status *status = line->data;
4481 const char *oldpath = status ? status->old.name : NULL;
4482 /* Diffs for unmerged entries are empty when passing the new
4483 * path, so leave it empty. */
4484 const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
4486 enum open_flags split;
4487 struct view *stage = VIEW(REQ_VIEW_STAGE);
4489 if (line->type == LINE_STAT_NONE ||
4490 (!status && line[1].type == LINE_STAT_NONE)) {
4491 report("No file to diff");
4495 switch (line->type) {
4496 case LINE_STAT_STAGED:
4497 if (is_initial_commit()) {
4498 const char *no_head_diff_argv[] = {
4499 "git", "diff", "--no-color", "--patch-with-stat",
4500 "--", "/dev/null", newpath, NULL
4503 if (!prepare_update(stage, no_head_diff_argv, opt_cdup, FORMAT_DASH))
4506 const char *index_show_argv[] = {
4507 "git", "diff-index", "--root", "--patch-with-stat",
4508 "-C", "-M", "--cached", "HEAD", "--",
4509 oldpath, newpath, NULL
4512 if (!prepare_update(stage, index_show_argv, opt_cdup, FORMAT_DASH))
4517 info = "Staged changes to %s";
4519 info = "Staged changes";
4522 case LINE_STAT_UNSTAGED:
4524 const char *files_show_argv[] = {
4525 "git", "diff-files", "--root", "--patch-with-stat",
4526 "-C", "-M", "--", oldpath, newpath, NULL
4529 if (!prepare_update(stage, files_show_argv, opt_cdup, FORMAT_DASH))
4532 info = "Unstaged changes to %s";
4534 info = "Unstaged changes";
4537 case LINE_STAT_UNTRACKED:
4539 report("No file to show");
4543 if (!suffixcmp(status->new.name, -1, "/")) {
4544 report("Cannot display a directory");
4548 if (!prepare_update_file(stage, newpath))
4550 info = "Untracked file %s";
4553 case LINE_STAT_HEAD:
4557 die("line type %d not handled in switch", line->type);
4560 split = view_is_displayed(view) ? OPEN_SPLIT : 0;
4561 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH | split);
4562 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
4564 stage_status = *status;
4566 memset(&stage_status, 0, sizeof(stage_status));
4569 stage_line_type = line->type;
4571 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
4578 status_exists(struct status *status, enum line_type type)
4580 struct view *view = VIEW(REQ_VIEW_STATUS);
4583 for (line = view->line; line < view->line + view->lines; line++) {
4584 struct status *pos = line->data;
4586 if (line->type != type)
4588 if (!pos && (!status || !status->status))
4590 if (pos && !strcmp(status->new.name, pos->new.name))
4599 status_update_prepare(struct io *io, enum line_type type)
4601 const char *staged_argv[] = {
4602 "git", "update-index", "-z", "--index-info", NULL
4604 const char *others_argv[] = {
4605 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
4609 case LINE_STAT_STAGED:
4610 return run_io(io, staged_argv, opt_cdup, IO_WR);
4612 case LINE_STAT_UNSTAGED:
4613 return run_io(io, others_argv, opt_cdup, IO_WR);
4615 case LINE_STAT_UNTRACKED:
4616 return run_io(io, others_argv, NULL, IO_WR);
4619 die("line type %d not handled in switch", type);
4625 status_update_write(struct io *io, struct status *status, enum line_type type)
4627 char buf[SIZEOF_STR];
4631 case LINE_STAT_STAGED:
4632 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
4635 status->old.name, 0))
4639 case LINE_STAT_UNSTAGED:
4640 case LINE_STAT_UNTRACKED:
4641 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
4646 die("line type %d not handled in switch", type);
4649 return io_write(io, buf, bufsize);
4653 status_update_file(struct status *status, enum line_type type)
4658 if (!status_update_prepare(&io, type))
4661 result = status_update_write(&io, status, type);
4667 status_update_files(struct view *view, struct line *line)
4671 struct line *pos = view->line + view->lines;
4675 if (!status_update_prepare(&io, line->type))
4678 for (pos = line; pos < view->line + view->lines && pos->data; pos++)
4681 for (file = 0, done = 0; result && file < files; line++, file++) {
4682 int almost_done = file * 100 / files;
4684 if (almost_done > done) {
4686 string_format(view->ref, "updating file %u of %u (%d%% done)",
4688 update_view_title(view);
4690 result = status_update_write(&io, line->data, line->type);
4698 status_update(struct view *view)
4700 struct line *line = &view->line[view->lineno];
4702 assert(view->lines);
4705 /* This should work even for the "On branch" line. */
4706 if (line < view->line + view->lines && !line[1].data) {
4707 report("Nothing to update");
4711 if (!status_update_files(view, line + 1)) {
4712 report("Failed to update file status");
4716 } else if (!status_update_file(line->data, line->type)) {
4717 report("Failed to update file status");
4725 status_revert(struct status *status, enum line_type type, bool has_none)
4727 if (!status || type != LINE_STAT_UNSTAGED) {
4728 if (type == LINE_STAT_STAGED) {
4729 report("Cannot revert changes to staged files");
4730 } else if (type == LINE_STAT_UNTRACKED) {
4731 report("Cannot revert changes to untracked files");
4732 } else if (has_none) {
4733 report("Nothing to revert");
4735 report("Cannot revert changes to multiple files");
4740 const char *checkout_argv[] = {
4741 "git", "checkout", "--", status->old.name, NULL
4744 if (!prompt_yesno("Are you sure you want to overwrite any changes?"))
4746 return run_io_fg(checkout_argv, opt_cdup);
4751 status_request(struct view *view, enum request request, struct line *line)
4753 struct status *status = line->data;
4756 case REQ_STATUS_UPDATE:
4757 if (!status_update(view))
4761 case REQ_STATUS_REVERT:
4762 if (!status_revert(status, line->type, status_has_none(view, line)))
4766 case REQ_STATUS_MERGE:
4767 if (!status || status->status != 'U') {
4768 report("Merging only possible for files with unmerged status ('U').");
4771 open_mergetool(status->new.name);
4777 if (status->status == 'D') {
4778 report("File has been deleted.");
4782 open_editor(status->status != '?', status->new.name);
4785 case REQ_VIEW_BLAME:
4787 string_copy(opt_file, status->new.name);
4793 /* After returning the status view has been split to
4794 * show the stage view. No further reloading is
4796 status_enter(view, line);
4800 /* Simply reload the view. */
4807 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
4813 status_select(struct view *view, struct line *line)
4815 struct status *status = line->data;
4816 char file[SIZEOF_STR] = "all files";
4820 if (status && !string_format(file, "'%s'", status->new.name))
4823 if (!status && line[1].type == LINE_STAT_NONE)
4826 switch (line->type) {
4827 case LINE_STAT_STAGED:
4828 text = "Press %s to unstage %s for commit";
4831 case LINE_STAT_UNSTAGED:
4832 text = "Press %s to stage %s for commit";
4835 case LINE_STAT_UNTRACKED:
4836 text = "Press %s to stage %s for addition";
4839 case LINE_STAT_HEAD:
4840 case LINE_STAT_NONE:
4841 text = "Nothing to update";
4845 die("line type %d not handled in switch", line->type);
4848 if (status && status->status == 'U') {
4849 text = "Press %s to resolve conflict in %s";
4850 key = get_key(REQ_STATUS_MERGE);
4853 key = get_key(REQ_STATUS_UPDATE);
4856 string_format(view->ref, text, key, file);
4860 status_grep(struct view *view, struct line *line)
4862 struct status *status = line->data;
4863 enum { S_STATUS, S_NAME, S_END } state;
4870 for (state = S_STATUS; state < S_END; state++) {
4874 case S_NAME: text = status->new.name; break;
4876 buf[0] = status->status;
4884 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4891 static struct view_ops status_ops = {
4904 stage_diff_write(struct io *io, struct line *line, struct line *end)
4906 while (line < end) {
4907 if (!io_write(io, line->data, strlen(line->data)) ||
4908 !io_write(io, "\n", 1))
4911 if (line->type == LINE_DIFF_CHUNK ||
4912 line->type == LINE_DIFF_HEADER)
4919 static struct line *
4920 stage_diff_find(struct view *view, struct line *line, enum line_type type)
4922 for (; view->line < line; line--)
4923 if (line->type == type)
4930 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
4932 const char *apply_argv[SIZEOF_ARG] = {
4933 "git", "apply", "--whitespace=nowarn", NULL
4935 struct line *diff_hdr;
4939 diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
4944 apply_argv[argc++] = "--cached";
4945 if (revert || stage_line_type == LINE_STAT_STAGED)
4946 apply_argv[argc++] = "-R";
4947 apply_argv[argc++] = "-";
4948 apply_argv[argc++] = NULL;
4949 if (!run_io(&io, apply_argv, opt_cdup, IO_WR))
4952 if (!stage_diff_write(&io, diff_hdr, chunk) ||
4953 !stage_diff_write(&io, chunk, view->line + view->lines))
4957 run_io_bg(update_index_argv);
4959 return chunk ? TRUE : FALSE;
4963 stage_update(struct view *view, struct line *line)
4965 struct line *chunk = NULL;
4967 if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
4968 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
4971 if (!stage_apply_chunk(view, chunk, FALSE)) {
4972 report("Failed to apply chunk");
4976 } else if (!stage_status.status) {
4977 view = VIEW(REQ_VIEW_STATUS);
4979 for (line = view->line; line < view->line + view->lines; line++)
4980 if (line->type == stage_line_type)
4983 if (!status_update_files(view, line + 1)) {
4984 report("Failed to update files");
4988 } else if (!status_update_file(&stage_status, stage_line_type)) {
4989 report("Failed to update file");
4997 stage_revert(struct view *view, struct line *line)
4999 struct line *chunk = NULL;
5001 if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
5002 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5005 if (!prompt_yesno("Are you sure you want to revert changes?"))
5008 if (!stage_apply_chunk(view, chunk, TRUE)) {
5009 report("Failed to revert chunk");
5015 return status_revert(stage_status.status ? &stage_status : NULL,
5016 stage_line_type, FALSE);
5022 stage_next(struct view *view, struct line *line)
5026 if (!stage_chunks) {
5027 static size_t alloc = 0;
5030 for (line = view->line; line < view->line + view->lines; line++) {
5031 if (line->type != LINE_DIFF_CHUNK)
5034 tmp = realloc_items(stage_chunk, &alloc,
5035 stage_chunks, sizeof(*tmp));
5037 report("Allocation failure");
5042 stage_chunk[stage_chunks++] = line - view->line;
5046 for (i = 0; i < stage_chunks; i++) {
5047 if (stage_chunk[i] > view->lineno) {
5048 do_scroll_view(view, stage_chunk[i] - view->lineno);
5049 report("Chunk %d of %d", i + 1, stage_chunks);
5054 report("No next chunk found");
5058 stage_request(struct view *view, enum request request, struct line *line)
5061 case REQ_STATUS_UPDATE:
5062 if (!stage_update(view, line))
5066 case REQ_STATUS_REVERT:
5067 if (!stage_revert(view, line))
5071 case REQ_STAGE_NEXT:
5072 if (stage_line_type == LINE_STAT_UNTRACKED) {
5073 report("File is untracked; press %s to add",
5074 get_key(REQ_STATUS_UPDATE));
5077 stage_next(view, line);
5081 if (!stage_status.new.name[0])
5083 if (stage_status.status == 'D') {
5084 report("File has been deleted.");
5088 open_editor(stage_status.status != '?', stage_status.new.name);
5092 /* Reload everything ... */
5095 case REQ_VIEW_BLAME:
5096 if (stage_status.new.name[0]) {
5097 string_copy(opt_file, stage_status.new.name);
5103 return pager_request(view, request, line);
5109 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD | OPEN_NOMAXIMIZE);
5111 /* Check whether the staged entry still exists, and close the
5112 * stage view if it doesn't. */
5113 if (!status_exists(&stage_status, stage_line_type))
5114 return REQ_VIEW_CLOSE;
5116 if (stage_line_type == LINE_STAT_UNTRACKED) {
5117 if (!suffixcmp(stage_status.new.name, -1, "/")) {
5118 report("Cannot display a directory");
5122 if (!prepare_update_file(view, stage_status.new.name)) {
5123 report("Failed to open file: %s", strerror(errno));
5127 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
5132 static struct view_ops stage_ops = {
5149 char id[SIZEOF_REV]; /* SHA1 ID. */
5150 char title[128]; /* First line of the commit message. */
5151 char author[75]; /* Author of the commit. */
5152 struct tm time; /* Date from the author ident. */
5153 struct ref **refs; /* Repository references. */
5154 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
5155 size_t graph_size; /* The width of the graph array. */
5156 bool has_parents; /* Rewritten --parents seen. */
5159 /* Size of rev graph with no "padding" columns */
5160 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
5163 struct rev_graph *prev, *next, *parents;
5164 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
5166 struct commit *commit;
5168 unsigned int boundary:1;
5171 /* Parents of the commit being visualized. */
5172 static struct rev_graph graph_parents[4];
5174 /* The current stack of revisions on the graph. */
5175 static struct rev_graph graph_stacks[4] = {
5176 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
5177 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
5178 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
5179 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
5183 graph_parent_is_merge(struct rev_graph *graph)
5185 return graph->parents->size > 1;
5189 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
5191 struct commit *commit = graph->commit;
5193 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
5194 commit->graph[commit->graph_size++] = symbol;
5198 clear_rev_graph(struct rev_graph *graph)
5200 graph->boundary = 0;
5201 graph->size = graph->pos = 0;
5202 graph->commit = NULL;
5203 memset(graph->parents, 0, sizeof(*graph->parents));
5207 done_rev_graph(struct rev_graph *graph)
5209 if (graph_parent_is_merge(graph) &&
5210 graph->pos < graph->size - 1 &&
5211 graph->next->size == graph->size + graph->parents->size - 1) {
5212 size_t i = graph->pos + graph->parents->size - 1;
5214 graph->commit->graph_size = i * 2;
5215 while (i < graph->next->size - 1) {
5216 append_to_rev_graph(graph, ' ');
5217 append_to_rev_graph(graph, '\\');
5222 clear_rev_graph(graph);
5226 push_rev_graph(struct rev_graph *graph, const char *parent)
5230 /* "Collapse" duplicate parents lines.
5232 * FIXME: This needs to also update update the drawn graph but
5233 * for now it just serves as a method for pruning graph lines. */
5234 for (i = 0; i < graph->size; i++)
5235 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
5238 if (graph->size < SIZEOF_REVITEMS) {
5239 string_copy_rev(graph->rev[graph->size++], parent);
5244 get_rev_graph_symbol(struct rev_graph *graph)
5248 if (graph->boundary)
5249 symbol = REVGRAPH_BOUND;
5250 else if (graph->parents->size == 0)
5251 symbol = REVGRAPH_INIT;
5252 else if (graph_parent_is_merge(graph))
5253 symbol = REVGRAPH_MERGE;
5254 else if (graph->pos >= graph->size)
5255 symbol = REVGRAPH_BRANCH;
5257 symbol = REVGRAPH_COMMIT;
5263 draw_rev_graph(struct rev_graph *graph)
5266 chtype separator, line;
5268 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
5269 static struct rev_filler fillers[] = {
5275 chtype symbol = get_rev_graph_symbol(graph);
5276 struct rev_filler *filler;
5279 if (opt_line_graphics)
5280 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
5282 filler = &fillers[DEFAULT];
5284 for (i = 0; i < graph->pos; i++) {
5285 append_to_rev_graph(graph, filler->line);
5286 if (graph_parent_is_merge(graph->prev) &&
5287 graph->prev->pos == i)
5288 filler = &fillers[RSHARP];
5290 append_to_rev_graph(graph, filler->separator);
5293 /* Place the symbol for this revision. */
5294 append_to_rev_graph(graph, symbol);
5296 if (graph->prev->size > graph->size)
5297 filler = &fillers[RDIAG];
5299 filler = &fillers[DEFAULT];
5303 for (; i < graph->size; i++) {
5304 append_to_rev_graph(graph, filler->separator);
5305 append_to_rev_graph(graph, filler->line);
5306 if (graph_parent_is_merge(graph->prev) &&
5307 i < graph->prev->pos + graph->parents->size)
5308 filler = &fillers[RSHARP];
5309 if (graph->prev->size > graph->size)
5310 filler = &fillers[LDIAG];
5313 if (graph->prev->size > graph->size) {
5314 append_to_rev_graph(graph, filler->separator);
5315 if (filler->line != ' ')
5316 append_to_rev_graph(graph, filler->line);
5320 /* Prepare the next rev graph */
5322 prepare_rev_graph(struct rev_graph *graph)
5326 /* First, traverse all lines of revisions up to the active one. */
5327 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
5328 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
5331 push_rev_graph(graph->next, graph->rev[graph->pos]);
5334 /* Interleave the new revision parent(s). */
5335 for (i = 0; !graph->boundary && i < graph->parents->size; i++)
5336 push_rev_graph(graph->next, graph->parents->rev[i]);
5338 /* Lastly, put any remaining revisions. */
5339 for (i = graph->pos + 1; i < graph->size; i++)
5340 push_rev_graph(graph->next, graph->rev[i]);
5344 update_rev_graph(struct view *view, struct rev_graph *graph)
5346 /* If this is the finalizing update ... */
5348 prepare_rev_graph(graph);
5350 /* Graph visualization needs a one rev look-ahead,
5351 * so the first update doesn't visualize anything. */
5352 if (!graph->prev->commit)
5355 if (view->lines > 2)
5356 view->line[view->lines - 3].dirty = 1;
5357 if (view->lines > 1)
5358 view->line[view->lines - 2].dirty = 1;
5359 draw_rev_graph(graph->prev);
5360 done_rev_graph(graph->prev->prev);
5368 static const char *main_argv[SIZEOF_ARG] = {
5369 "git", "log", "--no-color", "--pretty=raw", "--parents",
5370 "--topo-order", "%(head)", NULL
5374 main_draw(struct view *view, struct line *line, unsigned int lineno)
5376 struct commit *commit = line->data;
5378 if (!*commit->author)
5381 if (opt_date && draw_date(view, &commit->time))
5385 draw_field(view, LINE_MAIN_AUTHOR, commit->author, opt_author_cols, TRUE))
5388 if (opt_rev_graph && commit->graph_size &&
5389 draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
5392 if (opt_show_refs && commit->refs) {
5396 enum line_type type;
5398 if (commit->refs[i]->head)
5399 type = LINE_MAIN_HEAD;
5400 else if (commit->refs[i]->ltag)
5401 type = LINE_MAIN_LOCAL_TAG;
5402 else if (commit->refs[i]->tag)
5403 type = LINE_MAIN_TAG;
5404 else if (commit->refs[i]->tracked)
5405 type = LINE_MAIN_TRACKED;
5406 else if (commit->refs[i]->remote)
5407 type = LINE_MAIN_REMOTE;
5409 type = LINE_MAIN_REF;
5411 if (draw_text(view, type, "[", TRUE) ||
5412 draw_text(view, type, commit->refs[i]->name, TRUE) ||
5413 draw_text(view, type, "]", TRUE))
5416 if (draw_text(view, LINE_DEFAULT, " ", TRUE))
5418 } while (commit->refs[i++]->next);
5421 draw_text(view, LINE_DEFAULT, commit->title, TRUE);
5425 /* Reads git log --pretty=raw output and parses it into the commit struct. */
5427 main_read(struct view *view, char *line)
5429 static struct rev_graph *graph = graph_stacks;
5430 enum line_type type;
5431 struct commit *commit;
5436 if (!view->lines && !view->parent)
5437 die("No revisions match the given arguments.");
5438 if (view->lines > 0) {
5439 commit = view->line[view->lines - 1].data;
5440 view->line[view->lines - 1].dirty = 1;
5441 if (!*commit->author) {
5444 graph->commit = NULL;
5447 update_rev_graph(view, graph);
5449 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
5450 clear_rev_graph(&graph_stacks[i]);
5454 type = get_line_type(line);
5455 if (type == LINE_COMMIT) {
5456 commit = calloc(1, sizeof(struct commit));
5460 line += STRING_SIZE("commit ");
5462 graph->boundary = 1;
5466 string_copy_rev(commit->id, line);
5467 commit->refs = get_refs(commit->id);
5468 graph->commit = commit;
5469 add_line_data(view, commit, LINE_MAIN_COMMIT);
5471 while ((line = strchr(line, ' '))) {
5473 push_rev_graph(graph->parents, line);
5474 commit->has_parents = TRUE;
5481 commit = view->line[view->lines - 1].data;
5485 if (commit->has_parents)
5487 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
5492 /* Parse author lines where the name may be empty:
5493 * author <email@address.tld> 1138474660 +0100
5495 char *ident = line + STRING_SIZE("author ");
5496 char *nameend = strchr(ident, '<');
5497 char *emailend = strchr(ident, '>');
5499 if (!nameend || !emailend)
5502 update_rev_graph(view, graph);
5503 graph = graph->next;
5505 *nameend = *emailend = 0;
5506 ident = chomp_string(ident);
5508 ident = chomp_string(nameend + 1);
5513 string_ncopy(commit->author, ident, strlen(ident));
5514 view->line[view->lines - 1].dirty = 1;
5516 /* Parse epoch and timezone */
5517 if (emailend[1] == ' ') {
5518 char *secs = emailend + 2;
5519 char *zone = strchr(secs, ' ');
5520 time_t time = (time_t) atol(secs);
5522 if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
5526 tz = ('0' - zone[1]) * 60 * 60 * 10;
5527 tz += ('0' - zone[2]) * 60 * 60;
5528 tz += ('0' - zone[3]) * 60;
5529 tz += ('0' - zone[4]) * 60;
5537 gmtime_r(&time, &commit->time);
5542 /* Fill in the commit title if it has not already been set. */
5543 if (commit->title[0])
5546 /* Require titles to start with a non-space character at the
5547 * offset used by git log. */
5548 if (strncmp(line, " ", 4))
5551 /* Well, if the title starts with a whitespace character,
5552 * try to be forgiving. Otherwise we end up with no title. */
5553 while (isspace(*line))
5557 /* FIXME: More graceful handling of titles; append "..." to
5558 * shortened titles, etc. */
5560 string_ncopy(commit->title, line, strlen(line));
5561 view->line[view->lines - 1].dirty = 1;
5568 main_request(struct view *view, enum request request, struct line *line)
5570 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
5574 open_view(view, REQ_VIEW_DIFF, flags);
5578 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
5588 grep_refs(struct ref **refs, regex_t *regex)
5596 if (regexec(regex, refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
5598 } while (refs[i++]->next);
5604 main_grep(struct view *view, struct line *line)
5606 struct commit *commit = line->data;
5607 enum { S_TITLE, S_AUTHOR, S_DATE, S_REFS, S_END } state;
5608 char buf[DATE_COLS + 1];
5611 for (state = S_TITLE; state < S_END; state++) {
5615 case S_TITLE: text = commit->title; break;
5619 text = commit->author;
5624 if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
5631 if (grep_refs(commit->refs, view->regex) == TRUE)
5638 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
5646 main_select(struct view *view, struct line *line)
5648 struct commit *commit = line->data;
5650 string_copy_rev(view->ref, commit->id);
5651 string_copy_rev(ref_commit, view->ref);
5654 static struct view_ops main_ops = {
5667 * Unicode / UTF-8 handling
5669 * NOTE: Much of the following code for dealing with unicode is derived from
5670 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
5671 * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
5674 /* I've (over)annotated a lot of code snippets because I am not entirely
5675 * confident that the approach taken by this small UTF-8 interface is correct.
5679 unicode_width(unsigned long c)
5682 (c <= 0x115f /* Hangul Jamo */
5685 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
5687 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
5688 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
5689 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
5690 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
5691 || (c >= 0xffe0 && c <= 0xffe6)
5692 || (c >= 0x20000 && c <= 0x2fffd)
5693 || (c >= 0x30000 && c <= 0x3fffd)))
5697 return opt_tab_size;
5702 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
5703 * Illegal bytes are set one. */
5704 static const unsigned char utf8_bytes[256] = {
5705 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,
5706 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,
5707 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,
5708 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,
5709 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,
5710 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,
5711 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,
5712 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,
5715 /* Decode UTF-8 multi-byte representation into a unicode character. */
5716 static inline unsigned long
5717 utf8_to_unicode(const char *string, size_t length)
5719 unsigned long unicode;
5723 unicode = string[0];
5726 unicode = (string[0] & 0x1f) << 6;
5727 unicode += (string[1] & 0x3f);
5730 unicode = (string[0] & 0x0f) << 12;
5731 unicode += ((string[1] & 0x3f) << 6);
5732 unicode += (string[2] & 0x3f);
5735 unicode = (string[0] & 0x0f) << 18;
5736 unicode += ((string[1] & 0x3f) << 12);
5737 unicode += ((string[2] & 0x3f) << 6);
5738 unicode += (string[3] & 0x3f);
5741 unicode = (string[0] & 0x0f) << 24;
5742 unicode += ((string[1] & 0x3f) << 18);
5743 unicode += ((string[2] & 0x3f) << 12);
5744 unicode += ((string[3] & 0x3f) << 6);
5745 unicode += (string[4] & 0x3f);
5748 unicode = (string[0] & 0x01) << 30;
5749 unicode += ((string[1] & 0x3f) << 24);
5750 unicode += ((string[2] & 0x3f) << 18);
5751 unicode += ((string[3] & 0x3f) << 12);
5752 unicode += ((string[4] & 0x3f) << 6);
5753 unicode += (string[5] & 0x3f);
5756 die("Invalid unicode length");
5759 /* Invalid characters could return the special 0xfffd value but NUL
5760 * should be just as good. */
5761 return unicode > 0xffff ? 0 : unicode;
5764 /* Calculates how much of string can be shown within the given maximum width
5765 * and sets trimmed parameter to non-zero value if all of string could not be
5766 * shown. If the reserve flag is TRUE, it will reserve at least one
5767 * trailing character, which can be useful when drawing a delimiter.
5769 * Returns the number of bytes to output from string to satisfy max_width. */
5771 utf8_length(const char *string, int *width, size_t max_width, int *trimmed, bool reserve)
5773 const char *start = string;
5774 const char *end = strchr(string, '\0');
5775 unsigned char last_bytes = 0;
5776 size_t last_ucwidth = 0;
5781 while (string < end) {
5782 int c = *(unsigned char *) string;
5783 unsigned char bytes = utf8_bytes[c];
5785 unsigned long unicode;
5787 if (string + bytes > end)
5790 /* Change representation to figure out whether
5791 * it is a single- or double-width character. */
5793 unicode = utf8_to_unicode(string, bytes);
5794 /* FIXME: Graceful handling of invalid unicode character. */
5798 ucwidth = unicode_width(unicode);
5800 if (*width > max_width) {
5803 if (reserve && *width == max_width) {
5804 string -= last_bytes;
5805 *width -= last_ucwidth;
5812 last_ucwidth = ucwidth;
5815 return string - start;
5823 /* Whether or not the curses interface has been initialized. */
5824 static bool cursed = FALSE;
5826 /* The status window is used for polling keystrokes. */
5827 static WINDOW *status_win;
5829 static bool status_empty = TRUE;
5831 /* Update status and title window. */
5833 report(const char *msg, ...)
5835 struct view *view = display[current_view];
5841 char buf[SIZEOF_STR];
5844 va_start(args, msg);
5845 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
5846 buf[sizeof(buf) - 1] = 0;
5847 buf[sizeof(buf) - 2] = '.';
5848 buf[sizeof(buf) - 3] = '.';
5849 buf[sizeof(buf) - 4] = '.';
5855 if (!status_empty || *msg) {
5858 va_start(args, msg);
5860 wmove(status_win, 0, 0);
5862 vwprintw(status_win, msg, args);
5863 status_empty = FALSE;
5865 status_empty = TRUE;
5867 wclrtoeol(status_win);
5868 wrefresh(status_win);
5873 update_view_title(view);
5874 update_display_cursor(view);
5877 /* Controls when nodelay should be in effect when polling user input. */
5879 set_nonblocking_input(bool loading)
5881 static unsigned int loading_views;
5883 if ((loading == FALSE && loading_views-- == 1) ||
5884 (loading == TRUE && loading_views++ == 0))
5885 nodelay(status_win, loading);
5893 /* Initialize the curses library */
5894 if (isatty(STDIN_FILENO)) {
5895 cursed = !!initscr();
5898 /* Leave stdin and stdout alone when acting as a pager. */
5899 opt_tty = fopen("/dev/tty", "r+");
5901 die("Failed to open /dev/tty");
5902 cursed = !!newterm(NULL, opt_tty, opt_tty);
5906 die("Failed to initialize curses");
5908 nonl(); /* Tell curses not to do NL->CR/NL on output */
5909 cbreak(); /* Take input chars one at a time, no wait for \n */
5910 noecho(); /* Don't echo input */
5911 leaveok(stdscr, TRUE);
5916 getmaxyx(stdscr, y, x);
5917 status_win = newwin(1, 0, y - 1, 0);
5919 die("Failed to create status window");
5921 /* Enable keyboard mapping */
5922 keypad(status_win, TRUE);
5923 wbkgdset(status_win, get_line_attr(LINE_STATUS));
5925 TABSIZE = opt_tab_size;
5926 if (opt_line_graphics) {
5927 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
5932 prompt_yesno(const char *prompt)
5934 enum { WAIT, STOP, CANCEL } status = WAIT;
5935 bool answer = FALSE;
5937 while (status == WAIT) {
5943 foreach_view (view, i)
5948 mvwprintw(status_win, 0, 0, "%s [Yy]/[Nn]", prompt);
5949 wclrtoeol(status_win);
5951 /* Refresh, accept single keystroke of input */
5952 key = wgetch(status_win);
5976 /* Clear the status window */
5977 status_empty = FALSE;
5984 read_prompt(const char *prompt)
5986 enum { READING, STOP, CANCEL } status = READING;
5987 static char buf[SIZEOF_STR];
5990 while (status == READING) {
5996 foreach_view (view, i)
6001 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
6002 wclrtoeol(status_win);
6004 /* Refresh, accept single keystroke of input */
6005 key = wgetch(status_win);
6010 status = pos ? STOP : CANCEL;
6028 if (pos >= sizeof(buf)) {
6029 report("Input string too long");
6034 buf[pos++] = (char) key;
6038 /* Clear the status window */
6039 status_empty = FALSE;
6042 if (status == CANCEL)
6051 * Repository properties
6055 git_properties(const char **argv, const char *separators,
6056 int (*read_property)(char *, size_t, char *, size_t))
6060 if (init_io_rd(&io, argv, NULL, FORMAT_NONE))
6061 return read_properties(&io, separators, read_property);
6065 static struct ref *refs = NULL;
6066 static size_t refs_alloc = 0;
6067 static size_t refs_size = 0;
6069 /* Id <-> ref store */
6070 static struct ref ***id_refs = NULL;
6071 static size_t id_refs_alloc = 0;
6072 static size_t id_refs_size = 0;
6075 compare_refs(const void *ref1_, const void *ref2_)
6077 const struct ref *ref1 = *(const struct ref **)ref1_;
6078 const struct ref *ref2 = *(const struct ref **)ref2_;
6080 if (ref1->tag != ref2->tag)
6081 return ref2->tag - ref1->tag;
6082 if (ref1->ltag != ref2->ltag)
6083 return ref2->ltag - ref2->ltag;
6084 if (ref1->head != ref2->head)
6085 return ref2->head - ref1->head;
6086 if (ref1->tracked != ref2->tracked)
6087 return ref2->tracked - ref1->tracked;
6088 if (ref1->remote != ref2->remote)
6089 return ref2->remote - ref1->remote;
6090 return strcmp(ref1->name, ref2->name);
6093 static struct ref **
6094 get_refs(const char *id)
6096 struct ref ***tmp_id_refs;
6097 struct ref **ref_list = NULL;
6098 size_t ref_list_alloc = 0;
6099 size_t ref_list_size = 0;
6102 for (i = 0; i < id_refs_size; i++)
6103 if (!strcmp(id, id_refs[i][0]->id))
6106 tmp_id_refs = realloc_items(id_refs, &id_refs_alloc, id_refs_size + 1,
6111 id_refs = tmp_id_refs;
6113 for (i = 0; i < refs_size; i++) {
6116 if (strcmp(id, refs[i].id))
6119 tmp = realloc_items(ref_list, &ref_list_alloc,
6120 ref_list_size + 1, sizeof(*ref_list));
6128 ref_list[ref_list_size] = &refs[i];
6129 /* XXX: The properties of the commit chains ensures that we can
6130 * safely modify the shared ref. The repo references will
6131 * always be similar for the same id. */
6132 ref_list[ref_list_size]->next = 1;
6138 qsort(ref_list, ref_list_size, sizeof(*ref_list), compare_refs);
6139 ref_list[ref_list_size - 1]->next = 0;
6140 id_refs[id_refs_size++] = ref_list;
6147 read_ref(char *id, size_t idlen, char *name, size_t namelen)
6152 bool remote = FALSE;
6153 bool tracked = FALSE;
6154 bool check_replace = FALSE;
6157 if (!prefixcmp(name, "refs/tags/")) {
6158 if (!suffixcmp(name, namelen, "^{}")) {
6161 if (refs_size > 0 && refs[refs_size - 1].ltag == TRUE)
6162 check_replace = TRUE;
6168 namelen -= STRING_SIZE("refs/tags/");
6169 name += STRING_SIZE("refs/tags/");
6171 } else if (!prefixcmp(name, "refs/remotes/")) {
6173 namelen -= STRING_SIZE("refs/remotes/");
6174 name += STRING_SIZE("refs/remotes/");
6175 tracked = !strcmp(opt_remote, name);
6177 } else if (!prefixcmp(name, "refs/heads/")) {
6178 namelen -= STRING_SIZE("refs/heads/");
6179 name += STRING_SIZE("refs/heads/");
6180 head = !strncmp(opt_head, name, namelen);
6182 } else if (!strcmp(name, "HEAD")) {
6183 string_ncopy(opt_head_rev, id, idlen);
6187 if (check_replace && !strcmp(name, refs[refs_size - 1].name)) {
6188 /* it's an annotated tag, replace the previous sha1 with the
6189 * resolved commit id; relies on the fact git-ls-remote lists
6190 * the commit id of an annotated tag right before the commit id
6192 refs[refs_size - 1].ltag = ltag;
6193 string_copy_rev(refs[refs_size - 1].id, id);
6197 refs = realloc_items(refs, &refs_alloc, refs_size + 1, sizeof(*refs));
6201 ref = &refs[refs_size++];
6202 ref->name = malloc(namelen + 1);
6206 strncpy(ref->name, name, namelen);
6207 ref->name[namelen] = 0;
6211 ref->remote = remote;
6212 ref->tracked = tracked;
6213 string_copy_rev(ref->id, id);
6221 static const char *ls_remote_argv[SIZEOF_ARG] = {
6222 "git", "ls-remote", ".", NULL
6224 static bool init = FALSE;
6227 argv_from_env(ls_remote_argv, "TIG_LS_REMOTE");
6234 while (refs_size > 0)
6235 free(refs[--refs_size].name);
6236 while (id_refs_size > 0)
6237 free(id_refs[--id_refs_size]);
6239 return git_properties(ls_remote_argv, "\t", read_ref);
6243 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
6245 if (!strcmp(name, "i18n.commitencoding"))
6246 string_ncopy(opt_encoding, value, valuelen);
6248 if (!strcmp(name, "core.editor"))
6249 string_ncopy(opt_editor, value, valuelen);
6251 /* branch.<head>.remote */
6253 !strncmp(name, "branch.", 7) &&
6254 !strncmp(name + 7, opt_head, strlen(opt_head)) &&
6255 !strcmp(name + 7 + strlen(opt_head), ".remote"))
6256 string_ncopy(opt_remote, value, valuelen);
6258 if (*opt_head && *opt_remote &&
6259 !strncmp(name, "branch.", 7) &&
6260 !strncmp(name + 7, opt_head, strlen(opt_head)) &&
6261 !strcmp(name + 7 + strlen(opt_head), ".merge")) {
6262 size_t from = strlen(opt_remote);
6264 if (!prefixcmp(value, "refs/heads/")) {
6265 value += STRING_SIZE("refs/heads/");
6266 valuelen -= STRING_SIZE("refs/heads/");
6269 if (!string_format_from(opt_remote, &from, "/%s", value))
6277 load_git_config(void)
6279 const char *config_list_argv[] = { "git", GIT_CONFIG, "--list", NULL };
6281 return git_properties(config_list_argv, "=", read_repo_config_option);
6285 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
6287 if (!opt_git_dir[0]) {
6288 string_ncopy(opt_git_dir, name, namelen);
6290 } else if (opt_is_inside_work_tree == -1) {
6291 /* This can be 3 different values depending on the
6292 * version of git being used. If git-rev-parse does not
6293 * understand --is-inside-work-tree it will simply echo
6294 * the option else either "true" or "false" is printed.
6295 * Default to true for the unknown case. */
6296 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
6298 string_ncopy(opt_cdup, name, namelen);
6305 load_repo_info(void)
6307 const char *head_argv[] = {
6308 "git", "symbolic-ref", "HEAD", NULL
6310 const char *rev_parse_argv[] = {
6311 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
6315 if (run_io_buf(head_argv, opt_head, sizeof(opt_head))) {
6316 chomp_string(opt_head);
6317 if (!prefixcmp(opt_head, "refs/heads/")) {
6318 char *offset = opt_head + STRING_SIZE("refs/heads/");
6320 memmove(opt_head, offset, strlen(offset) + 1);
6324 return git_properties(rev_parse_argv, "=", read_repo_info);
6328 read_properties(struct io *io, const char *separators,
6329 int (*read_property)(char *, size_t, char *, size_t))
6337 while (state == OK && (name = io_get(io, '\n', TRUE))) {
6342 name = chomp_string(name);
6343 namelen = strcspn(name, separators);
6345 if (name[namelen]) {
6347 value = chomp_string(name + namelen + 1);
6348 valuelen = strlen(value);
6355 state = read_property(name, namelen, value, valuelen);
6358 if (state != ERR && io_error(io))
6370 static void __NORETURN
6373 /* XXX: Restore tty modes and let the OS cleanup the rest! */
6379 static void __NORETURN
6380 die(const char *err, ...)
6386 va_start(args, err);
6387 fputs("tig: ", stderr);
6388 vfprintf(stderr, err, args);
6389 fputs("\n", stderr);
6396 warn(const char *msg, ...)
6400 va_start(args, msg);
6401 fputs("tig warning: ", stderr);
6402 vfprintf(stderr, msg, args);
6403 fputs("\n", stderr);
6408 main(int argc, const char *argv[])
6410 const char **run_argv = NULL;
6412 enum request request;
6415 signal(SIGINT, quit);
6417 if (setlocale(LC_ALL, "")) {
6418 char *codeset = nl_langinfo(CODESET);
6420 string_ncopy(opt_codeset, codeset, strlen(codeset));
6423 if (load_repo_info() == ERR)
6424 die("Failed to load repo info.");
6426 if (load_options() == ERR)
6427 die("Failed to load user config.");
6429 if (load_git_config() == ERR)
6430 die("Failed to load repo config.");
6432 request = parse_options(argc, argv, &run_argv);
6433 if (request == REQ_NONE)
6436 /* Require a git repository unless when running in pager mode. */
6437 if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
6438 die("Not a git repository");
6440 if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
6443 if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
6444 opt_iconv = iconv_open(opt_codeset, opt_encoding);
6445 if (opt_iconv == ICONV_NONE)
6446 die("Failed to initialize character set conversion");
6449 if (load_refs() == ERR)
6450 die("Failed to load refs.");
6452 foreach_view (view, i)
6453 argv_from_env(view->ops->argv, view->cmd_env);
6457 if (request == REQ_VIEW_PAGER || run_argv) {
6458 if (request == REQ_VIEW_PAGER)
6459 io_open(&VIEW(request)->io, "");
6460 else if (!prepare_update(VIEW(request), run_argv, NULL, FORMAT_NONE))
6461 die("Failed to format arguments");
6462 open_view(NULL, request, OPEN_PREPARED);
6466 while (view_driver(display[current_view], request)) {
6470 foreach_view (view, i)
6472 view = display[current_view];
6474 /* Refresh, accept single keystroke of input */
6475 key = wgetch(status_win);
6477 /* wgetch() with nodelay() enabled returns ERR when there's no
6484 request = get_keybinding(view->keymap, key);
6486 /* Some low-level request handling. This keeps access to
6487 * status_win restricted. */
6491 char *cmd = read_prompt(":");
6494 struct view *next = VIEW(REQ_VIEW_PAGER);
6495 const char *argv[SIZEOF_ARG] = { "git" };
6498 /* When running random commands, initially show the
6499 * command in the title. However, it maybe later be
6500 * overwritten if a commit line is selected. */
6501 string_ncopy(next->ref, cmd, strlen(cmd));
6503 if (!argv_from_string(argv, &argc, cmd)) {
6504 report("Too many arguments");
6505 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
6506 report("Failed to format command");
6508 open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
6516 case REQ_SEARCH_BACK:
6518 const char *prompt = request == REQ_SEARCH ? "/" : "?";
6519 char *search = read_prompt(prompt);
6522 string_ncopy(opt_search, search, strlen(search));
6527 case REQ_SCREEN_RESIZE:
6531 getmaxyx(stdscr, height, width);
6533 /* Resize the status view and let the view driver take
6534 * care of resizing the displayed views. */
6535 wresize(status_win, 1, width);
6536 mvwin(status_win, height - 1, 0);
6537 wrefresh(status_win);