1 /* Copyright (c) 2006-2008 Jonas Fonseca <fonseca@diku.dk>
3 * This program is free software; you can redistribute it and/or
4 * modify it under the terms of the GNU General Public License as
5 * published by the Free Software Foundation; either version 2 of
6 * the License, or (at your option) any later version.
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
19 #define TIG_VERSION "unknown-version"
34 #include <sys/types.h>
45 /* ncurses(3): Must be defined to have extended wide-character functions. */
46 #define _XOPEN_SOURCE_EXTENDED
48 #ifdef HAVE_NCURSESW_NCURSES_H
49 #include <ncursesw/ncurses.h>
51 #ifdef HAVE_NCURSES_NCURSES_H
52 #include <ncurses/ncurses.h>
59 #define __NORETURN __attribute__((__noreturn__))
64 static void __NORETURN die(const char *err, ...);
65 static void warn(const char *msg, ...);
66 static void report(const char *msg, ...);
67 static int read_properties(FILE *pipe, const char *separators, int (*read)(char *, size_t, char *, size_t));
68 static void set_nonblocking_input(bool loading);
69 static size_t utf8_length(const char *string, int *width, size_t max_width, int *trimmed, bool reserve);
70 static bool prompt_yesno(const char *prompt);
71 static int load_refs(void);
73 #define ABS(x) ((x) >= 0 ? (x) : -(x))
74 #define MIN(x, y) ((x) < (y) ? (x) : (y))
76 #define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
77 #define STRING_SIZE(x) (sizeof(x) - 1)
79 #define SIZEOF_STR 1024 /* Default string size. */
80 #define SIZEOF_REF 256 /* Size of symbolic or SHA1 ID. */
81 #define SIZEOF_REV 41 /* Holds a SHA-1 and an ending NUL. */
82 #define SIZEOF_ARG 32 /* Default argument array size. */
86 #define REVGRAPH_INIT 'I'
87 #define REVGRAPH_MERGE 'M'
88 #define REVGRAPH_BRANCH '+'
89 #define REVGRAPH_COMMIT '*'
90 #define REVGRAPH_BOUND '^'
92 #define SIZEOF_REVGRAPH 19 /* Size of revision ancestry graphics. */
94 /* This color name can be used to refer to the default term colors. */
95 #define COLOR_DEFAULT (-1)
97 #define ICONV_NONE ((iconv_t) -1)
99 #define ICONV_CONST /* nothing */
102 /* The format and size of the date column in the main view. */
103 #define DATE_FORMAT "%Y-%m-%d %H:%M"
104 #define DATE_COLS STRING_SIZE("2006-04-29 14:21 ")
106 #define AUTHOR_COLS 20
109 /* The default interval between line numbers. */
110 #define NUMBER_INTERVAL 5
114 #define SCALE_SPLIT_VIEW(height) ((height) * 2 / 3)
116 #define NULL_ID "0000000000000000000000000000000000000000"
119 #define GIT_CONFIG "config"
122 #define TIG_LS_REMOTE \
123 "git ls-remote . 2>/dev/null"
125 /* Some ascii-shorthands fitted into the ncurses namespace. */
127 #define KEY_RETURN '\r'
132 char *name; /* Ref name; tag or head names are shortened. */
133 char id[SIZEOF_REV]; /* Commit SHA1 ID */
134 unsigned int head:1; /* Is it the current HEAD? */
135 unsigned int tag:1; /* Is it a tag? */
136 unsigned int ltag:1; /* If so, is the tag local? */
137 unsigned int remote:1; /* Is it a remote ref? */
138 unsigned int tracked:1; /* Is it the remote for the current HEAD? */
139 unsigned int next:1; /* For ref lists: are there more refs? */
142 static struct ref **get_refs(const char *id);
145 FORMAT_ALL, /* Perform replacement in all arguments. */
146 FORMAT_DASH, /* Perform replacement up until "--". */
147 FORMAT_NONE /* No replacement should be performed. */
150 static bool format_command(char dst[], const char *src[], enum format_flags flags);
151 static bool format_argv(const char *dst[], const char *src[], enum format_flags flags);
160 set_from_int_map(struct int_map *map, size_t map_size,
161 int *value, const char *name, int namelen)
166 for (i = 0; i < map_size; i++)
167 if (namelen == map[i].namelen &&
168 !strncasecmp(name, map[i].name, namelen)) {
169 *value = map[i].value;
182 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
184 if (srclen > dstlen - 1)
187 strncpy(dst, src, srclen);
191 /* Shorthands for safely copying into a fixed buffer. */
193 #define string_copy(dst, src) \
194 string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
196 #define string_ncopy(dst, src, srclen) \
197 string_ncopy_do(dst, sizeof(dst), src, srclen)
199 #define string_copy_rev(dst, src) \
200 string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
202 #define string_add(dst, from, src) \
203 string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
206 chomp_string(char *name)
210 while (isspace(*name))
213 namelen = strlen(name) - 1;
214 while (namelen > 0 && isspace(name[namelen]))
221 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
224 size_t pos = bufpos ? *bufpos : 0;
227 pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
233 return pos >= bufsize ? FALSE : TRUE;
236 #define string_format(buf, fmt, args...) \
237 string_nformat(buf, sizeof(buf), NULL, fmt, args)
239 #define string_format_from(buf, from, fmt, args...) \
240 string_nformat(buf, sizeof(buf), from, fmt, args)
243 string_enum_compare(const char *str1, const char *str2, int len)
247 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
249 /* Diff-Header == DIFF_HEADER */
250 for (i = 0; i < len; i++) {
251 if (toupper(str1[i]) == toupper(str2[i]))
254 if (string_enum_sep(str1[i]) &&
255 string_enum_sep(str2[i]))
258 return str1[i] - str2[i];
264 #define prefixcmp(str1, str2) \
265 strncmp(str1, str2, STRING_SIZE(str2))
268 suffixcmp(const char *str, int slen, const char *suffix)
270 size_t len = slen >= 0 ? slen : strlen(str);
271 size_t suffixlen = strlen(suffix);
273 return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
278 * NOTE: The following is a slightly modified copy of the git project's shell
279 * quoting routines found in the quote.c file.
281 * Help to copy the thing properly quoted for the shell safety. any single
282 * quote is replaced with '\'', any exclamation point is replaced with '\!',
283 * and the whole thing is enclosed in a
286 * original sq_quote result
287 * name ==> name ==> 'name'
288 * a b ==> a b ==> 'a b'
289 * a'b ==> a'\''b ==> 'a'\''b'
290 * a!b ==> a'\!'b ==> 'a'\!'b'
294 sq_quote(char buf[SIZEOF_STR], size_t bufsize, const char *src)
298 #define BUFPUT(x) do { if (bufsize < SIZEOF_STR) buf[bufsize++] = (x); } while (0)
301 while ((c = *src++)) {
302 if (c == '\'' || c == '!') {
313 if (bufsize < SIZEOF_STR)
320 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
324 while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
325 bool advance = cmd[valuelen] != 0;
328 argv[(*argc)++] = chomp_string(cmd);
329 cmd += valuelen + advance;
332 if (*argc < SIZEOF_ARG)
334 return *argc < SIZEOF_ARG;
338 argv_from_env(const char **argv, const char *name)
340 char *env = argv ? getenv(name) : NULL;
345 if (env && !argv_from_string(argv, &argc, env))
346 die("Too many arguments in the `%s` environment variable", name);
351 * Executing external commands.
355 IO_FD, /* File descriptor based IO. */
356 IO_FG, /* Execute command with same std{in,out,err}. */
357 IO_RD, /* Read only fork+exec IO. */
358 IO_WR, /* Write only fork+exec IO. */
362 enum io_type type; /* The requested type of pipe. */
363 const char *dir; /* Directory from which to execute. */
364 FILE *pipe; /* Pipe for reading or writing. */
365 int error; /* Error status. */
366 char sh[SIZEOF_STR]; /* Shell command buffer. */
367 char *buf; /* Read/write buffer. */
368 size_t bufalloc; /* Allocated buffer size. */
372 reset_io(struct io *io)
381 init_io(struct io *io, const char *dir, enum io_type type)
389 init_io_rd(struct io *io, const char *argv[], const char *dir,
390 enum format_flags flags)
392 init_io(io, dir, IO_RD);
393 return format_command(io->sh, argv, flags);
397 init_io_fd(struct io *io, FILE *pipe)
399 init_io(io, NULL, IO_FD);
401 return io->pipe != NULL;
405 done_io(struct io *io)
408 if (io->type == IO_FD)
410 else if (io->type == IO_RD || io->type == IO_WR)
417 start_io(struct io *io)
419 char buf[SIZEOF_STR * 2];
422 if (io->dir && *io->dir &&
423 !string_format_from(buf, &bufpos, "cd %s;", io->dir))
426 if (!string_format_from(buf, &bufpos, "%s", io->sh))
429 if (io->type == IO_FG)
430 return system(buf) == 0;
432 io->pipe = popen(io->sh, io->type == IO_RD ? "r" : "w");
433 return io->pipe != NULL;
437 run_io_do(struct io *io)
439 return start_io(io) && done_io(io);
443 run_io_fg(const char **argv, const char *dir)
447 init_io(&io, dir, IO_FG);
448 if (!format_command(io.sh, argv, FORMAT_NONE))
450 return run_io_do(&io);
454 run_io_rd(struct io *io, const char **argv, enum format_flags flags)
456 return init_io_rd(io, argv, NULL, flags) && start_io(io);
460 io_eof(struct io *io)
462 return feof(io->pipe);
466 io_error(struct io *io)
472 io_strerror(struct io *io)
474 return strerror(io->error);
478 io_gets(struct io *io)
481 io->buf = malloc(BUFSIZ);
484 io->bufalloc = BUFSIZ;
487 if (!fgets(io->buf, io->bufalloc, io->pipe)) {
488 if (ferror(io->pipe))
497 run_io_buf(const char **argv, char buf[], size_t bufsize)
502 if (!run_io_rd(&io, argv, FORMAT_NONE))
506 io.bufalloc = bufsize;
507 error = !io_gets(&io) && io_error(&io);
510 return done_io(&io) || error;
519 /* XXX: Keep the view request first and in sync with views[]. */ \
520 REQ_GROUP("View switching") \
521 REQ_(VIEW_MAIN, "Show main view"), \
522 REQ_(VIEW_DIFF, "Show diff view"), \
523 REQ_(VIEW_LOG, "Show log view"), \
524 REQ_(VIEW_TREE, "Show tree view"), \
525 REQ_(VIEW_BLOB, "Show blob view"), \
526 REQ_(VIEW_BLAME, "Show blame view"), \
527 REQ_(VIEW_HELP, "Show help page"), \
528 REQ_(VIEW_PAGER, "Show pager view"), \
529 REQ_(VIEW_STATUS, "Show status view"), \
530 REQ_(VIEW_STAGE, "Show stage view"), \
532 REQ_GROUP("View manipulation") \
533 REQ_(ENTER, "Enter current line and scroll"), \
534 REQ_(NEXT, "Move to next"), \
535 REQ_(PREVIOUS, "Move to previous"), \
536 REQ_(VIEW_NEXT, "Move focus to next view"), \
537 REQ_(REFRESH, "Reload and refresh"), \
538 REQ_(MAXIMIZE, "Maximize the current view"), \
539 REQ_(VIEW_CLOSE, "Close the current view"), \
540 REQ_(QUIT, "Close all views and quit"), \
542 REQ_GROUP("View specific requests") \
543 REQ_(STATUS_UPDATE, "Update file status"), \
544 REQ_(STATUS_REVERT, "Revert file changes"), \
545 REQ_(STATUS_MERGE, "Merge file using external tool"), \
546 REQ_(STAGE_NEXT, "Find next chunk to stage"), \
547 REQ_(TREE_PARENT, "Switch to parent directory in tree view"), \
549 REQ_GROUP("Cursor navigation") \
550 REQ_(MOVE_UP, "Move cursor one line up"), \
551 REQ_(MOVE_DOWN, "Move cursor one line down"), \
552 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
553 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
554 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
555 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
557 REQ_GROUP("Scrolling") \
558 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
559 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
560 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
561 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
563 REQ_GROUP("Searching") \
564 REQ_(SEARCH, "Search the view"), \
565 REQ_(SEARCH_BACK, "Search backwards in the view"), \
566 REQ_(FIND_NEXT, "Find next search match"), \
567 REQ_(FIND_PREV, "Find previous search match"), \
569 REQ_GROUP("Option manipulation") \
570 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
571 REQ_(TOGGLE_DATE, "Toggle date display"), \
572 REQ_(TOGGLE_AUTHOR, "Toggle author display"), \
573 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
574 REQ_(TOGGLE_REFS, "Toggle reference display (tags/branches)"), \
577 REQ_(PROMPT, "Bring up the prompt"), \
578 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
579 REQ_(SCREEN_RESIZE, "Resize the screen"), \
580 REQ_(SHOW_VERSION, "Show version information"), \
581 REQ_(STOP_LOADING, "Stop all loading views"), \
582 REQ_(EDIT, "Open in editor"), \
583 REQ_(NONE, "Do nothing")
586 /* User action requests. */
588 #define REQ_GROUP(help)
589 #define REQ_(req, help) REQ_##req
591 /* Offset all requests to avoid conflicts with ncurses getch values. */
592 REQ_OFFSET = KEY_MAX + 1,
599 struct request_info {
600 enum request request;
606 static struct request_info req_info[] = {
607 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
608 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
615 get_request(const char *name)
617 int namelen = strlen(name);
620 for (i = 0; i < ARRAY_SIZE(req_info); i++)
621 if (req_info[i].namelen == namelen &&
622 !string_enum_compare(req_info[i].name, name, namelen))
623 return req_info[i].request;
633 static const char usage[] =
634 "tig " TIG_VERSION " (" __DATE__ ")\n"
636 "Usage: tig [options] [revs] [--] [paths]\n"
637 " or: tig show [options] [revs] [--] [paths]\n"
638 " or: tig blame [rev] path\n"
640 " or: tig < [git command output]\n"
643 " -v, --version Show version and exit\n"
644 " -h, --help Show help message and exit";
646 /* Option and state variables. */
647 static bool opt_date = TRUE;
648 static bool opt_author = TRUE;
649 static bool opt_line_number = FALSE;
650 static bool opt_line_graphics = TRUE;
651 static bool opt_rev_graph = FALSE;
652 static bool opt_show_refs = TRUE;
653 static int opt_num_interval = NUMBER_INTERVAL;
654 static int opt_tab_size = TAB_SIZE;
655 static int opt_author_cols = AUTHOR_COLS-1;
656 static char opt_path[SIZEOF_STR] = "";
657 static char opt_file[SIZEOF_STR] = "";
658 static char opt_ref[SIZEOF_REF] = "";
659 static char opt_head[SIZEOF_REF] = "";
660 static char opt_head_rev[SIZEOF_REV] = "";
661 static char opt_remote[SIZEOF_REF] = "";
662 static FILE *opt_pipe = NULL;
663 static char opt_encoding[20] = "UTF-8";
664 static bool opt_utf8 = TRUE;
665 static char opt_codeset[20] = "UTF-8";
666 static iconv_t opt_iconv = ICONV_NONE;
667 static char opt_search[SIZEOF_STR] = "";
668 static char opt_cdup[SIZEOF_STR] = "";
669 static char opt_git_dir[SIZEOF_STR] = "";
670 static signed char opt_is_inside_work_tree = -1; /* set to TRUE or FALSE */
671 static char opt_editor[SIZEOF_STR] = "";
672 static FILE *opt_tty = NULL;
674 #define is_initial_commit() (!*opt_head_rev)
675 #define is_head_commit(rev) (!strcmp((rev), "HEAD") || !strcmp(opt_head_rev, (rev)))
678 parse_options(int argc, const char *argv[], const char ***run_argv)
680 enum request request = REQ_VIEW_MAIN;
681 const char *subcommand;
682 bool seen_dashdash = FALSE;
683 /* XXX: This is vulnerable to the user overriding options
684 * required for the main view parser. */
685 const char *custom_argv[SIZEOF_ARG] = {
686 "git", "log", "--no-color", "--pretty=raw", "--parents",
691 if (!isatty(STDIN_FILENO)) {
693 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 begin_update(struct view *view, bool refresh)
2526 if (init_io_fd(&view->io, opt_pipe)) {
2529 } else if (refresh) {
2530 if (!start_io(&view->io))
2534 if (view == VIEW(REQ_VIEW_TREE) && strcmp(view->vid, view->id))
2537 if (!run_io_rd(&view->io, view->ops->argv, FORMAT_ALL))
2540 /* Put the current ref_* value to the view title ref
2541 * member. This is needed by the blob view. Most other
2542 * views sets it automatically after loading because the
2543 * first line is a commit line. */
2544 string_copy_rev(view->ref, view->id);
2547 setup_update(view, view->id);
2552 #define ITEM_CHUNK_SIZE 256
2554 realloc_items(void *mem, size_t *size, size_t new_size, size_t item_size)
2556 size_t num_chunks = *size / ITEM_CHUNK_SIZE;
2557 size_t num_chunks_new = (new_size + ITEM_CHUNK_SIZE - 1) / ITEM_CHUNK_SIZE;
2559 if (mem == NULL || num_chunks != num_chunks_new) {
2560 *size = num_chunks_new * ITEM_CHUNK_SIZE;
2561 mem = realloc(mem, *size * item_size);
2567 static struct line *
2568 realloc_lines(struct view *view, size_t line_size)
2570 size_t alloc = view->line_alloc;
2571 struct line *tmp = realloc_items(view->line, &alloc, line_size,
2572 sizeof(*view->line));
2578 view->line_alloc = alloc;
2579 view->line_size = line_size;
2584 update_view(struct view *view)
2586 char out_buffer[BUFSIZ * 2];
2588 /* The number of lines to read. If too low it will cause too much
2589 * redrawing (and possible flickering), if too high responsiveness
2591 unsigned long lines = view->height;
2592 int redraw_from = -1;
2597 /* Only redraw if lines are visible. */
2598 if (view->offset + view->height >= view->lines)
2599 redraw_from = view->lines - view->offset;
2601 /* FIXME: This is probably not perfect for backgrounded views. */
2602 if (!realloc_lines(view, view->lines + lines))
2605 while ((line = io_gets(view->pipe))) {
2606 size_t linelen = strlen(line);
2609 line[linelen - 1] = 0;
2611 if (opt_iconv != ICONV_NONE) {
2612 ICONV_CONST char *inbuf = line;
2613 size_t inlen = linelen;
2615 char *outbuf = out_buffer;
2616 size_t outlen = sizeof(out_buffer);
2620 ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2621 if (ret != (size_t) -1) {
2623 linelen = strlen(out_buffer);
2627 if (!view->ops->read(view, line))
2637 lines = view->lines;
2638 for (digits = 0; lines; digits++)
2641 /* Keep the displayed view in sync with line number scaling. */
2642 if (digits != view->digits) {
2643 view->digits = digits;
2648 if (io_error(view->pipe)) {
2649 report("Failed to read: %s", io_strerror(view->pipe));
2650 end_update(view, TRUE);
2652 } else if (io_eof(view->pipe)) {
2654 end_update(view, FALSE);
2657 if (!view_is_displayed(view))
2660 if (view == VIEW(REQ_VIEW_TREE)) {
2661 /* Clear the view and redraw everything since the tree sorting
2662 * might have rearranged things. */
2665 } else if (redraw_from >= 0) {
2666 /* If this is an incremental update, redraw the previous line
2667 * since for commits some members could have changed when
2668 * loading the main view. */
2669 if (redraw_from > 0)
2672 /* Since revision graph visualization requires knowledge
2673 * about the parent commit, it causes a further one-off
2674 * needed to be redrawn for incremental updates. */
2675 if (redraw_from > 0 && opt_rev_graph)
2678 /* Incrementally draw avoids flickering. */
2679 redraw_view_from(view, redraw_from);
2682 if (view == VIEW(REQ_VIEW_BLAME))
2683 redraw_view_dirty(view);
2685 /* Update the title _after_ the redraw so that if the redraw picks up a
2686 * commit reference in view->ref it'll be available here. */
2687 update_view_title(view);
2691 report("Allocation failure");
2692 end_update(view, TRUE);
2696 static struct line *
2697 add_line_data(struct view *view, void *data, enum line_type type)
2699 struct line *line = &view->line[view->lines++];
2701 memset(line, 0, sizeof(*line));
2708 static struct line *
2709 add_line_text(struct view *view, const char *text, enum line_type type)
2711 char *data = text ? strdup(text) : NULL;
2713 return data ? add_line_data(view, data, type) : NULL;
2722 OPEN_DEFAULT = 0, /* Use default view switching. */
2723 OPEN_SPLIT = 1, /* Split current view. */
2724 OPEN_BACKGROUNDED = 2, /* Backgrounded. */
2725 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
2726 OPEN_NOMAXIMIZE = 8, /* Do not maximize the current view. */
2727 OPEN_REFRESH = 16, /* Refresh view using previous command. */
2728 OPEN_PREPARED = 32, /* Open already prepared command. */
2732 open_view(struct view *prev, enum request request, enum open_flags flags)
2734 bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
2735 bool split = !!(flags & OPEN_SPLIT);
2736 bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
2737 bool nomaximize = !!(flags & (OPEN_NOMAXIMIZE | OPEN_REFRESH));
2738 struct view *view = VIEW(request);
2739 int nviews = displayed_views();
2740 struct view *base_view = display[0];
2742 if (view == prev && nviews == 1 && !reload) {
2743 report("Already in %s view", view->name);
2747 if (view->git_dir && !opt_git_dir[0]) {
2748 report("The %s view is disabled in pager view", view->name);
2756 } else if (!nomaximize) {
2757 /* Maximize the current view. */
2758 memset(display, 0, sizeof(display));
2760 display[current_view] = view;
2763 /* Resize the view when switching between split- and full-screen,
2764 * or when switching between two different full-screen views. */
2765 if (nviews != displayed_views() ||
2766 (nviews == 1 && base_view != display[0]))
2770 end_update(view, TRUE);
2772 if (view->ops->open) {
2773 if (!view->ops->open(view)) {
2774 report("Failed to load %s view", view->name);
2778 } else if ((reload || strcmp(view->vid, view->id)) &&
2779 !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
2780 report("Failed to load %s view", view->name);
2784 if (split && prev->lineno - prev->offset >= prev->height) {
2785 /* Take the title line into account. */
2786 int lines = prev->lineno - prev->offset - prev->height + 1;
2788 /* Scroll the view that was split if the current line is
2789 * outside the new limited view. */
2790 do_scroll_view(prev, lines);
2793 if (prev && view != prev) {
2794 if (split && !backgrounded) {
2795 /* "Blur" the previous view. */
2796 update_view_title(prev);
2799 view->parent = prev;
2802 if (view->pipe && view->lines == 0) {
2803 /* Clear the old view and let the incremental updating refill
2807 } else if (view_is_displayed(view)) {
2812 /* If the view is backgrounded the above calls to report()
2813 * won't redraw the view title. */
2815 update_view_title(view);
2819 open_external_viewer(const char *argv[], const char *dir)
2821 def_prog_mode(); /* save current tty modes */
2822 endwin(); /* restore original tty modes */
2823 run_io_fg(argv, dir);
2824 fprintf(stderr, "Press Enter to continue");
2831 open_mergetool(const char *file)
2833 const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
2835 open_external_viewer(mergetool_argv, NULL);
2839 open_editor(bool from_root, const char *file)
2841 const char *editor_argv[] = { "vi", file, NULL };
2844 editor = getenv("GIT_EDITOR");
2845 if (!editor && *opt_editor)
2846 editor = opt_editor;
2848 editor = getenv("VISUAL");
2850 editor = getenv("EDITOR");
2854 editor_argv[0] = editor;
2855 open_external_viewer(editor_argv, from_root ? opt_cdup : NULL);
2859 open_run_request(enum request request)
2861 struct run_request *req = get_run_request(request);
2862 const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
2865 report("Unknown run request");
2869 if (format_argv(argv, req->argv, FORMAT_ALL))
2870 open_external_viewer(argv, NULL);
2875 * User request switch noodle
2879 view_driver(struct view *view, enum request request)
2883 if (request == REQ_NONE) {
2888 if (request > REQ_NONE) {
2889 open_run_request(request);
2890 /* FIXME: When all views can refresh always do this. */
2891 if (view == VIEW(REQ_VIEW_STATUS) ||
2892 view == VIEW(REQ_VIEW_MAIN) ||
2893 view == VIEW(REQ_VIEW_LOG) ||
2894 view == VIEW(REQ_VIEW_STAGE))
2895 request = REQ_REFRESH;
2900 if (view && view->lines) {
2901 request = view->ops->request(view, request, &view->line[view->lineno]);
2902 if (request == REQ_NONE)
2909 case REQ_MOVE_PAGE_UP:
2910 case REQ_MOVE_PAGE_DOWN:
2911 case REQ_MOVE_FIRST_LINE:
2912 case REQ_MOVE_LAST_LINE:
2913 move_view(view, request);
2916 case REQ_SCROLL_LINE_DOWN:
2917 case REQ_SCROLL_LINE_UP:
2918 case REQ_SCROLL_PAGE_DOWN:
2919 case REQ_SCROLL_PAGE_UP:
2920 scroll_view(view, request);
2923 case REQ_VIEW_BLAME:
2925 report("No file chosen, press %s to open tree view",
2926 get_key(REQ_VIEW_TREE));
2929 open_view(view, request, OPEN_DEFAULT);
2934 report("No file chosen, press %s to open tree view",
2935 get_key(REQ_VIEW_TREE));
2938 open_view(view, request, OPEN_DEFAULT);
2941 case REQ_VIEW_PAGER:
2942 if (!opt_pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
2943 report("No pager content, press %s to run command from prompt",
2944 get_key(REQ_PROMPT));
2947 open_view(view, request, OPEN_DEFAULT);
2950 case REQ_VIEW_STAGE:
2951 if (!VIEW(REQ_VIEW_STAGE)->lines) {
2952 report("No stage content, press %s to open the status view and choose file",
2953 get_key(REQ_VIEW_STATUS));
2956 open_view(view, request, OPEN_DEFAULT);
2959 case REQ_VIEW_STATUS:
2960 if (opt_is_inside_work_tree == FALSE) {
2961 report("The status view requires a working tree");
2964 open_view(view, request, OPEN_DEFAULT);
2972 open_view(view, request, OPEN_DEFAULT);
2977 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
2979 if ((view == VIEW(REQ_VIEW_DIFF) &&
2980 view->parent == VIEW(REQ_VIEW_MAIN)) ||
2981 (view == VIEW(REQ_VIEW_DIFF) &&
2982 view->parent == VIEW(REQ_VIEW_BLAME)) ||
2983 (view == VIEW(REQ_VIEW_STAGE) &&
2984 view->parent == VIEW(REQ_VIEW_STATUS)) ||
2985 (view == VIEW(REQ_VIEW_BLOB) &&
2986 view->parent == VIEW(REQ_VIEW_TREE))) {
2989 view = view->parent;
2990 line = view->lineno;
2991 move_view(view, request);
2992 if (view_is_displayed(view))
2993 update_view_title(view);
2994 if (line != view->lineno)
2995 view->ops->request(view, REQ_ENTER,
2996 &view->line[view->lineno]);
2999 move_view(view, request);
3005 int nviews = displayed_views();
3006 int next_view = (current_view + 1) % nviews;
3008 if (next_view == current_view) {
3009 report("Only one view is displayed");
3013 current_view = next_view;
3014 /* Blur out the title of the previous view. */
3015 update_view_title(view);
3020 report("Refreshing is not yet supported for the %s view", view->name);
3024 if (displayed_views() == 2)
3025 open_view(view, VIEW_REQ(view), OPEN_DEFAULT);
3028 case REQ_TOGGLE_LINENO:
3029 opt_line_number = !opt_line_number;
3033 case REQ_TOGGLE_DATE:
3034 opt_date = !opt_date;
3038 case REQ_TOGGLE_AUTHOR:
3039 opt_author = !opt_author;
3043 case REQ_TOGGLE_REV_GRAPH:
3044 opt_rev_graph = !opt_rev_graph;
3048 case REQ_TOGGLE_REFS:
3049 opt_show_refs = !opt_show_refs;
3054 case REQ_SEARCH_BACK:
3055 search_view(view, request);
3060 find_next(view, request);
3063 case REQ_STOP_LOADING:
3064 for (i = 0; i < ARRAY_SIZE(views); i++) {
3067 report("Stopped loading the %s view", view->name),
3068 end_update(view, TRUE);
3072 case REQ_SHOW_VERSION:
3073 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3076 case REQ_SCREEN_RESIZE:
3079 case REQ_SCREEN_REDRAW:
3084 report("Nothing to edit");
3088 report("Nothing to enter");
3091 case REQ_VIEW_CLOSE:
3092 /* XXX: Mark closed views by letting view->parent point to the
3093 * view itself. Parents to closed view should never be
3096 view->parent->parent != view->parent) {
3097 memset(display, 0, sizeof(display));
3099 display[current_view] = view->parent;
3100 view->parent = view;
3111 report("Unknown key, press 'h' for help");
3124 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3126 char *text = line->data;
3128 if (opt_line_number && draw_lineno(view, lineno))
3131 draw_text(view, line->type, text, TRUE);
3136 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3138 const char *describe_argv[] = { "git", "describe", commit_id, NULL };
3139 char refbuf[SIZEOF_STR];
3142 if (run_io_buf(describe_argv, refbuf, sizeof(refbuf)))
3143 ref = chomp_string(refbuf);
3148 /* This is the only fatal call, since it can "corrupt" the buffer. */
3149 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
3156 add_pager_refs(struct view *view, struct line *line)
3158 char buf[SIZEOF_STR];
3159 char *commit_id = (char *)line->data + STRING_SIZE("commit ");
3161 size_t bufpos = 0, refpos = 0;
3162 const char *sep = "Refs: ";
3163 bool is_tag = FALSE;
3165 assert(line->type == LINE_COMMIT);
3167 refs = get_refs(commit_id);
3169 if (view == VIEW(REQ_VIEW_DIFF))
3170 goto try_add_describe_ref;
3175 struct ref *ref = refs[refpos];
3176 const char *fmt = ref->tag ? "%s[%s]" :
3177 ref->remote ? "%s<%s>" : "%s%s";
3179 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
3184 } while (refs[refpos++]->next);
3186 if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
3187 try_add_describe_ref:
3188 /* Add <tag>-g<commit_id> "fake" reference. */
3189 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
3196 if (!realloc_lines(view, view->line_size + 1))
3199 add_line_text(view, buf, LINE_PP_REFS);
3203 pager_read(struct view *view, char *data)
3210 line = add_line_text(view, data, get_line_type(data));
3214 if (line->type == LINE_COMMIT &&
3215 (view == VIEW(REQ_VIEW_DIFF) ||
3216 view == VIEW(REQ_VIEW_LOG)))
3217 add_pager_refs(view, line);
3223 pager_request(struct view *view, enum request request, struct line *line)
3227 if (request != REQ_ENTER)
3230 if (line->type == LINE_COMMIT &&
3231 (view == VIEW(REQ_VIEW_LOG) ||
3232 view == VIEW(REQ_VIEW_PAGER))) {
3233 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3237 /* Always scroll the view even if it was split. That way
3238 * you can use Enter to scroll through the log view and
3239 * split open each commit diff. */
3240 scroll_view(view, REQ_SCROLL_LINE_DOWN);
3242 /* FIXME: A minor workaround. Scrolling the view will call report("")
3243 * but if we are scrolling a non-current view this won't properly
3244 * update the view title. */
3246 update_view_title(view);
3252 pager_grep(struct view *view, struct line *line)
3255 char *text = line->data;
3260 if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
3267 pager_select(struct view *view, struct line *line)
3269 if (line->type == LINE_COMMIT) {
3270 char *text = (char *)line->data + STRING_SIZE("commit ");
3272 if (view != VIEW(REQ_VIEW_PAGER))
3273 string_copy_rev(view->ref, text);
3274 string_copy_rev(ref_commit, text);
3278 static struct view_ops pager_ops = {
3289 static const char *log_argv[SIZEOF_ARG] = {
3290 "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3294 log_request(struct view *view, enum request request, struct line *line)
3299 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
3302 return pager_request(view, request, line);
3306 static struct view_ops log_ops = {
3317 static const char *diff_argv[SIZEOF_ARG] = {
3318 "git", "show", "--pretty=fuller", "--no-color", "--root",
3319 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
3322 static struct view_ops diff_ops = {
3338 help_open(struct view *view)
3341 int lines = ARRAY_SIZE(req_info) + 2;
3344 if (view->lines > 0)
3347 for (i = 0; i < ARRAY_SIZE(req_info); i++)
3348 if (!req_info[i].request)
3351 lines += run_requests + 1;
3353 view->line = calloc(lines, sizeof(*view->line));
3357 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
3359 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
3362 if (req_info[i].request == REQ_NONE)
3365 if (!req_info[i].request) {
3366 add_line_text(view, "", LINE_DEFAULT);
3367 add_line_text(view, req_info[i].help, LINE_DEFAULT);
3371 key = get_key(req_info[i].request);
3373 key = "(no key defined)";
3375 if (!string_format(buf, " %-25s %s", key, req_info[i].help))
3378 add_line_text(view, buf, LINE_DEFAULT);
3382 add_line_text(view, "", LINE_DEFAULT);
3383 add_line_text(view, "External commands:", LINE_DEFAULT);
3386 for (i = 0; i < run_requests; i++) {
3387 struct run_request *req = get_run_request(REQ_NONE + i + 1);
3389 char cmd[SIZEOF_STR];
3396 key = get_key_name(req->key);
3398 key = "(no key defined)";
3400 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
3401 if (!string_format_from(cmd, &bufpos, "%s%s",
3402 argc ? " " : "", req->argv[argc]))
3405 if (!string_format(buf, " %-10s %-14s `%s`",
3406 keymap_table[req->keymap].name, key, cmd))
3409 add_line_text(view, buf, LINE_DEFAULT);
3415 static struct view_ops help_ops = {
3431 struct tree_stack_entry {
3432 struct tree_stack_entry *prev; /* Entry below this in the stack */
3433 unsigned long lineno; /* Line number to restore */
3434 char *name; /* Position of name in opt_path */
3437 /* The top of the path stack. */
3438 static struct tree_stack_entry *tree_stack = NULL;
3439 unsigned long tree_lineno = 0;
3442 pop_tree_stack_entry(void)
3444 struct tree_stack_entry *entry = tree_stack;
3446 tree_lineno = entry->lineno;
3448 tree_stack = entry->prev;
3453 push_tree_stack_entry(const char *name, unsigned long lineno)
3455 struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
3456 size_t pathlen = strlen(opt_path);
3461 entry->prev = tree_stack;
3462 entry->name = opt_path + pathlen;
3465 if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
3466 pop_tree_stack_entry();
3470 /* Move the current line to the first tree entry. */
3472 entry->lineno = lineno;
3475 /* Parse output from git-ls-tree(1):
3477 * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile
3478 * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README
3479 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
3480 * 100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38 web.conf
3483 #define SIZEOF_TREE_ATTR \
3484 STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t")
3486 #define TREE_UP_FORMAT "040000 tree %s\t.."
3489 tree_compare_entry(enum line_type type1, const char *name1,
3490 enum line_type type2, const char *name2)
3492 if (type1 != type2) {
3493 if (type1 == LINE_TREE_DIR)
3498 return strcmp(name1, name2);
3502 tree_path(struct line *line)
3504 const char *path = line->data;
3506 return path + SIZEOF_TREE_ATTR;
3510 tree_read(struct view *view, char *text)
3512 size_t textlen = text ? strlen(text) : 0;
3513 char buf[SIZEOF_STR];
3515 enum line_type type;
3516 bool first_read = view->lines == 0;
3520 if (textlen <= SIZEOF_TREE_ATTR)
3523 type = text[STRING_SIZE("100644 ")] == 't'
3524 ? LINE_TREE_DIR : LINE_TREE_FILE;
3527 /* Add path info line */
3528 if (!string_format(buf, "Directory path /%s", opt_path) ||
3529 !realloc_lines(view, view->line_size + 1) ||
3530 !add_line_text(view, buf, LINE_DEFAULT))
3533 /* Insert "link" to parent directory. */
3535 if (!string_format(buf, TREE_UP_FORMAT, view->ref) ||
3536 !realloc_lines(view, view->line_size + 1) ||
3537 !add_line_text(view, buf, LINE_TREE_DIR))
3542 /* Strip the path part ... */
3544 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
3545 size_t striplen = strlen(opt_path);
3546 char *path = text + SIZEOF_TREE_ATTR;
3548 if (pathlen > striplen)
3549 memmove(path, path + striplen,
3550 pathlen - striplen + 1);
3553 /* Skip "Directory ..." and ".." line. */
3554 for (pos = 1 + !!*opt_path; pos < view->lines; pos++) {
3555 struct line *line = &view->line[pos];
3556 const char *path1 = tree_path(line);
3557 char *path2 = text + SIZEOF_TREE_ATTR;
3558 int cmp = tree_compare_entry(line->type, path1, type, path2);
3563 text = strdup(text);
3567 if (view->lines > pos)
3568 memmove(&view->line[pos + 1], &view->line[pos],
3569 (view->lines - pos) * sizeof(*line));
3571 line = &view->line[pos];
3578 if (!add_line_text(view, text, type))
3581 if (tree_lineno > view->lineno) {
3582 view->lineno = tree_lineno;
3590 tree_request(struct view *view, enum request request, struct line *line)
3592 enum open_flags flags;
3595 case REQ_VIEW_BLAME:
3596 if (line->type != LINE_TREE_FILE) {
3597 report("Blame only supported for files");
3601 string_copy(opt_ref, view->vid);
3605 if (line->type != LINE_TREE_FILE) {
3606 report("Edit only supported for files");
3607 } else if (!is_head_commit(view->vid)) {
3608 report("Edit only supported for files in the current work tree");
3610 open_editor(TRUE, opt_file);
3614 case REQ_TREE_PARENT:
3616 /* quit view if at top of tree */
3617 return REQ_VIEW_CLOSE;
3620 line = &view->line[1];
3630 /* Cleanup the stack if the tree view is at a different tree. */
3631 while (!*opt_path && tree_stack)
3632 pop_tree_stack_entry();
3634 switch (line->type) {
3636 /* Depending on whether it is a subdir or parent (updir?) link
3637 * mangle the path buffer. */
3638 if (line == &view->line[1] && *opt_path) {
3639 pop_tree_stack_entry();
3642 const char *basename = tree_path(line);
3644 push_tree_stack_entry(basename, view->lineno);
3647 /* Trees and subtrees share the same ID, so they are not not
3648 * unique like blobs. */
3649 flags = OPEN_RELOAD;
3650 request = REQ_VIEW_TREE;
3653 case LINE_TREE_FILE:
3654 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
3655 request = REQ_VIEW_BLOB;
3662 open_view(view, request, flags);
3663 if (request == REQ_VIEW_TREE) {
3664 view->lineno = tree_lineno;
3671 tree_select(struct view *view, struct line *line)
3673 char *text = (char *)line->data + STRING_SIZE("100644 blob ");
3675 if (line->type == LINE_TREE_FILE) {
3676 string_copy_rev(ref_blob, text);
3677 string_format(opt_file, "%s%s", opt_path, tree_path(line));
3679 } else if (line->type != LINE_TREE_DIR) {
3683 string_copy_rev(view->ref, text);
3686 static const char *tree_argv[SIZEOF_ARG] = {
3687 "git", "ls-tree", "%(commit)", "%(directory)", NULL
3690 static struct view_ops tree_ops = {
3702 blob_read(struct view *view, char *line)
3706 return add_line_text(view, line, LINE_DEFAULT) != NULL;
3709 static const char *blob_argv[SIZEOF_ARG] = {
3710 "git", "cat-file", "blob", "%(blob)", NULL
3713 static struct view_ops blob_ops = {
3727 * Loading the blame view is a two phase job:
3729 * 1. File content is read either using opt_file from the
3730 * filesystem or using git-cat-file.
3731 * 2. Then blame information is incrementally added by
3732 * reading output from git-blame.
3735 static const char *blame_head_argv[] = {
3736 "git", "blame", "--incremental", "--", "%(file)", NULL
3739 static const char *blame_ref_argv[] = {
3740 "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
3743 static const char *blame_cat_file_argv[] = {
3744 "git", "cat-file", "blob", "%(ref):%(file)", NULL
3747 struct blame_commit {
3748 char id[SIZEOF_REV]; /* SHA1 ID. */
3749 char title[128]; /* First line of the commit message. */
3750 char author[75]; /* Author of the commit. */
3751 struct tm time; /* Date from the author ident. */
3752 char filename[128]; /* Name of file. */
3756 struct blame_commit *commit;
3761 blame_open(struct view *view)
3763 if (*opt_ref || !init_io_fd(&view->io, fopen(opt_file, "r"))) {
3764 if (!run_io_rd(&view->io, blame_cat_file_argv, FORMAT_ALL))
3768 setup_update(view, opt_file);
3769 string_format(view->ref, "%s ...", opt_file);
3774 static struct blame_commit *
3775 get_blame_commit(struct view *view, const char *id)
3779 for (i = 0; i < view->lines; i++) {
3780 struct blame *blame = view->line[i].data;
3785 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
3786 return blame->commit;
3790 struct blame_commit *commit = calloc(1, sizeof(*commit));
3793 string_ncopy(commit->id, id, SIZEOF_REV);
3799 parse_number(const char **posref, size_t *number, size_t min, size_t max)
3801 const char *pos = *posref;
3804 pos = strchr(pos + 1, ' ');
3805 if (!pos || !isdigit(pos[1]))
3807 *number = atoi(pos + 1);
3808 if (*number < min || *number > max)
3815 static struct blame_commit *
3816 parse_blame_commit(struct view *view, const char *text, int *blamed)
3818 struct blame_commit *commit;
3819 struct blame *blame;
3820 const char *pos = text + SIZEOF_REV - 1;
3824 if (strlen(text) <= SIZEOF_REV || *pos != ' ')
3827 if (!parse_number(&pos, &lineno, 1, view->lines) ||
3828 !parse_number(&pos, &group, 1, view->lines - lineno + 1))
3831 commit = get_blame_commit(view, text);
3837 struct line *line = &view->line[lineno + group - 1];
3840 blame->commit = commit;
3848 blame_read_file(struct view *view, const char *line, bool *read_file)
3851 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
3854 if (view->lines == 0 && !view->parent)
3855 die("No blame exist for %s", view->vid);
3857 if (view->lines == 0 || !run_io_rd(&io, argv, FORMAT_ALL)) {
3858 report("Failed to load blame data");
3862 done_io(view->pipe);
3868 size_t linelen = strlen(line);
3869 struct blame *blame = malloc(sizeof(*blame) + linelen);
3871 blame->commit = NULL;
3872 strncpy(blame->text, line, linelen);
3873 blame->text[linelen] = 0;
3874 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
3879 match_blame_header(const char *name, char **line)
3881 size_t namelen = strlen(name);
3882 bool matched = !strncmp(name, *line, namelen);
3891 blame_read(struct view *view, char *line)
3893 static struct blame_commit *commit = NULL;
3894 static int blamed = 0;
3895 static time_t author_time;
3896 static bool read_file = TRUE;
3899 return blame_read_file(view, line, &read_file);
3906 string_format(view->ref, "%s", view->vid);
3907 if (view_is_displayed(view)) {
3908 update_view_title(view);
3909 redraw_view_from(view, 0);
3915 commit = parse_blame_commit(view, line, &blamed);
3916 string_format(view->ref, "%s %2d%%", view->vid,
3917 blamed * 100 / view->lines);
3919 } else if (match_blame_header("author ", &line)) {
3920 string_ncopy(commit->author, line, strlen(line));
3922 } else if (match_blame_header("author-time ", &line)) {
3923 author_time = (time_t) atol(line);
3925 } else if (match_blame_header("author-tz ", &line)) {
3928 tz = ('0' - line[1]) * 60 * 60 * 10;
3929 tz += ('0' - line[2]) * 60 * 60;
3930 tz += ('0' - line[3]) * 60;
3931 tz += ('0' - line[4]) * 60;
3937 gmtime_r(&author_time, &commit->time);
3939 } else if (match_blame_header("summary ", &line)) {
3940 string_ncopy(commit->title, line, strlen(line));
3942 } else if (match_blame_header("filename ", &line)) {
3943 string_ncopy(commit->filename, line, strlen(line));
3951 blame_draw(struct view *view, struct line *line, unsigned int lineno)
3953 struct blame *blame = line->data;
3954 struct tm *time = NULL;
3955 const char *id = NULL, *author = NULL;
3957 if (blame->commit && *blame->commit->filename) {
3958 id = blame->commit->id;
3959 author = blame->commit->author;
3960 time = &blame->commit->time;
3963 if (opt_date && draw_date(view, time))
3967 draw_field(view, LINE_MAIN_AUTHOR, author, opt_author_cols, TRUE))
3970 if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
3973 if (draw_lineno(view, lineno))
3976 draw_text(view, LINE_DEFAULT, blame->text, TRUE);
3981 blame_request(struct view *view, enum request request, struct line *line)
3983 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
3984 struct blame *blame = line->data;
3987 case REQ_VIEW_BLAME:
3988 if (!blame->commit || !strcmp(blame->commit->id, NULL_ID)) {
3989 report("Commit ID unknown");
3992 string_copy(opt_ref, blame->commit->id);
3993 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
3997 if (!blame->commit) {
3998 report("No commit loaded yet");
4002 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
4003 !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
4006 if (!strcmp(blame->commit->id, NULL_ID)) {
4007 struct view *diff = VIEW(REQ_VIEW_DIFF);
4008 const char *diff_index_argv[] = {
4009 "git", "diff-index", "--root", "--cached",
4010 "--patch-with-stat", "-C", "-M",
4011 "HEAD", "--", view->vid, NULL
4014 if (!prepare_update(diff, diff_index_argv, NULL, FORMAT_DASH)) {
4015 report("Failed to allocate diff command");
4018 flags |= OPEN_PREPARED;
4021 open_view(view, REQ_VIEW_DIFF, flags);
4032 blame_grep(struct view *view, struct line *line)
4034 struct blame *blame = line->data;
4035 struct blame_commit *commit = blame->commit;
4038 #define MATCH(text, on) \
4039 (on && *text && regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4042 char buf[DATE_COLS + 1];
4044 if (MATCH(commit->title, 1) ||
4045 MATCH(commit->author, opt_author) ||
4046 MATCH(commit->id, opt_date))
4049 if (strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time) &&
4054 return MATCH(blame->text, 1);
4060 blame_select(struct view *view, struct line *line)
4062 struct blame *blame = line->data;
4063 struct blame_commit *commit = blame->commit;
4068 if (!strcmp(commit->id, NULL_ID))
4069 string_ncopy(ref_commit, "HEAD", 4);
4071 string_copy_rev(ref_commit, commit->id);
4074 static struct view_ops blame_ops = {
4093 char rev[SIZEOF_REV];
4094 char name[SIZEOF_STR];
4098 char rev[SIZEOF_REV];
4099 char name[SIZEOF_STR];
4103 static char status_onbranch[SIZEOF_STR];
4104 static struct status stage_status;
4105 static enum line_type stage_line_type;
4106 static size_t stage_chunks;
4107 static int *stage_chunk;
4109 /* This should work even for the "On branch" line. */
4111 status_has_none(struct view *view, struct line *line)
4113 return line < view->line + view->lines && !line[1].data;
4116 /* Get fields from the diff line:
4117 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
4120 status_get_diff(struct status *file, const char *buf, size_t bufsize)
4122 const char *old_mode = buf + 1;
4123 const char *new_mode = buf + 8;
4124 const char *old_rev = buf + 15;
4125 const char *new_rev = buf + 56;
4126 const char *status = buf + 97;
4129 old_mode[-1] != ':' ||
4130 new_mode[-1] != ' ' ||
4131 old_rev[-1] != ' ' ||
4132 new_rev[-1] != ' ' ||
4136 file->status = *status;
4138 string_copy_rev(file->old.rev, old_rev);
4139 string_copy_rev(file->new.rev, new_rev);
4141 file->old.mode = strtoul(old_mode, NULL, 8);
4142 file->new.mode = strtoul(new_mode, NULL, 8);
4144 file->old.name[0] = file->new.name[0] = 0;
4150 status_run(struct view *view, const char cmd[], char status, enum line_type type)
4152 struct status *file = NULL;
4153 struct status *unmerged = NULL;
4154 char buf[SIZEOF_STR * 4];
4158 pipe = popen(cmd, "r");
4162 add_line_data(view, NULL, type);
4164 while (!feof(pipe) && !ferror(pipe)) {
4168 readsize = fread(buf + bufsize, 1, sizeof(buf) - bufsize, pipe);
4171 bufsize += readsize;
4173 /* Process while we have NUL chars. */
4174 while ((sep = memchr(buf, 0, bufsize))) {
4175 size_t sepsize = sep - buf + 1;
4178 if (!realloc_lines(view, view->line_size + 1))
4181 file = calloc(1, sizeof(*file));
4185 add_line_data(view, file, type);
4188 /* Parse diff info part. */
4190 file->status = status;
4192 string_copy(file->old.rev, NULL_ID);
4194 } else if (!file->status) {
4195 if (!status_get_diff(file, buf, sepsize))
4199 memmove(buf, sep + 1, bufsize);
4201 sep = memchr(buf, 0, bufsize);
4204 sepsize = sep - buf + 1;
4206 /* Collapse all 'M'odified entries that
4207 * follow a associated 'U'nmerged entry.
4209 if (file->status == 'U') {
4212 } else if (unmerged) {
4213 int collapse = !strcmp(buf, unmerged->new.name);
4224 /* Grab the old name for rename/copy. */
4225 if (!*file->old.name &&
4226 (file->status == 'R' || file->status == 'C')) {
4227 sepsize = sep - buf + 1;
4228 string_ncopy(file->old.name, buf, sepsize);
4230 memmove(buf, sep + 1, bufsize);
4232 sep = memchr(buf, 0, bufsize);
4235 sepsize = sep - buf + 1;
4238 /* git-ls-files just delivers a NUL separated
4239 * list of file names similar to the second half
4240 * of the git-diff-* output. */
4241 string_ncopy(file->new.name, buf, sepsize);
4242 if (!*file->old.name)
4243 string_copy(file->old.name, file->new.name);
4245 memmove(buf, sep + 1, bufsize);
4256 if (!view->line[view->lines - 1].data)
4257 add_line_data(view, NULL, LINE_STAT_NONE);
4263 /* Don't show unmerged entries in the staged section. */
4264 #define STATUS_DIFF_INDEX_CMD "git diff-index -z --diff-filter=ACDMRTXB --cached -M HEAD"
4265 #define STATUS_DIFF_FILES_CMD "git diff-files -z"
4266 #define STATUS_LIST_OTHER_CMD \
4267 "git ls-files -z --others --exclude-standard"
4268 #define STATUS_LIST_NO_HEAD_CMD \
4269 "git ls-files -z --cached --exclude-standard"
4271 /* First parse staged info using git-diff-index(1), then parse unstaged
4272 * info using git-diff-files(1), and finally untracked files using
4273 * git-ls-files(1). */
4275 status_open(struct view *view)
4277 unsigned long prev_lineno = view->lineno;
4281 if (!realloc_lines(view, view->line_size + 7))
4284 add_line_data(view, NULL, LINE_STAT_HEAD);
4285 if (is_initial_commit())
4286 string_copy(status_onbranch, "Initial commit");
4287 else if (!*opt_head)
4288 string_copy(status_onbranch, "Not currently on any branch");
4289 else if (!string_format(status_onbranch, "On branch %s", opt_head))
4292 system("git update-index -q --refresh >/dev/null 2>/dev/null");
4294 if (is_initial_commit()) {
4295 if (!status_run(view, STATUS_LIST_NO_HEAD_CMD, 'A', LINE_STAT_STAGED))
4297 } else if (!status_run(view, STATUS_DIFF_INDEX_CMD, 0, LINE_STAT_STAGED)) {
4301 if (!status_run(view, STATUS_DIFF_FILES_CMD, 0, LINE_STAT_UNSTAGED) ||
4302 !status_run(view, STATUS_LIST_OTHER_CMD, '?', LINE_STAT_UNTRACKED))
4305 /* If all went well restore the previous line number to stay in
4306 * the context or select a line with something that can be
4308 if (prev_lineno >= view->lines)
4309 prev_lineno = view->lines - 1;
4310 while (prev_lineno < view->lines && !view->line[prev_lineno].data)
4312 while (prev_lineno > 0 && !view->line[prev_lineno].data)
4315 /* If the above fails, always skip the "On branch" line. */
4316 if (prev_lineno < view->lines)
4317 view->lineno = prev_lineno;
4321 if (view->lineno < view->offset)
4322 view->offset = view->lineno;
4323 else if (view->offset + view->height <= view->lineno)
4324 view->offset = view->lineno - view->height + 1;
4330 status_draw(struct view *view, struct line *line, unsigned int lineno)
4332 struct status *status = line->data;
4333 enum line_type type;
4337 switch (line->type) {
4338 case LINE_STAT_STAGED:
4339 type = LINE_STAT_SECTION;
4340 text = "Changes to be committed:";
4343 case LINE_STAT_UNSTAGED:
4344 type = LINE_STAT_SECTION;
4345 text = "Changed but not updated:";
4348 case LINE_STAT_UNTRACKED:
4349 type = LINE_STAT_SECTION;
4350 text = "Untracked files:";
4353 case LINE_STAT_NONE:
4354 type = LINE_DEFAULT;
4355 text = " (no files)";
4358 case LINE_STAT_HEAD:
4359 type = LINE_STAT_HEAD;
4360 text = status_onbranch;
4367 static char buf[] = { '?', ' ', ' ', ' ', 0 };
4369 buf[0] = status->status;
4370 if (draw_text(view, line->type, buf, TRUE))
4372 type = LINE_DEFAULT;
4373 text = status->new.name;
4376 draw_text(view, type, text, TRUE);
4381 status_enter(struct view *view, struct line *line)
4383 struct status *status = line->data;
4384 const char *oldpath = status ? status->old.name : NULL;
4385 /* Diffs for unmerged entries are empty when passing the new
4386 * path, so leave it empty. */
4387 const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
4389 enum open_flags split;
4390 struct view *stage = VIEW(REQ_VIEW_STAGE);
4392 if (line->type == LINE_STAT_NONE ||
4393 (!status && line[1].type == LINE_STAT_NONE)) {
4394 report("No file to diff");
4398 switch (line->type) {
4399 case LINE_STAT_STAGED:
4400 if (is_initial_commit()) {
4401 const char *no_head_diff_argv[] = {
4402 "git", "diff", "--no-color", "--patch-with-stat",
4403 "--", "/dev/null", newpath, NULL
4406 if (!prepare_update(stage, no_head_diff_argv, opt_cdup, FORMAT_DASH))
4409 const char *index_show_argv[] = {
4410 "git", "diff-index", "--root", "--patch-with-stat",
4411 "-C", "-M", "--cached", "HEAD", "--",
4412 oldpath, newpath, NULL
4415 if (!prepare_update(stage, index_show_argv, opt_cdup, FORMAT_DASH))
4420 info = "Staged changes to %s";
4422 info = "Staged changes";
4425 case LINE_STAT_UNSTAGED:
4427 const char *files_show_argv[] = {
4428 "git", "diff-files", "--root", "--patch-with-stat",
4429 "-C", "-M", "--", oldpath, newpath, NULL
4432 if (!prepare_update(stage, files_show_argv, opt_cdup, FORMAT_DASH))
4435 info = "Unstaged changes to %s";
4437 info = "Unstaged changes";
4440 case LINE_STAT_UNTRACKED:
4445 report("No file to show");
4449 if (!suffixcmp(status->new.name, -1, "/")) {
4450 report("Cannot display a directory");
4454 opt_pipe = fopen(status->new.name, "r");
4455 info = "Untracked file %s";
4458 case LINE_STAT_HEAD:
4462 die("line type %d not handled in switch", line->type);
4465 split = view_is_displayed(view) ? OPEN_SPLIT : 0;
4466 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH | split);
4467 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
4469 stage_status = *status;
4471 memset(&stage_status, 0, sizeof(stage_status));
4474 stage_line_type = line->type;
4476 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
4483 status_exists(struct status *status, enum line_type type)
4485 struct view *view = VIEW(REQ_VIEW_STATUS);
4488 for (line = view->line; line < view->line + view->lines; line++) {
4489 struct status *pos = line->data;
4491 if (line->type == type && pos &&
4492 !strcmp(status->new.name, pos->new.name))
4501 status_update_prepare(enum line_type type)
4503 char cmd[SIZEOF_STR];
4507 type != LINE_STAT_UNTRACKED &&
4508 !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
4512 case LINE_STAT_STAGED:
4513 string_add(cmd, cmdsize, "git update-index -z --index-info");
4516 case LINE_STAT_UNSTAGED:
4517 case LINE_STAT_UNTRACKED:
4518 string_add(cmd, cmdsize, "git update-index -z --add --remove --stdin");
4522 die("line type %d not handled in switch", type);
4525 return popen(cmd, "w");
4529 status_update_write(FILE *pipe, struct status *status, enum line_type type)
4531 char buf[SIZEOF_STR];
4536 case LINE_STAT_STAGED:
4537 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
4540 status->old.name, 0))
4544 case LINE_STAT_UNSTAGED:
4545 case LINE_STAT_UNTRACKED:
4546 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
4551 die("line type %d not handled in switch", type);
4554 while (!ferror(pipe) && written < bufsize) {
4555 written += fwrite(buf + written, 1, bufsize - written, pipe);
4558 return written == bufsize;
4562 status_update_file(struct status *status, enum line_type type)
4564 FILE *pipe = status_update_prepare(type);
4570 result = status_update_write(pipe, status, type);
4576 status_update_files(struct view *view, struct line *line)
4578 FILE *pipe = status_update_prepare(line->type);
4580 struct line *pos = view->line + view->lines;
4587 for (pos = line; pos < view->line + view->lines && pos->data; pos++)
4590 for (file = 0, done = 0; result && file < files; line++, file++) {
4591 int almost_done = file * 100 / files;
4593 if (almost_done > done) {
4595 string_format(view->ref, "updating file %u of %u (%d%% done)",
4597 update_view_title(view);
4599 result = status_update_write(pipe, line->data, line->type);
4607 status_update(struct view *view)
4609 struct line *line = &view->line[view->lineno];
4611 assert(view->lines);
4614 /* This should work even for the "On branch" line. */
4615 if (line < view->line + view->lines && !line[1].data) {
4616 report("Nothing to update");
4620 if (!status_update_files(view, line + 1)) {
4621 report("Failed to update file status");
4625 } else if (!status_update_file(line->data, line->type)) {
4626 report("Failed to update file status");
4634 status_revert(struct status *status, enum line_type type, bool has_none)
4636 if (!status || type != LINE_STAT_UNSTAGED) {
4637 if (type == LINE_STAT_STAGED) {
4638 report("Cannot revert changes to staged files");
4639 } else if (type == LINE_STAT_UNTRACKED) {
4640 report("Cannot revert changes to untracked files");
4641 } else if (has_none) {
4642 report("Nothing to revert");
4644 report("Cannot revert changes to multiple files");
4649 const char *checkout_argv[] = {
4650 "git", "checkout", "--", status->old.name, NULL
4653 if (!prompt_yesno("Are you sure you want to overwrite any changes?"))
4655 return run_io_fg(checkout_argv, opt_cdup);
4660 status_request(struct view *view, enum request request, struct line *line)
4662 struct status *status = line->data;
4665 case REQ_STATUS_UPDATE:
4666 if (!status_update(view))
4670 case REQ_STATUS_REVERT:
4671 if (!status_revert(status, line->type, status_has_none(view, line)))
4675 case REQ_STATUS_MERGE:
4676 if (!status || status->status != 'U') {
4677 report("Merging only possible for files with unmerged status ('U').");
4680 open_mergetool(status->new.name);
4686 if (status->status == 'D') {
4687 report("File has been deleted.");
4691 open_editor(status->status != '?', status->new.name);
4694 case REQ_VIEW_BLAME:
4696 string_copy(opt_file, status->new.name);
4702 /* After returning the status view has been split to
4703 * show the stage view. No further reloading is
4705 status_enter(view, line);
4709 /* Simply reload the view. */
4716 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
4722 status_select(struct view *view, struct line *line)
4724 struct status *status = line->data;
4725 char file[SIZEOF_STR] = "all files";
4729 if (status && !string_format(file, "'%s'", status->new.name))
4732 if (!status && line[1].type == LINE_STAT_NONE)
4735 switch (line->type) {
4736 case LINE_STAT_STAGED:
4737 text = "Press %s to unstage %s for commit";
4740 case LINE_STAT_UNSTAGED:
4741 text = "Press %s to stage %s for commit";
4744 case LINE_STAT_UNTRACKED:
4745 text = "Press %s to stage %s for addition";
4748 case LINE_STAT_HEAD:
4749 case LINE_STAT_NONE:
4750 text = "Nothing to update";
4754 die("line type %d not handled in switch", line->type);
4757 if (status && status->status == 'U') {
4758 text = "Press %s to resolve conflict in %s";
4759 key = get_key(REQ_STATUS_MERGE);
4762 key = get_key(REQ_STATUS_UPDATE);
4765 string_format(view->ref, text, key, file);
4769 status_grep(struct view *view, struct line *line)
4771 struct status *status = line->data;
4772 enum { S_STATUS, S_NAME, S_END } state;
4779 for (state = S_STATUS; state < S_END; state++) {
4783 case S_NAME: text = status->new.name; break;
4785 buf[0] = status->status;
4793 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4800 static struct view_ops status_ops = {
4813 stage_diff_line(FILE *pipe, struct line *line)
4815 const char *buf = line->data;
4816 size_t bufsize = strlen(buf);
4819 while (!ferror(pipe) && written < bufsize) {
4820 written += fwrite(buf + written, 1, bufsize - written, pipe);
4825 return written == bufsize;
4829 stage_diff_write(FILE *pipe, struct line *line, struct line *end)
4831 while (line < end) {
4832 if (!stage_diff_line(pipe, line++))
4834 if (line->type == LINE_DIFF_CHUNK ||
4835 line->type == LINE_DIFF_HEADER)
4842 static struct line *
4843 stage_diff_find(struct view *view, struct line *line, enum line_type type)
4845 for (; view->line < line; line--)
4846 if (line->type == type)
4853 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
4855 char cmd[SIZEOF_STR];
4857 struct line *diff_hdr;
4860 diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
4865 !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
4868 if (!string_format_from(cmd, &cmdsize,
4869 "git apply --whitespace=nowarn %s %s - && "
4870 "git update-index -q --unmerged --refresh 2>/dev/null",
4871 revert ? "" : "--cached",
4872 revert || stage_line_type == LINE_STAT_STAGED ? "-R" : ""))
4875 pipe = popen(cmd, "w");
4879 if (!stage_diff_write(pipe, diff_hdr, chunk) ||
4880 !stage_diff_write(pipe, chunk, view->line + view->lines))
4885 return chunk ? TRUE : FALSE;
4889 stage_update(struct view *view, struct line *line)
4891 struct line *chunk = NULL;
4893 if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
4894 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
4897 if (!stage_apply_chunk(view, chunk, FALSE)) {
4898 report("Failed to apply chunk");
4902 } else if (!stage_status.status) {
4903 view = VIEW(REQ_VIEW_STATUS);
4905 for (line = view->line; line < view->line + view->lines; line++)
4906 if (line->type == stage_line_type)
4909 if (!status_update_files(view, line + 1)) {
4910 report("Failed to update files");
4914 } else if (!status_update_file(&stage_status, stage_line_type)) {
4915 report("Failed to update file");
4923 stage_revert(struct view *view, struct line *line)
4925 struct line *chunk = NULL;
4927 if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
4928 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
4931 if (!prompt_yesno("Are you sure you want to revert changes?"))
4934 if (!stage_apply_chunk(view, chunk, TRUE)) {
4935 report("Failed to revert chunk");
4941 return status_revert(stage_status.status ? &stage_status : NULL,
4942 stage_line_type, FALSE);
4948 stage_next(struct view *view, struct line *line)
4952 if (!stage_chunks) {
4953 static size_t alloc = 0;
4956 for (line = view->line; line < view->line + view->lines; line++) {
4957 if (line->type != LINE_DIFF_CHUNK)
4960 tmp = realloc_items(stage_chunk, &alloc,
4961 stage_chunks, sizeof(*tmp));
4963 report("Allocation failure");
4968 stage_chunk[stage_chunks++] = line - view->line;
4972 for (i = 0; i < stage_chunks; i++) {
4973 if (stage_chunk[i] > view->lineno) {
4974 do_scroll_view(view, stage_chunk[i] - view->lineno);
4975 report("Chunk %d of %d", i + 1, stage_chunks);
4980 report("No next chunk found");
4984 stage_request(struct view *view, enum request request, struct line *line)
4987 case REQ_STATUS_UPDATE:
4988 if (!stage_update(view, line))
4992 case REQ_STATUS_REVERT:
4993 if (!stage_revert(view, line))
4997 case REQ_STAGE_NEXT:
4998 if (stage_line_type == LINE_STAT_UNTRACKED) {
4999 report("File is untracked; press %s to add",
5000 get_key(REQ_STATUS_UPDATE));
5003 stage_next(view, line);
5007 if (!stage_status.new.name[0])
5009 if (stage_status.status == 'D') {
5010 report("File has been deleted.");
5014 open_editor(stage_status.status != '?', stage_status.new.name);
5018 /* Reload everything ... */
5021 case REQ_VIEW_BLAME:
5022 if (stage_status.new.name[0]) {
5023 string_copy(opt_file, stage_status.new.name);
5029 return pager_request(view, request, line);
5035 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD | OPEN_NOMAXIMIZE);
5037 /* Check whether the staged entry still exists, and close the
5038 * stage view if it doesn't. */
5039 if (!status_exists(&stage_status, stage_line_type))
5040 return REQ_VIEW_CLOSE;
5042 if (stage_line_type == LINE_STAT_UNTRACKED) {
5043 if (!suffixcmp(stage_status.new.name, -1, "/")) {
5044 report("Cannot display a directory");
5048 opt_pipe = fopen(stage_status.new.name, "r");
5050 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
5055 static struct view_ops stage_ops = {
5072 char id[SIZEOF_REV]; /* SHA1 ID. */
5073 char title[128]; /* First line of the commit message. */
5074 char author[75]; /* Author of the commit. */
5075 struct tm time; /* Date from the author ident. */
5076 struct ref **refs; /* Repository references. */
5077 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
5078 size_t graph_size; /* The width of the graph array. */
5079 bool has_parents; /* Rewritten --parents seen. */
5082 /* Size of rev graph with no "padding" columns */
5083 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
5086 struct rev_graph *prev, *next, *parents;
5087 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
5089 struct commit *commit;
5091 unsigned int boundary:1;
5094 /* Parents of the commit being visualized. */
5095 static struct rev_graph graph_parents[4];
5097 /* The current stack of revisions on the graph. */
5098 static struct rev_graph graph_stacks[4] = {
5099 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
5100 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
5101 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
5102 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
5106 graph_parent_is_merge(struct rev_graph *graph)
5108 return graph->parents->size > 1;
5112 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
5114 struct commit *commit = graph->commit;
5116 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
5117 commit->graph[commit->graph_size++] = symbol;
5121 clear_rev_graph(struct rev_graph *graph)
5123 graph->boundary = 0;
5124 graph->size = graph->pos = 0;
5125 graph->commit = NULL;
5126 memset(graph->parents, 0, sizeof(*graph->parents));
5130 done_rev_graph(struct rev_graph *graph)
5132 if (graph_parent_is_merge(graph) &&
5133 graph->pos < graph->size - 1 &&
5134 graph->next->size == graph->size + graph->parents->size - 1) {
5135 size_t i = graph->pos + graph->parents->size - 1;
5137 graph->commit->graph_size = i * 2;
5138 while (i < graph->next->size - 1) {
5139 append_to_rev_graph(graph, ' ');
5140 append_to_rev_graph(graph, '\\');
5145 clear_rev_graph(graph);
5149 push_rev_graph(struct rev_graph *graph, const char *parent)
5153 /* "Collapse" duplicate parents lines.
5155 * FIXME: This needs to also update update the drawn graph but
5156 * for now it just serves as a method for pruning graph lines. */
5157 for (i = 0; i < graph->size; i++)
5158 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
5161 if (graph->size < SIZEOF_REVITEMS) {
5162 string_copy_rev(graph->rev[graph->size++], parent);
5167 get_rev_graph_symbol(struct rev_graph *graph)
5171 if (graph->boundary)
5172 symbol = REVGRAPH_BOUND;
5173 else if (graph->parents->size == 0)
5174 symbol = REVGRAPH_INIT;
5175 else if (graph_parent_is_merge(graph))
5176 symbol = REVGRAPH_MERGE;
5177 else if (graph->pos >= graph->size)
5178 symbol = REVGRAPH_BRANCH;
5180 symbol = REVGRAPH_COMMIT;
5186 draw_rev_graph(struct rev_graph *graph)
5189 chtype separator, line;
5191 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
5192 static struct rev_filler fillers[] = {
5198 chtype symbol = get_rev_graph_symbol(graph);
5199 struct rev_filler *filler;
5202 if (opt_line_graphics)
5203 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
5205 filler = &fillers[DEFAULT];
5207 for (i = 0; i < graph->pos; i++) {
5208 append_to_rev_graph(graph, filler->line);
5209 if (graph_parent_is_merge(graph->prev) &&
5210 graph->prev->pos == i)
5211 filler = &fillers[RSHARP];
5213 append_to_rev_graph(graph, filler->separator);
5216 /* Place the symbol for this revision. */
5217 append_to_rev_graph(graph, symbol);
5219 if (graph->prev->size > graph->size)
5220 filler = &fillers[RDIAG];
5222 filler = &fillers[DEFAULT];
5226 for (; i < graph->size; i++) {
5227 append_to_rev_graph(graph, filler->separator);
5228 append_to_rev_graph(graph, filler->line);
5229 if (graph_parent_is_merge(graph->prev) &&
5230 i < graph->prev->pos + graph->parents->size)
5231 filler = &fillers[RSHARP];
5232 if (graph->prev->size > graph->size)
5233 filler = &fillers[LDIAG];
5236 if (graph->prev->size > graph->size) {
5237 append_to_rev_graph(graph, filler->separator);
5238 if (filler->line != ' ')
5239 append_to_rev_graph(graph, filler->line);
5243 /* Prepare the next rev graph */
5245 prepare_rev_graph(struct rev_graph *graph)
5249 /* First, traverse all lines of revisions up to the active one. */
5250 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
5251 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
5254 push_rev_graph(graph->next, graph->rev[graph->pos]);
5257 /* Interleave the new revision parent(s). */
5258 for (i = 0; !graph->boundary && i < graph->parents->size; i++)
5259 push_rev_graph(graph->next, graph->parents->rev[i]);
5261 /* Lastly, put any remaining revisions. */
5262 for (i = graph->pos + 1; i < graph->size; i++)
5263 push_rev_graph(graph->next, graph->rev[i]);
5267 update_rev_graph(struct rev_graph *graph)
5269 /* If this is the finalizing update ... */
5271 prepare_rev_graph(graph);
5273 /* Graph visualization needs a one rev look-ahead,
5274 * so the first update doesn't visualize anything. */
5275 if (!graph->prev->commit)
5278 draw_rev_graph(graph->prev);
5279 done_rev_graph(graph->prev->prev);
5287 static const char *main_argv[SIZEOF_ARG] = {
5288 "git", "log", "--no-color", "--pretty=raw", "--parents",
5289 "--topo-order", "%(head)", NULL
5293 main_draw(struct view *view, struct line *line, unsigned int lineno)
5295 struct commit *commit = line->data;
5297 if (!*commit->author)
5300 if (opt_date && draw_date(view, &commit->time))
5304 draw_field(view, LINE_MAIN_AUTHOR, commit->author, opt_author_cols, TRUE))
5307 if (opt_rev_graph && commit->graph_size &&
5308 draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
5311 if (opt_show_refs && commit->refs) {
5315 enum line_type type;
5317 if (commit->refs[i]->head)
5318 type = LINE_MAIN_HEAD;
5319 else if (commit->refs[i]->ltag)
5320 type = LINE_MAIN_LOCAL_TAG;
5321 else if (commit->refs[i]->tag)
5322 type = LINE_MAIN_TAG;
5323 else if (commit->refs[i]->tracked)
5324 type = LINE_MAIN_TRACKED;
5325 else if (commit->refs[i]->remote)
5326 type = LINE_MAIN_REMOTE;
5328 type = LINE_MAIN_REF;
5330 if (draw_text(view, type, "[", TRUE) ||
5331 draw_text(view, type, commit->refs[i]->name, TRUE) ||
5332 draw_text(view, type, "]", TRUE))
5335 if (draw_text(view, LINE_DEFAULT, " ", TRUE))
5337 } while (commit->refs[i++]->next);
5340 draw_text(view, LINE_DEFAULT, commit->title, TRUE);
5344 /* Reads git log --pretty=raw output and parses it into the commit struct. */
5346 main_read(struct view *view, char *line)
5348 static struct rev_graph *graph = graph_stacks;
5349 enum line_type type;
5350 struct commit *commit;
5355 if (!view->lines && !view->parent)
5356 die("No revisions match the given arguments.");
5357 if (view->lines > 0) {
5358 commit = view->line[view->lines - 1].data;
5359 if (!*commit->author) {
5362 graph->commit = NULL;
5365 update_rev_graph(graph);
5367 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
5368 clear_rev_graph(&graph_stacks[i]);
5372 type = get_line_type(line);
5373 if (type == LINE_COMMIT) {
5374 commit = calloc(1, sizeof(struct commit));
5378 line += STRING_SIZE("commit ");
5380 graph->boundary = 1;
5384 string_copy_rev(commit->id, line);
5385 commit->refs = get_refs(commit->id);
5386 graph->commit = commit;
5387 add_line_data(view, commit, LINE_MAIN_COMMIT);
5389 while ((line = strchr(line, ' '))) {
5391 push_rev_graph(graph->parents, line);
5392 commit->has_parents = TRUE;
5399 commit = view->line[view->lines - 1].data;
5403 if (commit->has_parents)
5405 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
5410 /* Parse author lines where the name may be empty:
5411 * author <email@address.tld> 1138474660 +0100
5413 char *ident = line + STRING_SIZE("author ");
5414 char *nameend = strchr(ident, '<');
5415 char *emailend = strchr(ident, '>');
5417 if (!nameend || !emailend)
5420 update_rev_graph(graph);
5421 graph = graph->next;
5423 *nameend = *emailend = 0;
5424 ident = chomp_string(ident);
5426 ident = chomp_string(nameend + 1);
5431 string_ncopy(commit->author, ident, strlen(ident));
5433 /* Parse epoch and timezone */
5434 if (emailend[1] == ' ') {
5435 char *secs = emailend + 2;
5436 char *zone = strchr(secs, ' ');
5437 time_t time = (time_t) atol(secs);
5439 if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
5443 tz = ('0' - zone[1]) * 60 * 60 * 10;
5444 tz += ('0' - zone[2]) * 60 * 60;
5445 tz += ('0' - zone[3]) * 60;
5446 tz += ('0' - zone[4]) * 60;
5454 gmtime_r(&time, &commit->time);
5459 /* Fill in the commit title if it has not already been set. */
5460 if (commit->title[0])
5463 /* Require titles to start with a non-space character at the
5464 * offset used by git log. */
5465 if (strncmp(line, " ", 4))
5468 /* Well, if the title starts with a whitespace character,
5469 * try to be forgiving. Otherwise we end up with no title. */
5470 while (isspace(*line))
5474 /* FIXME: More graceful handling of titles; append "..." to
5475 * shortened titles, etc. */
5477 string_ncopy(commit->title, line, strlen(line));
5484 main_request(struct view *view, enum request request, struct line *line)
5486 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
5490 open_view(view, REQ_VIEW_DIFF, flags);
5494 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
5504 grep_refs(struct ref **refs, regex_t *regex)
5512 if (regexec(regex, refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
5514 } while (refs[i++]->next);
5520 main_grep(struct view *view, struct line *line)
5522 struct commit *commit = line->data;
5523 enum { S_TITLE, S_AUTHOR, S_DATE, S_REFS, S_END } state;
5524 char buf[DATE_COLS + 1];
5527 for (state = S_TITLE; state < S_END; state++) {
5531 case S_TITLE: text = commit->title; break;
5535 text = commit->author;
5540 if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
5547 if (grep_refs(commit->refs, view->regex) == TRUE)
5554 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
5562 main_select(struct view *view, struct line *line)
5564 struct commit *commit = line->data;
5566 string_copy_rev(view->ref, commit->id);
5567 string_copy_rev(ref_commit, view->ref);
5570 static struct view_ops main_ops = {
5583 * Unicode / UTF-8 handling
5585 * NOTE: Much of the following code for dealing with unicode is derived from
5586 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
5587 * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
5590 /* I've (over)annotated a lot of code snippets because I am not entirely
5591 * confident that the approach taken by this small UTF-8 interface is correct.
5595 unicode_width(unsigned long c)
5598 (c <= 0x115f /* Hangul Jamo */
5601 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
5603 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
5604 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
5605 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
5606 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
5607 || (c >= 0xffe0 && c <= 0xffe6)
5608 || (c >= 0x20000 && c <= 0x2fffd)
5609 || (c >= 0x30000 && c <= 0x3fffd)))
5613 return opt_tab_size;
5618 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
5619 * Illegal bytes are set one. */
5620 static const unsigned char utf8_bytes[256] = {
5621 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,
5622 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,
5623 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,
5624 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,
5625 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,
5626 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,
5627 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,
5628 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,
5631 /* Decode UTF-8 multi-byte representation into a unicode character. */
5632 static inline unsigned long
5633 utf8_to_unicode(const char *string, size_t length)
5635 unsigned long unicode;
5639 unicode = string[0];
5642 unicode = (string[0] & 0x1f) << 6;
5643 unicode += (string[1] & 0x3f);
5646 unicode = (string[0] & 0x0f) << 12;
5647 unicode += ((string[1] & 0x3f) << 6);
5648 unicode += (string[2] & 0x3f);
5651 unicode = (string[0] & 0x0f) << 18;
5652 unicode += ((string[1] & 0x3f) << 12);
5653 unicode += ((string[2] & 0x3f) << 6);
5654 unicode += (string[3] & 0x3f);
5657 unicode = (string[0] & 0x0f) << 24;
5658 unicode += ((string[1] & 0x3f) << 18);
5659 unicode += ((string[2] & 0x3f) << 12);
5660 unicode += ((string[3] & 0x3f) << 6);
5661 unicode += (string[4] & 0x3f);
5664 unicode = (string[0] & 0x01) << 30;
5665 unicode += ((string[1] & 0x3f) << 24);
5666 unicode += ((string[2] & 0x3f) << 18);
5667 unicode += ((string[3] & 0x3f) << 12);
5668 unicode += ((string[4] & 0x3f) << 6);
5669 unicode += (string[5] & 0x3f);
5672 die("Invalid unicode length");
5675 /* Invalid characters could return the special 0xfffd value but NUL
5676 * should be just as good. */
5677 return unicode > 0xffff ? 0 : unicode;
5680 /* Calculates how much of string can be shown within the given maximum width
5681 * and sets trimmed parameter to non-zero value if all of string could not be
5682 * shown. If the reserve flag is TRUE, it will reserve at least one
5683 * trailing character, which can be useful when drawing a delimiter.
5685 * Returns the number of bytes to output from string to satisfy max_width. */
5687 utf8_length(const char *string, int *width, size_t max_width, int *trimmed, bool reserve)
5689 const char *start = string;
5690 const char *end = strchr(string, '\0');
5691 unsigned char last_bytes = 0;
5692 size_t last_ucwidth = 0;
5697 while (string < end) {
5698 int c = *(unsigned char *) string;
5699 unsigned char bytes = utf8_bytes[c];
5701 unsigned long unicode;
5703 if (string + bytes > end)
5706 /* Change representation to figure out whether
5707 * it is a single- or double-width character. */
5709 unicode = utf8_to_unicode(string, bytes);
5710 /* FIXME: Graceful handling of invalid unicode character. */
5714 ucwidth = unicode_width(unicode);
5716 if (*width > max_width) {
5719 if (reserve && *width == max_width) {
5720 string -= last_bytes;
5721 *width -= last_ucwidth;
5728 last_ucwidth = ucwidth;
5731 return string - start;
5739 /* Whether or not the curses interface has been initialized. */
5740 static bool cursed = FALSE;
5742 /* The status window is used for polling keystrokes. */
5743 static WINDOW *status_win;
5745 static bool status_empty = TRUE;
5747 /* Update status and title window. */
5749 report(const char *msg, ...)
5751 struct view *view = display[current_view];
5757 char buf[SIZEOF_STR];
5760 va_start(args, msg);
5761 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
5762 buf[sizeof(buf) - 1] = 0;
5763 buf[sizeof(buf) - 2] = '.';
5764 buf[sizeof(buf) - 3] = '.';
5765 buf[sizeof(buf) - 4] = '.';
5771 if (!status_empty || *msg) {
5774 va_start(args, msg);
5776 wmove(status_win, 0, 0);
5778 vwprintw(status_win, msg, args);
5779 status_empty = FALSE;
5781 status_empty = TRUE;
5783 wclrtoeol(status_win);
5784 wrefresh(status_win);
5789 update_view_title(view);
5790 update_display_cursor(view);
5793 /* Controls when nodelay should be in effect when polling user input. */
5795 set_nonblocking_input(bool loading)
5797 static unsigned int loading_views;
5799 if ((loading == FALSE && loading_views-- == 1) ||
5800 (loading == TRUE && loading_views++ == 0))
5801 nodelay(status_win, loading);
5809 /* Initialize the curses library */
5810 if (isatty(STDIN_FILENO)) {
5811 cursed = !!initscr();
5814 /* Leave stdin and stdout alone when acting as a pager. */
5815 opt_tty = fopen("/dev/tty", "r+");
5817 die("Failed to open /dev/tty");
5818 cursed = !!newterm(NULL, opt_tty, opt_tty);
5822 die("Failed to initialize curses");
5824 nonl(); /* Tell curses not to do NL->CR/NL on output */
5825 cbreak(); /* Take input chars one at a time, no wait for \n */
5826 noecho(); /* Don't echo input */
5827 leaveok(stdscr, TRUE);
5832 getmaxyx(stdscr, y, x);
5833 status_win = newwin(1, 0, y - 1, 0);
5835 die("Failed to create status window");
5837 /* Enable keyboard mapping */
5838 keypad(status_win, TRUE);
5839 wbkgdset(status_win, get_line_attr(LINE_STATUS));
5841 TABSIZE = opt_tab_size;
5842 if (opt_line_graphics) {
5843 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
5848 prompt_yesno(const char *prompt)
5850 enum { WAIT, STOP, CANCEL } status = WAIT;
5851 bool answer = FALSE;
5853 while (status == WAIT) {
5859 foreach_view (view, i)
5864 mvwprintw(status_win, 0, 0, "%s [Yy]/[Nn]", prompt);
5865 wclrtoeol(status_win);
5867 /* Refresh, accept single keystroke of input */
5868 key = wgetch(status_win);
5892 /* Clear the status window */
5893 status_empty = FALSE;
5900 read_prompt(const char *prompt)
5902 enum { READING, STOP, CANCEL } status = READING;
5903 static char buf[SIZEOF_STR];
5906 while (status == READING) {
5912 foreach_view (view, i)
5917 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
5918 wclrtoeol(status_win);
5920 /* Refresh, accept single keystroke of input */
5921 key = wgetch(status_win);
5926 status = pos ? STOP : CANCEL;
5944 if (pos >= sizeof(buf)) {
5945 report("Input string too long");
5950 buf[pos++] = (char) key;
5954 /* Clear the status window */
5955 status_empty = FALSE;
5958 if (status == CANCEL)
5967 * Repository references
5970 static struct ref *refs = NULL;
5971 static size_t refs_alloc = 0;
5972 static size_t refs_size = 0;
5974 /* Id <-> ref store */
5975 static struct ref ***id_refs = NULL;
5976 static size_t id_refs_alloc = 0;
5977 static size_t id_refs_size = 0;
5980 compare_refs(const void *ref1_, const void *ref2_)
5982 const struct ref *ref1 = *(const struct ref **)ref1_;
5983 const struct ref *ref2 = *(const struct ref **)ref2_;
5985 if (ref1->tag != ref2->tag)
5986 return ref2->tag - ref1->tag;
5987 if (ref1->ltag != ref2->ltag)
5988 return ref2->ltag - ref2->ltag;
5989 if (ref1->head != ref2->head)
5990 return ref2->head - ref1->head;
5991 if (ref1->tracked != ref2->tracked)
5992 return ref2->tracked - ref1->tracked;
5993 if (ref1->remote != ref2->remote)
5994 return ref2->remote - ref1->remote;
5995 return strcmp(ref1->name, ref2->name);
5998 static struct ref **
5999 get_refs(const char *id)
6001 struct ref ***tmp_id_refs;
6002 struct ref **ref_list = NULL;
6003 size_t ref_list_alloc = 0;
6004 size_t ref_list_size = 0;
6007 for (i = 0; i < id_refs_size; i++)
6008 if (!strcmp(id, id_refs[i][0]->id))
6011 tmp_id_refs = realloc_items(id_refs, &id_refs_alloc, id_refs_size + 1,
6016 id_refs = tmp_id_refs;
6018 for (i = 0; i < refs_size; i++) {
6021 if (strcmp(id, refs[i].id))
6024 tmp = realloc_items(ref_list, &ref_list_alloc,
6025 ref_list_size + 1, sizeof(*ref_list));
6033 ref_list[ref_list_size] = &refs[i];
6034 /* XXX: The properties of the commit chains ensures that we can
6035 * safely modify the shared ref. The repo references will
6036 * always be similar for the same id. */
6037 ref_list[ref_list_size]->next = 1;
6043 qsort(ref_list, ref_list_size, sizeof(*ref_list), compare_refs);
6044 ref_list[ref_list_size - 1]->next = 0;
6045 id_refs[id_refs_size++] = ref_list;
6052 read_ref(char *id, size_t idlen, char *name, size_t namelen)
6057 bool remote = FALSE;
6058 bool tracked = FALSE;
6059 bool check_replace = FALSE;
6062 if (!prefixcmp(name, "refs/tags/")) {
6063 if (!suffixcmp(name, namelen, "^{}")) {
6066 if (refs_size > 0 && refs[refs_size - 1].ltag == TRUE)
6067 check_replace = TRUE;
6073 namelen -= STRING_SIZE("refs/tags/");
6074 name += STRING_SIZE("refs/tags/");
6076 } else if (!prefixcmp(name, "refs/remotes/")) {
6078 namelen -= STRING_SIZE("refs/remotes/");
6079 name += STRING_SIZE("refs/remotes/");
6080 tracked = !strcmp(opt_remote, name);
6082 } else if (!prefixcmp(name, "refs/heads/")) {
6083 namelen -= STRING_SIZE("refs/heads/");
6084 name += STRING_SIZE("refs/heads/");
6085 head = !strncmp(opt_head, name, namelen);
6087 } else if (!strcmp(name, "HEAD")) {
6088 string_ncopy(opt_head_rev, id, idlen);
6092 if (check_replace && !strcmp(name, refs[refs_size - 1].name)) {
6093 /* it's an annotated tag, replace the previous sha1 with the
6094 * resolved commit id; relies on the fact git-ls-remote lists
6095 * the commit id of an annotated tag right before the commit id
6097 refs[refs_size - 1].ltag = ltag;
6098 string_copy_rev(refs[refs_size - 1].id, id);
6102 refs = realloc_items(refs, &refs_alloc, refs_size + 1, sizeof(*refs));
6106 ref = &refs[refs_size++];
6107 ref->name = malloc(namelen + 1);
6111 strncpy(ref->name, name, namelen);
6112 ref->name[namelen] = 0;
6116 ref->remote = remote;
6117 ref->tracked = tracked;
6118 string_copy_rev(ref->id, id);
6126 const char *cmd_env = getenv("TIG_LS_REMOTE");
6127 const char *cmd = cmd_env && *cmd_env ? cmd_env : TIG_LS_REMOTE;
6132 while (refs_size > 0)
6133 free(refs[--refs_size].name);
6134 while (id_refs_size > 0)
6135 free(id_refs[--id_refs_size]);
6137 return read_properties(popen(cmd, "r"), "\t", read_ref);
6141 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
6143 if (!strcmp(name, "i18n.commitencoding"))
6144 string_ncopy(opt_encoding, value, valuelen);
6146 if (!strcmp(name, "core.editor"))
6147 string_ncopy(opt_editor, value, valuelen);
6149 /* branch.<head>.remote */
6151 !strncmp(name, "branch.", 7) &&
6152 !strncmp(name + 7, opt_head, strlen(opt_head)) &&
6153 !strcmp(name + 7 + strlen(opt_head), ".remote"))
6154 string_ncopy(opt_remote, value, valuelen);
6156 if (*opt_head && *opt_remote &&
6157 !strncmp(name, "branch.", 7) &&
6158 !strncmp(name + 7, opt_head, strlen(opt_head)) &&
6159 !strcmp(name + 7 + strlen(opt_head), ".merge")) {
6160 size_t from = strlen(opt_remote);
6162 if (!prefixcmp(value, "refs/heads/")) {
6163 value += STRING_SIZE("refs/heads/");
6164 valuelen -= STRING_SIZE("refs/heads/");
6167 if (!string_format_from(opt_remote, &from, "/%s", value))
6175 load_git_config(void)
6177 return read_properties(popen("git " GIT_CONFIG " --list", "r"),
6178 "=", read_repo_config_option);
6182 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
6184 if (!opt_git_dir[0]) {
6185 string_ncopy(opt_git_dir, name, namelen);
6187 } else if (opt_is_inside_work_tree == -1) {
6188 /* This can be 3 different values depending on the
6189 * version of git being used. If git-rev-parse does not
6190 * understand --is-inside-work-tree it will simply echo
6191 * the option else either "true" or "false" is printed.
6192 * Default to true for the unknown case. */
6193 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
6195 } else if (opt_cdup[0] == ' ') {
6196 string_ncopy(opt_cdup, name, namelen);
6198 if (!prefixcmp(name, "refs/heads/")) {
6199 namelen -= STRING_SIZE("refs/heads/");
6200 name += STRING_SIZE("refs/heads/");
6201 string_ncopy(opt_head, name, namelen);
6209 load_repo_info(void)
6212 FILE *pipe = popen("(git rev-parse --git-dir --is-inside-work-tree "
6213 " --show-cdup; git symbolic-ref HEAD) 2>/dev/null", "r");
6215 /* XXX: The line outputted by "--show-cdup" can be empty so
6216 * initialize it to something invalid to make it possible to
6217 * detect whether it has been set or not. */
6220 result = read_properties(pipe, "=", read_repo_info);
6221 if (opt_cdup[0] == ' ')
6228 read_properties(FILE *pipe, const char *separators,
6229 int (*read_property)(char *, size_t, char *, size_t))
6231 char buffer[BUFSIZ];
6238 while (state == OK && (name = fgets(buffer, sizeof(buffer), pipe))) {
6243 name = chomp_string(name);
6244 namelen = strcspn(name, separators);
6246 if (name[namelen]) {
6248 value = chomp_string(name + namelen + 1);
6249 valuelen = strlen(value);
6256 state = read_property(name, namelen, value, valuelen);
6259 if (state != ERR && ferror(pipe))
6272 static void __NORETURN
6275 /* XXX: Restore tty modes and let the OS cleanup the rest! */
6281 static void __NORETURN
6282 die(const char *err, ...)
6288 va_start(args, err);
6289 fputs("tig: ", stderr);
6290 vfprintf(stderr, err, args);
6291 fputs("\n", stderr);
6298 warn(const char *msg, ...)
6302 va_start(args, msg);
6303 fputs("tig warning: ", stderr);
6304 vfprintf(stderr, msg, args);
6305 fputs("\n", stderr);
6310 main(int argc, const char *argv[])
6312 const char **run_argv = NULL;
6314 enum request request;
6317 signal(SIGINT, quit);
6319 if (setlocale(LC_ALL, "")) {
6320 char *codeset = nl_langinfo(CODESET);
6322 string_ncopy(opt_codeset, codeset, strlen(codeset));
6325 if (load_repo_info() == ERR)
6326 die("Failed to load repo info.");
6328 if (load_options() == ERR)
6329 die("Failed to load user config.");
6331 if (load_git_config() == ERR)
6332 die("Failed to load repo config.");
6334 request = parse_options(argc, argv, &run_argv);
6335 if (request == REQ_NONE)
6338 /* Require a git repository unless when running in pager mode. */
6339 if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
6340 die("Not a git repository");
6342 if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
6345 if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
6346 opt_iconv = iconv_open(opt_codeset, opt_encoding);
6347 if (opt_iconv == ICONV_NONE)
6348 die("Failed to initialize character set conversion");
6351 if (load_refs() == ERR)
6352 die("Failed to load refs.");
6354 foreach_view (view, i)
6355 argv_from_env(view->ops->argv, view->cmd_env);
6360 if (!prepare_update(VIEW(request), run_argv, NULL, FORMAT_NONE))
6361 die("Failed to format arguments");
6362 open_view(display[current_view], request, OPEN_PREPARED);
6366 while (view_driver(display[current_view], request)) {
6370 foreach_view (view, i)
6372 view = display[current_view];
6374 /* Refresh, accept single keystroke of input */
6375 key = wgetch(status_win);
6377 /* wgetch() with nodelay() enabled returns ERR when there's no
6384 request = get_keybinding(view->keymap, key);
6386 /* Some low-level request handling. This keeps access to
6387 * status_win restricted. */
6391 char *cmd = read_prompt(":");
6394 struct view *next = VIEW(REQ_VIEW_PAGER);
6395 const char *argv[SIZEOF_ARG] = { "git" };
6398 /* When running random commands, initially show the
6399 * command in the title. However, it maybe later be
6400 * overwritten if a commit line is selected. */
6401 string_ncopy(next->ref, cmd, strlen(cmd));
6403 if (!argv_from_string(argv, &argc, cmd)) {
6404 report("Too many arguments");
6405 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
6406 report("Failed to format command");
6408 open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
6416 case REQ_SEARCH_BACK:
6418 const char *prompt = request == REQ_SEARCH ? "/" : "?";
6419 char *search = read_prompt(prompt);
6422 string_ncopy(opt_search, search, strlen(search));
6427 case REQ_SCREEN_RESIZE:
6431 getmaxyx(stdscr, height, width);
6433 /* Resize the status view and let the view driver take
6434 * care of resizing the displayed views. */
6435 wresize(status_win, 1, width);
6436 mvwin(status_win, height - 1, 0);
6437 wrefresh(status_win);