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>
46 /* ncurses(3): Must be defined to have extended wide-character functions. */
47 #define _XOPEN_SOURCE_EXTENDED
49 #ifdef HAVE_NCURSESW_NCURSES_H
50 #include <ncursesw/ncurses.h>
52 #ifdef HAVE_NCURSES_NCURSES_H
53 #include <ncurses/ncurses.h>
60 #define __NORETURN __attribute__((__noreturn__))
65 static void __NORETURN die(const char *err, ...);
66 static void warn(const char *msg, ...);
67 static void report(const char *msg, ...);
68 static void set_nonblocking_input(bool loading);
69 static size_t utf8_length(const char *string, int *width, size_t max_width, int *trimmed, bool reserve);
70 static bool prompt_yesno(const char *prompt);
71 static int load_refs(void);
73 #define ABS(x) ((x) >= 0 ? (x) : -(x))
74 #define MIN(x, y) ((x) < (y) ? (x) : (y))
76 #define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
77 #define STRING_SIZE(x) (sizeof(x) - 1)
79 #define SIZEOF_STR 1024 /* Default string size. */
80 #define SIZEOF_REF 256 /* Size of symbolic or SHA1 ID. */
81 #define SIZEOF_REV 41 /* Holds a SHA-1 and an ending NUL. */
82 #define SIZEOF_ARG 32 /* Default argument array size. */
86 #define REVGRAPH_INIT 'I'
87 #define REVGRAPH_MERGE 'M'
88 #define REVGRAPH_BRANCH '+'
89 #define REVGRAPH_COMMIT '*'
90 #define REVGRAPH_BOUND '^'
92 #define SIZEOF_REVGRAPH 19 /* Size of revision ancestry graphics. */
94 /* This color name can be used to refer to the default term colors. */
95 #define COLOR_DEFAULT (-1)
97 #define ICONV_NONE ((iconv_t) -1)
99 #define ICONV_CONST /* nothing */
102 /* The format and size of the date column in the main view. */
103 #define DATE_FORMAT "%Y-%m-%d %H:%M"
104 #define DATE_COLS STRING_SIZE("2006-04-29 14:21 ")
106 #define AUTHOR_COLS 20
109 /* The default interval between line numbers. */
110 #define NUMBER_INTERVAL 5
114 #define SCALE_SPLIT_VIEW(height) ((height) * 2 / 3)
116 #define NULL_ID "0000000000000000000000000000000000000000"
119 #define GIT_CONFIG "config"
122 /* Some ascii-shorthands fitted into the ncurses namespace. */
124 #define KEY_RETURN '\r'
129 char *name; /* Ref name; tag or head names are shortened. */
130 char id[SIZEOF_REV]; /* Commit SHA1 ID */
131 unsigned int head:1; /* Is it the current HEAD? */
132 unsigned int tag:1; /* Is it a tag? */
133 unsigned int ltag:1; /* If so, is the tag local? */
134 unsigned int remote:1; /* Is it a remote ref? */
135 unsigned int tracked:1; /* Is it the remote for the current HEAD? */
136 unsigned int next:1; /* For ref lists: are there more refs? */
139 static struct ref **get_refs(const char *id);
142 FORMAT_ALL, /* Perform replacement in all arguments. */
143 FORMAT_DASH, /* Perform replacement up until "--". */
144 FORMAT_NONE /* No replacement should be performed. */
147 static bool format_command(char dst[], const char *src[], enum format_flags flags);
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 * NOTE: The following is a slightly modified copy of the git project's shell
276 * quoting routines found in the quote.c file.
278 * Help to copy the thing properly quoted for the shell safety. any single
279 * quote is replaced with '\'', any exclamation point is replaced with '\!',
280 * and the whole thing is enclosed in a
283 * original sq_quote result
284 * name ==> name ==> 'name'
285 * a b ==> a b ==> 'a b'
286 * a'b ==> a'\''b ==> 'a'\''b'
287 * a!b ==> a'\!'b ==> 'a'\!'b'
291 sq_quote(char buf[SIZEOF_STR], size_t bufsize, const char *src)
295 #define BUFPUT(x) do { if (bufsize < SIZEOF_STR) buf[bufsize++] = (x); } while (0)
298 while ((c = *src++)) {
299 if (c == '\'' || c == '!') {
310 if (bufsize < SIZEOF_STR)
317 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
321 while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
322 bool advance = cmd[valuelen] != 0;
325 argv[(*argc)++] = chomp_string(cmd);
326 cmd += valuelen + advance;
329 if (*argc < SIZEOF_ARG)
331 return *argc < SIZEOF_ARG;
335 argv_from_env(const char **argv, const char *name)
337 char *env = argv ? getenv(name) : NULL;
342 if (env && !argv_from_string(argv, &argc, env))
343 die("Too many arguments in the `%s` environment variable", name);
348 * Executing external commands.
352 IO_FD, /* File descriptor based IO. */
353 IO_BG, /* Execute command in the background. */
354 IO_FG, /* Execute command with same std{in,out,err}. */
355 IO_RD, /* Read only fork+exec IO. */
356 IO_WR, /* Write only fork+exec IO. */
360 enum io_type type; /* The requested type of pipe. */
361 const char *dir; /* Directory from which to execute. */
362 FILE *pid; /* Pipe for reading or writing. */
363 int pipe; /* Pipe end for reading or writing. */
364 int error; /* Error status. */
365 char sh[SIZEOF_STR]; /* Shell command buffer. */
366 char *buf; /* Read buffer. */
367 size_t bufalloc; /* Allocated buffer size. */
368 size_t bufsize; /* Buffer content size. */
369 char *bufpos; /* Current buffer position. */
370 unsigned int eof:1; /* Has end of file been reached. */
374 reset_io(struct io *io)
378 io->buf = io->bufpos = NULL;
379 io->bufalloc = io->bufsize = 0;
385 init_io(struct io *io, const char *dir, enum io_type type)
393 init_io_rd(struct io *io, const char *argv[], const char *dir,
394 enum format_flags flags)
396 init_io(io, dir, IO_RD);
397 return format_command(io->sh, argv, flags);
401 io_open(struct io *io, const char *name)
403 init_io(io, NULL, IO_FD);
404 io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
405 return io->pipe != -1;
409 done_io(struct io *io)
412 if (io->type == IO_FD)
414 else if (io->type == IO_RD || io->type == IO_WR)
421 start_io(struct io *io)
423 char buf[SIZEOF_STR * 2];
426 if (io->type == IO_FD)
429 if (io->dir && *io->dir &&
430 !string_format_from(buf, &bufpos, "cd %s;", io->dir))
433 if (!string_format_from(buf, &bufpos, "%s", io->sh))
436 if (io->type == IO_FG || io->type == IO_BG)
437 return system(buf) == 0;
439 io->pid = popen(io->sh, io->type == IO_RD ? "r" : "w");
442 io->pipe = fileno(io->pid);
443 return io->pipe != -1;
447 run_io(struct io *io, const char **argv, const char *dir, enum io_type type)
449 init_io(io, dir, type);
450 if (!format_command(io->sh, argv, FORMAT_NONE))
456 run_io_do(struct io *io)
458 return start_io(io) && done_io(io);
462 run_io_bg(const char **argv)
466 init_io(&io, NULL, IO_BG);
467 if (!format_command(io.sh, argv, FORMAT_NONE))
469 return run_io_do(&io);
473 run_io_fg(const char **argv, const char *dir)
477 init_io(&io, dir, IO_FG);
478 if (!format_command(io.sh, argv, FORMAT_NONE))
480 return run_io_do(&io);
484 run_io_rd(struct io *io, const char **argv, enum format_flags flags)
486 return init_io_rd(io, argv, NULL, flags) && start_io(io);
490 io_eof(struct io *io)
496 io_error(struct io *io)
502 io_strerror(struct io *io)
504 return strerror(io->error);
508 io_read(struct io *io, void *buf, size_t bufsize)
511 ssize_t readsize = read(io->pipe, buf, bufsize);
513 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
515 else if (readsize == -1)
517 else if (readsize == 0)
524 io_gets(struct io *io)
530 io->buf = io->bufpos = malloc(BUFSIZ);
533 io->bufalloc = BUFSIZ;
538 if (io->bufsize > 0) {
539 eol = memchr(io->bufpos, '\n', io->bufsize);
541 char *line = io->bufpos;
544 io->bufpos = eol + 1;
545 io->bufsize -= io->bufpos - line;
552 io->bufpos[io->bufsize] = 0;
559 if (io->bufsize > 0 && io->bufpos > io->buf)
560 memmove(io->buf, io->bufpos, io->bufsize);
562 io->bufpos = io->buf;
563 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
566 io->bufsize += readsize;
571 io_write(struct io *io, const void *buf, size_t bufsize)
575 while (!io_error(io) && written < bufsize) {
578 size = write(io->pipe, buf + written, bufsize - written);
579 if (size < 0 && (errno == EAGAIN || errno == EINTR))
587 return written == bufsize;
591 run_io_buf(const char **argv, char buf[], size_t bufsize)
596 if (!run_io_rd(&io, argv, FORMAT_NONE))
599 io.buf = io.bufpos = buf;
600 io.bufalloc = bufsize;
601 error = !io_gets(&io) && io_error(&io);
604 return done_io(&io) || error;
607 static int read_properties(struct io *io, const char *separators, int (*read)(char *, size_t, char *, size_t));
614 /* XXX: Keep the view request first and in sync with views[]. */ \
615 REQ_GROUP("View switching") \
616 REQ_(VIEW_MAIN, "Show main view"), \
617 REQ_(VIEW_DIFF, "Show diff view"), \
618 REQ_(VIEW_LOG, "Show log view"), \
619 REQ_(VIEW_TREE, "Show tree view"), \
620 REQ_(VIEW_BLOB, "Show blob view"), \
621 REQ_(VIEW_BLAME, "Show blame view"), \
622 REQ_(VIEW_HELP, "Show help page"), \
623 REQ_(VIEW_PAGER, "Show pager view"), \
624 REQ_(VIEW_STATUS, "Show status view"), \
625 REQ_(VIEW_STAGE, "Show stage view"), \
627 REQ_GROUP("View manipulation") \
628 REQ_(ENTER, "Enter current line and scroll"), \
629 REQ_(NEXT, "Move to next"), \
630 REQ_(PREVIOUS, "Move to previous"), \
631 REQ_(VIEW_NEXT, "Move focus to next view"), \
632 REQ_(REFRESH, "Reload and refresh"), \
633 REQ_(MAXIMIZE, "Maximize the current view"), \
634 REQ_(VIEW_CLOSE, "Close the current view"), \
635 REQ_(QUIT, "Close all views and quit"), \
637 REQ_GROUP("View specific requests") \
638 REQ_(STATUS_UPDATE, "Update file status"), \
639 REQ_(STATUS_REVERT, "Revert file changes"), \
640 REQ_(STATUS_MERGE, "Merge file using external tool"), \
641 REQ_(STAGE_NEXT, "Find next chunk to stage"), \
642 REQ_(TREE_PARENT, "Switch to parent directory in tree view"), \
644 REQ_GROUP("Cursor navigation") \
645 REQ_(MOVE_UP, "Move cursor one line up"), \
646 REQ_(MOVE_DOWN, "Move cursor one line down"), \
647 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
648 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
649 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
650 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
652 REQ_GROUP("Scrolling") \
653 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
654 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
655 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
656 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
658 REQ_GROUP("Searching") \
659 REQ_(SEARCH, "Search the view"), \
660 REQ_(SEARCH_BACK, "Search backwards in the view"), \
661 REQ_(FIND_NEXT, "Find next search match"), \
662 REQ_(FIND_PREV, "Find previous search match"), \
664 REQ_GROUP("Option manipulation") \
665 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
666 REQ_(TOGGLE_DATE, "Toggle date display"), \
667 REQ_(TOGGLE_AUTHOR, "Toggle author display"), \
668 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
669 REQ_(TOGGLE_REFS, "Toggle reference display (tags/branches)"), \
672 REQ_(PROMPT, "Bring up the prompt"), \
673 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
674 REQ_(SCREEN_RESIZE, "Resize the screen"), \
675 REQ_(SHOW_VERSION, "Show version information"), \
676 REQ_(STOP_LOADING, "Stop all loading views"), \
677 REQ_(EDIT, "Open in editor"), \
678 REQ_(NONE, "Do nothing")
681 /* User action requests. */
683 #define REQ_GROUP(help)
684 #define REQ_(req, help) REQ_##req
686 /* Offset all requests to avoid conflicts with ncurses getch values. */
687 REQ_OFFSET = KEY_MAX + 1,
694 struct request_info {
695 enum request request;
701 static struct request_info req_info[] = {
702 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
703 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
710 get_request(const char *name)
712 int namelen = strlen(name);
715 for (i = 0; i < ARRAY_SIZE(req_info); i++)
716 if (req_info[i].namelen == namelen &&
717 !string_enum_compare(req_info[i].name, name, namelen))
718 return req_info[i].request;
728 static const char usage[] =
729 "tig " TIG_VERSION " (" __DATE__ ")\n"
731 "Usage: tig [options] [revs] [--] [paths]\n"
732 " or: tig show [options] [revs] [--] [paths]\n"
733 " or: tig blame [rev] path\n"
735 " or: tig < [git command output]\n"
738 " -v, --version Show version and exit\n"
739 " -h, --help Show help message and exit";
741 /* Option and state variables. */
742 static bool opt_date = TRUE;
743 static bool opt_author = TRUE;
744 static bool opt_line_number = FALSE;
745 static bool opt_line_graphics = TRUE;
746 static bool opt_rev_graph = FALSE;
747 static bool opt_show_refs = TRUE;
748 static int opt_num_interval = NUMBER_INTERVAL;
749 static int opt_tab_size = TAB_SIZE;
750 static int opt_author_cols = AUTHOR_COLS-1;
751 static char opt_path[SIZEOF_STR] = "";
752 static char opt_file[SIZEOF_STR] = "";
753 static char opt_ref[SIZEOF_REF] = "";
754 static char opt_head[SIZEOF_REF] = "";
755 static char opt_head_rev[SIZEOF_REV] = "";
756 static char opt_remote[SIZEOF_REF] = "";
757 static char opt_encoding[20] = "UTF-8";
758 static bool opt_utf8 = TRUE;
759 static char opt_codeset[20] = "UTF-8";
760 static iconv_t opt_iconv = ICONV_NONE;
761 static char opt_search[SIZEOF_STR] = "";
762 static char opt_cdup[SIZEOF_STR] = "";
763 static char opt_git_dir[SIZEOF_STR] = "";
764 static signed char opt_is_inside_work_tree = -1; /* set to TRUE or FALSE */
765 static char opt_editor[SIZEOF_STR] = "";
766 static FILE *opt_tty = NULL;
768 #define is_initial_commit() (!*opt_head_rev)
769 #define is_head_commit(rev) (!strcmp((rev), "HEAD") || !strcmp(opt_head_rev, (rev)))
772 parse_options(int argc, const char *argv[], const char ***run_argv)
774 enum request request = REQ_VIEW_MAIN;
775 const char *subcommand;
776 bool seen_dashdash = FALSE;
777 /* XXX: This is vulnerable to the user overriding options
778 * required for the main view parser. */
779 const char *custom_argv[SIZEOF_ARG] = {
780 "git", "log", "--no-color", "--pretty=raw", "--parents",
785 if (!isatty(STDIN_FILENO))
786 return REQ_VIEW_PAGER;
789 return REQ_VIEW_MAIN;
791 subcommand = argv[1];
792 if (!strcmp(subcommand, "status") || !strcmp(subcommand, "-S")) {
793 if (!strcmp(subcommand, "-S"))
794 warn("`-S' has been deprecated; use `tig status' instead");
796 warn("ignoring arguments after `%s'", subcommand);
797 return REQ_VIEW_STATUS;
799 } else if (!strcmp(subcommand, "blame")) {
800 if (argc <= 2 || argc > 4)
801 die("invalid number of options to blame\n\n%s", usage);
805 string_ncopy(opt_ref, argv[i], strlen(argv[i]));
809 string_ncopy(opt_file, argv[i], strlen(argv[i]));
810 return REQ_VIEW_BLAME;
812 } else if (!strcmp(subcommand, "show")) {
813 request = REQ_VIEW_DIFF;
815 } else if (!strcmp(subcommand, "log") || !strcmp(subcommand, "diff")) {
816 request = subcommand[0] == 'l' ? REQ_VIEW_LOG : REQ_VIEW_DIFF;
817 warn("`tig %s' has been deprecated", subcommand);
824 custom_argv[1] = subcommand;
828 for (i = 1 + !!subcommand; i < argc; i++) {
829 const char *opt = argv[i];
831 if (seen_dashdash || !strcmp(opt, "--")) {
832 seen_dashdash = TRUE;
834 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
835 printf("tig version %s\n", TIG_VERSION);
838 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
839 printf("%s\n", usage);
843 custom_argv[j++] = opt;
844 if (j >= ARRAY_SIZE(custom_argv))
845 die("command too long");
848 custom_argv[j] = NULL;
849 *run_argv = custom_argv;
856 * Line-oriented content detection.
860 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
861 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
862 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
863 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
864 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
865 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
866 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
867 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
868 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
869 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
870 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
871 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
872 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
873 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
874 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
875 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
876 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
877 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
878 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
879 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
880 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
881 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
882 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
883 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
884 LINE(AUTHOR, "author ", COLOR_CYAN, COLOR_DEFAULT, 0), \
885 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
886 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
887 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
888 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
889 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
890 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
891 LINE(DELIMITER, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
892 LINE(DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
893 LINE(LINE_NUMBER, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
894 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
895 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
896 LINE(MAIN_AUTHOR, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
897 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
898 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
899 LINE(MAIN_LOCAL_TAG,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
900 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
901 LINE(MAIN_TRACKED, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
902 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
903 LINE(MAIN_HEAD, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
904 LINE(MAIN_REVGRAPH,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
905 LINE(TREE_DIR, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
906 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
907 LINE(STAT_HEAD, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
908 LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
909 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
910 LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
911 LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
912 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
913 LINE(BLAME_ID, "", COLOR_MAGENTA, COLOR_DEFAULT, 0)
916 #define LINE(type, line, fg, bg, attr) \
924 const char *name; /* Option name. */
925 int namelen; /* Size of option name. */
926 const char *line; /* The start of line to match. */
927 int linelen; /* Size of string to match. */
928 int fg, bg, attr; /* Color and text attributes for the lines. */
931 static struct line_info line_info[] = {
932 #define LINE(type, line, fg, bg, attr) \
933 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
938 static enum line_type
939 get_line_type(const char *line)
941 int linelen = strlen(line);
944 for (type = 0; type < ARRAY_SIZE(line_info); type++)
945 /* Case insensitive search matches Signed-off-by lines better. */
946 if (linelen >= line_info[type].linelen &&
947 !strncasecmp(line_info[type].line, line, line_info[type].linelen))
954 get_line_attr(enum line_type type)
956 assert(type < ARRAY_SIZE(line_info));
957 return COLOR_PAIR(type) | line_info[type].attr;
960 static struct line_info *
961 get_line_info(const char *name)
963 size_t namelen = strlen(name);
966 for (type = 0; type < ARRAY_SIZE(line_info); type++)
967 if (namelen == line_info[type].namelen &&
968 !string_enum_compare(line_info[type].name, name, namelen))
969 return &line_info[type];
977 int default_bg = line_info[LINE_DEFAULT].bg;
978 int default_fg = line_info[LINE_DEFAULT].fg;
983 if (assume_default_colors(default_fg, default_bg) == ERR) {
984 default_bg = COLOR_BLACK;
985 default_fg = COLOR_WHITE;
988 for (type = 0; type < ARRAY_SIZE(line_info); type++) {
989 struct line_info *info = &line_info[type];
990 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
991 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
993 init_pair(type, fg, bg);
1001 unsigned int selected:1;
1002 unsigned int dirty:1;
1004 void *data; /* User data */
1014 enum request request;
1017 static struct keybinding default_keybindings[] = {
1018 /* View switching */
1019 { 'm', REQ_VIEW_MAIN },
1020 { 'd', REQ_VIEW_DIFF },
1021 { 'l', REQ_VIEW_LOG },
1022 { 't', REQ_VIEW_TREE },
1023 { 'f', REQ_VIEW_BLOB },
1024 { 'B', REQ_VIEW_BLAME },
1025 { 'p', REQ_VIEW_PAGER },
1026 { 'h', REQ_VIEW_HELP },
1027 { 'S', REQ_VIEW_STATUS },
1028 { 'c', REQ_VIEW_STAGE },
1030 /* View manipulation */
1031 { 'q', REQ_VIEW_CLOSE },
1032 { KEY_TAB, REQ_VIEW_NEXT },
1033 { KEY_RETURN, REQ_ENTER },
1034 { KEY_UP, REQ_PREVIOUS },
1035 { KEY_DOWN, REQ_NEXT },
1036 { 'R', REQ_REFRESH },
1037 { KEY_F(5), REQ_REFRESH },
1038 { 'O', REQ_MAXIMIZE },
1040 /* Cursor navigation */
1041 { 'k', REQ_MOVE_UP },
1042 { 'j', REQ_MOVE_DOWN },
1043 { KEY_HOME, REQ_MOVE_FIRST_LINE },
1044 { KEY_END, REQ_MOVE_LAST_LINE },
1045 { KEY_NPAGE, REQ_MOVE_PAGE_DOWN },
1046 { ' ', REQ_MOVE_PAGE_DOWN },
1047 { KEY_PPAGE, REQ_MOVE_PAGE_UP },
1048 { 'b', REQ_MOVE_PAGE_UP },
1049 { '-', REQ_MOVE_PAGE_UP },
1052 { KEY_IC, REQ_SCROLL_LINE_UP },
1053 { KEY_DC, REQ_SCROLL_LINE_DOWN },
1054 { 'w', REQ_SCROLL_PAGE_UP },
1055 { 's', REQ_SCROLL_PAGE_DOWN },
1058 { '/', REQ_SEARCH },
1059 { '?', REQ_SEARCH_BACK },
1060 { 'n', REQ_FIND_NEXT },
1061 { 'N', REQ_FIND_PREV },
1065 { 'z', REQ_STOP_LOADING },
1066 { 'v', REQ_SHOW_VERSION },
1067 { 'r', REQ_SCREEN_REDRAW },
1068 { '.', REQ_TOGGLE_LINENO },
1069 { 'D', REQ_TOGGLE_DATE },
1070 { 'A', REQ_TOGGLE_AUTHOR },
1071 { 'g', REQ_TOGGLE_REV_GRAPH },
1072 { 'F', REQ_TOGGLE_REFS },
1073 { ':', REQ_PROMPT },
1074 { 'u', REQ_STATUS_UPDATE },
1075 { '!', REQ_STATUS_REVERT },
1076 { 'M', REQ_STATUS_MERGE },
1077 { '@', REQ_STAGE_NEXT },
1078 { ',', REQ_TREE_PARENT },
1081 /* Using the ncurses SIGWINCH handler. */
1082 { KEY_RESIZE, REQ_SCREEN_RESIZE },
1085 #define KEYMAP_INFO \
1099 #define KEYMAP_(name) KEYMAP_##name
1104 static struct int_map keymap_table[] = {
1105 #define KEYMAP_(name) { #name, STRING_SIZE(#name), KEYMAP_##name }
1110 #define set_keymap(map, name) \
1111 set_from_int_map(keymap_table, ARRAY_SIZE(keymap_table), map, name, strlen(name))
1113 struct keybinding_table {
1114 struct keybinding *data;
1118 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1121 add_keybinding(enum keymap keymap, enum request request, int key)
1123 struct keybinding_table *table = &keybindings[keymap];
1125 table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1127 die("Failed to allocate keybinding");
1128 table->data[table->size].alias = key;
1129 table->data[table->size++].request = request;
1132 /* Looks for a key binding first in the given map, then in the generic map, and
1133 * lastly in the default keybindings. */
1135 get_keybinding(enum keymap keymap, int key)
1139 for (i = 0; i < keybindings[keymap].size; i++)
1140 if (keybindings[keymap].data[i].alias == key)
1141 return keybindings[keymap].data[i].request;
1143 for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1144 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1145 return keybindings[KEYMAP_GENERIC].data[i].request;
1147 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1148 if (default_keybindings[i].alias == key)
1149 return default_keybindings[i].request;
1151 return (enum request) key;
1160 static struct key key_table[] = {
1161 { "Enter", KEY_RETURN },
1163 { "Backspace", KEY_BACKSPACE },
1165 { "Escape", KEY_ESC },
1166 { "Left", KEY_LEFT },
1167 { "Right", KEY_RIGHT },
1169 { "Down", KEY_DOWN },
1170 { "Insert", KEY_IC },
1171 { "Delete", KEY_DC },
1173 { "Home", KEY_HOME },
1175 { "PageUp", KEY_PPAGE },
1176 { "PageDown", KEY_NPAGE },
1186 { "F10", KEY_F(10) },
1187 { "F11", KEY_F(11) },
1188 { "F12", KEY_F(12) },
1192 get_key_value(const char *name)
1196 for (i = 0; i < ARRAY_SIZE(key_table); i++)
1197 if (!strcasecmp(key_table[i].name, name))
1198 return key_table[i].value;
1200 if (strlen(name) == 1 && isprint(*name))
1207 get_key_name(int key_value)
1209 static char key_char[] = "'X'";
1210 const char *seq = NULL;
1213 for (key = 0; key < ARRAY_SIZE(key_table); key++)
1214 if (key_table[key].value == key_value)
1215 seq = key_table[key].name;
1219 isprint(key_value)) {
1220 key_char[1] = (char) key_value;
1224 return seq ? seq : "(no key)";
1228 get_key(enum request request)
1230 static char buf[BUFSIZ];
1237 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1238 struct keybinding *keybinding = &default_keybindings[i];
1240 if (keybinding->request != request)
1243 if (!string_format_from(buf, &pos, "%s%s", sep,
1244 get_key_name(keybinding->alias)))
1245 return "Too many keybindings!";
1252 struct run_request {
1255 const char *argv[SIZEOF_ARG];
1258 static struct run_request *run_request;
1259 static size_t run_requests;
1262 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1264 struct run_request *req;
1266 if (argc >= ARRAY_SIZE(req->argv) - 1)
1269 req = realloc(run_request, (run_requests + 1) * sizeof(*run_request));
1274 req = &run_request[run_requests];
1275 req->keymap = keymap;
1277 req->argv[0] = NULL;
1279 if (!format_argv(req->argv, argv, FORMAT_NONE))
1282 return REQ_NONE + ++run_requests;
1285 static struct run_request *
1286 get_run_request(enum request request)
1288 if (request <= REQ_NONE)
1290 return &run_request[request - REQ_NONE - 1];
1294 add_builtin_run_requests(void)
1296 const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1297 const char *gc[] = { "git", "gc", NULL };
1304 { KEYMAP_MAIN, 'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1305 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1309 for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1312 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1313 if (req != REQ_NONE)
1314 add_keybinding(reqs[i].keymap, req, reqs[i].key);
1319 * User config file handling.
1322 static struct int_map color_map[] = {
1323 #define COLOR_MAP(name) { #name, STRING_SIZE(#name), COLOR_##name }
1335 #define set_color(color, name) \
1336 set_from_int_map(color_map, ARRAY_SIZE(color_map), color, name, strlen(name))
1338 static struct int_map attr_map[] = {
1339 #define ATTR_MAP(name) { #name, STRING_SIZE(#name), A_##name }
1346 ATTR_MAP(UNDERLINE),
1349 #define set_attribute(attr, name) \
1350 set_from_int_map(attr_map, ARRAY_SIZE(attr_map), attr, name, strlen(name))
1352 static int config_lineno;
1353 static bool config_errors;
1354 static const char *config_msg;
1356 /* Wants: object fgcolor bgcolor [attr] */
1358 option_color_command(int argc, const char *argv[])
1360 struct line_info *info;
1362 if (argc != 3 && argc != 4) {
1363 config_msg = "Wrong number of arguments given to color command";
1367 info = get_line_info(argv[0]);
1369 if (!string_enum_compare(argv[0], "main-delim", strlen("main-delim"))) {
1370 info = get_line_info("delimiter");
1372 } else if (!string_enum_compare(argv[0], "main-date", strlen("main-date"))) {
1373 info = get_line_info("date");
1376 config_msg = "Unknown color name";
1381 if (set_color(&info->fg, argv[1]) == ERR ||
1382 set_color(&info->bg, argv[2]) == ERR) {
1383 config_msg = "Unknown color";
1387 if (argc == 4 && set_attribute(&info->attr, argv[3]) == ERR) {
1388 config_msg = "Unknown attribute";
1395 static bool parse_bool(const char *s)
1397 return (!strcmp(s, "1") || !strcmp(s, "true") ||
1398 !strcmp(s, "yes")) ? TRUE : FALSE;
1402 parse_int(const char *s, int default_value, int min, int max)
1404 int value = atoi(s);
1406 return (value < min || value > max) ? default_value : value;
1409 /* Wants: name = value */
1411 option_set_command(int argc, const char *argv[])
1414 config_msg = "Wrong number of arguments given to set command";
1418 if (strcmp(argv[1], "=")) {
1419 config_msg = "No value assigned";
1423 if (!strcmp(argv[0], "show-author")) {
1424 opt_author = parse_bool(argv[2]);
1428 if (!strcmp(argv[0], "show-date")) {
1429 opt_date = parse_bool(argv[2]);
1433 if (!strcmp(argv[0], "show-rev-graph")) {
1434 opt_rev_graph = parse_bool(argv[2]);
1438 if (!strcmp(argv[0], "show-refs")) {
1439 opt_show_refs = parse_bool(argv[2]);
1443 if (!strcmp(argv[0], "show-line-numbers")) {
1444 opt_line_number = parse_bool(argv[2]);
1448 if (!strcmp(argv[0], "line-graphics")) {
1449 opt_line_graphics = parse_bool(argv[2]);
1453 if (!strcmp(argv[0], "line-number-interval")) {
1454 opt_num_interval = parse_int(argv[2], opt_num_interval, 1, 1024);
1458 if (!strcmp(argv[0], "author-width")) {
1459 opt_author_cols = parse_int(argv[2], opt_author_cols, 0, 1024);
1463 if (!strcmp(argv[0], "tab-size")) {
1464 opt_tab_size = parse_int(argv[2], opt_tab_size, 1, 1024);
1468 if (!strcmp(argv[0], "commit-encoding")) {
1469 const char *arg = argv[2];
1470 int arglen = strlen(arg);
1475 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1476 config_msg = "Unmatched quotation";
1479 arg += 1; arglen -= 2;
1481 string_ncopy(opt_encoding, arg, strlen(arg));
1486 config_msg = "Unknown variable name";
1490 /* Wants: mode request key */
1492 option_bind_command(int argc, const char *argv[])
1494 enum request request;
1499 config_msg = "Wrong number of arguments given to bind command";
1503 if (set_keymap(&keymap, argv[0]) == ERR) {
1504 config_msg = "Unknown key map";
1508 key = get_key_value(argv[1]);
1510 config_msg = "Unknown key";
1514 request = get_request(argv[2]);
1515 if (request == REQ_NONE) {
1516 const char *obsolete[] = { "cherry-pick" };
1517 size_t namelen = strlen(argv[2]);
1520 for (i = 0; i < ARRAY_SIZE(obsolete); i++) {
1521 if (namelen == strlen(obsolete[i]) &&
1522 !string_enum_compare(obsolete[i], argv[2], namelen)) {
1523 config_msg = "Obsolete request name";
1528 if (request == REQ_NONE && *argv[2]++ == '!')
1529 request = add_run_request(keymap, key, argc - 2, argv + 2);
1530 if (request == REQ_NONE) {
1531 config_msg = "Unknown request name";
1535 add_keybinding(keymap, request, key);
1541 set_option(const char *opt, char *value)
1543 const char *argv[SIZEOF_ARG];
1546 if (!argv_from_string(argv, &argc, value)) {
1547 config_msg = "Too many option arguments";
1551 if (!strcmp(opt, "color"))
1552 return option_color_command(argc, argv);
1554 if (!strcmp(opt, "set"))
1555 return option_set_command(argc, argv);
1557 if (!strcmp(opt, "bind"))
1558 return option_bind_command(argc, argv);
1560 config_msg = "Unknown option command";
1565 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1570 config_msg = "Internal error";
1572 /* Check for comment markers, since read_properties() will
1573 * only ensure opt and value are split at first " \t". */
1574 optlen = strcspn(opt, "#");
1578 if (opt[optlen] != 0) {
1579 config_msg = "No option value";
1583 /* Look for comment endings in the value. */
1584 size_t len = strcspn(value, "#");
1586 if (len < valuelen) {
1588 value[valuelen] = 0;
1591 status = set_option(opt, value);
1594 if (status == ERR) {
1595 fprintf(stderr, "Error on line %d, near '%.*s': %s\n",
1596 config_lineno, (int) optlen, opt, config_msg);
1597 config_errors = TRUE;
1600 /* Always keep going if errors are encountered. */
1605 load_option_file(const char *path)
1609 /* It's ok that the file doesn't exist. */
1610 if (!io_open(&io, path))
1614 config_errors = FALSE;
1616 if (read_properties(&io, " \t", read_option) == ERR ||
1617 config_errors == TRUE)
1618 fprintf(stderr, "Errors while loading %s.\n", path);
1624 const char *home = getenv("HOME");
1625 const char *tigrc_user = getenv("TIGRC_USER");
1626 const char *tigrc_system = getenv("TIGRC_SYSTEM");
1627 char buf[SIZEOF_STR];
1629 add_builtin_run_requests();
1631 if (!tigrc_system) {
1632 if (!string_format(buf, "%s/tigrc", SYSCONFDIR))
1636 load_option_file(tigrc_system);
1639 if (!home || !string_format(buf, "%s/.tigrc", home))
1643 load_option_file(tigrc_user);
1656 /* The display array of active views and the index of the current view. */
1657 static struct view *display[2];
1658 static unsigned int current_view;
1660 /* Reading from the prompt? */
1661 static bool input_mode = FALSE;
1663 #define foreach_displayed_view(view, i) \
1664 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1666 #define displayed_views() (display[1] != NULL ? 2 : 1)
1668 /* Current head and commit ID */
1669 static char ref_blob[SIZEOF_REF] = "";
1670 static char ref_commit[SIZEOF_REF] = "HEAD";
1671 static char ref_head[SIZEOF_REF] = "HEAD";
1674 const char *name; /* View name */
1675 const char *cmd_env; /* Command line set via environment */
1676 const char *id; /* Points to either of ref_{head,commit,blob} */
1678 struct view_ops *ops; /* View operations */
1680 enum keymap keymap; /* What keymap does this view have */
1681 bool git_dir; /* Whether the view requires a git directory. */
1683 char ref[SIZEOF_REF]; /* Hovered commit reference */
1684 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
1686 int height, width; /* The width and height of the main window */
1687 WINDOW *win; /* The main window */
1688 WINDOW *title; /* The title window living below the main window */
1691 unsigned long offset; /* Offset of the window top */
1692 unsigned long lineno; /* Current line number */
1695 char grep[SIZEOF_STR]; /* Search string */
1696 regex_t *regex; /* Pre-compiled regex */
1698 /* If non-NULL, points to the view that opened this view. If this view
1699 * is closed tig will switch back to the parent view. */
1700 struct view *parent;
1703 size_t lines; /* Total number of lines */
1704 struct line *line; /* Line index */
1705 size_t line_alloc; /* Total number of allocated lines */
1706 size_t line_size; /* Total number of used lines */
1707 unsigned int digits; /* Number of digits in the lines member. */
1710 struct line *curline; /* Line currently being drawn. */
1711 enum line_type curtype; /* Attribute currently used for drawing. */
1712 unsigned long col; /* Column when drawing. */
1721 /* What type of content being displayed. Used in the title bar. */
1723 /* Default command arguments. */
1725 /* Open and reads in all view content. */
1726 bool (*open)(struct view *view);
1727 /* Read one line; updates view->line. */
1728 bool (*read)(struct view *view, char *data);
1729 /* Draw one line; @lineno must be < view->height. */
1730 bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
1731 /* Depending on view handle a special requests. */
1732 enum request (*request)(struct view *view, enum request request, struct line *line);
1733 /* Search for regex in a line. */
1734 bool (*grep)(struct view *view, struct line *line);
1736 void (*select)(struct view *view, struct line *line);
1739 static struct view_ops blame_ops;
1740 static struct view_ops blob_ops;
1741 static struct view_ops diff_ops;
1742 static struct view_ops help_ops;
1743 static struct view_ops log_ops;
1744 static struct view_ops main_ops;
1745 static struct view_ops pager_ops;
1746 static struct view_ops stage_ops;
1747 static struct view_ops status_ops;
1748 static struct view_ops tree_ops;
1750 #define VIEW_STR(name, env, ref, ops, map, git) \
1751 { name, #env, ref, ops, map, git }
1753 #define VIEW_(id, name, ops, git, ref) \
1754 VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
1757 static struct view views[] = {
1758 VIEW_(MAIN, "main", &main_ops, TRUE, ref_head),
1759 VIEW_(DIFF, "diff", &diff_ops, TRUE, ref_commit),
1760 VIEW_(LOG, "log", &log_ops, TRUE, ref_head),
1761 VIEW_(TREE, "tree", &tree_ops, TRUE, ref_commit),
1762 VIEW_(BLOB, "blob", &blob_ops, TRUE, ref_blob),
1763 VIEW_(BLAME, "blame", &blame_ops, TRUE, ref_commit),
1764 VIEW_(HELP, "help", &help_ops, FALSE, ""),
1765 VIEW_(PAGER, "pager", &pager_ops, FALSE, "stdin"),
1766 VIEW_(STATUS, "status", &status_ops, TRUE, ""),
1767 VIEW_(STAGE, "stage", &stage_ops, TRUE, ""),
1770 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
1771 #define VIEW_REQ(view) ((view) - views + REQ_OFFSET + 1)
1773 #define foreach_view(view, i) \
1774 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1776 #define view_is_displayed(view) \
1777 (view == display[0] || view == display[1])
1784 static int line_graphics[] = {
1785 /* LINE_GRAPHIC_VLINE: */ '|'
1789 set_view_attr(struct view *view, enum line_type type)
1791 if (!view->curline->selected && view->curtype != type) {
1792 wattrset(view->win, get_line_attr(type));
1793 wchgat(view->win, -1, 0, type, NULL);
1794 view->curtype = type;
1799 draw_chars(struct view *view, enum line_type type, const char *string,
1800 int max_len, bool use_tilde)
1804 int trimmed = FALSE;
1810 len = utf8_length(string, &col, max_len, &trimmed, use_tilde);
1812 col = len = strlen(string);
1813 if (len > max_len) {
1817 col = len = max_len;
1822 set_view_attr(view, type);
1823 waddnstr(view->win, string, len);
1824 if (trimmed && use_tilde) {
1825 set_view_attr(view, LINE_DELIMITER);
1826 waddch(view->win, '~');
1834 draw_space(struct view *view, enum line_type type, int max, int spaces)
1836 static char space[] = " ";
1839 spaces = MIN(max, spaces);
1841 while (spaces > 0) {
1842 int len = MIN(spaces, sizeof(space) - 1);
1844 col += draw_chars(view, type, space, spaces, FALSE);
1852 draw_lineno(struct view *view, unsigned int lineno)
1855 int digits3 = view->digits < 3 ? 3 : view->digits;
1856 int max_number = MIN(digits3, STRING_SIZE(number));
1857 int max = view->width - view->col;
1860 if (max < max_number)
1863 lineno += view->offset + 1;
1864 if (lineno == 1 || (lineno % opt_num_interval) == 0) {
1865 static char fmt[] = "%1ld";
1867 if (view->digits <= 9)
1868 fmt[1] = '0' + digits3;
1870 if (!string_format(number, fmt, lineno))
1872 col = draw_chars(view, LINE_LINE_NUMBER, number, max_number, TRUE);
1874 col = draw_space(view, LINE_LINE_NUMBER, max_number, max_number);
1878 set_view_attr(view, LINE_DEFAULT);
1879 waddch(view->win, line_graphics[LINE_GRAPHIC_VLINE]);
1884 col += draw_space(view, LINE_DEFAULT, max - col, 1);
1887 return view->width - view->col <= 0;
1891 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
1893 view->col += draw_chars(view, type, string, view->width - view->col, trim);
1894 return view->width - view->col <= 0;
1898 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
1900 int max = view->width - view->col;
1906 set_view_attr(view, type);
1907 /* Using waddch() instead of waddnstr() ensures that
1908 * they'll be rendered correctly for the cursor line. */
1909 for (i = 0; i < size; i++)
1910 waddch(view->win, graphic[i]);
1914 waddch(view->win, ' ');
1918 return view->width - view->col <= 0;
1922 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
1924 int max = MIN(view->width - view->col, len);
1928 col = draw_chars(view, type, text, max - 1, trim);
1930 col = draw_space(view, type, max - 1, max - 1);
1932 view->col += col + draw_space(view, LINE_DEFAULT, max - col, max - col);
1933 return view->width - view->col <= 0;
1937 draw_date(struct view *view, struct tm *time)
1939 char buf[DATE_COLS];
1944 timelen = strftime(buf, sizeof(buf), DATE_FORMAT, time);
1945 date = timelen ? buf : NULL;
1947 return draw_field(view, LINE_DATE, date, DATE_COLS, FALSE);
1951 draw_view_line(struct view *view, unsigned int lineno)
1954 bool selected = (view->offset + lineno == view->lineno);
1957 assert(view_is_displayed(view));
1959 if (view->offset + lineno >= view->lines)
1962 line = &view->line[view->offset + lineno];
1964 wmove(view->win, lineno, 0);
1966 view->curline = line;
1967 view->curtype = LINE_NONE;
1968 line->selected = FALSE;
1971 set_view_attr(view, LINE_CURSOR);
1972 line->selected = TRUE;
1973 view->ops->select(view, line);
1974 } else if (line->selected) {
1975 wclrtoeol(view->win);
1978 scrollok(view->win, FALSE);
1979 draw_ok = view->ops->draw(view, line, lineno);
1980 scrollok(view->win, TRUE);
1986 redraw_view_dirty(struct view *view)
1991 for (lineno = 0; lineno < view->height; lineno++) {
1992 struct line *line = &view->line[view->offset + lineno];
1998 if (!draw_view_line(view, lineno))
2004 redrawwin(view->win);
2006 wnoutrefresh(view->win);
2008 wrefresh(view->win);
2012 redraw_view_from(struct view *view, int lineno)
2014 assert(0 <= lineno && lineno < view->height);
2016 for (; lineno < view->height; lineno++) {
2017 if (!draw_view_line(view, lineno))
2021 redrawwin(view->win);
2023 wnoutrefresh(view->win);
2025 wrefresh(view->win);
2029 redraw_view(struct view *view)
2032 redraw_view_from(view, 0);
2037 update_view_title(struct view *view)
2039 char buf[SIZEOF_STR];
2040 char state[SIZEOF_STR];
2041 size_t bufpos = 0, statelen = 0;
2043 assert(view_is_displayed(view));
2045 if (view != VIEW(REQ_VIEW_STATUS) && (view->lines || view->pipe)) {
2046 unsigned int view_lines = view->offset + view->height;
2047 unsigned int lines = view->lines
2048 ? MIN(view_lines, view->lines) * 100 / view->lines
2051 string_format_from(state, &statelen, "- %s %d of %d (%d%%)",
2058 time_t secs = time(NULL) - view->start_time;
2060 /* Three git seconds are a long time ... */
2062 string_format_from(state, &statelen, " %lds", secs);
2066 string_format_from(buf, &bufpos, "[%s]", view->name);
2067 if (*view->ref && bufpos < view->width) {
2068 size_t refsize = strlen(view->ref);
2069 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2071 if (minsize < view->width)
2072 refsize = view->width - minsize + 7;
2073 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2076 if (statelen && bufpos < view->width) {
2077 string_format_from(buf, &bufpos, " %s", state);
2080 if (view == display[current_view])
2081 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2083 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2085 mvwaddnstr(view->title, 0, 0, buf, bufpos);
2086 wclrtoeol(view->title);
2087 wmove(view->title, 0, view->width - 1);
2090 wnoutrefresh(view->title);
2092 wrefresh(view->title);
2096 resize_display(void)
2099 struct view *base = display[0];
2100 struct view *view = display[1] ? display[1] : display[0];
2102 /* Setup window dimensions */
2104 getmaxyx(stdscr, base->height, base->width);
2106 /* Make room for the status window. */
2110 /* Horizontal split. */
2111 view->width = base->width;
2112 view->height = SCALE_SPLIT_VIEW(base->height);
2113 base->height -= view->height;
2115 /* Make room for the title bar. */
2119 /* Make room for the title bar. */
2124 foreach_displayed_view (view, i) {
2126 view->win = newwin(view->height, 0, offset, 0);
2128 die("Failed to create %s view", view->name);
2130 scrollok(view->win, TRUE);
2132 view->title = newwin(1, 0, offset + view->height, 0);
2134 die("Failed to create title window");
2137 wresize(view->win, view->height, view->width);
2138 mvwin(view->win, offset, 0);
2139 mvwin(view->title, offset + view->height, 0);
2142 offset += view->height + 1;
2147 redraw_display(void)
2152 foreach_displayed_view (view, i) {
2154 update_view_title(view);
2159 update_display_cursor(struct view *view)
2161 /* Move the cursor to the right-most column of the cursor line.
2163 * XXX: This could turn out to be a bit expensive, but it ensures that
2164 * the cursor does not jump around. */
2166 wmove(view->win, view->lineno - view->offset, view->width - 1);
2167 wrefresh(view->win);
2175 /* Scrolling backend */
2177 do_scroll_view(struct view *view, int lines)
2179 bool redraw_current_line = FALSE;
2181 /* The rendering expects the new offset. */
2182 view->offset += lines;
2184 assert(0 <= view->offset && view->offset < view->lines);
2187 /* Move current line into the view. */
2188 if (view->lineno < view->offset) {
2189 view->lineno = view->offset;
2190 redraw_current_line = TRUE;
2191 } else if (view->lineno >= view->offset + view->height) {
2192 view->lineno = view->offset + view->height - 1;
2193 redraw_current_line = TRUE;
2196 assert(view->offset <= view->lineno && view->lineno < view->lines);
2198 /* Redraw the whole screen if scrolling is pointless. */
2199 if (view->height < ABS(lines)) {
2203 int line = lines > 0 ? view->height - lines : 0;
2204 int end = line + ABS(lines);
2206 wscrl(view->win, lines);
2208 for (; line < end; line++) {
2209 if (!draw_view_line(view, line))
2213 if (redraw_current_line)
2214 draw_view_line(view, view->lineno - view->offset);
2217 redrawwin(view->win);
2218 wrefresh(view->win);
2222 /* Scroll frontend */
2224 scroll_view(struct view *view, enum request request)
2228 assert(view_is_displayed(view));
2231 case REQ_SCROLL_PAGE_DOWN:
2232 lines = view->height;
2233 case REQ_SCROLL_LINE_DOWN:
2234 if (view->offset + lines > view->lines)
2235 lines = view->lines - view->offset;
2237 if (lines == 0 || view->offset + view->height >= view->lines) {
2238 report("Cannot scroll beyond the last line");
2243 case REQ_SCROLL_PAGE_UP:
2244 lines = view->height;
2245 case REQ_SCROLL_LINE_UP:
2246 if (lines > view->offset)
2247 lines = view->offset;
2250 report("Cannot scroll beyond the first line");
2258 die("request %d not handled in switch", request);
2261 do_scroll_view(view, lines);
2266 move_view(struct view *view, enum request request)
2268 int scroll_steps = 0;
2272 case REQ_MOVE_FIRST_LINE:
2273 steps = -view->lineno;
2276 case REQ_MOVE_LAST_LINE:
2277 steps = view->lines - view->lineno - 1;
2280 case REQ_MOVE_PAGE_UP:
2281 steps = view->height > view->lineno
2282 ? -view->lineno : -view->height;
2285 case REQ_MOVE_PAGE_DOWN:
2286 steps = view->lineno + view->height >= view->lines
2287 ? view->lines - view->lineno - 1 : view->height;
2299 die("request %d not handled in switch", request);
2302 if (steps <= 0 && view->lineno == 0) {
2303 report("Cannot move beyond the first line");
2306 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2307 report("Cannot move beyond the last line");
2311 /* Move the current line */
2312 view->lineno += steps;
2313 assert(0 <= view->lineno && view->lineno < view->lines);
2315 /* Check whether the view needs to be scrolled */
2316 if (view->lineno < view->offset ||
2317 view->lineno >= view->offset + view->height) {
2318 scroll_steps = steps;
2319 if (steps < 0 && -steps > view->offset) {
2320 scroll_steps = -view->offset;
2322 } else if (steps > 0) {
2323 if (view->lineno == view->lines - 1 &&
2324 view->lines > view->height) {
2325 scroll_steps = view->lines - view->offset - 1;
2326 if (scroll_steps >= view->height)
2327 scroll_steps -= view->height - 1;
2332 if (!view_is_displayed(view)) {
2333 view->offset += scroll_steps;
2334 assert(0 <= view->offset && view->offset < view->lines);
2335 view->ops->select(view, &view->line[view->lineno]);
2339 /* Repaint the old "current" line if we be scrolling */
2340 if (ABS(steps) < view->height)
2341 draw_view_line(view, view->lineno - steps - view->offset);
2344 do_scroll_view(view, scroll_steps);
2348 /* Draw the current line */
2349 draw_view_line(view, view->lineno - view->offset);
2351 redrawwin(view->win);
2352 wrefresh(view->win);
2361 static void search_view(struct view *view, enum request request);
2364 find_next_line(struct view *view, unsigned long lineno, struct line *line)
2366 assert(view_is_displayed(view));
2368 if (!view->ops->grep(view, line))
2371 if (lineno - view->offset >= view->height) {
2372 view->offset = lineno;
2373 view->lineno = lineno;
2377 unsigned long old_lineno = view->lineno - view->offset;
2379 view->lineno = lineno;
2380 draw_view_line(view, old_lineno);
2382 draw_view_line(view, view->lineno - view->offset);
2383 redrawwin(view->win);
2384 wrefresh(view->win);
2387 report("Line %ld matches '%s'", lineno + 1, view->grep);
2392 find_next(struct view *view, enum request request)
2394 unsigned long lineno = view->lineno;
2399 report("No previous search");
2401 search_view(view, request);
2411 case REQ_SEARCH_BACK:
2420 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2421 lineno += direction;
2423 /* Note, lineno is unsigned long so will wrap around in which case it
2424 * will become bigger than view->lines. */
2425 for (; lineno < view->lines; lineno += direction) {
2426 struct line *line = &view->line[lineno];
2428 if (find_next_line(view, lineno, line))
2432 report("No match found for '%s'", view->grep);
2436 search_view(struct view *view, enum request request)
2441 regfree(view->regex);
2444 view->regex = calloc(1, sizeof(*view->regex));
2449 regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2450 if (regex_err != 0) {
2451 char buf[SIZEOF_STR] = "unknown error";
2453 regerror(regex_err, view->regex, buf, sizeof(buf));
2454 report("Search failed: %s", buf);
2458 string_copy(view->grep, opt_search);
2460 find_next(view, request);
2464 * Incremental updating
2468 reset_view(struct view *view)
2472 for (i = 0; i < view->lines; i++)
2473 free(view->line[i].data);
2480 view->line_size = 0;
2481 view->line_alloc = 0;
2486 free_argv(const char *argv[])
2490 for (argc = 0; argv[argc]; argc++)
2491 free((void *) argv[argc]);
2495 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
2497 char buf[SIZEOF_STR];
2499 bool noreplace = flags == FORMAT_NONE;
2501 free_argv(dst_argv);
2503 for (argc = 0; src_argv[argc]; argc++) {
2504 const char *arg = src_argv[argc];
2508 char *next = strstr(arg, "%(");
2509 int len = next - arg;
2512 if (!next || noreplace) {
2513 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
2518 } else if (!prefixcmp(next, "%(directory)")) {
2521 } else if (!prefixcmp(next, "%(file)")) {
2524 } else if (!prefixcmp(next, "%(ref)")) {
2525 value = *opt_ref ? opt_ref : "HEAD";
2527 } else if (!prefixcmp(next, "%(head)")) {
2530 } else if (!prefixcmp(next, "%(commit)")) {
2533 } else if (!prefixcmp(next, "%(blob)")) {
2537 report("Unknown replacement: `%s`", next);
2541 if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
2544 arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
2547 dst_argv[argc] = strdup(buf);
2548 if (!dst_argv[argc])
2552 dst_argv[argc] = NULL;
2554 return src_argv[argc] == NULL;
2558 format_command(char dst[], const char *src_argv[], enum format_flags flags)
2560 const char *dst_argv[SIZEOF_ARG * 2] = { NULL };
2564 if (!format_argv(dst_argv, src_argv, flags)) {
2565 free_argv(dst_argv);
2569 for (argc = 0; dst_argv[argc] && bufsize < SIZEOF_STR; argc++) {
2571 dst[bufsize++] = ' ';
2572 bufsize = sq_quote(dst, bufsize, dst_argv[argc]);
2575 if (bufsize < SIZEOF_STR)
2577 free_argv(dst_argv);
2579 return src_argv[argc] == NULL && bufsize < SIZEOF_STR;
2583 end_update(struct view *view, bool force)
2587 while (!view->ops->read(view, NULL))
2590 set_nonblocking_input(FALSE);
2591 done_io(view->pipe);
2596 setup_update(struct view *view, const char *vid)
2598 set_nonblocking_input(TRUE);
2600 string_copy_rev(view->vid, vid);
2601 view->pipe = &view->io;
2602 view->start_time = time(NULL);
2606 prepare_update(struct view *view, const char *argv[], const char *dir,
2607 enum format_flags flags)
2610 end_update(view, TRUE);
2611 return init_io_rd(&view->io, argv, dir, flags);
2615 prepare_update_file(struct view *view, const char *name)
2618 end_update(view, TRUE);
2619 return io_open(&view->io, name);
2623 begin_update(struct view *view, bool refresh)
2626 if (!start_io(&view->io))
2630 if (view == VIEW(REQ_VIEW_TREE) && strcmp(view->vid, view->id))
2633 if (!run_io_rd(&view->io, view->ops->argv, FORMAT_ALL))
2636 /* Put the current ref_* value to the view title ref
2637 * member. This is needed by the blob view. Most other
2638 * views sets it automatically after loading because the
2639 * first line is a commit line. */
2640 string_copy_rev(view->ref, view->id);
2643 setup_update(view, view->id);
2648 #define ITEM_CHUNK_SIZE 256
2650 realloc_items(void *mem, size_t *size, size_t new_size, size_t item_size)
2652 size_t num_chunks = *size / ITEM_CHUNK_SIZE;
2653 size_t num_chunks_new = (new_size + ITEM_CHUNK_SIZE - 1) / ITEM_CHUNK_SIZE;
2655 if (mem == NULL || num_chunks != num_chunks_new) {
2656 *size = num_chunks_new * ITEM_CHUNK_SIZE;
2657 mem = realloc(mem, *size * item_size);
2663 static struct line *
2664 realloc_lines(struct view *view, size_t line_size)
2666 size_t alloc = view->line_alloc;
2667 struct line *tmp = realloc_items(view->line, &alloc, line_size,
2668 sizeof(*view->line));
2674 view->line_alloc = alloc;
2675 view->line_size = line_size;
2680 update_view(struct view *view)
2682 char out_buffer[BUFSIZ * 2];
2684 /* The number of lines to read. If too low it will cause too much
2685 * redrawing (and possible flickering), if too high responsiveness
2687 unsigned long lines = view->height;
2688 int redraw_from = -1;
2693 /* Only redraw if lines are visible. */
2694 if (view->offset + view->height >= view->lines)
2695 redraw_from = view->lines - view->offset;
2697 /* FIXME: This is probably not perfect for backgrounded views. */
2698 if (!realloc_lines(view, view->lines + lines))
2701 while ((line = io_gets(view->pipe))) {
2702 size_t linelen = strlen(line);
2704 if (opt_iconv != ICONV_NONE) {
2705 ICONV_CONST char *inbuf = line;
2706 size_t inlen = linelen;
2708 char *outbuf = out_buffer;
2709 size_t outlen = sizeof(out_buffer);
2713 ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2714 if (ret != (size_t) -1) {
2716 linelen = strlen(out_buffer);
2720 if (!view->ops->read(view, line))
2730 lines = view->lines;
2731 for (digits = 0; lines; digits++)
2734 /* Keep the displayed view in sync with line number scaling. */
2735 if (digits != view->digits) {
2736 view->digits = digits;
2741 if (io_error(view->pipe)) {
2742 report("Failed to read: %s", io_strerror(view->pipe));
2743 end_update(view, TRUE);
2745 } else if (io_eof(view->pipe)) {
2747 end_update(view, FALSE);
2750 if (!view_is_displayed(view))
2753 if (view == VIEW(REQ_VIEW_TREE)) {
2754 /* Clear the view and redraw everything since the tree sorting
2755 * might have rearranged things. */
2758 } else if (redraw_from >= 0) {
2759 /* If this is an incremental update, redraw the previous line
2760 * since for commits some members could have changed when
2761 * loading the main view. */
2762 if (redraw_from > 0)
2765 /* Since revision graph visualization requires knowledge
2766 * about the parent commit, it causes a further one-off
2767 * needed to be redrawn for incremental updates. */
2768 if (redraw_from > 0 && opt_rev_graph)
2771 /* Incrementally draw avoids flickering. */
2772 redraw_view_from(view, redraw_from);
2775 if (view == VIEW(REQ_VIEW_BLAME))
2776 redraw_view_dirty(view);
2778 /* Update the title _after_ the redraw so that if the redraw picks up a
2779 * commit reference in view->ref it'll be available here. */
2780 update_view_title(view);
2784 report("Allocation failure");
2785 end_update(view, TRUE);
2789 static struct line *
2790 add_line_data(struct view *view, void *data, enum line_type type)
2792 struct line *line = &view->line[view->lines++];
2794 memset(line, 0, sizeof(*line));
2801 static struct line *
2802 add_line_text(struct view *view, const char *text, enum line_type type)
2804 char *data = text ? strdup(text) : NULL;
2806 return data ? add_line_data(view, data, type) : NULL;
2815 OPEN_DEFAULT = 0, /* Use default view switching. */
2816 OPEN_SPLIT = 1, /* Split current view. */
2817 OPEN_BACKGROUNDED = 2, /* Backgrounded. */
2818 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
2819 OPEN_NOMAXIMIZE = 8, /* Do not maximize the current view. */
2820 OPEN_REFRESH = 16, /* Refresh view using previous command. */
2821 OPEN_PREPARED = 32, /* Open already prepared command. */
2825 open_view(struct view *prev, enum request request, enum open_flags flags)
2827 bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
2828 bool split = !!(flags & OPEN_SPLIT);
2829 bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
2830 bool nomaximize = !!(flags & (OPEN_NOMAXIMIZE | OPEN_REFRESH));
2831 struct view *view = VIEW(request);
2832 int nviews = displayed_views();
2833 struct view *base_view = display[0];
2835 if (view == prev && nviews == 1 && !reload) {
2836 report("Already in %s view", view->name);
2840 if (view->git_dir && !opt_git_dir[0]) {
2841 report("The %s view is disabled in pager view", view->name);
2849 } else if (!nomaximize) {
2850 /* Maximize the current view. */
2851 memset(display, 0, sizeof(display));
2853 display[current_view] = view;
2856 /* Resize the view when switching between split- and full-screen,
2857 * or when switching between two different full-screen views. */
2858 if (nviews != displayed_views() ||
2859 (nviews == 1 && base_view != display[0]))
2863 end_update(view, TRUE);
2865 if (view->ops->open) {
2866 if (!view->ops->open(view)) {
2867 report("Failed to load %s view", view->name);
2871 } else if ((reload || strcmp(view->vid, view->id)) &&
2872 !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
2873 report("Failed to load %s view", view->name);
2877 if (split && prev->lineno - prev->offset >= prev->height) {
2878 /* Take the title line into account. */
2879 int lines = prev->lineno - prev->offset - prev->height + 1;
2881 /* Scroll the view that was split if the current line is
2882 * outside the new limited view. */
2883 do_scroll_view(prev, lines);
2886 if (prev && view != prev) {
2887 if (split && !backgrounded) {
2888 /* "Blur" the previous view. */
2889 update_view_title(prev);
2892 view->parent = prev;
2895 if (view->pipe && view->lines == 0) {
2896 /* Clear the old view and let the incremental updating refill
2900 } else if (view_is_displayed(view)) {
2905 /* If the view is backgrounded the above calls to report()
2906 * won't redraw the view title. */
2908 update_view_title(view);
2912 open_external_viewer(const char *argv[], const char *dir)
2914 def_prog_mode(); /* save current tty modes */
2915 endwin(); /* restore original tty modes */
2916 run_io_fg(argv, dir);
2917 fprintf(stderr, "Press Enter to continue");
2924 open_mergetool(const char *file)
2926 const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
2928 open_external_viewer(mergetool_argv, NULL);
2932 open_editor(bool from_root, const char *file)
2934 const char *editor_argv[] = { "vi", file, NULL };
2937 editor = getenv("GIT_EDITOR");
2938 if (!editor && *opt_editor)
2939 editor = opt_editor;
2941 editor = getenv("VISUAL");
2943 editor = getenv("EDITOR");
2947 editor_argv[0] = editor;
2948 open_external_viewer(editor_argv, from_root ? opt_cdup : NULL);
2952 open_run_request(enum request request)
2954 struct run_request *req = get_run_request(request);
2955 const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
2958 report("Unknown run request");
2962 if (format_argv(argv, req->argv, FORMAT_ALL))
2963 open_external_viewer(argv, NULL);
2968 * User request switch noodle
2972 view_driver(struct view *view, enum request request)
2976 if (request == REQ_NONE) {
2981 if (request > REQ_NONE) {
2982 open_run_request(request);
2983 /* FIXME: When all views can refresh always do this. */
2984 if (view == VIEW(REQ_VIEW_STATUS) ||
2985 view == VIEW(REQ_VIEW_MAIN) ||
2986 view == VIEW(REQ_VIEW_LOG) ||
2987 view == VIEW(REQ_VIEW_STAGE))
2988 request = REQ_REFRESH;
2993 if (view && view->lines) {
2994 request = view->ops->request(view, request, &view->line[view->lineno]);
2995 if (request == REQ_NONE)
3002 case REQ_MOVE_PAGE_UP:
3003 case REQ_MOVE_PAGE_DOWN:
3004 case REQ_MOVE_FIRST_LINE:
3005 case REQ_MOVE_LAST_LINE:
3006 move_view(view, request);
3009 case REQ_SCROLL_LINE_DOWN:
3010 case REQ_SCROLL_LINE_UP:
3011 case REQ_SCROLL_PAGE_DOWN:
3012 case REQ_SCROLL_PAGE_UP:
3013 scroll_view(view, request);
3016 case REQ_VIEW_BLAME:
3018 report("No file chosen, press %s to open tree view",
3019 get_key(REQ_VIEW_TREE));
3022 open_view(view, request, OPEN_DEFAULT);
3027 report("No file chosen, press %s to open tree view",
3028 get_key(REQ_VIEW_TREE));
3031 open_view(view, request, OPEN_DEFAULT);
3034 case REQ_VIEW_PAGER:
3035 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3036 report("No pager content, press %s to run command from prompt",
3037 get_key(REQ_PROMPT));
3040 open_view(view, request, OPEN_DEFAULT);
3043 case REQ_VIEW_STAGE:
3044 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3045 report("No stage content, press %s to open the status view and choose file",
3046 get_key(REQ_VIEW_STATUS));
3049 open_view(view, request, OPEN_DEFAULT);
3052 case REQ_VIEW_STATUS:
3053 if (opt_is_inside_work_tree == FALSE) {
3054 report("The status view requires a working tree");
3057 open_view(view, request, OPEN_DEFAULT);
3065 open_view(view, request, OPEN_DEFAULT);
3070 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3072 if ((view == VIEW(REQ_VIEW_DIFF) &&
3073 view->parent == VIEW(REQ_VIEW_MAIN)) ||
3074 (view == VIEW(REQ_VIEW_DIFF) &&
3075 view->parent == VIEW(REQ_VIEW_BLAME)) ||
3076 (view == VIEW(REQ_VIEW_STAGE) &&
3077 view->parent == VIEW(REQ_VIEW_STATUS)) ||
3078 (view == VIEW(REQ_VIEW_BLOB) &&
3079 view->parent == VIEW(REQ_VIEW_TREE))) {
3082 view = view->parent;
3083 line = view->lineno;
3084 move_view(view, request);
3085 if (view_is_displayed(view))
3086 update_view_title(view);
3087 if (line != view->lineno)
3088 view->ops->request(view, REQ_ENTER,
3089 &view->line[view->lineno]);
3092 move_view(view, request);
3098 int nviews = displayed_views();
3099 int next_view = (current_view + 1) % nviews;
3101 if (next_view == current_view) {
3102 report("Only one view is displayed");
3106 current_view = next_view;
3107 /* Blur out the title of the previous view. */
3108 update_view_title(view);
3113 report("Refreshing is not yet supported for the %s view", view->name);
3117 if (displayed_views() == 2)
3118 open_view(view, VIEW_REQ(view), OPEN_DEFAULT);
3121 case REQ_TOGGLE_LINENO:
3122 opt_line_number = !opt_line_number;
3126 case REQ_TOGGLE_DATE:
3127 opt_date = !opt_date;
3131 case REQ_TOGGLE_AUTHOR:
3132 opt_author = !opt_author;
3136 case REQ_TOGGLE_REV_GRAPH:
3137 opt_rev_graph = !opt_rev_graph;
3141 case REQ_TOGGLE_REFS:
3142 opt_show_refs = !opt_show_refs;
3147 case REQ_SEARCH_BACK:
3148 search_view(view, request);
3153 find_next(view, request);
3156 case REQ_STOP_LOADING:
3157 for (i = 0; i < ARRAY_SIZE(views); i++) {
3160 report("Stopped loading the %s view", view->name),
3161 end_update(view, TRUE);
3165 case REQ_SHOW_VERSION:
3166 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3169 case REQ_SCREEN_RESIZE:
3172 case REQ_SCREEN_REDRAW:
3177 report("Nothing to edit");
3181 report("Nothing to enter");
3184 case REQ_VIEW_CLOSE:
3185 /* XXX: Mark closed views by letting view->parent point to the
3186 * view itself. Parents to closed view should never be
3189 view->parent->parent != view->parent) {
3190 memset(display, 0, sizeof(display));
3192 display[current_view] = view->parent;
3193 view->parent = view;
3204 report("Unknown key, press 'h' for help");
3217 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3219 char *text = line->data;
3221 if (opt_line_number && draw_lineno(view, lineno))
3224 draw_text(view, line->type, text, TRUE);
3229 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3231 const char *describe_argv[] = { "git", "describe", commit_id, NULL };
3232 char refbuf[SIZEOF_STR];
3235 if (run_io_buf(describe_argv, refbuf, sizeof(refbuf)))
3236 ref = chomp_string(refbuf);
3241 /* This is the only fatal call, since it can "corrupt" the buffer. */
3242 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
3249 add_pager_refs(struct view *view, struct line *line)
3251 char buf[SIZEOF_STR];
3252 char *commit_id = (char *)line->data + STRING_SIZE("commit ");
3254 size_t bufpos = 0, refpos = 0;
3255 const char *sep = "Refs: ";
3256 bool is_tag = FALSE;
3258 assert(line->type == LINE_COMMIT);
3260 refs = get_refs(commit_id);
3262 if (view == VIEW(REQ_VIEW_DIFF))
3263 goto try_add_describe_ref;
3268 struct ref *ref = refs[refpos];
3269 const char *fmt = ref->tag ? "%s[%s]" :
3270 ref->remote ? "%s<%s>" : "%s%s";
3272 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
3277 } while (refs[refpos++]->next);
3279 if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
3280 try_add_describe_ref:
3281 /* Add <tag>-g<commit_id> "fake" reference. */
3282 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
3289 if (!realloc_lines(view, view->line_size + 1))
3292 add_line_text(view, buf, LINE_PP_REFS);
3296 pager_read(struct view *view, char *data)
3303 line = add_line_text(view, data, get_line_type(data));
3307 if (line->type == LINE_COMMIT &&
3308 (view == VIEW(REQ_VIEW_DIFF) ||
3309 view == VIEW(REQ_VIEW_LOG)))
3310 add_pager_refs(view, line);
3316 pager_request(struct view *view, enum request request, struct line *line)
3320 if (request != REQ_ENTER)
3323 if (line->type == LINE_COMMIT &&
3324 (view == VIEW(REQ_VIEW_LOG) ||
3325 view == VIEW(REQ_VIEW_PAGER))) {
3326 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3330 /* Always scroll the view even if it was split. That way
3331 * you can use Enter to scroll through the log view and
3332 * split open each commit diff. */
3333 scroll_view(view, REQ_SCROLL_LINE_DOWN);
3335 /* FIXME: A minor workaround. Scrolling the view will call report("")
3336 * but if we are scrolling a non-current view this won't properly
3337 * update the view title. */
3339 update_view_title(view);
3345 pager_grep(struct view *view, struct line *line)
3348 char *text = line->data;
3353 if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
3360 pager_select(struct view *view, struct line *line)
3362 if (line->type == LINE_COMMIT) {
3363 char *text = (char *)line->data + STRING_SIZE("commit ");
3365 if (view != VIEW(REQ_VIEW_PAGER))
3366 string_copy_rev(view->ref, text);
3367 string_copy_rev(ref_commit, text);
3371 static struct view_ops pager_ops = {
3382 static const char *log_argv[SIZEOF_ARG] = {
3383 "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3387 log_request(struct view *view, enum request request, struct line *line)
3392 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
3395 return pager_request(view, request, line);
3399 static struct view_ops log_ops = {
3410 static const char *diff_argv[SIZEOF_ARG] = {
3411 "git", "show", "--pretty=fuller", "--no-color", "--root",
3412 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
3415 static struct view_ops diff_ops = {
3431 help_open(struct view *view)
3434 int lines = ARRAY_SIZE(req_info) + 2;
3437 if (view->lines > 0)
3440 for (i = 0; i < ARRAY_SIZE(req_info); i++)
3441 if (!req_info[i].request)
3444 lines += run_requests + 1;
3446 view->line = calloc(lines, sizeof(*view->line));
3450 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
3452 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
3455 if (req_info[i].request == REQ_NONE)
3458 if (!req_info[i].request) {
3459 add_line_text(view, "", LINE_DEFAULT);
3460 add_line_text(view, req_info[i].help, LINE_DEFAULT);
3464 key = get_key(req_info[i].request);
3466 key = "(no key defined)";
3468 if (!string_format(buf, " %-25s %s", key, req_info[i].help))
3471 add_line_text(view, buf, LINE_DEFAULT);
3475 add_line_text(view, "", LINE_DEFAULT);
3476 add_line_text(view, "External commands:", LINE_DEFAULT);
3479 for (i = 0; i < run_requests; i++) {
3480 struct run_request *req = get_run_request(REQ_NONE + i + 1);
3482 char cmd[SIZEOF_STR];
3489 key = get_key_name(req->key);
3491 key = "(no key defined)";
3493 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
3494 if (!string_format_from(cmd, &bufpos, "%s%s",
3495 argc ? " " : "", req->argv[argc]))
3498 if (!string_format(buf, " %-10s %-14s `%s`",
3499 keymap_table[req->keymap].name, key, cmd))
3502 add_line_text(view, buf, LINE_DEFAULT);
3508 static struct view_ops help_ops = {
3524 struct tree_stack_entry {
3525 struct tree_stack_entry *prev; /* Entry below this in the stack */
3526 unsigned long lineno; /* Line number to restore */
3527 char *name; /* Position of name in opt_path */
3530 /* The top of the path stack. */
3531 static struct tree_stack_entry *tree_stack = NULL;
3532 unsigned long tree_lineno = 0;
3535 pop_tree_stack_entry(void)
3537 struct tree_stack_entry *entry = tree_stack;
3539 tree_lineno = entry->lineno;
3541 tree_stack = entry->prev;
3546 push_tree_stack_entry(const char *name, unsigned long lineno)
3548 struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
3549 size_t pathlen = strlen(opt_path);
3554 entry->prev = tree_stack;
3555 entry->name = opt_path + pathlen;
3558 if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
3559 pop_tree_stack_entry();
3563 /* Move the current line to the first tree entry. */
3565 entry->lineno = lineno;
3568 /* Parse output from git-ls-tree(1):
3570 * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile
3571 * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README
3572 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
3573 * 100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38 web.conf
3576 #define SIZEOF_TREE_ATTR \
3577 STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t")
3579 #define TREE_UP_FORMAT "040000 tree %s\t.."
3582 tree_compare_entry(enum line_type type1, const char *name1,
3583 enum line_type type2, const char *name2)
3585 if (type1 != type2) {
3586 if (type1 == LINE_TREE_DIR)
3591 return strcmp(name1, name2);
3595 tree_path(struct line *line)
3597 const char *path = line->data;
3599 return path + SIZEOF_TREE_ATTR;
3603 tree_read(struct view *view, char *text)
3605 size_t textlen = text ? strlen(text) : 0;
3606 char buf[SIZEOF_STR];
3608 enum line_type type;
3609 bool first_read = view->lines == 0;
3613 if (textlen <= SIZEOF_TREE_ATTR)
3616 type = text[STRING_SIZE("100644 ")] == 't'
3617 ? LINE_TREE_DIR : LINE_TREE_FILE;
3620 /* Add path info line */
3621 if (!string_format(buf, "Directory path /%s", opt_path) ||
3622 !realloc_lines(view, view->line_size + 1) ||
3623 !add_line_text(view, buf, LINE_DEFAULT))
3626 /* Insert "link" to parent directory. */
3628 if (!string_format(buf, TREE_UP_FORMAT, view->ref) ||
3629 !realloc_lines(view, view->line_size + 1) ||
3630 !add_line_text(view, buf, LINE_TREE_DIR))
3635 /* Strip the path part ... */
3637 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
3638 size_t striplen = strlen(opt_path);
3639 char *path = text + SIZEOF_TREE_ATTR;
3641 if (pathlen > striplen)
3642 memmove(path, path + striplen,
3643 pathlen - striplen + 1);
3646 /* Skip "Directory ..." and ".." line. */
3647 for (pos = 1 + !!*opt_path; pos < view->lines; pos++) {
3648 struct line *line = &view->line[pos];
3649 const char *path1 = tree_path(line);
3650 char *path2 = text + SIZEOF_TREE_ATTR;
3651 int cmp = tree_compare_entry(line->type, path1, type, path2);
3656 text = strdup(text);
3660 if (view->lines > pos)
3661 memmove(&view->line[pos + 1], &view->line[pos],
3662 (view->lines - pos) * sizeof(*line));
3664 line = &view->line[pos];
3671 if (!add_line_text(view, text, type))
3674 if (tree_lineno > view->lineno) {
3675 view->lineno = tree_lineno;
3683 tree_request(struct view *view, enum request request, struct line *line)
3685 enum open_flags flags;
3688 case REQ_VIEW_BLAME:
3689 if (line->type != LINE_TREE_FILE) {
3690 report("Blame only supported for files");
3694 string_copy(opt_ref, view->vid);
3698 if (line->type != LINE_TREE_FILE) {
3699 report("Edit only supported for files");
3700 } else if (!is_head_commit(view->vid)) {
3701 report("Edit only supported for files in the current work tree");
3703 open_editor(TRUE, opt_file);
3707 case REQ_TREE_PARENT:
3709 /* quit view if at top of tree */
3710 return REQ_VIEW_CLOSE;
3713 line = &view->line[1];
3723 /* Cleanup the stack if the tree view is at a different tree. */
3724 while (!*opt_path && tree_stack)
3725 pop_tree_stack_entry();
3727 switch (line->type) {
3729 /* Depending on whether it is a subdir or parent (updir?) link
3730 * mangle the path buffer. */
3731 if (line == &view->line[1] && *opt_path) {
3732 pop_tree_stack_entry();
3735 const char *basename = tree_path(line);
3737 push_tree_stack_entry(basename, view->lineno);
3740 /* Trees and subtrees share the same ID, so they are not not
3741 * unique like blobs. */
3742 flags = OPEN_RELOAD;
3743 request = REQ_VIEW_TREE;
3746 case LINE_TREE_FILE:
3747 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
3748 request = REQ_VIEW_BLOB;
3755 open_view(view, request, flags);
3756 if (request == REQ_VIEW_TREE) {
3757 view->lineno = tree_lineno;
3764 tree_select(struct view *view, struct line *line)
3766 char *text = (char *)line->data + STRING_SIZE("100644 blob ");
3768 if (line->type == LINE_TREE_FILE) {
3769 string_copy_rev(ref_blob, text);
3770 string_format(opt_file, "%s%s", opt_path, tree_path(line));
3772 } else if (line->type != LINE_TREE_DIR) {
3776 string_copy_rev(view->ref, text);
3779 static const char *tree_argv[SIZEOF_ARG] = {
3780 "git", "ls-tree", "%(commit)", "%(directory)", NULL
3783 static struct view_ops tree_ops = {
3795 blob_read(struct view *view, char *line)
3799 return add_line_text(view, line, LINE_DEFAULT) != NULL;
3802 static const char *blob_argv[SIZEOF_ARG] = {
3803 "git", "cat-file", "blob", "%(blob)", NULL
3806 static struct view_ops blob_ops = {
3820 * Loading the blame view is a two phase job:
3822 * 1. File content is read either using opt_file from the
3823 * filesystem or using git-cat-file.
3824 * 2. Then blame information is incrementally added by
3825 * reading output from git-blame.
3828 static const char *blame_head_argv[] = {
3829 "git", "blame", "--incremental", "--", "%(file)", NULL
3832 static const char *blame_ref_argv[] = {
3833 "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
3836 static const char *blame_cat_file_argv[] = {
3837 "git", "cat-file", "blob", "%(ref):%(file)", NULL
3840 struct blame_commit {
3841 char id[SIZEOF_REV]; /* SHA1 ID. */
3842 char title[128]; /* First line of the commit message. */
3843 char author[75]; /* Author of the commit. */
3844 struct tm time; /* Date from the author ident. */
3845 char filename[128]; /* Name of file. */
3849 struct blame_commit *commit;
3854 blame_open(struct view *view)
3856 if (*opt_ref || !io_open(&view->io, opt_file)) {
3857 if (!run_io_rd(&view->io, blame_cat_file_argv, FORMAT_ALL))
3861 setup_update(view, opt_file);
3862 string_format(view->ref, "%s ...", opt_file);
3867 static struct blame_commit *
3868 get_blame_commit(struct view *view, const char *id)
3872 for (i = 0; i < view->lines; i++) {
3873 struct blame *blame = view->line[i].data;
3878 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
3879 return blame->commit;
3883 struct blame_commit *commit = calloc(1, sizeof(*commit));
3886 string_ncopy(commit->id, id, SIZEOF_REV);
3892 parse_number(const char **posref, size_t *number, size_t min, size_t max)
3894 const char *pos = *posref;
3897 pos = strchr(pos + 1, ' ');
3898 if (!pos || !isdigit(pos[1]))
3900 *number = atoi(pos + 1);
3901 if (*number < min || *number > max)
3908 static struct blame_commit *
3909 parse_blame_commit(struct view *view, const char *text, int *blamed)
3911 struct blame_commit *commit;
3912 struct blame *blame;
3913 const char *pos = text + SIZEOF_REV - 1;
3917 if (strlen(text) <= SIZEOF_REV || *pos != ' ')
3920 if (!parse_number(&pos, &lineno, 1, view->lines) ||
3921 !parse_number(&pos, &group, 1, view->lines - lineno + 1))
3924 commit = get_blame_commit(view, text);
3930 struct line *line = &view->line[lineno + group - 1];
3933 blame->commit = commit;
3941 blame_read_file(struct view *view, const char *line, bool *read_file)
3944 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
3947 if (view->lines == 0 && !view->parent)
3948 die("No blame exist for %s", view->vid);
3950 if (view->lines == 0 || !run_io_rd(&io, argv, FORMAT_ALL)) {
3951 report("Failed to load blame data");
3955 done_io(view->pipe);
3961 size_t linelen = strlen(line);
3962 struct blame *blame = malloc(sizeof(*blame) + linelen);
3964 blame->commit = NULL;
3965 strncpy(blame->text, line, linelen);
3966 blame->text[linelen] = 0;
3967 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
3972 match_blame_header(const char *name, char **line)
3974 size_t namelen = strlen(name);
3975 bool matched = !strncmp(name, *line, namelen);
3984 blame_read(struct view *view, char *line)
3986 static struct blame_commit *commit = NULL;
3987 static int blamed = 0;
3988 static time_t author_time;
3989 static bool read_file = TRUE;
3992 return blame_read_file(view, line, &read_file);
3999 string_format(view->ref, "%s", view->vid);
4000 if (view_is_displayed(view)) {
4001 update_view_title(view);
4002 redraw_view_from(view, 0);
4008 commit = parse_blame_commit(view, line, &blamed);
4009 string_format(view->ref, "%s %2d%%", view->vid,
4010 blamed * 100 / view->lines);
4012 } else if (match_blame_header("author ", &line)) {
4013 string_ncopy(commit->author, line, strlen(line));
4015 } else if (match_blame_header("author-time ", &line)) {
4016 author_time = (time_t) atol(line);
4018 } else if (match_blame_header("author-tz ", &line)) {
4021 tz = ('0' - line[1]) * 60 * 60 * 10;
4022 tz += ('0' - line[2]) * 60 * 60;
4023 tz += ('0' - line[3]) * 60;
4024 tz += ('0' - line[4]) * 60;
4030 gmtime_r(&author_time, &commit->time);
4032 } else if (match_blame_header("summary ", &line)) {
4033 string_ncopy(commit->title, line, strlen(line));
4035 } else if (match_blame_header("filename ", &line)) {
4036 string_ncopy(commit->filename, line, strlen(line));
4044 blame_draw(struct view *view, struct line *line, unsigned int lineno)
4046 struct blame *blame = line->data;
4047 struct tm *time = NULL;
4048 const char *id = NULL, *author = NULL;
4050 if (blame->commit && *blame->commit->filename) {
4051 id = blame->commit->id;
4052 author = blame->commit->author;
4053 time = &blame->commit->time;
4056 if (opt_date && draw_date(view, time))
4060 draw_field(view, LINE_MAIN_AUTHOR, author, opt_author_cols, TRUE))
4063 if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
4066 if (draw_lineno(view, lineno))
4069 draw_text(view, LINE_DEFAULT, blame->text, TRUE);
4074 blame_request(struct view *view, enum request request, struct line *line)
4076 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4077 struct blame *blame = line->data;
4080 case REQ_VIEW_BLAME:
4081 if (!blame->commit || !strcmp(blame->commit->id, NULL_ID)) {
4082 report("Commit ID unknown");
4085 string_copy(opt_ref, blame->commit->id);
4086 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4090 if (!blame->commit) {
4091 report("No commit loaded yet");
4095 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
4096 !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
4099 if (!strcmp(blame->commit->id, NULL_ID)) {
4100 struct view *diff = VIEW(REQ_VIEW_DIFF);
4101 const char *diff_index_argv[] = {
4102 "git", "diff-index", "--root", "--cached",
4103 "--patch-with-stat", "-C", "-M",
4104 "HEAD", "--", view->vid, NULL
4107 if (!prepare_update(diff, diff_index_argv, NULL, FORMAT_DASH)) {
4108 report("Failed to allocate diff command");
4111 flags |= OPEN_PREPARED;
4114 open_view(view, REQ_VIEW_DIFF, flags);
4125 blame_grep(struct view *view, struct line *line)
4127 struct blame *blame = line->data;
4128 struct blame_commit *commit = blame->commit;
4131 #define MATCH(text, on) \
4132 (on && *text && regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4135 char buf[DATE_COLS + 1];
4137 if (MATCH(commit->title, 1) ||
4138 MATCH(commit->author, opt_author) ||
4139 MATCH(commit->id, opt_date))
4142 if (strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time) &&
4147 return MATCH(blame->text, 1);
4153 blame_select(struct view *view, struct line *line)
4155 struct blame *blame = line->data;
4156 struct blame_commit *commit = blame->commit;
4161 if (!strcmp(commit->id, NULL_ID))
4162 string_ncopy(ref_commit, "HEAD", 4);
4164 string_copy_rev(ref_commit, commit->id);
4167 static struct view_ops blame_ops = {
4186 char rev[SIZEOF_REV];
4187 char name[SIZEOF_STR];
4191 char rev[SIZEOF_REV];
4192 char name[SIZEOF_STR];
4196 static char status_onbranch[SIZEOF_STR];
4197 static struct status stage_status;
4198 static enum line_type stage_line_type;
4199 static size_t stage_chunks;
4200 static int *stage_chunk;
4202 /* This should work even for the "On branch" line. */
4204 status_has_none(struct view *view, struct line *line)
4206 return line < view->line + view->lines && !line[1].data;
4209 /* Get fields from the diff line:
4210 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
4213 status_get_diff(struct status *file, const char *buf, size_t bufsize)
4215 const char *old_mode = buf + 1;
4216 const char *new_mode = buf + 8;
4217 const char *old_rev = buf + 15;
4218 const char *new_rev = buf + 56;
4219 const char *status = buf + 97;
4222 old_mode[-1] != ':' ||
4223 new_mode[-1] != ' ' ||
4224 old_rev[-1] != ' ' ||
4225 new_rev[-1] != ' ' ||
4229 file->status = *status;
4231 string_copy_rev(file->old.rev, old_rev);
4232 string_copy_rev(file->new.rev, new_rev);
4234 file->old.mode = strtoul(old_mode, NULL, 8);
4235 file->new.mode = strtoul(new_mode, NULL, 8);
4237 file->old.name[0] = file->new.name[0] = 0;
4243 status_run(struct view *view, const char *argv[], char status, enum line_type type)
4245 struct status *file = NULL;
4246 struct status *unmerged = NULL;
4247 char buf[SIZEOF_STR * 4];
4251 if (!run_io(&io, argv, NULL, IO_RD))
4254 add_line_data(view, NULL, type);
4256 while (!io_eof(&io)) {
4260 readsize = io_read(&io, buf + bufsize, sizeof(buf) - bufsize);
4263 bufsize += readsize;
4265 /* Process while we have NUL chars. */
4266 while ((sep = memchr(buf, 0, bufsize))) {
4267 size_t sepsize = sep - buf + 1;
4270 if (!realloc_lines(view, view->line_size + 1))
4273 file = calloc(1, sizeof(*file));
4277 add_line_data(view, file, type);
4280 /* Parse diff info part. */
4282 file->status = status;
4284 string_copy(file->old.rev, NULL_ID);
4286 } else if (!file->status) {
4287 if (!status_get_diff(file, buf, sepsize))
4291 memmove(buf, sep + 1, bufsize);
4293 sep = memchr(buf, 0, bufsize);
4296 sepsize = sep - buf + 1;
4298 /* Collapse all 'M'odified entries that
4299 * follow a associated 'U'nmerged entry.
4301 if (file->status == 'U') {
4304 } else if (unmerged) {
4305 int collapse = !strcmp(buf, unmerged->new.name);
4316 /* Grab the old name for rename/copy. */
4317 if (!*file->old.name &&
4318 (file->status == 'R' || file->status == 'C')) {
4319 sepsize = sep - buf + 1;
4320 string_ncopy(file->old.name, buf, sepsize);
4322 memmove(buf, sep + 1, bufsize);
4324 sep = memchr(buf, 0, bufsize);
4327 sepsize = sep - buf + 1;
4330 /* git-ls-files just delivers a NUL separated
4331 * list of file names similar to the second half
4332 * of the git-diff-* output. */
4333 string_ncopy(file->new.name, buf, sepsize);
4334 if (!*file->old.name)
4335 string_copy(file->old.name, file->new.name);
4337 memmove(buf, sep + 1, bufsize);
4342 if (io_error(&io)) {
4348 if (!view->line[view->lines - 1].data)
4349 add_line_data(view, NULL, LINE_STAT_NONE);
4355 /* Don't show unmerged entries in the staged section. */
4356 static const char *status_diff_index_argv[] = {
4357 "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
4358 "--cached", "-M", "HEAD", NULL
4361 static const char *status_diff_files_argv[] = {
4362 "git", "diff-files", "-z", NULL
4365 static const char *status_list_other_argv[] = {
4366 "git", "ls-files", "-z", "--others", "--exclude-standard", NULL
4369 static const char *status_list_no_head_argv[] = {
4370 "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
4373 static const char *update_index_argv[] = {
4374 "git", "update-index", "-q", "--unmerged", "--refresh", NULL
4377 /* First parse staged info using git-diff-index(1), then parse unstaged
4378 * info using git-diff-files(1), and finally untracked files using
4379 * git-ls-files(1). */
4381 status_open(struct view *view)
4383 unsigned long prev_lineno = view->lineno;
4387 if (!realloc_lines(view, view->line_size + 7))
4390 add_line_data(view, NULL, LINE_STAT_HEAD);
4391 if (is_initial_commit())
4392 string_copy(status_onbranch, "Initial commit");
4393 else if (!*opt_head)
4394 string_copy(status_onbranch, "Not currently on any branch");
4395 else if (!string_format(status_onbranch, "On branch %s", opt_head))
4398 run_io_bg(update_index_argv);
4400 if (is_initial_commit()) {
4401 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
4403 } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
4407 if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
4408 !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
4411 /* If all went well restore the previous line number to stay in
4412 * the context or select a line with something that can be
4414 if (prev_lineno >= view->lines)
4415 prev_lineno = view->lines - 1;
4416 while (prev_lineno < view->lines && !view->line[prev_lineno].data)
4418 while (prev_lineno > 0 && !view->line[prev_lineno].data)
4421 /* If the above fails, always skip the "On branch" line. */
4422 if (prev_lineno < view->lines)
4423 view->lineno = prev_lineno;
4427 if (view->lineno < view->offset)
4428 view->offset = view->lineno;
4429 else if (view->offset + view->height <= view->lineno)
4430 view->offset = view->lineno - view->height + 1;
4436 status_draw(struct view *view, struct line *line, unsigned int lineno)
4438 struct status *status = line->data;
4439 enum line_type type;
4443 switch (line->type) {
4444 case LINE_STAT_STAGED:
4445 type = LINE_STAT_SECTION;
4446 text = "Changes to be committed:";
4449 case LINE_STAT_UNSTAGED:
4450 type = LINE_STAT_SECTION;
4451 text = "Changed but not updated:";
4454 case LINE_STAT_UNTRACKED:
4455 type = LINE_STAT_SECTION;
4456 text = "Untracked files:";
4459 case LINE_STAT_NONE:
4460 type = LINE_DEFAULT;
4461 text = " (no files)";
4464 case LINE_STAT_HEAD:
4465 type = LINE_STAT_HEAD;
4466 text = status_onbranch;
4473 static char buf[] = { '?', ' ', ' ', ' ', 0 };
4475 buf[0] = status->status;
4476 if (draw_text(view, line->type, buf, TRUE))
4478 type = LINE_DEFAULT;
4479 text = status->new.name;
4482 draw_text(view, type, text, TRUE);
4487 status_enter(struct view *view, struct line *line)
4489 struct status *status = line->data;
4490 const char *oldpath = status ? status->old.name : NULL;
4491 /* Diffs for unmerged entries are empty when passing the new
4492 * path, so leave it empty. */
4493 const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
4495 enum open_flags split;
4496 struct view *stage = VIEW(REQ_VIEW_STAGE);
4498 if (line->type == LINE_STAT_NONE ||
4499 (!status && line[1].type == LINE_STAT_NONE)) {
4500 report("No file to diff");
4504 switch (line->type) {
4505 case LINE_STAT_STAGED:
4506 if (is_initial_commit()) {
4507 const char *no_head_diff_argv[] = {
4508 "git", "diff", "--no-color", "--patch-with-stat",
4509 "--", "/dev/null", newpath, NULL
4512 if (!prepare_update(stage, no_head_diff_argv, opt_cdup, FORMAT_DASH))
4515 const char *index_show_argv[] = {
4516 "git", "diff-index", "--root", "--patch-with-stat",
4517 "-C", "-M", "--cached", "HEAD", "--",
4518 oldpath, newpath, NULL
4521 if (!prepare_update(stage, index_show_argv, opt_cdup, FORMAT_DASH))
4526 info = "Staged changes to %s";
4528 info = "Staged changes";
4531 case LINE_STAT_UNSTAGED:
4533 const char *files_show_argv[] = {
4534 "git", "diff-files", "--root", "--patch-with-stat",
4535 "-C", "-M", "--", oldpath, newpath, NULL
4538 if (!prepare_update(stage, files_show_argv, opt_cdup, FORMAT_DASH))
4541 info = "Unstaged changes to %s";
4543 info = "Unstaged changes";
4546 case LINE_STAT_UNTRACKED:
4548 report("No file to show");
4552 if (!suffixcmp(status->new.name, -1, "/")) {
4553 report("Cannot display a directory");
4557 if (!prepare_update_file(stage, newpath))
4559 info = "Untracked file %s";
4562 case LINE_STAT_HEAD:
4566 die("line type %d not handled in switch", line->type);
4569 split = view_is_displayed(view) ? OPEN_SPLIT : 0;
4570 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH | split);
4571 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
4573 stage_status = *status;
4575 memset(&stage_status, 0, sizeof(stage_status));
4578 stage_line_type = line->type;
4580 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
4587 status_exists(struct status *status, enum line_type type)
4589 struct view *view = VIEW(REQ_VIEW_STATUS);
4592 for (line = view->line; line < view->line + view->lines; line++) {
4593 struct status *pos = line->data;
4595 if (line->type == type && pos &&
4596 !strcmp(status->new.name, pos->new.name))
4605 status_update_prepare(struct io *io, enum line_type type)
4607 const char *staged_argv[] = {
4608 "git", "update-index", "-z", "--index-info", NULL
4610 const char *others_argv[] = {
4611 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
4615 case LINE_STAT_STAGED:
4616 return run_io(io, staged_argv, opt_cdup, IO_WR);
4618 case LINE_STAT_UNSTAGED:
4619 return run_io(io, others_argv, opt_cdup, IO_WR);
4621 case LINE_STAT_UNTRACKED:
4622 return run_io(io, others_argv, NULL, IO_WR);
4625 die("line type %d not handled in switch", type);
4631 status_update_write(struct io *io, struct status *status, enum line_type type)
4633 char buf[SIZEOF_STR];
4637 case LINE_STAT_STAGED:
4638 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
4641 status->old.name, 0))
4645 case LINE_STAT_UNSTAGED:
4646 case LINE_STAT_UNTRACKED:
4647 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
4652 die("line type %d not handled in switch", type);
4655 return io_write(io, buf, bufsize);
4659 status_update_file(struct status *status, enum line_type type)
4664 if (!status_update_prepare(&io, type))
4667 result = status_update_write(&io, status, type);
4673 status_update_files(struct view *view, struct line *line)
4677 struct line *pos = view->line + view->lines;
4681 if (!status_update_prepare(&io, line->type))
4684 for (pos = line; pos < view->line + view->lines && pos->data; pos++)
4687 for (file = 0, done = 0; result && file < files; line++, file++) {
4688 int almost_done = file * 100 / files;
4690 if (almost_done > done) {
4692 string_format(view->ref, "updating file %u of %u (%d%% done)",
4694 update_view_title(view);
4696 result = status_update_write(&io, line->data, line->type);
4704 status_update(struct view *view)
4706 struct line *line = &view->line[view->lineno];
4708 assert(view->lines);
4711 /* This should work even for the "On branch" line. */
4712 if (line < view->line + view->lines && !line[1].data) {
4713 report("Nothing to update");
4717 if (!status_update_files(view, line + 1)) {
4718 report("Failed to update file status");
4722 } else if (!status_update_file(line->data, line->type)) {
4723 report("Failed to update file status");
4731 status_revert(struct status *status, enum line_type type, bool has_none)
4733 if (!status || type != LINE_STAT_UNSTAGED) {
4734 if (type == LINE_STAT_STAGED) {
4735 report("Cannot revert changes to staged files");
4736 } else if (type == LINE_STAT_UNTRACKED) {
4737 report("Cannot revert changes to untracked files");
4738 } else if (has_none) {
4739 report("Nothing to revert");
4741 report("Cannot revert changes to multiple files");
4746 const char *checkout_argv[] = {
4747 "git", "checkout", "--", status->old.name, NULL
4750 if (!prompt_yesno("Are you sure you want to overwrite any changes?"))
4752 return run_io_fg(checkout_argv, opt_cdup);
4757 status_request(struct view *view, enum request request, struct line *line)
4759 struct status *status = line->data;
4762 case REQ_STATUS_UPDATE:
4763 if (!status_update(view))
4767 case REQ_STATUS_REVERT:
4768 if (!status_revert(status, line->type, status_has_none(view, line)))
4772 case REQ_STATUS_MERGE:
4773 if (!status || status->status != 'U') {
4774 report("Merging only possible for files with unmerged status ('U').");
4777 open_mergetool(status->new.name);
4783 if (status->status == 'D') {
4784 report("File has been deleted.");
4788 open_editor(status->status != '?', status->new.name);
4791 case REQ_VIEW_BLAME:
4793 string_copy(opt_file, status->new.name);
4799 /* After returning the status view has been split to
4800 * show the stage view. No further reloading is
4802 status_enter(view, line);
4806 /* Simply reload the view. */
4813 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
4819 status_select(struct view *view, struct line *line)
4821 struct status *status = line->data;
4822 char file[SIZEOF_STR] = "all files";
4826 if (status && !string_format(file, "'%s'", status->new.name))
4829 if (!status && line[1].type == LINE_STAT_NONE)
4832 switch (line->type) {
4833 case LINE_STAT_STAGED:
4834 text = "Press %s to unstage %s for commit";
4837 case LINE_STAT_UNSTAGED:
4838 text = "Press %s to stage %s for commit";
4841 case LINE_STAT_UNTRACKED:
4842 text = "Press %s to stage %s for addition";
4845 case LINE_STAT_HEAD:
4846 case LINE_STAT_NONE:
4847 text = "Nothing to update";
4851 die("line type %d not handled in switch", line->type);
4854 if (status && status->status == 'U') {
4855 text = "Press %s to resolve conflict in %s";
4856 key = get_key(REQ_STATUS_MERGE);
4859 key = get_key(REQ_STATUS_UPDATE);
4862 string_format(view->ref, text, key, file);
4866 status_grep(struct view *view, struct line *line)
4868 struct status *status = line->data;
4869 enum { S_STATUS, S_NAME, S_END } state;
4876 for (state = S_STATUS; state < S_END; state++) {
4880 case S_NAME: text = status->new.name; break;
4882 buf[0] = status->status;
4890 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4897 static struct view_ops status_ops = {
4910 stage_diff_write(struct io *io, struct line *line, struct line *end)
4912 while (line < end) {
4913 if (!io_write(io, line->data, strlen(line->data)) ||
4914 !io_write(io, "\n", 1))
4917 if (line->type == LINE_DIFF_CHUNK ||
4918 line->type == LINE_DIFF_HEADER)
4925 static struct line *
4926 stage_diff_find(struct view *view, struct line *line, enum line_type type)
4928 for (; view->line < line; line--)
4929 if (line->type == type)
4936 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
4938 const char *apply_argv[SIZEOF_ARG] = {
4939 "git", "apply", "--whitespace=nowarn", NULL
4941 struct line *diff_hdr;
4945 diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
4950 apply_argv[argc++] = "--cached";
4951 if (revert || stage_line_type == LINE_STAT_STAGED)
4952 apply_argv[argc++] = "-R";
4953 apply_argv[argc++] = "-";
4954 apply_argv[argc++] = NULL;
4955 if (!run_io(&io, apply_argv, opt_cdup, IO_WR))
4958 if (!stage_diff_write(&io, diff_hdr, chunk) ||
4959 !stage_diff_write(&io, chunk, view->line + view->lines))
4963 run_io_bg(update_index_argv);
4965 return chunk ? TRUE : FALSE;
4969 stage_update(struct view *view, struct line *line)
4971 struct line *chunk = NULL;
4973 if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
4974 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
4977 if (!stage_apply_chunk(view, chunk, FALSE)) {
4978 report("Failed to apply chunk");
4982 } else if (!stage_status.status) {
4983 view = VIEW(REQ_VIEW_STATUS);
4985 for (line = view->line; line < view->line + view->lines; line++)
4986 if (line->type == stage_line_type)
4989 if (!status_update_files(view, line + 1)) {
4990 report("Failed to update files");
4994 } else if (!status_update_file(&stage_status, stage_line_type)) {
4995 report("Failed to update file");
5003 stage_revert(struct view *view, struct line *line)
5005 struct line *chunk = NULL;
5007 if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
5008 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5011 if (!prompt_yesno("Are you sure you want to revert changes?"))
5014 if (!stage_apply_chunk(view, chunk, TRUE)) {
5015 report("Failed to revert chunk");
5021 return status_revert(stage_status.status ? &stage_status : NULL,
5022 stage_line_type, FALSE);
5028 stage_next(struct view *view, struct line *line)
5032 if (!stage_chunks) {
5033 static size_t alloc = 0;
5036 for (line = view->line; line < view->line + view->lines; line++) {
5037 if (line->type != LINE_DIFF_CHUNK)
5040 tmp = realloc_items(stage_chunk, &alloc,
5041 stage_chunks, sizeof(*tmp));
5043 report("Allocation failure");
5048 stage_chunk[stage_chunks++] = line - view->line;
5052 for (i = 0; i < stage_chunks; i++) {
5053 if (stage_chunk[i] > view->lineno) {
5054 do_scroll_view(view, stage_chunk[i] - view->lineno);
5055 report("Chunk %d of %d", i + 1, stage_chunks);
5060 report("No next chunk found");
5064 stage_request(struct view *view, enum request request, struct line *line)
5067 case REQ_STATUS_UPDATE:
5068 if (!stage_update(view, line))
5072 case REQ_STATUS_REVERT:
5073 if (!stage_revert(view, line))
5077 case REQ_STAGE_NEXT:
5078 if (stage_line_type == LINE_STAT_UNTRACKED) {
5079 report("File is untracked; press %s to add",
5080 get_key(REQ_STATUS_UPDATE));
5083 stage_next(view, line);
5087 if (!stage_status.new.name[0])
5089 if (stage_status.status == 'D') {
5090 report("File has been deleted.");
5094 open_editor(stage_status.status != '?', stage_status.new.name);
5098 /* Reload everything ... */
5101 case REQ_VIEW_BLAME:
5102 if (stage_status.new.name[0]) {
5103 string_copy(opt_file, stage_status.new.name);
5109 return pager_request(view, request, line);
5115 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD | OPEN_NOMAXIMIZE);
5117 /* Check whether the staged entry still exists, and close the
5118 * stage view if it doesn't. */
5119 if (!status_exists(&stage_status, stage_line_type))
5120 return REQ_VIEW_CLOSE;
5122 if (stage_line_type == LINE_STAT_UNTRACKED) {
5123 if (!suffixcmp(stage_status.new.name, -1, "/")) {
5124 report("Cannot display a directory");
5128 if (!prepare_update_file(view, stage_status.new.name)) {
5129 report("Failed to open file: %s", strerror(errno));
5133 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
5138 static struct view_ops stage_ops = {
5155 char id[SIZEOF_REV]; /* SHA1 ID. */
5156 char title[128]; /* First line of the commit message. */
5157 char author[75]; /* Author of the commit. */
5158 struct tm time; /* Date from the author ident. */
5159 struct ref **refs; /* Repository references. */
5160 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
5161 size_t graph_size; /* The width of the graph array. */
5162 bool has_parents; /* Rewritten --parents seen. */
5165 /* Size of rev graph with no "padding" columns */
5166 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
5169 struct rev_graph *prev, *next, *parents;
5170 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
5172 struct commit *commit;
5174 unsigned int boundary:1;
5177 /* Parents of the commit being visualized. */
5178 static struct rev_graph graph_parents[4];
5180 /* The current stack of revisions on the graph. */
5181 static struct rev_graph graph_stacks[4] = {
5182 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
5183 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
5184 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
5185 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
5189 graph_parent_is_merge(struct rev_graph *graph)
5191 return graph->parents->size > 1;
5195 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
5197 struct commit *commit = graph->commit;
5199 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
5200 commit->graph[commit->graph_size++] = symbol;
5204 clear_rev_graph(struct rev_graph *graph)
5206 graph->boundary = 0;
5207 graph->size = graph->pos = 0;
5208 graph->commit = NULL;
5209 memset(graph->parents, 0, sizeof(*graph->parents));
5213 done_rev_graph(struct rev_graph *graph)
5215 if (graph_parent_is_merge(graph) &&
5216 graph->pos < graph->size - 1 &&
5217 graph->next->size == graph->size + graph->parents->size - 1) {
5218 size_t i = graph->pos + graph->parents->size - 1;
5220 graph->commit->graph_size = i * 2;
5221 while (i < graph->next->size - 1) {
5222 append_to_rev_graph(graph, ' ');
5223 append_to_rev_graph(graph, '\\');
5228 clear_rev_graph(graph);
5232 push_rev_graph(struct rev_graph *graph, const char *parent)
5236 /* "Collapse" duplicate parents lines.
5238 * FIXME: This needs to also update update the drawn graph but
5239 * for now it just serves as a method for pruning graph lines. */
5240 for (i = 0; i < graph->size; i++)
5241 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
5244 if (graph->size < SIZEOF_REVITEMS) {
5245 string_copy_rev(graph->rev[graph->size++], parent);
5250 get_rev_graph_symbol(struct rev_graph *graph)
5254 if (graph->boundary)
5255 symbol = REVGRAPH_BOUND;
5256 else if (graph->parents->size == 0)
5257 symbol = REVGRAPH_INIT;
5258 else if (graph_parent_is_merge(graph))
5259 symbol = REVGRAPH_MERGE;
5260 else if (graph->pos >= graph->size)
5261 symbol = REVGRAPH_BRANCH;
5263 symbol = REVGRAPH_COMMIT;
5269 draw_rev_graph(struct rev_graph *graph)
5272 chtype separator, line;
5274 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
5275 static struct rev_filler fillers[] = {
5281 chtype symbol = get_rev_graph_symbol(graph);
5282 struct rev_filler *filler;
5285 if (opt_line_graphics)
5286 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
5288 filler = &fillers[DEFAULT];
5290 for (i = 0; i < graph->pos; i++) {
5291 append_to_rev_graph(graph, filler->line);
5292 if (graph_parent_is_merge(graph->prev) &&
5293 graph->prev->pos == i)
5294 filler = &fillers[RSHARP];
5296 append_to_rev_graph(graph, filler->separator);
5299 /* Place the symbol for this revision. */
5300 append_to_rev_graph(graph, symbol);
5302 if (graph->prev->size > graph->size)
5303 filler = &fillers[RDIAG];
5305 filler = &fillers[DEFAULT];
5309 for (; i < graph->size; i++) {
5310 append_to_rev_graph(graph, filler->separator);
5311 append_to_rev_graph(graph, filler->line);
5312 if (graph_parent_is_merge(graph->prev) &&
5313 i < graph->prev->pos + graph->parents->size)
5314 filler = &fillers[RSHARP];
5315 if (graph->prev->size > graph->size)
5316 filler = &fillers[LDIAG];
5319 if (graph->prev->size > graph->size) {
5320 append_to_rev_graph(graph, filler->separator);
5321 if (filler->line != ' ')
5322 append_to_rev_graph(graph, filler->line);
5326 /* Prepare the next rev graph */
5328 prepare_rev_graph(struct rev_graph *graph)
5332 /* First, traverse all lines of revisions up to the active one. */
5333 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
5334 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
5337 push_rev_graph(graph->next, graph->rev[graph->pos]);
5340 /* Interleave the new revision parent(s). */
5341 for (i = 0; !graph->boundary && i < graph->parents->size; i++)
5342 push_rev_graph(graph->next, graph->parents->rev[i]);
5344 /* Lastly, put any remaining revisions. */
5345 for (i = graph->pos + 1; i < graph->size; i++)
5346 push_rev_graph(graph->next, graph->rev[i]);
5350 update_rev_graph(struct rev_graph *graph)
5352 /* If this is the finalizing update ... */
5354 prepare_rev_graph(graph);
5356 /* Graph visualization needs a one rev look-ahead,
5357 * so the first update doesn't visualize anything. */
5358 if (!graph->prev->commit)
5361 draw_rev_graph(graph->prev);
5362 done_rev_graph(graph->prev->prev);
5370 static const char *main_argv[SIZEOF_ARG] = {
5371 "git", "log", "--no-color", "--pretty=raw", "--parents",
5372 "--topo-order", "%(head)", NULL
5376 main_draw(struct view *view, struct line *line, unsigned int lineno)
5378 struct commit *commit = line->data;
5380 if (!*commit->author)
5383 if (opt_date && draw_date(view, &commit->time))
5387 draw_field(view, LINE_MAIN_AUTHOR, commit->author, opt_author_cols, TRUE))
5390 if (opt_rev_graph && commit->graph_size &&
5391 draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
5394 if (opt_show_refs && commit->refs) {
5398 enum line_type type;
5400 if (commit->refs[i]->head)
5401 type = LINE_MAIN_HEAD;
5402 else if (commit->refs[i]->ltag)
5403 type = LINE_MAIN_LOCAL_TAG;
5404 else if (commit->refs[i]->tag)
5405 type = LINE_MAIN_TAG;
5406 else if (commit->refs[i]->tracked)
5407 type = LINE_MAIN_TRACKED;
5408 else if (commit->refs[i]->remote)
5409 type = LINE_MAIN_REMOTE;
5411 type = LINE_MAIN_REF;
5413 if (draw_text(view, type, "[", TRUE) ||
5414 draw_text(view, type, commit->refs[i]->name, TRUE) ||
5415 draw_text(view, type, "]", TRUE))
5418 if (draw_text(view, LINE_DEFAULT, " ", TRUE))
5420 } while (commit->refs[i++]->next);
5423 draw_text(view, LINE_DEFAULT, commit->title, TRUE);
5427 /* Reads git log --pretty=raw output and parses it into the commit struct. */
5429 main_read(struct view *view, char *line)
5431 static struct rev_graph *graph = graph_stacks;
5432 enum line_type type;
5433 struct commit *commit;
5438 if (!view->lines && !view->parent)
5439 die("No revisions match the given arguments.");
5440 if (view->lines > 0) {
5441 commit = view->line[view->lines - 1].data;
5442 if (!*commit->author) {
5445 graph->commit = NULL;
5448 update_rev_graph(graph);
5450 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
5451 clear_rev_graph(&graph_stacks[i]);
5455 type = get_line_type(line);
5456 if (type == LINE_COMMIT) {
5457 commit = calloc(1, sizeof(struct commit));
5461 line += STRING_SIZE("commit ");
5463 graph->boundary = 1;
5467 string_copy_rev(commit->id, line);
5468 commit->refs = get_refs(commit->id);
5469 graph->commit = commit;
5470 add_line_data(view, commit, LINE_MAIN_COMMIT);
5472 while ((line = strchr(line, ' '))) {
5474 push_rev_graph(graph->parents, line);
5475 commit->has_parents = TRUE;
5482 commit = view->line[view->lines - 1].data;
5486 if (commit->has_parents)
5488 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
5493 /* Parse author lines where the name may be empty:
5494 * author <email@address.tld> 1138474660 +0100
5496 char *ident = line + STRING_SIZE("author ");
5497 char *nameend = strchr(ident, '<');
5498 char *emailend = strchr(ident, '>');
5500 if (!nameend || !emailend)
5503 update_rev_graph(graph);
5504 graph = graph->next;
5506 *nameend = *emailend = 0;
5507 ident = chomp_string(ident);
5509 ident = chomp_string(nameend + 1);
5514 string_ncopy(commit->author, ident, strlen(ident));
5516 /* Parse epoch and timezone */
5517 if (emailend[1] == ' ') {
5518 char *secs = emailend + 2;
5519 char *zone = strchr(secs, ' ');
5520 time_t time = (time_t) atol(secs);
5522 if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
5526 tz = ('0' - zone[1]) * 60 * 60 * 10;
5527 tz += ('0' - zone[2]) * 60 * 60;
5528 tz += ('0' - zone[3]) * 60;
5529 tz += ('0' - zone[4]) * 60;
5537 gmtime_r(&time, &commit->time);
5542 /* Fill in the commit title if it has not already been set. */
5543 if (commit->title[0])
5546 /* Require titles to start with a non-space character at the
5547 * offset used by git log. */
5548 if (strncmp(line, " ", 4))
5551 /* Well, if the title starts with a whitespace character,
5552 * try to be forgiving. Otherwise we end up with no title. */
5553 while (isspace(*line))
5557 /* FIXME: More graceful handling of titles; append "..." to
5558 * shortened titles, etc. */
5560 string_ncopy(commit->title, line, strlen(line));
5567 main_request(struct view *view, enum request request, struct line *line)
5569 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
5573 open_view(view, REQ_VIEW_DIFF, flags);
5577 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
5587 grep_refs(struct ref **refs, regex_t *regex)
5595 if (regexec(regex, refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
5597 } while (refs[i++]->next);
5603 main_grep(struct view *view, struct line *line)
5605 struct commit *commit = line->data;
5606 enum { S_TITLE, S_AUTHOR, S_DATE, S_REFS, S_END } state;
5607 char buf[DATE_COLS + 1];
5610 for (state = S_TITLE; state < S_END; state++) {
5614 case S_TITLE: text = commit->title; break;
5618 text = commit->author;
5623 if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
5630 if (grep_refs(commit->refs, view->regex) == TRUE)
5637 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
5645 main_select(struct view *view, struct line *line)
5647 struct commit *commit = line->data;
5649 string_copy_rev(view->ref, commit->id);
5650 string_copy_rev(ref_commit, view->ref);
5653 static struct view_ops main_ops = {
5666 * Unicode / UTF-8 handling
5668 * NOTE: Much of the following code for dealing with unicode is derived from
5669 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
5670 * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
5673 /* I've (over)annotated a lot of code snippets because I am not entirely
5674 * confident that the approach taken by this small UTF-8 interface is correct.
5678 unicode_width(unsigned long c)
5681 (c <= 0x115f /* Hangul Jamo */
5684 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
5686 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
5687 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
5688 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
5689 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
5690 || (c >= 0xffe0 && c <= 0xffe6)
5691 || (c >= 0x20000 && c <= 0x2fffd)
5692 || (c >= 0x30000 && c <= 0x3fffd)))
5696 return opt_tab_size;
5701 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
5702 * Illegal bytes are set one. */
5703 static const unsigned char utf8_bytes[256] = {
5704 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,
5705 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
5706 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
5707 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
5708 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
5709 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
5710 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,
5711 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,
5714 /* Decode UTF-8 multi-byte representation into a unicode character. */
5715 static inline unsigned long
5716 utf8_to_unicode(const char *string, size_t length)
5718 unsigned long unicode;
5722 unicode = string[0];
5725 unicode = (string[0] & 0x1f) << 6;
5726 unicode += (string[1] & 0x3f);
5729 unicode = (string[0] & 0x0f) << 12;
5730 unicode += ((string[1] & 0x3f) << 6);
5731 unicode += (string[2] & 0x3f);
5734 unicode = (string[0] & 0x0f) << 18;
5735 unicode += ((string[1] & 0x3f) << 12);
5736 unicode += ((string[2] & 0x3f) << 6);
5737 unicode += (string[3] & 0x3f);
5740 unicode = (string[0] & 0x0f) << 24;
5741 unicode += ((string[1] & 0x3f) << 18);
5742 unicode += ((string[2] & 0x3f) << 12);
5743 unicode += ((string[3] & 0x3f) << 6);
5744 unicode += (string[4] & 0x3f);
5747 unicode = (string[0] & 0x01) << 30;
5748 unicode += ((string[1] & 0x3f) << 24);
5749 unicode += ((string[2] & 0x3f) << 18);
5750 unicode += ((string[3] & 0x3f) << 12);
5751 unicode += ((string[4] & 0x3f) << 6);
5752 unicode += (string[5] & 0x3f);
5755 die("Invalid unicode length");
5758 /* Invalid characters could return the special 0xfffd value but NUL
5759 * should be just as good. */
5760 return unicode > 0xffff ? 0 : unicode;
5763 /* Calculates how much of string can be shown within the given maximum width
5764 * and sets trimmed parameter to non-zero value if all of string could not be
5765 * shown. If the reserve flag is TRUE, it will reserve at least one
5766 * trailing character, which can be useful when drawing a delimiter.
5768 * Returns the number of bytes to output from string to satisfy max_width. */
5770 utf8_length(const char *string, int *width, size_t max_width, int *trimmed, bool reserve)
5772 const char *start = string;
5773 const char *end = strchr(string, '\0');
5774 unsigned char last_bytes = 0;
5775 size_t last_ucwidth = 0;
5780 while (string < end) {
5781 int c = *(unsigned char *) string;
5782 unsigned char bytes = utf8_bytes[c];
5784 unsigned long unicode;
5786 if (string + bytes > end)
5789 /* Change representation to figure out whether
5790 * it is a single- or double-width character. */
5792 unicode = utf8_to_unicode(string, bytes);
5793 /* FIXME: Graceful handling of invalid unicode character. */
5797 ucwidth = unicode_width(unicode);
5799 if (*width > max_width) {
5802 if (reserve && *width == max_width) {
5803 string -= last_bytes;
5804 *width -= last_ucwidth;
5811 last_ucwidth = ucwidth;
5814 return string - start;
5822 /* Whether or not the curses interface has been initialized. */
5823 static bool cursed = FALSE;
5825 /* The status window is used for polling keystrokes. */
5826 static WINDOW *status_win;
5828 static bool status_empty = TRUE;
5830 /* Update status and title window. */
5832 report(const char *msg, ...)
5834 struct view *view = display[current_view];
5840 char buf[SIZEOF_STR];
5843 va_start(args, msg);
5844 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
5845 buf[sizeof(buf) - 1] = 0;
5846 buf[sizeof(buf) - 2] = '.';
5847 buf[sizeof(buf) - 3] = '.';
5848 buf[sizeof(buf) - 4] = '.';
5854 if (!status_empty || *msg) {
5857 va_start(args, msg);
5859 wmove(status_win, 0, 0);
5861 vwprintw(status_win, msg, args);
5862 status_empty = FALSE;
5864 status_empty = TRUE;
5866 wclrtoeol(status_win);
5867 wrefresh(status_win);
5872 update_view_title(view);
5873 update_display_cursor(view);
5876 /* Controls when nodelay should be in effect when polling user input. */
5878 set_nonblocking_input(bool loading)
5880 static unsigned int loading_views;
5882 if ((loading == FALSE && loading_views-- == 1) ||
5883 (loading == TRUE && loading_views++ == 0))
5884 nodelay(status_win, loading);
5892 /* Initialize the curses library */
5893 if (isatty(STDIN_FILENO)) {
5894 cursed = !!initscr();
5897 /* Leave stdin and stdout alone when acting as a pager. */
5898 opt_tty = fopen("/dev/tty", "r+");
5900 die("Failed to open /dev/tty");
5901 cursed = !!newterm(NULL, opt_tty, opt_tty);
5905 die("Failed to initialize curses");
5907 nonl(); /* Tell curses not to do NL->CR/NL on output */
5908 cbreak(); /* Take input chars one at a time, no wait for \n */
5909 noecho(); /* Don't echo input */
5910 leaveok(stdscr, TRUE);
5915 getmaxyx(stdscr, y, x);
5916 status_win = newwin(1, 0, y - 1, 0);
5918 die("Failed to create status window");
5920 /* Enable keyboard mapping */
5921 keypad(status_win, TRUE);
5922 wbkgdset(status_win, get_line_attr(LINE_STATUS));
5924 TABSIZE = opt_tab_size;
5925 if (opt_line_graphics) {
5926 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
5931 prompt_yesno(const char *prompt)
5933 enum { WAIT, STOP, CANCEL } status = WAIT;
5934 bool answer = FALSE;
5936 while (status == WAIT) {
5942 foreach_view (view, i)
5947 mvwprintw(status_win, 0, 0, "%s [Yy]/[Nn]", prompt);
5948 wclrtoeol(status_win);
5950 /* Refresh, accept single keystroke of input */
5951 key = wgetch(status_win);
5975 /* Clear the status window */
5976 status_empty = FALSE;
5983 read_prompt(const char *prompt)
5985 enum { READING, STOP, CANCEL } status = READING;
5986 static char buf[SIZEOF_STR];
5989 while (status == READING) {
5995 foreach_view (view, i)
6000 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
6001 wclrtoeol(status_win);
6003 /* Refresh, accept single keystroke of input */
6004 key = wgetch(status_win);
6009 status = pos ? STOP : CANCEL;
6027 if (pos >= sizeof(buf)) {
6028 report("Input string too long");
6033 buf[pos++] = (char) key;
6037 /* Clear the status window */
6038 status_empty = FALSE;
6041 if (status == CANCEL)
6050 * Repository properties
6054 git_properties(const char **argv, const char *separators,
6055 int (*read_property)(char *, size_t, char *, size_t))
6059 if (init_io_rd(&io, argv, NULL, FORMAT_NONE))
6060 return read_properties(&io, separators, read_property);
6064 static struct ref *refs = NULL;
6065 static size_t refs_alloc = 0;
6066 static size_t refs_size = 0;
6068 /* Id <-> ref store */
6069 static struct ref ***id_refs = NULL;
6070 static size_t id_refs_alloc = 0;
6071 static size_t id_refs_size = 0;
6074 compare_refs(const void *ref1_, const void *ref2_)
6076 const struct ref *ref1 = *(const struct ref **)ref1_;
6077 const struct ref *ref2 = *(const struct ref **)ref2_;
6079 if (ref1->tag != ref2->tag)
6080 return ref2->tag - ref1->tag;
6081 if (ref1->ltag != ref2->ltag)
6082 return ref2->ltag - ref2->ltag;
6083 if (ref1->head != ref2->head)
6084 return ref2->head - ref1->head;
6085 if (ref1->tracked != ref2->tracked)
6086 return ref2->tracked - ref1->tracked;
6087 if (ref1->remote != ref2->remote)
6088 return ref2->remote - ref1->remote;
6089 return strcmp(ref1->name, ref2->name);
6092 static struct ref **
6093 get_refs(const char *id)
6095 struct ref ***tmp_id_refs;
6096 struct ref **ref_list = NULL;
6097 size_t ref_list_alloc = 0;
6098 size_t ref_list_size = 0;
6101 for (i = 0; i < id_refs_size; i++)
6102 if (!strcmp(id, id_refs[i][0]->id))
6105 tmp_id_refs = realloc_items(id_refs, &id_refs_alloc, id_refs_size + 1,
6110 id_refs = tmp_id_refs;
6112 for (i = 0; i < refs_size; i++) {
6115 if (strcmp(id, refs[i].id))
6118 tmp = realloc_items(ref_list, &ref_list_alloc,
6119 ref_list_size + 1, sizeof(*ref_list));
6127 ref_list[ref_list_size] = &refs[i];
6128 /* XXX: The properties of the commit chains ensures that we can
6129 * safely modify the shared ref. The repo references will
6130 * always be similar for the same id. */
6131 ref_list[ref_list_size]->next = 1;
6137 qsort(ref_list, ref_list_size, sizeof(*ref_list), compare_refs);
6138 ref_list[ref_list_size - 1]->next = 0;
6139 id_refs[id_refs_size++] = ref_list;
6146 read_ref(char *id, size_t idlen, char *name, size_t namelen)
6151 bool remote = FALSE;
6152 bool tracked = FALSE;
6153 bool check_replace = FALSE;
6156 if (!prefixcmp(name, "refs/tags/")) {
6157 if (!suffixcmp(name, namelen, "^{}")) {
6160 if (refs_size > 0 && refs[refs_size - 1].ltag == TRUE)
6161 check_replace = TRUE;
6167 namelen -= STRING_SIZE("refs/tags/");
6168 name += STRING_SIZE("refs/tags/");
6170 } else if (!prefixcmp(name, "refs/remotes/")) {
6172 namelen -= STRING_SIZE("refs/remotes/");
6173 name += STRING_SIZE("refs/remotes/");
6174 tracked = !strcmp(opt_remote, name);
6176 } else if (!prefixcmp(name, "refs/heads/")) {
6177 namelen -= STRING_SIZE("refs/heads/");
6178 name += STRING_SIZE("refs/heads/");
6179 head = !strncmp(opt_head, name, namelen);
6181 } else if (!strcmp(name, "HEAD")) {
6182 string_ncopy(opt_head_rev, id, idlen);
6186 if (check_replace && !strcmp(name, refs[refs_size - 1].name)) {
6187 /* it's an annotated tag, replace the previous sha1 with the
6188 * resolved commit id; relies on the fact git-ls-remote lists
6189 * the commit id of an annotated tag right before the commit id
6191 refs[refs_size - 1].ltag = ltag;
6192 string_copy_rev(refs[refs_size - 1].id, id);
6196 refs = realloc_items(refs, &refs_alloc, refs_size + 1, sizeof(*refs));
6200 ref = &refs[refs_size++];
6201 ref->name = malloc(namelen + 1);
6205 strncpy(ref->name, name, namelen);
6206 ref->name[namelen] = 0;
6210 ref->remote = remote;
6211 ref->tracked = tracked;
6212 string_copy_rev(ref->id, id);
6220 static const char *ls_remote_argv[SIZEOF_ARG] = {
6221 "git", "ls-remote", ".", NULL
6223 static bool init = FALSE;
6226 argv_from_env(ls_remote_argv, "TIG_LS_REMOTE");
6233 while (refs_size > 0)
6234 free(refs[--refs_size].name);
6235 while (id_refs_size > 0)
6236 free(id_refs[--id_refs_size]);
6238 return git_properties(ls_remote_argv, "\t", read_ref);
6242 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
6244 if (!strcmp(name, "i18n.commitencoding"))
6245 string_ncopy(opt_encoding, value, valuelen);
6247 if (!strcmp(name, "core.editor"))
6248 string_ncopy(opt_editor, value, valuelen);
6250 /* branch.<head>.remote */
6252 !strncmp(name, "branch.", 7) &&
6253 !strncmp(name + 7, opt_head, strlen(opt_head)) &&
6254 !strcmp(name + 7 + strlen(opt_head), ".remote"))
6255 string_ncopy(opt_remote, value, valuelen);
6257 if (*opt_head && *opt_remote &&
6258 !strncmp(name, "branch.", 7) &&
6259 !strncmp(name + 7, opt_head, strlen(opt_head)) &&
6260 !strcmp(name + 7 + strlen(opt_head), ".merge")) {
6261 size_t from = strlen(opt_remote);
6263 if (!prefixcmp(value, "refs/heads/")) {
6264 value += STRING_SIZE("refs/heads/");
6265 valuelen -= STRING_SIZE("refs/heads/");
6268 if (!string_format_from(opt_remote, &from, "/%s", value))
6276 load_git_config(void)
6278 const char *config_list_argv[] = { "git", GIT_CONFIG, "--list", NULL };
6280 return git_properties(config_list_argv, "=", read_repo_config_option);
6284 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
6286 if (!opt_git_dir[0]) {
6287 string_ncopy(opt_git_dir, name, namelen);
6289 } else if (opt_is_inside_work_tree == -1) {
6290 /* This can be 3 different values depending on the
6291 * version of git being used. If git-rev-parse does not
6292 * understand --is-inside-work-tree it will simply echo
6293 * the option else either "true" or "false" is printed.
6294 * Default to true for the unknown case. */
6295 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
6297 string_ncopy(opt_cdup, name, namelen);
6304 load_repo_info(void)
6306 const char *head_argv[] = {
6307 "git", "symbolic-ref", "HEAD", NULL
6309 const char *rev_parse_argv[] = {
6310 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
6314 if (run_io_buf(head_argv, opt_head, sizeof(opt_head))) {
6315 chomp_string(opt_head);
6316 if (!prefixcmp(opt_head, "refs/heads/")) {
6317 char *offset = opt_head + STRING_SIZE("refs/heads/");
6319 memmove(opt_head, offset, strlen(offset) + 1);
6323 return git_properties(rev_parse_argv, "=", read_repo_info);
6327 read_properties(struct io *io, const char *separators,
6328 int (*read_property)(char *, size_t, char *, size_t))
6336 while (state == OK && (name = io_gets(io))) {
6341 name = chomp_string(name);
6342 namelen = strcspn(name, separators);
6344 if (name[namelen]) {
6346 value = chomp_string(name + namelen + 1);
6347 valuelen = strlen(value);
6354 state = read_property(name, namelen, value, valuelen);
6357 if (state != ERR && io_error(io))
6369 static void __NORETURN
6372 /* XXX: Restore tty modes and let the OS cleanup the rest! */
6378 static void __NORETURN
6379 die(const char *err, ...)
6385 va_start(args, err);
6386 fputs("tig: ", stderr);
6387 vfprintf(stderr, err, args);
6388 fputs("\n", stderr);
6395 warn(const char *msg, ...)
6399 va_start(args, msg);
6400 fputs("tig warning: ", stderr);
6401 vfprintf(stderr, msg, args);
6402 fputs("\n", stderr);
6407 main(int argc, const char *argv[])
6409 const char **run_argv = NULL;
6411 enum request request;
6414 signal(SIGINT, quit);
6416 if (setlocale(LC_ALL, "")) {
6417 char *codeset = nl_langinfo(CODESET);
6419 string_ncopy(opt_codeset, codeset, strlen(codeset));
6422 if (load_repo_info() == ERR)
6423 die("Failed to load repo info.");
6425 if (load_options() == ERR)
6426 die("Failed to load user config.");
6428 if (load_git_config() == ERR)
6429 die("Failed to load repo config.");
6431 request = parse_options(argc, argv, &run_argv);
6432 if (request == REQ_NONE)
6435 /* Require a git repository unless when running in pager mode. */
6436 if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
6437 die("Not a git repository");
6439 if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
6442 if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
6443 opt_iconv = iconv_open(opt_codeset, opt_encoding);
6444 if (opt_iconv == ICONV_NONE)
6445 die("Failed to initialize character set conversion");
6448 if (load_refs() == ERR)
6449 die("Failed to load refs.");
6451 foreach_view (view, i)
6452 argv_from_env(view->ops->argv, view->cmd_env);
6456 if (request == REQ_VIEW_PAGER || run_argv) {
6457 if (request == REQ_VIEW_PAGER)
6458 io_open(&VIEW(request)->io, "");
6459 else if (!prepare_update(VIEW(request), run_argv, NULL, FORMAT_NONE))
6460 die("Failed to format arguments");
6461 open_view(NULL, request, OPEN_PREPARED);
6465 while (view_driver(display[current_view], request)) {
6469 foreach_view (view, i)
6471 view = display[current_view];
6473 /* Refresh, accept single keystroke of input */
6474 key = wgetch(status_win);
6476 /* wgetch() with nodelay() enabled returns ERR when there's no
6483 request = get_keybinding(view->keymap, key);
6485 /* Some low-level request handling. This keeps access to
6486 * status_win restricted. */
6490 char *cmd = read_prompt(":");
6493 struct view *next = VIEW(REQ_VIEW_PAGER);
6494 const char *argv[SIZEOF_ARG] = { "git" };
6497 /* When running random commands, initially show the
6498 * command in the title. However, it maybe later be
6499 * overwritten if a commit line is selected. */
6500 string_ncopy(next->ref, cmd, strlen(cmd));
6502 if (!argv_from_string(argv, &argc, cmd)) {
6503 report("Too many arguments");
6504 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
6505 report("Failed to format command");
6507 open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
6515 case REQ_SEARCH_BACK:
6517 const char *prompt = request == REQ_SEARCH ? "/" : "?";
6518 char *search = read_prompt(prompt);
6521 string_ncopy(opt_search, search, strlen(search));
6526 case REQ_SCREEN_RESIZE:
6530 getmaxyx(stdscr, height, width);
6532 /* Resize the status view and let the view driver take
6533 * care of resizing the displayed views. */
6534 wresize(status_win, 1, width);
6535 mvwin(status_win, height - 1, 0);
6536 wrefresh(status_win);