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->type == IO_FD)
425 if (io->dir && *io->dir &&
426 !string_format_from(buf, &bufpos, "cd %s;", io->dir))
429 if (!string_format_from(buf, &bufpos, "%s", io->sh))
432 if (io->type == IO_FG)
433 return system(buf) == 0;
435 io->pipe = popen(io->sh, io->type == IO_RD ? "r" : "w");
436 return io->pipe != NULL;
440 run_io_do(struct io *io)
442 return start_io(io) && done_io(io);
446 run_io_fg(const char **argv, const char *dir)
450 init_io(&io, dir, IO_FG);
451 if (!format_command(io.sh, argv, FORMAT_NONE))
453 return run_io_do(&io);
457 run_io_rd(struct io *io, const char **argv, enum format_flags flags)
459 return init_io_rd(io, argv, NULL, flags) && start_io(io);
463 io_eof(struct io *io)
465 return feof(io->pipe);
469 io_error(struct io *io)
475 io_strerror(struct io *io)
477 return strerror(io->error);
481 io_gets(struct io *io)
484 io->buf = malloc(BUFSIZ);
487 io->bufalloc = BUFSIZ;
490 if (!fgets(io->buf, io->bufalloc, io->pipe)) {
491 if (ferror(io->pipe))
500 run_io_buf(const char **argv, char buf[], size_t bufsize)
505 if (!run_io_rd(&io, argv, FORMAT_NONE))
509 io.bufalloc = bufsize;
510 error = !io_gets(&io) && io_error(&io);
513 return done_io(&io) || error;
522 /* XXX: Keep the view request first and in sync with views[]. */ \
523 REQ_GROUP("View switching") \
524 REQ_(VIEW_MAIN, "Show main view"), \
525 REQ_(VIEW_DIFF, "Show diff view"), \
526 REQ_(VIEW_LOG, "Show log view"), \
527 REQ_(VIEW_TREE, "Show tree view"), \
528 REQ_(VIEW_BLOB, "Show blob view"), \
529 REQ_(VIEW_BLAME, "Show blame view"), \
530 REQ_(VIEW_HELP, "Show help page"), \
531 REQ_(VIEW_PAGER, "Show pager view"), \
532 REQ_(VIEW_STATUS, "Show status view"), \
533 REQ_(VIEW_STAGE, "Show stage view"), \
535 REQ_GROUP("View manipulation") \
536 REQ_(ENTER, "Enter current line and scroll"), \
537 REQ_(NEXT, "Move to next"), \
538 REQ_(PREVIOUS, "Move to previous"), \
539 REQ_(VIEW_NEXT, "Move focus to next view"), \
540 REQ_(REFRESH, "Reload and refresh"), \
541 REQ_(MAXIMIZE, "Maximize the current view"), \
542 REQ_(VIEW_CLOSE, "Close the current view"), \
543 REQ_(QUIT, "Close all views and quit"), \
545 REQ_GROUP("View specific requests") \
546 REQ_(STATUS_UPDATE, "Update file status"), \
547 REQ_(STATUS_REVERT, "Revert file changes"), \
548 REQ_(STATUS_MERGE, "Merge file using external tool"), \
549 REQ_(STAGE_NEXT, "Find next chunk to stage"), \
550 REQ_(TREE_PARENT, "Switch to parent directory in tree view"), \
552 REQ_GROUP("Cursor navigation") \
553 REQ_(MOVE_UP, "Move cursor one line up"), \
554 REQ_(MOVE_DOWN, "Move cursor one line down"), \
555 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
556 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
557 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
558 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
560 REQ_GROUP("Scrolling") \
561 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
562 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
563 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
564 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
566 REQ_GROUP("Searching") \
567 REQ_(SEARCH, "Search the view"), \
568 REQ_(SEARCH_BACK, "Search backwards in the view"), \
569 REQ_(FIND_NEXT, "Find next search match"), \
570 REQ_(FIND_PREV, "Find previous search match"), \
572 REQ_GROUP("Option manipulation") \
573 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
574 REQ_(TOGGLE_DATE, "Toggle date display"), \
575 REQ_(TOGGLE_AUTHOR, "Toggle author display"), \
576 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
577 REQ_(TOGGLE_REFS, "Toggle reference display (tags/branches)"), \
580 REQ_(PROMPT, "Bring up the prompt"), \
581 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
582 REQ_(SCREEN_RESIZE, "Resize the screen"), \
583 REQ_(SHOW_VERSION, "Show version information"), \
584 REQ_(STOP_LOADING, "Stop all loading views"), \
585 REQ_(EDIT, "Open in editor"), \
586 REQ_(NONE, "Do nothing")
589 /* User action requests. */
591 #define REQ_GROUP(help)
592 #define REQ_(req, help) REQ_##req
594 /* Offset all requests to avoid conflicts with ncurses getch values. */
595 REQ_OFFSET = KEY_MAX + 1,
602 struct request_info {
603 enum request request;
609 static struct request_info req_info[] = {
610 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
611 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
618 get_request(const char *name)
620 int namelen = strlen(name);
623 for (i = 0; i < ARRAY_SIZE(req_info); i++)
624 if (req_info[i].namelen == namelen &&
625 !string_enum_compare(req_info[i].name, name, namelen))
626 return req_info[i].request;
636 static const char usage[] =
637 "tig " TIG_VERSION " (" __DATE__ ")\n"
639 "Usage: tig [options] [revs] [--] [paths]\n"
640 " or: tig show [options] [revs] [--] [paths]\n"
641 " or: tig blame [rev] path\n"
643 " or: tig < [git command output]\n"
646 " -v, --version Show version and exit\n"
647 " -h, --help Show help message and exit";
649 /* Option and state variables. */
650 static bool opt_date = TRUE;
651 static bool opt_author = TRUE;
652 static bool opt_line_number = FALSE;
653 static bool opt_line_graphics = TRUE;
654 static bool opt_rev_graph = FALSE;
655 static bool opt_show_refs = TRUE;
656 static int opt_num_interval = NUMBER_INTERVAL;
657 static int opt_tab_size = TAB_SIZE;
658 static int opt_author_cols = AUTHOR_COLS-1;
659 static char opt_path[SIZEOF_STR] = "";
660 static char opt_file[SIZEOF_STR] = "";
661 static char opt_ref[SIZEOF_REF] = "";
662 static char opt_head[SIZEOF_REF] = "";
663 static char opt_head_rev[SIZEOF_REV] = "";
664 static char opt_remote[SIZEOF_REF] = "";
665 static char opt_encoding[20] = "UTF-8";
666 static bool opt_utf8 = TRUE;
667 static char opt_codeset[20] = "UTF-8";
668 static iconv_t opt_iconv = ICONV_NONE;
669 static char opt_search[SIZEOF_STR] = "";
670 static char opt_cdup[SIZEOF_STR] = "";
671 static char opt_git_dir[SIZEOF_STR] = "";
672 static signed char opt_is_inside_work_tree = -1; /* set to TRUE or FALSE */
673 static char opt_editor[SIZEOF_STR] = "";
674 static FILE *opt_tty = NULL;
676 #define is_initial_commit() (!*opt_head_rev)
677 #define is_head_commit(rev) (!strcmp((rev), "HEAD") || !strcmp(opt_head_rev, (rev)))
680 parse_options(int argc, const char *argv[], const char ***run_argv)
682 enum request request = REQ_VIEW_MAIN;
683 const char *subcommand;
684 bool seen_dashdash = FALSE;
685 /* XXX: This is vulnerable to the user overriding options
686 * required for the main view parser. */
687 const char *custom_argv[SIZEOF_ARG] = {
688 "git", "log", "--no-color", "--pretty=raw", "--parents",
693 if (!isatty(STDIN_FILENO))
694 return REQ_VIEW_PAGER;
697 return REQ_VIEW_MAIN;
699 subcommand = argv[1];
700 if (!strcmp(subcommand, "status") || !strcmp(subcommand, "-S")) {
701 if (!strcmp(subcommand, "-S"))
702 warn("`-S' has been deprecated; use `tig status' instead");
704 warn("ignoring arguments after `%s'", subcommand);
705 return REQ_VIEW_STATUS;
707 } else if (!strcmp(subcommand, "blame")) {
708 if (argc <= 2 || argc > 4)
709 die("invalid number of options to blame\n\n%s", usage);
713 string_ncopy(opt_ref, argv[i], strlen(argv[i]));
717 string_ncopy(opt_file, argv[i], strlen(argv[i]));
718 return REQ_VIEW_BLAME;
720 } else if (!strcmp(subcommand, "show")) {
721 request = REQ_VIEW_DIFF;
723 } else if (!strcmp(subcommand, "log") || !strcmp(subcommand, "diff")) {
724 request = subcommand[0] == 'l' ? REQ_VIEW_LOG : REQ_VIEW_DIFF;
725 warn("`tig %s' has been deprecated", subcommand);
732 custom_argv[1] = subcommand;
736 for (i = 1 + !!subcommand; i < argc; i++) {
737 const char *opt = argv[i];
739 if (seen_dashdash || !strcmp(opt, "--")) {
740 seen_dashdash = TRUE;
742 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
743 printf("tig version %s\n", TIG_VERSION);
746 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
747 printf("%s\n", usage);
751 custom_argv[j++] = opt;
752 if (j >= ARRAY_SIZE(custom_argv))
753 die("command too long");
756 custom_argv[j] = NULL;
757 *run_argv = custom_argv;
764 * Line-oriented content detection.
768 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
769 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
770 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
771 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
772 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
773 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
774 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
775 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
776 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
777 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
778 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
779 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
780 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
781 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
782 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
783 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
784 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
785 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
786 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
787 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
788 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
789 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
790 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
791 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
792 LINE(AUTHOR, "author ", COLOR_CYAN, COLOR_DEFAULT, 0), \
793 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
794 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
795 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
796 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
797 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
798 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
799 LINE(DELIMITER, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
800 LINE(DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
801 LINE(LINE_NUMBER, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
802 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
803 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
804 LINE(MAIN_AUTHOR, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
805 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
806 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
807 LINE(MAIN_LOCAL_TAG,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
808 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
809 LINE(MAIN_TRACKED, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
810 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
811 LINE(MAIN_HEAD, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
812 LINE(MAIN_REVGRAPH,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
813 LINE(TREE_DIR, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
814 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
815 LINE(STAT_HEAD, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
816 LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
817 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
818 LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
819 LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
820 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
821 LINE(BLAME_ID, "", COLOR_MAGENTA, COLOR_DEFAULT, 0)
824 #define LINE(type, line, fg, bg, attr) \
832 const char *name; /* Option name. */
833 int namelen; /* Size of option name. */
834 const char *line; /* The start of line to match. */
835 int linelen; /* Size of string to match. */
836 int fg, bg, attr; /* Color and text attributes for the lines. */
839 static struct line_info line_info[] = {
840 #define LINE(type, line, fg, bg, attr) \
841 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
846 static enum line_type
847 get_line_type(const char *line)
849 int linelen = strlen(line);
852 for (type = 0; type < ARRAY_SIZE(line_info); type++)
853 /* Case insensitive search matches Signed-off-by lines better. */
854 if (linelen >= line_info[type].linelen &&
855 !strncasecmp(line_info[type].line, line, line_info[type].linelen))
862 get_line_attr(enum line_type type)
864 assert(type < ARRAY_SIZE(line_info));
865 return COLOR_PAIR(type) | line_info[type].attr;
868 static struct line_info *
869 get_line_info(const char *name)
871 size_t namelen = strlen(name);
874 for (type = 0; type < ARRAY_SIZE(line_info); type++)
875 if (namelen == line_info[type].namelen &&
876 !string_enum_compare(line_info[type].name, name, namelen))
877 return &line_info[type];
885 int default_bg = line_info[LINE_DEFAULT].bg;
886 int default_fg = line_info[LINE_DEFAULT].fg;
891 if (assume_default_colors(default_fg, default_bg) == ERR) {
892 default_bg = COLOR_BLACK;
893 default_fg = COLOR_WHITE;
896 for (type = 0; type < ARRAY_SIZE(line_info); type++) {
897 struct line_info *info = &line_info[type];
898 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
899 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
901 init_pair(type, fg, bg);
909 unsigned int selected:1;
910 unsigned int dirty:1;
912 void *data; /* User data */
922 enum request request;
925 static struct keybinding default_keybindings[] = {
927 { 'm', REQ_VIEW_MAIN },
928 { 'd', REQ_VIEW_DIFF },
929 { 'l', REQ_VIEW_LOG },
930 { 't', REQ_VIEW_TREE },
931 { 'f', REQ_VIEW_BLOB },
932 { 'B', REQ_VIEW_BLAME },
933 { 'p', REQ_VIEW_PAGER },
934 { 'h', REQ_VIEW_HELP },
935 { 'S', REQ_VIEW_STATUS },
936 { 'c', REQ_VIEW_STAGE },
938 /* View manipulation */
939 { 'q', REQ_VIEW_CLOSE },
940 { KEY_TAB, REQ_VIEW_NEXT },
941 { KEY_RETURN, REQ_ENTER },
942 { KEY_UP, REQ_PREVIOUS },
943 { KEY_DOWN, REQ_NEXT },
944 { 'R', REQ_REFRESH },
945 { KEY_F(5), REQ_REFRESH },
946 { 'O', REQ_MAXIMIZE },
948 /* Cursor navigation */
949 { 'k', REQ_MOVE_UP },
950 { 'j', REQ_MOVE_DOWN },
951 { KEY_HOME, REQ_MOVE_FIRST_LINE },
952 { KEY_END, REQ_MOVE_LAST_LINE },
953 { KEY_NPAGE, REQ_MOVE_PAGE_DOWN },
954 { ' ', REQ_MOVE_PAGE_DOWN },
955 { KEY_PPAGE, REQ_MOVE_PAGE_UP },
956 { 'b', REQ_MOVE_PAGE_UP },
957 { '-', REQ_MOVE_PAGE_UP },
960 { KEY_IC, REQ_SCROLL_LINE_UP },
961 { KEY_DC, REQ_SCROLL_LINE_DOWN },
962 { 'w', REQ_SCROLL_PAGE_UP },
963 { 's', REQ_SCROLL_PAGE_DOWN },
967 { '?', REQ_SEARCH_BACK },
968 { 'n', REQ_FIND_NEXT },
969 { 'N', REQ_FIND_PREV },
973 { 'z', REQ_STOP_LOADING },
974 { 'v', REQ_SHOW_VERSION },
975 { 'r', REQ_SCREEN_REDRAW },
976 { '.', REQ_TOGGLE_LINENO },
977 { 'D', REQ_TOGGLE_DATE },
978 { 'A', REQ_TOGGLE_AUTHOR },
979 { 'g', REQ_TOGGLE_REV_GRAPH },
980 { 'F', REQ_TOGGLE_REFS },
982 { 'u', REQ_STATUS_UPDATE },
983 { '!', REQ_STATUS_REVERT },
984 { 'M', REQ_STATUS_MERGE },
985 { '@', REQ_STAGE_NEXT },
986 { ',', REQ_TREE_PARENT },
989 /* Using the ncurses SIGWINCH handler. */
990 { KEY_RESIZE, REQ_SCREEN_RESIZE },
993 #define KEYMAP_INFO \
1007 #define KEYMAP_(name) KEYMAP_##name
1012 static struct int_map keymap_table[] = {
1013 #define KEYMAP_(name) { #name, STRING_SIZE(#name), KEYMAP_##name }
1018 #define set_keymap(map, name) \
1019 set_from_int_map(keymap_table, ARRAY_SIZE(keymap_table), map, name, strlen(name))
1021 struct keybinding_table {
1022 struct keybinding *data;
1026 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1029 add_keybinding(enum keymap keymap, enum request request, int key)
1031 struct keybinding_table *table = &keybindings[keymap];
1033 table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1035 die("Failed to allocate keybinding");
1036 table->data[table->size].alias = key;
1037 table->data[table->size++].request = request;
1040 /* Looks for a key binding first in the given map, then in the generic map, and
1041 * lastly in the default keybindings. */
1043 get_keybinding(enum keymap keymap, int key)
1047 for (i = 0; i < keybindings[keymap].size; i++)
1048 if (keybindings[keymap].data[i].alias == key)
1049 return keybindings[keymap].data[i].request;
1051 for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1052 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1053 return keybindings[KEYMAP_GENERIC].data[i].request;
1055 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1056 if (default_keybindings[i].alias == key)
1057 return default_keybindings[i].request;
1059 return (enum request) key;
1068 static struct key key_table[] = {
1069 { "Enter", KEY_RETURN },
1071 { "Backspace", KEY_BACKSPACE },
1073 { "Escape", KEY_ESC },
1074 { "Left", KEY_LEFT },
1075 { "Right", KEY_RIGHT },
1077 { "Down", KEY_DOWN },
1078 { "Insert", KEY_IC },
1079 { "Delete", KEY_DC },
1081 { "Home", KEY_HOME },
1083 { "PageUp", KEY_PPAGE },
1084 { "PageDown", KEY_NPAGE },
1094 { "F10", KEY_F(10) },
1095 { "F11", KEY_F(11) },
1096 { "F12", KEY_F(12) },
1100 get_key_value(const char *name)
1104 for (i = 0; i < ARRAY_SIZE(key_table); i++)
1105 if (!strcasecmp(key_table[i].name, name))
1106 return key_table[i].value;
1108 if (strlen(name) == 1 && isprint(*name))
1115 get_key_name(int key_value)
1117 static char key_char[] = "'X'";
1118 const char *seq = NULL;
1121 for (key = 0; key < ARRAY_SIZE(key_table); key++)
1122 if (key_table[key].value == key_value)
1123 seq = key_table[key].name;
1127 isprint(key_value)) {
1128 key_char[1] = (char) key_value;
1132 return seq ? seq : "(no key)";
1136 get_key(enum request request)
1138 static char buf[BUFSIZ];
1145 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1146 struct keybinding *keybinding = &default_keybindings[i];
1148 if (keybinding->request != request)
1151 if (!string_format_from(buf, &pos, "%s%s", sep,
1152 get_key_name(keybinding->alias)))
1153 return "Too many keybindings!";
1160 struct run_request {
1163 const char *argv[SIZEOF_ARG];
1166 static struct run_request *run_request;
1167 static size_t run_requests;
1170 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1172 struct run_request *req;
1174 if (argc >= ARRAY_SIZE(req->argv) - 1)
1177 req = realloc(run_request, (run_requests + 1) * sizeof(*run_request));
1182 req = &run_request[run_requests];
1183 req->keymap = keymap;
1185 req->argv[0] = NULL;
1187 if (!format_argv(req->argv, argv, FORMAT_NONE))
1190 return REQ_NONE + ++run_requests;
1193 static struct run_request *
1194 get_run_request(enum request request)
1196 if (request <= REQ_NONE)
1198 return &run_request[request - REQ_NONE - 1];
1202 add_builtin_run_requests(void)
1204 const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1205 const char *gc[] = { "git", "gc", NULL };
1212 { KEYMAP_MAIN, 'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1213 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1217 for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1220 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1221 if (req != REQ_NONE)
1222 add_keybinding(reqs[i].keymap, req, reqs[i].key);
1227 * User config file handling.
1230 static struct int_map color_map[] = {
1231 #define COLOR_MAP(name) { #name, STRING_SIZE(#name), COLOR_##name }
1243 #define set_color(color, name) \
1244 set_from_int_map(color_map, ARRAY_SIZE(color_map), color, name, strlen(name))
1246 static struct int_map attr_map[] = {
1247 #define ATTR_MAP(name) { #name, STRING_SIZE(#name), A_##name }
1254 ATTR_MAP(UNDERLINE),
1257 #define set_attribute(attr, name) \
1258 set_from_int_map(attr_map, ARRAY_SIZE(attr_map), attr, name, strlen(name))
1260 static int config_lineno;
1261 static bool config_errors;
1262 static const char *config_msg;
1264 /* Wants: object fgcolor bgcolor [attr] */
1266 option_color_command(int argc, const char *argv[])
1268 struct line_info *info;
1270 if (argc != 3 && argc != 4) {
1271 config_msg = "Wrong number of arguments given to color command";
1275 info = get_line_info(argv[0]);
1277 if (!string_enum_compare(argv[0], "main-delim", strlen("main-delim"))) {
1278 info = get_line_info("delimiter");
1280 } else if (!string_enum_compare(argv[0], "main-date", strlen("main-date"))) {
1281 info = get_line_info("date");
1284 config_msg = "Unknown color name";
1289 if (set_color(&info->fg, argv[1]) == ERR ||
1290 set_color(&info->bg, argv[2]) == ERR) {
1291 config_msg = "Unknown color";
1295 if (argc == 4 && set_attribute(&info->attr, argv[3]) == ERR) {
1296 config_msg = "Unknown attribute";
1303 static bool parse_bool(const char *s)
1305 return (!strcmp(s, "1") || !strcmp(s, "true") ||
1306 !strcmp(s, "yes")) ? TRUE : FALSE;
1310 parse_int(const char *s, int default_value, int min, int max)
1312 int value = atoi(s);
1314 return (value < min || value > max) ? default_value : value;
1317 /* Wants: name = value */
1319 option_set_command(int argc, const char *argv[])
1322 config_msg = "Wrong number of arguments given to set command";
1326 if (strcmp(argv[1], "=")) {
1327 config_msg = "No value assigned";
1331 if (!strcmp(argv[0], "show-author")) {
1332 opt_author = parse_bool(argv[2]);
1336 if (!strcmp(argv[0], "show-date")) {
1337 opt_date = parse_bool(argv[2]);
1341 if (!strcmp(argv[0], "show-rev-graph")) {
1342 opt_rev_graph = parse_bool(argv[2]);
1346 if (!strcmp(argv[0], "show-refs")) {
1347 opt_show_refs = parse_bool(argv[2]);
1351 if (!strcmp(argv[0], "show-line-numbers")) {
1352 opt_line_number = parse_bool(argv[2]);
1356 if (!strcmp(argv[0], "line-graphics")) {
1357 opt_line_graphics = parse_bool(argv[2]);
1361 if (!strcmp(argv[0], "line-number-interval")) {
1362 opt_num_interval = parse_int(argv[2], opt_num_interval, 1, 1024);
1366 if (!strcmp(argv[0], "author-width")) {
1367 opt_author_cols = parse_int(argv[2], opt_author_cols, 0, 1024);
1371 if (!strcmp(argv[0], "tab-size")) {
1372 opt_tab_size = parse_int(argv[2], opt_tab_size, 1, 1024);
1376 if (!strcmp(argv[0], "commit-encoding")) {
1377 const char *arg = argv[2];
1378 int arglen = strlen(arg);
1383 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1384 config_msg = "Unmatched quotation";
1387 arg += 1; arglen -= 2;
1389 string_ncopy(opt_encoding, arg, strlen(arg));
1394 config_msg = "Unknown variable name";
1398 /* Wants: mode request key */
1400 option_bind_command(int argc, const char *argv[])
1402 enum request request;
1407 config_msg = "Wrong number of arguments given to bind command";
1411 if (set_keymap(&keymap, argv[0]) == ERR) {
1412 config_msg = "Unknown key map";
1416 key = get_key_value(argv[1]);
1418 config_msg = "Unknown key";
1422 request = get_request(argv[2]);
1423 if (request == REQ_NONE) {
1424 const char *obsolete[] = { "cherry-pick" };
1425 size_t namelen = strlen(argv[2]);
1428 for (i = 0; i < ARRAY_SIZE(obsolete); i++) {
1429 if (namelen == strlen(obsolete[i]) &&
1430 !string_enum_compare(obsolete[i], argv[2], namelen)) {
1431 config_msg = "Obsolete request name";
1436 if (request == REQ_NONE && *argv[2]++ == '!')
1437 request = add_run_request(keymap, key, argc - 2, argv + 2);
1438 if (request == REQ_NONE) {
1439 config_msg = "Unknown request name";
1443 add_keybinding(keymap, request, key);
1449 set_option(const char *opt, char *value)
1451 const char *argv[SIZEOF_ARG];
1454 if (!argv_from_string(argv, &argc, value)) {
1455 config_msg = "Too many option arguments";
1459 if (!strcmp(opt, "color"))
1460 return option_color_command(argc, argv);
1462 if (!strcmp(opt, "set"))
1463 return option_set_command(argc, argv);
1465 if (!strcmp(opt, "bind"))
1466 return option_bind_command(argc, argv);
1468 config_msg = "Unknown option command";
1473 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1478 config_msg = "Internal error";
1480 /* Check for comment markers, since read_properties() will
1481 * only ensure opt and value are split at first " \t". */
1482 optlen = strcspn(opt, "#");
1486 if (opt[optlen] != 0) {
1487 config_msg = "No option value";
1491 /* Look for comment endings in the value. */
1492 size_t len = strcspn(value, "#");
1494 if (len < valuelen) {
1496 value[valuelen] = 0;
1499 status = set_option(opt, value);
1502 if (status == ERR) {
1503 fprintf(stderr, "Error on line %d, near '%.*s': %s\n",
1504 config_lineno, (int) optlen, opt, config_msg);
1505 config_errors = TRUE;
1508 /* Always keep going if errors are encountered. */
1513 load_option_file(const char *path)
1517 /* It's ok that the file doesn't exist. */
1518 file = fopen(path, "r");
1523 config_errors = FALSE;
1525 if (read_properties(file, " \t", read_option) == ERR ||
1526 config_errors == TRUE)
1527 fprintf(stderr, "Errors while loading %s.\n", path);
1533 const char *home = getenv("HOME");
1534 const char *tigrc_user = getenv("TIGRC_USER");
1535 const char *tigrc_system = getenv("TIGRC_SYSTEM");
1536 char buf[SIZEOF_STR];
1538 add_builtin_run_requests();
1540 if (!tigrc_system) {
1541 if (!string_format(buf, "%s/tigrc", SYSCONFDIR))
1545 load_option_file(tigrc_system);
1548 if (!home || !string_format(buf, "%s/.tigrc", home))
1552 load_option_file(tigrc_user);
1565 /* The display array of active views and the index of the current view. */
1566 static struct view *display[2];
1567 static unsigned int current_view;
1569 /* Reading from the prompt? */
1570 static bool input_mode = FALSE;
1572 #define foreach_displayed_view(view, i) \
1573 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1575 #define displayed_views() (display[1] != NULL ? 2 : 1)
1577 /* Current head and commit ID */
1578 static char ref_blob[SIZEOF_REF] = "";
1579 static char ref_commit[SIZEOF_REF] = "HEAD";
1580 static char ref_head[SIZEOF_REF] = "HEAD";
1583 const char *name; /* View name */
1584 const char *cmd_env; /* Command line set via environment */
1585 const char *id; /* Points to either of ref_{head,commit,blob} */
1587 struct view_ops *ops; /* View operations */
1589 enum keymap keymap; /* What keymap does this view have */
1590 bool git_dir; /* Whether the view requires a git directory. */
1592 char ref[SIZEOF_REF]; /* Hovered commit reference */
1593 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
1595 int height, width; /* The width and height of the main window */
1596 WINDOW *win; /* The main window */
1597 WINDOW *title; /* The title window living below the main window */
1600 unsigned long offset; /* Offset of the window top */
1601 unsigned long lineno; /* Current line number */
1604 char grep[SIZEOF_STR]; /* Search string */
1605 regex_t *regex; /* Pre-compiled regex */
1607 /* If non-NULL, points to the view that opened this view. If this view
1608 * is closed tig will switch back to the parent view. */
1609 struct view *parent;
1612 size_t lines; /* Total number of lines */
1613 struct line *line; /* Line index */
1614 size_t line_alloc; /* Total number of allocated lines */
1615 size_t line_size; /* Total number of used lines */
1616 unsigned int digits; /* Number of digits in the lines member. */
1619 struct line *curline; /* Line currently being drawn. */
1620 enum line_type curtype; /* Attribute currently used for drawing. */
1621 unsigned long col; /* Column when drawing. */
1630 /* What type of content being displayed. Used in the title bar. */
1632 /* Default command arguments. */
1634 /* Open and reads in all view content. */
1635 bool (*open)(struct view *view);
1636 /* Read one line; updates view->line. */
1637 bool (*read)(struct view *view, char *data);
1638 /* Draw one line; @lineno must be < view->height. */
1639 bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
1640 /* Depending on view handle a special requests. */
1641 enum request (*request)(struct view *view, enum request request, struct line *line);
1642 /* Search for regex in a line. */
1643 bool (*grep)(struct view *view, struct line *line);
1645 void (*select)(struct view *view, struct line *line);
1648 static struct view_ops blame_ops;
1649 static struct view_ops blob_ops;
1650 static struct view_ops diff_ops;
1651 static struct view_ops help_ops;
1652 static struct view_ops log_ops;
1653 static struct view_ops main_ops;
1654 static struct view_ops pager_ops;
1655 static struct view_ops stage_ops;
1656 static struct view_ops status_ops;
1657 static struct view_ops tree_ops;
1659 #define VIEW_STR(name, env, ref, ops, map, git) \
1660 { name, #env, ref, ops, map, git }
1662 #define VIEW_(id, name, ops, git, ref) \
1663 VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
1666 static struct view views[] = {
1667 VIEW_(MAIN, "main", &main_ops, TRUE, ref_head),
1668 VIEW_(DIFF, "diff", &diff_ops, TRUE, ref_commit),
1669 VIEW_(LOG, "log", &log_ops, TRUE, ref_head),
1670 VIEW_(TREE, "tree", &tree_ops, TRUE, ref_commit),
1671 VIEW_(BLOB, "blob", &blob_ops, TRUE, ref_blob),
1672 VIEW_(BLAME, "blame", &blame_ops, TRUE, ref_commit),
1673 VIEW_(HELP, "help", &help_ops, FALSE, ""),
1674 VIEW_(PAGER, "pager", &pager_ops, FALSE, "stdin"),
1675 VIEW_(STATUS, "status", &status_ops, TRUE, ""),
1676 VIEW_(STAGE, "stage", &stage_ops, TRUE, ""),
1679 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
1680 #define VIEW_REQ(view) ((view) - views + REQ_OFFSET + 1)
1682 #define foreach_view(view, i) \
1683 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1685 #define view_is_displayed(view) \
1686 (view == display[0] || view == display[1])
1693 static int line_graphics[] = {
1694 /* LINE_GRAPHIC_VLINE: */ '|'
1698 set_view_attr(struct view *view, enum line_type type)
1700 if (!view->curline->selected && view->curtype != type) {
1701 wattrset(view->win, get_line_attr(type));
1702 wchgat(view->win, -1, 0, type, NULL);
1703 view->curtype = type;
1708 draw_chars(struct view *view, enum line_type type, const char *string,
1709 int max_len, bool use_tilde)
1713 int trimmed = FALSE;
1719 len = utf8_length(string, &col, max_len, &trimmed, use_tilde);
1721 col = len = strlen(string);
1722 if (len > max_len) {
1726 col = len = max_len;
1731 set_view_attr(view, type);
1732 waddnstr(view->win, string, len);
1733 if (trimmed && use_tilde) {
1734 set_view_attr(view, LINE_DELIMITER);
1735 waddch(view->win, '~');
1743 draw_space(struct view *view, enum line_type type, int max, int spaces)
1745 static char space[] = " ";
1748 spaces = MIN(max, spaces);
1750 while (spaces > 0) {
1751 int len = MIN(spaces, sizeof(space) - 1);
1753 col += draw_chars(view, type, space, spaces, FALSE);
1761 draw_lineno(struct view *view, unsigned int lineno)
1764 int digits3 = view->digits < 3 ? 3 : view->digits;
1765 int max_number = MIN(digits3, STRING_SIZE(number));
1766 int max = view->width - view->col;
1769 if (max < max_number)
1772 lineno += view->offset + 1;
1773 if (lineno == 1 || (lineno % opt_num_interval) == 0) {
1774 static char fmt[] = "%1ld";
1776 if (view->digits <= 9)
1777 fmt[1] = '0' + digits3;
1779 if (!string_format(number, fmt, lineno))
1781 col = draw_chars(view, LINE_LINE_NUMBER, number, max_number, TRUE);
1783 col = draw_space(view, LINE_LINE_NUMBER, max_number, max_number);
1787 set_view_attr(view, LINE_DEFAULT);
1788 waddch(view->win, line_graphics[LINE_GRAPHIC_VLINE]);
1793 col += draw_space(view, LINE_DEFAULT, max - col, 1);
1796 return view->width - view->col <= 0;
1800 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
1802 view->col += draw_chars(view, type, string, view->width - view->col, trim);
1803 return view->width - view->col <= 0;
1807 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
1809 int max = view->width - view->col;
1815 set_view_attr(view, type);
1816 /* Using waddch() instead of waddnstr() ensures that
1817 * they'll be rendered correctly for the cursor line. */
1818 for (i = 0; i < size; i++)
1819 waddch(view->win, graphic[i]);
1823 waddch(view->win, ' ');
1827 return view->width - view->col <= 0;
1831 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
1833 int max = MIN(view->width - view->col, len);
1837 col = draw_chars(view, type, text, max - 1, trim);
1839 col = draw_space(view, type, max - 1, max - 1);
1841 view->col += col + draw_space(view, LINE_DEFAULT, max - col, max - col);
1842 return view->width - view->col <= 0;
1846 draw_date(struct view *view, struct tm *time)
1848 char buf[DATE_COLS];
1853 timelen = strftime(buf, sizeof(buf), DATE_FORMAT, time);
1854 date = timelen ? buf : NULL;
1856 return draw_field(view, LINE_DATE, date, DATE_COLS, FALSE);
1860 draw_view_line(struct view *view, unsigned int lineno)
1863 bool selected = (view->offset + lineno == view->lineno);
1866 assert(view_is_displayed(view));
1868 if (view->offset + lineno >= view->lines)
1871 line = &view->line[view->offset + lineno];
1873 wmove(view->win, lineno, 0);
1875 view->curline = line;
1876 view->curtype = LINE_NONE;
1877 line->selected = FALSE;
1880 set_view_attr(view, LINE_CURSOR);
1881 line->selected = TRUE;
1882 view->ops->select(view, line);
1883 } else if (line->selected) {
1884 wclrtoeol(view->win);
1887 scrollok(view->win, FALSE);
1888 draw_ok = view->ops->draw(view, line, lineno);
1889 scrollok(view->win, TRUE);
1895 redraw_view_dirty(struct view *view)
1900 for (lineno = 0; lineno < view->height; lineno++) {
1901 struct line *line = &view->line[view->offset + lineno];
1907 if (!draw_view_line(view, lineno))
1913 redrawwin(view->win);
1915 wnoutrefresh(view->win);
1917 wrefresh(view->win);
1921 redraw_view_from(struct view *view, int lineno)
1923 assert(0 <= lineno && lineno < view->height);
1925 for (; lineno < view->height; lineno++) {
1926 if (!draw_view_line(view, lineno))
1930 redrawwin(view->win);
1932 wnoutrefresh(view->win);
1934 wrefresh(view->win);
1938 redraw_view(struct view *view)
1941 redraw_view_from(view, 0);
1946 update_view_title(struct view *view)
1948 char buf[SIZEOF_STR];
1949 char state[SIZEOF_STR];
1950 size_t bufpos = 0, statelen = 0;
1952 assert(view_is_displayed(view));
1954 if (view != VIEW(REQ_VIEW_STATUS) && (view->lines || view->pipe)) {
1955 unsigned int view_lines = view->offset + view->height;
1956 unsigned int lines = view->lines
1957 ? MIN(view_lines, view->lines) * 100 / view->lines
1960 string_format_from(state, &statelen, "- %s %d of %d (%d%%)",
1967 time_t secs = time(NULL) - view->start_time;
1969 /* Three git seconds are a long time ... */
1971 string_format_from(state, &statelen, " %lds", secs);
1975 string_format_from(buf, &bufpos, "[%s]", view->name);
1976 if (*view->ref && bufpos < view->width) {
1977 size_t refsize = strlen(view->ref);
1978 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
1980 if (minsize < view->width)
1981 refsize = view->width - minsize + 7;
1982 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
1985 if (statelen && bufpos < view->width) {
1986 string_format_from(buf, &bufpos, " %s", state);
1989 if (view == display[current_view])
1990 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
1992 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
1994 mvwaddnstr(view->title, 0, 0, buf, bufpos);
1995 wclrtoeol(view->title);
1996 wmove(view->title, 0, view->width - 1);
1999 wnoutrefresh(view->title);
2001 wrefresh(view->title);
2005 resize_display(void)
2008 struct view *base = display[0];
2009 struct view *view = display[1] ? display[1] : display[0];
2011 /* Setup window dimensions */
2013 getmaxyx(stdscr, base->height, base->width);
2015 /* Make room for the status window. */
2019 /* Horizontal split. */
2020 view->width = base->width;
2021 view->height = SCALE_SPLIT_VIEW(base->height);
2022 base->height -= view->height;
2024 /* Make room for the title bar. */
2028 /* Make room for the title bar. */
2033 foreach_displayed_view (view, i) {
2035 view->win = newwin(view->height, 0, offset, 0);
2037 die("Failed to create %s view", view->name);
2039 scrollok(view->win, TRUE);
2041 view->title = newwin(1, 0, offset + view->height, 0);
2043 die("Failed to create title window");
2046 wresize(view->win, view->height, view->width);
2047 mvwin(view->win, offset, 0);
2048 mvwin(view->title, offset + view->height, 0);
2051 offset += view->height + 1;
2056 redraw_display(void)
2061 foreach_displayed_view (view, i) {
2063 update_view_title(view);
2068 update_display_cursor(struct view *view)
2070 /* Move the cursor to the right-most column of the cursor line.
2072 * XXX: This could turn out to be a bit expensive, but it ensures that
2073 * the cursor does not jump around. */
2075 wmove(view->win, view->lineno - view->offset, view->width - 1);
2076 wrefresh(view->win);
2084 /* Scrolling backend */
2086 do_scroll_view(struct view *view, int lines)
2088 bool redraw_current_line = FALSE;
2090 /* The rendering expects the new offset. */
2091 view->offset += lines;
2093 assert(0 <= view->offset && view->offset < view->lines);
2096 /* Move current line into the view. */
2097 if (view->lineno < view->offset) {
2098 view->lineno = view->offset;
2099 redraw_current_line = TRUE;
2100 } else if (view->lineno >= view->offset + view->height) {
2101 view->lineno = view->offset + view->height - 1;
2102 redraw_current_line = TRUE;
2105 assert(view->offset <= view->lineno && view->lineno < view->lines);
2107 /* Redraw the whole screen if scrolling is pointless. */
2108 if (view->height < ABS(lines)) {
2112 int line = lines > 0 ? view->height - lines : 0;
2113 int end = line + ABS(lines);
2115 wscrl(view->win, lines);
2117 for (; line < end; line++) {
2118 if (!draw_view_line(view, line))
2122 if (redraw_current_line)
2123 draw_view_line(view, view->lineno - view->offset);
2126 redrawwin(view->win);
2127 wrefresh(view->win);
2131 /* Scroll frontend */
2133 scroll_view(struct view *view, enum request request)
2137 assert(view_is_displayed(view));
2140 case REQ_SCROLL_PAGE_DOWN:
2141 lines = view->height;
2142 case REQ_SCROLL_LINE_DOWN:
2143 if (view->offset + lines > view->lines)
2144 lines = view->lines - view->offset;
2146 if (lines == 0 || view->offset + view->height >= view->lines) {
2147 report("Cannot scroll beyond the last line");
2152 case REQ_SCROLL_PAGE_UP:
2153 lines = view->height;
2154 case REQ_SCROLL_LINE_UP:
2155 if (lines > view->offset)
2156 lines = view->offset;
2159 report("Cannot scroll beyond the first line");
2167 die("request %d not handled in switch", request);
2170 do_scroll_view(view, lines);
2175 move_view(struct view *view, enum request request)
2177 int scroll_steps = 0;
2181 case REQ_MOVE_FIRST_LINE:
2182 steps = -view->lineno;
2185 case REQ_MOVE_LAST_LINE:
2186 steps = view->lines - view->lineno - 1;
2189 case REQ_MOVE_PAGE_UP:
2190 steps = view->height > view->lineno
2191 ? -view->lineno : -view->height;
2194 case REQ_MOVE_PAGE_DOWN:
2195 steps = view->lineno + view->height >= view->lines
2196 ? view->lines - view->lineno - 1 : view->height;
2208 die("request %d not handled in switch", request);
2211 if (steps <= 0 && view->lineno == 0) {
2212 report("Cannot move beyond the first line");
2215 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2216 report("Cannot move beyond the last line");
2220 /* Move the current line */
2221 view->lineno += steps;
2222 assert(0 <= view->lineno && view->lineno < view->lines);
2224 /* Check whether the view needs to be scrolled */
2225 if (view->lineno < view->offset ||
2226 view->lineno >= view->offset + view->height) {
2227 scroll_steps = steps;
2228 if (steps < 0 && -steps > view->offset) {
2229 scroll_steps = -view->offset;
2231 } else if (steps > 0) {
2232 if (view->lineno == view->lines - 1 &&
2233 view->lines > view->height) {
2234 scroll_steps = view->lines - view->offset - 1;
2235 if (scroll_steps >= view->height)
2236 scroll_steps -= view->height - 1;
2241 if (!view_is_displayed(view)) {
2242 view->offset += scroll_steps;
2243 assert(0 <= view->offset && view->offset < view->lines);
2244 view->ops->select(view, &view->line[view->lineno]);
2248 /* Repaint the old "current" line if we be scrolling */
2249 if (ABS(steps) < view->height)
2250 draw_view_line(view, view->lineno - steps - view->offset);
2253 do_scroll_view(view, scroll_steps);
2257 /* Draw the current line */
2258 draw_view_line(view, view->lineno - view->offset);
2260 redrawwin(view->win);
2261 wrefresh(view->win);
2270 static void search_view(struct view *view, enum request request);
2273 find_next_line(struct view *view, unsigned long lineno, struct line *line)
2275 assert(view_is_displayed(view));
2277 if (!view->ops->grep(view, line))
2280 if (lineno - view->offset >= view->height) {
2281 view->offset = lineno;
2282 view->lineno = lineno;
2286 unsigned long old_lineno = view->lineno - view->offset;
2288 view->lineno = lineno;
2289 draw_view_line(view, old_lineno);
2291 draw_view_line(view, view->lineno - view->offset);
2292 redrawwin(view->win);
2293 wrefresh(view->win);
2296 report("Line %ld matches '%s'", lineno + 1, view->grep);
2301 find_next(struct view *view, enum request request)
2303 unsigned long lineno = view->lineno;
2308 report("No previous search");
2310 search_view(view, request);
2320 case REQ_SEARCH_BACK:
2329 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2330 lineno += direction;
2332 /* Note, lineno is unsigned long so will wrap around in which case it
2333 * will become bigger than view->lines. */
2334 for (; lineno < view->lines; lineno += direction) {
2335 struct line *line = &view->line[lineno];
2337 if (find_next_line(view, lineno, line))
2341 report("No match found for '%s'", view->grep);
2345 search_view(struct view *view, enum request request)
2350 regfree(view->regex);
2353 view->regex = calloc(1, sizeof(*view->regex));
2358 regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2359 if (regex_err != 0) {
2360 char buf[SIZEOF_STR] = "unknown error";
2362 regerror(regex_err, view->regex, buf, sizeof(buf));
2363 report("Search failed: %s", buf);
2367 string_copy(view->grep, opt_search);
2369 find_next(view, request);
2373 * Incremental updating
2377 reset_view(struct view *view)
2381 for (i = 0; i < view->lines; i++)
2382 free(view->line[i].data);
2389 view->line_size = 0;
2390 view->line_alloc = 0;
2395 free_argv(const char *argv[])
2399 for (argc = 0; argv[argc]; argc++)
2400 free((void *) argv[argc]);
2404 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
2406 char buf[SIZEOF_STR];
2408 bool noreplace = flags == FORMAT_NONE;
2410 free_argv(dst_argv);
2412 for (argc = 0; src_argv[argc]; argc++) {
2413 const char *arg = src_argv[argc];
2417 char *next = strstr(arg, "%(");
2418 int len = next - arg;
2421 if (!next || noreplace) {
2422 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
2427 } else if (!prefixcmp(next, "%(directory)")) {
2430 } else if (!prefixcmp(next, "%(file)")) {
2433 } else if (!prefixcmp(next, "%(ref)")) {
2434 value = *opt_ref ? opt_ref : "HEAD";
2436 } else if (!prefixcmp(next, "%(head)")) {
2439 } else if (!prefixcmp(next, "%(commit)")) {
2442 } else if (!prefixcmp(next, "%(blob)")) {
2446 report("Unknown replacement: `%s`", next);
2450 if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
2453 arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
2456 dst_argv[argc] = strdup(buf);
2457 if (!dst_argv[argc])
2461 dst_argv[argc] = NULL;
2463 return src_argv[argc] == NULL;
2467 format_command(char dst[], const char *src_argv[], enum format_flags flags)
2469 const char *dst_argv[SIZEOF_ARG * 2] = { NULL };
2473 if (!format_argv(dst_argv, src_argv, flags)) {
2474 free_argv(dst_argv);
2478 for (argc = 0; dst_argv[argc] && bufsize < SIZEOF_STR; argc++) {
2480 dst[bufsize++] = ' ';
2481 bufsize = sq_quote(dst, bufsize, dst_argv[argc]);
2484 if (bufsize < SIZEOF_STR)
2486 free_argv(dst_argv);
2488 return src_argv[argc] == NULL && bufsize < SIZEOF_STR;
2492 end_update(struct view *view, bool force)
2496 while (!view->ops->read(view, NULL))
2499 set_nonblocking_input(FALSE);
2500 done_io(view->pipe);
2505 setup_update(struct view *view, const char *vid)
2507 set_nonblocking_input(TRUE);
2509 string_copy_rev(view->vid, vid);
2510 view->pipe = &view->io;
2511 view->start_time = time(NULL);
2515 prepare_update(struct view *view, const char *argv[], const char *dir,
2516 enum format_flags flags)
2519 end_update(view, TRUE);
2520 return init_io_rd(&view->io, argv, dir, flags);
2524 prepare_update_file(struct view *view, const char *name)
2527 end_update(view, TRUE);
2528 return init_io_fd(&view->io, fopen(name, "r"));
2532 begin_update(struct view *view, bool refresh)
2535 if (!start_io(&view->io))
2539 if (view == VIEW(REQ_VIEW_TREE) && strcmp(view->vid, view->id))
2542 if (!run_io_rd(&view->io, view->ops->argv, FORMAT_ALL))
2545 /* Put the current ref_* value to the view title ref
2546 * member. This is needed by the blob view. Most other
2547 * views sets it automatically after loading because the
2548 * first line is a commit line. */
2549 string_copy_rev(view->ref, view->id);
2552 setup_update(view, view->id);
2557 #define ITEM_CHUNK_SIZE 256
2559 realloc_items(void *mem, size_t *size, size_t new_size, size_t item_size)
2561 size_t num_chunks = *size / ITEM_CHUNK_SIZE;
2562 size_t num_chunks_new = (new_size + ITEM_CHUNK_SIZE - 1) / ITEM_CHUNK_SIZE;
2564 if (mem == NULL || num_chunks != num_chunks_new) {
2565 *size = num_chunks_new * ITEM_CHUNK_SIZE;
2566 mem = realloc(mem, *size * item_size);
2572 static struct line *
2573 realloc_lines(struct view *view, size_t line_size)
2575 size_t alloc = view->line_alloc;
2576 struct line *tmp = realloc_items(view->line, &alloc, line_size,
2577 sizeof(*view->line));
2583 view->line_alloc = alloc;
2584 view->line_size = line_size;
2589 update_view(struct view *view)
2591 char out_buffer[BUFSIZ * 2];
2593 /* The number of lines to read. If too low it will cause too much
2594 * redrawing (and possible flickering), if too high responsiveness
2596 unsigned long lines = view->height;
2597 int redraw_from = -1;
2602 /* Only redraw if lines are visible. */
2603 if (view->offset + view->height >= view->lines)
2604 redraw_from = view->lines - view->offset;
2606 /* FIXME: This is probably not perfect for backgrounded views. */
2607 if (!realloc_lines(view, view->lines + lines))
2610 while ((line = io_gets(view->pipe))) {
2611 size_t linelen = strlen(line);
2614 line[linelen - 1] = 0;
2616 if (opt_iconv != ICONV_NONE) {
2617 ICONV_CONST char *inbuf = line;
2618 size_t inlen = linelen;
2620 char *outbuf = out_buffer;
2621 size_t outlen = sizeof(out_buffer);
2625 ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2626 if (ret != (size_t) -1) {
2628 linelen = strlen(out_buffer);
2632 if (!view->ops->read(view, line))
2642 lines = view->lines;
2643 for (digits = 0; lines; digits++)
2646 /* Keep the displayed view in sync with line number scaling. */
2647 if (digits != view->digits) {
2648 view->digits = digits;
2653 if (io_error(view->pipe)) {
2654 report("Failed to read: %s", io_strerror(view->pipe));
2655 end_update(view, TRUE);
2657 } else if (io_eof(view->pipe)) {
2659 end_update(view, FALSE);
2662 if (!view_is_displayed(view))
2665 if (view == VIEW(REQ_VIEW_TREE)) {
2666 /* Clear the view and redraw everything since the tree sorting
2667 * might have rearranged things. */
2670 } else if (redraw_from >= 0) {
2671 /* If this is an incremental update, redraw the previous line
2672 * since for commits some members could have changed when
2673 * loading the main view. */
2674 if (redraw_from > 0)
2677 /* Since revision graph visualization requires knowledge
2678 * about the parent commit, it causes a further one-off
2679 * needed to be redrawn for incremental updates. */
2680 if (redraw_from > 0 && opt_rev_graph)
2683 /* Incrementally draw avoids flickering. */
2684 redraw_view_from(view, redraw_from);
2687 if (view == VIEW(REQ_VIEW_BLAME))
2688 redraw_view_dirty(view);
2690 /* Update the title _after_ the redraw so that if the redraw picks up a
2691 * commit reference in view->ref it'll be available here. */
2692 update_view_title(view);
2696 report("Allocation failure");
2697 end_update(view, TRUE);
2701 static struct line *
2702 add_line_data(struct view *view, void *data, enum line_type type)
2704 struct line *line = &view->line[view->lines++];
2706 memset(line, 0, sizeof(*line));
2713 static struct line *
2714 add_line_text(struct view *view, const char *text, enum line_type type)
2716 char *data = text ? strdup(text) : NULL;
2718 return data ? add_line_data(view, data, type) : NULL;
2727 OPEN_DEFAULT = 0, /* Use default view switching. */
2728 OPEN_SPLIT = 1, /* Split current view. */
2729 OPEN_BACKGROUNDED = 2, /* Backgrounded. */
2730 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
2731 OPEN_NOMAXIMIZE = 8, /* Do not maximize the current view. */
2732 OPEN_REFRESH = 16, /* Refresh view using previous command. */
2733 OPEN_PREPARED = 32, /* Open already prepared command. */
2737 open_view(struct view *prev, enum request request, enum open_flags flags)
2739 bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
2740 bool split = !!(flags & OPEN_SPLIT);
2741 bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
2742 bool nomaximize = !!(flags & (OPEN_NOMAXIMIZE | OPEN_REFRESH));
2743 struct view *view = VIEW(request);
2744 int nviews = displayed_views();
2745 struct view *base_view = display[0];
2747 if (view == prev && nviews == 1 && !reload) {
2748 report("Already in %s view", view->name);
2752 if (view->git_dir && !opt_git_dir[0]) {
2753 report("The %s view is disabled in pager view", view->name);
2761 } else if (!nomaximize) {
2762 /* Maximize the current view. */
2763 memset(display, 0, sizeof(display));
2765 display[current_view] = view;
2768 /* Resize the view when switching between split- and full-screen,
2769 * or when switching between two different full-screen views. */
2770 if (nviews != displayed_views() ||
2771 (nviews == 1 && base_view != display[0]))
2775 end_update(view, TRUE);
2777 if (view->ops->open) {
2778 if (!view->ops->open(view)) {
2779 report("Failed to load %s view", view->name);
2783 } else if ((reload || strcmp(view->vid, view->id)) &&
2784 !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
2785 report("Failed to load %s view", view->name);
2789 if (split && prev->lineno - prev->offset >= prev->height) {
2790 /* Take the title line into account. */
2791 int lines = prev->lineno - prev->offset - prev->height + 1;
2793 /* Scroll the view that was split if the current line is
2794 * outside the new limited view. */
2795 do_scroll_view(prev, lines);
2798 if (prev && view != prev) {
2799 if (split && !backgrounded) {
2800 /* "Blur" the previous view. */
2801 update_view_title(prev);
2804 view->parent = prev;
2807 if (view->pipe && view->lines == 0) {
2808 /* Clear the old view and let the incremental updating refill
2812 } else if (view_is_displayed(view)) {
2817 /* If the view is backgrounded the above calls to report()
2818 * won't redraw the view title. */
2820 update_view_title(view);
2824 open_external_viewer(const char *argv[], const char *dir)
2826 def_prog_mode(); /* save current tty modes */
2827 endwin(); /* restore original tty modes */
2828 run_io_fg(argv, dir);
2829 fprintf(stderr, "Press Enter to continue");
2836 open_mergetool(const char *file)
2838 const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
2840 open_external_viewer(mergetool_argv, NULL);
2844 open_editor(bool from_root, const char *file)
2846 const char *editor_argv[] = { "vi", file, NULL };
2849 editor = getenv("GIT_EDITOR");
2850 if (!editor && *opt_editor)
2851 editor = opt_editor;
2853 editor = getenv("VISUAL");
2855 editor = getenv("EDITOR");
2859 editor_argv[0] = editor;
2860 open_external_viewer(editor_argv, from_root ? opt_cdup : NULL);
2864 open_run_request(enum request request)
2866 struct run_request *req = get_run_request(request);
2867 const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
2870 report("Unknown run request");
2874 if (format_argv(argv, req->argv, FORMAT_ALL))
2875 open_external_viewer(argv, NULL);
2880 * User request switch noodle
2884 view_driver(struct view *view, enum request request)
2888 if (request == REQ_NONE) {
2893 if (request > REQ_NONE) {
2894 open_run_request(request);
2895 /* FIXME: When all views can refresh always do this. */
2896 if (view == VIEW(REQ_VIEW_STATUS) ||
2897 view == VIEW(REQ_VIEW_MAIN) ||
2898 view == VIEW(REQ_VIEW_LOG) ||
2899 view == VIEW(REQ_VIEW_STAGE))
2900 request = REQ_REFRESH;
2905 if (view && view->lines) {
2906 request = view->ops->request(view, request, &view->line[view->lineno]);
2907 if (request == REQ_NONE)
2914 case REQ_MOVE_PAGE_UP:
2915 case REQ_MOVE_PAGE_DOWN:
2916 case REQ_MOVE_FIRST_LINE:
2917 case REQ_MOVE_LAST_LINE:
2918 move_view(view, request);
2921 case REQ_SCROLL_LINE_DOWN:
2922 case REQ_SCROLL_LINE_UP:
2923 case REQ_SCROLL_PAGE_DOWN:
2924 case REQ_SCROLL_PAGE_UP:
2925 scroll_view(view, request);
2928 case REQ_VIEW_BLAME:
2930 report("No file chosen, press %s to open tree view",
2931 get_key(REQ_VIEW_TREE));
2934 open_view(view, request, OPEN_DEFAULT);
2939 report("No file chosen, press %s to open tree view",
2940 get_key(REQ_VIEW_TREE));
2943 open_view(view, request, OPEN_DEFAULT);
2946 case REQ_VIEW_PAGER:
2947 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
2948 report("No pager content, press %s to run command from prompt",
2949 get_key(REQ_PROMPT));
2952 open_view(view, request, OPEN_DEFAULT);
2955 case REQ_VIEW_STAGE:
2956 if (!VIEW(REQ_VIEW_STAGE)->lines) {
2957 report("No stage content, press %s to open the status view and choose file",
2958 get_key(REQ_VIEW_STATUS));
2961 open_view(view, request, OPEN_DEFAULT);
2964 case REQ_VIEW_STATUS:
2965 if (opt_is_inside_work_tree == FALSE) {
2966 report("The status view requires a working tree");
2969 open_view(view, request, OPEN_DEFAULT);
2977 open_view(view, request, OPEN_DEFAULT);
2982 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
2984 if ((view == VIEW(REQ_VIEW_DIFF) &&
2985 view->parent == VIEW(REQ_VIEW_MAIN)) ||
2986 (view == VIEW(REQ_VIEW_DIFF) &&
2987 view->parent == VIEW(REQ_VIEW_BLAME)) ||
2988 (view == VIEW(REQ_VIEW_STAGE) &&
2989 view->parent == VIEW(REQ_VIEW_STATUS)) ||
2990 (view == VIEW(REQ_VIEW_BLOB) &&
2991 view->parent == VIEW(REQ_VIEW_TREE))) {
2994 view = view->parent;
2995 line = view->lineno;
2996 move_view(view, request);
2997 if (view_is_displayed(view))
2998 update_view_title(view);
2999 if (line != view->lineno)
3000 view->ops->request(view, REQ_ENTER,
3001 &view->line[view->lineno]);
3004 move_view(view, request);
3010 int nviews = displayed_views();
3011 int next_view = (current_view + 1) % nviews;
3013 if (next_view == current_view) {
3014 report("Only one view is displayed");
3018 current_view = next_view;
3019 /* Blur out the title of the previous view. */
3020 update_view_title(view);
3025 report("Refreshing is not yet supported for the %s view", view->name);
3029 if (displayed_views() == 2)
3030 open_view(view, VIEW_REQ(view), OPEN_DEFAULT);
3033 case REQ_TOGGLE_LINENO:
3034 opt_line_number = !opt_line_number;
3038 case REQ_TOGGLE_DATE:
3039 opt_date = !opt_date;
3043 case REQ_TOGGLE_AUTHOR:
3044 opt_author = !opt_author;
3048 case REQ_TOGGLE_REV_GRAPH:
3049 opt_rev_graph = !opt_rev_graph;
3053 case REQ_TOGGLE_REFS:
3054 opt_show_refs = !opt_show_refs;
3059 case REQ_SEARCH_BACK:
3060 search_view(view, request);
3065 find_next(view, request);
3068 case REQ_STOP_LOADING:
3069 for (i = 0; i < ARRAY_SIZE(views); i++) {
3072 report("Stopped loading the %s view", view->name),
3073 end_update(view, TRUE);
3077 case REQ_SHOW_VERSION:
3078 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3081 case REQ_SCREEN_RESIZE:
3084 case REQ_SCREEN_REDRAW:
3089 report("Nothing to edit");
3093 report("Nothing to enter");
3096 case REQ_VIEW_CLOSE:
3097 /* XXX: Mark closed views by letting view->parent point to the
3098 * view itself. Parents to closed view should never be
3101 view->parent->parent != view->parent) {
3102 memset(display, 0, sizeof(display));
3104 display[current_view] = view->parent;
3105 view->parent = view;
3116 report("Unknown key, press 'h' for help");
3129 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3131 char *text = line->data;
3133 if (opt_line_number && draw_lineno(view, lineno))
3136 draw_text(view, line->type, text, TRUE);
3141 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3143 const char *describe_argv[] = { "git", "describe", commit_id, NULL };
3144 char refbuf[SIZEOF_STR];
3147 if (run_io_buf(describe_argv, refbuf, sizeof(refbuf)))
3148 ref = chomp_string(refbuf);
3153 /* This is the only fatal call, since it can "corrupt" the buffer. */
3154 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
3161 add_pager_refs(struct view *view, struct line *line)
3163 char buf[SIZEOF_STR];
3164 char *commit_id = (char *)line->data + STRING_SIZE("commit ");
3166 size_t bufpos = 0, refpos = 0;
3167 const char *sep = "Refs: ";
3168 bool is_tag = FALSE;
3170 assert(line->type == LINE_COMMIT);
3172 refs = get_refs(commit_id);
3174 if (view == VIEW(REQ_VIEW_DIFF))
3175 goto try_add_describe_ref;
3180 struct ref *ref = refs[refpos];
3181 const char *fmt = ref->tag ? "%s[%s]" :
3182 ref->remote ? "%s<%s>" : "%s%s";
3184 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
3189 } while (refs[refpos++]->next);
3191 if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
3192 try_add_describe_ref:
3193 /* Add <tag>-g<commit_id> "fake" reference. */
3194 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
3201 if (!realloc_lines(view, view->line_size + 1))
3204 add_line_text(view, buf, LINE_PP_REFS);
3208 pager_read(struct view *view, char *data)
3215 line = add_line_text(view, data, get_line_type(data));
3219 if (line->type == LINE_COMMIT &&
3220 (view == VIEW(REQ_VIEW_DIFF) ||
3221 view == VIEW(REQ_VIEW_LOG)))
3222 add_pager_refs(view, line);
3228 pager_request(struct view *view, enum request request, struct line *line)
3232 if (request != REQ_ENTER)
3235 if (line->type == LINE_COMMIT &&
3236 (view == VIEW(REQ_VIEW_LOG) ||
3237 view == VIEW(REQ_VIEW_PAGER))) {
3238 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3242 /* Always scroll the view even if it was split. That way
3243 * you can use Enter to scroll through the log view and
3244 * split open each commit diff. */
3245 scroll_view(view, REQ_SCROLL_LINE_DOWN);
3247 /* FIXME: A minor workaround. Scrolling the view will call report("")
3248 * but if we are scrolling a non-current view this won't properly
3249 * update the view title. */
3251 update_view_title(view);
3257 pager_grep(struct view *view, struct line *line)
3260 char *text = line->data;
3265 if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
3272 pager_select(struct view *view, struct line *line)
3274 if (line->type == LINE_COMMIT) {
3275 char *text = (char *)line->data + STRING_SIZE("commit ");
3277 if (view != VIEW(REQ_VIEW_PAGER))
3278 string_copy_rev(view->ref, text);
3279 string_copy_rev(ref_commit, text);
3283 static struct view_ops pager_ops = {
3294 static const char *log_argv[SIZEOF_ARG] = {
3295 "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3299 log_request(struct view *view, enum request request, struct line *line)
3304 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
3307 return pager_request(view, request, line);
3311 static struct view_ops log_ops = {
3322 static const char *diff_argv[SIZEOF_ARG] = {
3323 "git", "show", "--pretty=fuller", "--no-color", "--root",
3324 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
3327 static struct view_ops diff_ops = {
3343 help_open(struct view *view)
3346 int lines = ARRAY_SIZE(req_info) + 2;
3349 if (view->lines > 0)
3352 for (i = 0; i < ARRAY_SIZE(req_info); i++)
3353 if (!req_info[i].request)
3356 lines += run_requests + 1;
3358 view->line = calloc(lines, sizeof(*view->line));
3362 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
3364 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
3367 if (req_info[i].request == REQ_NONE)
3370 if (!req_info[i].request) {
3371 add_line_text(view, "", LINE_DEFAULT);
3372 add_line_text(view, req_info[i].help, LINE_DEFAULT);
3376 key = get_key(req_info[i].request);
3378 key = "(no key defined)";
3380 if (!string_format(buf, " %-25s %s", key, req_info[i].help))
3383 add_line_text(view, buf, LINE_DEFAULT);
3387 add_line_text(view, "", LINE_DEFAULT);
3388 add_line_text(view, "External commands:", LINE_DEFAULT);
3391 for (i = 0; i < run_requests; i++) {
3392 struct run_request *req = get_run_request(REQ_NONE + i + 1);
3394 char cmd[SIZEOF_STR];
3401 key = get_key_name(req->key);
3403 key = "(no key defined)";
3405 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
3406 if (!string_format_from(cmd, &bufpos, "%s%s",
3407 argc ? " " : "", req->argv[argc]))
3410 if (!string_format(buf, " %-10s %-14s `%s`",
3411 keymap_table[req->keymap].name, key, cmd))
3414 add_line_text(view, buf, LINE_DEFAULT);
3420 static struct view_ops help_ops = {
3436 struct tree_stack_entry {
3437 struct tree_stack_entry *prev; /* Entry below this in the stack */
3438 unsigned long lineno; /* Line number to restore */
3439 char *name; /* Position of name in opt_path */
3442 /* The top of the path stack. */
3443 static struct tree_stack_entry *tree_stack = NULL;
3444 unsigned long tree_lineno = 0;
3447 pop_tree_stack_entry(void)
3449 struct tree_stack_entry *entry = tree_stack;
3451 tree_lineno = entry->lineno;
3453 tree_stack = entry->prev;
3458 push_tree_stack_entry(const char *name, unsigned long lineno)
3460 struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
3461 size_t pathlen = strlen(opt_path);
3466 entry->prev = tree_stack;
3467 entry->name = opt_path + pathlen;
3470 if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
3471 pop_tree_stack_entry();
3475 /* Move the current line to the first tree entry. */
3477 entry->lineno = lineno;
3480 /* Parse output from git-ls-tree(1):
3482 * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile
3483 * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README
3484 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
3485 * 100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38 web.conf
3488 #define SIZEOF_TREE_ATTR \
3489 STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t")
3491 #define TREE_UP_FORMAT "040000 tree %s\t.."
3494 tree_compare_entry(enum line_type type1, const char *name1,
3495 enum line_type type2, const char *name2)
3497 if (type1 != type2) {
3498 if (type1 == LINE_TREE_DIR)
3503 return strcmp(name1, name2);
3507 tree_path(struct line *line)
3509 const char *path = line->data;
3511 return path + SIZEOF_TREE_ATTR;
3515 tree_read(struct view *view, char *text)
3517 size_t textlen = text ? strlen(text) : 0;
3518 char buf[SIZEOF_STR];
3520 enum line_type type;
3521 bool first_read = view->lines == 0;
3525 if (textlen <= SIZEOF_TREE_ATTR)
3528 type = text[STRING_SIZE("100644 ")] == 't'
3529 ? LINE_TREE_DIR : LINE_TREE_FILE;
3532 /* Add path info line */
3533 if (!string_format(buf, "Directory path /%s", opt_path) ||
3534 !realloc_lines(view, view->line_size + 1) ||
3535 !add_line_text(view, buf, LINE_DEFAULT))
3538 /* Insert "link" to parent directory. */
3540 if (!string_format(buf, TREE_UP_FORMAT, view->ref) ||
3541 !realloc_lines(view, view->line_size + 1) ||
3542 !add_line_text(view, buf, LINE_TREE_DIR))
3547 /* Strip the path part ... */
3549 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
3550 size_t striplen = strlen(opt_path);
3551 char *path = text + SIZEOF_TREE_ATTR;
3553 if (pathlen > striplen)
3554 memmove(path, path + striplen,
3555 pathlen - striplen + 1);
3558 /* Skip "Directory ..." and ".." line. */
3559 for (pos = 1 + !!*opt_path; pos < view->lines; pos++) {
3560 struct line *line = &view->line[pos];
3561 const char *path1 = tree_path(line);
3562 char *path2 = text + SIZEOF_TREE_ATTR;
3563 int cmp = tree_compare_entry(line->type, path1, type, path2);
3568 text = strdup(text);
3572 if (view->lines > pos)
3573 memmove(&view->line[pos + 1], &view->line[pos],
3574 (view->lines - pos) * sizeof(*line));
3576 line = &view->line[pos];
3583 if (!add_line_text(view, text, type))
3586 if (tree_lineno > view->lineno) {
3587 view->lineno = tree_lineno;
3595 tree_request(struct view *view, enum request request, struct line *line)
3597 enum open_flags flags;
3600 case REQ_VIEW_BLAME:
3601 if (line->type != LINE_TREE_FILE) {
3602 report("Blame only supported for files");
3606 string_copy(opt_ref, view->vid);
3610 if (line->type != LINE_TREE_FILE) {
3611 report("Edit only supported for files");
3612 } else if (!is_head_commit(view->vid)) {
3613 report("Edit only supported for files in the current work tree");
3615 open_editor(TRUE, opt_file);
3619 case REQ_TREE_PARENT:
3621 /* quit view if at top of tree */
3622 return REQ_VIEW_CLOSE;
3625 line = &view->line[1];
3635 /* Cleanup the stack if the tree view is at a different tree. */
3636 while (!*opt_path && tree_stack)
3637 pop_tree_stack_entry();
3639 switch (line->type) {
3641 /* Depending on whether it is a subdir or parent (updir?) link
3642 * mangle the path buffer. */
3643 if (line == &view->line[1] && *opt_path) {
3644 pop_tree_stack_entry();
3647 const char *basename = tree_path(line);
3649 push_tree_stack_entry(basename, view->lineno);
3652 /* Trees and subtrees share the same ID, so they are not not
3653 * unique like blobs. */
3654 flags = OPEN_RELOAD;
3655 request = REQ_VIEW_TREE;
3658 case LINE_TREE_FILE:
3659 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
3660 request = REQ_VIEW_BLOB;
3667 open_view(view, request, flags);
3668 if (request == REQ_VIEW_TREE) {
3669 view->lineno = tree_lineno;
3676 tree_select(struct view *view, struct line *line)
3678 char *text = (char *)line->data + STRING_SIZE("100644 blob ");
3680 if (line->type == LINE_TREE_FILE) {
3681 string_copy_rev(ref_blob, text);
3682 string_format(opt_file, "%s%s", opt_path, tree_path(line));
3684 } else if (line->type != LINE_TREE_DIR) {
3688 string_copy_rev(view->ref, text);
3691 static const char *tree_argv[SIZEOF_ARG] = {
3692 "git", "ls-tree", "%(commit)", "%(directory)", NULL
3695 static struct view_ops tree_ops = {
3707 blob_read(struct view *view, char *line)
3711 return add_line_text(view, line, LINE_DEFAULT) != NULL;
3714 static const char *blob_argv[SIZEOF_ARG] = {
3715 "git", "cat-file", "blob", "%(blob)", NULL
3718 static struct view_ops blob_ops = {
3732 * Loading the blame view is a two phase job:
3734 * 1. File content is read either using opt_file from the
3735 * filesystem or using git-cat-file.
3736 * 2. Then blame information is incrementally added by
3737 * reading output from git-blame.
3740 static const char *blame_head_argv[] = {
3741 "git", "blame", "--incremental", "--", "%(file)", NULL
3744 static const char *blame_ref_argv[] = {
3745 "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
3748 static const char *blame_cat_file_argv[] = {
3749 "git", "cat-file", "blob", "%(ref):%(file)", NULL
3752 struct blame_commit {
3753 char id[SIZEOF_REV]; /* SHA1 ID. */
3754 char title[128]; /* First line of the commit message. */
3755 char author[75]; /* Author of the commit. */
3756 struct tm time; /* Date from the author ident. */
3757 char filename[128]; /* Name of file. */
3761 struct blame_commit *commit;
3766 blame_open(struct view *view)
3768 if (*opt_ref || !init_io_fd(&view->io, fopen(opt_file, "r"))) {
3769 if (!run_io_rd(&view->io, blame_cat_file_argv, FORMAT_ALL))
3773 setup_update(view, opt_file);
3774 string_format(view->ref, "%s ...", opt_file);
3779 static struct blame_commit *
3780 get_blame_commit(struct view *view, const char *id)
3784 for (i = 0; i < view->lines; i++) {
3785 struct blame *blame = view->line[i].data;
3790 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
3791 return blame->commit;
3795 struct blame_commit *commit = calloc(1, sizeof(*commit));
3798 string_ncopy(commit->id, id, SIZEOF_REV);
3804 parse_number(const char **posref, size_t *number, size_t min, size_t max)
3806 const char *pos = *posref;
3809 pos = strchr(pos + 1, ' ');
3810 if (!pos || !isdigit(pos[1]))
3812 *number = atoi(pos + 1);
3813 if (*number < min || *number > max)
3820 static struct blame_commit *
3821 parse_blame_commit(struct view *view, const char *text, int *blamed)
3823 struct blame_commit *commit;
3824 struct blame *blame;
3825 const char *pos = text + SIZEOF_REV - 1;
3829 if (strlen(text) <= SIZEOF_REV || *pos != ' ')
3832 if (!parse_number(&pos, &lineno, 1, view->lines) ||
3833 !parse_number(&pos, &group, 1, view->lines - lineno + 1))
3836 commit = get_blame_commit(view, text);
3842 struct line *line = &view->line[lineno + group - 1];
3845 blame->commit = commit;
3853 blame_read_file(struct view *view, const char *line, bool *read_file)
3856 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
3859 if (view->lines == 0 && !view->parent)
3860 die("No blame exist for %s", view->vid);
3862 if (view->lines == 0 || !run_io_rd(&io, argv, FORMAT_ALL)) {
3863 report("Failed to load blame data");
3867 done_io(view->pipe);
3873 size_t linelen = strlen(line);
3874 struct blame *blame = malloc(sizeof(*blame) + linelen);
3876 blame->commit = NULL;
3877 strncpy(blame->text, line, linelen);
3878 blame->text[linelen] = 0;
3879 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
3884 match_blame_header(const char *name, char **line)
3886 size_t namelen = strlen(name);
3887 bool matched = !strncmp(name, *line, namelen);
3896 blame_read(struct view *view, char *line)
3898 static struct blame_commit *commit = NULL;
3899 static int blamed = 0;
3900 static time_t author_time;
3901 static bool read_file = TRUE;
3904 return blame_read_file(view, line, &read_file);
3911 string_format(view->ref, "%s", view->vid);
3912 if (view_is_displayed(view)) {
3913 update_view_title(view);
3914 redraw_view_from(view, 0);
3920 commit = parse_blame_commit(view, line, &blamed);
3921 string_format(view->ref, "%s %2d%%", view->vid,
3922 blamed * 100 / view->lines);
3924 } else if (match_blame_header("author ", &line)) {
3925 string_ncopy(commit->author, line, strlen(line));
3927 } else if (match_blame_header("author-time ", &line)) {
3928 author_time = (time_t) atol(line);
3930 } else if (match_blame_header("author-tz ", &line)) {
3933 tz = ('0' - line[1]) * 60 * 60 * 10;
3934 tz += ('0' - line[2]) * 60 * 60;
3935 tz += ('0' - line[3]) * 60;
3936 tz += ('0' - line[4]) * 60;
3942 gmtime_r(&author_time, &commit->time);
3944 } else if (match_blame_header("summary ", &line)) {
3945 string_ncopy(commit->title, line, strlen(line));
3947 } else if (match_blame_header("filename ", &line)) {
3948 string_ncopy(commit->filename, line, strlen(line));
3956 blame_draw(struct view *view, struct line *line, unsigned int lineno)
3958 struct blame *blame = line->data;
3959 struct tm *time = NULL;
3960 const char *id = NULL, *author = NULL;
3962 if (blame->commit && *blame->commit->filename) {
3963 id = blame->commit->id;
3964 author = blame->commit->author;
3965 time = &blame->commit->time;
3968 if (opt_date && draw_date(view, time))
3972 draw_field(view, LINE_MAIN_AUTHOR, author, opt_author_cols, TRUE))
3975 if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
3978 if (draw_lineno(view, lineno))
3981 draw_text(view, LINE_DEFAULT, blame->text, TRUE);
3986 blame_request(struct view *view, enum request request, struct line *line)
3988 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
3989 struct blame *blame = line->data;
3992 case REQ_VIEW_BLAME:
3993 if (!blame->commit || !strcmp(blame->commit->id, NULL_ID)) {
3994 report("Commit ID unknown");
3997 string_copy(opt_ref, blame->commit->id);
3998 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4002 if (!blame->commit) {
4003 report("No commit loaded yet");
4007 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
4008 !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
4011 if (!strcmp(blame->commit->id, NULL_ID)) {
4012 struct view *diff = VIEW(REQ_VIEW_DIFF);
4013 const char *diff_index_argv[] = {
4014 "git", "diff-index", "--root", "--cached",
4015 "--patch-with-stat", "-C", "-M",
4016 "HEAD", "--", view->vid, NULL
4019 if (!prepare_update(diff, diff_index_argv, NULL, FORMAT_DASH)) {
4020 report("Failed to allocate diff command");
4023 flags |= OPEN_PREPARED;
4026 open_view(view, REQ_VIEW_DIFF, flags);
4037 blame_grep(struct view *view, struct line *line)
4039 struct blame *blame = line->data;
4040 struct blame_commit *commit = blame->commit;
4043 #define MATCH(text, on) \
4044 (on && *text && regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4047 char buf[DATE_COLS + 1];
4049 if (MATCH(commit->title, 1) ||
4050 MATCH(commit->author, opt_author) ||
4051 MATCH(commit->id, opt_date))
4054 if (strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time) &&
4059 return MATCH(blame->text, 1);
4065 blame_select(struct view *view, struct line *line)
4067 struct blame *blame = line->data;
4068 struct blame_commit *commit = blame->commit;
4073 if (!strcmp(commit->id, NULL_ID))
4074 string_ncopy(ref_commit, "HEAD", 4);
4076 string_copy_rev(ref_commit, commit->id);
4079 static struct view_ops blame_ops = {
4098 char rev[SIZEOF_REV];
4099 char name[SIZEOF_STR];
4103 char rev[SIZEOF_REV];
4104 char name[SIZEOF_STR];
4108 static char status_onbranch[SIZEOF_STR];
4109 static struct status stage_status;
4110 static enum line_type stage_line_type;
4111 static size_t stage_chunks;
4112 static int *stage_chunk;
4114 /* This should work even for the "On branch" line. */
4116 status_has_none(struct view *view, struct line *line)
4118 return line < view->line + view->lines && !line[1].data;
4121 /* Get fields from the diff line:
4122 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
4125 status_get_diff(struct status *file, const char *buf, size_t bufsize)
4127 const char *old_mode = buf + 1;
4128 const char *new_mode = buf + 8;
4129 const char *old_rev = buf + 15;
4130 const char *new_rev = buf + 56;
4131 const char *status = buf + 97;
4134 old_mode[-1] != ':' ||
4135 new_mode[-1] != ' ' ||
4136 old_rev[-1] != ' ' ||
4137 new_rev[-1] != ' ' ||
4141 file->status = *status;
4143 string_copy_rev(file->old.rev, old_rev);
4144 string_copy_rev(file->new.rev, new_rev);
4146 file->old.mode = strtoul(old_mode, NULL, 8);
4147 file->new.mode = strtoul(new_mode, NULL, 8);
4149 file->old.name[0] = file->new.name[0] = 0;
4155 status_run(struct view *view, const char cmd[], char status, enum line_type type)
4157 struct status *file = NULL;
4158 struct status *unmerged = NULL;
4159 char buf[SIZEOF_STR * 4];
4163 pipe = popen(cmd, "r");
4167 add_line_data(view, NULL, type);
4169 while (!feof(pipe) && !ferror(pipe)) {
4173 readsize = fread(buf + bufsize, 1, sizeof(buf) - bufsize, pipe);
4176 bufsize += readsize;
4178 /* Process while we have NUL chars. */
4179 while ((sep = memchr(buf, 0, bufsize))) {
4180 size_t sepsize = sep - buf + 1;
4183 if (!realloc_lines(view, view->line_size + 1))
4186 file = calloc(1, sizeof(*file));
4190 add_line_data(view, file, type);
4193 /* Parse diff info part. */
4195 file->status = status;
4197 string_copy(file->old.rev, NULL_ID);
4199 } else if (!file->status) {
4200 if (!status_get_diff(file, buf, sepsize))
4204 memmove(buf, sep + 1, bufsize);
4206 sep = memchr(buf, 0, bufsize);
4209 sepsize = sep - buf + 1;
4211 /* Collapse all 'M'odified entries that
4212 * follow a associated 'U'nmerged entry.
4214 if (file->status == 'U') {
4217 } else if (unmerged) {
4218 int collapse = !strcmp(buf, unmerged->new.name);
4229 /* Grab the old name for rename/copy. */
4230 if (!*file->old.name &&
4231 (file->status == 'R' || file->status == 'C')) {
4232 sepsize = sep - buf + 1;
4233 string_ncopy(file->old.name, buf, sepsize);
4235 memmove(buf, sep + 1, bufsize);
4237 sep = memchr(buf, 0, bufsize);
4240 sepsize = sep - buf + 1;
4243 /* git-ls-files just delivers a NUL separated
4244 * list of file names similar to the second half
4245 * of the git-diff-* output. */
4246 string_ncopy(file->new.name, buf, sepsize);
4247 if (!*file->old.name)
4248 string_copy(file->old.name, file->new.name);
4250 memmove(buf, sep + 1, bufsize);
4261 if (!view->line[view->lines - 1].data)
4262 add_line_data(view, NULL, LINE_STAT_NONE);
4268 /* Don't show unmerged entries in the staged section. */
4269 #define STATUS_DIFF_INDEX_CMD "git diff-index -z --diff-filter=ACDMRTXB --cached -M HEAD"
4270 #define STATUS_DIFF_FILES_CMD "git diff-files -z"
4271 #define STATUS_LIST_OTHER_CMD \
4272 "git ls-files -z --others --exclude-standard"
4273 #define STATUS_LIST_NO_HEAD_CMD \
4274 "git ls-files -z --cached --exclude-standard"
4276 /* First parse staged info using git-diff-index(1), then parse unstaged
4277 * info using git-diff-files(1), and finally untracked files using
4278 * git-ls-files(1). */
4280 status_open(struct view *view)
4282 unsigned long prev_lineno = view->lineno;
4286 if (!realloc_lines(view, view->line_size + 7))
4289 add_line_data(view, NULL, LINE_STAT_HEAD);
4290 if (is_initial_commit())
4291 string_copy(status_onbranch, "Initial commit");
4292 else if (!*opt_head)
4293 string_copy(status_onbranch, "Not currently on any branch");
4294 else if (!string_format(status_onbranch, "On branch %s", opt_head))
4297 system("git update-index -q --refresh >/dev/null 2>/dev/null");
4299 if (is_initial_commit()) {
4300 if (!status_run(view, STATUS_LIST_NO_HEAD_CMD, 'A', LINE_STAT_STAGED))
4302 } else if (!status_run(view, STATUS_DIFF_INDEX_CMD, 0, LINE_STAT_STAGED)) {
4306 if (!status_run(view, STATUS_DIFF_FILES_CMD, 0, LINE_STAT_UNSTAGED) ||
4307 !status_run(view, STATUS_LIST_OTHER_CMD, '?', LINE_STAT_UNTRACKED))
4310 /* If all went well restore the previous line number to stay in
4311 * the context or select a line with something that can be
4313 if (prev_lineno >= view->lines)
4314 prev_lineno = view->lines - 1;
4315 while (prev_lineno < view->lines && !view->line[prev_lineno].data)
4317 while (prev_lineno > 0 && !view->line[prev_lineno].data)
4320 /* If the above fails, always skip the "On branch" line. */
4321 if (prev_lineno < view->lines)
4322 view->lineno = prev_lineno;
4326 if (view->lineno < view->offset)
4327 view->offset = view->lineno;
4328 else if (view->offset + view->height <= view->lineno)
4329 view->offset = view->lineno - view->height + 1;
4335 status_draw(struct view *view, struct line *line, unsigned int lineno)
4337 struct status *status = line->data;
4338 enum line_type type;
4342 switch (line->type) {
4343 case LINE_STAT_STAGED:
4344 type = LINE_STAT_SECTION;
4345 text = "Changes to be committed:";
4348 case LINE_STAT_UNSTAGED:
4349 type = LINE_STAT_SECTION;
4350 text = "Changed but not updated:";
4353 case LINE_STAT_UNTRACKED:
4354 type = LINE_STAT_SECTION;
4355 text = "Untracked files:";
4358 case LINE_STAT_NONE:
4359 type = LINE_DEFAULT;
4360 text = " (no files)";
4363 case LINE_STAT_HEAD:
4364 type = LINE_STAT_HEAD;
4365 text = status_onbranch;
4372 static char buf[] = { '?', ' ', ' ', ' ', 0 };
4374 buf[0] = status->status;
4375 if (draw_text(view, line->type, buf, TRUE))
4377 type = LINE_DEFAULT;
4378 text = status->new.name;
4381 draw_text(view, type, text, TRUE);
4386 status_enter(struct view *view, struct line *line)
4388 struct status *status = line->data;
4389 const char *oldpath = status ? status->old.name : NULL;
4390 /* Diffs for unmerged entries are empty when passing the new
4391 * path, so leave it empty. */
4392 const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
4394 enum open_flags split;
4395 struct view *stage = VIEW(REQ_VIEW_STAGE);
4397 if (line->type == LINE_STAT_NONE ||
4398 (!status && line[1].type == LINE_STAT_NONE)) {
4399 report("No file to diff");
4403 switch (line->type) {
4404 case LINE_STAT_STAGED:
4405 if (is_initial_commit()) {
4406 const char *no_head_diff_argv[] = {
4407 "git", "diff", "--no-color", "--patch-with-stat",
4408 "--", "/dev/null", newpath, NULL
4411 if (!prepare_update(stage, no_head_diff_argv, opt_cdup, FORMAT_DASH))
4414 const char *index_show_argv[] = {
4415 "git", "diff-index", "--root", "--patch-with-stat",
4416 "-C", "-M", "--cached", "HEAD", "--",
4417 oldpath, newpath, NULL
4420 if (!prepare_update(stage, index_show_argv, opt_cdup, FORMAT_DASH))
4425 info = "Staged changes to %s";
4427 info = "Staged changes";
4430 case LINE_STAT_UNSTAGED:
4432 const char *files_show_argv[] = {
4433 "git", "diff-files", "--root", "--patch-with-stat",
4434 "-C", "-M", "--", oldpath, newpath, NULL
4437 if (!prepare_update(stage, files_show_argv, opt_cdup, FORMAT_DASH))
4440 info = "Unstaged changes to %s";
4442 info = "Unstaged changes";
4445 case LINE_STAT_UNTRACKED:
4447 report("No file to show");
4451 if (!suffixcmp(status->new.name, -1, "/")) {
4452 report("Cannot display a directory");
4456 if (!prepare_update_file(stage, newpath))
4458 info = "Untracked file %s";
4461 case LINE_STAT_HEAD:
4465 die("line type %d not handled in switch", line->type);
4468 split = view_is_displayed(view) ? OPEN_SPLIT : 0;
4469 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH | split);
4470 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
4472 stage_status = *status;
4474 memset(&stage_status, 0, sizeof(stage_status));
4477 stage_line_type = line->type;
4479 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
4486 status_exists(struct status *status, enum line_type type)
4488 struct view *view = VIEW(REQ_VIEW_STATUS);
4491 for (line = view->line; line < view->line + view->lines; line++) {
4492 struct status *pos = line->data;
4494 if (line->type == type && pos &&
4495 !strcmp(status->new.name, pos->new.name))
4504 status_update_prepare(enum line_type type)
4506 char cmd[SIZEOF_STR];
4510 type != LINE_STAT_UNTRACKED &&
4511 !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
4515 case LINE_STAT_STAGED:
4516 string_add(cmd, cmdsize, "git update-index -z --index-info");
4519 case LINE_STAT_UNSTAGED:
4520 case LINE_STAT_UNTRACKED:
4521 string_add(cmd, cmdsize, "git update-index -z --add --remove --stdin");
4525 die("line type %d not handled in switch", type);
4528 return popen(cmd, "w");
4532 status_update_write(FILE *pipe, struct status *status, enum line_type type)
4534 char buf[SIZEOF_STR];
4539 case LINE_STAT_STAGED:
4540 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
4543 status->old.name, 0))
4547 case LINE_STAT_UNSTAGED:
4548 case LINE_STAT_UNTRACKED:
4549 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
4554 die("line type %d not handled in switch", type);
4557 while (!ferror(pipe) && written < bufsize) {
4558 written += fwrite(buf + written, 1, bufsize - written, pipe);
4561 return written == bufsize;
4565 status_update_file(struct status *status, enum line_type type)
4567 FILE *pipe = status_update_prepare(type);
4573 result = status_update_write(pipe, status, type);
4579 status_update_files(struct view *view, struct line *line)
4581 FILE *pipe = status_update_prepare(line->type);
4583 struct line *pos = view->line + view->lines;
4590 for (pos = line; pos < view->line + view->lines && pos->data; pos++)
4593 for (file = 0, done = 0; result && file < files; line++, file++) {
4594 int almost_done = file * 100 / files;
4596 if (almost_done > done) {
4598 string_format(view->ref, "updating file %u of %u (%d%% done)",
4600 update_view_title(view);
4602 result = status_update_write(pipe, line->data, line->type);
4610 status_update(struct view *view)
4612 struct line *line = &view->line[view->lineno];
4614 assert(view->lines);
4617 /* This should work even for the "On branch" line. */
4618 if (line < view->line + view->lines && !line[1].data) {
4619 report("Nothing to update");
4623 if (!status_update_files(view, line + 1)) {
4624 report("Failed to update file status");
4628 } else if (!status_update_file(line->data, line->type)) {
4629 report("Failed to update file status");
4637 status_revert(struct status *status, enum line_type type, bool has_none)
4639 if (!status || type != LINE_STAT_UNSTAGED) {
4640 if (type == LINE_STAT_STAGED) {
4641 report("Cannot revert changes to staged files");
4642 } else if (type == LINE_STAT_UNTRACKED) {
4643 report("Cannot revert changes to untracked files");
4644 } else if (has_none) {
4645 report("Nothing to revert");
4647 report("Cannot revert changes to multiple files");
4652 const char *checkout_argv[] = {
4653 "git", "checkout", "--", status->old.name, NULL
4656 if (!prompt_yesno("Are you sure you want to overwrite any changes?"))
4658 return run_io_fg(checkout_argv, opt_cdup);
4663 status_request(struct view *view, enum request request, struct line *line)
4665 struct status *status = line->data;
4668 case REQ_STATUS_UPDATE:
4669 if (!status_update(view))
4673 case REQ_STATUS_REVERT:
4674 if (!status_revert(status, line->type, status_has_none(view, line)))
4678 case REQ_STATUS_MERGE:
4679 if (!status || status->status != 'U') {
4680 report("Merging only possible for files with unmerged status ('U').");
4683 open_mergetool(status->new.name);
4689 if (status->status == 'D') {
4690 report("File has been deleted.");
4694 open_editor(status->status != '?', status->new.name);
4697 case REQ_VIEW_BLAME:
4699 string_copy(opt_file, status->new.name);
4705 /* After returning the status view has been split to
4706 * show the stage view. No further reloading is
4708 status_enter(view, line);
4712 /* Simply reload the view. */
4719 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
4725 status_select(struct view *view, struct line *line)
4727 struct status *status = line->data;
4728 char file[SIZEOF_STR] = "all files";
4732 if (status && !string_format(file, "'%s'", status->new.name))
4735 if (!status && line[1].type == LINE_STAT_NONE)
4738 switch (line->type) {
4739 case LINE_STAT_STAGED:
4740 text = "Press %s to unstage %s for commit";
4743 case LINE_STAT_UNSTAGED:
4744 text = "Press %s to stage %s for commit";
4747 case LINE_STAT_UNTRACKED:
4748 text = "Press %s to stage %s for addition";
4751 case LINE_STAT_HEAD:
4752 case LINE_STAT_NONE:
4753 text = "Nothing to update";
4757 die("line type %d not handled in switch", line->type);
4760 if (status && status->status == 'U') {
4761 text = "Press %s to resolve conflict in %s";
4762 key = get_key(REQ_STATUS_MERGE);
4765 key = get_key(REQ_STATUS_UPDATE);
4768 string_format(view->ref, text, key, file);
4772 status_grep(struct view *view, struct line *line)
4774 struct status *status = line->data;
4775 enum { S_STATUS, S_NAME, S_END } state;
4782 for (state = S_STATUS; state < S_END; state++) {
4786 case S_NAME: text = status->new.name; break;
4788 buf[0] = status->status;
4796 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4803 static struct view_ops status_ops = {
4816 stage_diff_line(FILE *pipe, struct line *line)
4818 const char *buf = line->data;
4819 size_t bufsize = strlen(buf);
4822 while (!ferror(pipe) && written < bufsize) {
4823 written += fwrite(buf + written, 1, bufsize - written, pipe);
4828 return written == bufsize;
4832 stage_diff_write(FILE *pipe, struct line *line, struct line *end)
4834 while (line < end) {
4835 if (!stage_diff_line(pipe, line++))
4837 if (line->type == LINE_DIFF_CHUNK ||
4838 line->type == LINE_DIFF_HEADER)
4845 static struct line *
4846 stage_diff_find(struct view *view, struct line *line, enum line_type type)
4848 for (; view->line < line; line--)
4849 if (line->type == type)
4856 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
4858 char cmd[SIZEOF_STR];
4860 struct line *diff_hdr;
4863 diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
4868 !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
4871 if (!string_format_from(cmd, &cmdsize,
4872 "git apply --whitespace=nowarn %s %s - && "
4873 "git update-index -q --unmerged --refresh 2>/dev/null",
4874 revert ? "" : "--cached",
4875 revert || stage_line_type == LINE_STAT_STAGED ? "-R" : ""))
4878 pipe = popen(cmd, "w");
4882 if (!stage_diff_write(pipe, diff_hdr, chunk) ||
4883 !stage_diff_write(pipe, chunk, view->line + view->lines))
4888 return chunk ? TRUE : FALSE;
4892 stage_update(struct view *view, struct line *line)
4894 struct line *chunk = NULL;
4896 if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
4897 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
4900 if (!stage_apply_chunk(view, chunk, FALSE)) {
4901 report("Failed to apply chunk");
4905 } else if (!stage_status.status) {
4906 view = VIEW(REQ_VIEW_STATUS);
4908 for (line = view->line; line < view->line + view->lines; line++)
4909 if (line->type == stage_line_type)
4912 if (!status_update_files(view, line + 1)) {
4913 report("Failed to update files");
4917 } else if (!status_update_file(&stage_status, stage_line_type)) {
4918 report("Failed to update file");
4926 stage_revert(struct view *view, struct line *line)
4928 struct line *chunk = NULL;
4930 if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
4931 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
4934 if (!prompt_yesno("Are you sure you want to revert changes?"))
4937 if (!stage_apply_chunk(view, chunk, TRUE)) {
4938 report("Failed to revert chunk");
4944 return status_revert(stage_status.status ? &stage_status : NULL,
4945 stage_line_type, FALSE);
4951 stage_next(struct view *view, struct line *line)
4955 if (!stage_chunks) {
4956 static size_t alloc = 0;
4959 for (line = view->line; line < view->line + view->lines; line++) {
4960 if (line->type != LINE_DIFF_CHUNK)
4963 tmp = realloc_items(stage_chunk, &alloc,
4964 stage_chunks, sizeof(*tmp));
4966 report("Allocation failure");
4971 stage_chunk[stage_chunks++] = line - view->line;
4975 for (i = 0; i < stage_chunks; i++) {
4976 if (stage_chunk[i] > view->lineno) {
4977 do_scroll_view(view, stage_chunk[i] - view->lineno);
4978 report("Chunk %d of %d", i + 1, stage_chunks);
4983 report("No next chunk found");
4987 stage_request(struct view *view, enum request request, struct line *line)
4990 case REQ_STATUS_UPDATE:
4991 if (!stage_update(view, line))
4995 case REQ_STATUS_REVERT:
4996 if (!stage_revert(view, line))
5000 case REQ_STAGE_NEXT:
5001 if (stage_line_type == LINE_STAT_UNTRACKED) {
5002 report("File is untracked; press %s to add",
5003 get_key(REQ_STATUS_UPDATE));
5006 stage_next(view, line);
5010 if (!stage_status.new.name[0])
5012 if (stage_status.status == 'D') {
5013 report("File has been deleted.");
5017 open_editor(stage_status.status != '?', stage_status.new.name);
5021 /* Reload everything ... */
5024 case REQ_VIEW_BLAME:
5025 if (stage_status.new.name[0]) {
5026 string_copy(opt_file, stage_status.new.name);
5032 return pager_request(view, request, line);
5038 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD | OPEN_NOMAXIMIZE);
5040 /* Check whether the staged entry still exists, and close the
5041 * stage view if it doesn't. */
5042 if (!status_exists(&stage_status, stage_line_type))
5043 return REQ_VIEW_CLOSE;
5045 if (stage_line_type == LINE_STAT_UNTRACKED) {
5046 if (!suffixcmp(stage_status.new.name, -1, "/")) {
5047 report("Cannot display a directory");
5051 if (!prepare_update_file(view, stage_status.new.name)) {
5052 report("Failed to open file: %s", strerror(errno));
5056 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
5061 static struct view_ops stage_ops = {
5078 char id[SIZEOF_REV]; /* SHA1 ID. */
5079 char title[128]; /* First line of the commit message. */
5080 char author[75]; /* Author of the commit. */
5081 struct tm time; /* Date from the author ident. */
5082 struct ref **refs; /* Repository references. */
5083 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
5084 size_t graph_size; /* The width of the graph array. */
5085 bool has_parents; /* Rewritten --parents seen. */
5088 /* Size of rev graph with no "padding" columns */
5089 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
5092 struct rev_graph *prev, *next, *parents;
5093 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
5095 struct commit *commit;
5097 unsigned int boundary:1;
5100 /* Parents of the commit being visualized. */
5101 static struct rev_graph graph_parents[4];
5103 /* The current stack of revisions on the graph. */
5104 static struct rev_graph graph_stacks[4] = {
5105 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
5106 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
5107 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
5108 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
5112 graph_parent_is_merge(struct rev_graph *graph)
5114 return graph->parents->size > 1;
5118 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
5120 struct commit *commit = graph->commit;
5122 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
5123 commit->graph[commit->graph_size++] = symbol;
5127 clear_rev_graph(struct rev_graph *graph)
5129 graph->boundary = 0;
5130 graph->size = graph->pos = 0;
5131 graph->commit = NULL;
5132 memset(graph->parents, 0, sizeof(*graph->parents));
5136 done_rev_graph(struct rev_graph *graph)
5138 if (graph_parent_is_merge(graph) &&
5139 graph->pos < graph->size - 1 &&
5140 graph->next->size == graph->size + graph->parents->size - 1) {
5141 size_t i = graph->pos + graph->parents->size - 1;
5143 graph->commit->graph_size = i * 2;
5144 while (i < graph->next->size - 1) {
5145 append_to_rev_graph(graph, ' ');
5146 append_to_rev_graph(graph, '\\');
5151 clear_rev_graph(graph);
5155 push_rev_graph(struct rev_graph *graph, const char *parent)
5159 /* "Collapse" duplicate parents lines.
5161 * FIXME: This needs to also update update the drawn graph but
5162 * for now it just serves as a method for pruning graph lines. */
5163 for (i = 0; i < graph->size; i++)
5164 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
5167 if (graph->size < SIZEOF_REVITEMS) {
5168 string_copy_rev(graph->rev[graph->size++], parent);
5173 get_rev_graph_symbol(struct rev_graph *graph)
5177 if (graph->boundary)
5178 symbol = REVGRAPH_BOUND;
5179 else if (graph->parents->size == 0)
5180 symbol = REVGRAPH_INIT;
5181 else if (graph_parent_is_merge(graph))
5182 symbol = REVGRAPH_MERGE;
5183 else if (graph->pos >= graph->size)
5184 symbol = REVGRAPH_BRANCH;
5186 symbol = REVGRAPH_COMMIT;
5192 draw_rev_graph(struct rev_graph *graph)
5195 chtype separator, line;
5197 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
5198 static struct rev_filler fillers[] = {
5204 chtype symbol = get_rev_graph_symbol(graph);
5205 struct rev_filler *filler;
5208 if (opt_line_graphics)
5209 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
5211 filler = &fillers[DEFAULT];
5213 for (i = 0; i < graph->pos; i++) {
5214 append_to_rev_graph(graph, filler->line);
5215 if (graph_parent_is_merge(graph->prev) &&
5216 graph->prev->pos == i)
5217 filler = &fillers[RSHARP];
5219 append_to_rev_graph(graph, filler->separator);
5222 /* Place the symbol for this revision. */
5223 append_to_rev_graph(graph, symbol);
5225 if (graph->prev->size > graph->size)
5226 filler = &fillers[RDIAG];
5228 filler = &fillers[DEFAULT];
5232 for (; i < graph->size; i++) {
5233 append_to_rev_graph(graph, filler->separator);
5234 append_to_rev_graph(graph, filler->line);
5235 if (graph_parent_is_merge(graph->prev) &&
5236 i < graph->prev->pos + graph->parents->size)
5237 filler = &fillers[RSHARP];
5238 if (graph->prev->size > graph->size)
5239 filler = &fillers[LDIAG];
5242 if (graph->prev->size > graph->size) {
5243 append_to_rev_graph(graph, filler->separator);
5244 if (filler->line != ' ')
5245 append_to_rev_graph(graph, filler->line);
5249 /* Prepare the next rev graph */
5251 prepare_rev_graph(struct rev_graph *graph)
5255 /* First, traverse all lines of revisions up to the active one. */
5256 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
5257 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
5260 push_rev_graph(graph->next, graph->rev[graph->pos]);
5263 /* Interleave the new revision parent(s). */
5264 for (i = 0; !graph->boundary && i < graph->parents->size; i++)
5265 push_rev_graph(graph->next, graph->parents->rev[i]);
5267 /* Lastly, put any remaining revisions. */
5268 for (i = graph->pos + 1; i < graph->size; i++)
5269 push_rev_graph(graph->next, graph->rev[i]);
5273 update_rev_graph(struct rev_graph *graph)
5275 /* If this is the finalizing update ... */
5277 prepare_rev_graph(graph);
5279 /* Graph visualization needs a one rev look-ahead,
5280 * so the first update doesn't visualize anything. */
5281 if (!graph->prev->commit)
5284 draw_rev_graph(graph->prev);
5285 done_rev_graph(graph->prev->prev);
5293 static const char *main_argv[SIZEOF_ARG] = {
5294 "git", "log", "--no-color", "--pretty=raw", "--parents",
5295 "--topo-order", "%(head)", NULL
5299 main_draw(struct view *view, struct line *line, unsigned int lineno)
5301 struct commit *commit = line->data;
5303 if (!*commit->author)
5306 if (opt_date && draw_date(view, &commit->time))
5310 draw_field(view, LINE_MAIN_AUTHOR, commit->author, opt_author_cols, TRUE))
5313 if (opt_rev_graph && commit->graph_size &&
5314 draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
5317 if (opt_show_refs && commit->refs) {
5321 enum line_type type;
5323 if (commit->refs[i]->head)
5324 type = LINE_MAIN_HEAD;
5325 else if (commit->refs[i]->ltag)
5326 type = LINE_MAIN_LOCAL_TAG;
5327 else if (commit->refs[i]->tag)
5328 type = LINE_MAIN_TAG;
5329 else if (commit->refs[i]->tracked)
5330 type = LINE_MAIN_TRACKED;
5331 else if (commit->refs[i]->remote)
5332 type = LINE_MAIN_REMOTE;
5334 type = LINE_MAIN_REF;
5336 if (draw_text(view, type, "[", TRUE) ||
5337 draw_text(view, type, commit->refs[i]->name, TRUE) ||
5338 draw_text(view, type, "]", TRUE))
5341 if (draw_text(view, LINE_DEFAULT, " ", TRUE))
5343 } while (commit->refs[i++]->next);
5346 draw_text(view, LINE_DEFAULT, commit->title, TRUE);
5350 /* Reads git log --pretty=raw output and parses it into the commit struct. */
5352 main_read(struct view *view, char *line)
5354 static struct rev_graph *graph = graph_stacks;
5355 enum line_type type;
5356 struct commit *commit;
5361 if (!view->lines && !view->parent)
5362 die("No revisions match the given arguments.");
5363 if (view->lines > 0) {
5364 commit = view->line[view->lines - 1].data;
5365 if (!*commit->author) {
5368 graph->commit = NULL;
5371 update_rev_graph(graph);
5373 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
5374 clear_rev_graph(&graph_stacks[i]);
5378 type = get_line_type(line);
5379 if (type == LINE_COMMIT) {
5380 commit = calloc(1, sizeof(struct commit));
5384 line += STRING_SIZE("commit ");
5386 graph->boundary = 1;
5390 string_copy_rev(commit->id, line);
5391 commit->refs = get_refs(commit->id);
5392 graph->commit = commit;
5393 add_line_data(view, commit, LINE_MAIN_COMMIT);
5395 while ((line = strchr(line, ' '))) {
5397 push_rev_graph(graph->parents, line);
5398 commit->has_parents = TRUE;
5405 commit = view->line[view->lines - 1].data;
5409 if (commit->has_parents)
5411 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
5416 /* Parse author lines where the name may be empty:
5417 * author <email@address.tld> 1138474660 +0100
5419 char *ident = line + STRING_SIZE("author ");
5420 char *nameend = strchr(ident, '<');
5421 char *emailend = strchr(ident, '>');
5423 if (!nameend || !emailend)
5426 update_rev_graph(graph);
5427 graph = graph->next;
5429 *nameend = *emailend = 0;
5430 ident = chomp_string(ident);
5432 ident = chomp_string(nameend + 1);
5437 string_ncopy(commit->author, ident, strlen(ident));
5439 /* Parse epoch and timezone */
5440 if (emailend[1] == ' ') {
5441 char *secs = emailend + 2;
5442 char *zone = strchr(secs, ' ');
5443 time_t time = (time_t) atol(secs);
5445 if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
5449 tz = ('0' - zone[1]) * 60 * 60 * 10;
5450 tz += ('0' - zone[2]) * 60 * 60;
5451 tz += ('0' - zone[3]) * 60;
5452 tz += ('0' - zone[4]) * 60;
5460 gmtime_r(&time, &commit->time);
5465 /* Fill in the commit title if it has not already been set. */
5466 if (commit->title[0])
5469 /* Require titles to start with a non-space character at the
5470 * offset used by git log. */
5471 if (strncmp(line, " ", 4))
5474 /* Well, if the title starts with a whitespace character,
5475 * try to be forgiving. Otherwise we end up with no title. */
5476 while (isspace(*line))
5480 /* FIXME: More graceful handling of titles; append "..." to
5481 * shortened titles, etc. */
5483 string_ncopy(commit->title, line, strlen(line));
5490 main_request(struct view *view, enum request request, struct line *line)
5492 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
5496 open_view(view, REQ_VIEW_DIFF, flags);
5500 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
5510 grep_refs(struct ref **refs, regex_t *regex)
5518 if (regexec(regex, refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
5520 } while (refs[i++]->next);
5526 main_grep(struct view *view, struct line *line)
5528 struct commit *commit = line->data;
5529 enum { S_TITLE, S_AUTHOR, S_DATE, S_REFS, S_END } state;
5530 char buf[DATE_COLS + 1];
5533 for (state = S_TITLE; state < S_END; state++) {
5537 case S_TITLE: text = commit->title; break;
5541 text = commit->author;
5546 if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
5553 if (grep_refs(commit->refs, view->regex) == TRUE)
5560 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
5568 main_select(struct view *view, struct line *line)
5570 struct commit *commit = line->data;
5572 string_copy_rev(view->ref, commit->id);
5573 string_copy_rev(ref_commit, view->ref);
5576 static struct view_ops main_ops = {
5589 * Unicode / UTF-8 handling
5591 * NOTE: Much of the following code for dealing with unicode is derived from
5592 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
5593 * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
5596 /* I've (over)annotated a lot of code snippets because I am not entirely
5597 * confident that the approach taken by this small UTF-8 interface is correct.
5601 unicode_width(unsigned long c)
5604 (c <= 0x115f /* Hangul Jamo */
5607 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
5609 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
5610 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
5611 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
5612 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
5613 || (c >= 0xffe0 && c <= 0xffe6)
5614 || (c >= 0x20000 && c <= 0x2fffd)
5615 || (c >= 0x30000 && c <= 0x3fffd)))
5619 return opt_tab_size;
5624 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
5625 * Illegal bytes are set one. */
5626 static const unsigned char utf8_bytes[256] = {
5627 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,
5628 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,
5629 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,
5630 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,
5631 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,
5632 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,
5633 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,
5634 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,
5637 /* Decode UTF-8 multi-byte representation into a unicode character. */
5638 static inline unsigned long
5639 utf8_to_unicode(const char *string, size_t length)
5641 unsigned long unicode;
5645 unicode = string[0];
5648 unicode = (string[0] & 0x1f) << 6;
5649 unicode += (string[1] & 0x3f);
5652 unicode = (string[0] & 0x0f) << 12;
5653 unicode += ((string[1] & 0x3f) << 6);
5654 unicode += (string[2] & 0x3f);
5657 unicode = (string[0] & 0x0f) << 18;
5658 unicode += ((string[1] & 0x3f) << 12);
5659 unicode += ((string[2] & 0x3f) << 6);
5660 unicode += (string[3] & 0x3f);
5663 unicode = (string[0] & 0x0f) << 24;
5664 unicode += ((string[1] & 0x3f) << 18);
5665 unicode += ((string[2] & 0x3f) << 12);
5666 unicode += ((string[3] & 0x3f) << 6);
5667 unicode += (string[4] & 0x3f);
5670 unicode = (string[0] & 0x01) << 30;
5671 unicode += ((string[1] & 0x3f) << 24);
5672 unicode += ((string[2] & 0x3f) << 18);
5673 unicode += ((string[3] & 0x3f) << 12);
5674 unicode += ((string[4] & 0x3f) << 6);
5675 unicode += (string[5] & 0x3f);
5678 die("Invalid unicode length");
5681 /* Invalid characters could return the special 0xfffd value but NUL
5682 * should be just as good. */
5683 return unicode > 0xffff ? 0 : unicode;
5686 /* Calculates how much of string can be shown within the given maximum width
5687 * and sets trimmed parameter to non-zero value if all of string could not be
5688 * shown. If the reserve flag is TRUE, it will reserve at least one
5689 * trailing character, which can be useful when drawing a delimiter.
5691 * Returns the number of bytes to output from string to satisfy max_width. */
5693 utf8_length(const char *string, int *width, size_t max_width, int *trimmed, bool reserve)
5695 const char *start = string;
5696 const char *end = strchr(string, '\0');
5697 unsigned char last_bytes = 0;
5698 size_t last_ucwidth = 0;
5703 while (string < end) {
5704 int c = *(unsigned char *) string;
5705 unsigned char bytes = utf8_bytes[c];
5707 unsigned long unicode;
5709 if (string + bytes > end)
5712 /* Change representation to figure out whether
5713 * it is a single- or double-width character. */
5715 unicode = utf8_to_unicode(string, bytes);
5716 /* FIXME: Graceful handling of invalid unicode character. */
5720 ucwidth = unicode_width(unicode);
5722 if (*width > max_width) {
5725 if (reserve && *width == max_width) {
5726 string -= last_bytes;
5727 *width -= last_ucwidth;
5734 last_ucwidth = ucwidth;
5737 return string - start;
5745 /* Whether or not the curses interface has been initialized. */
5746 static bool cursed = FALSE;
5748 /* The status window is used for polling keystrokes. */
5749 static WINDOW *status_win;
5751 static bool status_empty = TRUE;
5753 /* Update status and title window. */
5755 report(const char *msg, ...)
5757 struct view *view = display[current_view];
5763 char buf[SIZEOF_STR];
5766 va_start(args, msg);
5767 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
5768 buf[sizeof(buf) - 1] = 0;
5769 buf[sizeof(buf) - 2] = '.';
5770 buf[sizeof(buf) - 3] = '.';
5771 buf[sizeof(buf) - 4] = '.';
5777 if (!status_empty || *msg) {
5780 va_start(args, msg);
5782 wmove(status_win, 0, 0);
5784 vwprintw(status_win, msg, args);
5785 status_empty = FALSE;
5787 status_empty = TRUE;
5789 wclrtoeol(status_win);
5790 wrefresh(status_win);
5795 update_view_title(view);
5796 update_display_cursor(view);
5799 /* Controls when nodelay should be in effect when polling user input. */
5801 set_nonblocking_input(bool loading)
5803 static unsigned int loading_views;
5805 if ((loading == FALSE && loading_views-- == 1) ||
5806 (loading == TRUE && loading_views++ == 0))
5807 nodelay(status_win, loading);
5815 /* Initialize the curses library */
5816 if (isatty(STDIN_FILENO)) {
5817 cursed = !!initscr();
5820 /* Leave stdin and stdout alone when acting as a pager. */
5821 opt_tty = fopen("/dev/tty", "r+");
5823 die("Failed to open /dev/tty");
5824 cursed = !!newterm(NULL, opt_tty, opt_tty);
5828 die("Failed to initialize curses");
5830 nonl(); /* Tell curses not to do NL->CR/NL on output */
5831 cbreak(); /* Take input chars one at a time, no wait for \n */
5832 noecho(); /* Don't echo input */
5833 leaveok(stdscr, TRUE);
5838 getmaxyx(stdscr, y, x);
5839 status_win = newwin(1, 0, y - 1, 0);
5841 die("Failed to create status window");
5843 /* Enable keyboard mapping */
5844 keypad(status_win, TRUE);
5845 wbkgdset(status_win, get_line_attr(LINE_STATUS));
5847 TABSIZE = opt_tab_size;
5848 if (opt_line_graphics) {
5849 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
5854 prompt_yesno(const char *prompt)
5856 enum { WAIT, STOP, CANCEL } status = WAIT;
5857 bool answer = FALSE;
5859 while (status == WAIT) {
5865 foreach_view (view, i)
5870 mvwprintw(status_win, 0, 0, "%s [Yy]/[Nn]", prompt);
5871 wclrtoeol(status_win);
5873 /* Refresh, accept single keystroke of input */
5874 key = wgetch(status_win);
5898 /* Clear the status window */
5899 status_empty = FALSE;
5906 read_prompt(const char *prompt)
5908 enum { READING, STOP, CANCEL } status = READING;
5909 static char buf[SIZEOF_STR];
5912 while (status == READING) {
5918 foreach_view (view, i)
5923 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
5924 wclrtoeol(status_win);
5926 /* Refresh, accept single keystroke of input */
5927 key = wgetch(status_win);
5932 status = pos ? STOP : CANCEL;
5950 if (pos >= sizeof(buf)) {
5951 report("Input string too long");
5956 buf[pos++] = (char) key;
5960 /* Clear the status window */
5961 status_empty = FALSE;
5964 if (status == CANCEL)
5973 * Repository references
5976 static struct ref *refs = NULL;
5977 static size_t refs_alloc = 0;
5978 static size_t refs_size = 0;
5980 /* Id <-> ref store */
5981 static struct ref ***id_refs = NULL;
5982 static size_t id_refs_alloc = 0;
5983 static size_t id_refs_size = 0;
5986 compare_refs(const void *ref1_, const void *ref2_)
5988 const struct ref *ref1 = *(const struct ref **)ref1_;
5989 const struct ref *ref2 = *(const struct ref **)ref2_;
5991 if (ref1->tag != ref2->tag)
5992 return ref2->tag - ref1->tag;
5993 if (ref1->ltag != ref2->ltag)
5994 return ref2->ltag - ref2->ltag;
5995 if (ref1->head != ref2->head)
5996 return ref2->head - ref1->head;
5997 if (ref1->tracked != ref2->tracked)
5998 return ref2->tracked - ref1->tracked;
5999 if (ref1->remote != ref2->remote)
6000 return ref2->remote - ref1->remote;
6001 return strcmp(ref1->name, ref2->name);
6004 static struct ref **
6005 get_refs(const char *id)
6007 struct ref ***tmp_id_refs;
6008 struct ref **ref_list = NULL;
6009 size_t ref_list_alloc = 0;
6010 size_t ref_list_size = 0;
6013 for (i = 0; i < id_refs_size; i++)
6014 if (!strcmp(id, id_refs[i][0]->id))
6017 tmp_id_refs = realloc_items(id_refs, &id_refs_alloc, id_refs_size + 1,
6022 id_refs = tmp_id_refs;
6024 for (i = 0; i < refs_size; i++) {
6027 if (strcmp(id, refs[i].id))
6030 tmp = realloc_items(ref_list, &ref_list_alloc,
6031 ref_list_size + 1, sizeof(*ref_list));
6039 ref_list[ref_list_size] = &refs[i];
6040 /* XXX: The properties of the commit chains ensures that we can
6041 * safely modify the shared ref. The repo references will
6042 * always be similar for the same id. */
6043 ref_list[ref_list_size]->next = 1;
6049 qsort(ref_list, ref_list_size, sizeof(*ref_list), compare_refs);
6050 ref_list[ref_list_size - 1]->next = 0;
6051 id_refs[id_refs_size++] = ref_list;
6058 read_ref(char *id, size_t idlen, char *name, size_t namelen)
6063 bool remote = FALSE;
6064 bool tracked = FALSE;
6065 bool check_replace = FALSE;
6068 if (!prefixcmp(name, "refs/tags/")) {
6069 if (!suffixcmp(name, namelen, "^{}")) {
6072 if (refs_size > 0 && refs[refs_size - 1].ltag == TRUE)
6073 check_replace = TRUE;
6079 namelen -= STRING_SIZE("refs/tags/");
6080 name += STRING_SIZE("refs/tags/");
6082 } else if (!prefixcmp(name, "refs/remotes/")) {
6084 namelen -= STRING_SIZE("refs/remotes/");
6085 name += STRING_SIZE("refs/remotes/");
6086 tracked = !strcmp(opt_remote, name);
6088 } else if (!prefixcmp(name, "refs/heads/")) {
6089 namelen -= STRING_SIZE("refs/heads/");
6090 name += STRING_SIZE("refs/heads/");
6091 head = !strncmp(opt_head, name, namelen);
6093 } else if (!strcmp(name, "HEAD")) {
6094 string_ncopy(opt_head_rev, id, idlen);
6098 if (check_replace && !strcmp(name, refs[refs_size - 1].name)) {
6099 /* it's an annotated tag, replace the previous sha1 with the
6100 * resolved commit id; relies on the fact git-ls-remote lists
6101 * the commit id of an annotated tag right before the commit id
6103 refs[refs_size - 1].ltag = ltag;
6104 string_copy_rev(refs[refs_size - 1].id, id);
6108 refs = realloc_items(refs, &refs_alloc, refs_size + 1, sizeof(*refs));
6112 ref = &refs[refs_size++];
6113 ref->name = malloc(namelen + 1);
6117 strncpy(ref->name, name, namelen);
6118 ref->name[namelen] = 0;
6122 ref->remote = remote;
6123 ref->tracked = tracked;
6124 string_copy_rev(ref->id, id);
6132 const char *cmd_env = getenv("TIG_LS_REMOTE");
6133 const char *cmd = cmd_env && *cmd_env ? cmd_env : TIG_LS_REMOTE;
6138 while (refs_size > 0)
6139 free(refs[--refs_size].name);
6140 while (id_refs_size > 0)
6141 free(id_refs[--id_refs_size]);
6143 return read_properties(popen(cmd, "r"), "\t", read_ref);
6147 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
6149 if (!strcmp(name, "i18n.commitencoding"))
6150 string_ncopy(opt_encoding, value, valuelen);
6152 if (!strcmp(name, "core.editor"))
6153 string_ncopy(opt_editor, value, valuelen);
6155 /* branch.<head>.remote */
6157 !strncmp(name, "branch.", 7) &&
6158 !strncmp(name + 7, opt_head, strlen(opt_head)) &&
6159 !strcmp(name + 7 + strlen(opt_head), ".remote"))
6160 string_ncopy(opt_remote, value, valuelen);
6162 if (*opt_head && *opt_remote &&
6163 !strncmp(name, "branch.", 7) &&
6164 !strncmp(name + 7, opt_head, strlen(opt_head)) &&
6165 !strcmp(name + 7 + strlen(opt_head), ".merge")) {
6166 size_t from = strlen(opt_remote);
6168 if (!prefixcmp(value, "refs/heads/")) {
6169 value += STRING_SIZE("refs/heads/");
6170 valuelen -= STRING_SIZE("refs/heads/");
6173 if (!string_format_from(opt_remote, &from, "/%s", value))
6181 load_git_config(void)
6183 return read_properties(popen("git " GIT_CONFIG " --list", "r"),
6184 "=", read_repo_config_option);
6188 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
6190 if (!opt_git_dir[0]) {
6191 string_ncopy(opt_git_dir, name, namelen);
6193 } else if (opt_is_inside_work_tree == -1) {
6194 /* This can be 3 different values depending on the
6195 * version of git being used. If git-rev-parse does not
6196 * understand --is-inside-work-tree it will simply echo
6197 * the option else either "true" or "false" is printed.
6198 * Default to true for the unknown case. */
6199 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
6201 } else if (opt_cdup[0] == ' ') {
6202 string_ncopy(opt_cdup, name, namelen);
6204 if (!prefixcmp(name, "refs/heads/")) {
6205 namelen -= STRING_SIZE("refs/heads/");
6206 name += STRING_SIZE("refs/heads/");
6207 string_ncopy(opt_head, name, namelen);
6215 load_repo_info(void)
6218 FILE *pipe = popen("(git rev-parse --git-dir --is-inside-work-tree "
6219 " --show-cdup; git symbolic-ref HEAD) 2>/dev/null", "r");
6221 /* XXX: The line outputted by "--show-cdup" can be empty so
6222 * initialize it to something invalid to make it possible to
6223 * detect whether it has been set or not. */
6226 result = read_properties(pipe, "=", read_repo_info);
6227 if (opt_cdup[0] == ' ')
6234 read_properties(FILE *pipe, const char *separators,
6235 int (*read_property)(char *, size_t, char *, size_t))
6237 char buffer[BUFSIZ];
6244 while (state == OK && (name = fgets(buffer, sizeof(buffer), pipe))) {
6249 name = chomp_string(name);
6250 namelen = strcspn(name, separators);
6252 if (name[namelen]) {
6254 value = chomp_string(name + namelen + 1);
6255 valuelen = strlen(value);
6262 state = read_property(name, namelen, value, valuelen);
6265 if (state != ERR && ferror(pipe))
6278 static void __NORETURN
6281 /* XXX: Restore tty modes and let the OS cleanup the rest! */
6287 static void __NORETURN
6288 die(const char *err, ...)
6294 va_start(args, err);
6295 fputs("tig: ", stderr);
6296 vfprintf(stderr, err, args);
6297 fputs("\n", stderr);
6304 warn(const char *msg, ...)
6308 va_start(args, msg);
6309 fputs("tig warning: ", stderr);
6310 vfprintf(stderr, msg, args);
6311 fputs("\n", stderr);
6316 main(int argc, const char *argv[])
6318 const char **run_argv = NULL;
6320 enum request request;
6323 signal(SIGINT, quit);
6325 if (setlocale(LC_ALL, "")) {
6326 char *codeset = nl_langinfo(CODESET);
6328 string_ncopy(opt_codeset, codeset, strlen(codeset));
6331 if (load_repo_info() == ERR)
6332 die("Failed to load repo info.");
6334 if (load_options() == ERR)
6335 die("Failed to load user config.");
6337 if (load_git_config() == ERR)
6338 die("Failed to load repo config.");
6340 request = parse_options(argc, argv, &run_argv);
6341 if (request == REQ_NONE)
6344 /* Require a git repository unless when running in pager mode. */
6345 if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
6346 die("Not a git repository");
6348 if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
6351 if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
6352 opt_iconv = iconv_open(opt_codeset, opt_encoding);
6353 if (opt_iconv == ICONV_NONE)
6354 die("Failed to initialize character set conversion");
6357 if (load_refs() == ERR)
6358 die("Failed to load refs.");
6360 foreach_view (view, i)
6361 argv_from_env(view->ops->argv, view->cmd_env);
6365 if (request == REQ_VIEW_PAGER || run_argv) {
6366 if (request == REQ_VIEW_PAGER)
6367 init_io_fd(&VIEW(request)->io, stdin);
6368 else if (!prepare_update(VIEW(request), run_argv, NULL, FORMAT_NONE))
6369 die("Failed to format arguments");
6370 open_view(NULL, request, OPEN_PREPARED);
6374 while (view_driver(display[current_view], request)) {
6378 foreach_view (view, i)
6380 view = display[current_view];
6382 /* Refresh, accept single keystroke of input */
6383 key = wgetch(status_win);
6385 /* wgetch() with nodelay() enabled returns ERR when there's no
6392 request = get_keybinding(view->keymap, key);
6394 /* Some low-level request handling. This keeps access to
6395 * status_win restricted. */
6399 char *cmd = read_prompt(":");
6402 struct view *next = VIEW(REQ_VIEW_PAGER);
6403 const char *argv[SIZEOF_ARG] = { "git" };
6406 /* When running random commands, initially show the
6407 * command in the title. However, it maybe later be
6408 * overwritten if a commit line is selected. */
6409 string_ncopy(next->ref, cmd, strlen(cmd));
6411 if (!argv_from_string(argv, &argc, cmd)) {
6412 report("Too many arguments");
6413 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
6414 report("Failed to format command");
6416 open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
6424 case REQ_SEARCH_BACK:
6426 const char *prompt = request == REQ_SEARCH ? "/" : "?";
6427 char *search = read_prompt(prompt);
6430 string_ncopy(opt_search, search, strlen(search));
6435 case REQ_SCREEN_RESIZE:
6439 getmaxyx(stdscr, height, width);
6441 /* Resize the status view and let the view driver take
6442 * care of resizing the displayed views. */
6443 wresize(status_win, 1, width);
6444 mvwin(status_win, height - 1, 0);
6445 wrefresh(status_win);