1 /* Copyright (c) 2006-2008 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>
47 /* ncurses(3): Must be defined to have extended wide-character functions. */
48 #define _XOPEN_SOURCE_EXTENDED
50 #ifdef HAVE_NCURSESW_NCURSES_H
51 #include <ncursesw/ncurses.h>
53 #ifdef HAVE_NCURSES_NCURSES_H
54 #include <ncurses/ncurses.h>
61 #define __NORETURN __attribute__((__noreturn__))
66 static void __NORETURN die(const char *err, ...);
67 static void warn(const char *msg, ...);
68 static void report(const char *msg, ...);
69 static void set_nonblocking_input(bool loading);
70 static size_t utf8_length(const char *string, int *width, size_t max_width, int *trimmed, bool reserve);
71 static bool prompt_yesno(const char *prompt);
72 static int load_refs(void);
74 #define ABS(x) ((x) >= 0 ? (x) : -(x))
75 #define MIN(x, y) ((x) < (y) ? (x) : (y))
77 #define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
78 #define STRING_SIZE(x) (sizeof(x) - 1)
80 #define SIZEOF_STR 1024 /* Default string size. */
81 #define SIZEOF_REF 256 /* Size of symbolic or SHA1 ID. */
82 #define SIZEOF_REV 41 /* Holds a SHA-1 and an ending NUL. */
83 #define SIZEOF_ARG 32 /* Default argument array size. */
87 #define REVGRAPH_INIT 'I'
88 #define REVGRAPH_MERGE 'M'
89 #define REVGRAPH_BRANCH '+'
90 #define REVGRAPH_COMMIT '*'
91 #define REVGRAPH_BOUND '^'
93 #define SIZEOF_REVGRAPH 19 /* Size of revision ancestry graphics. */
95 /* This color name can be used to refer to the default term colors. */
96 #define COLOR_DEFAULT (-1)
98 #define ICONV_NONE ((iconv_t) -1)
100 #define ICONV_CONST /* nothing */
103 /* The format and size of the date column in the main view. */
104 #define DATE_FORMAT "%Y-%m-%d %H:%M"
105 #define DATE_COLS STRING_SIZE("2006-04-29 14:21 ")
107 #define AUTHOR_COLS 20
110 /* The default interval between line numbers. */
111 #define NUMBER_INTERVAL 5
115 #define SCALE_SPLIT_VIEW(height) ((height) * 2 / 3)
117 #define NULL_ID "0000000000000000000000000000000000000000"
120 #define GIT_CONFIG "config"
123 /* Some ascii-shorthands fitted into the ncurses namespace. */
125 #define KEY_RETURN '\r'
130 char *name; /* Ref name; tag or head names are shortened. */
131 char id[SIZEOF_REV]; /* Commit SHA1 ID */
132 unsigned int head:1; /* Is it the current HEAD? */
133 unsigned int tag:1; /* Is it a tag? */
134 unsigned int ltag:1; /* If so, is the tag local? */
135 unsigned int remote:1; /* Is it a remote ref? */
136 unsigned int tracked:1; /* Is it the remote for the current HEAD? */
137 unsigned int next:1; /* For ref lists: are there more refs? */
140 static struct ref **get_refs(const char *id);
143 FORMAT_ALL, /* Perform replacement in all arguments. */
144 FORMAT_DASH, /* Perform replacement up until "--". */
145 FORMAT_NONE /* No replacement should be performed. */
148 static bool format_argv(const char *dst[], const char *src[], enum format_flags flags);
157 set_from_int_map(struct int_map *map, size_t map_size,
158 int *value, const char *name, int namelen)
163 for (i = 0; i < map_size; i++)
164 if (namelen == map[i].namelen &&
165 !strncasecmp(name, map[i].name, namelen)) {
166 *value = map[i].value;
179 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
181 if (srclen > dstlen - 1)
184 strncpy(dst, src, srclen);
188 /* Shorthands for safely copying into a fixed buffer. */
190 #define string_copy(dst, src) \
191 string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
193 #define string_ncopy(dst, src, srclen) \
194 string_ncopy_do(dst, sizeof(dst), src, srclen)
196 #define string_copy_rev(dst, src) \
197 string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
199 #define string_add(dst, from, src) \
200 string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
203 chomp_string(char *name)
207 while (isspace(*name))
210 namelen = strlen(name) - 1;
211 while (namelen > 0 && isspace(name[namelen]))
218 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
221 size_t pos = bufpos ? *bufpos : 0;
224 pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
230 return pos >= bufsize ? FALSE : TRUE;
233 #define string_format(buf, fmt, args...) \
234 string_nformat(buf, sizeof(buf), NULL, fmt, args)
236 #define string_format_from(buf, from, fmt, args...) \
237 string_nformat(buf, sizeof(buf), from, fmt, args)
240 string_enum_compare(const char *str1, const char *str2, int len)
244 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
246 /* Diff-Header == DIFF_HEADER */
247 for (i = 0; i < len; i++) {
248 if (toupper(str1[i]) == toupper(str2[i]))
251 if (string_enum_sep(str1[i]) &&
252 string_enum_sep(str2[i]))
255 return str1[i] - str2[i];
261 #define prefixcmp(str1, str2) \
262 strncmp(str1, str2, STRING_SIZE(str2))
265 suffixcmp(const char *str, int slen, const char *suffix)
267 size_t len = slen >= 0 ? slen : strlen(str);
268 size_t suffixlen = strlen(suffix);
270 return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
275 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
279 while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
280 bool advance = cmd[valuelen] != 0;
283 argv[(*argc)++] = chomp_string(cmd);
284 cmd += valuelen + advance;
287 if (*argc < SIZEOF_ARG)
289 return *argc < SIZEOF_ARG;
293 argv_from_env(const char **argv, const char *name)
295 char *env = argv ? getenv(name) : NULL;
300 if (env && !argv_from_string(argv, &argc, env))
301 die("Too many arguments in the `%s` environment variable", name);
306 * Executing external commands.
310 IO_FD, /* File descriptor based IO. */
311 IO_BG, /* Execute command in the background. */
312 IO_FG, /* Execute command with same std{in,out,err}. */
313 IO_RD, /* Read only fork+exec IO. */
314 IO_WR, /* Write only fork+exec IO. */
318 enum io_type type; /* The requested type of pipe. */
319 const char *dir; /* Directory from which to execute. */
320 pid_t pid; /* Pipe for reading or writing. */
321 int pipe; /* Pipe end for reading or writing. */
322 int error; /* Error status. */
323 const char *argv[SIZEOF_ARG]; /* Shell command arguments. */
324 char *buf; /* Read buffer. */
325 size_t bufalloc; /* Allocated buffer size. */
326 size_t bufsize; /* Buffer content size. */
327 char *bufpos; /* Current buffer position. */
328 unsigned int eof:1; /* Has end of file been reached. */
332 reset_io(struct io *io)
336 io->buf = io->bufpos = NULL;
337 io->bufalloc = io->bufsize = 0;
343 init_io(struct io *io, const char *dir, enum io_type type)
351 init_io_rd(struct io *io, const char *argv[], const char *dir,
352 enum format_flags flags)
354 init_io(io, dir, IO_RD);
355 return format_argv(io->argv, argv, flags);
359 io_open(struct io *io, const char *name)
361 init_io(io, NULL, IO_FD);
362 io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
363 return io->pipe != -1;
367 kill_io(struct io *io)
369 return kill(io->pid, SIGKILL) != -1;
373 done_io(struct io *io)
384 pid_t waiting = waitpid(pid, &status, 0);
389 report("waitpid failed (%s)", strerror(errno));
393 return waiting == pid &&
394 !WIFSIGNALED(status) &&
396 !WEXITSTATUS(status);
403 start_io(struct io *io)
405 int pipefds[2] = { -1, -1 };
407 if (io->type == IO_FD)
410 if ((io->type == IO_RD || io->type == IO_WR) &&
414 if ((io->pid = fork())) {
415 if (pipefds[!(io->type == IO_WR)] != -1)
416 close(pipefds[!(io->type == IO_WR)]);
418 io->pipe = pipefds[!!(io->type == IO_WR)];
423 if (io->type != IO_FG) {
424 int devnull = open("/dev/null", O_RDWR);
425 int readfd = io->type == IO_WR ? pipefds[0] : devnull;
426 int writefd = io->type == IO_RD ? pipefds[1] : devnull;
428 dup2(readfd, STDIN_FILENO);
429 dup2(writefd, STDOUT_FILENO);
430 dup2(devnull, STDERR_FILENO);
433 if (pipefds[0] != -1)
435 if (pipefds[1] != -1)
439 if (io->dir && *io->dir && chdir(io->dir) == -1)
440 die("Failed to change directory: %s", strerror(errno));
442 execvp(io->argv[0], (char *const*) io->argv);
443 die("Failed to execute program: %s", strerror(errno));
446 if (pipefds[!!(io->type == IO_WR)] != -1)
447 close(pipefds[!!(io->type == IO_WR)]);
452 run_io(struct io *io, const char **argv, const char *dir, enum io_type type)
454 init_io(io, dir, type);
455 if (!format_argv(io->argv, argv, FORMAT_NONE))
461 run_io_do(struct io *io)
463 return start_io(io) && done_io(io);
467 run_io_bg(const char **argv)
471 init_io(&io, NULL, IO_BG);
472 if (!format_argv(io.argv, argv, FORMAT_NONE))
474 return run_io_do(&io);
478 run_io_fg(const char **argv, const char *dir)
482 init_io(&io, dir, IO_FG);
483 if (!format_argv(io.argv, argv, FORMAT_NONE))
485 return run_io_do(&io);
489 run_io_rd(struct io *io, const char **argv, enum format_flags flags)
491 return init_io_rd(io, argv, NULL, flags) && start_io(io);
495 io_eof(struct io *io)
501 io_error(struct io *io)
507 io_strerror(struct io *io)
509 return strerror(io->error);
513 io_read(struct io *io, void *buf, size_t bufsize)
516 ssize_t readsize = read(io->pipe, buf, bufsize);
518 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
520 else if (readsize == -1)
522 else if (readsize == 0)
529 io_gets(struct io *io)
535 io->buf = io->bufpos = malloc(BUFSIZ);
538 io->bufalloc = BUFSIZ;
543 if (io->bufsize > 0) {
544 eol = memchr(io->bufpos, '\n', io->bufsize);
546 char *line = io->bufpos;
549 io->bufpos = eol + 1;
550 io->bufsize -= io->bufpos - line;
557 io->bufpos[io->bufsize] = 0;
564 if (io->bufsize > 0 && io->bufpos > io->buf)
565 memmove(io->buf, io->bufpos, io->bufsize);
567 io->bufpos = io->buf;
568 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
571 io->bufsize += readsize;
576 io_write(struct io *io, const void *buf, size_t bufsize)
580 while (!io_error(io) && written < bufsize) {
583 size = write(io->pipe, buf + written, bufsize - written);
584 if (size < 0 && (errno == EAGAIN || errno == EINTR))
592 return written == bufsize;
596 run_io_buf(const char **argv, char buf[], size_t bufsize)
601 if (!run_io_rd(&io, argv, FORMAT_NONE))
604 io.buf = io.bufpos = buf;
605 io.bufalloc = bufsize;
606 error = !io_gets(&io) && io_error(&io);
609 return done_io(&io) || error;
612 static int read_properties(struct io *io, const char *separators, int (*read)(char *, size_t, char *, size_t));
619 /* XXX: Keep the view request first and in sync with views[]. */ \
620 REQ_GROUP("View switching") \
621 REQ_(VIEW_MAIN, "Show main view"), \
622 REQ_(VIEW_DIFF, "Show diff view"), \
623 REQ_(VIEW_LOG, "Show log view"), \
624 REQ_(VIEW_TREE, "Show tree view"), \
625 REQ_(VIEW_BLOB, "Show blob view"), \
626 REQ_(VIEW_BLAME, "Show blame view"), \
627 REQ_(VIEW_HELP, "Show help page"), \
628 REQ_(VIEW_PAGER, "Show pager view"), \
629 REQ_(VIEW_STATUS, "Show status view"), \
630 REQ_(VIEW_STAGE, "Show stage view"), \
632 REQ_GROUP("View manipulation") \
633 REQ_(ENTER, "Enter current line and scroll"), \
634 REQ_(NEXT, "Move to next"), \
635 REQ_(PREVIOUS, "Move to previous"), \
636 REQ_(VIEW_NEXT, "Move focus to next view"), \
637 REQ_(REFRESH, "Reload and refresh"), \
638 REQ_(MAXIMIZE, "Maximize the current view"), \
639 REQ_(VIEW_CLOSE, "Close the current view"), \
640 REQ_(QUIT, "Close all views and quit"), \
642 REQ_GROUP("View specific requests") \
643 REQ_(STATUS_UPDATE, "Update file status"), \
644 REQ_(STATUS_REVERT, "Revert file changes"), \
645 REQ_(STATUS_MERGE, "Merge file using external tool"), \
646 REQ_(STAGE_NEXT, "Find next chunk to stage"), \
647 REQ_(TREE_PARENT, "Switch to parent directory in tree view"), \
649 REQ_GROUP("Cursor navigation") \
650 REQ_(MOVE_UP, "Move cursor one line up"), \
651 REQ_(MOVE_DOWN, "Move cursor one line down"), \
652 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
653 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
654 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
655 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
657 REQ_GROUP("Scrolling") \
658 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
659 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
660 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
661 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
663 REQ_GROUP("Searching") \
664 REQ_(SEARCH, "Search the view"), \
665 REQ_(SEARCH_BACK, "Search backwards in the view"), \
666 REQ_(FIND_NEXT, "Find next search match"), \
667 REQ_(FIND_PREV, "Find previous search match"), \
669 REQ_GROUP("Option manipulation") \
670 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
671 REQ_(TOGGLE_DATE, "Toggle date display"), \
672 REQ_(TOGGLE_AUTHOR, "Toggle author display"), \
673 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
674 REQ_(TOGGLE_REFS, "Toggle reference display (tags/branches)"), \
677 REQ_(PROMPT, "Bring up the prompt"), \
678 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
679 REQ_(SCREEN_RESIZE, "Resize the screen"), \
680 REQ_(SHOW_VERSION, "Show version information"), \
681 REQ_(STOP_LOADING, "Stop all loading views"), \
682 REQ_(EDIT, "Open in editor"), \
683 REQ_(NONE, "Do nothing")
686 /* User action requests. */
688 #define REQ_GROUP(help)
689 #define REQ_(req, help) REQ_##req
691 /* Offset all requests to avoid conflicts with ncurses getch values. */
692 REQ_OFFSET = KEY_MAX + 1,
699 struct request_info {
700 enum request request;
706 static struct request_info req_info[] = {
707 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
708 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
715 get_request(const char *name)
717 int namelen = strlen(name);
720 for (i = 0; i < ARRAY_SIZE(req_info); i++)
721 if (req_info[i].namelen == namelen &&
722 !string_enum_compare(req_info[i].name, name, namelen))
723 return req_info[i].request;
733 static const char usage[] =
734 "tig " TIG_VERSION " (" __DATE__ ")\n"
736 "Usage: tig [options] [revs] [--] [paths]\n"
737 " or: tig show [options] [revs] [--] [paths]\n"
738 " or: tig blame [rev] path\n"
740 " or: tig < [git command output]\n"
743 " -v, --version Show version and exit\n"
744 " -h, --help Show help message and exit";
746 /* Option and state variables. */
747 static bool opt_date = TRUE;
748 static bool opt_author = TRUE;
749 static bool opt_line_number = FALSE;
750 static bool opt_line_graphics = TRUE;
751 static bool opt_rev_graph = FALSE;
752 static bool opt_show_refs = TRUE;
753 static int opt_num_interval = NUMBER_INTERVAL;
754 static int opt_tab_size = TAB_SIZE;
755 static int opt_author_cols = AUTHOR_COLS-1;
756 static char opt_path[SIZEOF_STR] = "";
757 static char opt_file[SIZEOF_STR] = "";
758 static char opt_ref[SIZEOF_REF] = "";
759 static char opt_head[SIZEOF_REF] = "";
760 static char opt_head_rev[SIZEOF_REV] = "";
761 static char opt_remote[SIZEOF_REF] = "";
762 static char opt_encoding[20] = "UTF-8";
763 static bool opt_utf8 = TRUE;
764 static char opt_codeset[20] = "UTF-8";
765 static iconv_t opt_iconv = ICONV_NONE;
766 static char opt_search[SIZEOF_STR] = "";
767 static char opt_cdup[SIZEOF_STR] = "";
768 static char opt_git_dir[SIZEOF_STR] = "";
769 static signed char opt_is_inside_work_tree = -1; /* set to TRUE or FALSE */
770 static char opt_editor[SIZEOF_STR] = "";
771 static FILE *opt_tty = NULL;
773 #define is_initial_commit() (!*opt_head_rev)
774 #define is_head_commit(rev) (!strcmp((rev), "HEAD") || !strcmp(opt_head_rev, (rev)))
777 parse_options(int argc, const char *argv[], const char ***run_argv)
779 enum request request = REQ_VIEW_MAIN;
780 const char *subcommand;
781 bool seen_dashdash = FALSE;
782 /* XXX: This is vulnerable to the user overriding options
783 * required for the main view parser. */
784 const char *custom_argv[SIZEOF_ARG] = {
785 "git", "log", "--no-color", "--pretty=raw", "--parents",
790 if (!isatty(STDIN_FILENO))
791 return REQ_VIEW_PAGER;
794 return REQ_VIEW_MAIN;
796 subcommand = argv[1];
797 if (!strcmp(subcommand, "status") || !strcmp(subcommand, "-S")) {
798 if (!strcmp(subcommand, "-S"))
799 warn("`-S' has been deprecated; use `tig status' instead");
801 warn("ignoring arguments after `%s'", subcommand);
802 return REQ_VIEW_STATUS;
804 } else if (!strcmp(subcommand, "blame")) {
805 if (argc <= 2 || argc > 4)
806 die("invalid number of options to blame\n\n%s", usage);
810 string_ncopy(opt_ref, argv[i], strlen(argv[i]));
814 string_ncopy(opt_file, argv[i], strlen(argv[i]));
815 return REQ_VIEW_BLAME;
817 } else if (!strcmp(subcommand, "show")) {
818 request = REQ_VIEW_DIFF;
820 } else if (!strcmp(subcommand, "log") || !strcmp(subcommand, "diff")) {
821 request = subcommand[0] == 'l' ? REQ_VIEW_LOG : REQ_VIEW_DIFF;
822 warn("`tig %s' has been deprecated", subcommand);
829 custom_argv[1] = subcommand;
833 for (i = 1 + !!subcommand; i < argc; i++) {
834 const char *opt = argv[i];
836 if (seen_dashdash || !strcmp(opt, "--")) {
837 seen_dashdash = TRUE;
839 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
840 printf("tig version %s\n", TIG_VERSION);
843 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
844 printf("%s\n", usage);
848 custom_argv[j++] = opt;
849 if (j >= ARRAY_SIZE(custom_argv))
850 die("command too long");
853 custom_argv[j] = NULL;
854 *run_argv = custom_argv;
861 * Line-oriented content detection.
865 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
866 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
867 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
868 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
869 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
870 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
871 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
872 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
873 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
874 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
875 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
876 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
877 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
878 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
879 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
880 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
881 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
882 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
883 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
884 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
885 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
886 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
887 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
888 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
889 LINE(AUTHOR, "author ", COLOR_CYAN, COLOR_DEFAULT, 0), \
890 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
891 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
892 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
893 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
894 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
895 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
896 LINE(DELIMITER, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
897 LINE(DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
898 LINE(LINE_NUMBER, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
899 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
900 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
901 LINE(MAIN_AUTHOR, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
902 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
903 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
904 LINE(MAIN_LOCAL_TAG,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
905 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
906 LINE(MAIN_TRACKED, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
907 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
908 LINE(MAIN_HEAD, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
909 LINE(MAIN_REVGRAPH,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
910 LINE(TREE_DIR, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
911 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
912 LINE(STAT_HEAD, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
913 LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
914 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
915 LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
916 LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
917 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
918 LINE(BLAME_ID, "", COLOR_MAGENTA, COLOR_DEFAULT, 0)
921 #define LINE(type, line, fg, bg, attr) \
929 const char *name; /* Option name. */
930 int namelen; /* Size of option name. */
931 const char *line; /* The start of line to match. */
932 int linelen; /* Size of string to match. */
933 int fg, bg, attr; /* Color and text attributes for the lines. */
936 static struct line_info line_info[] = {
937 #define LINE(type, line, fg, bg, attr) \
938 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
943 static enum line_type
944 get_line_type(const char *line)
946 int linelen = strlen(line);
949 for (type = 0; type < ARRAY_SIZE(line_info); type++)
950 /* Case insensitive search matches Signed-off-by lines better. */
951 if (linelen >= line_info[type].linelen &&
952 !strncasecmp(line_info[type].line, line, line_info[type].linelen))
959 get_line_attr(enum line_type type)
961 assert(type < ARRAY_SIZE(line_info));
962 return COLOR_PAIR(type) | line_info[type].attr;
965 static struct line_info *
966 get_line_info(const char *name)
968 size_t namelen = strlen(name);
971 for (type = 0; type < ARRAY_SIZE(line_info); type++)
972 if (namelen == line_info[type].namelen &&
973 !string_enum_compare(line_info[type].name, name, namelen))
974 return &line_info[type];
982 int default_bg = line_info[LINE_DEFAULT].bg;
983 int default_fg = line_info[LINE_DEFAULT].fg;
988 if (assume_default_colors(default_fg, default_bg) == ERR) {
989 default_bg = COLOR_BLACK;
990 default_fg = COLOR_WHITE;
993 for (type = 0; type < ARRAY_SIZE(line_info); type++) {
994 struct line_info *info = &line_info[type];
995 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
996 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
998 init_pair(type, fg, bg);
1003 enum line_type type;
1006 unsigned int selected:1;
1007 unsigned int dirty:1;
1009 void *data; /* User data */
1019 enum request request;
1022 static struct keybinding default_keybindings[] = {
1023 /* View switching */
1024 { 'm', REQ_VIEW_MAIN },
1025 { 'd', REQ_VIEW_DIFF },
1026 { 'l', REQ_VIEW_LOG },
1027 { 't', REQ_VIEW_TREE },
1028 { 'f', REQ_VIEW_BLOB },
1029 { 'B', REQ_VIEW_BLAME },
1030 { 'p', REQ_VIEW_PAGER },
1031 { 'h', REQ_VIEW_HELP },
1032 { 'S', REQ_VIEW_STATUS },
1033 { 'c', REQ_VIEW_STAGE },
1035 /* View manipulation */
1036 { 'q', REQ_VIEW_CLOSE },
1037 { KEY_TAB, REQ_VIEW_NEXT },
1038 { KEY_RETURN, REQ_ENTER },
1039 { KEY_UP, REQ_PREVIOUS },
1040 { KEY_DOWN, REQ_NEXT },
1041 { 'R', REQ_REFRESH },
1042 { KEY_F(5), REQ_REFRESH },
1043 { 'O', REQ_MAXIMIZE },
1045 /* Cursor navigation */
1046 { 'k', REQ_MOVE_UP },
1047 { 'j', REQ_MOVE_DOWN },
1048 { KEY_HOME, REQ_MOVE_FIRST_LINE },
1049 { KEY_END, REQ_MOVE_LAST_LINE },
1050 { KEY_NPAGE, REQ_MOVE_PAGE_DOWN },
1051 { ' ', REQ_MOVE_PAGE_DOWN },
1052 { KEY_PPAGE, REQ_MOVE_PAGE_UP },
1053 { 'b', REQ_MOVE_PAGE_UP },
1054 { '-', REQ_MOVE_PAGE_UP },
1057 { KEY_IC, REQ_SCROLL_LINE_UP },
1058 { KEY_DC, REQ_SCROLL_LINE_DOWN },
1059 { 'w', REQ_SCROLL_PAGE_UP },
1060 { 's', REQ_SCROLL_PAGE_DOWN },
1063 { '/', REQ_SEARCH },
1064 { '?', REQ_SEARCH_BACK },
1065 { 'n', REQ_FIND_NEXT },
1066 { 'N', REQ_FIND_PREV },
1070 { 'z', REQ_STOP_LOADING },
1071 { 'v', REQ_SHOW_VERSION },
1072 { 'r', REQ_SCREEN_REDRAW },
1073 { '.', REQ_TOGGLE_LINENO },
1074 { 'D', REQ_TOGGLE_DATE },
1075 { 'A', REQ_TOGGLE_AUTHOR },
1076 { 'g', REQ_TOGGLE_REV_GRAPH },
1077 { 'F', REQ_TOGGLE_REFS },
1078 { ':', REQ_PROMPT },
1079 { 'u', REQ_STATUS_UPDATE },
1080 { '!', REQ_STATUS_REVERT },
1081 { 'M', REQ_STATUS_MERGE },
1082 { '@', REQ_STAGE_NEXT },
1083 { ',', REQ_TREE_PARENT },
1086 /* Using the ncurses SIGWINCH handler. */
1087 { KEY_RESIZE, REQ_SCREEN_RESIZE },
1090 #define KEYMAP_INFO \
1104 #define KEYMAP_(name) KEYMAP_##name
1109 static struct int_map keymap_table[] = {
1110 #define KEYMAP_(name) { #name, STRING_SIZE(#name), KEYMAP_##name }
1115 #define set_keymap(map, name) \
1116 set_from_int_map(keymap_table, ARRAY_SIZE(keymap_table), map, name, strlen(name))
1118 struct keybinding_table {
1119 struct keybinding *data;
1123 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1126 add_keybinding(enum keymap keymap, enum request request, int key)
1128 struct keybinding_table *table = &keybindings[keymap];
1130 table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1132 die("Failed to allocate keybinding");
1133 table->data[table->size].alias = key;
1134 table->data[table->size++].request = request;
1137 /* Looks for a key binding first in the given map, then in the generic map, and
1138 * lastly in the default keybindings. */
1140 get_keybinding(enum keymap keymap, int key)
1144 for (i = 0; i < keybindings[keymap].size; i++)
1145 if (keybindings[keymap].data[i].alias == key)
1146 return keybindings[keymap].data[i].request;
1148 for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1149 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1150 return keybindings[KEYMAP_GENERIC].data[i].request;
1152 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1153 if (default_keybindings[i].alias == key)
1154 return default_keybindings[i].request;
1156 return (enum request) key;
1165 static struct key key_table[] = {
1166 { "Enter", KEY_RETURN },
1168 { "Backspace", KEY_BACKSPACE },
1170 { "Escape", KEY_ESC },
1171 { "Left", KEY_LEFT },
1172 { "Right", KEY_RIGHT },
1174 { "Down", KEY_DOWN },
1175 { "Insert", KEY_IC },
1176 { "Delete", KEY_DC },
1178 { "Home", KEY_HOME },
1180 { "PageUp", KEY_PPAGE },
1181 { "PageDown", KEY_NPAGE },
1191 { "F10", KEY_F(10) },
1192 { "F11", KEY_F(11) },
1193 { "F12", KEY_F(12) },
1197 get_key_value(const char *name)
1201 for (i = 0; i < ARRAY_SIZE(key_table); i++)
1202 if (!strcasecmp(key_table[i].name, name))
1203 return key_table[i].value;
1205 if (strlen(name) == 1 && isprint(*name))
1212 get_key_name(int key_value)
1214 static char key_char[] = "'X'";
1215 const char *seq = NULL;
1218 for (key = 0; key < ARRAY_SIZE(key_table); key++)
1219 if (key_table[key].value == key_value)
1220 seq = key_table[key].name;
1224 isprint(key_value)) {
1225 key_char[1] = (char) key_value;
1229 return seq ? seq : "(no key)";
1233 get_key(enum request request)
1235 static char buf[BUFSIZ];
1242 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1243 struct keybinding *keybinding = &default_keybindings[i];
1245 if (keybinding->request != request)
1248 if (!string_format_from(buf, &pos, "%s%s", sep,
1249 get_key_name(keybinding->alias)))
1250 return "Too many keybindings!";
1257 struct run_request {
1260 const char *argv[SIZEOF_ARG];
1263 static struct run_request *run_request;
1264 static size_t run_requests;
1267 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1269 struct run_request *req;
1271 if (argc >= ARRAY_SIZE(req->argv) - 1)
1274 req = realloc(run_request, (run_requests + 1) * sizeof(*run_request));
1279 req = &run_request[run_requests];
1280 req->keymap = keymap;
1282 req->argv[0] = NULL;
1284 if (!format_argv(req->argv, argv, FORMAT_NONE))
1287 return REQ_NONE + ++run_requests;
1290 static struct run_request *
1291 get_run_request(enum request request)
1293 if (request <= REQ_NONE)
1295 return &run_request[request - REQ_NONE - 1];
1299 add_builtin_run_requests(void)
1301 const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1302 const char *gc[] = { "git", "gc", NULL };
1309 { KEYMAP_MAIN, 'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1310 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1314 for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1317 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1318 if (req != REQ_NONE)
1319 add_keybinding(reqs[i].keymap, req, reqs[i].key);
1324 * User config file handling.
1327 static struct int_map color_map[] = {
1328 #define COLOR_MAP(name) { #name, STRING_SIZE(#name), COLOR_##name }
1340 #define set_color(color, name) \
1341 set_from_int_map(color_map, ARRAY_SIZE(color_map), color, name, strlen(name))
1343 static struct int_map attr_map[] = {
1344 #define ATTR_MAP(name) { #name, STRING_SIZE(#name), A_##name }
1351 ATTR_MAP(UNDERLINE),
1354 #define set_attribute(attr, name) \
1355 set_from_int_map(attr_map, ARRAY_SIZE(attr_map), attr, name, strlen(name))
1357 static int config_lineno;
1358 static bool config_errors;
1359 static const char *config_msg;
1361 /* Wants: object fgcolor bgcolor [attr] */
1363 option_color_command(int argc, const char *argv[])
1365 struct line_info *info;
1367 if (argc != 3 && argc != 4) {
1368 config_msg = "Wrong number of arguments given to color command";
1372 info = get_line_info(argv[0]);
1374 if (!string_enum_compare(argv[0], "main-delim", strlen("main-delim"))) {
1375 info = get_line_info("delimiter");
1377 } else if (!string_enum_compare(argv[0], "main-date", strlen("main-date"))) {
1378 info = get_line_info("date");
1381 config_msg = "Unknown color name";
1386 if (set_color(&info->fg, argv[1]) == ERR ||
1387 set_color(&info->bg, argv[2]) == ERR) {
1388 config_msg = "Unknown color";
1392 if (argc == 4 && set_attribute(&info->attr, argv[3]) == ERR) {
1393 config_msg = "Unknown attribute";
1400 static bool parse_bool(const char *s)
1402 return (!strcmp(s, "1") || !strcmp(s, "true") ||
1403 !strcmp(s, "yes")) ? TRUE : FALSE;
1407 parse_int(const char *s, int default_value, int min, int max)
1409 int value = atoi(s);
1411 return (value < min || value > max) ? default_value : value;
1414 /* Wants: name = value */
1416 option_set_command(int argc, const char *argv[])
1419 config_msg = "Wrong number of arguments given to set command";
1423 if (strcmp(argv[1], "=")) {
1424 config_msg = "No value assigned";
1428 if (!strcmp(argv[0], "show-author")) {
1429 opt_author = parse_bool(argv[2]);
1433 if (!strcmp(argv[0], "show-date")) {
1434 opt_date = parse_bool(argv[2]);
1438 if (!strcmp(argv[0], "show-rev-graph")) {
1439 opt_rev_graph = parse_bool(argv[2]);
1443 if (!strcmp(argv[0], "show-refs")) {
1444 opt_show_refs = parse_bool(argv[2]);
1448 if (!strcmp(argv[0], "show-line-numbers")) {
1449 opt_line_number = parse_bool(argv[2]);
1453 if (!strcmp(argv[0], "line-graphics")) {
1454 opt_line_graphics = parse_bool(argv[2]);
1458 if (!strcmp(argv[0], "line-number-interval")) {
1459 opt_num_interval = parse_int(argv[2], opt_num_interval, 1, 1024);
1463 if (!strcmp(argv[0], "author-width")) {
1464 opt_author_cols = parse_int(argv[2], opt_author_cols, 0, 1024);
1468 if (!strcmp(argv[0], "tab-size")) {
1469 opt_tab_size = parse_int(argv[2], opt_tab_size, 1, 1024);
1473 if (!strcmp(argv[0], "commit-encoding")) {
1474 const char *arg = argv[2];
1475 int arglen = strlen(arg);
1480 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1481 config_msg = "Unmatched quotation";
1484 arg += 1; arglen -= 2;
1486 string_ncopy(opt_encoding, arg, strlen(arg));
1491 config_msg = "Unknown variable name";
1495 /* Wants: mode request key */
1497 option_bind_command(int argc, const char *argv[])
1499 enum request request;
1504 config_msg = "Wrong number of arguments given to bind command";
1508 if (set_keymap(&keymap, argv[0]) == ERR) {
1509 config_msg = "Unknown key map";
1513 key = get_key_value(argv[1]);
1515 config_msg = "Unknown key";
1519 request = get_request(argv[2]);
1520 if (request == REQ_NONE) {
1521 const char *obsolete[] = { "cherry-pick" };
1522 size_t namelen = strlen(argv[2]);
1525 for (i = 0; i < ARRAY_SIZE(obsolete); i++) {
1526 if (namelen == strlen(obsolete[i]) &&
1527 !string_enum_compare(obsolete[i], argv[2], namelen)) {
1528 config_msg = "Obsolete request name";
1533 if (request == REQ_NONE && *argv[2]++ == '!')
1534 request = add_run_request(keymap, key, argc - 2, argv + 2);
1535 if (request == REQ_NONE) {
1536 config_msg = "Unknown request name";
1540 add_keybinding(keymap, request, key);
1546 set_option(const char *opt, char *value)
1548 const char *argv[SIZEOF_ARG];
1551 if (!argv_from_string(argv, &argc, value)) {
1552 config_msg = "Too many option arguments";
1556 if (!strcmp(opt, "color"))
1557 return option_color_command(argc, argv);
1559 if (!strcmp(opt, "set"))
1560 return option_set_command(argc, argv);
1562 if (!strcmp(opt, "bind"))
1563 return option_bind_command(argc, argv);
1565 config_msg = "Unknown option command";
1570 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1575 config_msg = "Internal error";
1577 /* Check for comment markers, since read_properties() will
1578 * only ensure opt and value are split at first " \t". */
1579 optlen = strcspn(opt, "#");
1583 if (opt[optlen] != 0) {
1584 config_msg = "No option value";
1588 /* Look for comment endings in the value. */
1589 size_t len = strcspn(value, "#");
1591 if (len < valuelen) {
1593 value[valuelen] = 0;
1596 status = set_option(opt, value);
1599 if (status == ERR) {
1600 fprintf(stderr, "Error on line %d, near '%.*s': %s\n",
1601 config_lineno, (int) optlen, opt, config_msg);
1602 config_errors = TRUE;
1605 /* Always keep going if errors are encountered. */
1610 load_option_file(const char *path)
1614 /* It's ok that the file doesn't exist. */
1615 if (!io_open(&io, path))
1619 config_errors = FALSE;
1621 if (read_properties(&io, " \t", read_option) == ERR ||
1622 config_errors == TRUE)
1623 fprintf(stderr, "Errors while loading %s.\n", path);
1629 const char *home = getenv("HOME");
1630 const char *tigrc_user = getenv("TIGRC_USER");
1631 const char *tigrc_system = getenv("TIGRC_SYSTEM");
1632 char buf[SIZEOF_STR];
1634 add_builtin_run_requests();
1636 if (!tigrc_system) {
1637 if (!string_format(buf, "%s/tigrc", SYSCONFDIR))
1641 load_option_file(tigrc_system);
1644 if (!home || !string_format(buf, "%s/.tigrc", home))
1648 load_option_file(tigrc_user);
1661 /* The display array of active views and the index of the current view. */
1662 static struct view *display[2];
1663 static unsigned int current_view;
1665 /* Reading from the prompt? */
1666 static bool input_mode = FALSE;
1668 #define foreach_displayed_view(view, i) \
1669 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1671 #define displayed_views() (display[1] != NULL ? 2 : 1)
1673 /* Current head and commit ID */
1674 static char ref_blob[SIZEOF_REF] = "";
1675 static char ref_commit[SIZEOF_REF] = "HEAD";
1676 static char ref_head[SIZEOF_REF] = "HEAD";
1679 const char *name; /* View name */
1680 const char *cmd_env; /* Command line set via environment */
1681 const char *id; /* Points to either of ref_{head,commit,blob} */
1683 struct view_ops *ops; /* View operations */
1685 enum keymap keymap; /* What keymap does this view have */
1686 bool git_dir; /* Whether the view requires a git directory. */
1688 char ref[SIZEOF_REF]; /* Hovered commit reference */
1689 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
1691 int height, width; /* The width and height of the main window */
1692 WINDOW *win; /* The main window */
1693 WINDOW *title; /* The title window living below the main window */
1696 unsigned long offset; /* Offset of the window top */
1697 unsigned long lineno; /* Current line number */
1700 char grep[SIZEOF_STR]; /* Search string */
1701 regex_t *regex; /* Pre-compiled regex */
1703 /* If non-NULL, points to the view that opened this view. If this view
1704 * is closed tig will switch back to the parent view. */
1705 struct view *parent;
1708 size_t lines; /* Total number of lines */
1709 struct line *line; /* Line index */
1710 size_t line_alloc; /* Total number of allocated lines */
1711 size_t line_size; /* Total number of used lines */
1712 unsigned int digits; /* Number of digits in the lines member. */
1715 struct line *curline; /* Line currently being drawn. */
1716 enum line_type curtype; /* Attribute currently used for drawing. */
1717 unsigned long col; /* Column when drawing. */
1726 /* What type of content being displayed. Used in the title bar. */
1728 /* Default command arguments. */
1730 /* Open and reads in all view content. */
1731 bool (*open)(struct view *view);
1732 /* Read one line; updates view->line. */
1733 bool (*read)(struct view *view, char *data);
1734 /* Draw one line; @lineno must be < view->height. */
1735 bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
1736 /* Depending on view handle a special requests. */
1737 enum request (*request)(struct view *view, enum request request, struct line *line);
1738 /* Search for regex in a line. */
1739 bool (*grep)(struct view *view, struct line *line);
1741 void (*select)(struct view *view, struct line *line);
1744 static struct view_ops blame_ops;
1745 static struct view_ops blob_ops;
1746 static struct view_ops diff_ops;
1747 static struct view_ops help_ops;
1748 static struct view_ops log_ops;
1749 static struct view_ops main_ops;
1750 static struct view_ops pager_ops;
1751 static struct view_ops stage_ops;
1752 static struct view_ops status_ops;
1753 static struct view_ops tree_ops;
1755 #define VIEW_STR(name, env, ref, ops, map, git) \
1756 { name, #env, ref, ops, map, git }
1758 #define VIEW_(id, name, ops, git, ref) \
1759 VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
1762 static struct view views[] = {
1763 VIEW_(MAIN, "main", &main_ops, TRUE, ref_head),
1764 VIEW_(DIFF, "diff", &diff_ops, TRUE, ref_commit),
1765 VIEW_(LOG, "log", &log_ops, TRUE, ref_head),
1766 VIEW_(TREE, "tree", &tree_ops, TRUE, ref_commit),
1767 VIEW_(BLOB, "blob", &blob_ops, TRUE, ref_blob),
1768 VIEW_(BLAME, "blame", &blame_ops, TRUE, ref_commit),
1769 VIEW_(HELP, "help", &help_ops, FALSE, ""),
1770 VIEW_(PAGER, "pager", &pager_ops, FALSE, "stdin"),
1771 VIEW_(STATUS, "status", &status_ops, TRUE, ""),
1772 VIEW_(STAGE, "stage", &stage_ops, TRUE, ""),
1775 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
1776 #define VIEW_REQ(view) ((view) - views + REQ_OFFSET + 1)
1778 #define foreach_view(view, i) \
1779 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1781 #define view_is_displayed(view) \
1782 (view == display[0] || view == display[1])
1789 static int line_graphics[] = {
1790 /* LINE_GRAPHIC_VLINE: */ '|'
1794 set_view_attr(struct view *view, enum line_type type)
1796 if (!view->curline->selected && view->curtype != type) {
1797 wattrset(view->win, get_line_attr(type));
1798 wchgat(view->win, -1, 0, type, NULL);
1799 view->curtype = type;
1804 draw_chars(struct view *view, enum line_type type, const char *string,
1805 int max_len, bool use_tilde)
1809 int trimmed = FALSE;
1815 len = utf8_length(string, &col, max_len, &trimmed, use_tilde);
1817 col = len = strlen(string);
1818 if (len > max_len) {
1822 col = len = max_len;
1827 set_view_attr(view, type);
1828 waddnstr(view->win, string, len);
1829 if (trimmed && use_tilde) {
1830 set_view_attr(view, LINE_DELIMITER);
1831 waddch(view->win, '~');
1839 draw_space(struct view *view, enum line_type type, int max, int spaces)
1841 static char space[] = " ";
1844 spaces = MIN(max, spaces);
1846 while (spaces > 0) {
1847 int len = MIN(spaces, sizeof(space) - 1);
1849 col += draw_chars(view, type, space, spaces, FALSE);
1857 draw_lineno(struct view *view, unsigned int lineno)
1860 int digits3 = view->digits < 3 ? 3 : view->digits;
1861 int max_number = MIN(digits3, STRING_SIZE(number));
1862 int max = view->width - view->col;
1865 if (max < max_number)
1868 lineno += view->offset + 1;
1869 if (lineno == 1 || (lineno % opt_num_interval) == 0) {
1870 static char fmt[] = "%1ld";
1872 if (view->digits <= 9)
1873 fmt[1] = '0' + digits3;
1875 if (!string_format(number, fmt, lineno))
1877 col = draw_chars(view, LINE_LINE_NUMBER, number, max_number, TRUE);
1879 col = draw_space(view, LINE_LINE_NUMBER, max_number, max_number);
1883 set_view_attr(view, LINE_DEFAULT);
1884 waddch(view->win, line_graphics[LINE_GRAPHIC_VLINE]);
1889 col += draw_space(view, LINE_DEFAULT, max - col, 1);
1892 return view->width - view->col <= 0;
1896 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
1898 view->col += draw_chars(view, type, string, view->width - view->col, trim);
1899 return view->width - view->col <= 0;
1903 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
1905 int max = view->width - view->col;
1911 set_view_attr(view, type);
1912 /* Using waddch() instead of waddnstr() ensures that
1913 * they'll be rendered correctly for the cursor line. */
1914 for (i = 0; i < size; i++)
1915 waddch(view->win, graphic[i]);
1919 waddch(view->win, ' ');
1923 return view->width - view->col <= 0;
1927 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
1929 int max = MIN(view->width - view->col, len);
1933 col = draw_chars(view, type, text, max - 1, trim);
1935 col = draw_space(view, type, max - 1, max - 1);
1937 view->col += col + draw_space(view, LINE_DEFAULT, max - col, max - col);
1938 return view->width - view->col <= 0;
1942 draw_date(struct view *view, struct tm *time)
1944 char buf[DATE_COLS];
1949 timelen = strftime(buf, sizeof(buf), DATE_FORMAT, time);
1950 date = timelen ? buf : NULL;
1952 return draw_field(view, LINE_DATE, date, DATE_COLS, FALSE);
1956 draw_view_line(struct view *view, unsigned int lineno)
1959 bool selected = (view->offset + lineno == view->lineno);
1962 assert(view_is_displayed(view));
1964 if (view->offset + lineno >= view->lines)
1967 line = &view->line[view->offset + lineno];
1969 wmove(view->win, lineno, 0);
1971 view->curline = line;
1972 view->curtype = LINE_NONE;
1973 line->selected = FALSE;
1976 set_view_attr(view, LINE_CURSOR);
1977 line->selected = TRUE;
1978 view->ops->select(view, line);
1979 } else if (line->selected) {
1980 wclrtoeol(view->win);
1983 scrollok(view->win, FALSE);
1984 draw_ok = view->ops->draw(view, line, lineno);
1985 scrollok(view->win, TRUE);
1991 redraw_view_dirty(struct view *view)
1996 for (lineno = 0; lineno < view->height; lineno++) {
1997 struct line *line = &view->line[view->offset + lineno];
2003 if (!draw_view_line(view, lineno))
2009 redrawwin(view->win);
2011 wnoutrefresh(view->win);
2013 wrefresh(view->win);
2017 redraw_view_from(struct view *view, int lineno)
2019 assert(0 <= lineno && lineno < view->height);
2021 for (; lineno < view->height; lineno++) {
2022 if (!draw_view_line(view, lineno))
2026 redrawwin(view->win);
2028 wnoutrefresh(view->win);
2030 wrefresh(view->win);
2034 redraw_view(struct view *view)
2037 redraw_view_from(view, 0);
2042 update_view_title(struct view *view)
2044 char buf[SIZEOF_STR];
2045 char state[SIZEOF_STR];
2046 size_t bufpos = 0, statelen = 0;
2048 assert(view_is_displayed(view));
2050 if (view != VIEW(REQ_VIEW_STATUS) && (view->lines || view->pipe)) {
2051 unsigned int view_lines = view->offset + view->height;
2052 unsigned int lines = view->lines
2053 ? MIN(view_lines, view->lines) * 100 / view->lines
2056 string_format_from(state, &statelen, "- %s %d of %d (%d%%)",
2063 time_t secs = time(NULL) - view->start_time;
2065 /* Three git seconds are a long time ... */
2067 string_format_from(state, &statelen, " %lds", secs);
2071 string_format_from(buf, &bufpos, "[%s]", view->name);
2072 if (*view->ref && bufpos < view->width) {
2073 size_t refsize = strlen(view->ref);
2074 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2076 if (minsize < view->width)
2077 refsize = view->width - minsize + 7;
2078 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2081 if (statelen && bufpos < view->width) {
2082 string_format_from(buf, &bufpos, " %s", state);
2085 if (view == display[current_view])
2086 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2088 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2090 mvwaddnstr(view->title, 0, 0, buf, bufpos);
2091 wclrtoeol(view->title);
2092 wmove(view->title, 0, view->width - 1);
2095 wnoutrefresh(view->title);
2097 wrefresh(view->title);
2101 resize_display(void)
2104 struct view *base = display[0];
2105 struct view *view = display[1] ? display[1] : display[0];
2107 /* Setup window dimensions */
2109 getmaxyx(stdscr, base->height, base->width);
2111 /* Make room for the status window. */
2115 /* Horizontal split. */
2116 view->width = base->width;
2117 view->height = SCALE_SPLIT_VIEW(base->height);
2118 base->height -= view->height;
2120 /* Make room for the title bar. */
2124 /* Make room for the title bar. */
2129 foreach_displayed_view (view, i) {
2131 view->win = newwin(view->height, 0, offset, 0);
2133 die("Failed to create %s view", view->name);
2135 scrollok(view->win, TRUE);
2137 view->title = newwin(1, 0, offset + view->height, 0);
2139 die("Failed to create title window");
2142 wresize(view->win, view->height, view->width);
2143 mvwin(view->win, offset, 0);
2144 mvwin(view->title, offset + view->height, 0);
2147 offset += view->height + 1;
2152 redraw_display(void)
2157 foreach_displayed_view (view, i) {
2159 update_view_title(view);
2164 update_display_cursor(struct view *view)
2166 /* Move the cursor to the right-most column of the cursor line.
2168 * XXX: This could turn out to be a bit expensive, but it ensures that
2169 * the cursor does not jump around. */
2171 wmove(view->win, view->lineno - view->offset, view->width - 1);
2172 wrefresh(view->win);
2180 /* Scrolling backend */
2182 do_scroll_view(struct view *view, int lines)
2184 bool redraw_current_line = FALSE;
2186 /* The rendering expects the new offset. */
2187 view->offset += lines;
2189 assert(0 <= view->offset && view->offset < view->lines);
2192 /* Move current line into the view. */
2193 if (view->lineno < view->offset) {
2194 view->lineno = view->offset;
2195 redraw_current_line = TRUE;
2196 } else if (view->lineno >= view->offset + view->height) {
2197 view->lineno = view->offset + view->height - 1;
2198 redraw_current_line = TRUE;
2201 assert(view->offset <= view->lineno && view->lineno < view->lines);
2203 /* Redraw the whole screen if scrolling is pointless. */
2204 if (view->height < ABS(lines)) {
2208 int line = lines > 0 ? view->height - lines : 0;
2209 int end = line + ABS(lines);
2211 wscrl(view->win, lines);
2213 for (; line < end; line++) {
2214 if (!draw_view_line(view, line))
2218 if (redraw_current_line)
2219 draw_view_line(view, view->lineno - view->offset);
2222 redrawwin(view->win);
2223 wrefresh(view->win);
2227 /* Scroll frontend */
2229 scroll_view(struct view *view, enum request request)
2233 assert(view_is_displayed(view));
2236 case REQ_SCROLL_PAGE_DOWN:
2237 lines = view->height;
2238 case REQ_SCROLL_LINE_DOWN:
2239 if (view->offset + lines > view->lines)
2240 lines = view->lines - view->offset;
2242 if (lines == 0 || view->offset + view->height >= view->lines) {
2243 report("Cannot scroll beyond the last line");
2248 case REQ_SCROLL_PAGE_UP:
2249 lines = view->height;
2250 case REQ_SCROLL_LINE_UP:
2251 if (lines > view->offset)
2252 lines = view->offset;
2255 report("Cannot scroll beyond the first line");
2263 die("request %d not handled in switch", request);
2266 do_scroll_view(view, lines);
2271 move_view(struct view *view, enum request request)
2273 int scroll_steps = 0;
2277 case REQ_MOVE_FIRST_LINE:
2278 steps = -view->lineno;
2281 case REQ_MOVE_LAST_LINE:
2282 steps = view->lines - view->lineno - 1;
2285 case REQ_MOVE_PAGE_UP:
2286 steps = view->height > view->lineno
2287 ? -view->lineno : -view->height;
2290 case REQ_MOVE_PAGE_DOWN:
2291 steps = view->lineno + view->height >= view->lines
2292 ? view->lines - view->lineno - 1 : view->height;
2304 die("request %d not handled in switch", request);
2307 if (steps <= 0 && view->lineno == 0) {
2308 report("Cannot move beyond the first line");
2311 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2312 report("Cannot move beyond the last line");
2316 /* Move the current line */
2317 view->lineno += steps;
2318 assert(0 <= view->lineno && view->lineno < view->lines);
2320 /* Check whether the view needs to be scrolled */
2321 if (view->lineno < view->offset ||
2322 view->lineno >= view->offset + view->height) {
2323 scroll_steps = steps;
2324 if (steps < 0 && -steps > view->offset) {
2325 scroll_steps = -view->offset;
2327 } else if (steps > 0) {
2328 if (view->lineno == view->lines - 1 &&
2329 view->lines > view->height) {
2330 scroll_steps = view->lines - view->offset - 1;
2331 if (scroll_steps >= view->height)
2332 scroll_steps -= view->height - 1;
2337 if (!view_is_displayed(view)) {
2338 view->offset += scroll_steps;
2339 assert(0 <= view->offset && view->offset < view->lines);
2340 view->ops->select(view, &view->line[view->lineno]);
2344 /* Repaint the old "current" line if we be scrolling */
2345 if (ABS(steps) < view->height)
2346 draw_view_line(view, view->lineno - steps - view->offset);
2349 do_scroll_view(view, scroll_steps);
2353 /* Draw the current line */
2354 draw_view_line(view, view->lineno - view->offset);
2356 redrawwin(view->win);
2357 wrefresh(view->win);
2366 static void search_view(struct view *view, enum request request);
2369 find_next_line(struct view *view, unsigned long lineno, struct line *line)
2371 assert(view_is_displayed(view));
2373 if (!view->ops->grep(view, line))
2376 if (lineno - view->offset >= view->height) {
2377 view->offset = lineno;
2378 view->lineno = lineno;
2382 unsigned long old_lineno = view->lineno - view->offset;
2384 view->lineno = lineno;
2385 draw_view_line(view, old_lineno);
2387 draw_view_line(view, view->lineno - view->offset);
2388 redrawwin(view->win);
2389 wrefresh(view->win);
2392 report("Line %ld matches '%s'", lineno + 1, view->grep);
2397 find_next(struct view *view, enum request request)
2399 unsigned long lineno = view->lineno;
2404 report("No previous search");
2406 search_view(view, request);
2416 case REQ_SEARCH_BACK:
2425 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2426 lineno += direction;
2428 /* Note, lineno is unsigned long so will wrap around in which case it
2429 * will become bigger than view->lines. */
2430 for (; lineno < view->lines; lineno += direction) {
2431 struct line *line = &view->line[lineno];
2433 if (find_next_line(view, lineno, line))
2437 report("No match found for '%s'", view->grep);
2441 search_view(struct view *view, enum request request)
2446 regfree(view->regex);
2449 view->regex = calloc(1, sizeof(*view->regex));
2454 regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2455 if (regex_err != 0) {
2456 char buf[SIZEOF_STR] = "unknown error";
2458 regerror(regex_err, view->regex, buf, sizeof(buf));
2459 report("Search failed: %s", buf);
2463 string_copy(view->grep, opt_search);
2465 find_next(view, request);
2469 * Incremental updating
2473 reset_view(struct view *view)
2477 for (i = 0; i < view->lines; i++)
2478 free(view->line[i].data);
2485 view->line_size = 0;
2486 view->line_alloc = 0;
2491 free_argv(const char *argv[])
2495 for (argc = 0; argv[argc]; argc++)
2496 free((void *) argv[argc]);
2500 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
2502 char buf[SIZEOF_STR];
2504 bool noreplace = flags == FORMAT_NONE;
2506 free_argv(dst_argv);
2508 for (argc = 0; src_argv[argc]; argc++) {
2509 const char *arg = src_argv[argc];
2513 char *next = strstr(arg, "%(");
2514 int len = next - arg;
2517 if (!next || noreplace) {
2518 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
2523 } else if (!prefixcmp(next, "%(directory)")) {
2526 } else if (!prefixcmp(next, "%(file)")) {
2529 } else if (!prefixcmp(next, "%(ref)")) {
2530 value = *opt_ref ? opt_ref : "HEAD";
2532 } else if (!prefixcmp(next, "%(head)")) {
2535 } else if (!prefixcmp(next, "%(commit)")) {
2538 } else if (!prefixcmp(next, "%(blob)")) {
2542 report("Unknown replacement: `%s`", next);
2546 if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
2549 arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
2552 dst_argv[argc] = strdup(buf);
2553 if (!dst_argv[argc])
2557 dst_argv[argc] = NULL;
2559 return src_argv[argc] == NULL;
2563 end_update(struct view *view, bool force)
2567 while (!view->ops->read(view, NULL))
2570 set_nonblocking_input(FALSE);
2572 kill_io(view->pipe);
2573 done_io(view->pipe);
2578 setup_update(struct view *view, const char *vid)
2580 set_nonblocking_input(TRUE);
2582 string_copy_rev(view->vid, vid);
2583 view->pipe = &view->io;
2584 view->start_time = time(NULL);
2588 prepare_update(struct view *view, const char *argv[], const char *dir,
2589 enum format_flags flags)
2592 end_update(view, TRUE);
2593 return init_io_rd(&view->io, argv, dir, flags);
2597 prepare_update_file(struct view *view, const char *name)
2600 end_update(view, TRUE);
2601 return io_open(&view->io, name);
2605 begin_update(struct view *view, bool refresh)
2608 if (!start_io(&view->io))
2612 if (view == VIEW(REQ_VIEW_TREE) && strcmp(view->vid, view->id))
2615 if (!run_io_rd(&view->io, view->ops->argv, FORMAT_ALL))
2618 /* Put the current ref_* value to the view title ref
2619 * member. This is needed by the blob view. Most other
2620 * views sets it automatically after loading because the
2621 * first line is a commit line. */
2622 string_copy_rev(view->ref, view->id);
2625 setup_update(view, view->id);
2630 #define ITEM_CHUNK_SIZE 256
2632 realloc_items(void *mem, size_t *size, size_t new_size, size_t item_size)
2634 size_t num_chunks = *size / ITEM_CHUNK_SIZE;
2635 size_t num_chunks_new = (new_size + ITEM_CHUNK_SIZE - 1) / ITEM_CHUNK_SIZE;
2637 if (mem == NULL || num_chunks != num_chunks_new) {
2638 *size = num_chunks_new * ITEM_CHUNK_SIZE;
2639 mem = realloc(mem, *size * item_size);
2645 static struct line *
2646 realloc_lines(struct view *view, size_t line_size)
2648 size_t alloc = view->line_alloc;
2649 struct line *tmp = realloc_items(view->line, &alloc, line_size,
2650 sizeof(*view->line));
2656 view->line_alloc = alloc;
2657 view->line_size = line_size;
2662 update_view(struct view *view)
2664 char out_buffer[BUFSIZ * 2];
2666 /* The number of lines to read. If too low it will cause too much
2667 * redrawing (and possible flickering), if too high responsiveness
2669 unsigned long lines = view->height;
2670 int redraw_from = -1;
2675 /* Only redraw if lines are visible. */
2676 if (view->offset + view->height >= view->lines)
2677 redraw_from = view->lines - view->offset;
2679 /* FIXME: This is probably not perfect for backgrounded views. */
2680 if (!realloc_lines(view, view->lines + lines))
2683 while ((line = io_gets(view->pipe))) {
2684 size_t linelen = strlen(line);
2686 if (opt_iconv != ICONV_NONE) {
2687 ICONV_CONST char *inbuf = line;
2688 size_t inlen = linelen;
2690 char *outbuf = out_buffer;
2691 size_t outlen = sizeof(out_buffer);
2695 ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2696 if (ret != (size_t) -1) {
2698 linelen = strlen(out_buffer);
2702 if (!view->ops->read(view, line))
2712 lines = view->lines;
2713 for (digits = 0; lines; digits++)
2716 /* Keep the displayed view in sync with line number scaling. */
2717 if (digits != view->digits) {
2718 view->digits = digits;
2723 if (io_error(view->pipe)) {
2724 report("Failed to read: %s", io_strerror(view->pipe));
2725 end_update(view, TRUE);
2727 } else if (io_eof(view->pipe)) {
2729 end_update(view, FALSE);
2732 if (!view_is_displayed(view))
2735 if (view == VIEW(REQ_VIEW_TREE)) {
2736 /* Clear the view and redraw everything since the tree sorting
2737 * might have rearranged things. */
2740 } else if (redraw_from >= 0) {
2741 /* If this is an incremental update, redraw the previous line
2742 * since for commits some members could have changed when
2743 * loading the main view. */
2744 if (redraw_from > 0)
2747 /* Since revision graph visualization requires knowledge
2748 * about the parent commit, it causes a further one-off
2749 * needed to be redrawn for incremental updates. */
2750 if (redraw_from > 0 && opt_rev_graph)
2753 /* Incrementally draw avoids flickering. */
2754 redraw_view_from(view, redraw_from);
2757 if (view == VIEW(REQ_VIEW_BLAME))
2758 redraw_view_dirty(view);
2760 /* Update the title _after_ the redraw so that if the redraw picks up a
2761 * commit reference in view->ref it'll be available here. */
2762 update_view_title(view);
2766 report("Allocation failure");
2767 end_update(view, TRUE);
2771 static struct line *
2772 add_line_data(struct view *view, void *data, enum line_type type)
2774 struct line *line = &view->line[view->lines++];
2776 memset(line, 0, sizeof(*line));
2783 static struct line *
2784 add_line_text(struct view *view, const char *text, enum line_type type)
2786 char *data = text ? strdup(text) : NULL;
2788 return data ? add_line_data(view, data, type) : NULL;
2797 OPEN_DEFAULT = 0, /* Use default view switching. */
2798 OPEN_SPLIT = 1, /* Split current view. */
2799 OPEN_BACKGROUNDED = 2, /* Backgrounded. */
2800 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
2801 OPEN_NOMAXIMIZE = 8, /* Do not maximize the current view. */
2802 OPEN_REFRESH = 16, /* Refresh view using previous command. */
2803 OPEN_PREPARED = 32, /* Open already prepared command. */
2807 open_view(struct view *prev, enum request request, enum open_flags flags)
2809 bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
2810 bool split = !!(flags & OPEN_SPLIT);
2811 bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
2812 bool nomaximize = !!(flags & (OPEN_NOMAXIMIZE | OPEN_REFRESH));
2813 struct view *view = VIEW(request);
2814 int nviews = displayed_views();
2815 struct view *base_view = display[0];
2817 if (view == prev && nviews == 1 && !reload) {
2818 report("Already in %s view", view->name);
2822 if (view->git_dir && !opt_git_dir[0]) {
2823 report("The %s view is disabled in pager view", view->name);
2831 } else if (!nomaximize) {
2832 /* Maximize the current view. */
2833 memset(display, 0, sizeof(display));
2835 display[current_view] = view;
2838 /* Resize the view when switching between split- and full-screen,
2839 * or when switching between two different full-screen views. */
2840 if (nviews != displayed_views() ||
2841 (nviews == 1 && base_view != display[0]))
2845 end_update(view, TRUE);
2847 if (view->ops->open) {
2848 if (!view->ops->open(view)) {
2849 report("Failed to load %s view", view->name);
2853 } else if ((reload || strcmp(view->vid, view->id)) &&
2854 !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
2855 report("Failed to load %s view", view->name);
2859 if (split && prev->lineno - prev->offset >= prev->height) {
2860 /* Take the title line into account. */
2861 int lines = prev->lineno - prev->offset - prev->height + 1;
2863 /* Scroll the view that was split if the current line is
2864 * outside the new limited view. */
2865 do_scroll_view(prev, lines);
2868 if (prev && view != prev) {
2869 if (split && !backgrounded) {
2870 /* "Blur" the previous view. */
2871 update_view_title(prev);
2874 view->parent = prev;
2877 if (view->pipe && view->lines == 0) {
2878 /* Clear the old view and let the incremental updating refill
2882 } else if (view_is_displayed(view)) {
2887 /* If the view is backgrounded the above calls to report()
2888 * won't redraw the view title. */
2890 update_view_title(view);
2894 open_external_viewer(const char *argv[], const char *dir)
2896 def_prog_mode(); /* save current tty modes */
2897 endwin(); /* restore original tty modes */
2898 run_io_fg(argv, dir);
2899 fprintf(stderr, "Press Enter to continue");
2906 open_mergetool(const char *file)
2908 const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
2910 open_external_viewer(mergetool_argv, NULL);
2914 open_editor(bool from_root, const char *file)
2916 const char *editor_argv[] = { "vi", file, NULL };
2919 editor = getenv("GIT_EDITOR");
2920 if (!editor && *opt_editor)
2921 editor = opt_editor;
2923 editor = getenv("VISUAL");
2925 editor = getenv("EDITOR");
2929 editor_argv[0] = editor;
2930 open_external_viewer(editor_argv, from_root ? opt_cdup : NULL);
2934 open_run_request(enum request request)
2936 struct run_request *req = get_run_request(request);
2937 const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
2940 report("Unknown run request");
2944 if (format_argv(argv, req->argv, FORMAT_ALL))
2945 open_external_viewer(argv, NULL);
2950 * User request switch noodle
2954 view_driver(struct view *view, enum request request)
2958 if (request == REQ_NONE) {
2963 if (request > REQ_NONE) {
2964 open_run_request(request);
2965 /* FIXME: When all views can refresh always do this. */
2966 if (view == VIEW(REQ_VIEW_STATUS) ||
2967 view == VIEW(REQ_VIEW_MAIN) ||
2968 view == VIEW(REQ_VIEW_LOG) ||
2969 view == VIEW(REQ_VIEW_STAGE))
2970 request = REQ_REFRESH;
2975 if (view && view->lines) {
2976 request = view->ops->request(view, request, &view->line[view->lineno]);
2977 if (request == REQ_NONE)
2984 case REQ_MOVE_PAGE_UP:
2985 case REQ_MOVE_PAGE_DOWN:
2986 case REQ_MOVE_FIRST_LINE:
2987 case REQ_MOVE_LAST_LINE:
2988 move_view(view, request);
2991 case REQ_SCROLL_LINE_DOWN:
2992 case REQ_SCROLL_LINE_UP:
2993 case REQ_SCROLL_PAGE_DOWN:
2994 case REQ_SCROLL_PAGE_UP:
2995 scroll_view(view, request);
2998 case REQ_VIEW_BLAME:
3000 report("No file chosen, press %s to open tree view",
3001 get_key(REQ_VIEW_TREE));
3004 open_view(view, request, OPEN_DEFAULT);
3009 report("No file chosen, press %s to open tree view",
3010 get_key(REQ_VIEW_TREE));
3013 open_view(view, request, OPEN_DEFAULT);
3016 case REQ_VIEW_PAGER:
3017 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3018 report("No pager content, press %s to run command from prompt",
3019 get_key(REQ_PROMPT));
3022 open_view(view, request, OPEN_DEFAULT);
3025 case REQ_VIEW_STAGE:
3026 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3027 report("No stage content, press %s to open the status view and choose file",
3028 get_key(REQ_VIEW_STATUS));
3031 open_view(view, request, OPEN_DEFAULT);
3034 case REQ_VIEW_STATUS:
3035 if (opt_is_inside_work_tree == FALSE) {
3036 report("The status view requires a working tree");
3039 open_view(view, request, OPEN_DEFAULT);
3047 open_view(view, request, OPEN_DEFAULT);
3052 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3054 if ((view == VIEW(REQ_VIEW_DIFF) &&
3055 view->parent == VIEW(REQ_VIEW_MAIN)) ||
3056 (view == VIEW(REQ_VIEW_DIFF) &&
3057 view->parent == VIEW(REQ_VIEW_BLAME)) ||
3058 (view == VIEW(REQ_VIEW_STAGE) &&
3059 view->parent == VIEW(REQ_VIEW_STATUS)) ||
3060 (view == VIEW(REQ_VIEW_BLOB) &&
3061 view->parent == VIEW(REQ_VIEW_TREE))) {
3064 view = view->parent;
3065 line = view->lineno;
3066 move_view(view, request);
3067 if (view_is_displayed(view))
3068 update_view_title(view);
3069 if (line != view->lineno)
3070 view->ops->request(view, REQ_ENTER,
3071 &view->line[view->lineno]);
3074 move_view(view, request);
3080 int nviews = displayed_views();
3081 int next_view = (current_view + 1) % nviews;
3083 if (next_view == current_view) {
3084 report("Only one view is displayed");
3088 current_view = next_view;
3089 /* Blur out the title of the previous view. */
3090 update_view_title(view);
3095 report("Refreshing is not yet supported for the %s view", view->name);
3099 if (displayed_views() == 2)
3100 open_view(view, VIEW_REQ(view), OPEN_DEFAULT);
3103 case REQ_TOGGLE_LINENO:
3104 opt_line_number = !opt_line_number;
3108 case REQ_TOGGLE_DATE:
3109 opt_date = !opt_date;
3113 case REQ_TOGGLE_AUTHOR:
3114 opt_author = !opt_author;
3118 case REQ_TOGGLE_REV_GRAPH:
3119 opt_rev_graph = !opt_rev_graph;
3123 case REQ_TOGGLE_REFS:
3124 opt_show_refs = !opt_show_refs;
3129 case REQ_SEARCH_BACK:
3130 search_view(view, request);
3135 find_next(view, request);
3138 case REQ_STOP_LOADING:
3139 for (i = 0; i < ARRAY_SIZE(views); i++) {
3142 report("Stopped loading the %s view", view->name),
3143 end_update(view, TRUE);
3147 case REQ_SHOW_VERSION:
3148 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3151 case REQ_SCREEN_RESIZE:
3154 case REQ_SCREEN_REDRAW:
3159 report("Nothing to edit");
3163 report("Nothing to enter");
3166 case REQ_VIEW_CLOSE:
3167 /* XXX: Mark closed views by letting view->parent point to the
3168 * view itself. Parents to closed view should never be
3171 view->parent->parent != view->parent) {
3172 memset(display, 0, sizeof(display));
3174 display[current_view] = view->parent;
3175 view->parent = view;
3186 report("Unknown key, press 'h' for help");
3199 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3201 char *text = line->data;
3203 if (opt_line_number && draw_lineno(view, lineno))
3206 draw_text(view, line->type, text, TRUE);
3211 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3213 const char *describe_argv[] = { "git", "describe", commit_id, NULL };
3214 char refbuf[SIZEOF_STR];
3217 if (run_io_buf(describe_argv, refbuf, sizeof(refbuf)))
3218 ref = chomp_string(refbuf);
3223 /* This is the only fatal call, since it can "corrupt" the buffer. */
3224 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
3231 add_pager_refs(struct view *view, struct line *line)
3233 char buf[SIZEOF_STR];
3234 char *commit_id = (char *)line->data + STRING_SIZE("commit ");
3236 size_t bufpos = 0, refpos = 0;
3237 const char *sep = "Refs: ";
3238 bool is_tag = FALSE;
3240 assert(line->type == LINE_COMMIT);
3242 refs = get_refs(commit_id);
3244 if (view == VIEW(REQ_VIEW_DIFF))
3245 goto try_add_describe_ref;
3250 struct ref *ref = refs[refpos];
3251 const char *fmt = ref->tag ? "%s[%s]" :
3252 ref->remote ? "%s<%s>" : "%s%s";
3254 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
3259 } while (refs[refpos++]->next);
3261 if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
3262 try_add_describe_ref:
3263 /* Add <tag>-g<commit_id> "fake" reference. */
3264 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
3271 if (!realloc_lines(view, view->line_size + 1))
3274 add_line_text(view, buf, LINE_PP_REFS);
3278 pager_read(struct view *view, char *data)
3285 line = add_line_text(view, data, get_line_type(data));
3289 if (line->type == LINE_COMMIT &&
3290 (view == VIEW(REQ_VIEW_DIFF) ||
3291 view == VIEW(REQ_VIEW_LOG)))
3292 add_pager_refs(view, line);
3298 pager_request(struct view *view, enum request request, struct line *line)
3302 if (request != REQ_ENTER)
3305 if (line->type == LINE_COMMIT &&
3306 (view == VIEW(REQ_VIEW_LOG) ||
3307 view == VIEW(REQ_VIEW_PAGER))) {
3308 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3312 /* Always scroll the view even if it was split. That way
3313 * you can use Enter to scroll through the log view and
3314 * split open each commit diff. */
3315 scroll_view(view, REQ_SCROLL_LINE_DOWN);
3317 /* FIXME: A minor workaround. Scrolling the view will call report("")
3318 * but if we are scrolling a non-current view this won't properly
3319 * update the view title. */
3321 update_view_title(view);
3327 pager_grep(struct view *view, struct line *line)
3330 char *text = line->data;
3335 if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
3342 pager_select(struct view *view, struct line *line)
3344 if (line->type == LINE_COMMIT) {
3345 char *text = (char *)line->data + STRING_SIZE("commit ");
3347 if (view != VIEW(REQ_VIEW_PAGER))
3348 string_copy_rev(view->ref, text);
3349 string_copy_rev(ref_commit, text);
3353 static struct view_ops pager_ops = {
3364 static const char *log_argv[SIZEOF_ARG] = {
3365 "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3369 log_request(struct view *view, enum request request, struct line *line)
3374 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
3377 return pager_request(view, request, line);
3381 static struct view_ops log_ops = {
3392 static const char *diff_argv[SIZEOF_ARG] = {
3393 "git", "show", "--pretty=fuller", "--no-color", "--root",
3394 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
3397 static struct view_ops diff_ops = {
3413 help_open(struct view *view)
3416 int lines = ARRAY_SIZE(req_info) + 2;
3419 if (view->lines > 0)
3422 for (i = 0; i < ARRAY_SIZE(req_info); i++)
3423 if (!req_info[i].request)
3426 lines += run_requests + 1;
3428 view->line = calloc(lines, sizeof(*view->line));
3432 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
3434 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
3437 if (req_info[i].request == REQ_NONE)
3440 if (!req_info[i].request) {
3441 add_line_text(view, "", LINE_DEFAULT);
3442 add_line_text(view, req_info[i].help, LINE_DEFAULT);
3446 key = get_key(req_info[i].request);
3448 key = "(no key defined)";
3450 if (!string_format(buf, " %-25s %s", key, req_info[i].help))
3453 add_line_text(view, buf, LINE_DEFAULT);
3457 add_line_text(view, "", LINE_DEFAULT);
3458 add_line_text(view, "External commands:", LINE_DEFAULT);
3461 for (i = 0; i < run_requests; i++) {
3462 struct run_request *req = get_run_request(REQ_NONE + i + 1);
3464 char cmd[SIZEOF_STR];
3471 key = get_key_name(req->key);
3473 key = "(no key defined)";
3475 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
3476 if (!string_format_from(cmd, &bufpos, "%s%s",
3477 argc ? " " : "", req->argv[argc]))
3480 if (!string_format(buf, " %-10s %-14s `%s`",
3481 keymap_table[req->keymap].name, key, cmd))
3484 add_line_text(view, buf, LINE_DEFAULT);
3490 static struct view_ops help_ops = {
3506 struct tree_stack_entry {
3507 struct tree_stack_entry *prev; /* Entry below this in the stack */
3508 unsigned long lineno; /* Line number to restore */
3509 char *name; /* Position of name in opt_path */
3512 /* The top of the path stack. */
3513 static struct tree_stack_entry *tree_stack = NULL;
3514 unsigned long tree_lineno = 0;
3517 pop_tree_stack_entry(void)
3519 struct tree_stack_entry *entry = tree_stack;
3521 tree_lineno = entry->lineno;
3523 tree_stack = entry->prev;
3528 push_tree_stack_entry(const char *name, unsigned long lineno)
3530 struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
3531 size_t pathlen = strlen(opt_path);
3536 entry->prev = tree_stack;
3537 entry->name = opt_path + pathlen;
3540 if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
3541 pop_tree_stack_entry();
3545 /* Move the current line to the first tree entry. */
3547 entry->lineno = lineno;
3550 /* Parse output from git-ls-tree(1):
3552 * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile
3553 * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README
3554 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
3555 * 100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38 web.conf
3558 #define SIZEOF_TREE_ATTR \
3559 STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t")
3561 #define TREE_UP_FORMAT "040000 tree %s\t.."
3564 tree_compare_entry(enum line_type type1, const char *name1,
3565 enum line_type type2, const char *name2)
3567 if (type1 != type2) {
3568 if (type1 == LINE_TREE_DIR)
3573 return strcmp(name1, name2);
3577 tree_path(struct line *line)
3579 const char *path = line->data;
3581 return path + SIZEOF_TREE_ATTR;
3585 tree_read(struct view *view, char *text)
3587 size_t textlen = text ? strlen(text) : 0;
3588 char buf[SIZEOF_STR];
3590 enum line_type type;
3591 bool first_read = view->lines == 0;
3595 if (textlen <= SIZEOF_TREE_ATTR)
3598 type = text[STRING_SIZE("100644 ")] == 't'
3599 ? LINE_TREE_DIR : LINE_TREE_FILE;
3602 /* Add path info line */
3603 if (!string_format(buf, "Directory path /%s", opt_path) ||
3604 !realloc_lines(view, view->line_size + 1) ||
3605 !add_line_text(view, buf, LINE_DEFAULT))
3608 /* Insert "link" to parent directory. */
3610 if (!string_format(buf, TREE_UP_FORMAT, view->ref) ||
3611 !realloc_lines(view, view->line_size + 1) ||
3612 !add_line_text(view, buf, LINE_TREE_DIR))
3617 /* Strip the path part ... */
3619 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
3620 size_t striplen = strlen(opt_path);
3621 char *path = text + SIZEOF_TREE_ATTR;
3623 if (pathlen > striplen)
3624 memmove(path, path + striplen,
3625 pathlen - striplen + 1);
3628 /* Skip "Directory ..." and ".." line. */
3629 for (pos = 1 + !!*opt_path; pos < view->lines; pos++) {
3630 struct line *line = &view->line[pos];
3631 const char *path1 = tree_path(line);
3632 char *path2 = text + SIZEOF_TREE_ATTR;
3633 int cmp = tree_compare_entry(line->type, path1, type, path2);
3638 text = strdup(text);
3642 if (view->lines > pos)
3643 memmove(&view->line[pos + 1], &view->line[pos],
3644 (view->lines - pos) * sizeof(*line));
3646 line = &view->line[pos];
3653 if (!add_line_text(view, text, type))
3656 if (tree_lineno > view->lineno) {
3657 view->lineno = tree_lineno;
3665 tree_request(struct view *view, enum request request, struct line *line)
3667 enum open_flags flags;
3670 case REQ_VIEW_BLAME:
3671 if (line->type != LINE_TREE_FILE) {
3672 report("Blame only supported for files");
3676 string_copy(opt_ref, view->vid);
3680 if (line->type != LINE_TREE_FILE) {
3681 report("Edit only supported for files");
3682 } else if (!is_head_commit(view->vid)) {
3683 report("Edit only supported for files in the current work tree");
3685 open_editor(TRUE, opt_file);
3689 case REQ_TREE_PARENT:
3691 /* quit view if at top of tree */
3692 return REQ_VIEW_CLOSE;
3695 line = &view->line[1];
3705 /* Cleanup the stack if the tree view is at a different tree. */
3706 while (!*opt_path && tree_stack)
3707 pop_tree_stack_entry();
3709 switch (line->type) {
3711 /* Depending on whether it is a subdir or parent (updir?) link
3712 * mangle the path buffer. */
3713 if (line == &view->line[1] && *opt_path) {
3714 pop_tree_stack_entry();
3717 const char *basename = tree_path(line);
3719 push_tree_stack_entry(basename, view->lineno);
3722 /* Trees and subtrees share the same ID, so they are not not
3723 * unique like blobs. */
3724 flags = OPEN_RELOAD;
3725 request = REQ_VIEW_TREE;
3728 case LINE_TREE_FILE:
3729 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
3730 request = REQ_VIEW_BLOB;
3737 open_view(view, request, flags);
3738 if (request == REQ_VIEW_TREE) {
3739 view->lineno = tree_lineno;
3746 tree_select(struct view *view, struct line *line)
3748 char *text = (char *)line->data + STRING_SIZE("100644 blob ");
3750 if (line->type == LINE_TREE_FILE) {
3751 string_copy_rev(ref_blob, text);
3752 string_format(opt_file, "%s%s", opt_path, tree_path(line));
3754 } else if (line->type != LINE_TREE_DIR) {
3758 string_copy_rev(view->ref, text);
3761 static const char *tree_argv[SIZEOF_ARG] = {
3762 "git", "ls-tree", "%(commit)", "%(directory)", NULL
3765 static struct view_ops tree_ops = {
3777 blob_read(struct view *view, char *line)
3781 return add_line_text(view, line, LINE_DEFAULT) != NULL;
3784 static const char *blob_argv[SIZEOF_ARG] = {
3785 "git", "cat-file", "blob", "%(blob)", NULL
3788 static struct view_ops blob_ops = {
3802 * Loading the blame view is a two phase job:
3804 * 1. File content is read either using opt_file from the
3805 * filesystem or using git-cat-file.
3806 * 2. Then blame information is incrementally added by
3807 * reading output from git-blame.
3810 static const char *blame_head_argv[] = {
3811 "git", "blame", "--incremental", "--", "%(file)", NULL
3814 static const char *blame_ref_argv[] = {
3815 "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
3818 static const char *blame_cat_file_argv[] = {
3819 "git", "cat-file", "blob", "%(ref):%(file)", NULL
3822 struct blame_commit {
3823 char id[SIZEOF_REV]; /* SHA1 ID. */
3824 char title[128]; /* First line of the commit message. */
3825 char author[75]; /* Author of the commit. */
3826 struct tm time; /* Date from the author ident. */
3827 char filename[128]; /* Name of file. */
3831 struct blame_commit *commit;
3836 blame_open(struct view *view)
3838 if (*opt_ref || !io_open(&view->io, opt_file)) {
3839 if (!run_io_rd(&view->io, blame_cat_file_argv, FORMAT_ALL))
3843 setup_update(view, opt_file);
3844 string_format(view->ref, "%s ...", opt_file);
3849 static struct blame_commit *
3850 get_blame_commit(struct view *view, const char *id)
3854 for (i = 0; i < view->lines; i++) {
3855 struct blame *blame = view->line[i].data;
3860 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
3861 return blame->commit;
3865 struct blame_commit *commit = calloc(1, sizeof(*commit));
3868 string_ncopy(commit->id, id, SIZEOF_REV);
3874 parse_number(const char **posref, size_t *number, size_t min, size_t max)
3876 const char *pos = *posref;
3879 pos = strchr(pos + 1, ' ');
3880 if (!pos || !isdigit(pos[1]))
3882 *number = atoi(pos + 1);
3883 if (*number < min || *number > max)
3890 static struct blame_commit *
3891 parse_blame_commit(struct view *view, const char *text, int *blamed)
3893 struct blame_commit *commit;
3894 struct blame *blame;
3895 const char *pos = text + SIZEOF_REV - 1;
3899 if (strlen(text) <= SIZEOF_REV || *pos != ' ')
3902 if (!parse_number(&pos, &lineno, 1, view->lines) ||
3903 !parse_number(&pos, &group, 1, view->lines - lineno + 1))
3906 commit = get_blame_commit(view, text);
3912 struct line *line = &view->line[lineno + group - 1];
3915 blame->commit = commit;
3923 blame_read_file(struct view *view, const char *line, bool *read_file)
3926 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
3929 if (view->lines == 0 && !view->parent)
3930 die("No blame exist for %s", view->vid);
3932 if (view->lines == 0 || !run_io_rd(&io, argv, FORMAT_ALL)) {
3933 report("Failed to load blame data");
3937 done_io(view->pipe);
3943 size_t linelen = strlen(line);
3944 struct blame *blame = malloc(sizeof(*blame) + linelen);
3946 blame->commit = NULL;
3947 strncpy(blame->text, line, linelen);
3948 blame->text[linelen] = 0;
3949 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
3954 match_blame_header(const char *name, char **line)
3956 size_t namelen = strlen(name);
3957 bool matched = !strncmp(name, *line, namelen);
3966 blame_read(struct view *view, char *line)
3968 static struct blame_commit *commit = NULL;
3969 static int blamed = 0;
3970 static time_t author_time;
3971 static bool read_file = TRUE;
3974 return blame_read_file(view, line, &read_file);
3981 string_format(view->ref, "%s", view->vid);
3982 if (view_is_displayed(view)) {
3983 update_view_title(view);
3984 redraw_view_from(view, 0);
3990 commit = parse_blame_commit(view, line, &blamed);
3991 string_format(view->ref, "%s %2d%%", view->vid,
3992 blamed * 100 / view->lines);
3994 } else if (match_blame_header("author ", &line)) {
3995 string_ncopy(commit->author, line, strlen(line));
3997 } else if (match_blame_header("author-time ", &line)) {
3998 author_time = (time_t) atol(line);
4000 } else if (match_blame_header("author-tz ", &line)) {
4003 tz = ('0' - line[1]) * 60 * 60 * 10;
4004 tz += ('0' - line[2]) * 60 * 60;
4005 tz += ('0' - line[3]) * 60;
4006 tz += ('0' - line[4]) * 60;
4012 gmtime_r(&author_time, &commit->time);
4014 } else if (match_blame_header("summary ", &line)) {
4015 string_ncopy(commit->title, line, strlen(line));
4017 } else if (match_blame_header("filename ", &line)) {
4018 string_ncopy(commit->filename, line, strlen(line));
4026 blame_draw(struct view *view, struct line *line, unsigned int lineno)
4028 struct blame *blame = line->data;
4029 struct tm *time = NULL;
4030 const char *id = NULL, *author = NULL;
4032 if (blame->commit && *blame->commit->filename) {
4033 id = blame->commit->id;
4034 author = blame->commit->author;
4035 time = &blame->commit->time;
4038 if (opt_date && draw_date(view, time))
4042 draw_field(view, LINE_MAIN_AUTHOR, author, opt_author_cols, TRUE))
4045 if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
4048 if (draw_lineno(view, lineno))
4051 draw_text(view, LINE_DEFAULT, blame->text, TRUE);
4056 blame_request(struct view *view, enum request request, struct line *line)
4058 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4059 struct blame *blame = line->data;
4062 case REQ_VIEW_BLAME:
4063 if (!blame->commit || !strcmp(blame->commit->id, NULL_ID)) {
4064 report("Commit ID unknown");
4067 string_copy(opt_ref, blame->commit->id);
4068 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4072 if (!blame->commit) {
4073 report("No commit loaded yet");
4077 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
4078 !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
4081 if (!strcmp(blame->commit->id, NULL_ID)) {
4082 struct view *diff = VIEW(REQ_VIEW_DIFF);
4083 const char *diff_index_argv[] = {
4084 "git", "diff-index", "--root", "--cached",
4085 "--patch-with-stat", "-C", "-M",
4086 "HEAD", "--", view->vid, NULL
4089 if (!prepare_update(diff, diff_index_argv, NULL, FORMAT_DASH)) {
4090 report("Failed to allocate diff command");
4093 flags |= OPEN_PREPARED;
4096 open_view(view, REQ_VIEW_DIFF, flags);
4107 blame_grep(struct view *view, struct line *line)
4109 struct blame *blame = line->data;
4110 struct blame_commit *commit = blame->commit;
4113 #define MATCH(text, on) \
4114 (on && *text && regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4117 char buf[DATE_COLS + 1];
4119 if (MATCH(commit->title, 1) ||
4120 MATCH(commit->author, opt_author) ||
4121 MATCH(commit->id, opt_date))
4124 if (strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time) &&
4129 return MATCH(blame->text, 1);
4135 blame_select(struct view *view, struct line *line)
4137 struct blame *blame = line->data;
4138 struct blame_commit *commit = blame->commit;
4143 if (!strcmp(commit->id, NULL_ID))
4144 string_ncopy(ref_commit, "HEAD", 4);
4146 string_copy_rev(ref_commit, commit->id);
4149 static struct view_ops blame_ops = {
4168 char rev[SIZEOF_REV];
4169 char name[SIZEOF_STR];
4173 char rev[SIZEOF_REV];
4174 char name[SIZEOF_STR];
4178 static char status_onbranch[SIZEOF_STR];
4179 static struct status stage_status;
4180 static enum line_type stage_line_type;
4181 static size_t stage_chunks;
4182 static int *stage_chunk;
4184 /* This should work even for the "On branch" line. */
4186 status_has_none(struct view *view, struct line *line)
4188 return line < view->line + view->lines && !line[1].data;
4191 /* Get fields from the diff line:
4192 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
4195 status_get_diff(struct status *file, const char *buf, size_t bufsize)
4197 const char *old_mode = buf + 1;
4198 const char *new_mode = buf + 8;
4199 const char *old_rev = buf + 15;
4200 const char *new_rev = buf + 56;
4201 const char *status = buf + 97;
4204 old_mode[-1] != ':' ||
4205 new_mode[-1] != ' ' ||
4206 old_rev[-1] != ' ' ||
4207 new_rev[-1] != ' ' ||
4211 file->status = *status;
4213 string_copy_rev(file->old.rev, old_rev);
4214 string_copy_rev(file->new.rev, new_rev);
4216 file->old.mode = strtoul(old_mode, NULL, 8);
4217 file->new.mode = strtoul(new_mode, NULL, 8);
4219 file->old.name[0] = file->new.name[0] = 0;
4225 status_run(struct view *view, const char *argv[], char status, enum line_type type)
4227 struct status *file = NULL;
4228 struct status *unmerged = NULL;
4229 char buf[SIZEOF_STR * 4];
4233 if (!run_io(&io, argv, NULL, IO_RD))
4236 add_line_data(view, NULL, type);
4238 while (!io_eof(&io)) {
4242 readsize = io_read(&io, buf + bufsize, sizeof(buf) - bufsize);
4245 bufsize += readsize;
4247 /* Process while we have NUL chars. */
4248 while ((sep = memchr(buf, 0, bufsize))) {
4249 size_t sepsize = sep - buf + 1;
4252 if (!realloc_lines(view, view->line_size + 1))
4255 file = calloc(1, sizeof(*file));
4259 add_line_data(view, file, type);
4262 /* Parse diff info part. */
4264 file->status = status;
4266 string_copy(file->old.rev, NULL_ID);
4268 } else if (!file->status) {
4269 if (!status_get_diff(file, buf, sepsize))
4273 memmove(buf, sep + 1, bufsize);
4275 sep = memchr(buf, 0, bufsize);
4278 sepsize = sep - buf + 1;
4280 /* Collapse all 'M'odified entries that
4281 * follow a associated 'U'nmerged entry.
4283 if (file->status == 'U') {
4286 } else if (unmerged) {
4287 int collapse = !strcmp(buf, unmerged->new.name);
4298 /* Grab the old name for rename/copy. */
4299 if (!*file->old.name &&
4300 (file->status == 'R' || file->status == 'C')) {
4301 sepsize = sep - buf + 1;
4302 string_ncopy(file->old.name, buf, sepsize);
4304 memmove(buf, sep + 1, bufsize);
4306 sep = memchr(buf, 0, bufsize);
4309 sepsize = sep - buf + 1;
4312 /* git-ls-files just delivers a NUL separated
4313 * list of file names similar to the second half
4314 * of the git-diff-* output. */
4315 string_ncopy(file->new.name, buf, sepsize);
4316 if (!*file->old.name)
4317 string_copy(file->old.name, file->new.name);
4319 memmove(buf, sep + 1, bufsize);
4324 if (io_error(&io)) {
4330 if (!view->line[view->lines - 1].data)
4331 add_line_data(view, NULL, LINE_STAT_NONE);
4337 /* Don't show unmerged entries in the staged section. */
4338 static const char *status_diff_index_argv[] = {
4339 "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
4340 "--cached", "-M", "HEAD", NULL
4343 static const char *status_diff_files_argv[] = {
4344 "git", "diff-files", "-z", NULL
4347 static const char *status_list_other_argv[] = {
4348 "git", "ls-files", "-z", "--others", "--exclude-standard", NULL
4351 static const char *status_list_no_head_argv[] = {
4352 "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
4355 static const char *update_index_argv[] = {
4356 "git", "update-index", "-q", "--unmerged", "--refresh", NULL
4359 /* First parse staged info using git-diff-index(1), then parse unstaged
4360 * info using git-diff-files(1), and finally untracked files using
4361 * git-ls-files(1). */
4363 status_open(struct view *view)
4365 unsigned long prev_lineno = view->lineno;
4369 if (!realloc_lines(view, view->line_size + 7))
4372 add_line_data(view, NULL, LINE_STAT_HEAD);
4373 if (is_initial_commit())
4374 string_copy(status_onbranch, "Initial commit");
4375 else if (!*opt_head)
4376 string_copy(status_onbranch, "Not currently on any branch");
4377 else if (!string_format(status_onbranch, "On branch %s", opt_head))
4380 run_io_bg(update_index_argv);
4382 if (is_initial_commit()) {
4383 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
4385 } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
4389 if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
4390 !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
4393 /* If all went well restore the previous line number to stay in
4394 * the context or select a line with something that can be
4396 if (prev_lineno >= view->lines)
4397 prev_lineno = view->lines - 1;
4398 while (prev_lineno < view->lines && !view->line[prev_lineno].data)
4400 while (prev_lineno > 0 && !view->line[prev_lineno].data)
4403 /* If the above fails, always skip the "On branch" line. */
4404 if (prev_lineno < view->lines)
4405 view->lineno = prev_lineno;
4409 if (view->lineno < view->offset)
4410 view->offset = view->lineno;
4411 else if (view->offset + view->height <= view->lineno)
4412 view->offset = view->lineno - view->height + 1;
4418 status_draw(struct view *view, struct line *line, unsigned int lineno)
4420 struct status *status = line->data;
4421 enum line_type type;
4425 switch (line->type) {
4426 case LINE_STAT_STAGED:
4427 type = LINE_STAT_SECTION;
4428 text = "Changes to be committed:";
4431 case LINE_STAT_UNSTAGED:
4432 type = LINE_STAT_SECTION;
4433 text = "Changed but not updated:";
4436 case LINE_STAT_UNTRACKED:
4437 type = LINE_STAT_SECTION;
4438 text = "Untracked files:";
4441 case LINE_STAT_NONE:
4442 type = LINE_DEFAULT;
4443 text = " (no files)";
4446 case LINE_STAT_HEAD:
4447 type = LINE_STAT_HEAD;
4448 text = status_onbranch;
4455 static char buf[] = { '?', ' ', ' ', ' ', 0 };
4457 buf[0] = status->status;
4458 if (draw_text(view, line->type, buf, TRUE))
4460 type = LINE_DEFAULT;
4461 text = status->new.name;
4464 draw_text(view, type, text, TRUE);
4469 status_enter(struct view *view, struct line *line)
4471 struct status *status = line->data;
4472 const char *oldpath = status ? status->old.name : NULL;
4473 /* Diffs for unmerged entries are empty when passing the new
4474 * path, so leave it empty. */
4475 const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
4477 enum open_flags split;
4478 struct view *stage = VIEW(REQ_VIEW_STAGE);
4480 if (line->type == LINE_STAT_NONE ||
4481 (!status && line[1].type == LINE_STAT_NONE)) {
4482 report("No file to diff");
4486 switch (line->type) {
4487 case LINE_STAT_STAGED:
4488 if (is_initial_commit()) {
4489 const char *no_head_diff_argv[] = {
4490 "git", "diff", "--no-color", "--patch-with-stat",
4491 "--", "/dev/null", newpath, NULL
4494 if (!prepare_update(stage, no_head_diff_argv, opt_cdup, FORMAT_DASH))
4497 const char *index_show_argv[] = {
4498 "git", "diff-index", "--root", "--patch-with-stat",
4499 "-C", "-M", "--cached", "HEAD", "--",
4500 oldpath, newpath, NULL
4503 if (!prepare_update(stage, index_show_argv, opt_cdup, FORMAT_DASH))
4508 info = "Staged changes to %s";
4510 info = "Staged changes";
4513 case LINE_STAT_UNSTAGED:
4515 const char *files_show_argv[] = {
4516 "git", "diff-files", "--root", "--patch-with-stat",
4517 "-C", "-M", "--", oldpath, newpath, NULL
4520 if (!prepare_update(stage, files_show_argv, opt_cdup, FORMAT_DASH))
4523 info = "Unstaged changes to %s";
4525 info = "Unstaged changes";
4528 case LINE_STAT_UNTRACKED:
4530 report("No file to show");
4534 if (!suffixcmp(status->new.name, -1, "/")) {
4535 report("Cannot display a directory");
4539 if (!prepare_update_file(stage, newpath))
4541 info = "Untracked file %s";
4544 case LINE_STAT_HEAD:
4548 die("line type %d not handled in switch", line->type);
4551 split = view_is_displayed(view) ? OPEN_SPLIT : 0;
4552 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH | split);
4553 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
4555 stage_status = *status;
4557 memset(&stage_status, 0, sizeof(stage_status));
4560 stage_line_type = line->type;
4562 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
4569 status_exists(struct status *status, enum line_type type)
4571 struct view *view = VIEW(REQ_VIEW_STATUS);
4574 for (line = view->line; line < view->line + view->lines; line++) {
4575 struct status *pos = line->data;
4577 if (line->type == type && pos &&
4578 !strcmp(status->new.name, pos->new.name))
4587 status_update_prepare(struct io *io, enum line_type type)
4589 const char *staged_argv[] = {
4590 "git", "update-index", "-z", "--index-info", NULL
4592 const char *others_argv[] = {
4593 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
4597 case LINE_STAT_STAGED:
4598 return run_io(io, staged_argv, opt_cdup, IO_WR);
4600 case LINE_STAT_UNSTAGED:
4601 return run_io(io, others_argv, opt_cdup, IO_WR);
4603 case LINE_STAT_UNTRACKED:
4604 return run_io(io, others_argv, NULL, IO_WR);
4607 die("line type %d not handled in switch", type);
4613 status_update_write(struct io *io, struct status *status, enum line_type type)
4615 char buf[SIZEOF_STR];
4619 case LINE_STAT_STAGED:
4620 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
4623 status->old.name, 0))
4627 case LINE_STAT_UNSTAGED:
4628 case LINE_STAT_UNTRACKED:
4629 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
4634 die("line type %d not handled in switch", type);
4637 return io_write(io, buf, bufsize);
4641 status_update_file(struct status *status, enum line_type type)
4646 if (!status_update_prepare(&io, type))
4649 result = status_update_write(&io, status, type);
4655 status_update_files(struct view *view, struct line *line)
4659 struct line *pos = view->line + view->lines;
4663 if (!status_update_prepare(&io, line->type))
4666 for (pos = line; pos < view->line + view->lines && pos->data; pos++)
4669 for (file = 0, done = 0; result && file < files; line++, file++) {
4670 int almost_done = file * 100 / files;
4672 if (almost_done > done) {
4674 string_format(view->ref, "updating file %u of %u (%d%% done)",
4676 update_view_title(view);
4678 result = status_update_write(&io, line->data, line->type);
4686 status_update(struct view *view)
4688 struct line *line = &view->line[view->lineno];
4690 assert(view->lines);
4693 /* This should work even for the "On branch" line. */
4694 if (line < view->line + view->lines && !line[1].data) {
4695 report("Nothing to update");
4699 if (!status_update_files(view, line + 1)) {
4700 report("Failed to update file status");
4704 } else if (!status_update_file(line->data, line->type)) {
4705 report("Failed to update file status");
4713 status_revert(struct status *status, enum line_type type, bool has_none)
4715 if (!status || type != LINE_STAT_UNSTAGED) {
4716 if (type == LINE_STAT_STAGED) {
4717 report("Cannot revert changes to staged files");
4718 } else if (type == LINE_STAT_UNTRACKED) {
4719 report("Cannot revert changes to untracked files");
4720 } else if (has_none) {
4721 report("Nothing to revert");
4723 report("Cannot revert changes to multiple files");
4728 const char *checkout_argv[] = {
4729 "git", "checkout", "--", status->old.name, NULL
4732 if (!prompt_yesno("Are you sure you want to overwrite any changes?"))
4734 return run_io_fg(checkout_argv, opt_cdup);
4739 status_request(struct view *view, enum request request, struct line *line)
4741 struct status *status = line->data;
4744 case REQ_STATUS_UPDATE:
4745 if (!status_update(view))
4749 case REQ_STATUS_REVERT:
4750 if (!status_revert(status, line->type, status_has_none(view, line)))
4754 case REQ_STATUS_MERGE:
4755 if (!status || status->status != 'U') {
4756 report("Merging only possible for files with unmerged status ('U').");
4759 open_mergetool(status->new.name);
4765 if (status->status == 'D') {
4766 report("File has been deleted.");
4770 open_editor(status->status != '?', status->new.name);
4773 case REQ_VIEW_BLAME:
4775 string_copy(opt_file, status->new.name);
4781 /* After returning the status view has been split to
4782 * show the stage view. No further reloading is
4784 status_enter(view, line);
4788 /* Simply reload the view. */
4795 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
4801 status_select(struct view *view, struct line *line)
4803 struct status *status = line->data;
4804 char file[SIZEOF_STR] = "all files";
4808 if (status && !string_format(file, "'%s'", status->new.name))
4811 if (!status && line[1].type == LINE_STAT_NONE)
4814 switch (line->type) {
4815 case LINE_STAT_STAGED:
4816 text = "Press %s to unstage %s for commit";
4819 case LINE_STAT_UNSTAGED:
4820 text = "Press %s to stage %s for commit";
4823 case LINE_STAT_UNTRACKED:
4824 text = "Press %s to stage %s for addition";
4827 case LINE_STAT_HEAD:
4828 case LINE_STAT_NONE:
4829 text = "Nothing to update";
4833 die("line type %d not handled in switch", line->type);
4836 if (status && status->status == 'U') {
4837 text = "Press %s to resolve conflict in %s";
4838 key = get_key(REQ_STATUS_MERGE);
4841 key = get_key(REQ_STATUS_UPDATE);
4844 string_format(view->ref, text, key, file);
4848 status_grep(struct view *view, struct line *line)
4850 struct status *status = line->data;
4851 enum { S_STATUS, S_NAME, S_END } state;
4858 for (state = S_STATUS; state < S_END; state++) {
4862 case S_NAME: text = status->new.name; break;
4864 buf[0] = status->status;
4872 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4879 static struct view_ops status_ops = {
4892 stage_diff_write(struct io *io, struct line *line, struct line *end)
4894 while (line < end) {
4895 if (!io_write(io, line->data, strlen(line->data)) ||
4896 !io_write(io, "\n", 1))
4899 if (line->type == LINE_DIFF_CHUNK ||
4900 line->type == LINE_DIFF_HEADER)
4907 static struct line *
4908 stage_diff_find(struct view *view, struct line *line, enum line_type type)
4910 for (; view->line < line; line--)
4911 if (line->type == type)
4918 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
4920 const char *apply_argv[SIZEOF_ARG] = {
4921 "git", "apply", "--whitespace=nowarn", NULL
4923 struct line *diff_hdr;
4927 diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
4932 apply_argv[argc++] = "--cached";
4933 if (revert || stage_line_type == LINE_STAT_STAGED)
4934 apply_argv[argc++] = "-R";
4935 apply_argv[argc++] = "-";
4936 apply_argv[argc++] = NULL;
4937 if (!run_io(&io, apply_argv, opt_cdup, IO_WR))
4940 if (!stage_diff_write(&io, diff_hdr, chunk) ||
4941 !stage_diff_write(&io, chunk, view->line + view->lines))
4945 run_io_bg(update_index_argv);
4947 return chunk ? TRUE : FALSE;
4951 stage_update(struct view *view, struct line *line)
4953 struct line *chunk = NULL;
4955 if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
4956 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
4959 if (!stage_apply_chunk(view, chunk, FALSE)) {
4960 report("Failed to apply chunk");
4964 } else if (!stage_status.status) {
4965 view = VIEW(REQ_VIEW_STATUS);
4967 for (line = view->line; line < view->line + view->lines; line++)
4968 if (line->type == stage_line_type)
4971 if (!status_update_files(view, line + 1)) {
4972 report("Failed to update files");
4976 } else if (!status_update_file(&stage_status, stage_line_type)) {
4977 report("Failed to update file");
4985 stage_revert(struct view *view, struct line *line)
4987 struct line *chunk = NULL;
4989 if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
4990 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
4993 if (!prompt_yesno("Are you sure you want to revert changes?"))
4996 if (!stage_apply_chunk(view, chunk, TRUE)) {
4997 report("Failed to revert chunk");
5003 return status_revert(stage_status.status ? &stage_status : NULL,
5004 stage_line_type, FALSE);
5010 stage_next(struct view *view, struct line *line)
5014 if (!stage_chunks) {
5015 static size_t alloc = 0;
5018 for (line = view->line; line < view->line + view->lines; line++) {
5019 if (line->type != LINE_DIFF_CHUNK)
5022 tmp = realloc_items(stage_chunk, &alloc,
5023 stage_chunks, sizeof(*tmp));
5025 report("Allocation failure");
5030 stage_chunk[stage_chunks++] = line - view->line;
5034 for (i = 0; i < stage_chunks; i++) {
5035 if (stage_chunk[i] > view->lineno) {
5036 do_scroll_view(view, stage_chunk[i] - view->lineno);
5037 report("Chunk %d of %d", i + 1, stage_chunks);
5042 report("No next chunk found");
5046 stage_request(struct view *view, enum request request, struct line *line)
5049 case REQ_STATUS_UPDATE:
5050 if (!stage_update(view, line))
5054 case REQ_STATUS_REVERT:
5055 if (!stage_revert(view, line))
5059 case REQ_STAGE_NEXT:
5060 if (stage_line_type == LINE_STAT_UNTRACKED) {
5061 report("File is untracked; press %s to add",
5062 get_key(REQ_STATUS_UPDATE));
5065 stage_next(view, line);
5069 if (!stage_status.new.name[0])
5071 if (stage_status.status == 'D') {
5072 report("File has been deleted.");
5076 open_editor(stage_status.status != '?', stage_status.new.name);
5080 /* Reload everything ... */
5083 case REQ_VIEW_BLAME:
5084 if (stage_status.new.name[0]) {
5085 string_copy(opt_file, stage_status.new.name);
5091 return pager_request(view, request, line);
5097 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD | OPEN_NOMAXIMIZE);
5099 /* Check whether the staged entry still exists, and close the
5100 * stage view if it doesn't. */
5101 if (!status_exists(&stage_status, stage_line_type))
5102 return REQ_VIEW_CLOSE;
5104 if (stage_line_type == LINE_STAT_UNTRACKED) {
5105 if (!suffixcmp(stage_status.new.name, -1, "/")) {
5106 report("Cannot display a directory");
5110 if (!prepare_update_file(view, stage_status.new.name)) {
5111 report("Failed to open file: %s", strerror(errno));
5115 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
5120 static struct view_ops stage_ops = {
5137 char id[SIZEOF_REV]; /* SHA1 ID. */
5138 char title[128]; /* First line of the commit message. */
5139 char author[75]; /* Author of the commit. */
5140 struct tm time; /* Date from the author ident. */
5141 struct ref **refs; /* Repository references. */
5142 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
5143 size_t graph_size; /* The width of the graph array. */
5144 bool has_parents; /* Rewritten --parents seen. */
5147 /* Size of rev graph with no "padding" columns */
5148 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
5151 struct rev_graph *prev, *next, *parents;
5152 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
5154 struct commit *commit;
5156 unsigned int boundary:1;
5159 /* Parents of the commit being visualized. */
5160 static struct rev_graph graph_parents[4];
5162 /* The current stack of revisions on the graph. */
5163 static struct rev_graph graph_stacks[4] = {
5164 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
5165 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
5166 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
5167 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
5171 graph_parent_is_merge(struct rev_graph *graph)
5173 return graph->parents->size > 1;
5177 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
5179 struct commit *commit = graph->commit;
5181 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
5182 commit->graph[commit->graph_size++] = symbol;
5186 clear_rev_graph(struct rev_graph *graph)
5188 graph->boundary = 0;
5189 graph->size = graph->pos = 0;
5190 graph->commit = NULL;
5191 memset(graph->parents, 0, sizeof(*graph->parents));
5195 done_rev_graph(struct rev_graph *graph)
5197 if (graph_parent_is_merge(graph) &&
5198 graph->pos < graph->size - 1 &&
5199 graph->next->size == graph->size + graph->parents->size - 1) {
5200 size_t i = graph->pos + graph->parents->size - 1;
5202 graph->commit->graph_size = i * 2;
5203 while (i < graph->next->size - 1) {
5204 append_to_rev_graph(graph, ' ');
5205 append_to_rev_graph(graph, '\\');
5210 clear_rev_graph(graph);
5214 push_rev_graph(struct rev_graph *graph, const char *parent)
5218 /* "Collapse" duplicate parents lines.
5220 * FIXME: This needs to also update update the drawn graph but
5221 * for now it just serves as a method for pruning graph lines. */
5222 for (i = 0; i < graph->size; i++)
5223 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
5226 if (graph->size < SIZEOF_REVITEMS) {
5227 string_copy_rev(graph->rev[graph->size++], parent);
5232 get_rev_graph_symbol(struct rev_graph *graph)
5236 if (graph->boundary)
5237 symbol = REVGRAPH_BOUND;
5238 else if (graph->parents->size == 0)
5239 symbol = REVGRAPH_INIT;
5240 else if (graph_parent_is_merge(graph))
5241 symbol = REVGRAPH_MERGE;
5242 else if (graph->pos >= graph->size)
5243 symbol = REVGRAPH_BRANCH;
5245 symbol = REVGRAPH_COMMIT;
5251 draw_rev_graph(struct rev_graph *graph)
5254 chtype separator, line;
5256 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
5257 static struct rev_filler fillers[] = {
5263 chtype symbol = get_rev_graph_symbol(graph);
5264 struct rev_filler *filler;
5267 if (opt_line_graphics)
5268 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
5270 filler = &fillers[DEFAULT];
5272 for (i = 0; i < graph->pos; i++) {
5273 append_to_rev_graph(graph, filler->line);
5274 if (graph_parent_is_merge(graph->prev) &&
5275 graph->prev->pos == i)
5276 filler = &fillers[RSHARP];
5278 append_to_rev_graph(graph, filler->separator);
5281 /* Place the symbol for this revision. */
5282 append_to_rev_graph(graph, symbol);
5284 if (graph->prev->size > graph->size)
5285 filler = &fillers[RDIAG];
5287 filler = &fillers[DEFAULT];
5291 for (; i < graph->size; i++) {
5292 append_to_rev_graph(graph, filler->separator);
5293 append_to_rev_graph(graph, filler->line);
5294 if (graph_parent_is_merge(graph->prev) &&
5295 i < graph->prev->pos + graph->parents->size)
5296 filler = &fillers[RSHARP];
5297 if (graph->prev->size > graph->size)
5298 filler = &fillers[LDIAG];
5301 if (graph->prev->size > graph->size) {
5302 append_to_rev_graph(graph, filler->separator);
5303 if (filler->line != ' ')
5304 append_to_rev_graph(graph, filler->line);
5308 /* Prepare the next rev graph */
5310 prepare_rev_graph(struct rev_graph *graph)
5314 /* First, traverse all lines of revisions up to the active one. */
5315 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
5316 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
5319 push_rev_graph(graph->next, graph->rev[graph->pos]);
5322 /* Interleave the new revision parent(s). */
5323 for (i = 0; !graph->boundary && i < graph->parents->size; i++)
5324 push_rev_graph(graph->next, graph->parents->rev[i]);
5326 /* Lastly, put any remaining revisions. */
5327 for (i = graph->pos + 1; i < graph->size; i++)
5328 push_rev_graph(graph->next, graph->rev[i]);
5332 update_rev_graph(struct rev_graph *graph)
5334 /* If this is the finalizing update ... */
5336 prepare_rev_graph(graph);
5338 /* Graph visualization needs a one rev look-ahead,
5339 * so the first update doesn't visualize anything. */
5340 if (!graph->prev->commit)
5343 draw_rev_graph(graph->prev);
5344 done_rev_graph(graph->prev->prev);
5352 static const char *main_argv[SIZEOF_ARG] = {
5353 "git", "log", "--no-color", "--pretty=raw", "--parents",
5354 "--topo-order", "%(head)", NULL
5358 main_draw(struct view *view, struct line *line, unsigned int lineno)
5360 struct commit *commit = line->data;
5362 if (!*commit->author)
5365 if (opt_date && draw_date(view, &commit->time))
5369 draw_field(view, LINE_MAIN_AUTHOR, commit->author, opt_author_cols, TRUE))
5372 if (opt_rev_graph && commit->graph_size &&
5373 draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
5376 if (opt_show_refs && commit->refs) {
5380 enum line_type type;
5382 if (commit->refs[i]->head)
5383 type = LINE_MAIN_HEAD;
5384 else if (commit->refs[i]->ltag)
5385 type = LINE_MAIN_LOCAL_TAG;
5386 else if (commit->refs[i]->tag)
5387 type = LINE_MAIN_TAG;
5388 else if (commit->refs[i]->tracked)
5389 type = LINE_MAIN_TRACKED;
5390 else if (commit->refs[i]->remote)
5391 type = LINE_MAIN_REMOTE;
5393 type = LINE_MAIN_REF;
5395 if (draw_text(view, type, "[", TRUE) ||
5396 draw_text(view, type, commit->refs[i]->name, TRUE) ||
5397 draw_text(view, type, "]", TRUE))
5400 if (draw_text(view, LINE_DEFAULT, " ", TRUE))
5402 } while (commit->refs[i++]->next);
5405 draw_text(view, LINE_DEFAULT, commit->title, TRUE);
5409 /* Reads git log --pretty=raw output and parses it into the commit struct. */
5411 main_read(struct view *view, char *line)
5413 static struct rev_graph *graph = graph_stacks;
5414 enum line_type type;
5415 struct commit *commit;
5420 if (!view->lines && !view->parent)
5421 die("No revisions match the given arguments.");
5422 if (view->lines > 0) {
5423 commit = view->line[view->lines - 1].data;
5424 if (!*commit->author) {
5427 graph->commit = NULL;
5430 update_rev_graph(graph);
5432 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
5433 clear_rev_graph(&graph_stacks[i]);
5437 type = get_line_type(line);
5438 if (type == LINE_COMMIT) {
5439 commit = calloc(1, sizeof(struct commit));
5443 line += STRING_SIZE("commit ");
5445 graph->boundary = 1;
5449 string_copy_rev(commit->id, line);
5450 commit->refs = get_refs(commit->id);
5451 graph->commit = commit;
5452 add_line_data(view, commit, LINE_MAIN_COMMIT);
5454 while ((line = strchr(line, ' '))) {
5456 push_rev_graph(graph->parents, line);
5457 commit->has_parents = TRUE;
5464 commit = view->line[view->lines - 1].data;
5468 if (commit->has_parents)
5470 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
5475 /* Parse author lines where the name may be empty:
5476 * author <email@address.tld> 1138474660 +0100
5478 char *ident = line + STRING_SIZE("author ");
5479 char *nameend = strchr(ident, '<');
5480 char *emailend = strchr(ident, '>');
5482 if (!nameend || !emailend)
5485 update_rev_graph(graph);
5486 graph = graph->next;
5488 *nameend = *emailend = 0;
5489 ident = chomp_string(ident);
5491 ident = chomp_string(nameend + 1);
5496 string_ncopy(commit->author, ident, strlen(ident));
5498 /* Parse epoch and timezone */
5499 if (emailend[1] == ' ') {
5500 char *secs = emailend + 2;
5501 char *zone = strchr(secs, ' ');
5502 time_t time = (time_t) atol(secs);
5504 if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
5508 tz = ('0' - zone[1]) * 60 * 60 * 10;
5509 tz += ('0' - zone[2]) * 60 * 60;
5510 tz += ('0' - zone[3]) * 60;
5511 tz += ('0' - zone[4]) * 60;
5519 gmtime_r(&time, &commit->time);
5524 /* Fill in the commit title if it has not already been set. */
5525 if (commit->title[0])
5528 /* Require titles to start with a non-space character at the
5529 * offset used by git log. */
5530 if (strncmp(line, " ", 4))
5533 /* Well, if the title starts with a whitespace character,
5534 * try to be forgiving. Otherwise we end up with no title. */
5535 while (isspace(*line))
5539 /* FIXME: More graceful handling of titles; append "..." to
5540 * shortened titles, etc. */
5542 string_ncopy(commit->title, line, strlen(line));
5549 main_request(struct view *view, enum request request, struct line *line)
5551 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
5555 open_view(view, REQ_VIEW_DIFF, flags);
5559 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
5569 grep_refs(struct ref **refs, regex_t *regex)
5577 if (regexec(regex, refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
5579 } while (refs[i++]->next);
5585 main_grep(struct view *view, struct line *line)
5587 struct commit *commit = line->data;
5588 enum { S_TITLE, S_AUTHOR, S_DATE, S_REFS, S_END } state;
5589 char buf[DATE_COLS + 1];
5592 for (state = S_TITLE; state < S_END; state++) {
5596 case S_TITLE: text = commit->title; break;
5600 text = commit->author;
5605 if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
5612 if (grep_refs(commit->refs, view->regex) == TRUE)
5619 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
5627 main_select(struct view *view, struct line *line)
5629 struct commit *commit = line->data;
5631 string_copy_rev(view->ref, commit->id);
5632 string_copy_rev(ref_commit, view->ref);
5635 static struct view_ops main_ops = {
5648 * Unicode / UTF-8 handling
5650 * NOTE: Much of the following code for dealing with unicode is derived from
5651 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
5652 * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
5655 /* I've (over)annotated a lot of code snippets because I am not entirely
5656 * confident that the approach taken by this small UTF-8 interface is correct.
5660 unicode_width(unsigned long c)
5663 (c <= 0x115f /* Hangul Jamo */
5666 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
5668 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
5669 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
5670 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
5671 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
5672 || (c >= 0xffe0 && c <= 0xffe6)
5673 || (c >= 0x20000 && c <= 0x2fffd)
5674 || (c >= 0x30000 && c <= 0x3fffd)))
5678 return opt_tab_size;
5683 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
5684 * Illegal bytes are set one. */
5685 static const unsigned char utf8_bytes[256] = {
5686 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,
5687 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,
5688 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,
5689 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,
5690 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,
5691 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,
5692 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,
5693 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,
5696 /* Decode UTF-8 multi-byte representation into a unicode character. */
5697 static inline unsigned long
5698 utf8_to_unicode(const char *string, size_t length)
5700 unsigned long unicode;
5704 unicode = string[0];
5707 unicode = (string[0] & 0x1f) << 6;
5708 unicode += (string[1] & 0x3f);
5711 unicode = (string[0] & 0x0f) << 12;
5712 unicode += ((string[1] & 0x3f) << 6);
5713 unicode += (string[2] & 0x3f);
5716 unicode = (string[0] & 0x0f) << 18;
5717 unicode += ((string[1] & 0x3f) << 12);
5718 unicode += ((string[2] & 0x3f) << 6);
5719 unicode += (string[3] & 0x3f);
5722 unicode = (string[0] & 0x0f) << 24;
5723 unicode += ((string[1] & 0x3f) << 18);
5724 unicode += ((string[2] & 0x3f) << 12);
5725 unicode += ((string[3] & 0x3f) << 6);
5726 unicode += (string[4] & 0x3f);
5729 unicode = (string[0] & 0x01) << 30;
5730 unicode += ((string[1] & 0x3f) << 24);
5731 unicode += ((string[2] & 0x3f) << 18);
5732 unicode += ((string[3] & 0x3f) << 12);
5733 unicode += ((string[4] & 0x3f) << 6);
5734 unicode += (string[5] & 0x3f);
5737 die("Invalid unicode length");
5740 /* Invalid characters could return the special 0xfffd value but NUL
5741 * should be just as good. */
5742 return unicode > 0xffff ? 0 : unicode;
5745 /* Calculates how much of string can be shown within the given maximum width
5746 * and sets trimmed parameter to non-zero value if all of string could not be
5747 * shown. If the reserve flag is TRUE, it will reserve at least one
5748 * trailing character, which can be useful when drawing a delimiter.
5750 * Returns the number of bytes to output from string to satisfy max_width. */
5752 utf8_length(const char *string, int *width, size_t max_width, int *trimmed, bool reserve)
5754 const char *start = string;
5755 const char *end = strchr(string, '\0');
5756 unsigned char last_bytes = 0;
5757 size_t last_ucwidth = 0;
5762 while (string < end) {
5763 int c = *(unsigned char *) string;
5764 unsigned char bytes = utf8_bytes[c];
5766 unsigned long unicode;
5768 if (string + bytes > end)
5771 /* Change representation to figure out whether
5772 * it is a single- or double-width character. */
5774 unicode = utf8_to_unicode(string, bytes);
5775 /* FIXME: Graceful handling of invalid unicode character. */
5779 ucwidth = unicode_width(unicode);
5781 if (*width > max_width) {
5784 if (reserve && *width == max_width) {
5785 string -= last_bytes;
5786 *width -= last_ucwidth;
5793 last_ucwidth = ucwidth;
5796 return string - start;
5804 /* Whether or not the curses interface has been initialized. */
5805 static bool cursed = FALSE;
5807 /* The status window is used for polling keystrokes. */
5808 static WINDOW *status_win;
5810 static bool status_empty = TRUE;
5812 /* Update status and title window. */
5814 report(const char *msg, ...)
5816 struct view *view = display[current_view];
5822 char buf[SIZEOF_STR];
5825 va_start(args, msg);
5826 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
5827 buf[sizeof(buf) - 1] = 0;
5828 buf[sizeof(buf) - 2] = '.';
5829 buf[sizeof(buf) - 3] = '.';
5830 buf[sizeof(buf) - 4] = '.';
5836 if (!status_empty || *msg) {
5839 va_start(args, msg);
5841 wmove(status_win, 0, 0);
5843 vwprintw(status_win, msg, args);
5844 status_empty = FALSE;
5846 status_empty = TRUE;
5848 wclrtoeol(status_win);
5849 wrefresh(status_win);
5854 update_view_title(view);
5855 update_display_cursor(view);
5858 /* Controls when nodelay should be in effect when polling user input. */
5860 set_nonblocking_input(bool loading)
5862 static unsigned int loading_views;
5864 if ((loading == FALSE && loading_views-- == 1) ||
5865 (loading == TRUE && loading_views++ == 0))
5866 nodelay(status_win, loading);
5874 /* Initialize the curses library */
5875 if (isatty(STDIN_FILENO)) {
5876 cursed = !!initscr();
5879 /* Leave stdin and stdout alone when acting as a pager. */
5880 opt_tty = fopen("/dev/tty", "r+");
5882 die("Failed to open /dev/tty");
5883 cursed = !!newterm(NULL, opt_tty, opt_tty);
5887 die("Failed to initialize curses");
5889 nonl(); /* Tell curses not to do NL->CR/NL on output */
5890 cbreak(); /* Take input chars one at a time, no wait for \n */
5891 noecho(); /* Don't echo input */
5892 leaveok(stdscr, TRUE);
5897 getmaxyx(stdscr, y, x);
5898 status_win = newwin(1, 0, y - 1, 0);
5900 die("Failed to create status window");
5902 /* Enable keyboard mapping */
5903 keypad(status_win, TRUE);
5904 wbkgdset(status_win, get_line_attr(LINE_STATUS));
5906 TABSIZE = opt_tab_size;
5907 if (opt_line_graphics) {
5908 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
5913 prompt_yesno(const char *prompt)
5915 enum { WAIT, STOP, CANCEL } status = WAIT;
5916 bool answer = FALSE;
5918 while (status == WAIT) {
5924 foreach_view (view, i)
5929 mvwprintw(status_win, 0, 0, "%s [Yy]/[Nn]", prompt);
5930 wclrtoeol(status_win);
5932 /* Refresh, accept single keystroke of input */
5933 key = wgetch(status_win);
5957 /* Clear the status window */
5958 status_empty = FALSE;
5965 read_prompt(const char *prompt)
5967 enum { READING, STOP, CANCEL } status = READING;
5968 static char buf[SIZEOF_STR];
5971 while (status == READING) {
5977 foreach_view (view, i)
5982 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
5983 wclrtoeol(status_win);
5985 /* Refresh, accept single keystroke of input */
5986 key = wgetch(status_win);
5991 status = pos ? STOP : CANCEL;
6009 if (pos >= sizeof(buf)) {
6010 report("Input string too long");
6015 buf[pos++] = (char) key;
6019 /* Clear the status window */
6020 status_empty = FALSE;
6023 if (status == CANCEL)
6032 * Repository properties
6036 git_properties(const char **argv, const char *separators,
6037 int (*read_property)(char *, size_t, char *, size_t))
6041 if (init_io_rd(&io, argv, NULL, FORMAT_NONE))
6042 return read_properties(&io, separators, read_property);
6046 static struct ref *refs = NULL;
6047 static size_t refs_alloc = 0;
6048 static size_t refs_size = 0;
6050 /* Id <-> ref store */
6051 static struct ref ***id_refs = NULL;
6052 static size_t id_refs_alloc = 0;
6053 static size_t id_refs_size = 0;
6056 compare_refs(const void *ref1_, const void *ref2_)
6058 const struct ref *ref1 = *(const struct ref **)ref1_;
6059 const struct ref *ref2 = *(const struct ref **)ref2_;
6061 if (ref1->tag != ref2->tag)
6062 return ref2->tag - ref1->tag;
6063 if (ref1->ltag != ref2->ltag)
6064 return ref2->ltag - ref2->ltag;
6065 if (ref1->head != ref2->head)
6066 return ref2->head - ref1->head;
6067 if (ref1->tracked != ref2->tracked)
6068 return ref2->tracked - ref1->tracked;
6069 if (ref1->remote != ref2->remote)
6070 return ref2->remote - ref1->remote;
6071 return strcmp(ref1->name, ref2->name);
6074 static struct ref **
6075 get_refs(const char *id)
6077 struct ref ***tmp_id_refs;
6078 struct ref **ref_list = NULL;
6079 size_t ref_list_alloc = 0;
6080 size_t ref_list_size = 0;
6083 for (i = 0; i < id_refs_size; i++)
6084 if (!strcmp(id, id_refs[i][0]->id))
6087 tmp_id_refs = realloc_items(id_refs, &id_refs_alloc, id_refs_size + 1,
6092 id_refs = tmp_id_refs;
6094 for (i = 0; i < refs_size; i++) {
6097 if (strcmp(id, refs[i].id))
6100 tmp = realloc_items(ref_list, &ref_list_alloc,
6101 ref_list_size + 1, sizeof(*ref_list));
6109 ref_list[ref_list_size] = &refs[i];
6110 /* XXX: The properties of the commit chains ensures that we can
6111 * safely modify the shared ref. The repo references will
6112 * always be similar for the same id. */
6113 ref_list[ref_list_size]->next = 1;
6119 qsort(ref_list, ref_list_size, sizeof(*ref_list), compare_refs);
6120 ref_list[ref_list_size - 1]->next = 0;
6121 id_refs[id_refs_size++] = ref_list;
6128 read_ref(char *id, size_t idlen, char *name, size_t namelen)
6133 bool remote = FALSE;
6134 bool tracked = FALSE;
6135 bool check_replace = FALSE;
6138 if (!prefixcmp(name, "refs/tags/")) {
6139 if (!suffixcmp(name, namelen, "^{}")) {
6142 if (refs_size > 0 && refs[refs_size - 1].ltag == TRUE)
6143 check_replace = TRUE;
6149 namelen -= STRING_SIZE("refs/tags/");
6150 name += STRING_SIZE("refs/tags/");
6152 } else if (!prefixcmp(name, "refs/remotes/")) {
6154 namelen -= STRING_SIZE("refs/remotes/");
6155 name += STRING_SIZE("refs/remotes/");
6156 tracked = !strcmp(opt_remote, name);
6158 } else if (!prefixcmp(name, "refs/heads/")) {
6159 namelen -= STRING_SIZE("refs/heads/");
6160 name += STRING_SIZE("refs/heads/");
6161 head = !strncmp(opt_head, name, namelen);
6163 } else if (!strcmp(name, "HEAD")) {
6164 string_ncopy(opt_head_rev, id, idlen);
6168 if (check_replace && !strcmp(name, refs[refs_size - 1].name)) {
6169 /* it's an annotated tag, replace the previous sha1 with the
6170 * resolved commit id; relies on the fact git-ls-remote lists
6171 * the commit id of an annotated tag right before the commit id
6173 refs[refs_size - 1].ltag = ltag;
6174 string_copy_rev(refs[refs_size - 1].id, id);
6178 refs = realloc_items(refs, &refs_alloc, refs_size + 1, sizeof(*refs));
6182 ref = &refs[refs_size++];
6183 ref->name = malloc(namelen + 1);
6187 strncpy(ref->name, name, namelen);
6188 ref->name[namelen] = 0;
6192 ref->remote = remote;
6193 ref->tracked = tracked;
6194 string_copy_rev(ref->id, id);
6202 static const char *ls_remote_argv[SIZEOF_ARG] = {
6203 "git", "ls-remote", ".", NULL
6205 static bool init = FALSE;
6208 argv_from_env(ls_remote_argv, "TIG_LS_REMOTE");
6215 while (refs_size > 0)
6216 free(refs[--refs_size].name);
6217 while (id_refs_size > 0)
6218 free(id_refs[--id_refs_size]);
6220 return git_properties(ls_remote_argv, "\t", read_ref);
6224 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
6226 if (!strcmp(name, "i18n.commitencoding"))
6227 string_ncopy(opt_encoding, value, valuelen);
6229 if (!strcmp(name, "core.editor"))
6230 string_ncopy(opt_editor, value, valuelen);
6232 /* branch.<head>.remote */
6234 !strncmp(name, "branch.", 7) &&
6235 !strncmp(name + 7, opt_head, strlen(opt_head)) &&
6236 !strcmp(name + 7 + strlen(opt_head), ".remote"))
6237 string_ncopy(opt_remote, value, valuelen);
6239 if (*opt_head && *opt_remote &&
6240 !strncmp(name, "branch.", 7) &&
6241 !strncmp(name + 7, opt_head, strlen(opt_head)) &&
6242 !strcmp(name + 7 + strlen(opt_head), ".merge")) {
6243 size_t from = strlen(opt_remote);
6245 if (!prefixcmp(value, "refs/heads/")) {
6246 value += STRING_SIZE("refs/heads/");
6247 valuelen -= STRING_SIZE("refs/heads/");
6250 if (!string_format_from(opt_remote, &from, "/%s", value))
6258 load_git_config(void)
6260 const char *config_list_argv[] = { "git", GIT_CONFIG, "--list", NULL };
6262 return git_properties(config_list_argv, "=", read_repo_config_option);
6266 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
6268 if (!opt_git_dir[0]) {
6269 string_ncopy(opt_git_dir, name, namelen);
6271 } else if (opt_is_inside_work_tree == -1) {
6272 /* This can be 3 different values depending on the
6273 * version of git being used. If git-rev-parse does not
6274 * understand --is-inside-work-tree it will simply echo
6275 * the option else either "true" or "false" is printed.
6276 * Default to true for the unknown case. */
6277 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
6279 string_ncopy(opt_cdup, name, namelen);
6286 load_repo_info(void)
6288 const char *head_argv[] = {
6289 "git", "symbolic-ref", "HEAD", NULL
6291 const char *rev_parse_argv[] = {
6292 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
6296 if (run_io_buf(head_argv, opt_head, sizeof(opt_head))) {
6297 chomp_string(opt_head);
6298 if (!prefixcmp(opt_head, "refs/heads/")) {
6299 char *offset = opt_head + STRING_SIZE("refs/heads/");
6301 memmove(opt_head, offset, strlen(offset) + 1);
6305 return git_properties(rev_parse_argv, "=", read_repo_info);
6309 read_properties(struct io *io, const char *separators,
6310 int (*read_property)(char *, size_t, char *, size_t))
6318 while (state == OK && (name = io_gets(io))) {
6323 name = chomp_string(name);
6324 namelen = strcspn(name, separators);
6326 if (name[namelen]) {
6328 value = chomp_string(name + namelen + 1);
6329 valuelen = strlen(value);
6336 state = read_property(name, namelen, value, valuelen);
6339 if (state != ERR && io_error(io))
6351 static void __NORETURN
6354 /* XXX: Restore tty modes and let the OS cleanup the rest! */
6360 static void __NORETURN
6361 die(const char *err, ...)
6367 va_start(args, err);
6368 fputs("tig: ", stderr);
6369 vfprintf(stderr, err, args);
6370 fputs("\n", stderr);
6377 warn(const char *msg, ...)
6381 va_start(args, msg);
6382 fputs("tig warning: ", stderr);
6383 vfprintf(stderr, msg, args);
6384 fputs("\n", stderr);
6389 main(int argc, const char *argv[])
6391 const char **run_argv = NULL;
6393 enum request request;
6396 signal(SIGINT, quit);
6398 if (setlocale(LC_ALL, "")) {
6399 char *codeset = nl_langinfo(CODESET);
6401 string_ncopy(opt_codeset, codeset, strlen(codeset));
6404 if (load_repo_info() == ERR)
6405 die("Failed to load repo info.");
6407 if (load_options() == ERR)
6408 die("Failed to load user config.");
6410 if (load_git_config() == ERR)
6411 die("Failed to load repo config.");
6413 request = parse_options(argc, argv, &run_argv);
6414 if (request == REQ_NONE)
6417 /* Require a git repository unless when running in pager mode. */
6418 if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
6419 die("Not a git repository");
6421 if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
6424 if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
6425 opt_iconv = iconv_open(opt_codeset, opt_encoding);
6426 if (opt_iconv == ICONV_NONE)
6427 die("Failed to initialize character set conversion");
6430 if (load_refs() == ERR)
6431 die("Failed to load refs.");
6433 foreach_view (view, i)
6434 argv_from_env(view->ops->argv, view->cmd_env);
6438 if (request == REQ_VIEW_PAGER || run_argv) {
6439 if (request == REQ_VIEW_PAGER)
6440 io_open(&VIEW(request)->io, "");
6441 else if (!prepare_update(VIEW(request), run_argv, NULL, FORMAT_NONE))
6442 die("Failed to format arguments");
6443 open_view(NULL, request, OPEN_PREPARED);
6447 while (view_driver(display[current_view], request)) {
6451 foreach_view (view, i)
6453 view = display[current_view];
6455 /* Refresh, accept single keystroke of input */
6456 key = wgetch(status_win);
6458 /* wgetch() with nodelay() enabled returns ERR when there's no
6465 request = get_keybinding(view->keymap, key);
6467 /* Some low-level request handling. This keeps access to
6468 * status_win restricted. */
6472 char *cmd = read_prompt(":");
6475 struct view *next = VIEW(REQ_VIEW_PAGER);
6476 const char *argv[SIZEOF_ARG] = { "git" };
6479 /* When running random commands, initially show the
6480 * command in the title. However, it maybe later be
6481 * overwritten if a commit line is selected. */
6482 string_ncopy(next->ref, cmd, strlen(cmd));
6484 if (!argv_from_string(argv, &argc, cmd)) {
6485 report("Too many arguments");
6486 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
6487 report("Failed to format command");
6489 open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
6497 case REQ_SEARCH_BACK:
6499 const char *prompt = request == REQ_SEARCH ? "/" : "?";
6500 char *search = read_prompt(prompt);
6503 string_ncopy(opt_search, search, strlen(search));
6508 case REQ_SCREEN_RESIZE:
6512 getmaxyx(stdscr, height, width);
6514 /* Resize the status view and let the view driver take
6515 * care of resizing the displayed views. */
6516 wresize(status_win, 1, width);
6517 mvwin(status_win, height - 1, 0);
6518 wrefresh(status_win);