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>
45 /* ncurses(3): Must be defined to have extended wide-character functions. */
46 #define _XOPEN_SOURCE_EXTENDED
48 #ifdef HAVE_NCURSESW_NCURSES_H
49 #include <ncursesw/ncurses.h>
51 #ifdef HAVE_NCURSES_NCURSES_H
52 #include <ncurses/ncurses.h>
59 #define __NORETURN __attribute__((__noreturn__))
64 static void __NORETURN die(const char *err, ...);
65 static void warn(const char *msg, ...);
66 static void report(const char *msg, ...);
67 static void set_nonblocking_input(bool loading);
68 static size_t utf8_length(const char *string, int *width, size_t max_width, int *trimmed, bool reserve);
69 static bool prompt_yesno(const char *prompt);
70 static int load_refs(void);
72 #define ABS(x) ((x) >= 0 ? (x) : -(x))
73 #define MIN(x, y) ((x) < (y) ? (x) : (y))
75 #define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
76 #define STRING_SIZE(x) (sizeof(x) - 1)
78 #define SIZEOF_STR 1024 /* Default string size. */
79 #define SIZEOF_REF 256 /* Size of symbolic or SHA1 ID. */
80 #define SIZEOF_REV 41 /* Holds a SHA-1 and an ending NUL. */
81 #define SIZEOF_ARG 32 /* Default argument array size. */
85 #define REVGRAPH_INIT 'I'
86 #define REVGRAPH_MERGE 'M'
87 #define REVGRAPH_BRANCH '+'
88 #define REVGRAPH_COMMIT '*'
89 #define REVGRAPH_BOUND '^'
91 #define SIZEOF_REVGRAPH 19 /* Size of revision ancestry graphics. */
93 /* This color name can be used to refer to the default term colors. */
94 #define COLOR_DEFAULT (-1)
96 #define ICONV_NONE ((iconv_t) -1)
98 #define ICONV_CONST /* nothing */
101 /* The format and size of the date column in the main view. */
102 #define DATE_FORMAT "%Y-%m-%d %H:%M"
103 #define DATE_COLS STRING_SIZE("2006-04-29 14:21 ")
105 #define AUTHOR_COLS 20
108 /* The default interval between line numbers. */
109 #define NUMBER_INTERVAL 5
113 #define SCALE_SPLIT_VIEW(height) ((height) * 2 / 3)
115 #define NULL_ID "0000000000000000000000000000000000000000"
118 #define GIT_CONFIG "config"
121 /* Some ascii-shorthands fitted into the ncurses namespace. */
123 #define KEY_RETURN '\r'
128 char *name; /* Ref name; tag or head names are shortened. */
129 char id[SIZEOF_REV]; /* Commit SHA1 ID */
130 unsigned int head:1; /* Is it the current HEAD? */
131 unsigned int tag:1; /* Is it a tag? */
132 unsigned int ltag:1; /* If so, is the tag local? */
133 unsigned int remote:1; /* Is it a remote ref? */
134 unsigned int tracked:1; /* Is it the remote for the current HEAD? */
135 unsigned int next:1; /* For ref lists: are there more refs? */
138 static struct ref **get_refs(const char *id);
141 FORMAT_ALL, /* Perform replacement in all arguments. */
142 FORMAT_DASH, /* Perform replacement up until "--". */
143 FORMAT_NONE /* No replacement should be performed. */
146 static bool format_command(char dst[], const char *src[], enum format_flags flags);
147 static bool format_argv(const char *dst[], const char *src[], enum format_flags flags);
156 set_from_int_map(struct int_map *map, size_t map_size,
157 int *value, const char *name, int namelen)
162 for (i = 0; i < map_size; i++)
163 if (namelen == map[i].namelen &&
164 !strncasecmp(name, map[i].name, namelen)) {
165 *value = map[i].value;
178 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
180 if (srclen > dstlen - 1)
183 strncpy(dst, src, srclen);
187 /* Shorthands for safely copying into a fixed buffer. */
189 #define string_copy(dst, src) \
190 string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
192 #define string_ncopy(dst, src, srclen) \
193 string_ncopy_do(dst, sizeof(dst), src, srclen)
195 #define string_copy_rev(dst, src) \
196 string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
198 #define string_add(dst, from, src) \
199 string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
202 chomp_string(char *name)
206 while (isspace(*name))
209 namelen = strlen(name) - 1;
210 while (namelen > 0 && isspace(name[namelen]))
217 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
220 size_t pos = bufpos ? *bufpos : 0;
223 pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
229 return pos >= bufsize ? FALSE : TRUE;
232 #define string_format(buf, fmt, args...) \
233 string_nformat(buf, sizeof(buf), NULL, fmt, args)
235 #define string_format_from(buf, from, fmt, args...) \
236 string_nformat(buf, sizeof(buf), from, fmt, args)
239 string_enum_compare(const char *str1, const char *str2, int len)
243 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
245 /* Diff-Header == DIFF_HEADER */
246 for (i = 0; i < len; i++) {
247 if (toupper(str1[i]) == toupper(str2[i]))
250 if (string_enum_sep(str1[i]) &&
251 string_enum_sep(str2[i]))
254 return str1[i] - str2[i];
260 #define prefixcmp(str1, str2) \
261 strncmp(str1, str2, STRING_SIZE(str2))
264 suffixcmp(const char *str, int slen, const char *suffix)
266 size_t len = slen >= 0 ? slen : strlen(str);
267 size_t suffixlen = strlen(suffix);
269 return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
274 * NOTE: The following is a slightly modified copy of the git project's shell
275 * quoting routines found in the quote.c file.
277 * Help to copy the thing properly quoted for the shell safety. any single
278 * quote is replaced with '\'', any exclamation point is replaced with '\!',
279 * and the whole thing is enclosed in a
282 * original sq_quote result
283 * name ==> name ==> 'name'
284 * a b ==> a b ==> 'a b'
285 * a'b ==> a'\''b ==> 'a'\''b'
286 * a!b ==> a'\!'b ==> 'a'\!'b'
290 sq_quote(char buf[SIZEOF_STR], size_t bufsize, const char *src)
294 #define BUFPUT(x) do { if (bufsize < SIZEOF_STR) buf[bufsize++] = (x); } while (0)
297 while ((c = *src++)) {
298 if (c == '\'' || c == '!') {
309 if (bufsize < SIZEOF_STR)
316 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
320 while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
321 bool advance = cmd[valuelen] != 0;
324 argv[(*argc)++] = chomp_string(cmd);
325 cmd += valuelen + advance;
328 if (*argc < SIZEOF_ARG)
330 return *argc < SIZEOF_ARG;
334 argv_from_env(const char **argv, const char *name)
336 char *env = argv ? getenv(name) : NULL;
341 if (env && !argv_from_string(argv, &argc, env))
342 die("Too many arguments in the `%s` environment variable", name);
347 * Executing external commands.
351 IO_FD, /* File descriptor based IO. */
352 IO_BG, /* Execute command in the background. */
353 IO_FG, /* Execute command with same std{in,out,err}. */
354 IO_RD, /* Read only fork+exec IO. */
355 IO_WR, /* Write only fork+exec IO. */
359 enum io_type type; /* The requested type of pipe. */
360 const char *dir; /* Directory from which to execute. */
361 FILE *pipe; /* Pipe for reading or writing. */
362 int error; /* Error status. */
363 char sh[SIZEOF_STR]; /* Shell command buffer. */
364 char *buf; /* Read/write buffer. */
365 size_t bufalloc; /* Allocated buffer size. */
369 reset_io(struct io *io)
378 init_io(struct io *io, const char *dir, enum io_type type)
386 init_io_rd(struct io *io, const char *argv[], const char *dir,
387 enum format_flags flags)
389 init_io(io, dir, IO_RD);
390 return format_command(io->sh, argv, flags);
394 init_io_fd(struct io *io, FILE *pipe)
396 init_io(io, NULL, IO_FD);
398 return io->pipe != NULL;
402 done_io(struct io *io)
405 if (io->type == IO_FD)
407 else if (io->type == IO_RD || io->type == IO_WR)
414 start_io(struct io *io)
416 char buf[SIZEOF_STR * 2];
419 if (io->type == IO_FD)
422 if (io->dir && *io->dir &&
423 !string_format_from(buf, &bufpos, "cd %s;", io->dir))
426 if (!string_format_from(buf, &bufpos, "%s", io->sh))
429 if (io->type == IO_FG || io->type == IO_BG)
430 return system(buf) == 0;
432 io->pipe = popen(io->sh, io->type == IO_RD ? "r" : "w");
433 return io->pipe != NULL;
437 run_io(struct io *io, const char **argv, const char *dir, enum io_type type)
439 init_io(io, dir, type);
440 if (!format_command(io->sh, argv, FORMAT_NONE))
446 run_io_do(struct io *io)
448 return start_io(io) && done_io(io);
452 run_io_bg(const char **argv)
456 init_io(&io, NULL, IO_BG);
457 if (!format_command(io.sh, argv, FORMAT_NONE))
459 return run_io_do(&io);
463 run_io_fg(const char **argv, const char *dir)
467 init_io(&io, dir, IO_FG);
468 if (!format_command(io.sh, argv, FORMAT_NONE))
470 return run_io_do(&io);
474 run_io_rd(struct io *io, const char **argv, enum format_flags flags)
476 return init_io_rd(io, argv, NULL, flags) && start_io(io);
480 io_eof(struct io *io)
482 return feof(io->pipe);
486 io_error(struct io *io)
492 io_strerror(struct io *io)
494 return strerror(io->error);
498 io_read(struct io *io, void *buf, size_t bufsize)
500 size_t readsize = fread(buf, 1, bufsize, io->pipe);
502 if (ferror(io->pipe))
509 io_gets(struct io *io)
512 io->buf = malloc(BUFSIZ);
515 io->bufalloc = BUFSIZ;
518 if (!fgets(io->buf, io->bufalloc, io->pipe)) {
519 if (ferror(io->pipe))
528 io_write(struct io *io, const void *buf, size_t bufsize)
532 while (!io_error(io) && written < bufsize) {
533 written += fwrite(buf + written, 1, bufsize - written, io->pipe);
534 if (ferror(io->pipe))
538 return written == bufsize;
542 run_io_buf(const char **argv, char buf[], size_t bufsize)
547 if (!run_io_rd(&io, argv, FORMAT_NONE))
551 io.bufalloc = bufsize;
552 error = !io_gets(&io) && io_error(&io);
555 return done_io(&io) || error;
558 static int read_properties(struct io *io, const char *separators, int (*read)(char *, size_t, char *, size_t));
565 /* XXX: Keep the view request first and in sync with views[]. */ \
566 REQ_GROUP("View switching") \
567 REQ_(VIEW_MAIN, "Show main view"), \
568 REQ_(VIEW_DIFF, "Show diff view"), \
569 REQ_(VIEW_LOG, "Show log view"), \
570 REQ_(VIEW_TREE, "Show tree view"), \
571 REQ_(VIEW_BLOB, "Show blob view"), \
572 REQ_(VIEW_BLAME, "Show blame view"), \
573 REQ_(VIEW_HELP, "Show help page"), \
574 REQ_(VIEW_PAGER, "Show pager view"), \
575 REQ_(VIEW_STATUS, "Show status view"), \
576 REQ_(VIEW_STAGE, "Show stage view"), \
578 REQ_GROUP("View manipulation") \
579 REQ_(ENTER, "Enter current line and scroll"), \
580 REQ_(NEXT, "Move to next"), \
581 REQ_(PREVIOUS, "Move to previous"), \
582 REQ_(VIEW_NEXT, "Move focus to next view"), \
583 REQ_(REFRESH, "Reload and refresh"), \
584 REQ_(MAXIMIZE, "Maximize the current view"), \
585 REQ_(VIEW_CLOSE, "Close the current view"), \
586 REQ_(QUIT, "Close all views and quit"), \
588 REQ_GROUP("View specific requests") \
589 REQ_(STATUS_UPDATE, "Update file status"), \
590 REQ_(STATUS_REVERT, "Revert file changes"), \
591 REQ_(STATUS_MERGE, "Merge file using external tool"), \
592 REQ_(STAGE_NEXT, "Find next chunk to stage"), \
593 REQ_(TREE_PARENT, "Switch to parent directory in tree view"), \
595 REQ_GROUP("Cursor navigation") \
596 REQ_(MOVE_UP, "Move cursor one line up"), \
597 REQ_(MOVE_DOWN, "Move cursor one line down"), \
598 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
599 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
600 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
601 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
603 REQ_GROUP("Scrolling") \
604 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
605 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
606 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
607 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
609 REQ_GROUP("Searching") \
610 REQ_(SEARCH, "Search the view"), \
611 REQ_(SEARCH_BACK, "Search backwards in the view"), \
612 REQ_(FIND_NEXT, "Find next search match"), \
613 REQ_(FIND_PREV, "Find previous search match"), \
615 REQ_GROUP("Option manipulation") \
616 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
617 REQ_(TOGGLE_DATE, "Toggle date display"), \
618 REQ_(TOGGLE_AUTHOR, "Toggle author display"), \
619 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
620 REQ_(TOGGLE_REFS, "Toggle reference display (tags/branches)"), \
623 REQ_(PROMPT, "Bring up the prompt"), \
624 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
625 REQ_(SCREEN_RESIZE, "Resize the screen"), \
626 REQ_(SHOW_VERSION, "Show version information"), \
627 REQ_(STOP_LOADING, "Stop all loading views"), \
628 REQ_(EDIT, "Open in editor"), \
629 REQ_(NONE, "Do nothing")
632 /* User action requests. */
634 #define REQ_GROUP(help)
635 #define REQ_(req, help) REQ_##req
637 /* Offset all requests to avoid conflicts with ncurses getch values. */
638 REQ_OFFSET = KEY_MAX + 1,
645 struct request_info {
646 enum request request;
652 static struct request_info req_info[] = {
653 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
654 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
661 get_request(const char *name)
663 int namelen = strlen(name);
666 for (i = 0; i < ARRAY_SIZE(req_info); i++)
667 if (req_info[i].namelen == namelen &&
668 !string_enum_compare(req_info[i].name, name, namelen))
669 return req_info[i].request;
679 static const char usage[] =
680 "tig " TIG_VERSION " (" __DATE__ ")\n"
682 "Usage: tig [options] [revs] [--] [paths]\n"
683 " or: tig show [options] [revs] [--] [paths]\n"
684 " or: tig blame [rev] path\n"
686 " or: tig < [git command output]\n"
689 " -v, --version Show version and exit\n"
690 " -h, --help Show help message and exit";
692 /* Option and state variables. */
693 static bool opt_date = TRUE;
694 static bool opt_author = TRUE;
695 static bool opt_line_number = FALSE;
696 static bool opt_line_graphics = TRUE;
697 static bool opt_rev_graph = FALSE;
698 static bool opt_show_refs = TRUE;
699 static int opt_num_interval = NUMBER_INTERVAL;
700 static int opt_tab_size = TAB_SIZE;
701 static int opt_author_cols = AUTHOR_COLS-1;
702 static char opt_path[SIZEOF_STR] = "";
703 static char opt_file[SIZEOF_STR] = "";
704 static char opt_ref[SIZEOF_REF] = "";
705 static char opt_head[SIZEOF_REF] = "";
706 static char opt_head_rev[SIZEOF_REV] = "";
707 static char opt_remote[SIZEOF_REF] = "";
708 static char opt_encoding[20] = "UTF-8";
709 static bool opt_utf8 = TRUE;
710 static char opt_codeset[20] = "UTF-8";
711 static iconv_t opt_iconv = ICONV_NONE;
712 static char opt_search[SIZEOF_STR] = "";
713 static char opt_cdup[SIZEOF_STR] = "";
714 static char opt_git_dir[SIZEOF_STR] = "";
715 static signed char opt_is_inside_work_tree = -1; /* set to TRUE or FALSE */
716 static char opt_editor[SIZEOF_STR] = "";
717 static FILE *opt_tty = NULL;
719 #define is_initial_commit() (!*opt_head_rev)
720 #define is_head_commit(rev) (!strcmp((rev), "HEAD") || !strcmp(opt_head_rev, (rev)))
723 parse_options(int argc, const char *argv[], const char ***run_argv)
725 enum request request = REQ_VIEW_MAIN;
726 const char *subcommand;
727 bool seen_dashdash = FALSE;
728 /* XXX: This is vulnerable to the user overriding options
729 * required for the main view parser. */
730 const char *custom_argv[SIZEOF_ARG] = {
731 "git", "log", "--no-color", "--pretty=raw", "--parents",
736 if (!isatty(STDIN_FILENO))
737 return REQ_VIEW_PAGER;
740 return REQ_VIEW_MAIN;
742 subcommand = argv[1];
743 if (!strcmp(subcommand, "status") || !strcmp(subcommand, "-S")) {
744 if (!strcmp(subcommand, "-S"))
745 warn("`-S' has been deprecated; use `tig status' instead");
747 warn("ignoring arguments after `%s'", subcommand);
748 return REQ_VIEW_STATUS;
750 } else if (!strcmp(subcommand, "blame")) {
751 if (argc <= 2 || argc > 4)
752 die("invalid number of options to blame\n\n%s", usage);
756 string_ncopy(opt_ref, argv[i], strlen(argv[i]));
760 string_ncopy(opt_file, argv[i], strlen(argv[i]));
761 return REQ_VIEW_BLAME;
763 } else if (!strcmp(subcommand, "show")) {
764 request = REQ_VIEW_DIFF;
766 } else if (!strcmp(subcommand, "log") || !strcmp(subcommand, "diff")) {
767 request = subcommand[0] == 'l' ? REQ_VIEW_LOG : REQ_VIEW_DIFF;
768 warn("`tig %s' has been deprecated", subcommand);
775 custom_argv[1] = subcommand;
779 for (i = 1 + !!subcommand; i < argc; i++) {
780 const char *opt = argv[i];
782 if (seen_dashdash || !strcmp(opt, "--")) {
783 seen_dashdash = TRUE;
785 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
786 printf("tig version %s\n", TIG_VERSION);
789 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
790 printf("%s\n", usage);
794 custom_argv[j++] = opt;
795 if (j >= ARRAY_SIZE(custom_argv))
796 die("command too long");
799 custom_argv[j] = NULL;
800 *run_argv = custom_argv;
807 * Line-oriented content detection.
811 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
812 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
813 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
814 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
815 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
816 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
817 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
818 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
819 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
820 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
821 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
822 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
823 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
824 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
825 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
826 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
827 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
828 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
829 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
830 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
831 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
832 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
833 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
834 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
835 LINE(AUTHOR, "author ", COLOR_CYAN, COLOR_DEFAULT, 0), \
836 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
837 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
838 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
839 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
840 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
841 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
842 LINE(DELIMITER, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
843 LINE(DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
844 LINE(LINE_NUMBER, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
845 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
846 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
847 LINE(MAIN_AUTHOR, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
848 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
849 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
850 LINE(MAIN_LOCAL_TAG,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
851 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
852 LINE(MAIN_TRACKED, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
853 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
854 LINE(MAIN_HEAD, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
855 LINE(MAIN_REVGRAPH,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
856 LINE(TREE_DIR, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
857 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
858 LINE(STAT_HEAD, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
859 LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
860 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
861 LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
862 LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
863 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
864 LINE(BLAME_ID, "", COLOR_MAGENTA, COLOR_DEFAULT, 0)
867 #define LINE(type, line, fg, bg, attr) \
875 const char *name; /* Option name. */
876 int namelen; /* Size of option name. */
877 const char *line; /* The start of line to match. */
878 int linelen; /* Size of string to match. */
879 int fg, bg, attr; /* Color and text attributes for the lines. */
882 static struct line_info line_info[] = {
883 #define LINE(type, line, fg, bg, attr) \
884 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
889 static enum line_type
890 get_line_type(const char *line)
892 int linelen = strlen(line);
895 for (type = 0; type < ARRAY_SIZE(line_info); type++)
896 /* Case insensitive search matches Signed-off-by lines better. */
897 if (linelen >= line_info[type].linelen &&
898 !strncasecmp(line_info[type].line, line, line_info[type].linelen))
905 get_line_attr(enum line_type type)
907 assert(type < ARRAY_SIZE(line_info));
908 return COLOR_PAIR(type) | line_info[type].attr;
911 static struct line_info *
912 get_line_info(const char *name)
914 size_t namelen = strlen(name);
917 for (type = 0; type < ARRAY_SIZE(line_info); type++)
918 if (namelen == line_info[type].namelen &&
919 !string_enum_compare(line_info[type].name, name, namelen))
920 return &line_info[type];
928 int default_bg = line_info[LINE_DEFAULT].bg;
929 int default_fg = line_info[LINE_DEFAULT].fg;
934 if (assume_default_colors(default_fg, default_bg) == ERR) {
935 default_bg = COLOR_BLACK;
936 default_fg = COLOR_WHITE;
939 for (type = 0; type < ARRAY_SIZE(line_info); type++) {
940 struct line_info *info = &line_info[type];
941 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
942 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
944 init_pair(type, fg, bg);
952 unsigned int selected:1;
953 unsigned int dirty:1;
955 void *data; /* User data */
965 enum request request;
968 static struct keybinding default_keybindings[] = {
970 { 'm', REQ_VIEW_MAIN },
971 { 'd', REQ_VIEW_DIFF },
972 { 'l', REQ_VIEW_LOG },
973 { 't', REQ_VIEW_TREE },
974 { 'f', REQ_VIEW_BLOB },
975 { 'B', REQ_VIEW_BLAME },
976 { 'p', REQ_VIEW_PAGER },
977 { 'h', REQ_VIEW_HELP },
978 { 'S', REQ_VIEW_STATUS },
979 { 'c', REQ_VIEW_STAGE },
981 /* View manipulation */
982 { 'q', REQ_VIEW_CLOSE },
983 { KEY_TAB, REQ_VIEW_NEXT },
984 { KEY_RETURN, REQ_ENTER },
985 { KEY_UP, REQ_PREVIOUS },
986 { KEY_DOWN, REQ_NEXT },
987 { 'R', REQ_REFRESH },
988 { KEY_F(5), REQ_REFRESH },
989 { 'O', REQ_MAXIMIZE },
991 /* Cursor navigation */
992 { 'k', REQ_MOVE_UP },
993 { 'j', REQ_MOVE_DOWN },
994 { KEY_HOME, REQ_MOVE_FIRST_LINE },
995 { KEY_END, REQ_MOVE_LAST_LINE },
996 { KEY_NPAGE, REQ_MOVE_PAGE_DOWN },
997 { ' ', REQ_MOVE_PAGE_DOWN },
998 { KEY_PPAGE, REQ_MOVE_PAGE_UP },
999 { 'b', REQ_MOVE_PAGE_UP },
1000 { '-', REQ_MOVE_PAGE_UP },
1003 { KEY_IC, REQ_SCROLL_LINE_UP },
1004 { KEY_DC, REQ_SCROLL_LINE_DOWN },
1005 { 'w', REQ_SCROLL_PAGE_UP },
1006 { 's', REQ_SCROLL_PAGE_DOWN },
1009 { '/', REQ_SEARCH },
1010 { '?', REQ_SEARCH_BACK },
1011 { 'n', REQ_FIND_NEXT },
1012 { 'N', REQ_FIND_PREV },
1016 { 'z', REQ_STOP_LOADING },
1017 { 'v', REQ_SHOW_VERSION },
1018 { 'r', REQ_SCREEN_REDRAW },
1019 { '.', REQ_TOGGLE_LINENO },
1020 { 'D', REQ_TOGGLE_DATE },
1021 { 'A', REQ_TOGGLE_AUTHOR },
1022 { 'g', REQ_TOGGLE_REV_GRAPH },
1023 { 'F', REQ_TOGGLE_REFS },
1024 { ':', REQ_PROMPT },
1025 { 'u', REQ_STATUS_UPDATE },
1026 { '!', REQ_STATUS_REVERT },
1027 { 'M', REQ_STATUS_MERGE },
1028 { '@', REQ_STAGE_NEXT },
1029 { ',', REQ_TREE_PARENT },
1032 /* Using the ncurses SIGWINCH handler. */
1033 { KEY_RESIZE, REQ_SCREEN_RESIZE },
1036 #define KEYMAP_INFO \
1050 #define KEYMAP_(name) KEYMAP_##name
1055 static struct int_map keymap_table[] = {
1056 #define KEYMAP_(name) { #name, STRING_SIZE(#name), KEYMAP_##name }
1061 #define set_keymap(map, name) \
1062 set_from_int_map(keymap_table, ARRAY_SIZE(keymap_table), map, name, strlen(name))
1064 struct keybinding_table {
1065 struct keybinding *data;
1069 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1072 add_keybinding(enum keymap keymap, enum request request, int key)
1074 struct keybinding_table *table = &keybindings[keymap];
1076 table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1078 die("Failed to allocate keybinding");
1079 table->data[table->size].alias = key;
1080 table->data[table->size++].request = request;
1083 /* Looks for a key binding first in the given map, then in the generic map, and
1084 * lastly in the default keybindings. */
1086 get_keybinding(enum keymap keymap, int key)
1090 for (i = 0; i < keybindings[keymap].size; i++)
1091 if (keybindings[keymap].data[i].alias == key)
1092 return keybindings[keymap].data[i].request;
1094 for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1095 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1096 return keybindings[KEYMAP_GENERIC].data[i].request;
1098 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1099 if (default_keybindings[i].alias == key)
1100 return default_keybindings[i].request;
1102 return (enum request) key;
1111 static struct key key_table[] = {
1112 { "Enter", KEY_RETURN },
1114 { "Backspace", KEY_BACKSPACE },
1116 { "Escape", KEY_ESC },
1117 { "Left", KEY_LEFT },
1118 { "Right", KEY_RIGHT },
1120 { "Down", KEY_DOWN },
1121 { "Insert", KEY_IC },
1122 { "Delete", KEY_DC },
1124 { "Home", KEY_HOME },
1126 { "PageUp", KEY_PPAGE },
1127 { "PageDown", KEY_NPAGE },
1137 { "F10", KEY_F(10) },
1138 { "F11", KEY_F(11) },
1139 { "F12", KEY_F(12) },
1143 get_key_value(const char *name)
1147 for (i = 0; i < ARRAY_SIZE(key_table); i++)
1148 if (!strcasecmp(key_table[i].name, name))
1149 return key_table[i].value;
1151 if (strlen(name) == 1 && isprint(*name))
1158 get_key_name(int key_value)
1160 static char key_char[] = "'X'";
1161 const char *seq = NULL;
1164 for (key = 0; key < ARRAY_SIZE(key_table); key++)
1165 if (key_table[key].value == key_value)
1166 seq = key_table[key].name;
1170 isprint(key_value)) {
1171 key_char[1] = (char) key_value;
1175 return seq ? seq : "(no key)";
1179 get_key(enum request request)
1181 static char buf[BUFSIZ];
1188 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1189 struct keybinding *keybinding = &default_keybindings[i];
1191 if (keybinding->request != request)
1194 if (!string_format_from(buf, &pos, "%s%s", sep,
1195 get_key_name(keybinding->alias)))
1196 return "Too many keybindings!";
1203 struct run_request {
1206 const char *argv[SIZEOF_ARG];
1209 static struct run_request *run_request;
1210 static size_t run_requests;
1213 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1215 struct run_request *req;
1217 if (argc >= ARRAY_SIZE(req->argv) - 1)
1220 req = realloc(run_request, (run_requests + 1) * sizeof(*run_request));
1225 req = &run_request[run_requests];
1226 req->keymap = keymap;
1228 req->argv[0] = NULL;
1230 if (!format_argv(req->argv, argv, FORMAT_NONE))
1233 return REQ_NONE + ++run_requests;
1236 static struct run_request *
1237 get_run_request(enum request request)
1239 if (request <= REQ_NONE)
1241 return &run_request[request - REQ_NONE - 1];
1245 add_builtin_run_requests(void)
1247 const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1248 const char *gc[] = { "git", "gc", NULL };
1255 { KEYMAP_MAIN, 'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1256 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1260 for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1263 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1264 if (req != REQ_NONE)
1265 add_keybinding(reqs[i].keymap, req, reqs[i].key);
1270 * User config file handling.
1273 static struct int_map color_map[] = {
1274 #define COLOR_MAP(name) { #name, STRING_SIZE(#name), COLOR_##name }
1286 #define set_color(color, name) \
1287 set_from_int_map(color_map, ARRAY_SIZE(color_map), color, name, strlen(name))
1289 static struct int_map attr_map[] = {
1290 #define ATTR_MAP(name) { #name, STRING_SIZE(#name), A_##name }
1297 ATTR_MAP(UNDERLINE),
1300 #define set_attribute(attr, name) \
1301 set_from_int_map(attr_map, ARRAY_SIZE(attr_map), attr, name, strlen(name))
1303 static int config_lineno;
1304 static bool config_errors;
1305 static const char *config_msg;
1307 /* Wants: object fgcolor bgcolor [attr] */
1309 option_color_command(int argc, const char *argv[])
1311 struct line_info *info;
1313 if (argc != 3 && argc != 4) {
1314 config_msg = "Wrong number of arguments given to color command";
1318 info = get_line_info(argv[0]);
1320 if (!string_enum_compare(argv[0], "main-delim", strlen("main-delim"))) {
1321 info = get_line_info("delimiter");
1323 } else if (!string_enum_compare(argv[0], "main-date", strlen("main-date"))) {
1324 info = get_line_info("date");
1327 config_msg = "Unknown color name";
1332 if (set_color(&info->fg, argv[1]) == ERR ||
1333 set_color(&info->bg, argv[2]) == ERR) {
1334 config_msg = "Unknown color";
1338 if (argc == 4 && set_attribute(&info->attr, argv[3]) == ERR) {
1339 config_msg = "Unknown attribute";
1346 static bool parse_bool(const char *s)
1348 return (!strcmp(s, "1") || !strcmp(s, "true") ||
1349 !strcmp(s, "yes")) ? TRUE : FALSE;
1353 parse_int(const char *s, int default_value, int min, int max)
1355 int value = atoi(s);
1357 return (value < min || value > max) ? default_value : value;
1360 /* Wants: name = value */
1362 option_set_command(int argc, const char *argv[])
1365 config_msg = "Wrong number of arguments given to set command";
1369 if (strcmp(argv[1], "=")) {
1370 config_msg = "No value assigned";
1374 if (!strcmp(argv[0], "show-author")) {
1375 opt_author = parse_bool(argv[2]);
1379 if (!strcmp(argv[0], "show-date")) {
1380 opt_date = parse_bool(argv[2]);
1384 if (!strcmp(argv[0], "show-rev-graph")) {
1385 opt_rev_graph = parse_bool(argv[2]);
1389 if (!strcmp(argv[0], "show-refs")) {
1390 opt_show_refs = parse_bool(argv[2]);
1394 if (!strcmp(argv[0], "show-line-numbers")) {
1395 opt_line_number = parse_bool(argv[2]);
1399 if (!strcmp(argv[0], "line-graphics")) {
1400 opt_line_graphics = parse_bool(argv[2]);
1404 if (!strcmp(argv[0], "line-number-interval")) {
1405 opt_num_interval = parse_int(argv[2], opt_num_interval, 1, 1024);
1409 if (!strcmp(argv[0], "author-width")) {
1410 opt_author_cols = parse_int(argv[2], opt_author_cols, 0, 1024);
1414 if (!strcmp(argv[0], "tab-size")) {
1415 opt_tab_size = parse_int(argv[2], opt_tab_size, 1, 1024);
1419 if (!strcmp(argv[0], "commit-encoding")) {
1420 const char *arg = argv[2];
1421 int arglen = strlen(arg);
1426 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1427 config_msg = "Unmatched quotation";
1430 arg += 1; arglen -= 2;
1432 string_ncopy(opt_encoding, arg, strlen(arg));
1437 config_msg = "Unknown variable name";
1441 /* Wants: mode request key */
1443 option_bind_command(int argc, const char *argv[])
1445 enum request request;
1450 config_msg = "Wrong number of arguments given to bind command";
1454 if (set_keymap(&keymap, argv[0]) == ERR) {
1455 config_msg = "Unknown key map";
1459 key = get_key_value(argv[1]);
1461 config_msg = "Unknown key";
1465 request = get_request(argv[2]);
1466 if (request == REQ_NONE) {
1467 const char *obsolete[] = { "cherry-pick" };
1468 size_t namelen = strlen(argv[2]);
1471 for (i = 0; i < ARRAY_SIZE(obsolete); i++) {
1472 if (namelen == strlen(obsolete[i]) &&
1473 !string_enum_compare(obsolete[i], argv[2], namelen)) {
1474 config_msg = "Obsolete request name";
1479 if (request == REQ_NONE && *argv[2]++ == '!')
1480 request = add_run_request(keymap, key, argc - 2, argv + 2);
1481 if (request == REQ_NONE) {
1482 config_msg = "Unknown request name";
1486 add_keybinding(keymap, request, key);
1492 set_option(const char *opt, char *value)
1494 const char *argv[SIZEOF_ARG];
1497 if (!argv_from_string(argv, &argc, value)) {
1498 config_msg = "Too many option arguments";
1502 if (!strcmp(opt, "color"))
1503 return option_color_command(argc, argv);
1505 if (!strcmp(opt, "set"))
1506 return option_set_command(argc, argv);
1508 if (!strcmp(opt, "bind"))
1509 return option_bind_command(argc, argv);
1511 config_msg = "Unknown option command";
1516 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1521 config_msg = "Internal error";
1523 /* Check for comment markers, since read_properties() will
1524 * only ensure opt and value are split at first " \t". */
1525 optlen = strcspn(opt, "#");
1529 if (opt[optlen] != 0) {
1530 config_msg = "No option value";
1534 /* Look for comment endings in the value. */
1535 size_t len = strcspn(value, "#");
1537 if (len < valuelen) {
1539 value[valuelen] = 0;
1542 status = set_option(opt, value);
1545 if (status == ERR) {
1546 fprintf(stderr, "Error on line %d, near '%.*s': %s\n",
1547 config_lineno, (int) optlen, opt, config_msg);
1548 config_errors = TRUE;
1551 /* Always keep going if errors are encountered. */
1556 load_option_file(const char *path)
1560 /* It's ok that the file doesn't exist. */
1561 if (!init_io_fd(&io, fopen(path, "r")))
1565 config_errors = FALSE;
1567 if (read_properties(&io, " \t", read_option) == ERR ||
1568 config_errors == TRUE)
1569 fprintf(stderr, "Errors while loading %s.\n", path);
1575 const char *home = getenv("HOME");
1576 const char *tigrc_user = getenv("TIGRC_USER");
1577 const char *tigrc_system = getenv("TIGRC_SYSTEM");
1578 char buf[SIZEOF_STR];
1580 add_builtin_run_requests();
1582 if (!tigrc_system) {
1583 if (!string_format(buf, "%s/tigrc", SYSCONFDIR))
1587 load_option_file(tigrc_system);
1590 if (!home || !string_format(buf, "%s/.tigrc", home))
1594 load_option_file(tigrc_user);
1607 /* The display array of active views and the index of the current view. */
1608 static struct view *display[2];
1609 static unsigned int current_view;
1611 /* Reading from the prompt? */
1612 static bool input_mode = FALSE;
1614 #define foreach_displayed_view(view, i) \
1615 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1617 #define displayed_views() (display[1] != NULL ? 2 : 1)
1619 /* Current head and commit ID */
1620 static char ref_blob[SIZEOF_REF] = "";
1621 static char ref_commit[SIZEOF_REF] = "HEAD";
1622 static char ref_head[SIZEOF_REF] = "HEAD";
1625 const char *name; /* View name */
1626 const char *cmd_env; /* Command line set via environment */
1627 const char *id; /* Points to either of ref_{head,commit,blob} */
1629 struct view_ops *ops; /* View operations */
1631 enum keymap keymap; /* What keymap does this view have */
1632 bool git_dir; /* Whether the view requires a git directory. */
1634 char ref[SIZEOF_REF]; /* Hovered commit reference */
1635 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
1637 int height, width; /* The width and height of the main window */
1638 WINDOW *win; /* The main window */
1639 WINDOW *title; /* The title window living below the main window */
1642 unsigned long offset; /* Offset of the window top */
1643 unsigned long lineno; /* Current line number */
1646 char grep[SIZEOF_STR]; /* Search string */
1647 regex_t *regex; /* Pre-compiled regex */
1649 /* If non-NULL, points to the view that opened this view. If this view
1650 * is closed tig will switch back to the parent view. */
1651 struct view *parent;
1654 size_t lines; /* Total number of lines */
1655 struct line *line; /* Line index */
1656 size_t line_alloc; /* Total number of allocated lines */
1657 size_t line_size; /* Total number of used lines */
1658 unsigned int digits; /* Number of digits in the lines member. */
1661 struct line *curline; /* Line currently being drawn. */
1662 enum line_type curtype; /* Attribute currently used for drawing. */
1663 unsigned long col; /* Column when drawing. */
1672 /* What type of content being displayed. Used in the title bar. */
1674 /* Default command arguments. */
1676 /* Open and reads in all view content. */
1677 bool (*open)(struct view *view);
1678 /* Read one line; updates view->line. */
1679 bool (*read)(struct view *view, char *data);
1680 /* Draw one line; @lineno must be < view->height. */
1681 bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
1682 /* Depending on view handle a special requests. */
1683 enum request (*request)(struct view *view, enum request request, struct line *line);
1684 /* Search for regex in a line. */
1685 bool (*grep)(struct view *view, struct line *line);
1687 void (*select)(struct view *view, struct line *line);
1690 static struct view_ops blame_ops;
1691 static struct view_ops blob_ops;
1692 static struct view_ops diff_ops;
1693 static struct view_ops help_ops;
1694 static struct view_ops log_ops;
1695 static struct view_ops main_ops;
1696 static struct view_ops pager_ops;
1697 static struct view_ops stage_ops;
1698 static struct view_ops status_ops;
1699 static struct view_ops tree_ops;
1701 #define VIEW_STR(name, env, ref, ops, map, git) \
1702 { name, #env, ref, ops, map, git }
1704 #define VIEW_(id, name, ops, git, ref) \
1705 VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
1708 static struct view views[] = {
1709 VIEW_(MAIN, "main", &main_ops, TRUE, ref_head),
1710 VIEW_(DIFF, "diff", &diff_ops, TRUE, ref_commit),
1711 VIEW_(LOG, "log", &log_ops, TRUE, ref_head),
1712 VIEW_(TREE, "tree", &tree_ops, TRUE, ref_commit),
1713 VIEW_(BLOB, "blob", &blob_ops, TRUE, ref_blob),
1714 VIEW_(BLAME, "blame", &blame_ops, TRUE, ref_commit),
1715 VIEW_(HELP, "help", &help_ops, FALSE, ""),
1716 VIEW_(PAGER, "pager", &pager_ops, FALSE, "stdin"),
1717 VIEW_(STATUS, "status", &status_ops, TRUE, ""),
1718 VIEW_(STAGE, "stage", &stage_ops, TRUE, ""),
1721 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
1722 #define VIEW_REQ(view) ((view) - views + REQ_OFFSET + 1)
1724 #define foreach_view(view, i) \
1725 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1727 #define view_is_displayed(view) \
1728 (view == display[0] || view == display[1])
1735 static int line_graphics[] = {
1736 /* LINE_GRAPHIC_VLINE: */ '|'
1740 set_view_attr(struct view *view, enum line_type type)
1742 if (!view->curline->selected && view->curtype != type) {
1743 wattrset(view->win, get_line_attr(type));
1744 wchgat(view->win, -1, 0, type, NULL);
1745 view->curtype = type;
1750 draw_chars(struct view *view, enum line_type type, const char *string,
1751 int max_len, bool use_tilde)
1755 int trimmed = FALSE;
1761 len = utf8_length(string, &col, max_len, &trimmed, use_tilde);
1763 col = len = strlen(string);
1764 if (len > max_len) {
1768 col = len = max_len;
1773 set_view_attr(view, type);
1774 waddnstr(view->win, string, len);
1775 if (trimmed && use_tilde) {
1776 set_view_attr(view, LINE_DELIMITER);
1777 waddch(view->win, '~');
1785 draw_space(struct view *view, enum line_type type, int max, int spaces)
1787 static char space[] = " ";
1790 spaces = MIN(max, spaces);
1792 while (spaces > 0) {
1793 int len = MIN(spaces, sizeof(space) - 1);
1795 col += draw_chars(view, type, space, spaces, FALSE);
1803 draw_lineno(struct view *view, unsigned int lineno)
1806 int digits3 = view->digits < 3 ? 3 : view->digits;
1807 int max_number = MIN(digits3, STRING_SIZE(number));
1808 int max = view->width - view->col;
1811 if (max < max_number)
1814 lineno += view->offset + 1;
1815 if (lineno == 1 || (lineno % opt_num_interval) == 0) {
1816 static char fmt[] = "%1ld";
1818 if (view->digits <= 9)
1819 fmt[1] = '0' + digits3;
1821 if (!string_format(number, fmt, lineno))
1823 col = draw_chars(view, LINE_LINE_NUMBER, number, max_number, TRUE);
1825 col = draw_space(view, LINE_LINE_NUMBER, max_number, max_number);
1829 set_view_attr(view, LINE_DEFAULT);
1830 waddch(view->win, line_graphics[LINE_GRAPHIC_VLINE]);
1835 col += draw_space(view, LINE_DEFAULT, max - col, 1);
1838 return view->width - view->col <= 0;
1842 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
1844 view->col += draw_chars(view, type, string, view->width - view->col, trim);
1845 return view->width - view->col <= 0;
1849 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
1851 int max = view->width - view->col;
1857 set_view_attr(view, type);
1858 /* Using waddch() instead of waddnstr() ensures that
1859 * they'll be rendered correctly for the cursor line. */
1860 for (i = 0; i < size; i++)
1861 waddch(view->win, graphic[i]);
1865 waddch(view->win, ' ');
1869 return view->width - view->col <= 0;
1873 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
1875 int max = MIN(view->width - view->col, len);
1879 col = draw_chars(view, type, text, max - 1, trim);
1881 col = draw_space(view, type, max - 1, max - 1);
1883 view->col += col + draw_space(view, LINE_DEFAULT, max - col, max - col);
1884 return view->width - view->col <= 0;
1888 draw_date(struct view *view, struct tm *time)
1890 char buf[DATE_COLS];
1895 timelen = strftime(buf, sizeof(buf), DATE_FORMAT, time);
1896 date = timelen ? buf : NULL;
1898 return draw_field(view, LINE_DATE, date, DATE_COLS, FALSE);
1902 draw_view_line(struct view *view, unsigned int lineno)
1905 bool selected = (view->offset + lineno == view->lineno);
1908 assert(view_is_displayed(view));
1910 if (view->offset + lineno >= view->lines)
1913 line = &view->line[view->offset + lineno];
1915 wmove(view->win, lineno, 0);
1917 view->curline = line;
1918 view->curtype = LINE_NONE;
1919 line->selected = FALSE;
1922 set_view_attr(view, LINE_CURSOR);
1923 line->selected = TRUE;
1924 view->ops->select(view, line);
1925 } else if (line->selected) {
1926 wclrtoeol(view->win);
1929 scrollok(view->win, FALSE);
1930 draw_ok = view->ops->draw(view, line, lineno);
1931 scrollok(view->win, TRUE);
1937 redraw_view_dirty(struct view *view)
1942 for (lineno = 0; lineno < view->height; lineno++) {
1943 struct line *line = &view->line[view->offset + lineno];
1949 if (!draw_view_line(view, lineno))
1955 redrawwin(view->win);
1957 wnoutrefresh(view->win);
1959 wrefresh(view->win);
1963 redraw_view_from(struct view *view, int lineno)
1965 assert(0 <= lineno && lineno < view->height);
1967 for (; lineno < view->height; lineno++) {
1968 if (!draw_view_line(view, lineno))
1972 redrawwin(view->win);
1974 wnoutrefresh(view->win);
1976 wrefresh(view->win);
1980 redraw_view(struct view *view)
1983 redraw_view_from(view, 0);
1988 update_view_title(struct view *view)
1990 char buf[SIZEOF_STR];
1991 char state[SIZEOF_STR];
1992 size_t bufpos = 0, statelen = 0;
1994 assert(view_is_displayed(view));
1996 if (view != VIEW(REQ_VIEW_STATUS) && (view->lines || view->pipe)) {
1997 unsigned int view_lines = view->offset + view->height;
1998 unsigned int lines = view->lines
1999 ? MIN(view_lines, view->lines) * 100 / view->lines
2002 string_format_from(state, &statelen, "- %s %d of %d (%d%%)",
2009 time_t secs = time(NULL) - view->start_time;
2011 /* Three git seconds are a long time ... */
2013 string_format_from(state, &statelen, " %lds", secs);
2017 string_format_from(buf, &bufpos, "[%s]", view->name);
2018 if (*view->ref && bufpos < view->width) {
2019 size_t refsize = strlen(view->ref);
2020 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2022 if (minsize < view->width)
2023 refsize = view->width - minsize + 7;
2024 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2027 if (statelen && bufpos < view->width) {
2028 string_format_from(buf, &bufpos, " %s", state);
2031 if (view == display[current_view])
2032 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2034 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2036 mvwaddnstr(view->title, 0, 0, buf, bufpos);
2037 wclrtoeol(view->title);
2038 wmove(view->title, 0, view->width - 1);
2041 wnoutrefresh(view->title);
2043 wrefresh(view->title);
2047 resize_display(void)
2050 struct view *base = display[0];
2051 struct view *view = display[1] ? display[1] : display[0];
2053 /* Setup window dimensions */
2055 getmaxyx(stdscr, base->height, base->width);
2057 /* Make room for the status window. */
2061 /* Horizontal split. */
2062 view->width = base->width;
2063 view->height = SCALE_SPLIT_VIEW(base->height);
2064 base->height -= view->height;
2066 /* Make room for the title bar. */
2070 /* Make room for the title bar. */
2075 foreach_displayed_view (view, i) {
2077 view->win = newwin(view->height, 0, offset, 0);
2079 die("Failed to create %s view", view->name);
2081 scrollok(view->win, TRUE);
2083 view->title = newwin(1, 0, offset + view->height, 0);
2085 die("Failed to create title window");
2088 wresize(view->win, view->height, view->width);
2089 mvwin(view->win, offset, 0);
2090 mvwin(view->title, offset + view->height, 0);
2093 offset += view->height + 1;
2098 redraw_display(void)
2103 foreach_displayed_view (view, i) {
2105 update_view_title(view);
2110 update_display_cursor(struct view *view)
2112 /* Move the cursor to the right-most column of the cursor line.
2114 * XXX: This could turn out to be a bit expensive, but it ensures that
2115 * the cursor does not jump around. */
2117 wmove(view->win, view->lineno - view->offset, view->width - 1);
2118 wrefresh(view->win);
2126 /* Scrolling backend */
2128 do_scroll_view(struct view *view, int lines)
2130 bool redraw_current_line = FALSE;
2132 /* The rendering expects the new offset. */
2133 view->offset += lines;
2135 assert(0 <= view->offset && view->offset < view->lines);
2138 /* Move current line into the view. */
2139 if (view->lineno < view->offset) {
2140 view->lineno = view->offset;
2141 redraw_current_line = TRUE;
2142 } else if (view->lineno >= view->offset + view->height) {
2143 view->lineno = view->offset + view->height - 1;
2144 redraw_current_line = TRUE;
2147 assert(view->offset <= view->lineno && view->lineno < view->lines);
2149 /* Redraw the whole screen if scrolling is pointless. */
2150 if (view->height < ABS(lines)) {
2154 int line = lines > 0 ? view->height - lines : 0;
2155 int end = line + ABS(lines);
2157 wscrl(view->win, lines);
2159 for (; line < end; line++) {
2160 if (!draw_view_line(view, line))
2164 if (redraw_current_line)
2165 draw_view_line(view, view->lineno - view->offset);
2168 redrawwin(view->win);
2169 wrefresh(view->win);
2173 /* Scroll frontend */
2175 scroll_view(struct view *view, enum request request)
2179 assert(view_is_displayed(view));
2182 case REQ_SCROLL_PAGE_DOWN:
2183 lines = view->height;
2184 case REQ_SCROLL_LINE_DOWN:
2185 if (view->offset + lines > view->lines)
2186 lines = view->lines - view->offset;
2188 if (lines == 0 || view->offset + view->height >= view->lines) {
2189 report("Cannot scroll beyond the last line");
2194 case REQ_SCROLL_PAGE_UP:
2195 lines = view->height;
2196 case REQ_SCROLL_LINE_UP:
2197 if (lines > view->offset)
2198 lines = view->offset;
2201 report("Cannot scroll beyond the first line");
2209 die("request %d not handled in switch", request);
2212 do_scroll_view(view, lines);
2217 move_view(struct view *view, enum request request)
2219 int scroll_steps = 0;
2223 case REQ_MOVE_FIRST_LINE:
2224 steps = -view->lineno;
2227 case REQ_MOVE_LAST_LINE:
2228 steps = view->lines - view->lineno - 1;
2231 case REQ_MOVE_PAGE_UP:
2232 steps = view->height > view->lineno
2233 ? -view->lineno : -view->height;
2236 case REQ_MOVE_PAGE_DOWN:
2237 steps = view->lineno + view->height >= view->lines
2238 ? view->lines - view->lineno - 1 : view->height;
2250 die("request %d not handled in switch", request);
2253 if (steps <= 0 && view->lineno == 0) {
2254 report("Cannot move beyond the first line");
2257 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2258 report("Cannot move beyond the last line");
2262 /* Move the current line */
2263 view->lineno += steps;
2264 assert(0 <= view->lineno && view->lineno < view->lines);
2266 /* Check whether the view needs to be scrolled */
2267 if (view->lineno < view->offset ||
2268 view->lineno >= view->offset + view->height) {
2269 scroll_steps = steps;
2270 if (steps < 0 && -steps > view->offset) {
2271 scroll_steps = -view->offset;
2273 } else if (steps > 0) {
2274 if (view->lineno == view->lines - 1 &&
2275 view->lines > view->height) {
2276 scroll_steps = view->lines - view->offset - 1;
2277 if (scroll_steps >= view->height)
2278 scroll_steps -= view->height - 1;
2283 if (!view_is_displayed(view)) {
2284 view->offset += scroll_steps;
2285 assert(0 <= view->offset && view->offset < view->lines);
2286 view->ops->select(view, &view->line[view->lineno]);
2290 /* Repaint the old "current" line if we be scrolling */
2291 if (ABS(steps) < view->height)
2292 draw_view_line(view, view->lineno - steps - view->offset);
2295 do_scroll_view(view, scroll_steps);
2299 /* Draw the current line */
2300 draw_view_line(view, view->lineno - view->offset);
2302 redrawwin(view->win);
2303 wrefresh(view->win);
2312 static void search_view(struct view *view, enum request request);
2315 find_next_line(struct view *view, unsigned long lineno, struct line *line)
2317 assert(view_is_displayed(view));
2319 if (!view->ops->grep(view, line))
2322 if (lineno - view->offset >= view->height) {
2323 view->offset = lineno;
2324 view->lineno = lineno;
2328 unsigned long old_lineno = view->lineno - view->offset;
2330 view->lineno = lineno;
2331 draw_view_line(view, old_lineno);
2333 draw_view_line(view, view->lineno - view->offset);
2334 redrawwin(view->win);
2335 wrefresh(view->win);
2338 report("Line %ld matches '%s'", lineno + 1, view->grep);
2343 find_next(struct view *view, enum request request)
2345 unsigned long lineno = view->lineno;
2350 report("No previous search");
2352 search_view(view, request);
2362 case REQ_SEARCH_BACK:
2371 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2372 lineno += direction;
2374 /* Note, lineno is unsigned long so will wrap around in which case it
2375 * will become bigger than view->lines. */
2376 for (; lineno < view->lines; lineno += direction) {
2377 struct line *line = &view->line[lineno];
2379 if (find_next_line(view, lineno, line))
2383 report("No match found for '%s'", view->grep);
2387 search_view(struct view *view, enum request request)
2392 regfree(view->regex);
2395 view->regex = calloc(1, sizeof(*view->regex));
2400 regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2401 if (regex_err != 0) {
2402 char buf[SIZEOF_STR] = "unknown error";
2404 regerror(regex_err, view->regex, buf, sizeof(buf));
2405 report("Search failed: %s", buf);
2409 string_copy(view->grep, opt_search);
2411 find_next(view, request);
2415 * Incremental updating
2419 reset_view(struct view *view)
2423 for (i = 0; i < view->lines; i++)
2424 free(view->line[i].data);
2431 view->line_size = 0;
2432 view->line_alloc = 0;
2437 free_argv(const char *argv[])
2441 for (argc = 0; argv[argc]; argc++)
2442 free((void *) argv[argc]);
2446 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
2448 char buf[SIZEOF_STR];
2450 bool noreplace = flags == FORMAT_NONE;
2452 free_argv(dst_argv);
2454 for (argc = 0; src_argv[argc]; argc++) {
2455 const char *arg = src_argv[argc];
2459 char *next = strstr(arg, "%(");
2460 int len = next - arg;
2463 if (!next || noreplace) {
2464 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
2469 } else if (!prefixcmp(next, "%(directory)")) {
2472 } else if (!prefixcmp(next, "%(file)")) {
2475 } else if (!prefixcmp(next, "%(ref)")) {
2476 value = *opt_ref ? opt_ref : "HEAD";
2478 } else if (!prefixcmp(next, "%(head)")) {
2481 } else if (!prefixcmp(next, "%(commit)")) {
2484 } else if (!prefixcmp(next, "%(blob)")) {
2488 report("Unknown replacement: `%s`", next);
2492 if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
2495 arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
2498 dst_argv[argc] = strdup(buf);
2499 if (!dst_argv[argc])
2503 dst_argv[argc] = NULL;
2505 return src_argv[argc] == NULL;
2509 format_command(char dst[], const char *src_argv[], enum format_flags flags)
2511 const char *dst_argv[SIZEOF_ARG * 2] = { NULL };
2515 if (!format_argv(dst_argv, src_argv, flags)) {
2516 free_argv(dst_argv);
2520 for (argc = 0; dst_argv[argc] && bufsize < SIZEOF_STR; argc++) {
2522 dst[bufsize++] = ' ';
2523 bufsize = sq_quote(dst, bufsize, dst_argv[argc]);
2526 if (bufsize < SIZEOF_STR)
2528 free_argv(dst_argv);
2530 return src_argv[argc] == NULL && bufsize < SIZEOF_STR;
2534 end_update(struct view *view, bool force)
2538 while (!view->ops->read(view, NULL))
2541 set_nonblocking_input(FALSE);
2542 done_io(view->pipe);
2547 setup_update(struct view *view, const char *vid)
2549 set_nonblocking_input(TRUE);
2551 string_copy_rev(view->vid, vid);
2552 view->pipe = &view->io;
2553 view->start_time = time(NULL);
2557 prepare_update(struct view *view, const char *argv[], const char *dir,
2558 enum format_flags flags)
2561 end_update(view, TRUE);
2562 return init_io_rd(&view->io, argv, dir, flags);
2566 prepare_update_file(struct view *view, const char *name)
2569 end_update(view, TRUE);
2570 return init_io_fd(&view->io, fopen(name, "r"));
2574 begin_update(struct view *view, bool refresh)
2577 if (!start_io(&view->io))
2581 if (view == VIEW(REQ_VIEW_TREE) && strcmp(view->vid, view->id))
2584 if (!run_io_rd(&view->io, view->ops->argv, FORMAT_ALL))
2587 /* Put the current ref_* value to the view title ref
2588 * member. This is needed by the blob view. Most other
2589 * views sets it automatically after loading because the
2590 * first line is a commit line. */
2591 string_copy_rev(view->ref, view->id);
2594 setup_update(view, view->id);
2599 #define ITEM_CHUNK_SIZE 256
2601 realloc_items(void *mem, size_t *size, size_t new_size, size_t item_size)
2603 size_t num_chunks = *size / ITEM_CHUNK_SIZE;
2604 size_t num_chunks_new = (new_size + ITEM_CHUNK_SIZE - 1) / ITEM_CHUNK_SIZE;
2606 if (mem == NULL || num_chunks != num_chunks_new) {
2607 *size = num_chunks_new * ITEM_CHUNK_SIZE;
2608 mem = realloc(mem, *size * item_size);
2614 static struct line *
2615 realloc_lines(struct view *view, size_t line_size)
2617 size_t alloc = view->line_alloc;
2618 struct line *tmp = realloc_items(view->line, &alloc, line_size,
2619 sizeof(*view->line));
2625 view->line_alloc = alloc;
2626 view->line_size = line_size;
2631 update_view(struct view *view)
2633 char out_buffer[BUFSIZ * 2];
2635 /* The number of lines to read. If too low it will cause too much
2636 * redrawing (and possible flickering), if too high responsiveness
2638 unsigned long lines = view->height;
2639 int redraw_from = -1;
2644 /* Only redraw if lines are visible. */
2645 if (view->offset + view->height >= view->lines)
2646 redraw_from = view->lines - view->offset;
2648 /* FIXME: This is probably not perfect for backgrounded views. */
2649 if (!realloc_lines(view, view->lines + lines))
2652 while ((line = io_gets(view->pipe))) {
2653 size_t linelen = strlen(line);
2656 line[linelen - 1] = 0;
2658 if (opt_iconv != ICONV_NONE) {
2659 ICONV_CONST char *inbuf = line;
2660 size_t inlen = linelen;
2662 char *outbuf = out_buffer;
2663 size_t outlen = sizeof(out_buffer);
2667 ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2668 if (ret != (size_t) -1) {
2670 linelen = strlen(out_buffer);
2674 if (!view->ops->read(view, line))
2684 lines = view->lines;
2685 for (digits = 0; lines; digits++)
2688 /* Keep the displayed view in sync with line number scaling. */
2689 if (digits != view->digits) {
2690 view->digits = digits;
2695 if (io_error(view->pipe)) {
2696 report("Failed to read: %s", io_strerror(view->pipe));
2697 end_update(view, TRUE);
2699 } else if (io_eof(view->pipe)) {
2701 end_update(view, FALSE);
2704 if (!view_is_displayed(view))
2707 if (view == VIEW(REQ_VIEW_TREE)) {
2708 /* Clear the view and redraw everything since the tree sorting
2709 * might have rearranged things. */
2712 } else if (redraw_from >= 0) {
2713 /* If this is an incremental update, redraw the previous line
2714 * since for commits some members could have changed when
2715 * loading the main view. */
2716 if (redraw_from > 0)
2719 /* Since revision graph visualization requires knowledge
2720 * about the parent commit, it causes a further one-off
2721 * needed to be redrawn for incremental updates. */
2722 if (redraw_from > 0 && opt_rev_graph)
2725 /* Incrementally draw avoids flickering. */
2726 redraw_view_from(view, redraw_from);
2729 if (view == VIEW(REQ_VIEW_BLAME))
2730 redraw_view_dirty(view);
2732 /* Update the title _after_ the redraw so that if the redraw picks up a
2733 * commit reference in view->ref it'll be available here. */
2734 update_view_title(view);
2738 report("Allocation failure");
2739 end_update(view, TRUE);
2743 static struct line *
2744 add_line_data(struct view *view, void *data, enum line_type type)
2746 struct line *line = &view->line[view->lines++];
2748 memset(line, 0, sizeof(*line));
2755 static struct line *
2756 add_line_text(struct view *view, const char *text, enum line_type type)
2758 char *data = text ? strdup(text) : NULL;
2760 return data ? add_line_data(view, data, type) : NULL;
2769 OPEN_DEFAULT = 0, /* Use default view switching. */
2770 OPEN_SPLIT = 1, /* Split current view. */
2771 OPEN_BACKGROUNDED = 2, /* Backgrounded. */
2772 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
2773 OPEN_NOMAXIMIZE = 8, /* Do not maximize the current view. */
2774 OPEN_REFRESH = 16, /* Refresh view using previous command. */
2775 OPEN_PREPARED = 32, /* Open already prepared command. */
2779 open_view(struct view *prev, enum request request, enum open_flags flags)
2781 bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
2782 bool split = !!(flags & OPEN_SPLIT);
2783 bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
2784 bool nomaximize = !!(flags & (OPEN_NOMAXIMIZE | OPEN_REFRESH));
2785 struct view *view = VIEW(request);
2786 int nviews = displayed_views();
2787 struct view *base_view = display[0];
2789 if (view == prev && nviews == 1 && !reload) {
2790 report("Already in %s view", view->name);
2794 if (view->git_dir && !opt_git_dir[0]) {
2795 report("The %s view is disabled in pager view", view->name);
2803 } else if (!nomaximize) {
2804 /* Maximize the current view. */
2805 memset(display, 0, sizeof(display));
2807 display[current_view] = view;
2810 /* Resize the view when switching between split- and full-screen,
2811 * or when switching between two different full-screen views. */
2812 if (nviews != displayed_views() ||
2813 (nviews == 1 && base_view != display[0]))
2817 end_update(view, TRUE);
2819 if (view->ops->open) {
2820 if (!view->ops->open(view)) {
2821 report("Failed to load %s view", view->name);
2825 } else if ((reload || strcmp(view->vid, view->id)) &&
2826 !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
2827 report("Failed to load %s view", view->name);
2831 if (split && prev->lineno - prev->offset >= prev->height) {
2832 /* Take the title line into account. */
2833 int lines = prev->lineno - prev->offset - prev->height + 1;
2835 /* Scroll the view that was split if the current line is
2836 * outside the new limited view. */
2837 do_scroll_view(prev, lines);
2840 if (prev && view != prev) {
2841 if (split && !backgrounded) {
2842 /* "Blur" the previous view. */
2843 update_view_title(prev);
2846 view->parent = prev;
2849 if (view->pipe && view->lines == 0) {
2850 /* Clear the old view and let the incremental updating refill
2854 } else if (view_is_displayed(view)) {
2859 /* If the view is backgrounded the above calls to report()
2860 * won't redraw the view title. */
2862 update_view_title(view);
2866 open_external_viewer(const char *argv[], const char *dir)
2868 def_prog_mode(); /* save current tty modes */
2869 endwin(); /* restore original tty modes */
2870 run_io_fg(argv, dir);
2871 fprintf(stderr, "Press Enter to continue");
2878 open_mergetool(const char *file)
2880 const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
2882 open_external_viewer(mergetool_argv, NULL);
2886 open_editor(bool from_root, const char *file)
2888 const char *editor_argv[] = { "vi", file, NULL };
2891 editor = getenv("GIT_EDITOR");
2892 if (!editor && *opt_editor)
2893 editor = opt_editor;
2895 editor = getenv("VISUAL");
2897 editor = getenv("EDITOR");
2901 editor_argv[0] = editor;
2902 open_external_viewer(editor_argv, from_root ? opt_cdup : NULL);
2906 open_run_request(enum request request)
2908 struct run_request *req = get_run_request(request);
2909 const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
2912 report("Unknown run request");
2916 if (format_argv(argv, req->argv, FORMAT_ALL))
2917 open_external_viewer(argv, NULL);
2922 * User request switch noodle
2926 view_driver(struct view *view, enum request request)
2930 if (request == REQ_NONE) {
2935 if (request > REQ_NONE) {
2936 open_run_request(request);
2937 /* FIXME: When all views can refresh always do this. */
2938 if (view == VIEW(REQ_VIEW_STATUS) ||
2939 view == VIEW(REQ_VIEW_MAIN) ||
2940 view == VIEW(REQ_VIEW_LOG) ||
2941 view == VIEW(REQ_VIEW_STAGE))
2942 request = REQ_REFRESH;
2947 if (view && view->lines) {
2948 request = view->ops->request(view, request, &view->line[view->lineno]);
2949 if (request == REQ_NONE)
2956 case REQ_MOVE_PAGE_UP:
2957 case REQ_MOVE_PAGE_DOWN:
2958 case REQ_MOVE_FIRST_LINE:
2959 case REQ_MOVE_LAST_LINE:
2960 move_view(view, request);
2963 case REQ_SCROLL_LINE_DOWN:
2964 case REQ_SCROLL_LINE_UP:
2965 case REQ_SCROLL_PAGE_DOWN:
2966 case REQ_SCROLL_PAGE_UP:
2967 scroll_view(view, request);
2970 case REQ_VIEW_BLAME:
2972 report("No file chosen, press %s to open tree view",
2973 get_key(REQ_VIEW_TREE));
2976 open_view(view, request, OPEN_DEFAULT);
2981 report("No file chosen, press %s to open tree view",
2982 get_key(REQ_VIEW_TREE));
2985 open_view(view, request, OPEN_DEFAULT);
2988 case REQ_VIEW_PAGER:
2989 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
2990 report("No pager content, press %s to run command from prompt",
2991 get_key(REQ_PROMPT));
2994 open_view(view, request, OPEN_DEFAULT);
2997 case REQ_VIEW_STAGE:
2998 if (!VIEW(REQ_VIEW_STAGE)->lines) {
2999 report("No stage content, press %s to open the status view and choose file",
3000 get_key(REQ_VIEW_STATUS));
3003 open_view(view, request, OPEN_DEFAULT);
3006 case REQ_VIEW_STATUS:
3007 if (opt_is_inside_work_tree == FALSE) {
3008 report("The status view requires a working tree");
3011 open_view(view, request, OPEN_DEFAULT);
3019 open_view(view, request, OPEN_DEFAULT);
3024 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3026 if ((view == VIEW(REQ_VIEW_DIFF) &&
3027 view->parent == VIEW(REQ_VIEW_MAIN)) ||
3028 (view == VIEW(REQ_VIEW_DIFF) &&
3029 view->parent == VIEW(REQ_VIEW_BLAME)) ||
3030 (view == VIEW(REQ_VIEW_STAGE) &&
3031 view->parent == VIEW(REQ_VIEW_STATUS)) ||
3032 (view == VIEW(REQ_VIEW_BLOB) &&
3033 view->parent == VIEW(REQ_VIEW_TREE))) {
3036 view = view->parent;
3037 line = view->lineno;
3038 move_view(view, request);
3039 if (view_is_displayed(view))
3040 update_view_title(view);
3041 if (line != view->lineno)
3042 view->ops->request(view, REQ_ENTER,
3043 &view->line[view->lineno]);
3046 move_view(view, request);
3052 int nviews = displayed_views();
3053 int next_view = (current_view + 1) % nviews;
3055 if (next_view == current_view) {
3056 report("Only one view is displayed");
3060 current_view = next_view;
3061 /* Blur out the title of the previous view. */
3062 update_view_title(view);
3067 report("Refreshing is not yet supported for the %s view", view->name);
3071 if (displayed_views() == 2)
3072 open_view(view, VIEW_REQ(view), OPEN_DEFAULT);
3075 case REQ_TOGGLE_LINENO:
3076 opt_line_number = !opt_line_number;
3080 case REQ_TOGGLE_DATE:
3081 opt_date = !opt_date;
3085 case REQ_TOGGLE_AUTHOR:
3086 opt_author = !opt_author;
3090 case REQ_TOGGLE_REV_GRAPH:
3091 opt_rev_graph = !opt_rev_graph;
3095 case REQ_TOGGLE_REFS:
3096 opt_show_refs = !opt_show_refs;
3101 case REQ_SEARCH_BACK:
3102 search_view(view, request);
3107 find_next(view, request);
3110 case REQ_STOP_LOADING:
3111 for (i = 0; i < ARRAY_SIZE(views); i++) {
3114 report("Stopped loading the %s view", view->name),
3115 end_update(view, TRUE);
3119 case REQ_SHOW_VERSION:
3120 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3123 case REQ_SCREEN_RESIZE:
3126 case REQ_SCREEN_REDRAW:
3131 report("Nothing to edit");
3135 report("Nothing to enter");
3138 case REQ_VIEW_CLOSE:
3139 /* XXX: Mark closed views by letting view->parent point to the
3140 * view itself. Parents to closed view should never be
3143 view->parent->parent != view->parent) {
3144 memset(display, 0, sizeof(display));
3146 display[current_view] = view->parent;
3147 view->parent = view;
3158 report("Unknown key, press 'h' for help");
3171 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3173 char *text = line->data;
3175 if (opt_line_number && draw_lineno(view, lineno))
3178 draw_text(view, line->type, text, TRUE);
3183 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3185 const char *describe_argv[] = { "git", "describe", commit_id, NULL };
3186 char refbuf[SIZEOF_STR];
3189 if (run_io_buf(describe_argv, refbuf, sizeof(refbuf)))
3190 ref = chomp_string(refbuf);
3195 /* This is the only fatal call, since it can "corrupt" the buffer. */
3196 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
3203 add_pager_refs(struct view *view, struct line *line)
3205 char buf[SIZEOF_STR];
3206 char *commit_id = (char *)line->data + STRING_SIZE("commit ");
3208 size_t bufpos = 0, refpos = 0;
3209 const char *sep = "Refs: ";
3210 bool is_tag = FALSE;
3212 assert(line->type == LINE_COMMIT);
3214 refs = get_refs(commit_id);
3216 if (view == VIEW(REQ_VIEW_DIFF))
3217 goto try_add_describe_ref;
3222 struct ref *ref = refs[refpos];
3223 const char *fmt = ref->tag ? "%s[%s]" :
3224 ref->remote ? "%s<%s>" : "%s%s";
3226 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
3231 } while (refs[refpos++]->next);
3233 if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
3234 try_add_describe_ref:
3235 /* Add <tag>-g<commit_id> "fake" reference. */
3236 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
3243 if (!realloc_lines(view, view->line_size + 1))
3246 add_line_text(view, buf, LINE_PP_REFS);
3250 pager_read(struct view *view, char *data)
3257 line = add_line_text(view, data, get_line_type(data));
3261 if (line->type == LINE_COMMIT &&
3262 (view == VIEW(REQ_VIEW_DIFF) ||
3263 view == VIEW(REQ_VIEW_LOG)))
3264 add_pager_refs(view, line);
3270 pager_request(struct view *view, enum request request, struct line *line)
3274 if (request != REQ_ENTER)
3277 if (line->type == LINE_COMMIT &&
3278 (view == VIEW(REQ_VIEW_LOG) ||
3279 view == VIEW(REQ_VIEW_PAGER))) {
3280 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3284 /* Always scroll the view even if it was split. That way
3285 * you can use Enter to scroll through the log view and
3286 * split open each commit diff. */
3287 scroll_view(view, REQ_SCROLL_LINE_DOWN);
3289 /* FIXME: A minor workaround. Scrolling the view will call report("")
3290 * but if we are scrolling a non-current view this won't properly
3291 * update the view title. */
3293 update_view_title(view);
3299 pager_grep(struct view *view, struct line *line)
3302 char *text = line->data;
3307 if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
3314 pager_select(struct view *view, struct line *line)
3316 if (line->type == LINE_COMMIT) {
3317 char *text = (char *)line->data + STRING_SIZE("commit ");
3319 if (view != VIEW(REQ_VIEW_PAGER))
3320 string_copy_rev(view->ref, text);
3321 string_copy_rev(ref_commit, text);
3325 static struct view_ops pager_ops = {
3336 static const char *log_argv[SIZEOF_ARG] = {
3337 "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3341 log_request(struct view *view, enum request request, struct line *line)
3346 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
3349 return pager_request(view, request, line);
3353 static struct view_ops log_ops = {
3364 static const char *diff_argv[SIZEOF_ARG] = {
3365 "git", "show", "--pretty=fuller", "--no-color", "--root",
3366 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
3369 static struct view_ops diff_ops = {
3385 help_open(struct view *view)
3388 int lines = ARRAY_SIZE(req_info) + 2;
3391 if (view->lines > 0)
3394 for (i = 0; i < ARRAY_SIZE(req_info); i++)
3395 if (!req_info[i].request)
3398 lines += run_requests + 1;
3400 view->line = calloc(lines, sizeof(*view->line));
3404 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
3406 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
3409 if (req_info[i].request == REQ_NONE)
3412 if (!req_info[i].request) {
3413 add_line_text(view, "", LINE_DEFAULT);
3414 add_line_text(view, req_info[i].help, LINE_DEFAULT);
3418 key = get_key(req_info[i].request);
3420 key = "(no key defined)";
3422 if (!string_format(buf, " %-25s %s", key, req_info[i].help))
3425 add_line_text(view, buf, LINE_DEFAULT);
3429 add_line_text(view, "", LINE_DEFAULT);
3430 add_line_text(view, "External commands:", LINE_DEFAULT);
3433 for (i = 0; i < run_requests; i++) {
3434 struct run_request *req = get_run_request(REQ_NONE + i + 1);
3436 char cmd[SIZEOF_STR];
3443 key = get_key_name(req->key);
3445 key = "(no key defined)";
3447 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
3448 if (!string_format_from(cmd, &bufpos, "%s%s",
3449 argc ? " " : "", req->argv[argc]))
3452 if (!string_format(buf, " %-10s %-14s `%s`",
3453 keymap_table[req->keymap].name, key, cmd))
3456 add_line_text(view, buf, LINE_DEFAULT);
3462 static struct view_ops help_ops = {
3478 struct tree_stack_entry {
3479 struct tree_stack_entry *prev; /* Entry below this in the stack */
3480 unsigned long lineno; /* Line number to restore */
3481 char *name; /* Position of name in opt_path */
3484 /* The top of the path stack. */
3485 static struct tree_stack_entry *tree_stack = NULL;
3486 unsigned long tree_lineno = 0;
3489 pop_tree_stack_entry(void)
3491 struct tree_stack_entry *entry = tree_stack;
3493 tree_lineno = entry->lineno;
3495 tree_stack = entry->prev;
3500 push_tree_stack_entry(const char *name, unsigned long lineno)
3502 struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
3503 size_t pathlen = strlen(opt_path);
3508 entry->prev = tree_stack;
3509 entry->name = opt_path + pathlen;
3512 if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
3513 pop_tree_stack_entry();
3517 /* Move the current line to the first tree entry. */
3519 entry->lineno = lineno;
3522 /* Parse output from git-ls-tree(1):
3524 * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile
3525 * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README
3526 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
3527 * 100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38 web.conf
3530 #define SIZEOF_TREE_ATTR \
3531 STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t")
3533 #define TREE_UP_FORMAT "040000 tree %s\t.."
3536 tree_compare_entry(enum line_type type1, const char *name1,
3537 enum line_type type2, const char *name2)
3539 if (type1 != type2) {
3540 if (type1 == LINE_TREE_DIR)
3545 return strcmp(name1, name2);
3549 tree_path(struct line *line)
3551 const char *path = line->data;
3553 return path + SIZEOF_TREE_ATTR;
3557 tree_read(struct view *view, char *text)
3559 size_t textlen = text ? strlen(text) : 0;
3560 char buf[SIZEOF_STR];
3562 enum line_type type;
3563 bool first_read = view->lines == 0;
3567 if (textlen <= SIZEOF_TREE_ATTR)
3570 type = text[STRING_SIZE("100644 ")] == 't'
3571 ? LINE_TREE_DIR : LINE_TREE_FILE;
3574 /* Add path info line */
3575 if (!string_format(buf, "Directory path /%s", opt_path) ||
3576 !realloc_lines(view, view->line_size + 1) ||
3577 !add_line_text(view, buf, LINE_DEFAULT))
3580 /* Insert "link" to parent directory. */
3582 if (!string_format(buf, TREE_UP_FORMAT, view->ref) ||
3583 !realloc_lines(view, view->line_size + 1) ||
3584 !add_line_text(view, buf, LINE_TREE_DIR))
3589 /* Strip the path part ... */
3591 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
3592 size_t striplen = strlen(opt_path);
3593 char *path = text + SIZEOF_TREE_ATTR;
3595 if (pathlen > striplen)
3596 memmove(path, path + striplen,
3597 pathlen - striplen + 1);
3600 /* Skip "Directory ..." and ".." line. */
3601 for (pos = 1 + !!*opt_path; pos < view->lines; pos++) {
3602 struct line *line = &view->line[pos];
3603 const char *path1 = tree_path(line);
3604 char *path2 = text + SIZEOF_TREE_ATTR;
3605 int cmp = tree_compare_entry(line->type, path1, type, path2);
3610 text = strdup(text);
3614 if (view->lines > pos)
3615 memmove(&view->line[pos + 1], &view->line[pos],
3616 (view->lines - pos) * sizeof(*line));
3618 line = &view->line[pos];
3625 if (!add_line_text(view, text, type))
3628 if (tree_lineno > view->lineno) {
3629 view->lineno = tree_lineno;
3637 tree_request(struct view *view, enum request request, struct line *line)
3639 enum open_flags flags;
3642 case REQ_VIEW_BLAME:
3643 if (line->type != LINE_TREE_FILE) {
3644 report("Blame only supported for files");
3648 string_copy(opt_ref, view->vid);
3652 if (line->type != LINE_TREE_FILE) {
3653 report("Edit only supported for files");
3654 } else if (!is_head_commit(view->vid)) {
3655 report("Edit only supported for files in the current work tree");
3657 open_editor(TRUE, opt_file);
3661 case REQ_TREE_PARENT:
3663 /* quit view if at top of tree */
3664 return REQ_VIEW_CLOSE;
3667 line = &view->line[1];
3677 /* Cleanup the stack if the tree view is at a different tree. */
3678 while (!*opt_path && tree_stack)
3679 pop_tree_stack_entry();
3681 switch (line->type) {
3683 /* Depending on whether it is a subdir or parent (updir?) link
3684 * mangle the path buffer. */
3685 if (line == &view->line[1] && *opt_path) {
3686 pop_tree_stack_entry();
3689 const char *basename = tree_path(line);
3691 push_tree_stack_entry(basename, view->lineno);
3694 /* Trees and subtrees share the same ID, so they are not not
3695 * unique like blobs. */
3696 flags = OPEN_RELOAD;
3697 request = REQ_VIEW_TREE;
3700 case LINE_TREE_FILE:
3701 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
3702 request = REQ_VIEW_BLOB;
3709 open_view(view, request, flags);
3710 if (request == REQ_VIEW_TREE) {
3711 view->lineno = tree_lineno;
3718 tree_select(struct view *view, struct line *line)
3720 char *text = (char *)line->data + STRING_SIZE("100644 blob ");
3722 if (line->type == LINE_TREE_FILE) {
3723 string_copy_rev(ref_blob, text);
3724 string_format(opt_file, "%s%s", opt_path, tree_path(line));
3726 } else if (line->type != LINE_TREE_DIR) {
3730 string_copy_rev(view->ref, text);
3733 static const char *tree_argv[SIZEOF_ARG] = {
3734 "git", "ls-tree", "%(commit)", "%(directory)", NULL
3737 static struct view_ops tree_ops = {
3749 blob_read(struct view *view, char *line)
3753 return add_line_text(view, line, LINE_DEFAULT) != NULL;
3756 static const char *blob_argv[SIZEOF_ARG] = {
3757 "git", "cat-file", "blob", "%(blob)", NULL
3760 static struct view_ops blob_ops = {
3774 * Loading the blame view is a two phase job:
3776 * 1. File content is read either using opt_file from the
3777 * filesystem or using git-cat-file.
3778 * 2. Then blame information is incrementally added by
3779 * reading output from git-blame.
3782 static const char *blame_head_argv[] = {
3783 "git", "blame", "--incremental", "--", "%(file)", NULL
3786 static const char *blame_ref_argv[] = {
3787 "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
3790 static const char *blame_cat_file_argv[] = {
3791 "git", "cat-file", "blob", "%(ref):%(file)", NULL
3794 struct blame_commit {
3795 char id[SIZEOF_REV]; /* SHA1 ID. */
3796 char title[128]; /* First line of the commit message. */
3797 char author[75]; /* Author of the commit. */
3798 struct tm time; /* Date from the author ident. */
3799 char filename[128]; /* Name of file. */
3803 struct blame_commit *commit;
3808 blame_open(struct view *view)
3810 if (*opt_ref || !init_io_fd(&view->io, fopen(opt_file, "r"))) {
3811 if (!run_io_rd(&view->io, blame_cat_file_argv, FORMAT_ALL))
3815 setup_update(view, opt_file);
3816 string_format(view->ref, "%s ...", opt_file);
3821 static struct blame_commit *
3822 get_blame_commit(struct view *view, const char *id)
3826 for (i = 0; i < view->lines; i++) {
3827 struct blame *blame = view->line[i].data;
3832 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
3833 return blame->commit;
3837 struct blame_commit *commit = calloc(1, sizeof(*commit));
3840 string_ncopy(commit->id, id, SIZEOF_REV);
3846 parse_number(const char **posref, size_t *number, size_t min, size_t max)
3848 const char *pos = *posref;
3851 pos = strchr(pos + 1, ' ');
3852 if (!pos || !isdigit(pos[1]))
3854 *number = atoi(pos + 1);
3855 if (*number < min || *number > max)
3862 static struct blame_commit *
3863 parse_blame_commit(struct view *view, const char *text, int *blamed)
3865 struct blame_commit *commit;
3866 struct blame *blame;
3867 const char *pos = text + SIZEOF_REV - 1;
3871 if (strlen(text) <= SIZEOF_REV || *pos != ' ')
3874 if (!parse_number(&pos, &lineno, 1, view->lines) ||
3875 !parse_number(&pos, &group, 1, view->lines - lineno + 1))
3878 commit = get_blame_commit(view, text);
3884 struct line *line = &view->line[lineno + group - 1];
3887 blame->commit = commit;
3895 blame_read_file(struct view *view, const char *line, bool *read_file)
3898 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
3901 if (view->lines == 0 && !view->parent)
3902 die("No blame exist for %s", view->vid);
3904 if (view->lines == 0 || !run_io_rd(&io, argv, FORMAT_ALL)) {
3905 report("Failed to load blame data");
3909 done_io(view->pipe);
3915 size_t linelen = strlen(line);
3916 struct blame *blame = malloc(sizeof(*blame) + linelen);
3918 blame->commit = NULL;
3919 strncpy(blame->text, line, linelen);
3920 blame->text[linelen] = 0;
3921 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
3926 match_blame_header(const char *name, char **line)
3928 size_t namelen = strlen(name);
3929 bool matched = !strncmp(name, *line, namelen);
3938 blame_read(struct view *view, char *line)
3940 static struct blame_commit *commit = NULL;
3941 static int blamed = 0;
3942 static time_t author_time;
3943 static bool read_file = TRUE;
3946 return blame_read_file(view, line, &read_file);
3953 string_format(view->ref, "%s", view->vid);
3954 if (view_is_displayed(view)) {
3955 update_view_title(view);
3956 redraw_view_from(view, 0);
3962 commit = parse_blame_commit(view, line, &blamed);
3963 string_format(view->ref, "%s %2d%%", view->vid,
3964 blamed * 100 / view->lines);
3966 } else if (match_blame_header("author ", &line)) {
3967 string_ncopy(commit->author, line, strlen(line));
3969 } else if (match_blame_header("author-time ", &line)) {
3970 author_time = (time_t) atol(line);
3972 } else if (match_blame_header("author-tz ", &line)) {
3975 tz = ('0' - line[1]) * 60 * 60 * 10;
3976 tz += ('0' - line[2]) * 60 * 60;
3977 tz += ('0' - line[3]) * 60;
3978 tz += ('0' - line[4]) * 60;
3984 gmtime_r(&author_time, &commit->time);
3986 } else if (match_blame_header("summary ", &line)) {
3987 string_ncopy(commit->title, line, strlen(line));
3989 } else if (match_blame_header("filename ", &line)) {
3990 string_ncopy(commit->filename, line, strlen(line));
3998 blame_draw(struct view *view, struct line *line, unsigned int lineno)
4000 struct blame *blame = line->data;
4001 struct tm *time = NULL;
4002 const char *id = NULL, *author = NULL;
4004 if (blame->commit && *blame->commit->filename) {
4005 id = blame->commit->id;
4006 author = blame->commit->author;
4007 time = &blame->commit->time;
4010 if (opt_date && draw_date(view, time))
4014 draw_field(view, LINE_MAIN_AUTHOR, author, opt_author_cols, TRUE))
4017 if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
4020 if (draw_lineno(view, lineno))
4023 draw_text(view, LINE_DEFAULT, blame->text, TRUE);
4028 blame_request(struct view *view, enum request request, struct line *line)
4030 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4031 struct blame *blame = line->data;
4034 case REQ_VIEW_BLAME:
4035 if (!blame->commit || !strcmp(blame->commit->id, NULL_ID)) {
4036 report("Commit ID unknown");
4039 string_copy(opt_ref, blame->commit->id);
4040 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4044 if (!blame->commit) {
4045 report("No commit loaded yet");
4049 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
4050 !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
4053 if (!strcmp(blame->commit->id, NULL_ID)) {
4054 struct view *diff = VIEW(REQ_VIEW_DIFF);
4055 const char *diff_index_argv[] = {
4056 "git", "diff-index", "--root", "--cached",
4057 "--patch-with-stat", "-C", "-M",
4058 "HEAD", "--", view->vid, NULL
4061 if (!prepare_update(diff, diff_index_argv, NULL, FORMAT_DASH)) {
4062 report("Failed to allocate diff command");
4065 flags |= OPEN_PREPARED;
4068 open_view(view, REQ_VIEW_DIFF, flags);
4079 blame_grep(struct view *view, struct line *line)
4081 struct blame *blame = line->data;
4082 struct blame_commit *commit = blame->commit;
4085 #define MATCH(text, on) \
4086 (on && *text && regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4089 char buf[DATE_COLS + 1];
4091 if (MATCH(commit->title, 1) ||
4092 MATCH(commit->author, opt_author) ||
4093 MATCH(commit->id, opt_date))
4096 if (strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time) &&
4101 return MATCH(blame->text, 1);
4107 blame_select(struct view *view, struct line *line)
4109 struct blame *blame = line->data;
4110 struct blame_commit *commit = blame->commit;
4115 if (!strcmp(commit->id, NULL_ID))
4116 string_ncopy(ref_commit, "HEAD", 4);
4118 string_copy_rev(ref_commit, commit->id);
4121 static struct view_ops blame_ops = {
4140 char rev[SIZEOF_REV];
4141 char name[SIZEOF_STR];
4145 char rev[SIZEOF_REV];
4146 char name[SIZEOF_STR];
4150 static char status_onbranch[SIZEOF_STR];
4151 static struct status stage_status;
4152 static enum line_type stage_line_type;
4153 static size_t stage_chunks;
4154 static int *stage_chunk;
4156 /* This should work even for the "On branch" line. */
4158 status_has_none(struct view *view, struct line *line)
4160 return line < view->line + view->lines && !line[1].data;
4163 /* Get fields from the diff line:
4164 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
4167 status_get_diff(struct status *file, const char *buf, size_t bufsize)
4169 const char *old_mode = buf + 1;
4170 const char *new_mode = buf + 8;
4171 const char *old_rev = buf + 15;
4172 const char *new_rev = buf + 56;
4173 const char *status = buf + 97;
4176 old_mode[-1] != ':' ||
4177 new_mode[-1] != ' ' ||
4178 old_rev[-1] != ' ' ||
4179 new_rev[-1] != ' ' ||
4183 file->status = *status;
4185 string_copy_rev(file->old.rev, old_rev);
4186 string_copy_rev(file->new.rev, new_rev);
4188 file->old.mode = strtoul(old_mode, NULL, 8);
4189 file->new.mode = strtoul(new_mode, NULL, 8);
4191 file->old.name[0] = file->new.name[0] = 0;
4197 status_run(struct view *view, const char *argv[], char status, enum line_type type)
4199 struct status *file = NULL;
4200 struct status *unmerged = NULL;
4201 char buf[SIZEOF_STR * 4];
4205 if (!run_io(&io, argv, NULL, IO_RD))
4208 add_line_data(view, NULL, type);
4210 while (!io_eof(&io)) {
4214 readsize = io_read(&io, buf + bufsize, sizeof(buf) - bufsize);
4217 bufsize += readsize;
4219 /* Process while we have NUL chars. */
4220 while ((sep = memchr(buf, 0, bufsize))) {
4221 size_t sepsize = sep - buf + 1;
4224 if (!realloc_lines(view, view->line_size + 1))
4227 file = calloc(1, sizeof(*file));
4231 add_line_data(view, file, type);
4234 /* Parse diff info part. */
4236 file->status = status;
4238 string_copy(file->old.rev, NULL_ID);
4240 } else if (!file->status) {
4241 if (!status_get_diff(file, buf, sepsize))
4245 memmove(buf, sep + 1, bufsize);
4247 sep = memchr(buf, 0, bufsize);
4250 sepsize = sep - buf + 1;
4252 /* Collapse all 'M'odified entries that
4253 * follow a associated 'U'nmerged entry.
4255 if (file->status == 'U') {
4258 } else if (unmerged) {
4259 int collapse = !strcmp(buf, unmerged->new.name);
4270 /* Grab the old name for rename/copy. */
4271 if (!*file->old.name &&
4272 (file->status == 'R' || file->status == 'C')) {
4273 sepsize = sep - buf + 1;
4274 string_ncopy(file->old.name, buf, sepsize);
4276 memmove(buf, sep + 1, bufsize);
4278 sep = memchr(buf, 0, bufsize);
4281 sepsize = sep - buf + 1;
4284 /* git-ls-files just delivers a NUL separated
4285 * list of file names similar to the second half
4286 * of the git-diff-* output. */
4287 string_ncopy(file->new.name, buf, sepsize);
4288 if (!*file->old.name)
4289 string_copy(file->old.name, file->new.name);
4291 memmove(buf, sep + 1, bufsize);
4296 if (io_error(&io)) {
4302 if (!view->line[view->lines - 1].data)
4303 add_line_data(view, NULL, LINE_STAT_NONE);
4309 /* Don't show unmerged entries in the staged section. */
4310 static const char *status_diff_index_argv[] = {
4311 "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
4312 "--cached", "-M", "HEAD", NULL
4315 static const char *status_diff_files_argv[] = {
4316 "git", "diff-files", "-z", NULL
4319 static const char *status_list_other_argv[] = {
4320 "git", "ls-files", "-z", "--others", "--exclude-standard", NULL
4323 static const char *status_list_no_head_argv[] = {
4324 "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
4327 static const char *update_index_argv[] = {
4328 "git", "update-index", "-q", "--unmerged", "--refresh", NULL
4331 /* First parse staged info using git-diff-index(1), then parse unstaged
4332 * info using git-diff-files(1), and finally untracked files using
4333 * git-ls-files(1). */
4335 status_open(struct view *view)
4337 unsigned long prev_lineno = view->lineno;
4341 if (!realloc_lines(view, view->line_size + 7))
4344 add_line_data(view, NULL, LINE_STAT_HEAD);
4345 if (is_initial_commit())
4346 string_copy(status_onbranch, "Initial commit");
4347 else if (!*opt_head)
4348 string_copy(status_onbranch, "Not currently on any branch");
4349 else if (!string_format(status_onbranch, "On branch %s", opt_head))
4352 run_io_bg(update_index_argv);
4354 if (is_initial_commit()) {
4355 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
4357 } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
4361 if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
4362 !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
4365 /* If all went well restore the previous line number to stay in
4366 * the context or select a line with something that can be
4368 if (prev_lineno >= view->lines)
4369 prev_lineno = view->lines - 1;
4370 while (prev_lineno < view->lines && !view->line[prev_lineno].data)
4372 while (prev_lineno > 0 && !view->line[prev_lineno].data)
4375 /* If the above fails, always skip the "On branch" line. */
4376 if (prev_lineno < view->lines)
4377 view->lineno = prev_lineno;
4381 if (view->lineno < view->offset)
4382 view->offset = view->lineno;
4383 else if (view->offset + view->height <= view->lineno)
4384 view->offset = view->lineno - view->height + 1;
4390 status_draw(struct view *view, struct line *line, unsigned int lineno)
4392 struct status *status = line->data;
4393 enum line_type type;
4397 switch (line->type) {
4398 case LINE_STAT_STAGED:
4399 type = LINE_STAT_SECTION;
4400 text = "Changes to be committed:";
4403 case LINE_STAT_UNSTAGED:
4404 type = LINE_STAT_SECTION;
4405 text = "Changed but not updated:";
4408 case LINE_STAT_UNTRACKED:
4409 type = LINE_STAT_SECTION;
4410 text = "Untracked files:";
4413 case LINE_STAT_NONE:
4414 type = LINE_DEFAULT;
4415 text = " (no files)";
4418 case LINE_STAT_HEAD:
4419 type = LINE_STAT_HEAD;
4420 text = status_onbranch;
4427 static char buf[] = { '?', ' ', ' ', ' ', 0 };
4429 buf[0] = status->status;
4430 if (draw_text(view, line->type, buf, TRUE))
4432 type = LINE_DEFAULT;
4433 text = status->new.name;
4436 draw_text(view, type, text, TRUE);
4441 status_enter(struct view *view, struct line *line)
4443 struct status *status = line->data;
4444 const char *oldpath = status ? status->old.name : NULL;
4445 /* Diffs for unmerged entries are empty when passing the new
4446 * path, so leave it empty. */
4447 const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
4449 enum open_flags split;
4450 struct view *stage = VIEW(REQ_VIEW_STAGE);
4452 if (line->type == LINE_STAT_NONE ||
4453 (!status && line[1].type == LINE_STAT_NONE)) {
4454 report("No file to diff");
4458 switch (line->type) {
4459 case LINE_STAT_STAGED:
4460 if (is_initial_commit()) {
4461 const char *no_head_diff_argv[] = {
4462 "git", "diff", "--no-color", "--patch-with-stat",
4463 "--", "/dev/null", newpath, NULL
4466 if (!prepare_update(stage, no_head_diff_argv, opt_cdup, FORMAT_DASH))
4469 const char *index_show_argv[] = {
4470 "git", "diff-index", "--root", "--patch-with-stat",
4471 "-C", "-M", "--cached", "HEAD", "--",
4472 oldpath, newpath, NULL
4475 if (!prepare_update(stage, index_show_argv, opt_cdup, FORMAT_DASH))
4480 info = "Staged changes to %s";
4482 info = "Staged changes";
4485 case LINE_STAT_UNSTAGED:
4487 const char *files_show_argv[] = {
4488 "git", "diff-files", "--root", "--patch-with-stat",
4489 "-C", "-M", "--", oldpath, newpath, NULL
4492 if (!prepare_update(stage, files_show_argv, opt_cdup, FORMAT_DASH))
4495 info = "Unstaged changes to %s";
4497 info = "Unstaged changes";
4500 case LINE_STAT_UNTRACKED:
4502 report("No file to show");
4506 if (!suffixcmp(status->new.name, -1, "/")) {
4507 report("Cannot display a directory");
4511 if (!prepare_update_file(stage, newpath))
4513 info = "Untracked file %s";
4516 case LINE_STAT_HEAD:
4520 die("line type %d not handled in switch", line->type);
4523 split = view_is_displayed(view) ? OPEN_SPLIT : 0;
4524 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH | split);
4525 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
4527 stage_status = *status;
4529 memset(&stage_status, 0, sizeof(stage_status));
4532 stage_line_type = line->type;
4534 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
4541 status_exists(struct status *status, enum line_type type)
4543 struct view *view = VIEW(REQ_VIEW_STATUS);
4546 for (line = view->line; line < view->line + view->lines; line++) {
4547 struct status *pos = line->data;
4549 if (line->type == type && pos &&
4550 !strcmp(status->new.name, pos->new.name))
4559 status_update_prepare(struct io *io, enum line_type type)
4561 const char *staged_argv[] = {
4562 "git", "update-index", "-z", "--index-info", NULL
4564 const char *others_argv[] = {
4565 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
4569 case LINE_STAT_STAGED:
4570 return run_io(io, staged_argv, opt_cdup, IO_WR);
4572 case LINE_STAT_UNSTAGED:
4573 return run_io(io, others_argv, opt_cdup, IO_WR);
4575 case LINE_STAT_UNTRACKED:
4576 return run_io(io, others_argv, NULL, IO_WR);
4579 die("line type %d not handled in switch", type);
4585 status_update_write(struct io *io, struct status *status, enum line_type type)
4587 char buf[SIZEOF_STR];
4591 case LINE_STAT_STAGED:
4592 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
4595 status->old.name, 0))
4599 case LINE_STAT_UNSTAGED:
4600 case LINE_STAT_UNTRACKED:
4601 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
4606 die("line type %d not handled in switch", type);
4609 return io_write(io, buf, bufsize);
4613 status_update_file(struct status *status, enum line_type type)
4618 if (!status_update_prepare(&io, type))
4621 result = status_update_write(&io, status, type);
4627 status_update_files(struct view *view, struct line *line)
4631 struct line *pos = view->line + view->lines;
4635 if (!status_update_prepare(&io, line->type))
4638 for (pos = line; pos < view->line + view->lines && pos->data; pos++)
4641 for (file = 0, done = 0; result && file < files; line++, file++) {
4642 int almost_done = file * 100 / files;
4644 if (almost_done > done) {
4646 string_format(view->ref, "updating file %u of %u (%d%% done)",
4648 update_view_title(view);
4650 result = status_update_write(&io, line->data, line->type);
4658 status_update(struct view *view)
4660 struct line *line = &view->line[view->lineno];
4662 assert(view->lines);
4665 /* This should work even for the "On branch" line. */
4666 if (line < view->line + view->lines && !line[1].data) {
4667 report("Nothing to update");
4671 if (!status_update_files(view, line + 1)) {
4672 report("Failed to update file status");
4676 } else if (!status_update_file(line->data, line->type)) {
4677 report("Failed to update file status");
4685 status_revert(struct status *status, enum line_type type, bool has_none)
4687 if (!status || type != LINE_STAT_UNSTAGED) {
4688 if (type == LINE_STAT_STAGED) {
4689 report("Cannot revert changes to staged files");
4690 } else if (type == LINE_STAT_UNTRACKED) {
4691 report("Cannot revert changes to untracked files");
4692 } else if (has_none) {
4693 report("Nothing to revert");
4695 report("Cannot revert changes to multiple files");
4700 const char *checkout_argv[] = {
4701 "git", "checkout", "--", status->old.name, NULL
4704 if (!prompt_yesno("Are you sure you want to overwrite any changes?"))
4706 return run_io_fg(checkout_argv, opt_cdup);
4711 status_request(struct view *view, enum request request, struct line *line)
4713 struct status *status = line->data;
4716 case REQ_STATUS_UPDATE:
4717 if (!status_update(view))
4721 case REQ_STATUS_REVERT:
4722 if (!status_revert(status, line->type, status_has_none(view, line)))
4726 case REQ_STATUS_MERGE:
4727 if (!status || status->status != 'U') {
4728 report("Merging only possible for files with unmerged status ('U').");
4731 open_mergetool(status->new.name);
4737 if (status->status == 'D') {
4738 report("File has been deleted.");
4742 open_editor(status->status != '?', status->new.name);
4745 case REQ_VIEW_BLAME:
4747 string_copy(opt_file, status->new.name);
4753 /* After returning the status view has been split to
4754 * show the stage view. No further reloading is
4756 status_enter(view, line);
4760 /* Simply reload the view. */
4767 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
4773 status_select(struct view *view, struct line *line)
4775 struct status *status = line->data;
4776 char file[SIZEOF_STR] = "all files";
4780 if (status && !string_format(file, "'%s'", status->new.name))
4783 if (!status && line[1].type == LINE_STAT_NONE)
4786 switch (line->type) {
4787 case LINE_STAT_STAGED:
4788 text = "Press %s to unstage %s for commit";
4791 case LINE_STAT_UNSTAGED:
4792 text = "Press %s to stage %s for commit";
4795 case LINE_STAT_UNTRACKED:
4796 text = "Press %s to stage %s for addition";
4799 case LINE_STAT_HEAD:
4800 case LINE_STAT_NONE:
4801 text = "Nothing to update";
4805 die("line type %d not handled in switch", line->type);
4808 if (status && status->status == 'U') {
4809 text = "Press %s to resolve conflict in %s";
4810 key = get_key(REQ_STATUS_MERGE);
4813 key = get_key(REQ_STATUS_UPDATE);
4816 string_format(view->ref, text, key, file);
4820 status_grep(struct view *view, struct line *line)
4822 struct status *status = line->data;
4823 enum { S_STATUS, S_NAME, S_END } state;
4830 for (state = S_STATUS; state < S_END; state++) {
4834 case S_NAME: text = status->new.name; break;
4836 buf[0] = status->status;
4844 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4851 static struct view_ops status_ops = {
4864 stage_diff_write(struct io *io, struct line *line, struct line *end)
4866 while (line < end) {
4867 if (!io_write(io, line->data, strlen(line->data)) ||
4868 !io_write(io, "\n", 1))
4871 if (line->type == LINE_DIFF_CHUNK ||
4872 line->type == LINE_DIFF_HEADER)
4879 static struct line *
4880 stage_diff_find(struct view *view, struct line *line, enum line_type type)
4882 for (; view->line < line; line--)
4883 if (line->type == type)
4890 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
4892 const char *apply_argv[SIZEOF_ARG] = {
4893 "git", "apply", "--whitespace=nowarn", NULL
4895 struct line *diff_hdr;
4899 diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
4904 apply_argv[argc++] = "--cached";
4905 if (revert || stage_line_type == LINE_STAT_STAGED)
4906 apply_argv[argc++] = "-R";
4907 apply_argv[argc++] = "-";
4908 apply_argv[argc++] = NULL;
4909 if (!run_io(&io, apply_argv, opt_cdup, IO_WR))
4912 if (!stage_diff_write(&io, diff_hdr, chunk) ||
4913 !stage_diff_write(&io, chunk, view->line + view->lines))
4917 run_io_bg(update_index_argv);
4919 return chunk ? TRUE : FALSE;
4923 stage_update(struct view *view, struct line *line)
4925 struct line *chunk = NULL;
4927 if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
4928 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
4931 if (!stage_apply_chunk(view, chunk, FALSE)) {
4932 report("Failed to apply chunk");
4936 } else if (!stage_status.status) {
4937 view = VIEW(REQ_VIEW_STATUS);
4939 for (line = view->line; line < view->line + view->lines; line++)
4940 if (line->type == stage_line_type)
4943 if (!status_update_files(view, line + 1)) {
4944 report("Failed to update files");
4948 } else if (!status_update_file(&stage_status, stage_line_type)) {
4949 report("Failed to update file");
4957 stage_revert(struct view *view, struct line *line)
4959 struct line *chunk = NULL;
4961 if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
4962 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
4965 if (!prompt_yesno("Are you sure you want to revert changes?"))
4968 if (!stage_apply_chunk(view, chunk, TRUE)) {
4969 report("Failed to revert chunk");
4975 return status_revert(stage_status.status ? &stage_status : NULL,
4976 stage_line_type, FALSE);
4982 stage_next(struct view *view, struct line *line)
4986 if (!stage_chunks) {
4987 static size_t alloc = 0;
4990 for (line = view->line; line < view->line + view->lines; line++) {
4991 if (line->type != LINE_DIFF_CHUNK)
4994 tmp = realloc_items(stage_chunk, &alloc,
4995 stage_chunks, sizeof(*tmp));
4997 report("Allocation failure");
5002 stage_chunk[stage_chunks++] = line - view->line;
5006 for (i = 0; i < stage_chunks; i++) {
5007 if (stage_chunk[i] > view->lineno) {
5008 do_scroll_view(view, stage_chunk[i] - view->lineno);
5009 report("Chunk %d of %d", i + 1, stage_chunks);
5014 report("No next chunk found");
5018 stage_request(struct view *view, enum request request, struct line *line)
5021 case REQ_STATUS_UPDATE:
5022 if (!stage_update(view, line))
5026 case REQ_STATUS_REVERT:
5027 if (!stage_revert(view, line))
5031 case REQ_STAGE_NEXT:
5032 if (stage_line_type == LINE_STAT_UNTRACKED) {
5033 report("File is untracked; press %s to add",
5034 get_key(REQ_STATUS_UPDATE));
5037 stage_next(view, line);
5041 if (!stage_status.new.name[0])
5043 if (stage_status.status == 'D') {
5044 report("File has been deleted.");
5048 open_editor(stage_status.status != '?', stage_status.new.name);
5052 /* Reload everything ... */
5055 case REQ_VIEW_BLAME:
5056 if (stage_status.new.name[0]) {
5057 string_copy(opt_file, stage_status.new.name);
5063 return pager_request(view, request, line);
5069 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD | OPEN_NOMAXIMIZE);
5071 /* Check whether the staged entry still exists, and close the
5072 * stage view if it doesn't. */
5073 if (!status_exists(&stage_status, stage_line_type))
5074 return REQ_VIEW_CLOSE;
5076 if (stage_line_type == LINE_STAT_UNTRACKED) {
5077 if (!suffixcmp(stage_status.new.name, -1, "/")) {
5078 report("Cannot display a directory");
5082 if (!prepare_update_file(view, stage_status.new.name)) {
5083 report("Failed to open file: %s", strerror(errno));
5087 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
5092 static struct view_ops stage_ops = {
5109 char id[SIZEOF_REV]; /* SHA1 ID. */
5110 char title[128]; /* First line of the commit message. */
5111 char author[75]; /* Author of the commit. */
5112 struct tm time; /* Date from the author ident. */
5113 struct ref **refs; /* Repository references. */
5114 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
5115 size_t graph_size; /* The width of the graph array. */
5116 bool has_parents; /* Rewritten --parents seen. */
5119 /* Size of rev graph with no "padding" columns */
5120 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
5123 struct rev_graph *prev, *next, *parents;
5124 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
5126 struct commit *commit;
5128 unsigned int boundary:1;
5131 /* Parents of the commit being visualized. */
5132 static struct rev_graph graph_parents[4];
5134 /* The current stack of revisions on the graph. */
5135 static struct rev_graph graph_stacks[4] = {
5136 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
5137 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
5138 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
5139 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
5143 graph_parent_is_merge(struct rev_graph *graph)
5145 return graph->parents->size > 1;
5149 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
5151 struct commit *commit = graph->commit;
5153 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
5154 commit->graph[commit->graph_size++] = symbol;
5158 clear_rev_graph(struct rev_graph *graph)
5160 graph->boundary = 0;
5161 graph->size = graph->pos = 0;
5162 graph->commit = NULL;
5163 memset(graph->parents, 0, sizeof(*graph->parents));
5167 done_rev_graph(struct rev_graph *graph)
5169 if (graph_parent_is_merge(graph) &&
5170 graph->pos < graph->size - 1 &&
5171 graph->next->size == graph->size + graph->parents->size - 1) {
5172 size_t i = graph->pos + graph->parents->size - 1;
5174 graph->commit->graph_size = i * 2;
5175 while (i < graph->next->size - 1) {
5176 append_to_rev_graph(graph, ' ');
5177 append_to_rev_graph(graph, '\\');
5182 clear_rev_graph(graph);
5186 push_rev_graph(struct rev_graph *graph, const char *parent)
5190 /* "Collapse" duplicate parents lines.
5192 * FIXME: This needs to also update update the drawn graph but
5193 * for now it just serves as a method for pruning graph lines. */
5194 for (i = 0; i < graph->size; i++)
5195 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
5198 if (graph->size < SIZEOF_REVITEMS) {
5199 string_copy_rev(graph->rev[graph->size++], parent);
5204 get_rev_graph_symbol(struct rev_graph *graph)
5208 if (graph->boundary)
5209 symbol = REVGRAPH_BOUND;
5210 else if (graph->parents->size == 0)
5211 symbol = REVGRAPH_INIT;
5212 else if (graph_parent_is_merge(graph))
5213 symbol = REVGRAPH_MERGE;
5214 else if (graph->pos >= graph->size)
5215 symbol = REVGRAPH_BRANCH;
5217 symbol = REVGRAPH_COMMIT;
5223 draw_rev_graph(struct rev_graph *graph)
5226 chtype separator, line;
5228 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
5229 static struct rev_filler fillers[] = {
5235 chtype symbol = get_rev_graph_symbol(graph);
5236 struct rev_filler *filler;
5239 if (opt_line_graphics)
5240 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
5242 filler = &fillers[DEFAULT];
5244 for (i = 0; i < graph->pos; i++) {
5245 append_to_rev_graph(graph, filler->line);
5246 if (graph_parent_is_merge(graph->prev) &&
5247 graph->prev->pos == i)
5248 filler = &fillers[RSHARP];
5250 append_to_rev_graph(graph, filler->separator);
5253 /* Place the symbol for this revision. */
5254 append_to_rev_graph(graph, symbol);
5256 if (graph->prev->size > graph->size)
5257 filler = &fillers[RDIAG];
5259 filler = &fillers[DEFAULT];
5263 for (; i < graph->size; i++) {
5264 append_to_rev_graph(graph, filler->separator);
5265 append_to_rev_graph(graph, filler->line);
5266 if (graph_parent_is_merge(graph->prev) &&
5267 i < graph->prev->pos + graph->parents->size)
5268 filler = &fillers[RSHARP];
5269 if (graph->prev->size > graph->size)
5270 filler = &fillers[LDIAG];
5273 if (graph->prev->size > graph->size) {
5274 append_to_rev_graph(graph, filler->separator);
5275 if (filler->line != ' ')
5276 append_to_rev_graph(graph, filler->line);
5280 /* Prepare the next rev graph */
5282 prepare_rev_graph(struct rev_graph *graph)
5286 /* First, traverse all lines of revisions up to the active one. */
5287 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
5288 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
5291 push_rev_graph(graph->next, graph->rev[graph->pos]);
5294 /* Interleave the new revision parent(s). */
5295 for (i = 0; !graph->boundary && i < graph->parents->size; i++)
5296 push_rev_graph(graph->next, graph->parents->rev[i]);
5298 /* Lastly, put any remaining revisions. */
5299 for (i = graph->pos + 1; i < graph->size; i++)
5300 push_rev_graph(graph->next, graph->rev[i]);
5304 update_rev_graph(struct rev_graph *graph)
5306 /* If this is the finalizing update ... */
5308 prepare_rev_graph(graph);
5310 /* Graph visualization needs a one rev look-ahead,
5311 * so the first update doesn't visualize anything. */
5312 if (!graph->prev->commit)
5315 draw_rev_graph(graph->prev);
5316 done_rev_graph(graph->prev->prev);
5324 static const char *main_argv[SIZEOF_ARG] = {
5325 "git", "log", "--no-color", "--pretty=raw", "--parents",
5326 "--topo-order", "%(head)", NULL
5330 main_draw(struct view *view, struct line *line, unsigned int lineno)
5332 struct commit *commit = line->data;
5334 if (!*commit->author)
5337 if (opt_date && draw_date(view, &commit->time))
5341 draw_field(view, LINE_MAIN_AUTHOR, commit->author, opt_author_cols, TRUE))
5344 if (opt_rev_graph && commit->graph_size &&
5345 draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
5348 if (opt_show_refs && commit->refs) {
5352 enum line_type type;
5354 if (commit->refs[i]->head)
5355 type = LINE_MAIN_HEAD;
5356 else if (commit->refs[i]->ltag)
5357 type = LINE_MAIN_LOCAL_TAG;
5358 else if (commit->refs[i]->tag)
5359 type = LINE_MAIN_TAG;
5360 else if (commit->refs[i]->tracked)
5361 type = LINE_MAIN_TRACKED;
5362 else if (commit->refs[i]->remote)
5363 type = LINE_MAIN_REMOTE;
5365 type = LINE_MAIN_REF;
5367 if (draw_text(view, type, "[", TRUE) ||
5368 draw_text(view, type, commit->refs[i]->name, TRUE) ||
5369 draw_text(view, type, "]", TRUE))
5372 if (draw_text(view, LINE_DEFAULT, " ", TRUE))
5374 } while (commit->refs[i++]->next);
5377 draw_text(view, LINE_DEFAULT, commit->title, TRUE);
5381 /* Reads git log --pretty=raw output and parses it into the commit struct. */
5383 main_read(struct view *view, char *line)
5385 static struct rev_graph *graph = graph_stacks;
5386 enum line_type type;
5387 struct commit *commit;
5392 if (!view->lines && !view->parent)
5393 die("No revisions match the given arguments.");
5394 if (view->lines > 0) {
5395 commit = view->line[view->lines - 1].data;
5396 if (!*commit->author) {
5399 graph->commit = NULL;
5402 update_rev_graph(graph);
5404 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
5405 clear_rev_graph(&graph_stacks[i]);
5409 type = get_line_type(line);
5410 if (type == LINE_COMMIT) {
5411 commit = calloc(1, sizeof(struct commit));
5415 line += STRING_SIZE("commit ");
5417 graph->boundary = 1;
5421 string_copy_rev(commit->id, line);
5422 commit->refs = get_refs(commit->id);
5423 graph->commit = commit;
5424 add_line_data(view, commit, LINE_MAIN_COMMIT);
5426 while ((line = strchr(line, ' '))) {
5428 push_rev_graph(graph->parents, line);
5429 commit->has_parents = TRUE;
5436 commit = view->line[view->lines - 1].data;
5440 if (commit->has_parents)
5442 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
5447 /* Parse author lines where the name may be empty:
5448 * author <email@address.tld> 1138474660 +0100
5450 char *ident = line + STRING_SIZE("author ");
5451 char *nameend = strchr(ident, '<');
5452 char *emailend = strchr(ident, '>');
5454 if (!nameend || !emailend)
5457 update_rev_graph(graph);
5458 graph = graph->next;
5460 *nameend = *emailend = 0;
5461 ident = chomp_string(ident);
5463 ident = chomp_string(nameend + 1);
5468 string_ncopy(commit->author, ident, strlen(ident));
5470 /* Parse epoch and timezone */
5471 if (emailend[1] == ' ') {
5472 char *secs = emailend + 2;
5473 char *zone = strchr(secs, ' ');
5474 time_t time = (time_t) atol(secs);
5476 if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
5480 tz = ('0' - zone[1]) * 60 * 60 * 10;
5481 tz += ('0' - zone[2]) * 60 * 60;
5482 tz += ('0' - zone[3]) * 60;
5483 tz += ('0' - zone[4]) * 60;
5491 gmtime_r(&time, &commit->time);
5496 /* Fill in the commit title if it has not already been set. */
5497 if (commit->title[0])
5500 /* Require titles to start with a non-space character at the
5501 * offset used by git log. */
5502 if (strncmp(line, " ", 4))
5505 /* Well, if the title starts with a whitespace character,
5506 * try to be forgiving. Otherwise we end up with no title. */
5507 while (isspace(*line))
5511 /* FIXME: More graceful handling of titles; append "..." to
5512 * shortened titles, etc. */
5514 string_ncopy(commit->title, line, strlen(line));
5521 main_request(struct view *view, enum request request, struct line *line)
5523 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
5527 open_view(view, REQ_VIEW_DIFF, flags);
5531 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
5541 grep_refs(struct ref **refs, regex_t *regex)
5549 if (regexec(regex, refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
5551 } while (refs[i++]->next);
5557 main_grep(struct view *view, struct line *line)
5559 struct commit *commit = line->data;
5560 enum { S_TITLE, S_AUTHOR, S_DATE, S_REFS, S_END } state;
5561 char buf[DATE_COLS + 1];
5564 for (state = S_TITLE; state < S_END; state++) {
5568 case S_TITLE: text = commit->title; break;
5572 text = commit->author;
5577 if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
5584 if (grep_refs(commit->refs, view->regex) == TRUE)
5591 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
5599 main_select(struct view *view, struct line *line)
5601 struct commit *commit = line->data;
5603 string_copy_rev(view->ref, commit->id);
5604 string_copy_rev(ref_commit, view->ref);
5607 static struct view_ops main_ops = {
5620 * Unicode / UTF-8 handling
5622 * NOTE: Much of the following code for dealing with unicode is derived from
5623 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
5624 * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
5627 /* I've (over)annotated a lot of code snippets because I am not entirely
5628 * confident that the approach taken by this small UTF-8 interface is correct.
5632 unicode_width(unsigned long c)
5635 (c <= 0x115f /* Hangul Jamo */
5638 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
5640 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
5641 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
5642 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
5643 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
5644 || (c >= 0xffe0 && c <= 0xffe6)
5645 || (c >= 0x20000 && c <= 0x2fffd)
5646 || (c >= 0x30000 && c <= 0x3fffd)))
5650 return opt_tab_size;
5655 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
5656 * Illegal bytes are set one. */
5657 static const unsigned char utf8_bytes[256] = {
5658 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,
5659 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,
5660 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,
5661 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,
5662 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,
5663 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,
5664 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,
5665 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,
5668 /* Decode UTF-8 multi-byte representation into a unicode character. */
5669 static inline unsigned long
5670 utf8_to_unicode(const char *string, size_t length)
5672 unsigned long unicode;
5676 unicode = string[0];
5679 unicode = (string[0] & 0x1f) << 6;
5680 unicode += (string[1] & 0x3f);
5683 unicode = (string[0] & 0x0f) << 12;
5684 unicode += ((string[1] & 0x3f) << 6);
5685 unicode += (string[2] & 0x3f);
5688 unicode = (string[0] & 0x0f) << 18;
5689 unicode += ((string[1] & 0x3f) << 12);
5690 unicode += ((string[2] & 0x3f) << 6);
5691 unicode += (string[3] & 0x3f);
5694 unicode = (string[0] & 0x0f) << 24;
5695 unicode += ((string[1] & 0x3f) << 18);
5696 unicode += ((string[2] & 0x3f) << 12);
5697 unicode += ((string[3] & 0x3f) << 6);
5698 unicode += (string[4] & 0x3f);
5701 unicode = (string[0] & 0x01) << 30;
5702 unicode += ((string[1] & 0x3f) << 24);
5703 unicode += ((string[2] & 0x3f) << 18);
5704 unicode += ((string[3] & 0x3f) << 12);
5705 unicode += ((string[4] & 0x3f) << 6);
5706 unicode += (string[5] & 0x3f);
5709 die("Invalid unicode length");
5712 /* Invalid characters could return the special 0xfffd value but NUL
5713 * should be just as good. */
5714 return unicode > 0xffff ? 0 : unicode;
5717 /* Calculates how much of string can be shown within the given maximum width
5718 * and sets trimmed parameter to non-zero value if all of string could not be
5719 * shown. If the reserve flag is TRUE, it will reserve at least one
5720 * trailing character, which can be useful when drawing a delimiter.
5722 * Returns the number of bytes to output from string to satisfy max_width. */
5724 utf8_length(const char *string, int *width, size_t max_width, int *trimmed, bool reserve)
5726 const char *start = string;
5727 const char *end = strchr(string, '\0');
5728 unsigned char last_bytes = 0;
5729 size_t last_ucwidth = 0;
5734 while (string < end) {
5735 int c = *(unsigned char *) string;
5736 unsigned char bytes = utf8_bytes[c];
5738 unsigned long unicode;
5740 if (string + bytes > end)
5743 /* Change representation to figure out whether
5744 * it is a single- or double-width character. */
5746 unicode = utf8_to_unicode(string, bytes);
5747 /* FIXME: Graceful handling of invalid unicode character. */
5751 ucwidth = unicode_width(unicode);
5753 if (*width > max_width) {
5756 if (reserve && *width == max_width) {
5757 string -= last_bytes;
5758 *width -= last_ucwidth;
5765 last_ucwidth = ucwidth;
5768 return string - start;
5776 /* Whether or not the curses interface has been initialized. */
5777 static bool cursed = FALSE;
5779 /* The status window is used for polling keystrokes. */
5780 static WINDOW *status_win;
5782 static bool status_empty = TRUE;
5784 /* Update status and title window. */
5786 report(const char *msg, ...)
5788 struct view *view = display[current_view];
5794 char buf[SIZEOF_STR];
5797 va_start(args, msg);
5798 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
5799 buf[sizeof(buf) - 1] = 0;
5800 buf[sizeof(buf) - 2] = '.';
5801 buf[sizeof(buf) - 3] = '.';
5802 buf[sizeof(buf) - 4] = '.';
5808 if (!status_empty || *msg) {
5811 va_start(args, msg);
5813 wmove(status_win, 0, 0);
5815 vwprintw(status_win, msg, args);
5816 status_empty = FALSE;
5818 status_empty = TRUE;
5820 wclrtoeol(status_win);
5821 wrefresh(status_win);
5826 update_view_title(view);
5827 update_display_cursor(view);
5830 /* Controls when nodelay should be in effect when polling user input. */
5832 set_nonblocking_input(bool loading)
5834 static unsigned int loading_views;
5836 if ((loading == FALSE && loading_views-- == 1) ||
5837 (loading == TRUE && loading_views++ == 0))
5838 nodelay(status_win, loading);
5846 /* Initialize the curses library */
5847 if (isatty(STDIN_FILENO)) {
5848 cursed = !!initscr();
5851 /* Leave stdin and stdout alone when acting as a pager. */
5852 opt_tty = fopen("/dev/tty", "r+");
5854 die("Failed to open /dev/tty");
5855 cursed = !!newterm(NULL, opt_tty, opt_tty);
5859 die("Failed to initialize curses");
5861 nonl(); /* Tell curses not to do NL->CR/NL on output */
5862 cbreak(); /* Take input chars one at a time, no wait for \n */
5863 noecho(); /* Don't echo input */
5864 leaveok(stdscr, TRUE);
5869 getmaxyx(stdscr, y, x);
5870 status_win = newwin(1, 0, y - 1, 0);
5872 die("Failed to create status window");
5874 /* Enable keyboard mapping */
5875 keypad(status_win, TRUE);
5876 wbkgdset(status_win, get_line_attr(LINE_STATUS));
5878 TABSIZE = opt_tab_size;
5879 if (opt_line_graphics) {
5880 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
5885 prompt_yesno(const char *prompt)
5887 enum { WAIT, STOP, CANCEL } status = WAIT;
5888 bool answer = FALSE;
5890 while (status == WAIT) {
5896 foreach_view (view, i)
5901 mvwprintw(status_win, 0, 0, "%s [Yy]/[Nn]", prompt);
5902 wclrtoeol(status_win);
5904 /* Refresh, accept single keystroke of input */
5905 key = wgetch(status_win);
5929 /* Clear the status window */
5930 status_empty = FALSE;
5937 read_prompt(const char *prompt)
5939 enum { READING, STOP, CANCEL } status = READING;
5940 static char buf[SIZEOF_STR];
5943 while (status == READING) {
5949 foreach_view (view, i)
5954 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
5955 wclrtoeol(status_win);
5957 /* Refresh, accept single keystroke of input */
5958 key = wgetch(status_win);
5963 status = pos ? STOP : CANCEL;
5981 if (pos >= sizeof(buf)) {
5982 report("Input string too long");
5987 buf[pos++] = (char) key;
5991 /* Clear the status window */
5992 status_empty = FALSE;
5995 if (status == CANCEL)
6004 * Repository properties
6008 git_properties(const char **argv, const char *separators,
6009 int (*read_property)(char *, size_t, char *, size_t))
6013 if (init_io_rd(&io, argv, NULL, FORMAT_NONE))
6014 return read_properties(&io, separators, read_property);
6018 static struct ref *refs = NULL;
6019 static size_t refs_alloc = 0;
6020 static size_t refs_size = 0;
6022 /* Id <-> ref store */
6023 static struct ref ***id_refs = NULL;
6024 static size_t id_refs_alloc = 0;
6025 static size_t id_refs_size = 0;
6028 compare_refs(const void *ref1_, const void *ref2_)
6030 const struct ref *ref1 = *(const struct ref **)ref1_;
6031 const struct ref *ref2 = *(const struct ref **)ref2_;
6033 if (ref1->tag != ref2->tag)
6034 return ref2->tag - ref1->tag;
6035 if (ref1->ltag != ref2->ltag)
6036 return ref2->ltag - ref2->ltag;
6037 if (ref1->head != ref2->head)
6038 return ref2->head - ref1->head;
6039 if (ref1->tracked != ref2->tracked)
6040 return ref2->tracked - ref1->tracked;
6041 if (ref1->remote != ref2->remote)
6042 return ref2->remote - ref1->remote;
6043 return strcmp(ref1->name, ref2->name);
6046 static struct ref **
6047 get_refs(const char *id)
6049 struct ref ***tmp_id_refs;
6050 struct ref **ref_list = NULL;
6051 size_t ref_list_alloc = 0;
6052 size_t ref_list_size = 0;
6055 for (i = 0; i < id_refs_size; i++)
6056 if (!strcmp(id, id_refs[i][0]->id))
6059 tmp_id_refs = realloc_items(id_refs, &id_refs_alloc, id_refs_size + 1,
6064 id_refs = tmp_id_refs;
6066 for (i = 0; i < refs_size; i++) {
6069 if (strcmp(id, refs[i].id))
6072 tmp = realloc_items(ref_list, &ref_list_alloc,
6073 ref_list_size + 1, sizeof(*ref_list));
6081 ref_list[ref_list_size] = &refs[i];
6082 /* XXX: The properties of the commit chains ensures that we can
6083 * safely modify the shared ref. The repo references will
6084 * always be similar for the same id. */
6085 ref_list[ref_list_size]->next = 1;
6091 qsort(ref_list, ref_list_size, sizeof(*ref_list), compare_refs);
6092 ref_list[ref_list_size - 1]->next = 0;
6093 id_refs[id_refs_size++] = ref_list;
6100 read_ref(char *id, size_t idlen, char *name, size_t namelen)
6105 bool remote = FALSE;
6106 bool tracked = FALSE;
6107 bool check_replace = FALSE;
6110 if (!prefixcmp(name, "refs/tags/")) {
6111 if (!suffixcmp(name, namelen, "^{}")) {
6114 if (refs_size > 0 && refs[refs_size - 1].ltag == TRUE)
6115 check_replace = TRUE;
6121 namelen -= STRING_SIZE("refs/tags/");
6122 name += STRING_SIZE("refs/tags/");
6124 } else if (!prefixcmp(name, "refs/remotes/")) {
6126 namelen -= STRING_SIZE("refs/remotes/");
6127 name += STRING_SIZE("refs/remotes/");
6128 tracked = !strcmp(opt_remote, name);
6130 } else if (!prefixcmp(name, "refs/heads/")) {
6131 namelen -= STRING_SIZE("refs/heads/");
6132 name += STRING_SIZE("refs/heads/");
6133 head = !strncmp(opt_head, name, namelen);
6135 } else if (!strcmp(name, "HEAD")) {
6136 string_ncopy(opt_head_rev, id, idlen);
6140 if (check_replace && !strcmp(name, refs[refs_size - 1].name)) {
6141 /* it's an annotated tag, replace the previous sha1 with the
6142 * resolved commit id; relies on the fact git-ls-remote lists
6143 * the commit id of an annotated tag right before the commit id
6145 refs[refs_size - 1].ltag = ltag;
6146 string_copy_rev(refs[refs_size - 1].id, id);
6150 refs = realloc_items(refs, &refs_alloc, refs_size + 1, sizeof(*refs));
6154 ref = &refs[refs_size++];
6155 ref->name = malloc(namelen + 1);
6159 strncpy(ref->name, name, namelen);
6160 ref->name[namelen] = 0;
6164 ref->remote = remote;
6165 ref->tracked = tracked;
6166 string_copy_rev(ref->id, id);
6174 static const char *ls_remote_argv[SIZEOF_ARG] = {
6175 "git", "ls-remote", ".", NULL
6177 static bool init = FALSE;
6180 argv_from_env(ls_remote_argv, "TIG_LS_REMOTE");
6187 while (refs_size > 0)
6188 free(refs[--refs_size].name);
6189 while (id_refs_size > 0)
6190 free(id_refs[--id_refs_size]);
6192 return git_properties(ls_remote_argv, "\t", read_ref);
6196 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
6198 if (!strcmp(name, "i18n.commitencoding"))
6199 string_ncopy(opt_encoding, value, valuelen);
6201 if (!strcmp(name, "core.editor"))
6202 string_ncopy(opt_editor, value, valuelen);
6204 /* branch.<head>.remote */
6206 !strncmp(name, "branch.", 7) &&
6207 !strncmp(name + 7, opt_head, strlen(opt_head)) &&
6208 !strcmp(name + 7 + strlen(opt_head), ".remote"))
6209 string_ncopy(opt_remote, value, valuelen);
6211 if (*opt_head && *opt_remote &&
6212 !strncmp(name, "branch.", 7) &&
6213 !strncmp(name + 7, opt_head, strlen(opt_head)) &&
6214 !strcmp(name + 7 + strlen(opt_head), ".merge")) {
6215 size_t from = strlen(opt_remote);
6217 if (!prefixcmp(value, "refs/heads/")) {
6218 value += STRING_SIZE("refs/heads/");
6219 valuelen -= STRING_SIZE("refs/heads/");
6222 if (!string_format_from(opt_remote, &from, "/%s", value))
6230 load_git_config(void)
6232 const char *config_list_argv[] = { "git", GIT_CONFIG, "--list", NULL };
6234 return git_properties(config_list_argv, "=", read_repo_config_option);
6238 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
6240 if (!opt_git_dir[0]) {
6241 string_ncopy(opt_git_dir, name, namelen);
6243 } else if (opt_is_inside_work_tree == -1) {
6244 /* This can be 3 different values depending on the
6245 * version of git being used. If git-rev-parse does not
6246 * understand --is-inside-work-tree it will simply echo
6247 * the option else either "true" or "false" is printed.
6248 * Default to true for the unknown case. */
6249 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
6251 string_ncopy(opt_cdup, name, namelen);
6258 load_repo_info(void)
6260 const char *head_argv[] = {
6261 "git", "symbolic-ref", "HEAD", NULL
6263 const char *rev_parse_argv[] = {
6264 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
6268 if (run_io_buf(head_argv, opt_head, sizeof(opt_head))) {
6269 chomp_string(opt_head);
6270 if (!prefixcmp(opt_head, "refs/heads/")) {
6271 char *offset = opt_head + STRING_SIZE("refs/heads/");
6273 memmove(opt_head, offset, strlen(offset) + 1);
6277 return git_properties(rev_parse_argv, "=", read_repo_info);
6281 read_properties(struct io *io, const char *separators,
6282 int (*read_property)(char *, size_t, char *, size_t))
6290 while (state == OK && (name = io_gets(io))) {
6295 name = chomp_string(name);
6296 namelen = strcspn(name, separators);
6298 if (name[namelen]) {
6300 value = chomp_string(name + namelen + 1);
6301 valuelen = strlen(value);
6308 state = read_property(name, namelen, value, valuelen);
6311 if (state != ERR && io_error(io))
6323 static void __NORETURN
6326 /* XXX: Restore tty modes and let the OS cleanup the rest! */
6332 static void __NORETURN
6333 die(const char *err, ...)
6339 va_start(args, err);
6340 fputs("tig: ", stderr);
6341 vfprintf(stderr, err, args);
6342 fputs("\n", stderr);
6349 warn(const char *msg, ...)
6353 va_start(args, msg);
6354 fputs("tig warning: ", stderr);
6355 vfprintf(stderr, msg, args);
6356 fputs("\n", stderr);
6361 main(int argc, const char *argv[])
6363 const char **run_argv = NULL;
6365 enum request request;
6368 signal(SIGINT, quit);
6370 if (setlocale(LC_ALL, "")) {
6371 char *codeset = nl_langinfo(CODESET);
6373 string_ncopy(opt_codeset, codeset, strlen(codeset));
6376 if (load_repo_info() == ERR)
6377 die("Failed to load repo info.");
6379 if (load_options() == ERR)
6380 die("Failed to load user config.");
6382 if (load_git_config() == ERR)
6383 die("Failed to load repo config.");
6385 request = parse_options(argc, argv, &run_argv);
6386 if (request == REQ_NONE)
6389 /* Require a git repository unless when running in pager mode. */
6390 if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
6391 die("Not a git repository");
6393 if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
6396 if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
6397 opt_iconv = iconv_open(opt_codeset, opt_encoding);
6398 if (opt_iconv == ICONV_NONE)
6399 die("Failed to initialize character set conversion");
6402 if (load_refs() == ERR)
6403 die("Failed to load refs.");
6405 foreach_view (view, i)
6406 argv_from_env(view->ops->argv, view->cmd_env);
6410 if (request == REQ_VIEW_PAGER || run_argv) {
6411 if (request == REQ_VIEW_PAGER)
6412 init_io_fd(&VIEW(request)->io, stdin);
6413 else if (!prepare_update(VIEW(request), run_argv, NULL, FORMAT_NONE))
6414 die("Failed to format arguments");
6415 open_view(NULL, request, OPEN_PREPARED);
6419 while (view_driver(display[current_view], request)) {
6423 foreach_view (view, i)
6425 view = display[current_view];
6427 /* Refresh, accept single keystroke of input */
6428 key = wgetch(status_win);
6430 /* wgetch() with nodelay() enabled returns ERR when there's no
6437 request = get_keybinding(view->keymap, key);
6439 /* Some low-level request handling. This keeps access to
6440 * status_win restricted. */
6444 char *cmd = read_prompt(":");
6447 struct view *next = VIEW(REQ_VIEW_PAGER);
6448 const char *argv[SIZEOF_ARG] = { "git" };
6451 /* When running random commands, initially show the
6452 * command in the title. However, it maybe later be
6453 * overwritten if a commit line is selected. */
6454 string_ncopy(next->ref, cmd, strlen(cmd));
6456 if (!argv_from_string(argv, &argc, cmd)) {
6457 report("Too many arguments");
6458 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
6459 report("Failed to format command");
6461 open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
6469 case REQ_SEARCH_BACK:
6471 const char *prompt = request == REQ_SEARCH ? "/" : "?";
6472 char *search = read_prompt(prompt);
6475 string_ncopy(opt_search, search, strlen(search));
6480 case REQ_SCREEN_RESIZE:
6484 getmaxyx(stdscr, height, width);
6486 /* Resize the status view and let the view driver take
6487 * care of resizing the displayed views. */
6488 wresize(status_win, 1, width);
6489 mvwin(status_win, height - 1, 0);
6490 wrefresh(status_win);