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 int read_properties(FILE *pipe, const char *separators, int (*read)(char *, size_t, char *, size_t));
68 static void set_nonblocking_input(bool loading);
69 static size_t utf8_length(const char *string, int *width, size_t max_width, int *trimmed, bool reserve);
70 static bool prompt_yesno(const char *prompt);
71 static int load_refs(void);
73 #define ABS(x) ((x) >= 0 ? (x) : -(x))
74 #define MIN(x, y) ((x) < (y) ? (x) : (y))
76 #define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
77 #define STRING_SIZE(x) (sizeof(x) - 1)
79 #define SIZEOF_STR 1024 /* Default string size. */
80 #define SIZEOF_REF 256 /* Size of symbolic or SHA1 ID. */
81 #define SIZEOF_REV 41 /* Holds a SHA-1 and an ending NUL. */
82 #define SIZEOF_ARG 32 /* Default argument array size. */
86 #define REVGRAPH_INIT 'I'
87 #define REVGRAPH_MERGE 'M'
88 #define REVGRAPH_BRANCH '+'
89 #define REVGRAPH_COMMIT '*'
90 #define REVGRAPH_BOUND '^'
92 #define SIZEOF_REVGRAPH 19 /* Size of revision ancestry graphics. */
94 /* This color name can be used to refer to the default term colors. */
95 #define COLOR_DEFAULT (-1)
97 #define ICONV_NONE ((iconv_t) -1)
99 #define ICONV_CONST /* nothing */
102 /* The format and size of the date column in the main view. */
103 #define DATE_FORMAT "%Y-%m-%d %H:%M"
104 #define DATE_COLS STRING_SIZE("2006-04-29 14:21 ")
106 #define AUTHOR_COLS 20
109 /* The default interval between line numbers. */
110 #define NUMBER_INTERVAL 5
114 #define SCALE_SPLIT_VIEW(height) ((height) * 2 / 3)
116 #define NULL_ID "0000000000000000000000000000000000000000"
119 #define GIT_CONFIG "config"
122 #define TIG_LS_REMOTE \
123 "git ls-remote . 2>/dev/null"
125 /* Some ascii-shorthands fitted into the ncurses namespace. */
127 #define KEY_RETURN '\r'
132 char *name; /* Ref name; tag or head names are shortened. */
133 char id[SIZEOF_REV]; /* Commit SHA1 ID */
134 unsigned int head:1; /* Is it the current HEAD? */
135 unsigned int tag:1; /* Is it a tag? */
136 unsigned int ltag:1; /* If so, is the tag local? */
137 unsigned int remote:1; /* Is it a remote ref? */
138 unsigned int tracked:1; /* Is it the remote for the current HEAD? */
139 unsigned int next:1; /* For ref lists: are there more refs? */
142 static struct ref **get_refs(const char *id);
145 FORMAT_ALL, /* Perform replacement in all arguments. */
146 FORMAT_DASH, /* Perform replacement up until "--". */
147 FORMAT_NONE /* No replacement should be performed. */
150 static bool format_command(char dst[], const char *src[], enum format_flags flags);
151 static bool format_argv(const char *dst[], const char *src[], enum format_flags flags);
160 set_from_int_map(struct int_map *map, size_t map_size,
161 int *value, const char *name, int namelen)
166 for (i = 0; i < map_size; i++)
167 if (namelen == map[i].namelen &&
168 !strncasecmp(name, map[i].name, namelen)) {
169 *value = map[i].value;
182 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
184 if (srclen > dstlen - 1)
187 strncpy(dst, src, srclen);
191 /* Shorthands for safely copying into a fixed buffer. */
193 #define string_copy(dst, src) \
194 string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
196 #define string_ncopy(dst, src, srclen) \
197 string_ncopy_do(dst, sizeof(dst), src, srclen)
199 #define string_copy_rev(dst, src) \
200 string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
202 #define string_add(dst, from, src) \
203 string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
206 chomp_string(char *name)
210 while (isspace(*name))
213 namelen = strlen(name) - 1;
214 while (namelen > 0 && isspace(name[namelen]))
221 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
224 size_t pos = bufpos ? *bufpos : 0;
227 pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
233 return pos >= bufsize ? FALSE : TRUE;
236 #define string_format(buf, fmt, args...) \
237 string_nformat(buf, sizeof(buf), NULL, fmt, args)
239 #define string_format_from(buf, from, fmt, args...) \
240 string_nformat(buf, sizeof(buf), from, fmt, args)
243 string_enum_compare(const char *str1, const char *str2, int len)
247 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
249 /* Diff-Header == DIFF_HEADER */
250 for (i = 0; i < len; i++) {
251 if (toupper(str1[i]) == toupper(str2[i]))
254 if (string_enum_sep(str1[i]) &&
255 string_enum_sep(str2[i]))
258 return str1[i] - str2[i];
264 #define prefixcmp(str1, str2) \
265 strncmp(str1, str2, STRING_SIZE(str2))
268 suffixcmp(const char *str, int slen, const char *suffix)
270 size_t len = slen >= 0 ? slen : strlen(str);
271 size_t suffixlen = strlen(suffix);
273 return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
278 * NOTE: The following is a slightly modified copy of the git project's shell
279 * quoting routines found in the quote.c file.
281 * Help to copy the thing properly quoted for the shell safety. any single
282 * quote is replaced with '\'', any exclamation point is replaced with '\!',
283 * and the whole thing is enclosed in a
286 * original sq_quote result
287 * name ==> name ==> 'name'
288 * a b ==> a b ==> 'a b'
289 * a'b ==> a'\''b ==> 'a'\''b'
290 * a!b ==> a'\!'b ==> 'a'\!'b'
294 sq_quote(char buf[SIZEOF_STR], size_t bufsize, const char *src)
298 #define BUFPUT(x) do { if (bufsize < SIZEOF_STR) buf[bufsize++] = (x); } while (0)
301 while ((c = *src++)) {
302 if (c == '\'' || c == '!') {
313 if (bufsize < SIZEOF_STR)
320 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
324 while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
325 bool advance = cmd[valuelen] != 0;
328 argv[(*argc)++] = chomp_string(cmd);
329 cmd += valuelen + advance;
332 if (*argc < SIZEOF_ARG)
334 return *argc < SIZEOF_ARG;
338 argv_from_env(const char **argv, const char *name)
340 char *env = argv ? getenv(name) : NULL;
345 if (env && !argv_from_string(argv, &argc, env))
346 die("Too many arguments in the `%s` environment variable", name);
351 * Executing external commands.
355 IO_FD, /* File descriptor based IO. */
356 IO_FG, /* Execute command with same std{in,out,err}. */
357 IO_RD, /* Read only fork+exec IO. */
358 IO_WR, /* Write only fork+exec IO. */
362 enum io_type type; /* The requested type of pipe. */
363 const char *dir; /* Directory from which to execute. */
364 FILE *pipe; /* Pipe for reading or writing. */
365 int error; /* Error status. */
366 char sh[SIZEOF_STR]; /* Shell command buffer. */
367 char *buf; /* Read/write buffer. */
368 size_t bufalloc; /* Allocated buffer size. */
372 reset_io(struct io *io)
381 init_io(struct io *io, const char *dir, enum io_type type)
389 init_io_rd(struct io *io, const char *argv[], const char *dir,
390 enum format_flags flags)
392 init_io(io, dir, IO_RD);
393 return format_command(io->sh, argv, flags);
397 init_io_fd(struct io *io, FILE *pipe)
399 init_io(io, NULL, IO_FD);
401 return io->pipe != NULL;
405 done_io(struct io *io)
408 if (io->type == IO_FD)
410 else if (io->type == IO_RD || io->type == IO_WR)
417 start_io(struct io *io)
419 char buf[SIZEOF_STR * 2];
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)
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, enum io_type type, const char *cmd)
439 init_io(io, NULL, type);
440 string_ncopy(io->sh, cmd, strlen(cmd));
445 run_io_do(struct io *io)
447 return start_io(io) && done_io(io);
451 run_io_fg(const char **argv, const char *dir)
455 init_io(&io, dir, IO_FG);
456 if (!format_command(io.sh, argv, FORMAT_NONE))
458 return run_io_do(&io);
462 run_io_rd(struct io *io, const char **argv, enum format_flags flags)
464 return init_io_rd(io, argv, NULL, flags) && start_io(io);
468 io_eof(struct io *io)
470 return feof(io->pipe);
474 io_error(struct io *io)
480 io_strerror(struct io *io)
482 return strerror(io->error);
486 io_gets(struct io *io)
489 io->buf = malloc(BUFSIZ);
492 io->bufalloc = BUFSIZ;
495 if (!fgets(io->buf, io->bufalloc, io->pipe)) {
496 if (ferror(io->pipe))
505 run_io_buf(const char **argv, char buf[], size_t bufsize)
510 if (!run_io_rd(&io, argv, FORMAT_NONE))
514 io.bufalloc = bufsize;
515 error = !io_gets(&io) && io_error(&io);
518 return done_io(&io) || error;
527 /* XXX: Keep the view request first and in sync with views[]. */ \
528 REQ_GROUP("View switching") \
529 REQ_(VIEW_MAIN, "Show main view"), \
530 REQ_(VIEW_DIFF, "Show diff view"), \
531 REQ_(VIEW_LOG, "Show log view"), \
532 REQ_(VIEW_TREE, "Show tree view"), \
533 REQ_(VIEW_BLOB, "Show blob view"), \
534 REQ_(VIEW_BLAME, "Show blame view"), \
535 REQ_(VIEW_HELP, "Show help page"), \
536 REQ_(VIEW_PAGER, "Show pager view"), \
537 REQ_(VIEW_STATUS, "Show status view"), \
538 REQ_(VIEW_STAGE, "Show stage view"), \
540 REQ_GROUP("View manipulation") \
541 REQ_(ENTER, "Enter current line and scroll"), \
542 REQ_(NEXT, "Move to next"), \
543 REQ_(PREVIOUS, "Move to previous"), \
544 REQ_(VIEW_NEXT, "Move focus to next view"), \
545 REQ_(REFRESH, "Reload and refresh"), \
546 REQ_(MAXIMIZE, "Maximize the current view"), \
547 REQ_(VIEW_CLOSE, "Close the current view"), \
548 REQ_(QUIT, "Close all views and quit"), \
550 REQ_GROUP("View specific requests") \
551 REQ_(STATUS_UPDATE, "Update file status"), \
552 REQ_(STATUS_REVERT, "Revert file changes"), \
553 REQ_(STATUS_MERGE, "Merge file using external tool"), \
554 REQ_(STAGE_NEXT, "Find next chunk to stage"), \
555 REQ_(TREE_PARENT, "Switch to parent directory in tree view"), \
557 REQ_GROUP("Cursor navigation") \
558 REQ_(MOVE_UP, "Move cursor one line up"), \
559 REQ_(MOVE_DOWN, "Move cursor one line down"), \
560 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
561 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
562 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
563 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
565 REQ_GROUP("Scrolling") \
566 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
567 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
568 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
569 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
571 REQ_GROUP("Searching") \
572 REQ_(SEARCH, "Search the view"), \
573 REQ_(SEARCH_BACK, "Search backwards in the view"), \
574 REQ_(FIND_NEXT, "Find next search match"), \
575 REQ_(FIND_PREV, "Find previous search match"), \
577 REQ_GROUP("Option manipulation") \
578 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
579 REQ_(TOGGLE_DATE, "Toggle date display"), \
580 REQ_(TOGGLE_AUTHOR, "Toggle author display"), \
581 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
582 REQ_(TOGGLE_REFS, "Toggle reference display (tags/branches)"), \
585 REQ_(PROMPT, "Bring up the prompt"), \
586 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
587 REQ_(SCREEN_RESIZE, "Resize the screen"), \
588 REQ_(SHOW_VERSION, "Show version information"), \
589 REQ_(STOP_LOADING, "Stop all loading views"), \
590 REQ_(EDIT, "Open in editor"), \
591 REQ_(NONE, "Do nothing")
594 /* User action requests. */
596 #define REQ_GROUP(help)
597 #define REQ_(req, help) REQ_##req
599 /* Offset all requests to avoid conflicts with ncurses getch values. */
600 REQ_OFFSET = KEY_MAX + 1,
607 struct request_info {
608 enum request request;
614 static struct request_info req_info[] = {
615 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
616 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
623 get_request(const char *name)
625 int namelen = strlen(name);
628 for (i = 0; i < ARRAY_SIZE(req_info); i++)
629 if (req_info[i].namelen == namelen &&
630 !string_enum_compare(req_info[i].name, name, namelen))
631 return req_info[i].request;
641 static const char usage[] =
642 "tig " TIG_VERSION " (" __DATE__ ")\n"
644 "Usage: tig [options] [revs] [--] [paths]\n"
645 " or: tig show [options] [revs] [--] [paths]\n"
646 " or: tig blame [rev] path\n"
648 " or: tig < [git command output]\n"
651 " -v, --version Show version and exit\n"
652 " -h, --help Show help message and exit";
654 /* Option and state variables. */
655 static bool opt_date = TRUE;
656 static bool opt_author = TRUE;
657 static bool opt_line_number = FALSE;
658 static bool opt_line_graphics = TRUE;
659 static bool opt_rev_graph = FALSE;
660 static bool opt_show_refs = TRUE;
661 static int opt_num_interval = NUMBER_INTERVAL;
662 static int opt_tab_size = TAB_SIZE;
663 static int opt_author_cols = AUTHOR_COLS-1;
664 static char opt_cmd[SIZEOF_STR] = "";
665 static char opt_path[SIZEOF_STR] = "";
666 static char opt_file[SIZEOF_STR] = "";
667 static char opt_ref[SIZEOF_REF] = "";
668 static char opt_head[SIZEOF_REF] = "";
669 static char opt_head_rev[SIZEOF_REV] = "";
670 static char opt_remote[SIZEOF_REF] = "";
671 static FILE *opt_pipe = NULL;
672 static char opt_encoding[20] = "UTF-8";
673 static bool opt_utf8 = TRUE;
674 static char opt_codeset[20] = "UTF-8";
675 static iconv_t opt_iconv = ICONV_NONE;
676 static char opt_search[SIZEOF_STR] = "";
677 static char opt_cdup[SIZEOF_STR] = "";
678 static char opt_git_dir[SIZEOF_STR] = "";
679 static signed char opt_is_inside_work_tree = -1; /* set to TRUE or FALSE */
680 static char opt_editor[SIZEOF_STR] = "";
681 static FILE *opt_tty = NULL;
683 #define is_initial_commit() (!*opt_head_rev)
684 #define is_head_commit(rev) (!strcmp((rev), "HEAD") || !strcmp(opt_head_rev, (rev)))
687 parse_options(int argc, const char *argv[], const char ***run_argv)
689 enum request request = REQ_VIEW_MAIN;
690 const char *subcommand;
691 bool seen_dashdash = FALSE;
692 /* XXX: This is vulnerable to the user overriding options
693 * required for the main view parser. */
694 const char *custom_argv[SIZEOF_ARG] = {
695 "git", "log", "--no-color", "--pretty=raw", "--parents",
700 if (!isatty(STDIN_FILENO)) {
702 return REQ_VIEW_PAGER;
706 return REQ_VIEW_MAIN;
708 subcommand = argv[1];
709 if (!strcmp(subcommand, "status") || !strcmp(subcommand, "-S")) {
710 if (!strcmp(subcommand, "-S"))
711 warn("`-S' has been deprecated; use `tig status' instead");
713 warn("ignoring arguments after `%s'", subcommand);
714 return REQ_VIEW_STATUS;
716 } else if (!strcmp(subcommand, "blame")) {
717 if (argc <= 2 || argc > 4)
718 die("invalid number of options to blame\n\n%s", usage);
722 string_ncopy(opt_ref, argv[i], strlen(argv[i]));
726 string_ncopy(opt_file, argv[i], strlen(argv[i]));
727 return REQ_VIEW_BLAME;
729 } else if (!strcmp(subcommand, "show")) {
730 request = REQ_VIEW_DIFF;
732 } else if (!strcmp(subcommand, "log") || !strcmp(subcommand, "diff")) {
733 request = subcommand[0] == 'l' ? REQ_VIEW_LOG : REQ_VIEW_DIFF;
734 warn("`tig %s' has been deprecated", subcommand);
741 custom_argv[1] = subcommand;
745 for (i = 1 + !!subcommand; i < argc; i++) {
746 const char *opt = argv[i];
748 if (seen_dashdash || !strcmp(opt, "--")) {
749 seen_dashdash = TRUE;
751 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
752 printf("tig version %s\n", TIG_VERSION);
755 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
756 printf("%s\n", usage);
760 custom_argv[j++] = opt;
761 if (j >= ARRAY_SIZE(custom_argv))
762 die("command too long");
765 custom_argv[j] = NULL;
766 *run_argv = custom_argv;
773 * Line-oriented content detection.
777 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
778 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
779 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
780 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
781 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
782 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
783 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
784 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
785 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
786 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
787 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
788 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
789 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
790 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
791 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
792 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
793 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
794 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
795 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
796 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
797 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
798 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
799 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
800 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
801 LINE(AUTHOR, "author ", COLOR_CYAN, COLOR_DEFAULT, 0), \
802 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
803 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
804 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
805 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
806 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
807 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
808 LINE(DELIMITER, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
809 LINE(DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
810 LINE(LINE_NUMBER, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
811 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
812 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
813 LINE(MAIN_AUTHOR, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
814 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
815 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
816 LINE(MAIN_LOCAL_TAG,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
817 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
818 LINE(MAIN_TRACKED, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
819 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
820 LINE(MAIN_HEAD, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
821 LINE(MAIN_REVGRAPH,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
822 LINE(TREE_DIR, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
823 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
824 LINE(STAT_HEAD, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
825 LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
826 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
827 LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
828 LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
829 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
830 LINE(BLAME_ID, "", COLOR_MAGENTA, COLOR_DEFAULT, 0)
833 #define LINE(type, line, fg, bg, attr) \
841 const char *name; /* Option name. */
842 int namelen; /* Size of option name. */
843 const char *line; /* The start of line to match. */
844 int linelen; /* Size of string to match. */
845 int fg, bg, attr; /* Color and text attributes for the lines. */
848 static struct line_info line_info[] = {
849 #define LINE(type, line, fg, bg, attr) \
850 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
855 static enum line_type
856 get_line_type(const char *line)
858 int linelen = strlen(line);
861 for (type = 0; type < ARRAY_SIZE(line_info); type++)
862 /* Case insensitive search matches Signed-off-by lines better. */
863 if (linelen >= line_info[type].linelen &&
864 !strncasecmp(line_info[type].line, line, line_info[type].linelen))
871 get_line_attr(enum line_type type)
873 assert(type < ARRAY_SIZE(line_info));
874 return COLOR_PAIR(type) | line_info[type].attr;
877 static struct line_info *
878 get_line_info(const char *name)
880 size_t namelen = strlen(name);
883 for (type = 0; type < ARRAY_SIZE(line_info); type++)
884 if (namelen == line_info[type].namelen &&
885 !string_enum_compare(line_info[type].name, name, namelen))
886 return &line_info[type];
894 int default_bg = line_info[LINE_DEFAULT].bg;
895 int default_fg = line_info[LINE_DEFAULT].fg;
900 if (assume_default_colors(default_fg, default_bg) == ERR) {
901 default_bg = COLOR_BLACK;
902 default_fg = COLOR_WHITE;
905 for (type = 0; type < ARRAY_SIZE(line_info); type++) {
906 struct line_info *info = &line_info[type];
907 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
908 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
910 init_pair(type, fg, bg);
918 unsigned int selected:1;
919 unsigned int dirty:1;
921 void *data; /* User data */
931 enum request request;
934 static struct keybinding default_keybindings[] = {
936 { 'm', REQ_VIEW_MAIN },
937 { 'd', REQ_VIEW_DIFF },
938 { 'l', REQ_VIEW_LOG },
939 { 't', REQ_VIEW_TREE },
940 { 'f', REQ_VIEW_BLOB },
941 { 'B', REQ_VIEW_BLAME },
942 { 'p', REQ_VIEW_PAGER },
943 { 'h', REQ_VIEW_HELP },
944 { 'S', REQ_VIEW_STATUS },
945 { 'c', REQ_VIEW_STAGE },
947 /* View manipulation */
948 { 'q', REQ_VIEW_CLOSE },
949 { KEY_TAB, REQ_VIEW_NEXT },
950 { KEY_RETURN, REQ_ENTER },
951 { KEY_UP, REQ_PREVIOUS },
952 { KEY_DOWN, REQ_NEXT },
953 { 'R', REQ_REFRESH },
954 { KEY_F(5), REQ_REFRESH },
955 { 'O', REQ_MAXIMIZE },
957 /* Cursor navigation */
958 { 'k', REQ_MOVE_UP },
959 { 'j', REQ_MOVE_DOWN },
960 { KEY_HOME, REQ_MOVE_FIRST_LINE },
961 { KEY_END, REQ_MOVE_LAST_LINE },
962 { KEY_NPAGE, REQ_MOVE_PAGE_DOWN },
963 { ' ', REQ_MOVE_PAGE_DOWN },
964 { KEY_PPAGE, REQ_MOVE_PAGE_UP },
965 { 'b', REQ_MOVE_PAGE_UP },
966 { '-', REQ_MOVE_PAGE_UP },
969 { KEY_IC, REQ_SCROLL_LINE_UP },
970 { KEY_DC, REQ_SCROLL_LINE_DOWN },
971 { 'w', REQ_SCROLL_PAGE_UP },
972 { 's', REQ_SCROLL_PAGE_DOWN },
976 { '?', REQ_SEARCH_BACK },
977 { 'n', REQ_FIND_NEXT },
978 { 'N', REQ_FIND_PREV },
982 { 'z', REQ_STOP_LOADING },
983 { 'v', REQ_SHOW_VERSION },
984 { 'r', REQ_SCREEN_REDRAW },
985 { '.', REQ_TOGGLE_LINENO },
986 { 'D', REQ_TOGGLE_DATE },
987 { 'A', REQ_TOGGLE_AUTHOR },
988 { 'g', REQ_TOGGLE_REV_GRAPH },
989 { 'F', REQ_TOGGLE_REFS },
991 { 'u', REQ_STATUS_UPDATE },
992 { '!', REQ_STATUS_REVERT },
993 { 'M', REQ_STATUS_MERGE },
994 { '@', REQ_STAGE_NEXT },
995 { ',', REQ_TREE_PARENT },
998 /* Using the ncurses SIGWINCH handler. */
999 { KEY_RESIZE, REQ_SCREEN_RESIZE },
1002 #define KEYMAP_INFO \
1016 #define KEYMAP_(name) KEYMAP_##name
1021 static struct int_map keymap_table[] = {
1022 #define KEYMAP_(name) { #name, STRING_SIZE(#name), KEYMAP_##name }
1027 #define set_keymap(map, name) \
1028 set_from_int_map(keymap_table, ARRAY_SIZE(keymap_table), map, name, strlen(name))
1030 struct keybinding_table {
1031 struct keybinding *data;
1035 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1038 add_keybinding(enum keymap keymap, enum request request, int key)
1040 struct keybinding_table *table = &keybindings[keymap];
1042 table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1044 die("Failed to allocate keybinding");
1045 table->data[table->size].alias = key;
1046 table->data[table->size++].request = request;
1049 /* Looks for a key binding first in the given map, then in the generic map, and
1050 * lastly in the default keybindings. */
1052 get_keybinding(enum keymap keymap, int key)
1056 for (i = 0; i < keybindings[keymap].size; i++)
1057 if (keybindings[keymap].data[i].alias == key)
1058 return keybindings[keymap].data[i].request;
1060 for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1061 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1062 return keybindings[KEYMAP_GENERIC].data[i].request;
1064 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1065 if (default_keybindings[i].alias == key)
1066 return default_keybindings[i].request;
1068 return (enum request) key;
1077 static struct key key_table[] = {
1078 { "Enter", KEY_RETURN },
1080 { "Backspace", KEY_BACKSPACE },
1082 { "Escape", KEY_ESC },
1083 { "Left", KEY_LEFT },
1084 { "Right", KEY_RIGHT },
1086 { "Down", KEY_DOWN },
1087 { "Insert", KEY_IC },
1088 { "Delete", KEY_DC },
1090 { "Home", KEY_HOME },
1092 { "PageUp", KEY_PPAGE },
1093 { "PageDown", KEY_NPAGE },
1103 { "F10", KEY_F(10) },
1104 { "F11", KEY_F(11) },
1105 { "F12", KEY_F(12) },
1109 get_key_value(const char *name)
1113 for (i = 0; i < ARRAY_SIZE(key_table); i++)
1114 if (!strcasecmp(key_table[i].name, name))
1115 return key_table[i].value;
1117 if (strlen(name) == 1 && isprint(*name))
1124 get_key_name(int key_value)
1126 static char key_char[] = "'X'";
1127 const char *seq = NULL;
1130 for (key = 0; key < ARRAY_SIZE(key_table); key++)
1131 if (key_table[key].value == key_value)
1132 seq = key_table[key].name;
1136 isprint(key_value)) {
1137 key_char[1] = (char) key_value;
1141 return seq ? seq : "(no key)";
1145 get_key(enum request request)
1147 static char buf[BUFSIZ];
1154 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1155 struct keybinding *keybinding = &default_keybindings[i];
1157 if (keybinding->request != request)
1160 if (!string_format_from(buf, &pos, "%s%s", sep,
1161 get_key_name(keybinding->alias)))
1162 return "Too many keybindings!";
1169 struct run_request {
1172 const char *argv[SIZEOF_ARG];
1175 static struct run_request *run_request;
1176 static size_t run_requests;
1179 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1181 struct run_request *req;
1183 if (argc >= ARRAY_SIZE(req->argv) - 1)
1186 req = realloc(run_request, (run_requests + 1) * sizeof(*run_request));
1191 req = &run_request[run_requests];
1192 req->keymap = keymap;
1194 req->argv[0] = NULL;
1196 if (!format_argv(req->argv, argv, FORMAT_NONE))
1199 return REQ_NONE + ++run_requests;
1202 static struct run_request *
1203 get_run_request(enum request request)
1205 if (request <= REQ_NONE)
1207 return &run_request[request - REQ_NONE - 1];
1211 add_builtin_run_requests(void)
1213 const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1214 const char *gc[] = { "git", "gc", NULL };
1221 { KEYMAP_MAIN, 'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1222 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1226 for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1229 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1230 if (req != REQ_NONE)
1231 add_keybinding(reqs[i].keymap, req, reqs[i].key);
1236 * User config file handling.
1239 static struct int_map color_map[] = {
1240 #define COLOR_MAP(name) { #name, STRING_SIZE(#name), COLOR_##name }
1252 #define set_color(color, name) \
1253 set_from_int_map(color_map, ARRAY_SIZE(color_map), color, name, strlen(name))
1255 static struct int_map attr_map[] = {
1256 #define ATTR_MAP(name) { #name, STRING_SIZE(#name), A_##name }
1263 ATTR_MAP(UNDERLINE),
1266 #define set_attribute(attr, name) \
1267 set_from_int_map(attr_map, ARRAY_SIZE(attr_map), attr, name, strlen(name))
1269 static int config_lineno;
1270 static bool config_errors;
1271 static const char *config_msg;
1273 /* Wants: object fgcolor bgcolor [attr] */
1275 option_color_command(int argc, const char *argv[])
1277 struct line_info *info;
1279 if (argc != 3 && argc != 4) {
1280 config_msg = "Wrong number of arguments given to color command";
1284 info = get_line_info(argv[0]);
1286 if (!string_enum_compare(argv[0], "main-delim", strlen("main-delim"))) {
1287 info = get_line_info("delimiter");
1289 } else if (!string_enum_compare(argv[0], "main-date", strlen("main-date"))) {
1290 info = get_line_info("date");
1293 config_msg = "Unknown color name";
1298 if (set_color(&info->fg, argv[1]) == ERR ||
1299 set_color(&info->bg, argv[2]) == ERR) {
1300 config_msg = "Unknown color";
1304 if (argc == 4 && set_attribute(&info->attr, argv[3]) == ERR) {
1305 config_msg = "Unknown attribute";
1312 static bool parse_bool(const char *s)
1314 return (!strcmp(s, "1") || !strcmp(s, "true") ||
1315 !strcmp(s, "yes")) ? TRUE : FALSE;
1319 parse_int(const char *s, int default_value, int min, int max)
1321 int value = atoi(s);
1323 return (value < min || value > max) ? default_value : value;
1326 /* Wants: name = value */
1328 option_set_command(int argc, const char *argv[])
1331 config_msg = "Wrong number of arguments given to set command";
1335 if (strcmp(argv[1], "=")) {
1336 config_msg = "No value assigned";
1340 if (!strcmp(argv[0], "show-author")) {
1341 opt_author = parse_bool(argv[2]);
1345 if (!strcmp(argv[0], "show-date")) {
1346 opt_date = parse_bool(argv[2]);
1350 if (!strcmp(argv[0], "show-rev-graph")) {
1351 opt_rev_graph = parse_bool(argv[2]);
1355 if (!strcmp(argv[0], "show-refs")) {
1356 opt_show_refs = parse_bool(argv[2]);
1360 if (!strcmp(argv[0], "show-line-numbers")) {
1361 opt_line_number = parse_bool(argv[2]);
1365 if (!strcmp(argv[0], "line-graphics")) {
1366 opt_line_graphics = parse_bool(argv[2]);
1370 if (!strcmp(argv[0], "line-number-interval")) {
1371 opt_num_interval = parse_int(argv[2], opt_num_interval, 1, 1024);
1375 if (!strcmp(argv[0], "author-width")) {
1376 opt_author_cols = parse_int(argv[2], opt_author_cols, 0, 1024);
1380 if (!strcmp(argv[0], "tab-size")) {
1381 opt_tab_size = parse_int(argv[2], opt_tab_size, 1, 1024);
1385 if (!strcmp(argv[0], "commit-encoding")) {
1386 const char *arg = argv[2];
1387 int arglen = strlen(arg);
1392 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1393 config_msg = "Unmatched quotation";
1396 arg += 1; arglen -= 2;
1398 string_ncopy(opt_encoding, arg, strlen(arg));
1403 config_msg = "Unknown variable name";
1407 /* Wants: mode request key */
1409 option_bind_command(int argc, const char *argv[])
1411 enum request request;
1416 config_msg = "Wrong number of arguments given to bind command";
1420 if (set_keymap(&keymap, argv[0]) == ERR) {
1421 config_msg = "Unknown key map";
1425 key = get_key_value(argv[1]);
1427 config_msg = "Unknown key";
1431 request = get_request(argv[2]);
1432 if (request == REQ_NONE) {
1433 const char *obsolete[] = { "cherry-pick" };
1434 size_t namelen = strlen(argv[2]);
1437 for (i = 0; i < ARRAY_SIZE(obsolete); i++) {
1438 if (namelen == strlen(obsolete[i]) &&
1439 !string_enum_compare(obsolete[i], argv[2], namelen)) {
1440 config_msg = "Obsolete request name";
1445 if (request == REQ_NONE && *argv[2]++ == '!')
1446 request = add_run_request(keymap, key, argc - 2, argv + 2);
1447 if (request == REQ_NONE) {
1448 config_msg = "Unknown request name";
1452 add_keybinding(keymap, request, key);
1458 set_option(const char *opt, char *value)
1460 const char *argv[SIZEOF_ARG];
1463 if (!argv_from_string(argv, &argc, value)) {
1464 config_msg = "Too many option arguments";
1468 if (!strcmp(opt, "color"))
1469 return option_color_command(argc, argv);
1471 if (!strcmp(opt, "set"))
1472 return option_set_command(argc, argv);
1474 if (!strcmp(opt, "bind"))
1475 return option_bind_command(argc, argv);
1477 config_msg = "Unknown option command";
1482 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1487 config_msg = "Internal error";
1489 /* Check for comment markers, since read_properties() will
1490 * only ensure opt and value are split at first " \t". */
1491 optlen = strcspn(opt, "#");
1495 if (opt[optlen] != 0) {
1496 config_msg = "No option value";
1500 /* Look for comment endings in the value. */
1501 size_t len = strcspn(value, "#");
1503 if (len < valuelen) {
1505 value[valuelen] = 0;
1508 status = set_option(opt, value);
1511 if (status == ERR) {
1512 fprintf(stderr, "Error on line %d, near '%.*s': %s\n",
1513 config_lineno, (int) optlen, opt, config_msg);
1514 config_errors = TRUE;
1517 /* Always keep going if errors are encountered. */
1522 load_option_file(const char *path)
1526 /* It's ok that the file doesn't exist. */
1527 file = fopen(path, "r");
1532 config_errors = FALSE;
1534 if (read_properties(file, " \t", read_option) == ERR ||
1535 config_errors == TRUE)
1536 fprintf(stderr, "Errors while loading %s.\n", path);
1542 const char *home = getenv("HOME");
1543 const char *tigrc_user = getenv("TIGRC_USER");
1544 const char *tigrc_system = getenv("TIGRC_SYSTEM");
1545 char buf[SIZEOF_STR];
1547 add_builtin_run_requests();
1549 if (!tigrc_system) {
1550 if (!string_format(buf, "%s/tigrc", SYSCONFDIR))
1554 load_option_file(tigrc_system);
1557 if (!home || !string_format(buf, "%s/.tigrc", home))
1561 load_option_file(tigrc_user);
1574 /* The display array of active views and the index of the current view. */
1575 static struct view *display[2];
1576 static unsigned int current_view;
1578 /* Reading from the prompt? */
1579 static bool input_mode = FALSE;
1581 #define foreach_displayed_view(view, i) \
1582 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1584 #define displayed_views() (display[1] != NULL ? 2 : 1)
1586 /* Current head and commit ID */
1587 static char ref_blob[SIZEOF_REF] = "";
1588 static char ref_commit[SIZEOF_REF] = "HEAD";
1589 static char ref_head[SIZEOF_REF] = "HEAD";
1592 const char *name; /* View name */
1593 const char *cmd_env; /* Command line set via environment */
1594 const char *id; /* Points to either of ref_{head,commit,blob} */
1596 struct view_ops *ops; /* View operations */
1598 enum keymap keymap; /* What keymap does this view have */
1599 bool git_dir; /* Whether the view requires a git directory. */
1601 char ref[SIZEOF_REF]; /* Hovered commit reference */
1602 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
1604 int height, width; /* The width and height of the main window */
1605 WINDOW *win; /* The main window */
1606 WINDOW *title; /* The title window living below the main window */
1609 unsigned long offset; /* Offset of the window top */
1610 unsigned long lineno; /* Current line number */
1613 char grep[SIZEOF_STR]; /* Search string */
1614 regex_t *regex; /* Pre-compiled regex */
1616 /* If non-NULL, points to the view that opened this view. If this view
1617 * is closed tig will switch back to the parent view. */
1618 struct view *parent;
1621 size_t lines; /* Total number of lines */
1622 struct line *line; /* Line index */
1623 size_t line_alloc; /* Total number of allocated lines */
1624 size_t line_size; /* Total number of used lines */
1625 unsigned int digits; /* Number of digits in the lines member. */
1628 struct line *curline; /* Line currently being drawn. */
1629 enum line_type curtype; /* Attribute currently used for drawing. */
1630 unsigned long col; /* Column when drawing. */
1639 /* What type of content being displayed. Used in the title bar. */
1641 /* Default command arguments. */
1643 /* Open and reads in all view content. */
1644 bool (*open)(struct view *view);
1645 /* Read one line; updates view->line. */
1646 bool (*read)(struct view *view, char *data);
1647 /* Draw one line; @lineno must be < view->height. */
1648 bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
1649 /* Depending on view handle a special requests. */
1650 enum request (*request)(struct view *view, enum request request, struct line *line);
1651 /* Search for regex in a line. */
1652 bool (*grep)(struct view *view, struct line *line);
1654 void (*select)(struct view *view, struct line *line);
1657 static struct view_ops blame_ops;
1658 static struct view_ops blob_ops;
1659 static struct view_ops diff_ops;
1660 static struct view_ops help_ops;
1661 static struct view_ops log_ops;
1662 static struct view_ops main_ops;
1663 static struct view_ops pager_ops;
1664 static struct view_ops stage_ops;
1665 static struct view_ops status_ops;
1666 static struct view_ops tree_ops;
1668 #define VIEW_STR(name, env, ref, ops, map, git) \
1669 { name, #env, ref, ops, map, git }
1671 #define VIEW_(id, name, ops, git, ref) \
1672 VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
1675 static struct view views[] = {
1676 VIEW_(MAIN, "main", &main_ops, TRUE, ref_head),
1677 VIEW_(DIFF, "diff", &diff_ops, TRUE, ref_commit),
1678 VIEW_(LOG, "log", &log_ops, TRUE, ref_head),
1679 VIEW_(TREE, "tree", &tree_ops, TRUE, ref_commit),
1680 VIEW_(BLOB, "blob", &blob_ops, TRUE, ref_blob),
1681 VIEW_(BLAME, "blame", &blame_ops, TRUE, ref_commit),
1682 VIEW_(HELP, "help", &help_ops, FALSE, ""),
1683 VIEW_(PAGER, "pager", &pager_ops, FALSE, "stdin"),
1684 VIEW_(STATUS, "status", &status_ops, TRUE, ""),
1685 VIEW_(STAGE, "stage", &stage_ops, TRUE, ""),
1688 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
1689 #define VIEW_REQ(view) ((view) - views + REQ_OFFSET + 1)
1691 #define foreach_view(view, i) \
1692 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1694 #define view_is_displayed(view) \
1695 (view == display[0] || view == display[1])
1702 static int line_graphics[] = {
1703 /* LINE_GRAPHIC_VLINE: */ '|'
1707 set_view_attr(struct view *view, enum line_type type)
1709 if (!view->curline->selected && view->curtype != type) {
1710 wattrset(view->win, get_line_attr(type));
1711 wchgat(view->win, -1, 0, type, NULL);
1712 view->curtype = type;
1717 draw_chars(struct view *view, enum line_type type, const char *string,
1718 int max_len, bool use_tilde)
1722 int trimmed = FALSE;
1728 len = utf8_length(string, &col, max_len, &trimmed, use_tilde);
1730 col = len = strlen(string);
1731 if (len > max_len) {
1735 col = len = max_len;
1740 set_view_attr(view, type);
1741 waddnstr(view->win, string, len);
1742 if (trimmed && use_tilde) {
1743 set_view_attr(view, LINE_DELIMITER);
1744 waddch(view->win, '~');
1752 draw_space(struct view *view, enum line_type type, int max, int spaces)
1754 static char space[] = " ";
1757 spaces = MIN(max, spaces);
1759 while (spaces > 0) {
1760 int len = MIN(spaces, sizeof(space) - 1);
1762 col += draw_chars(view, type, space, spaces, FALSE);
1770 draw_lineno(struct view *view, unsigned int lineno)
1773 int digits3 = view->digits < 3 ? 3 : view->digits;
1774 int max_number = MIN(digits3, STRING_SIZE(number));
1775 int max = view->width - view->col;
1778 if (max < max_number)
1781 lineno += view->offset + 1;
1782 if (lineno == 1 || (lineno % opt_num_interval) == 0) {
1783 static char fmt[] = "%1ld";
1785 if (view->digits <= 9)
1786 fmt[1] = '0' + digits3;
1788 if (!string_format(number, fmt, lineno))
1790 col = draw_chars(view, LINE_LINE_NUMBER, number, max_number, TRUE);
1792 col = draw_space(view, LINE_LINE_NUMBER, max_number, max_number);
1796 set_view_attr(view, LINE_DEFAULT);
1797 waddch(view->win, line_graphics[LINE_GRAPHIC_VLINE]);
1802 col += draw_space(view, LINE_DEFAULT, max - col, 1);
1805 return view->width - view->col <= 0;
1809 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
1811 view->col += draw_chars(view, type, string, view->width - view->col, trim);
1812 return view->width - view->col <= 0;
1816 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
1818 int max = view->width - view->col;
1824 set_view_attr(view, type);
1825 /* Using waddch() instead of waddnstr() ensures that
1826 * they'll be rendered correctly for the cursor line. */
1827 for (i = 0; i < size; i++)
1828 waddch(view->win, graphic[i]);
1832 waddch(view->win, ' ');
1836 return view->width - view->col <= 0;
1840 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
1842 int max = MIN(view->width - view->col, len);
1846 col = draw_chars(view, type, text, max - 1, trim);
1848 col = draw_space(view, type, max - 1, max - 1);
1850 view->col += col + draw_space(view, LINE_DEFAULT, max - col, max - col);
1851 return view->width - view->col <= 0;
1855 draw_date(struct view *view, struct tm *time)
1857 char buf[DATE_COLS];
1862 timelen = strftime(buf, sizeof(buf), DATE_FORMAT, time);
1863 date = timelen ? buf : NULL;
1865 return draw_field(view, LINE_DATE, date, DATE_COLS, FALSE);
1869 draw_view_line(struct view *view, unsigned int lineno)
1872 bool selected = (view->offset + lineno == view->lineno);
1875 assert(view_is_displayed(view));
1877 if (view->offset + lineno >= view->lines)
1880 line = &view->line[view->offset + lineno];
1882 wmove(view->win, lineno, 0);
1884 view->curline = line;
1885 view->curtype = LINE_NONE;
1886 line->selected = FALSE;
1889 set_view_attr(view, LINE_CURSOR);
1890 line->selected = TRUE;
1891 view->ops->select(view, line);
1892 } else if (line->selected) {
1893 wclrtoeol(view->win);
1896 scrollok(view->win, FALSE);
1897 draw_ok = view->ops->draw(view, line, lineno);
1898 scrollok(view->win, TRUE);
1904 redraw_view_dirty(struct view *view)
1909 for (lineno = 0; lineno < view->height; lineno++) {
1910 struct line *line = &view->line[view->offset + lineno];
1916 if (!draw_view_line(view, lineno))
1922 redrawwin(view->win);
1924 wnoutrefresh(view->win);
1926 wrefresh(view->win);
1930 redraw_view_from(struct view *view, int lineno)
1932 assert(0 <= lineno && lineno < view->height);
1934 for (; lineno < view->height; lineno++) {
1935 if (!draw_view_line(view, lineno))
1939 redrawwin(view->win);
1941 wnoutrefresh(view->win);
1943 wrefresh(view->win);
1947 redraw_view(struct view *view)
1950 redraw_view_from(view, 0);
1955 update_view_title(struct view *view)
1957 char buf[SIZEOF_STR];
1958 char state[SIZEOF_STR];
1959 size_t bufpos = 0, statelen = 0;
1961 assert(view_is_displayed(view));
1963 if (view != VIEW(REQ_VIEW_STATUS) && (view->lines || view->pipe)) {
1964 unsigned int view_lines = view->offset + view->height;
1965 unsigned int lines = view->lines
1966 ? MIN(view_lines, view->lines) * 100 / view->lines
1969 string_format_from(state, &statelen, "- %s %d of %d (%d%%)",
1976 time_t secs = time(NULL) - view->start_time;
1978 /* Three git seconds are a long time ... */
1980 string_format_from(state, &statelen, " %lds", secs);
1984 string_format_from(buf, &bufpos, "[%s]", view->name);
1985 if (*view->ref && bufpos < view->width) {
1986 size_t refsize = strlen(view->ref);
1987 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
1989 if (minsize < view->width)
1990 refsize = view->width - minsize + 7;
1991 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
1994 if (statelen && bufpos < view->width) {
1995 string_format_from(buf, &bufpos, " %s", state);
1998 if (view == display[current_view])
1999 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2001 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2003 mvwaddnstr(view->title, 0, 0, buf, bufpos);
2004 wclrtoeol(view->title);
2005 wmove(view->title, 0, view->width - 1);
2008 wnoutrefresh(view->title);
2010 wrefresh(view->title);
2014 resize_display(void)
2017 struct view *base = display[0];
2018 struct view *view = display[1] ? display[1] : display[0];
2020 /* Setup window dimensions */
2022 getmaxyx(stdscr, base->height, base->width);
2024 /* Make room for the status window. */
2028 /* Horizontal split. */
2029 view->width = base->width;
2030 view->height = SCALE_SPLIT_VIEW(base->height);
2031 base->height -= view->height;
2033 /* Make room for the title bar. */
2037 /* Make room for the title bar. */
2042 foreach_displayed_view (view, i) {
2044 view->win = newwin(view->height, 0, offset, 0);
2046 die("Failed to create %s view", view->name);
2048 scrollok(view->win, TRUE);
2050 view->title = newwin(1, 0, offset + view->height, 0);
2052 die("Failed to create title window");
2055 wresize(view->win, view->height, view->width);
2056 mvwin(view->win, offset, 0);
2057 mvwin(view->title, offset + view->height, 0);
2060 offset += view->height + 1;
2065 redraw_display(void)
2070 foreach_displayed_view (view, i) {
2072 update_view_title(view);
2077 update_display_cursor(struct view *view)
2079 /* Move the cursor to the right-most column of the cursor line.
2081 * XXX: This could turn out to be a bit expensive, but it ensures that
2082 * the cursor does not jump around. */
2084 wmove(view->win, view->lineno - view->offset, view->width - 1);
2085 wrefresh(view->win);
2093 /* Scrolling backend */
2095 do_scroll_view(struct view *view, int lines)
2097 bool redraw_current_line = FALSE;
2099 /* The rendering expects the new offset. */
2100 view->offset += lines;
2102 assert(0 <= view->offset && view->offset < view->lines);
2105 /* Move current line into the view. */
2106 if (view->lineno < view->offset) {
2107 view->lineno = view->offset;
2108 redraw_current_line = TRUE;
2109 } else if (view->lineno >= view->offset + view->height) {
2110 view->lineno = view->offset + view->height - 1;
2111 redraw_current_line = TRUE;
2114 assert(view->offset <= view->lineno && view->lineno < view->lines);
2116 /* Redraw the whole screen if scrolling is pointless. */
2117 if (view->height < ABS(lines)) {
2121 int line = lines > 0 ? view->height - lines : 0;
2122 int end = line + ABS(lines);
2124 wscrl(view->win, lines);
2126 for (; line < end; line++) {
2127 if (!draw_view_line(view, line))
2131 if (redraw_current_line)
2132 draw_view_line(view, view->lineno - view->offset);
2135 redrawwin(view->win);
2136 wrefresh(view->win);
2140 /* Scroll frontend */
2142 scroll_view(struct view *view, enum request request)
2146 assert(view_is_displayed(view));
2149 case REQ_SCROLL_PAGE_DOWN:
2150 lines = view->height;
2151 case REQ_SCROLL_LINE_DOWN:
2152 if (view->offset + lines > view->lines)
2153 lines = view->lines - view->offset;
2155 if (lines == 0 || view->offset + view->height >= view->lines) {
2156 report("Cannot scroll beyond the last line");
2161 case REQ_SCROLL_PAGE_UP:
2162 lines = view->height;
2163 case REQ_SCROLL_LINE_UP:
2164 if (lines > view->offset)
2165 lines = view->offset;
2168 report("Cannot scroll beyond the first line");
2176 die("request %d not handled in switch", request);
2179 do_scroll_view(view, lines);
2184 move_view(struct view *view, enum request request)
2186 int scroll_steps = 0;
2190 case REQ_MOVE_FIRST_LINE:
2191 steps = -view->lineno;
2194 case REQ_MOVE_LAST_LINE:
2195 steps = view->lines - view->lineno - 1;
2198 case REQ_MOVE_PAGE_UP:
2199 steps = view->height > view->lineno
2200 ? -view->lineno : -view->height;
2203 case REQ_MOVE_PAGE_DOWN:
2204 steps = view->lineno + view->height >= view->lines
2205 ? view->lines - view->lineno - 1 : view->height;
2217 die("request %d not handled in switch", request);
2220 if (steps <= 0 && view->lineno == 0) {
2221 report("Cannot move beyond the first line");
2224 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2225 report("Cannot move beyond the last line");
2229 /* Move the current line */
2230 view->lineno += steps;
2231 assert(0 <= view->lineno && view->lineno < view->lines);
2233 /* Check whether the view needs to be scrolled */
2234 if (view->lineno < view->offset ||
2235 view->lineno >= view->offset + view->height) {
2236 scroll_steps = steps;
2237 if (steps < 0 && -steps > view->offset) {
2238 scroll_steps = -view->offset;
2240 } else if (steps > 0) {
2241 if (view->lineno == view->lines - 1 &&
2242 view->lines > view->height) {
2243 scroll_steps = view->lines - view->offset - 1;
2244 if (scroll_steps >= view->height)
2245 scroll_steps -= view->height - 1;
2250 if (!view_is_displayed(view)) {
2251 view->offset += scroll_steps;
2252 assert(0 <= view->offset && view->offset < view->lines);
2253 view->ops->select(view, &view->line[view->lineno]);
2257 /* Repaint the old "current" line if we be scrolling */
2258 if (ABS(steps) < view->height)
2259 draw_view_line(view, view->lineno - steps - view->offset);
2262 do_scroll_view(view, scroll_steps);
2266 /* Draw the current line */
2267 draw_view_line(view, view->lineno - view->offset);
2269 redrawwin(view->win);
2270 wrefresh(view->win);
2279 static void search_view(struct view *view, enum request request);
2282 find_next_line(struct view *view, unsigned long lineno, struct line *line)
2284 assert(view_is_displayed(view));
2286 if (!view->ops->grep(view, line))
2289 if (lineno - view->offset >= view->height) {
2290 view->offset = lineno;
2291 view->lineno = lineno;
2295 unsigned long old_lineno = view->lineno - view->offset;
2297 view->lineno = lineno;
2298 draw_view_line(view, old_lineno);
2300 draw_view_line(view, view->lineno - view->offset);
2301 redrawwin(view->win);
2302 wrefresh(view->win);
2305 report("Line %ld matches '%s'", lineno + 1, view->grep);
2310 find_next(struct view *view, enum request request)
2312 unsigned long lineno = view->lineno;
2317 report("No previous search");
2319 search_view(view, request);
2329 case REQ_SEARCH_BACK:
2338 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2339 lineno += direction;
2341 /* Note, lineno is unsigned long so will wrap around in which case it
2342 * will become bigger than view->lines. */
2343 for (; lineno < view->lines; lineno += direction) {
2344 struct line *line = &view->line[lineno];
2346 if (find_next_line(view, lineno, line))
2350 report("No match found for '%s'", view->grep);
2354 search_view(struct view *view, enum request request)
2359 regfree(view->regex);
2362 view->regex = calloc(1, sizeof(*view->regex));
2367 regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2368 if (regex_err != 0) {
2369 char buf[SIZEOF_STR] = "unknown error";
2371 regerror(regex_err, view->regex, buf, sizeof(buf));
2372 report("Search failed: %s", buf);
2376 string_copy(view->grep, opt_search);
2378 find_next(view, request);
2382 * Incremental updating
2386 reset_view(struct view *view)
2390 for (i = 0; i < view->lines; i++)
2391 free(view->line[i].data);
2398 view->line_size = 0;
2399 view->line_alloc = 0;
2404 free_argv(const char *argv[])
2408 for (argc = 0; argv[argc]; argc++)
2409 free((void *) argv[argc]);
2413 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
2415 char buf[SIZEOF_STR];
2417 bool noreplace = flags == FORMAT_NONE;
2419 free_argv(dst_argv);
2421 for (argc = 0; src_argv[argc]; argc++) {
2422 const char *arg = src_argv[argc];
2426 char *next = strstr(arg, "%(");
2427 int len = next - arg;
2430 if (!next || noreplace) {
2431 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
2436 } else if (!prefixcmp(next, "%(directory)")) {
2439 } else if (!prefixcmp(next, "%(file)")) {
2442 } else if (!prefixcmp(next, "%(ref)")) {
2443 value = *opt_ref ? opt_ref : "HEAD";
2445 } else if (!prefixcmp(next, "%(head)")) {
2448 } else if (!prefixcmp(next, "%(commit)")) {
2451 } else if (!prefixcmp(next, "%(blob)")) {
2455 report("Unknown replacement: `%s`", next);
2459 if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
2462 arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
2465 dst_argv[argc] = strdup(buf);
2466 if (!dst_argv[argc])
2470 dst_argv[argc] = NULL;
2472 return src_argv[argc] == NULL;
2476 format_command(char dst[], const char *src_argv[], enum format_flags flags)
2478 const char *dst_argv[SIZEOF_ARG * 2] = { NULL };
2482 if (!format_argv(dst_argv, src_argv, flags)) {
2483 free_argv(dst_argv);
2487 for (argc = 0; dst_argv[argc] && bufsize < SIZEOF_STR; argc++) {
2489 dst[bufsize++] = ' ';
2490 bufsize = sq_quote(dst, bufsize, dst_argv[argc]);
2493 if (bufsize < SIZEOF_STR)
2495 free_argv(dst_argv);
2497 return src_argv[argc] == NULL && bufsize < SIZEOF_STR;
2501 end_update(struct view *view, bool force)
2505 while (!view->ops->read(view, NULL))
2508 set_nonblocking_input(FALSE);
2509 done_io(view->pipe);
2514 setup_update(struct view *view, const char *vid)
2516 set_nonblocking_input(TRUE);
2518 string_copy_rev(view->vid, vid);
2519 view->pipe = &view->io;
2520 view->start_time = time(NULL);
2524 prepare_update(struct view *view, const char *argv[], const char *dir,
2525 enum format_flags flags)
2528 end_update(view, TRUE);
2529 return init_io_rd(&view->io, argv, dir, flags);
2533 begin_update(struct view *view, bool refresh)
2535 if (init_io_fd(&view->io, opt_pipe)) {
2538 } else if (opt_cmd[0]) {
2539 if (!run_io(&view->io, IO_RD, opt_cmd))
2544 } else if (refresh) {
2545 if (!start_io(&view->io))
2549 if (view == VIEW(REQ_VIEW_TREE) && strcmp(view->vid, view->id))
2552 if (!run_io_rd(&view->io, view->ops->argv, FORMAT_ALL))
2555 /* Put the current ref_* value to the view title ref
2556 * member. This is needed by the blob view. Most other
2557 * views sets it automatically after loading because the
2558 * first line is a commit line. */
2559 string_copy_rev(view->ref, view->id);
2562 setup_update(view, view->id);
2567 #define ITEM_CHUNK_SIZE 256
2569 realloc_items(void *mem, size_t *size, size_t new_size, size_t item_size)
2571 size_t num_chunks = *size / ITEM_CHUNK_SIZE;
2572 size_t num_chunks_new = (new_size + ITEM_CHUNK_SIZE - 1) / ITEM_CHUNK_SIZE;
2574 if (mem == NULL || num_chunks != num_chunks_new) {
2575 *size = num_chunks_new * ITEM_CHUNK_SIZE;
2576 mem = realloc(mem, *size * item_size);
2582 static struct line *
2583 realloc_lines(struct view *view, size_t line_size)
2585 size_t alloc = view->line_alloc;
2586 struct line *tmp = realloc_items(view->line, &alloc, line_size,
2587 sizeof(*view->line));
2593 view->line_alloc = alloc;
2594 view->line_size = line_size;
2599 update_view(struct view *view)
2601 char out_buffer[BUFSIZ * 2];
2603 /* The number of lines to read. If too low it will cause too much
2604 * redrawing (and possible flickering), if too high responsiveness
2606 unsigned long lines = view->height;
2607 int redraw_from = -1;
2612 /* Only redraw if lines are visible. */
2613 if (view->offset + view->height >= view->lines)
2614 redraw_from = view->lines - view->offset;
2616 /* FIXME: This is probably not perfect for backgrounded views. */
2617 if (!realloc_lines(view, view->lines + lines))
2620 while ((line = io_gets(view->pipe))) {
2621 size_t linelen = strlen(line);
2624 line[linelen - 1] = 0;
2626 if (opt_iconv != ICONV_NONE) {
2627 ICONV_CONST char *inbuf = line;
2628 size_t inlen = linelen;
2630 char *outbuf = out_buffer;
2631 size_t outlen = sizeof(out_buffer);
2635 ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2636 if (ret != (size_t) -1) {
2638 linelen = strlen(out_buffer);
2642 if (!view->ops->read(view, line))
2652 lines = view->lines;
2653 for (digits = 0; lines; digits++)
2656 /* Keep the displayed view in sync with line number scaling. */
2657 if (digits != view->digits) {
2658 view->digits = digits;
2663 if (io_error(view->pipe)) {
2664 report("Failed to read: %s", io_strerror(view->pipe));
2665 end_update(view, TRUE);
2667 } else if (io_eof(view->pipe)) {
2669 end_update(view, FALSE);
2672 if (!view_is_displayed(view))
2675 if (view == VIEW(REQ_VIEW_TREE)) {
2676 /* Clear the view and redraw everything since the tree sorting
2677 * might have rearranged things. */
2680 } else if (redraw_from >= 0) {
2681 /* If this is an incremental update, redraw the previous line
2682 * since for commits some members could have changed when
2683 * loading the main view. */
2684 if (redraw_from > 0)
2687 /* Since revision graph visualization requires knowledge
2688 * about the parent commit, it causes a further one-off
2689 * needed to be redrawn for incremental updates. */
2690 if (redraw_from > 0 && opt_rev_graph)
2693 /* Incrementally draw avoids flickering. */
2694 redraw_view_from(view, redraw_from);
2697 if (view == VIEW(REQ_VIEW_BLAME))
2698 redraw_view_dirty(view);
2700 /* Update the title _after_ the redraw so that if the redraw picks up a
2701 * commit reference in view->ref it'll be available here. */
2702 update_view_title(view);
2706 report("Allocation failure");
2707 end_update(view, TRUE);
2711 static struct line *
2712 add_line_data(struct view *view, void *data, enum line_type type)
2714 struct line *line = &view->line[view->lines++];
2716 memset(line, 0, sizeof(*line));
2723 static struct line *
2724 add_line_text(struct view *view, const char *text, enum line_type type)
2726 char *data = text ? strdup(text) : NULL;
2728 return data ? add_line_data(view, data, type) : NULL;
2737 OPEN_DEFAULT = 0, /* Use default view switching. */
2738 OPEN_SPLIT = 1, /* Split current view. */
2739 OPEN_BACKGROUNDED = 2, /* Backgrounded. */
2740 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
2741 OPEN_NOMAXIMIZE = 8, /* Do not maximize the current view. */
2742 OPEN_REFRESH = 16, /* Refresh view using previous command. */
2743 OPEN_PREPARED = 32, /* Open already prepared command. */
2747 open_view(struct view *prev, enum request request, enum open_flags flags)
2749 bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
2750 bool split = !!(flags & OPEN_SPLIT);
2751 bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
2752 bool nomaximize = !!(flags & (OPEN_NOMAXIMIZE | OPEN_REFRESH));
2753 struct view *view = VIEW(request);
2754 int nviews = displayed_views();
2755 struct view *base_view = display[0];
2757 if (view == prev && nviews == 1 && !reload) {
2758 report("Already in %s view", view->name);
2762 if (view->git_dir && !opt_git_dir[0]) {
2763 report("The %s view is disabled in pager view", view->name);
2771 } else if (!nomaximize) {
2772 /* Maximize the current view. */
2773 memset(display, 0, sizeof(display));
2775 display[current_view] = view;
2778 /* Resize the view when switching between split- and full-screen,
2779 * or when switching between two different full-screen views. */
2780 if (nviews != displayed_views() ||
2781 (nviews == 1 && base_view != display[0]))
2785 end_update(view, TRUE);
2787 if (view->ops->open) {
2788 if (!view->ops->open(view)) {
2789 report("Failed to load %s view", view->name);
2793 } else if ((reload || strcmp(view->vid, view->id)) &&
2794 !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
2795 report("Failed to load %s view", view->name);
2799 if (split && prev->lineno - prev->offset >= prev->height) {
2800 /* Take the title line into account. */
2801 int lines = prev->lineno - prev->offset - prev->height + 1;
2803 /* Scroll the view that was split if the current line is
2804 * outside the new limited view. */
2805 do_scroll_view(prev, lines);
2808 if (prev && view != prev) {
2809 if (split && !backgrounded) {
2810 /* "Blur" the previous view. */
2811 update_view_title(prev);
2814 view->parent = prev;
2817 if (view->pipe && view->lines == 0) {
2818 /* Clear the old view and let the incremental updating refill
2822 } else if (view_is_displayed(view)) {
2827 /* If the view is backgrounded the above calls to report()
2828 * won't redraw the view title. */
2830 update_view_title(view);
2834 open_external_viewer(const char *argv[], const char *dir)
2836 def_prog_mode(); /* save current tty modes */
2837 endwin(); /* restore original tty modes */
2838 run_io_fg(argv, dir);
2839 fprintf(stderr, "Press Enter to continue");
2846 open_mergetool(const char *file)
2848 const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
2850 open_external_viewer(mergetool_argv, NULL);
2854 open_editor(bool from_root, const char *file)
2856 const char *editor_argv[] = { "vi", file, NULL };
2859 editor = getenv("GIT_EDITOR");
2860 if (!editor && *opt_editor)
2861 editor = opt_editor;
2863 editor = getenv("VISUAL");
2865 editor = getenv("EDITOR");
2869 editor_argv[0] = editor;
2870 open_external_viewer(editor_argv, from_root ? opt_cdup : NULL);
2874 open_run_request(enum request request)
2876 struct run_request *req = get_run_request(request);
2877 const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
2880 report("Unknown run request");
2884 if (format_argv(argv, req->argv, FORMAT_ALL))
2885 open_external_viewer(argv, NULL);
2890 * User request switch noodle
2894 view_driver(struct view *view, enum request request)
2898 if (request == REQ_NONE) {
2903 if (request > REQ_NONE) {
2904 open_run_request(request);
2905 /* FIXME: When all views can refresh always do this. */
2906 if (view == VIEW(REQ_VIEW_STATUS) ||
2907 view == VIEW(REQ_VIEW_MAIN) ||
2908 view == VIEW(REQ_VIEW_LOG) ||
2909 view == VIEW(REQ_VIEW_STAGE))
2910 request = REQ_REFRESH;
2915 if (view && view->lines) {
2916 request = view->ops->request(view, request, &view->line[view->lineno]);
2917 if (request == REQ_NONE)
2924 case REQ_MOVE_PAGE_UP:
2925 case REQ_MOVE_PAGE_DOWN:
2926 case REQ_MOVE_FIRST_LINE:
2927 case REQ_MOVE_LAST_LINE:
2928 move_view(view, request);
2931 case REQ_SCROLL_LINE_DOWN:
2932 case REQ_SCROLL_LINE_UP:
2933 case REQ_SCROLL_PAGE_DOWN:
2934 case REQ_SCROLL_PAGE_UP:
2935 scroll_view(view, request);
2938 case REQ_VIEW_BLAME:
2940 report("No file chosen, press %s to open tree view",
2941 get_key(REQ_VIEW_TREE));
2944 open_view(view, request, OPEN_DEFAULT);
2949 report("No file chosen, press %s to open tree view",
2950 get_key(REQ_VIEW_TREE));
2953 open_view(view, request, OPEN_DEFAULT);
2956 case REQ_VIEW_PAGER:
2957 if (!opt_pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
2958 report("No pager content, press %s to run command from prompt",
2959 get_key(REQ_PROMPT));
2962 open_view(view, request, OPEN_DEFAULT);
2965 case REQ_VIEW_STAGE:
2966 if (!VIEW(REQ_VIEW_STAGE)->lines) {
2967 report("No stage content, press %s to open the status view and choose file",
2968 get_key(REQ_VIEW_STATUS));
2971 open_view(view, request, OPEN_DEFAULT);
2974 case REQ_VIEW_STATUS:
2975 if (opt_is_inside_work_tree == FALSE) {
2976 report("The status view requires a working tree");
2979 open_view(view, request, OPEN_DEFAULT);
2987 open_view(view, request, OPEN_DEFAULT);
2992 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
2994 if ((view == VIEW(REQ_VIEW_DIFF) &&
2995 view->parent == VIEW(REQ_VIEW_MAIN)) ||
2996 (view == VIEW(REQ_VIEW_DIFF) &&
2997 view->parent == VIEW(REQ_VIEW_BLAME)) ||
2998 (view == VIEW(REQ_VIEW_STAGE) &&
2999 view->parent == VIEW(REQ_VIEW_STATUS)) ||
3000 (view == VIEW(REQ_VIEW_BLOB) &&
3001 view->parent == VIEW(REQ_VIEW_TREE))) {
3004 view = view->parent;
3005 line = view->lineno;
3006 move_view(view, request);
3007 if (view_is_displayed(view))
3008 update_view_title(view);
3009 if (line != view->lineno)
3010 view->ops->request(view, REQ_ENTER,
3011 &view->line[view->lineno]);
3014 move_view(view, request);
3020 int nviews = displayed_views();
3021 int next_view = (current_view + 1) % nviews;
3023 if (next_view == current_view) {
3024 report("Only one view is displayed");
3028 current_view = next_view;
3029 /* Blur out the title of the previous view. */
3030 update_view_title(view);
3035 report("Refreshing is not yet supported for the %s view", view->name);
3039 if (displayed_views() == 2)
3040 open_view(view, VIEW_REQ(view), OPEN_DEFAULT);
3043 case REQ_TOGGLE_LINENO:
3044 opt_line_number = !opt_line_number;
3048 case REQ_TOGGLE_DATE:
3049 opt_date = !opt_date;
3053 case REQ_TOGGLE_AUTHOR:
3054 opt_author = !opt_author;
3058 case REQ_TOGGLE_REV_GRAPH:
3059 opt_rev_graph = !opt_rev_graph;
3063 case REQ_TOGGLE_REFS:
3064 opt_show_refs = !opt_show_refs;
3069 case REQ_SEARCH_BACK:
3070 search_view(view, request);
3075 find_next(view, request);
3078 case REQ_STOP_LOADING:
3079 for (i = 0; i < ARRAY_SIZE(views); i++) {
3082 report("Stopped loading the %s view", view->name),
3083 end_update(view, TRUE);
3087 case REQ_SHOW_VERSION:
3088 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3091 case REQ_SCREEN_RESIZE:
3094 case REQ_SCREEN_REDRAW:
3099 report("Nothing to edit");
3103 report("Nothing to enter");
3106 case REQ_VIEW_CLOSE:
3107 /* XXX: Mark closed views by letting view->parent point to the
3108 * view itself. Parents to closed view should never be
3111 view->parent->parent != view->parent) {
3112 memset(display, 0, sizeof(display));
3114 display[current_view] = view->parent;
3115 view->parent = view;
3126 report("Unknown key, press 'h' for help");
3139 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3141 char *text = line->data;
3143 if (opt_line_number && draw_lineno(view, lineno))
3146 draw_text(view, line->type, text, TRUE);
3151 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3153 const char *describe_argv[] = { "git", "describe", commit_id, NULL };
3154 char refbuf[SIZEOF_STR];
3157 if (run_io_buf(describe_argv, refbuf, sizeof(refbuf)))
3158 ref = chomp_string(refbuf);
3163 /* This is the only fatal call, since it can "corrupt" the buffer. */
3164 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
3171 add_pager_refs(struct view *view, struct line *line)
3173 char buf[SIZEOF_STR];
3174 char *commit_id = (char *)line->data + STRING_SIZE("commit ");
3176 size_t bufpos = 0, refpos = 0;
3177 const char *sep = "Refs: ";
3178 bool is_tag = FALSE;
3180 assert(line->type == LINE_COMMIT);
3182 refs = get_refs(commit_id);
3184 if (view == VIEW(REQ_VIEW_DIFF))
3185 goto try_add_describe_ref;
3190 struct ref *ref = refs[refpos];
3191 const char *fmt = ref->tag ? "%s[%s]" :
3192 ref->remote ? "%s<%s>" : "%s%s";
3194 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
3199 } while (refs[refpos++]->next);
3201 if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
3202 try_add_describe_ref:
3203 /* Add <tag>-g<commit_id> "fake" reference. */
3204 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
3211 if (!realloc_lines(view, view->line_size + 1))
3214 add_line_text(view, buf, LINE_PP_REFS);
3218 pager_read(struct view *view, char *data)
3225 line = add_line_text(view, data, get_line_type(data));
3229 if (line->type == LINE_COMMIT &&
3230 (view == VIEW(REQ_VIEW_DIFF) ||
3231 view == VIEW(REQ_VIEW_LOG)))
3232 add_pager_refs(view, line);
3238 pager_request(struct view *view, enum request request, struct line *line)
3242 if (request != REQ_ENTER)
3245 if (line->type == LINE_COMMIT &&
3246 (view == VIEW(REQ_VIEW_LOG) ||
3247 view == VIEW(REQ_VIEW_PAGER))) {
3248 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3252 /* Always scroll the view even if it was split. That way
3253 * you can use Enter to scroll through the log view and
3254 * split open each commit diff. */
3255 scroll_view(view, REQ_SCROLL_LINE_DOWN);
3257 /* FIXME: A minor workaround. Scrolling the view will call report("")
3258 * but if we are scrolling a non-current view this won't properly
3259 * update the view title. */
3261 update_view_title(view);
3267 pager_grep(struct view *view, struct line *line)
3270 char *text = line->data;
3275 if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
3282 pager_select(struct view *view, struct line *line)
3284 if (line->type == LINE_COMMIT) {
3285 char *text = (char *)line->data + STRING_SIZE("commit ");
3287 if (view != VIEW(REQ_VIEW_PAGER))
3288 string_copy_rev(view->ref, text);
3289 string_copy_rev(ref_commit, text);
3293 static struct view_ops pager_ops = {
3304 static const char *log_argv[SIZEOF_ARG] = {
3305 "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3309 log_request(struct view *view, enum request request, struct line *line)
3314 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
3317 return pager_request(view, request, line);
3321 static struct view_ops log_ops = {
3332 static const char *diff_argv[SIZEOF_ARG] = {
3333 "git", "show", "--pretty=fuller", "--no-color", "--root",
3334 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
3337 static struct view_ops diff_ops = {
3353 help_open(struct view *view)
3356 int lines = ARRAY_SIZE(req_info) + 2;
3359 if (view->lines > 0)
3362 for (i = 0; i < ARRAY_SIZE(req_info); i++)
3363 if (!req_info[i].request)
3366 lines += run_requests + 1;
3368 view->line = calloc(lines, sizeof(*view->line));
3372 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
3374 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
3377 if (req_info[i].request == REQ_NONE)
3380 if (!req_info[i].request) {
3381 add_line_text(view, "", LINE_DEFAULT);
3382 add_line_text(view, req_info[i].help, LINE_DEFAULT);
3386 key = get_key(req_info[i].request);
3388 key = "(no key defined)";
3390 if (!string_format(buf, " %-25s %s", key, req_info[i].help))
3393 add_line_text(view, buf, LINE_DEFAULT);
3397 add_line_text(view, "", LINE_DEFAULT);
3398 add_line_text(view, "External commands:", LINE_DEFAULT);
3401 for (i = 0; i < run_requests; i++) {
3402 struct run_request *req = get_run_request(REQ_NONE + i + 1);
3404 char cmd[SIZEOF_STR];
3411 key = get_key_name(req->key);
3413 key = "(no key defined)";
3415 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
3416 if (!string_format_from(cmd, &bufpos, "%s%s",
3417 argc ? " " : "", req->argv[argc]))
3420 if (!string_format(buf, " %-10s %-14s `%s`",
3421 keymap_table[req->keymap].name, key, cmd))
3424 add_line_text(view, buf, LINE_DEFAULT);
3430 static struct view_ops help_ops = {
3446 struct tree_stack_entry {
3447 struct tree_stack_entry *prev; /* Entry below this in the stack */
3448 unsigned long lineno; /* Line number to restore */
3449 char *name; /* Position of name in opt_path */
3452 /* The top of the path stack. */
3453 static struct tree_stack_entry *tree_stack = NULL;
3454 unsigned long tree_lineno = 0;
3457 pop_tree_stack_entry(void)
3459 struct tree_stack_entry *entry = tree_stack;
3461 tree_lineno = entry->lineno;
3463 tree_stack = entry->prev;
3468 push_tree_stack_entry(const char *name, unsigned long lineno)
3470 struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
3471 size_t pathlen = strlen(opt_path);
3476 entry->prev = tree_stack;
3477 entry->name = opt_path + pathlen;
3480 if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
3481 pop_tree_stack_entry();
3485 /* Move the current line to the first tree entry. */
3487 entry->lineno = lineno;
3490 /* Parse output from git-ls-tree(1):
3492 * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile
3493 * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README
3494 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
3495 * 100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38 web.conf
3498 #define SIZEOF_TREE_ATTR \
3499 STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t")
3501 #define TREE_UP_FORMAT "040000 tree %s\t.."
3504 tree_compare_entry(enum line_type type1, const char *name1,
3505 enum line_type type2, const char *name2)
3507 if (type1 != type2) {
3508 if (type1 == LINE_TREE_DIR)
3513 return strcmp(name1, name2);
3517 tree_path(struct line *line)
3519 const char *path = line->data;
3521 return path + SIZEOF_TREE_ATTR;
3525 tree_read(struct view *view, char *text)
3527 size_t textlen = text ? strlen(text) : 0;
3528 char buf[SIZEOF_STR];
3530 enum line_type type;
3531 bool first_read = view->lines == 0;
3535 if (textlen <= SIZEOF_TREE_ATTR)
3538 type = text[STRING_SIZE("100644 ")] == 't'
3539 ? LINE_TREE_DIR : LINE_TREE_FILE;
3542 /* Add path info line */
3543 if (!string_format(buf, "Directory path /%s", opt_path) ||
3544 !realloc_lines(view, view->line_size + 1) ||
3545 !add_line_text(view, buf, LINE_DEFAULT))
3548 /* Insert "link" to parent directory. */
3550 if (!string_format(buf, TREE_UP_FORMAT, view->ref) ||
3551 !realloc_lines(view, view->line_size + 1) ||
3552 !add_line_text(view, buf, LINE_TREE_DIR))
3557 /* Strip the path part ... */
3559 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
3560 size_t striplen = strlen(opt_path);
3561 char *path = text + SIZEOF_TREE_ATTR;
3563 if (pathlen > striplen)
3564 memmove(path, path + striplen,
3565 pathlen - striplen + 1);
3568 /* Skip "Directory ..." and ".." line. */
3569 for (pos = 1 + !!*opt_path; pos < view->lines; pos++) {
3570 struct line *line = &view->line[pos];
3571 const char *path1 = tree_path(line);
3572 char *path2 = text + SIZEOF_TREE_ATTR;
3573 int cmp = tree_compare_entry(line->type, path1, type, path2);
3578 text = strdup(text);
3582 if (view->lines > pos)
3583 memmove(&view->line[pos + 1], &view->line[pos],
3584 (view->lines - pos) * sizeof(*line));
3586 line = &view->line[pos];
3593 if (!add_line_text(view, text, type))
3596 if (tree_lineno > view->lineno) {
3597 view->lineno = tree_lineno;
3605 tree_request(struct view *view, enum request request, struct line *line)
3607 enum open_flags flags;
3610 case REQ_VIEW_BLAME:
3611 if (line->type != LINE_TREE_FILE) {
3612 report("Blame only supported for files");
3616 string_copy(opt_ref, view->vid);
3620 if (line->type != LINE_TREE_FILE) {
3621 report("Edit only supported for files");
3622 } else if (!is_head_commit(view->vid)) {
3623 report("Edit only supported for files in the current work tree");
3625 open_editor(TRUE, opt_file);
3629 case REQ_TREE_PARENT:
3631 /* quit view if at top of tree */
3632 return REQ_VIEW_CLOSE;
3635 line = &view->line[1];
3645 /* Cleanup the stack if the tree view is at a different tree. */
3646 while (!*opt_path && tree_stack)
3647 pop_tree_stack_entry();
3649 switch (line->type) {
3651 /* Depending on whether it is a subdir or parent (updir?) link
3652 * mangle the path buffer. */
3653 if (line == &view->line[1] && *opt_path) {
3654 pop_tree_stack_entry();
3657 const char *basename = tree_path(line);
3659 push_tree_stack_entry(basename, view->lineno);
3662 /* Trees and subtrees share the same ID, so they are not not
3663 * unique like blobs. */
3664 flags = OPEN_RELOAD;
3665 request = REQ_VIEW_TREE;
3668 case LINE_TREE_FILE:
3669 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
3670 request = REQ_VIEW_BLOB;
3677 open_view(view, request, flags);
3678 if (request == REQ_VIEW_TREE) {
3679 view->lineno = tree_lineno;
3686 tree_select(struct view *view, struct line *line)
3688 char *text = (char *)line->data + STRING_SIZE("100644 blob ");
3690 if (line->type == LINE_TREE_FILE) {
3691 string_copy_rev(ref_blob, text);
3692 string_format(opt_file, "%s%s", opt_path, tree_path(line));
3694 } else if (line->type != LINE_TREE_DIR) {
3698 string_copy_rev(view->ref, text);
3701 static const char *tree_argv[SIZEOF_ARG] = {
3702 "git", "ls-tree", "%(commit)", "%(directory)", NULL
3705 static struct view_ops tree_ops = {
3717 blob_read(struct view *view, char *line)
3721 return add_line_text(view, line, LINE_DEFAULT) != NULL;
3724 static const char *blob_argv[SIZEOF_ARG] = {
3725 "git", "cat-file", "blob", "%(blob)", NULL
3728 static struct view_ops blob_ops = {
3742 * Loading the blame view is a two phase job:
3744 * 1. File content is read either using opt_file from the
3745 * filesystem or using git-cat-file.
3746 * 2. Then blame information is incrementally added by
3747 * reading output from git-blame.
3750 static const char *blame_head_argv[] = {
3751 "git", "blame", "--incremental", "--", "%(file)", NULL
3754 static const char *blame_ref_argv[] = {
3755 "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
3758 static const char *blame_cat_file_argv[] = {
3759 "git", "cat-file", "blob", "%(ref):%(file)", NULL
3762 struct blame_commit {
3763 char id[SIZEOF_REV]; /* SHA1 ID. */
3764 char title[128]; /* First line of the commit message. */
3765 char author[75]; /* Author of the commit. */
3766 struct tm time; /* Date from the author ident. */
3767 char filename[128]; /* Name of file. */
3771 struct blame_commit *commit;
3776 blame_open(struct view *view)
3778 if (*opt_ref || !init_io_fd(&view->io, fopen(opt_file, "r"))) {
3779 if (!run_io_rd(&view->io, blame_cat_file_argv, FORMAT_ALL))
3783 setup_update(view, opt_file);
3784 string_format(view->ref, "%s ...", opt_file);
3789 static struct blame_commit *
3790 get_blame_commit(struct view *view, const char *id)
3794 for (i = 0; i < view->lines; i++) {
3795 struct blame *blame = view->line[i].data;
3800 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
3801 return blame->commit;
3805 struct blame_commit *commit = calloc(1, sizeof(*commit));
3808 string_ncopy(commit->id, id, SIZEOF_REV);
3814 parse_number(const char **posref, size_t *number, size_t min, size_t max)
3816 const char *pos = *posref;
3819 pos = strchr(pos + 1, ' ');
3820 if (!pos || !isdigit(pos[1]))
3822 *number = atoi(pos + 1);
3823 if (*number < min || *number > max)
3830 static struct blame_commit *
3831 parse_blame_commit(struct view *view, const char *text, int *blamed)
3833 struct blame_commit *commit;
3834 struct blame *blame;
3835 const char *pos = text + SIZEOF_REV - 1;
3839 if (strlen(text) <= SIZEOF_REV || *pos != ' ')
3842 if (!parse_number(&pos, &lineno, 1, view->lines) ||
3843 !parse_number(&pos, &group, 1, view->lines - lineno + 1))
3846 commit = get_blame_commit(view, text);
3852 struct line *line = &view->line[lineno + group - 1];
3855 blame->commit = commit;
3863 blame_read_file(struct view *view, const char *line, bool *read_file)
3866 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
3869 if (view->lines == 0 && !view->parent)
3870 die("No blame exist for %s", view->vid);
3872 if (view->lines == 0 || !run_io_rd(&io, argv, FORMAT_ALL)) {
3873 report("Failed to load blame data");
3877 done_io(view->pipe);
3883 size_t linelen = strlen(line);
3884 struct blame *blame = malloc(sizeof(*blame) + linelen);
3886 blame->commit = NULL;
3887 strncpy(blame->text, line, linelen);
3888 blame->text[linelen] = 0;
3889 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
3894 match_blame_header(const char *name, char **line)
3896 size_t namelen = strlen(name);
3897 bool matched = !strncmp(name, *line, namelen);
3906 blame_read(struct view *view, char *line)
3908 static struct blame_commit *commit = NULL;
3909 static int blamed = 0;
3910 static time_t author_time;
3911 static bool read_file = TRUE;
3914 return blame_read_file(view, line, &read_file);
3921 string_format(view->ref, "%s", view->vid);
3922 if (view_is_displayed(view)) {
3923 update_view_title(view);
3924 redraw_view_from(view, 0);
3930 commit = parse_blame_commit(view, line, &blamed);
3931 string_format(view->ref, "%s %2d%%", view->vid,
3932 blamed * 100 / view->lines);
3934 } else if (match_blame_header("author ", &line)) {
3935 string_ncopy(commit->author, line, strlen(line));
3937 } else if (match_blame_header("author-time ", &line)) {
3938 author_time = (time_t) atol(line);
3940 } else if (match_blame_header("author-tz ", &line)) {
3943 tz = ('0' - line[1]) * 60 * 60 * 10;
3944 tz += ('0' - line[2]) * 60 * 60;
3945 tz += ('0' - line[3]) * 60;
3946 tz += ('0' - line[4]) * 60;
3952 gmtime_r(&author_time, &commit->time);
3954 } else if (match_blame_header("summary ", &line)) {
3955 string_ncopy(commit->title, line, strlen(line));
3957 } else if (match_blame_header("filename ", &line)) {
3958 string_ncopy(commit->filename, line, strlen(line));
3966 blame_draw(struct view *view, struct line *line, unsigned int lineno)
3968 struct blame *blame = line->data;
3969 struct tm *time = NULL;
3970 const char *id = NULL, *author = NULL;
3972 if (blame->commit && *blame->commit->filename) {
3973 id = blame->commit->id;
3974 author = blame->commit->author;
3975 time = &blame->commit->time;
3978 if (opt_date && draw_date(view, time))
3982 draw_field(view, LINE_MAIN_AUTHOR, author, opt_author_cols, TRUE))
3985 if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
3988 if (draw_lineno(view, lineno))
3991 draw_text(view, LINE_DEFAULT, blame->text, TRUE);
3996 blame_request(struct view *view, enum request request, struct line *line)
3998 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
3999 struct blame *blame = line->data;
4002 case REQ_VIEW_BLAME:
4003 if (!blame->commit || !strcmp(blame->commit->id, NULL_ID)) {
4004 report("Commit ID unknown");
4007 string_copy(opt_ref, blame->commit->id);
4008 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4012 if (!blame->commit) {
4013 report("No commit loaded yet");
4017 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
4018 !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
4021 if (!strcmp(blame->commit->id, NULL_ID)) {
4022 struct view *diff = VIEW(REQ_VIEW_DIFF);
4023 const char *diff_index_argv[] = {
4024 "git", "diff-index", "--root", "--cached",
4025 "--patch-with-stat", "-C", "-M",
4026 "HEAD", "--", view->vid, NULL
4029 if (!prepare_update(diff, diff_index_argv, NULL, FORMAT_DASH)) {
4030 report("Failed to allocate diff command");
4033 flags |= OPEN_PREPARED;
4036 open_view(view, REQ_VIEW_DIFF, flags);
4047 blame_grep(struct view *view, struct line *line)
4049 struct blame *blame = line->data;
4050 struct blame_commit *commit = blame->commit;
4053 #define MATCH(text, on) \
4054 (on && *text && regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4057 char buf[DATE_COLS + 1];
4059 if (MATCH(commit->title, 1) ||
4060 MATCH(commit->author, opt_author) ||
4061 MATCH(commit->id, opt_date))
4064 if (strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time) &&
4069 return MATCH(blame->text, 1);
4075 blame_select(struct view *view, struct line *line)
4077 struct blame *blame = line->data;
4078 struct blame_commit *commit = blame->commit;
4083 if (!strcmp(commit->id, NULL_ID))
4084 string_ncopy(ref_commit, "HEAD", 4);
4086 string_copy_rev(ref_commit, commit->id);
4089 static struct view_ops blame_ops = {
4108 char rev[SIZEOF_REV];
4109 char name[SIZEOF_STR];
4113 char rev[SIZEOF_REV];
4114 char name[SIZEOF_STR];
4118 static char status_onbranch[SIZEOF_STR];
4119 static struct status stage_status;
4120 static enum line_type stage_line_type;
4121 static size_t stage_chunks;
4122 static int *stage_chunk;
4124 /* This should work even for the "On branch" line. */
4126 status_has_none(struct view *view, struct line *line)
4128 return line < view->line + view->lines && !line[1].data;
4131 /* Get fields from the diff line:
4132 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
4135 status_get_diff(struct status *file, const char *buf, size_t bufsize)
4137 const char *old_mode = buf + 1;
4138 const char *new_mode = buf + 8;
4139 const char *old_rev = buf + 15;
4140 const char *new_rev = buf + 56;
4141 const char *status = buf + 97;
4144 old_mode[-1] != ':' ||
4145 new_mode[-1] != ' ' ||
4146 old_rev[-1] != ' ' ||
4147 new_rev[-1] != ' ' ||
4151 file->status = *status;
4153 string_copy_rev(file->old.rev, old_rev);
4154 string_copy_rev(file->new.rev, new_rev);
4156 file->old.mode = strtoul(old_mode, NULL, 8);
4157 file->new.mode = strtoul(new_mode, NULL, 8);
4159 file->old.name[0] = file->new.name[0] = 0;
4165 status_run(struct view *view, const char cmd[], char status, enum line_type type)
4167 struct status *file = NULL;
4168 struct status *unmerged = NULL;
4169 char buf[SIZEOF_STR * 4];
4173 pipe = popen(cmd, "r");
4177 add_line_data(view, NULL, type);
4179 while (!feof(pipe) && !ferror(pipe)) {
4183 readsize = fread(buf + bufsize, 1, sizeof(buf) - bufsize, pipe);
4186 bufsize += readsize;
4188 /* Process while we have NUL chars. */
4189 while ((sep = memchr(buf, 0, bufsize))) {
4190 size_t sepsize = sep - buf + 1;
4193 if (!realloc_lines(view, view->line_size + 1))
4196 file = calloc(1, sizeof(*file));
4200 add_line_data(view, file, type);
4203 /* Parse diff info part. */
4205 file->status = status;
4207 string_copy(file->old.rev, NULL_ID);
4209 } else if (!file->status) {
4210 if (!status_get_diff(file, buf, sepsize))
4214 memmove(buf, sep + 1, bufsize);
4216 sep = memchr(buf, 0, bufsize);
4219 sepsize = sep - buf + 1;
4221 /* Collapse all 'M'odified entries that
4222 * follow a associated 'U'nmerged entry.
4224 if (file->status == 'U') {
4227 } else if (unmerged) {
4228 int collapse = !strcmp(buf, unmerged->new.name);
4239 /* Grab the old name for rename/copy. */
4240 if (!*file->old.name &&
4241 (file->status == 'R' || file->status == 'C')) {
4242 sepsize = sep - buf + 1;
4243 string_ncopy(file->old.name, buf, sepsize);
4245 memmove(buf, sep + 1, bufsize);
4247 sep = memchr(buf, 0, bufsize);
4250 sepsize = sep - buf + 1;
4253 /* git-ls-files just delivers a NUL separated
4254 * list of file names similar to the second half
4255 * of the git-diff-* output. */
4256 string_ncopy(file->new.name, buf, sepsize);
4257 if (!*file->old.name)
4258 string_copy(file->old.name, file->new.name);
4260 memmove(buf, sep + 1, bufsize);
4271 if (!view->line[view->lines - 1].data)
4272 add_line_data(view, NULL, LINE_STAT_NONE);
4278 /* Don't show unmerged entries in the staged section. */
4279 #define STATUS_DIFF_INDEX_CMD "git diff-index -z --diff-filter=ACDMRTXB --cached -M HEAD"
4280 #define STATUS_DIFF_FILES_CMD "git diff-files -z"
4281 #define STATUS_LIST_OTHER_CMD \
4282 "git ls-files -z --others --exclude-standard"
4283 #define STATUS_LIST_NO_HEAD_CMD \
4284 "git ls-files -z --cached --exclude-standard"
4286 #define STATUS_DIFF_INDEX_SHOW_CMD \
4287 "git diff-index --root --patch-with-stat -C -M --cached HEAD -- %s %s 2>/dev/null"
4289 #define STATUS_DIFF_FILES_SHOW_CMD \
4290 "git diff-files --root --patch-with-stat -C -M -- %s %s 2>/dev/null"
4292 #define STATUS_DIFF_NO_HEAD_SHOW_CMD \
4293 "git diff --no-color --patch-with-stat /dev/null %s 2>/dev/null"
4295 /* First parse staged info using git-diff-index(1), then parse unstaged
4296 * info using git-diff-files(1), and finally untracked files using
4297 * git-ls-files(1). */
4299 status_open(struct view *view)
4301 unsigned long prev_lineno = view->lineno;
4305 if (!realloc_lines(view, view->line_size + 7))
4308 add_line_data(view, NULL, LINE_STAT_HEAD);
4309 if (is_initial_commit())
4310 string_copy(status_onbranch, "Initial commit");
4311 else if (!*opt_head)
4312 string_copy(status_onbranch, "Not currently on any branch");
4313 else if (!string_format(status_onbranch, "On branch %s", opt_head))
4316 system("git update-index -q --refresh >/dev/null 2>/dev/null");
4318 if (is_initial_commit()) {
4319 if (!status_run(view, STATUS_LIST_NO_HEAD_CMD, 'A', LINE_STAT_STAGED))
4321 } else if (!status_run(view, STATUS_DIFF_INDEX_CMD, 0, LINE_STAT_STAGED)) {
4325 if (!status_run(view, STATUS_DIFF_FILES_CMD, 0, LINE_STAT_UNSTAGED) ||
4326 !status_run(view, STATUS_LIST_OTHER_CMD, '?', LINE_STAT_UNTRACKED))
4329 /* If all went well restore the previous line number to stay in
4330 * the context or select a line with something that can be
4332 if (prev_lineno >= view->lines)
4333 prev_lineno = view->lines - 1;
4334 while (prev_lineno < view->lines && !view->line[prev_lineno].data)
4336 while (prev_lineno > 0 && !view->line[prev_lineno].data)
4339 /* If the above fails, always skip the "On branch" line. */
4340 if (prev_lineno < view->lines)
4341 view->lineno = prev_lineno;
4345 if (view->lineno < view->offset)
4346 view->offset = view->lineno;
4347 else if (view->offset + view->height <= view->lineno)
4348 view->offset = view->lineno - view->height + 1;
4354 status_draw(struct view *view, struct line *line, unsigned int lineno)
4356 struct status *status = line->data;
4357 enum line_type type;
4361 switch (line->type) {
4362 case LINE_STAT_STAGED:
4363 type = LINE_STAT_SECTION;
4364 text = "Changes to be committed:";
4367 case LINE_STAT_UNSTAGED:
4368 type = LINE_STAT_SECTION;
4369 text = "Changed but not updated:";
4372 case LINE_STAT_UNTRACKED:
4373 type = LINE_STAT_SECTION;
4374 text = "Untracked files:";
4377 case LINE_STAT_NONE:
4378 type = LINE_DEFAULT;
4379 text = " (no files)";
4382 case LINE_STAT_HEAD:
4383 type = LINE_STAT_HEAD;
4384 text = status_onbranch;
4391 static char buf[] = { '?', ' ', ' ', ' ', 0 };
4393 buf[0] = status->status;
4394 if (draw_text(view, line->type, buf, TRUE))
4396 type = LINE_DEFAULT;
4397 text = status->new.name;
4400 draw_text(view, type, text, TRUE);
4405 status_enter(struct view *view, struct line *line)
4407 struct status *status = line->data;
4408 char oldpath[SIZEOF_STR] = "";
4409 char newpath[SIZEOF_STR] = "";
4412 enum open_flags split;
4414 if (line->type == LINE_STAT_NONE ||
4415 (!status && line[1].type == LINE_STAT_NONE)) {
4416 report("No file to diff");
4421 if (sq_quote(oldpath, 0, status->old.name) >= sizeof(oldpath))
4423 /* Diffs for unmerged entries are empty when pasing the
4424 * new path, so leave it empty. */
4425 if (status->status != 'U' &&
4426 sq_quote(newpath, 0, status->new.name) >= sizeof(newpath))
4431 line->type != LINE_STAT_UNTRACKED &&
4432 !string_format_from(opt_cmd, &cmdsize, "cd %s;", opt_cdup))
4435 switch (line->type) {
4436 case LINE_STAT_STAGED:
4437 if (is_initial_commit()) {
4438 if (!string_format_from(opt_cmd, &cmdsize,
4439 STATUS_DIFF_NO_HEAD_SHOW_CMD,
4443 if (!string_format_from(opt_cmd, &cmdsize,
4444 STATUS_DIFF_INDEX_SHOW_CMD,
4450 info = "Staged changes to %s";
4452 info = "Staged changes";
4455 case LINE_STAT_UNSTAGED:
4456 if (!string_format_from(opt_cmd, &cmdsize,
4457 STATUS_DIFF_FILES_SHOW_CMD, oldpath, newpath))
4460 info = "Unstaged changes to %s";
4462 info = "Unstaged changes";
4465 case LINE_STAT_UNTRACKED:
4470 report("No file to show");
4474 if (!suffixcmp(status->new.name, -1, "/")) {
4475 report("Cannot display a directory");
4479 opt_pipe = fopen(status->new.name, "r");
4480 info = "Untracked file %s";
4483 case LINE_STAT_HEAD:
4487 die("line type %d not handled in switch", line->type);
4490 split = view_is_displayed(view) ? OPEN_SPLIT : 0;
4491 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH | split);
4492 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
4494 stage_status = *status;
4496 memset(&stage_status, 0, sizeof(stage_status));
4499 stage_line_type = line->type;
4501 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
4508 status_exists(struct status *status, enum line_type type)
4510 struct view *view = VIEW(REQ_VIEW_STATUS);
4513 for (line = view->line; line < view->line + view->lines; line++) {
4514 struct status *pos = line->data;
4516 if (line->type == type && pos &&
4517 !strcmp(status->new.name, pos->new.name))
4526 status_update_prepare(enum line_type type)
4528 char cmd[SIZEOF_STR];
4532 type != LINE_STAT_UNTRACKED &&
4533 !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
4537 case LINE_STAT_STAGED:
4538 string_add(cmd, cmdsize, "git update-index -z --index-info");
4541 case LINE_STAT_UNSTAGED:
4542 case LINE_STAT_UNTRACKED:
4543 string_add(cmd, cmdsize, "git update-index -z --add --remove --stdin");
4547 die("line type %d not handled in switch", type);
4550 return popen(cmd, "w");
4554 status_update_write(FILE *pipe, struct status *status, enum line_type type)
4556 char buf[SIZEOF_STR];
4561 case LINE_STAT_STAGED:
4562 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
4565 status->old.name, 0))
4569 case LINE_STAT_UNSTAGED:
4570 case LINE_STAT_UNTRACKED:
4571 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
4576 die("line type %d not handled in switch", type);
4579 while (!ferror(pipe) && written < bufsize) {
4580 written += fwrite(buf + written, 1, bufsize - written, pipe);
4583 return written == bufsize;
4587 status_update_file(struct status *status, enum line_type type)
4589 FILE *pipe = status_update_prepare(type);
4595 result = status_update_write(pipe, status, type);
4601 status_update_files(struct view *view, struct line *line)
4603 FILE *pipe = status_update_prepare(line->type);
4605 struct line *pos = view->line + view->lines;
4612 for (pos = line; pos < view->line + view->lines && pos->data; pos++)
4615 for (file = 0, done = 0; result && file < files; line++, file++) {
4616 int almost_done = file * 100 / files;
4618 if (almost_done > done) {
4620 string_format(view->ref, "updating file %u of %u (%d%% done)",
4622 update_view_title(view);
4624 result = status_update_write(pipe, line->data, line->type);
4632 status_update(struct view *view)
4634 struct line *line = &view->line[view->lineno];
4636 assert(view->lines);
4639 /* This should work even for the "On branch" line. */
4640 if (line < view->line + view->lines && !line[1].data) {
4641 report("Nothing to update");
4645 if (!status_update_files(view, line + 1)) {
4646 report("Failed to update file status");
4650 } else if (!status_update_file(line->data, line->type)) {
4651 report("Failed to update file status");
4659 status_revert(struct status *status, enum line_type type, bool has_none)
4661 if (!status || type != LINE_STAT_UNSTAGED) {
4662 if (type == LINE_STAT_STAGED) {
4663 report("Cannot revert changes to staged files");
4664 } else if (type == LINE_STAT_UNTRACKED) {
4665 report("Cannot revert changes to untracked files");
4666 } else if (has_none) {
4667 report("Nothing to revert");
4669 report("Cannot revert changes to multiple files");
4674 const char *checkout_argv[] = {
4675 "git", "checkout", "--", status->old.name, NULL
4678 if (!prompt_yesno("Are you sure you want to overwrite any changes?"))
4680 return run_io_fg(checkout_argv, opt_cdup);
4685 status_request(struct view *view, enum request request, struct line *line)
4687 struct status *status = line->data;
4690 case REQ_STATUS_UPDATE:
4691 if (!status_update(view))
4695 case REQ_STATUS_REVERT:
4696 if (!status_revert(status, line->type, status_has_none(view, line)))
4700 case REQ_STATUS_MERGE:
4701 if (!status || status->status != 'U') {
4702 report("Merging only possible for files with unmerged status ('U').");
4705 open_mergetool(status->new.name);
4711 if (status->status == 'D') {
4712 report("File has been deleted.");
4716 open_editor(status->status != '?', status->new.name);
4719 case REQ_VIEW_BLAME:
4721 string_copy(opt_file, status->new.name);
4727 /* After returning the status view has been split to
4728 * show the stage view. No further reloading is
4730 status_enter(view, line);
4734 /* Simply reload the view. */
4741 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
4747 status_select(struct view *view, struct line *line)
4749 struct status *status = line->data;
4750 char file[SIZEOF_STR] = "all files";
4754 if (status && !string_format(file, "'%s'", status->new.name))
4757 if (!status && line[1].type == LINE_STAT_NONE)
4760 switch (line->type) {
4761 case LINE_STAT_STAGED:
4762 text = "Press %s to unstage %s for commit";
4765 case LINE_STAT_UNSTAGED:
4766 text = "Press %s to stage %s for commit";
4769 case LINE_STAT_UNTRACKED:
4770 text = "Press %s to stage %s for addition";
4773 case LINE_STAT_HEAD:
4774 case LINE_STAT_NONE:
4775 text = "Nothing to update";
4779 die("line type %d not handled in switch", line->type);
4782 if (status && status->status == 'U') {
4783 text = "Press %s to resolve conflict in %s";
4784 key = get_key(REQ_STATUS_MERGE);
4787 key = get_key(REQ_STATUS_UPDATE);
4790 string_format(view->ref, text, key, file);
4794 status_grep(struct view *view, struct line *line)
4796 struct status *status = line->data;
4797 enum { S_STATUS, S_NAME, S_END } state;
4804 for (state = S_STATUS; state < S_END; state++) {
4808 case S_NAME: text = status->new.name; break;
4810 buf[0] = status->status;
4818 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4825 static struct view_ops status_ops = {
4838 stage_diff_line(FILE *pipe, struct line *line)
4840 const char *buf = line->data;
4841 size_t bufsize = strlen(buf);
4844 while (!ferror(pipe) && written < bufsize) {
4845 written += fwrite(buf + written, 1, bufsize - written, pipe);
4850 return written == bufsize;
4854 stage_diff_write(FILE *pipe, struct line *line, struct line *end)
4856 while (line < end) {
4857 if (!stage_diff_line(pipe, line++))
4859 if (line->type == LINE_DIFF_CHUNK ||
4860 line->type == LINE_DIFF_HEADER)
4867 static struct line *
4868 stage_diff_find(struct view *view, struct line *line, enum line_type type)
4870 for (; view->line < line; line--)
4871 if (line->type == type)
4878 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
4880 char cmd[SIZEOF_STR];
4882 struct line *diff_hdr;
4885 diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
4890 !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
4893 if (!string_format_from(cmd, &cmdsize,
4894 "git apply --whitespace=nowarn %s %s - && "
4895 "git update-index -q --unmerged --refresh 2>/dev/null",
4896 revert ? "" : "--cached",
4897 revert || stage_line_type == LINE_STAT_STAGED ? "-R" : ""))
4900 pipe = popen(cmd, "w");
4904 if (!stage_diff_write(pipe, diff_hdr, chunk) ||
4905 !stage_diff_write(pipe, chunk, view->line + view->lines))
4910 return chunk ? TRUE : FALSE;
4914 stage_update(struct view *view, struct line *line)
4916 struct line *chunk = NULL;
4918 if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
4919 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
4922 if (!stage_apply_chunk(view, chunk, FALSE)) {
4923 report("Failed to apply chunk");
4927 } else if (!stage_status.status) {
4928 view = VIEW(REQ_VIEW_STATUS);
4930 for (line = view->line; line < view->line + view->lines; line++)
4931 if (line->type == stage_line_type)
4934 if (!status_update_files(view, line + 1)) {
4935 report("Failed to update files");
4939 } else if (!status_update_file(&stage_status, stage_line_type)) {
4940 report("Failed to update file");
4948 stage_revert(struct view *view, struct line *line)
4950 struct line *chunk = NULL;
4952 if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
4953 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
4956 if (!prompt_yesno("Are you sure you want to revert changes?"))
4959 if (!stage_apply_chunk(view, chunk, TRUE)) {
4960 report("Failed to revert chunk");
4966 return status_revert(stage_status.status ? &stage_status : NULL,
4967 stage_line_type, FALSE);
4973 stage_next(struct view *view, struct line *line)
4977 if (!stage_chunks) {
4978 static size_t alloc = 0;
4981 for (line = view->line; line < view->line + view->lines; line++) {
4982 if (line->type != LINE_DIFF_CHUNK)
4985 tmp = realloc_items(stage_chunk, &alloc,
4986 stage_chunks, sizeof(*tmp));
4988 report("Allocation failure");
4993 stage_chunk[stage_chunks++] = line - view->line;
4997 for (i = 0; i < stage_chunks; i++) {
4998 if (stage_chunk[i] > view->lineno) {
4999 do_scroll_view(view, stage_chunk[i] - view->lineno);
5000 report("Chunk %d of %d", i + 1, stage_chunks);
5005 report("No next chunk found");
5009 stage_request(struct view *view, enum request request, struct line *line)
5012 case REQ_STATUS_UPDATE:
5013 if (!stage_update(view, line))
5017 case REQ_STATUS_REVERT:
5018 if (!stage_revert(view, line))
5022 case REQ_STAGE_NEXT:
5023 if (stage_line_type == LINE_STAT_UNTRACKED) {
5024 report("File is untracked; press %s to add",
5025 get_key(REQ_STATUS_UPDATE));
5028 stage_next(view, line);
5032 if (!stage_status.new.name[0])
5034 if (stage_status.status == 'D') {
5035 report("File has been deleted.");
5039 open_editor(stage_status.status != '?', stage_status.new.name);
5043 /* Reload everything ... */
5046 case REQ_VIEW_BLAME:
5047 if (stage_status.new.name[0]) {
5048 string_copy(opt_file, stage_status.new.name);
5054 return pager_request(view, request, line);
5060 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD | OPEN_NOMAXIMIZE);
5062 /* Check whether the staged entry still exists, and close the
5063 * stage view if it doesn't. */
5064 if (!status_exists(&stage_status, stage_line_type))
5065 return REQ_VIEW_CLOSE;
5067 if (stage_line_type == LINE_STAT_UNTRACKED) {
5068 if (!suffixcmp(stage_status.new.name, -1, "/")) {
5069 report("Cannot display a directory");
5073 opt_pipe = fopen(stage_status.new.name, "r");
5075 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
5080 static struct view_ops stage_ops = {
5097 char id[SIZEOF_REV]; /* SHA1 ID. */
5098 char title[128]; /* First line of the commit message. */
5099 char author[75]; /* Author of the commit. */
5100 struct tm time; /* Date from the author ident. */
5101 struct ref **refs; /* Repository references. */
5102 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
5103 size_t graph_size; /* The width of the graph array. */
5104 bool has_parents; /* Rewritten --parents seen. */
5107 /* Size of rev graph with no "padding" columns */
5108 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
5111 struct rev_graph *prev, *next, *parents;
5112 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
5114 struct commit *commit;
5116 unsigned int boundary:1;
5119 /* Parents of the commit being visualized. */
5120 static struct rev_graph graph_parents[4];
5122 /* The current stack of revisions on the graph. */
5123 static struct rev_graph graph_stacks[4] = {
5124 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
5125 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
5126 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
5127 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
5131 graph_parent_is_merge(struct rev_graph *graph)
5133 return graph->parents->size > 1;
5137 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
5139 struct commit *commit = graph->commit;
5141 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
5142 commit->graph[commit->graph_size++] = symbol;
5146 clear_rev_graph(struct rev_graph *graph)
5148 graph->boundary = 0;
5149 graph->size = graph->pos = 0;
5150 graph->commit = NULL;
5151 memset(graph->parents, 0, sizeof(*graph->parents));
5155 done_rev_graph(struct rev_graph *graph)
5157 if (graph_parent_is_merge(graph) &&
5158 graph->pos < graph->size - 1 &&
5159 graph->next->size == graph->size + graph->parents->size - 1) {
5160 size_t i = graph->pos + graph->parents->size - 1;
5162 graph->commit->graph_size = i * 2;
5163 while (i < graph->next->size - 1) {
5164 append_to_rev_graph(graph, ' ');
5165 append_to_rev_graph(graph, '\\');
5170 clear_rev_graph(graph);
5174 push_rev_graph(struct rev_graph *graph, const char *parent)
5178 /* "Collapse" duplicate parents lines.
5180 * FIXME: This needs to also update update the drawn graph but
5181 * for now it just serves as a method for pruning graph lines. */
5182 for (i = 0; i < graph->size; i++)
5183 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
5186 if (graph->size < SIZEOF_REVITEMS) {
5187 string_copy_rev(graph->rev[graph->size++], parent);
5192 get_rev_graph_symbol(struct rev_graph *graph)
5196 if (graph->boundary)
5197 symbol = REVGRAPH_BOUND;
5198 else if (graph->parents->size == 0)
5199 symbol = REVGRAPH_INIT;
5200 else if (graph_parent_is_merge(graph))
5201 symbol = REVGRAPH_MERGE;
5202 else if (graph->pos >= graph->size)
5203 symbol = REVGRAPH_BRANCH;
5205 symbol = REVGRAPH_COMMIT;
5211 draw_rev_graph(struct rev_graph *graph)
5214 chtype separator, line;
5216 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
5217 static struct rev_filler fillers[] = {
5223 chtype symbol = get_rev_graph_symbol(graph);
5224 struct rev_filler *filler;
5227 if (opt_line_graphics)
5228 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
5230 filler = &fillers[DEFAULT];
5232 for (i = 0; i < graph->pos; i++) {
5233 append_to_rev_graph(graph, filler->line);
5234 if (graph_parent_is_merge(graph->prev) &&
5235 graph->prev->pos == i)
5236 filler = &fillers[RSHARP];
5238 append_to_rev_graph(graph, filler->separator);
5241 /* Place the symbol for this revision. */
5242 append_to_rev_graph(graph, symbol);
5244 if (graph->prev->size > graph->size)
5245 filler = &fillers[RDIAG];
5247 filler = &fillers[DEFAULT];
5251 for (; i < graph->size; i++) {
5252 append_to_rev_graph(graph, filler->separator);
5253 append_to_rev_graph(graph, filler->line);
5254 if (graph_parent_is_merge(graph->prev) &&
5255 i < graph->prev->pos + graph->parents->size)
5256 filler = &fillers[RSHARP];
5257 if (graph->prev->size > graph->size)
5258 filler = &fillers[LDIAG];
5261 if (graph->prev->size > graph->size) {
5262 append_to_rev_graph(graph, filler->separator);
5263 if (filler->line != ' ')
5264 append_to_rev_graph(graph, filler->line);
5268 /* Prepare the next rev graph */
5270 prepare_rev_graph(struct rev_graph *graph)
5274 /* First, traverse all lines of revisions up to the active one. */
5275 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
5276 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
5279 push_rev_graph(graph->next, graph->rev[graph->pos]);
5282 /* Interleave the new revision parent(s). */
5283 for (i = 0; !graph->boundary && i < graph->parents->size; i++)
5284 push_rev_graph(graph->next, graph->parents->rev[i]);
5286 /* Lastly, put any remaining revisions. */
5287 for (i = graph->pos + 1; i < graph->size; i++)
5288 push_rev_graph(graph->next, graph->rev[i]);
5292 update_rev_graph(struct rev_graph *graph)
5294 /* If this is the finalizing update ... */
5296 prepare_rev_graph(graph);
5298 /* Graph visualization needs a one rev look-ahead,
5299 * so the first update doesn't visualize anything. */
5300 if (!graph->prev->commit)
5303 draw_rev_graph(graph->prev);
5304 done_rev_graph(graph->prev->prev);
5312 static const char *main_argv[SIZEOF_ARG] = {
5313 "git", "log", "--no-color", "--pretty=raw", "--parents",
5314 "--topo-order", "%(head)", NULL
5318 main_draw(struct view *view, struct line *line, unsigned int lineno)
5320 struct commit *commit = line->data;
5322 if (!*commit->author)
5325 if (opt_date && draw_date(view, &commit->time))
5329 draw_field(view, LINE_MAIN_AUTHOR, commit->author, opt_author_cols, TRUE))
5332 if (opt_rev_graph && commit->graph_size &&
5333 draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
5336 if (opt_show_refs && commit->refs) {
5340 enum line_type type;
5342 if (commit->refs[i]->head)
5343 type = LINE_MAIN_HEAD;
5344 else if (commit->refs[i]->ltag)
5345 type = LINE_MAIN_LOCAL_TAG;
5346 else if (commit->refs[i]->tag)
5347 type = LINE_MAIN_TAG;
5348 else if (commit->refs[i]->tracked)
5349 type = LINE_MAIN_TRACKED;
5350 else if (commit->refs[i]->remote)
5351 type = LINE_MAIN_REMOTE;
5353 type = LINE_MAIN_REF;
5355 if (draw_text(view, type, "[", TRUE) ||
5356 draw_text(view, type, commit->refs[i]->name, TRUE) ||
5357 draw_text(view, type, "]", TRUE))
5360 if (draw_text(view, LINE_DEFAULT, " ", TRUE))
5362 } while (commit->refs[i++]->next);
5365 draw_text(view, LINE_DEFAULT, commit->title, TRUE);
5369 /* Reads git log --pretty=raw output and parses it into the commit struct. */
5371 main_read(struct view *view, char *line)
5373 static struct rev_graph *graph = graph_stacks;
5374 enum line_type type;
5375 struct commit *commit;
5380 if (!view->lines && !view->parent)
5381 die("No revisions match the given arguments.");
5382 if (view->lines > 0) {
5383 commit = view->line[view->lines - 1].data;
5384 if (!*commit->author) {
5387 graph->commit = NULL;
5390 update_rev_graph(graph);
5392 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
5393 clear_rev_graph(&graph_stacks[i]);
5397 type = get_line_type(line);
5398 if (type == LINE_COMMIT) {
5399 commit = calloc(1, sizeof(struct commit));
5403 line += STRING_SIZE("commit ");
5405 graph->boundary = 1;
5409 string_copy_rev(commit->id, line);
5410 commit->refs = get_refs(commit->id);
5411 graph->commit = commit;
5412 add_line_data(view, commit, LINE_MAIN_COMMIT);
5414 while ((line = strchr(line, ' '))) {
5416 push_rev_graph(graph->parents, line);
5417 commit->has_parents = TRUE;
5424 commit = view->line[view->lines - 1].data;
5428 if (commit->has_parents)
5430 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
5435 /* Parse author lines where the name may be empty:
5436 * author <email@address.tld> 1138474660 +0100
5438 char *ident = line + STRING_SIZE("author ");
5439 char *nameend = strchr(ident, '<');
5440 char *emailend = strchr(ident, '>');
5442 if (!nameend || !emailend)
5445 update_rev_graph(graph);
5446 graph = graph->next;
5448 *nameend = *emailend = 0;
5449 ident = chomp_string(ident);
5451 ident = chomp_string(nameend + 1);
5456 string_ncopy(commit->author, ident, strlen(ident));
5458 /* Parse epoch and timezone */
5459 if (emailend[1] == ' ') {
5460 char *secs = emailend + 2;
5461 char *zone = strchr(secs, ' ');
5462 time_t time = (time_t) atol(secs);
5464 if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
5468 tz = ('0' - zone[1]) * 60 * 60 * 10;
5469 tz += ('0' - zone[2]) * 60 * 60;
5470 tz += ('0' - zone[3]) * 60;
5471 tz += ('0' - zone[4]) * 60;
5479 gmtime_r(&time, &commit->time);
5484 /* Fill in the commit title if it has not already been set. */
5485 if (commit->title[0])
5488 /* Require titles to start with a non-space character at the
5489 * offset used by git log. */
5490 if (strncmp(line, " ", 4))
5493 /* Well, if the title starts with a whitespace character,
5494 * try to be forgiving. Otherwise we end up with no title. */
5495 while (isspace(*line))
5499 /* FIXME: More graceful handling of titles; append "..." to
5500 * shortened titles, etc. */
5502 string_ncopy(commit->title, line, strlen(line));
5509 main_request(struct view *view, enum request request, struct line *line)
5511 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
5515 open_view(view, REQ_VIEW_DIFF, flags);
5519 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
5529 grep_refs(struct ref **refs, regex_t *regex)
5537 if (regexec(regex, refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
5539 } while (refs[i++]->next);
5545 main_grep(struct view *view, struct line *line)
5547 struct commit *commit = line->data;
5548 enum { S_TITLE, S_AUTHOR, S_DATE, S_REFS, S_END } state;
5549 char buf[DATE_COLS + 1];
5552 for (state = S_TITLE; state < S_END; state++) {
5556 case S_TITLE: text = commit->title; break;
5560 text = commit->author;
5565 if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
5572 if (grep_refs(commit->refs, view->regex) == TRUE)
5579 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
5587 main_select(struct view *view, struct line *line)
5589 struct commit *commit = line->data;
5591 string_copy_rev(view->ref, commit->id);
5592 string_copy_rev(ref_commit, view->ref);
5595 static struct view_ops main_ops = {
5608 * Unicode / UTF-8 handling
5610 * NOTE: Much of the following code for dealing with unicode is derived from
5611 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
5612 * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
5615 /* I've (over)annotated a lot of code snippets because I am not entirely
5616 * confident that the approach taken by this small UTF-8 interface is correct.
5620 unicode_width(unsigned long c)
5623 (c <= 0x115f /* Hangul Jamo */
5626 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
5628 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
5629 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
5630 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
5631 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
5632 || (c >= 0xffe0 && c <= 0xffe6)
5633 || (c >= 0x20000 && c <= 0x2fffd)
5634 || (c >= 0x30000 && c <= 0x3fffd)))
5638 return opt_tab_size;
5643 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
5644 * Illegal bytes are set one. */
5645 static const unsigned char utf8_bytes[256] = {
5646 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,
5647 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,
5648 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,
5649 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,
5650 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,
5651 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,
5652 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,
5653 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,
5656 /* Decode UTF-8 multi-byte representation into a unicode character. */
5657 static inline unsigned long
5658 utf8_to_unicode(const char *string, size_t length)
5660 unsigned long unicode;
5664 unicode = string[0];
5667 unicode = (string[0] & 0x1f) << 6;
5668 unicode += (string[1] & 0x3f);
5671 unicode = (string[0] & 0x0f) << 12;
5672 unicode += ((string[1] & 0x3f) << 6);
5673 unicode += (string[2] & 0x3f);
5676 unicode = (string[0] & 0x0f) << 18;
5677 unicode += ((string[1] & 0x3f) << 12);
5678 unicode += ((string[2] & 0x3f) << 6);
5679 unicode += (string[3] & 0x3f);
5682 unicode = (string[0] & 0x0f) << 24;
5683 unicode += ((string[1] & 0x3f) << 18);
5684 unicode += ((string[2] & 0x3f) << 12);
5685 unicode += ((string[3] & 0x3f) << 6);
5686 unicode += (string[4] & 0x3f);
5689 unicode = (string[0] & 0x01) << 30;
5690 unicode += ((string[1] & 0x3f) << 24);
5691 unicode += ((string[2] & 0x3f) << 18);
5692 unicode += ((string[3] & 0x3f) << 12);
5693 unicode += ((string[4] & 0x3f) << 6);
5694 unicode += (string[5] & 0x3f);
5697 die("Invalid unicode length");
5700 /* Invalid characters could return the special 0xfffd value but NUL
5701 * should be just as good. */
5702 return unicode > 0xffff ? 0 : unicode;
5705 /* Calculates how much of string can be shown within the given maximum width
5706 * and sets trimmed parameter to non-zero value if all of string could not be
5707 * shown. If the reserve flag is TRUE, it will reserve at least one
5708 * trailing character, which can be useful when drawing a delimiter.
5710 * Returns the number of bytes to output from string to satisfy max_width. */
5712 utf8_length(const char *string, int *width, size_t max_width, int *trimmed, bool reserve)
5714 const char *start = string;
5715 const char *end = strchr(string, '\0');
5716 unsigned char last_bytes = 0;
5717 size_t last_ucwidth = 0;
5722 while (string < end) {
5723 int c = *(unsigned char *) string;
5724 unsigned char bytes = utf8_bytes[c];
5726 unsigned long unicode;
5728 if (string + bytes > end)
5731 /* Change representation to figure out whether
5732 * it is a single- or double-width character. */
5734 unicode = utf8_to_unicode(string, bytes);
5735 /* FIXME: Graceful handling of invalid unicode character. */
5739 ucwidth = unicode_width(unicode);
5741 if (*width > max_width) {
5744 if (reserve && *width == max_width) {
5745 string -= last_bytes;
5746 *width -= last_ucwidth;
5753 last_ucwidth = ucwidth;
5756 return string - start;
5764 /* Whether or not the curses interface has been initialized. */
5765 static bool cursed = FALSE;
5767 /* The status window is used for polling keystrokes. */
5768 static WINDOW *status_win;
5770 static bool status_empty = TRUE;
5772 /* Update status and title window. */
5774 report(const char *msg, ...)
5776 struct view *view = display[current_view];
5782 char buf[SIZEOF_STR];
5785 va_start(args, msg);
5786 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
5787 buf[sizeof(buf) - 1] = 0;
5788 buf[sizeof(buf) - 2] = '.';
5789 buf[sizeof(buf) - 3] = '.';
5790 buf[sizeof(buf) - 4] = '.';
5796 if (!status_empty || *msg) {
5799 va_start(args, msg);
5801 wmove(status_win, 0, 0);
5803 vwprintw(status_win, msg, args);
5804 status_empty = FALSE;
5806 status_empty = TRUE;
5808 wclrtoeol(status_win);
5809 wrefresh(status_win);
5814 update_view_title(view);
5815 update_display_cursor(view);
5818 /* Controls when nodelay should be in effect when polling user input. */
5820 set_nonblocking_input(bool loading)
5822 static unsigned int loading_views;
5824 if ((loading == FALSE && loading_views-- == 1) ||
5825 (loading == TRUE && loading_views++ == 0))
5826 nodelay(status_win, loading);
5834 /* Initialize the curses library */
5835 if (isatty(STDIN_FILENO)) {
5836 cursed = !!initscr();
5839 /* Leave stdin and stdout alone when acting as a pager. */
5840 opt_tty = fopen("/dev/tty", "r+");
5842 die("Failed to open /dev/tty");
5843 cursed = !!newterm(NULL, opt_tty, opt_tty);
5847 die("Failed to initialize curses");
5849 nonl(); /* Tell curses not to do NL->CR/NL on output */
5850 cbreak(); /* Take input chars one at a time, no wait for \n */
5851 noecho(); /* Don't echo input */
5852 leaveok(stdscr, TRUE);
5857 getmaxyx(stdscr, y, x);
5858 status_win = newwin(1, 0, y - 1, 0);
5860 die("Failed to create status window");
5862 /* Enable keyboard mapping */
5863 keypad(status_win, TRUE);
5864 wbkgdset(status_win, get_line_attr(LINE_STATUS));
5866 TABSIZE = opt_tab_size;
5867 if (opt_line_graphics) {
5868 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
5873 prompt_yesno(const char *prompt)
5875 enum { WAIT, STOP, CANCEL } status = WAIT;
5876 bool answer = FALSE;
5878 while (status == WAIT) {
5884 foreach_view (view, i)
5889 mvwprintw(status_win, 0, 0, "%s [Yy]/[Nn]", prompt);
5890 wclrtoeol(status_win);
5892 /* Refresh, accept single keystroke of input */
5893 key = wgetch(status_win);
5917 /* Clear the status window */
5918 status_empty = FALSE;
5925 read_prompt(const char *prompt)
5927 enum { READING, STOP, CANCEL } status = READING;
5928 static char buf[SIZEOF_STR];
5931 while (status == READING) {
5937 foreach_view (view, i)
5942 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
5943 wclrtoeol(status_win);
5945 /* Refresh, accept single keystroke of input */
5946 key = wgetch(status_win);
5951 status = pos ? STOP : CANCEL;
5969 if (pos >= sizeof(buf)) {
5970 report("Input string too long");
5975 buf[pos++] = (char) key;
5979 /* Clear the status window */
5980 status_empty = FALSE;
5983 if (status == CANCEL)
5992 * Repository references
5995 static struct ref *refs = NULL;
5996 static size_t refs_alloc = 0;
5997 static size_t refs_size = 0;
5999 /* Id <-> ref store */
6000 static struct ref ***id_refs = NULL;
6001 static size_t id_refs_alloc = 0;
6002 static size_t id_refs_size = 0;
6005 compare_refs(const void *ref1_, const void *ref2_)
6007 const struct ref *ref1 = *(const struct ref **)ref1_;
6008 const struct ref *ref2 = *(const struct ref **)ref2_;
6010 if (ref1->tag != ref2->tag)
6011 return ref2->tag - ref1->tag;
6012 if (ref1->ltag != ref2->ltag)
6013 return ref2->ltag - ref2->ltag;
6014 if (ref1->head != ref2->head)
6015 return ref2->head - ref1->head;
6016 if (ref1->tracked != ref2->tracked)
6017 return ref2->tracked - ref1->tracked;
6018 if (ref1->remote != ref2->remote)
6019 return ref2->remote - ref1->remote;
6020 return strcmp(ref1->name, ref2->name);
6023 static struct ref **
6024 get_refs(const char *id)
6026 struct ref ***tmp_id_refs;
6027 struct ref **ref_list = NULL;
6028 size_t ref_list_alloc = 0;
6029 size_t ref_list_size = 0;
6032 for (i = 0; i < id_refs_size; i++)
6033 if (!strcmp(id, id_refs[i][0]->id))
6036 tmp_id_refs = realloc_items(id_refs, &id_refs_alloc, id_refs_size + 1,
6041 id_refs = tmp_id_refs;
6043 for (i = 0; i < refs_size; i++) {
6046 if (strcmp(id, refs[i].id))
6049 tmp = realloc_items(ref_list, &ref_list_alloc,
6050 ref_list_size + 1, sizeof(*ref_list));
6058 ref_list[ref_list_size] = &refs[i];
6059 /* XXX: The properties of the commit chains ensures that we can
6060 * safely modify the shared ref. The repo references will
6061 * always be similar for the same id. */
6062 ref_list[ref_list_size]->next = 1;
6068 qsort(ref_list, ref_list_size, sizeof(*ref_list), compare_refs);
6069 ref_list[ref_list_size - 1]->next = 0;
6070 id_refs[id_refs_size++] = ref_list;
6077 read_ref(char *id, size_t idlen, char *name, size_t namelen)
6082 bool remote = FALSE;
6083 bool tracked = FALSE;
6084 bool check_replace = FALSE;
6087 if (!prefixcmp(name, "refs/tags/")) {
6088 if (!suffixcmp(name, namelen, "^{}")) {
6091 if (refs_size > 0 && refs[refs_size - 1].ltag == TRUE)
6092 check_replace = TRUE;
6098 namelen -= STRING_SIZE("refs/tags/");
6099 name += STRING_SIZE("refs/tags/");
6101 } else if (!prefixcmp(name, "refs/remotes/")) {
6103 namelen -= STRING_SIZE("refs/remotes/");
6104 name += STRING_SIZE("refs/remotes/");
6105 tracked = !strcmp(opt_remote, name);
6107 } else if (!prefixcmp(name, "refs/heads/")) {
6108 namelen -= STRING_SIZE("refs/heads/");
6109 name += STRING_SIZE("refs/heads/");
6110 head = !strncmp(opt_head, name, namelen);
6112 } else if (!strcmp(name, "HEAD")) {
6113 string_ncopy(opt_head_rev, id, idlen);
6117 if (check_replace && !strcmp(name, refs[refs_size - 1].name)) {
6118 /* it's an annotated tag, replace the previous sha1 with the
6119 * resolved commit id; relies on the fact git-ls-remote lists
6120 * the commit id of an annotated tag right before the commit id
6122 refs[refs_size - 1].ltag = ltag;
6123 string_copy_rev(refs[refs_size - 1].id, id);
6127 refs = realloc_items(refs, &refs_alloc, refs_size + 1, sizeof(*refs));
6131 ref = &refs[refs_size++];
6132 ref->name = malloc(namelen + 1);
6136 strncpy(ref->name, name, namelen);
6137 ref->name[namelen] = 0;
6141 ref->remote = remote;
6142 ref->tracked = tracked;
6143 string_copy_rev(ref->id, id);
6151 const char *cmd_env = getenv("TIG_LS_REMOTE");
6152 const char *cmd = cmd_env && *cmd_env ? cmd_env : TIG_LS_REMOTE;
6157 while (refs_size > 0)
6158 free(refs[--refs_size].name);
6159 while (id_refs_size > 0)
6160 free(id_refs[--id_refs_size]);
6162 return read_properties(popen(cmd, "r"), "\t", read_ref);
6166 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
6168 if (!strcmp(name, "i18n.commitencoding"))
6169 string_ncopy(opt_encoding, value, valuelen);
6171 if (!strcmp(name, "core.editor"))
6172 string_ncopy(opt_editor, value, valuelen);
6174 /* branch.<head>.remote */
6176 !strncmp(name, "branch.", 7) &&
6177 !strncmp(name + 7, opt_head, strlen(opt_head)) &&
6178 !strcmp(name + 7 + strlen(opt_head), ".remote"))
6179 string_ncopy(opt_remote, value, valuelen);
6181 if (*opt_head && *opt_remote &&
6182 !strncmp(name, "branch.", 7) &&
6183 !strncmp(name + 7, opt_head, strlen(opt_head)) &&
6184 !strcmp(name + 7 + strlen(opt_head), ".merge")) {
6185 size_t from = strlen(opt_remote);
6187 if (!prefixcmp(value, "refs/heads/")) {
6188 value += STRING_SIZE("refs/heads/");
6189 valuelen -= STRING_SIZE("refs/heads/");
6192 if (!string_format_from(opt_remote, &from, "/%s", value))
6200 load_git_config(void)
6202 return read_properties(popen("git " GIT_CONFIG " --list", "r"),
6203 "=", read_repo_config_option);
6207 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
6209 if (!opt_git_dir[0]) {
6210 string_ncopy(opt_git_dir, name, namelen);
6212 } else if (opt_is_inside_work_tree == -1) {
6213 /* This can be 3 different values depending on the
6214 * version of git being used. If git-rev-parse does not
6215 * understand --is-inside-work-tree it will simply echo
6216 * the option else either "true" or "false" is printed.
6217 * Default to true for the unknown case. */
6218 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
6220 } else if (opt_cdup[0] == ' ') {
6221 string_ncopy(opt_cdup, name, namelen);
6223 if (!prefixcmp(name, "refs/heads/")) {
6224 namelen -= STRING_SIZE("refs/heads/");
6225 name += STRING_SIZE("refs/heads/");
6226 string_ncopy(opt_head, name, namelen);
6234 load_repo_info(void)
6237 FILE *pipe = popen("(git rev-parse --git-dir --is-inside-work-tree "
6238 " --show-cdup; git symbolic-ref HEAD) 2>/dev/null", "r");
6240 /* XXX: The line outputted by "--show-cdup" can be empty so
6241 * initialize it to something invalid to make it possible to
6242 * detect whether it has been set or not. */
6245 result = read_properties(pipe, "=", read_repo_info);
6246 if (opt_cdup[0] == ' ')
6253 read_properties(FILE *pipe, const char *separators,
6254 int (*read_property)(char *, size_t, char *, size_t))
6256 char buffer[BUFSIZ];
6263 while (state == OK && (name = fgets(buffer, sizeof(buffer), pipe))) {
6268 name = chomp_string(name);
6269 namelen = strcspn(name, separators);
6271 if (name[namelen]) {
6273 value = chomp_string(name + namelen + 1);
6274 valuelen = strlen(value);
6281 state = read_property(name, namelen, value, valuelen);
6284 if (state != ERR && ferror(pipe))
6297 static void __NORETURN
6300 /* XXX: Restore tty modes and let the OS cleanup the rest! */
6306 static void __NORETURN
6307 die(const char *err, ...)
6313 va_start(args, err);
6314 fputs("tig: ", stderr);
6315 vfprintf(stderr, err, args);
6316 fputs("\n", stderr);
6323 warn(const char *msg, ...)
6327 va_start(args, msg);
6328 fputs("tig warning: ", stderr);
6329 vfprintf(stderr, msg, args);
6330 fputs("\n", stderr);
6335 main(int argc, const char *argv[])
6337 const char **run_argv = NULL;
6339 enum request request;
6342 signal(SIGINT, quit);
6344 if (setlocale(LC_ALL, "")) {
6345 char *codeset = nl_langinfo(CODESET);
6347 string_ncopy(opt_codeset, codeset, strlen(codeset));
6350 if (load_repo_info() == ERR)
6351 die("Failed to load repo info.");
6353 if (load_options() == ERR)
6354 die("Failed to load user config.");
6356 if (load_git_config() == ERR)
6357 die("Failed to load repo config.");
6359 request = parse_options(argc, argv, &run_argv);
6360 if (request == REQ_NONE)
6363 /* Require a git repository unless when running in pager mode. */
6364 if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
6365 die("Not a git repository");
6367 if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
6370 if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
6371 opt_iconv = iconv_open(opt_codeset, opt_encoding);
6372 if (opt_iconv == ICONV_NONE)
6373 die("Failed to initialize character set conversion");
6376 if (load_refs() == ERR)
6377 die("Failed to load refs.");
6379 foreach_view (view, i)
6380 argv_from_env(view->ops->argv, view->cmd_env);
6385 if (!prepare_update(VIEW(request), run_argv, NULL, FORMAT_NONE))
6386 die("Failed to format arguments");
6387 open_view(display[current_view], request, OPEN_PREPARED);
6391 while (view_driver(display[current_view], request)) {
6395 foreach_view (view, i)
6397 view = display[current_view];
6399 /* Refresh, accept single keystroke of input */
6400 key = wgetch(status_win);
6402 /* wgetch() with nodelay() enabled returns ERR when there's no
6409 request = get_keybinding(view->keymap, key);
6411 /* Some low-level request handling. This keeps access to
6412 * status_win restricted. */
6416 char *cmd = read_prompt(":");
6419 struct view *next = VIEW(REQ_VIEW_PAGER);
6420 const char *argv[SIZEOF_ARG] = { "git" };
6423 /* When running random commands, initially show the
6424 * command in the title. However, it maybe later be
6425 * overwritten if a commit line is selected. */
6426 string_ncopy(next->ref, cmd, strlen(cmd));
6428 if (!argv_from_string(argv, &argc, cmd)) {
6429 report("Too many arguments");
6430 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
6431 report("Failed to format command");
6433 open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
6441 case REQ_SEARCH_BACK:
6443 const char *prompt = request == REQ_SEARCH ? "/" : "?";
6444 char *search = read_prompt(prompt);
6447 string_ncopy(opt_search, search, strlen(search));
6452 case REQ_SCREEN_RESIZE:
6456 getmaxyx(stdscr, height, width);
6458 /* Resize the status view and let the view driver take
6459 * care of resizing the displayed views. */
6460 wresize(status_win, 1, width);
6461 mvwin(status_win, height - 1, 0);
6462 wrefresh(status_win);