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 #define TIG_DIFF_CMD \
126 "git show --pretty=fuller --no-color --root --patch-with-stat --find-copies-harder -C %s 2>/dev/null"
128 #define TIG_LOG_CMD \
129 "git log --no-color --cc --stat -n100 %s 2>/dev/null"
131 #define TIG_MAIN_BASE \
132 "git log --no-color --pretty=raw --parents --topo-order"
134 #define TIG_MAIN_CMD \
135 TIG_MAIN_BASE " %s 2>/dev/null"
137 #define TIG_TREE_CMD \
140 #define TIG_BLOB_CMD \
141 "git cat-file blob %s"
143 /* XXX: Needs to be defined to the empty string. */
144 #define TIG_HELP_CMD ""
145 #define TIG_PAGER_CMD ""
146 #define TIG_STATUS_CMD ""
147 #define TIG_STAGE_CMD ""
148 #define TIG_BLAME_CMD ""
150 /* Some ascii-shorthands fitted into the ncurses namespace. */
152 #define KEY_RETURN '\r'
157 char *name; /* Ref name; tag or head names are shortened. */
158 char id[SIZEOF_REV]; /* Commit SHA1 ID */
159 unsigned int head:1; /* Is it the current HEAD? */
160 unsigned int tag:1; /* Is it a tag? */
161 unsigned int ltag:1; /* If so, is the tag local? */
162 unsigned int remote:1; /* Is it a remote ref? */
163 unsigned int tracked:1; /* Is it the remote for the current HEAD? */
164 unsigned int next:1; /* For ref lists: are there more refs? */
167 static struct ref **get_refs(const char *id);
170 FORMAT_ALL, /* Perform replacement in all arguments. */
171 FORMAT_DASH, /* Perform replacement up until "--". */
172 FORMAT_NONE /* No replacement should be performed. */
175 static bool format_command(char dst[], const char *src[], enum format_flags flags);
176 static bool format_argv(const char *dst[], const char *src[], enum format_flags flags);
185 set_from_int_map(struct int_map *map, size_t map_size,
186 int *value, const char *name, int namelen)
191 for (i = 0; i < map_size; i++)
192 if (namelen == map[i].namelen &&
193 !strncasecmp(name, map[i].name, namelen)) {
194 *value = map[i].value;
207 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
209 if (srclen > dstlen - 1)
212 strncpy(dst, src, srclen);
216 /* Shorthands for safely copying into a fixed buffer. */
218 #define string_copy(dst, src) \
219 string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
221 #define string_ncopy(dst, src, srclen) \
222 string_ncopy_do(dst, sizeof(dst), src, srclen)
224 #define string_copy_rev(dst, src) \
225 string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
227 #define string_add(dst, from, src) \
228 string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
231 chomp_string(char *name)
235 while (isspace(*name))
238 namelen = strlen(name) - 1;
239 while (namelen > 0 && isspace(name[namelen]))
246 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
249 size_t pos = bufpos ? *bufpos : 0;
252 pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
258 return pos >= bufsize ? FALSE : TRUE;
261 #define string_format(buf, fmt, args...) \
262 string_nformat(buf, sizeof(buf), NULL, fmt, args)
264 #define string_format_from(buf, from, fmt, args...) \
265 string_nformat(buf, sizeof(buf), from, fmt, args)
268 string_enum_compare(const char *str1, const char *str2, int len)
272 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
274 /* Diff-Header == DIFF_HEADER */
275 for (i = 0; i < len; i++) {
276 if (toupper(str1[i]) == toupper(str2[i]))
279 if (string_enum_sep(str1[i]) &&
280 string_enum_sep(str2[i]))
283 return str1[i] - str2[i];
289 #define prefixcmp(str1, str2) \
290 strncmp(str1, str2, STRING_SIZE(str2))
293 suffixcmp(const char *str, int slen, const char *suffix)
295 size_t len = slen >= 0 ? slen : strlen(str);
296 size_t suffixlen = strlen(suffix);
298 return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
303 * NOTE: The following is a slightly modified copy of the git project's shell
304 * quoting routines found in the quote.c file.
306 * Help to copy the thing properly quoted for the shell safety. any single
307 * quote is replaced with '\'', any exclamation point is replaced with '\!',
308 * and the whole thing is enclosed in a
311 * original sq_quote result
312 * name ==> name ==> 'name'
313 * a b ==> a b ==> 'a b'
314 * a'b ==> a'\''b ==> 'a'\''b'
315 * a!b ==> a'\!'b ==> 'a'\!'b'
319 sq_quote(char buf[SIZEOF_STR], size_t bufsize, const char *src)
323 #define BUFPUT(x) do { if (bufsize < SIZEOF_STR) buf[bufsize++] = (x); } while (0)
326 while ((c = *src++)) {
327 if (c == '\'' || c == '!') {
338 if (bufsize < SIZEOF_STR)
345 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
349 while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
350 bool advance = cmd[valuelen] != 0;
353 argv[(*argc)++] = chomp_string(cmd);
354 cmd += valuelen + advance;
357 if (*argc < SIZEOF_ARG)
359 return *argc < SIZEOF_ARG;
364 * Executing external commands.
368 IO_FD, /* File descriptor based IO. */
369 IO_FG, /* Execute command with same std{in,out,err}. */
370 IO_RD, /* Read only fork+exec IO. */
371 IO_WR, /* Write only fork+exec IO. */
375 enum io_type type; /* The requested type of pipe. */
376 const char *dir; /* Directory from which to execute. */
377 FILE *pipe; /* Pipe for reading or writing. */
378 int error; /* Error status. */
379 char sh[SIZEOF_STR]; /* Shell command buffer. */
380 char *buf; /* Read/write buffer. */
381 size_t bufalloc; /* Allocated buffer size. */
385 reset_io(struct io *io)
394 init_io(struct io *io, const char *dir, enum io_type type)
402 init_io_fd(struct io *io, FILE *pipe)
404 init_io(io, NULL, IO_FD);
406 return io->pipe != NULL;
410 done_io(struct io *io)
413 if (io->type == IO_FD)
415 else if (io->type == IO_RD || io->type == IO_WR)
422 start_io(struct io *io)
424 char buf[SIZEOF_STR * 2];
427 if (io->dir && *io->dir &&
428 !string_format_from(buf, &bufpos, "cd %s;", io->dir))
431 if (!string_format_from(buf, &bufpos, "%s", io->sh))
434 if (io->type == IO_FG)
435 return system(buf) == 0;
437 io->pipe = popen(io->sh, io->type == IO_RD ? "r" : "w");
438 return io->pipe != NULL;
442 run_io(struct io *io, enum io_type type, const char *cmd)
444 init_io(io, NULL, type);
445 string_ncopy(io->sh, cmd, strlen(cmd));
450 run_io_do(struct io *io)
452 return start_io(io) && done_io(io);
456 run_io_fg(const char **argv, const char *dir)
460 init_io(&io, dir, IO_FG);
461 if (!format_command(io.sh, argv, FORMAT_NONE))
463 return run_io_do(&io);
467 run_io_format(struct io *io, const char *cmd, ...)
472 init_io(io, NULL, IO_RD);
474 if (vsnprintf(io->sh, sizeof(io->sh), cmd, args) >= sizeof(io->sh))
478 return io->sh[0] ? start_io(io) : FALSE;
482 io_eof(struct io *io)
484 return feof(io->pipe);
488 io_error(struct io *io)
494 io_strerror(struct io *io)
496 return strerror(io->error);
500 io_gets(struct io *io)
503 io->buf = malloc(BUFSIZ);
506 io->bufalloc = BUFSIZ;
509 if (!fgets(io->buf, io->bufalloc, io->pipe)) {
510 if (ferror(io->pipe))
524 /* XXX: Keep the view request first and in sync with views[]. */ \
525 REQ_GROUP("View switching") \
526 REQ_(VIEW_MAIN, "Show main view"), \
527 REQ_(VIEW_DIFF, "Show diff view"), \
528 REQ_(VIEW_LOG, "Show log view"), \
529 REQ_(VIEW_TREE, "Show tree view"), \
530 REQ_(VIEW_BLOB, "Show blob view"), \
531 REQ_(VIEW_BLAME, "Show blame view"), \
532 REQ_(VIEW_HELP, "Show help page"), \
533 REQ_(VIEW_PAGER, "Show pager view"), \
534 REQ_(VIEW_STATUS, "Show status view"), \
535 REQ_(VIEW_STAGE, "Show stage view"), \
537 REQ_GROUP("View manipulation") \
538 REQ_(ENTER, "Enter current line and scroll"), \
539 REQ_(NEXT, "Move to next"), \
540 REQ_(PREVIOUS, "Move to previous"), \
541 REQ_(VIEW_NEXT, "Move focus to next view"), \
542 REQ_(REFRESH, "Reload and refresh"), \
543 REQ_(MAXIMIZE, "Maximize the current view"), \
544 REQ_(VIEW_CLOSE, "Close the current view"), \
545 REQ_(QUIT, "Close all views and quit"), \
547 REQ_GROUP("View specific requests") \
548 REQ_(STATUS_UPDATE, "Update file status"), \
549 REQ_(STATUS_REVERT, "Revert file changes"), \
550 REQ_(STATUS_MERGE, "Merge file using external tool"), \
551 REQ_(STAGE_NEXT, "Find next chunk to stage"), \
552 REQ_(TREE_PARENT, "Switch to parent directory in tree view"), \
554 REQ_GROUP("Cursor navigation") \
555 REQ_(MOVE_UP, "Move cursor one line up"), \
556 REQ_(MOVE_DOWN, "Move cursor one line down"), \
557 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
558 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
559 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
560 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
562 REQ_GROUP("Scrolling") \
563 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
564 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
565 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
566 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
568 REQ_GROUP("Searching") \
569 REQ_(SEARCH, "Search the view"), \
570 REQ_(SEARCH_BACK, "Search backwards in the view"), \
571 REQ_(FIND_NEXT, "Find next search match"), \
572 REQ_(FIND_PREV, "Find previous search match"), \
574 REQ_GROUP("Option manipulation") \
575 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
576 REQ_(TOGGLE_DATE, "Toggle date display"), \
577 REQ_(TOGGLE_AUTHOR, "Toggle author display"), \
578 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
579 REQ_(TOGGLE_REFS, "Toggle reference display (tags/branches)"), \
582 REQ_(PROMPT, "Bring up the prompt"), \
583 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
584 REQ_(SCREEN_RESIZE, "Resize the screen"), \
585 REQ_(SHOW_VERSION, "Show version information"), \
586 REQ_(STOP_LOADING, "Stop all loading views"), \
587 REQ_(EDIT, "Open in editor"), \
588 REQ_(NONE, "Do nothing")
591 /* User action requests. */
593 #define REQ_GROUP(help)
594 #define REQ_(req, help) REQ_##req
596 /* Offset all requests to avoid conflicts with ncurses getch values. */
597 REQ_OFFSET = KEY_MAX + 1,
604 struct request_info {
605 enum request request;
611 static struct request_info req_info[] = {
612 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
613 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
620 get_request(const char *name)
622 int namelen = strlen(name);
625 for (i = 0; i < ARRAY_SIZE(req_info); i++)
626 if (req_info[i].namelen == namelen &&
627 !string_enum_compare(req_info[i].name, name, namelen))
628 return req_info[i].request;
638 static const char usage[] =
639 "tig " TIG_VERSION " (" __DATE__ ")\n"
641 "Usage: tig [options] [revs] [--] [paths]\n"
642 " or: tig show [options] [revs] [--] [paths]\n"
643 " or: tig blame [rev] path\n"
645 " or: tig < [git command output]\n"
648 " -v, --version Show version and exit\n"
649 " -h, --help Show help message and exit";
651 /* Option and state variables. */
652 static bool opt_date = TRUE;
653 static bool opt_author = TRUE;
654 static bool opt_line_number = FALSE;
655 static bool opt_line_graphics = TRUE;
656 static bool opt_rev_graph = FALSE;
657 static bool opt_show_refs = TRUE;
658 static int opt_num_interval = NUMBER_INTERVAL;
659 static int opt_tab_size = TAB_SIZE;
660 static int opt_author_cols = AUTHOR_COLS-1;
661 static char opt_cmd[SIZEOF_STR] = "";
662 static char opt_path[SIZEOF_STR] = "";
663 static char opt_file[SIZEOF_STR] = "";
664 static char opt_ref[SIZEOF_REF] = "";
665 static char opt_head[SIZEOF_REF] = "";
666 static char opt_head_rev[SIZEOF_REV] = "";
667 static char opt_remote[SIZEOF_REF] = "";
668 static FILE *opt_pipe = NULL;
669 static char opt_encoding[20] = "UTF-8";
670 static bool opt_utf8 = TRUE;
671 static char opt_codeset[20] = "UTF-8";
672 static iconv_t opt_iconv = ICONV_NONE;
673 static char opt_search[SIZEOF_STR] = "";
674 static char opt_cdup[SIZEOF_STR] = "";
675 static char opt_git_dir[SIZEOF_STR] = "";
676 static signed char opt_is_inside_work_tree = -1; /* set to TRUE or FALSE */
677 static char opt_editor[SIZEOF_STR] = "";
678 static FILE *opt_tty = NULL;
680 #define is_initial_commit() (!*opt_head_rev)
681 #define is_head_commit(rev) (!strcmp((rev), "HEAD") || !strcmp(opt_head_rev, (rev)))
684 parse_options(int argc, const char *argv[])
686 enum request request = REQ_VIEW_MAIN;
688 const char *subcommand;
689 bool seen_dashdash = FALSE;
692 if (!isatty(STDIN_FILENO)) {
694 return REQ_VIEW_PAGER;
698 return REQ_VIEW_MAIN;
700 subcommand = argv[1];
701 if (!strcmp(subcommand, "status") || !strcmp(subcommand, "-S")) {
702 if (!strcmp(subcommand, "-S"))
703 warn("`-S' has been deprecated; use `tig status' instead");
705 warn("ignoring arguments after `%s'", subcommand);
706 return REQ_VIEW_STATUS;
708 } else if (!strcmp(subcommand, "blame")) {
709 if (argc <= 2 || argc > 4)
710 die("invalid number of options to blame\n\n%s", usage);
714 string_ncopy(opt_ref, argv[i], strlen(argv[i]));
718 string_ncopy(opt_file, argv[i], strlen(argv[i]));
719 return REQ_VIEW_BLAME;
721 } else if (!strcmp(subcommand, "show")) {
722 request = REQ_VIEW_DIFF;
724 } else if (!strcmp(subcommand, "log") || !strcmp(subcommand, "diff")) {
725 request = subcommand[0] == 'l' ? REQ_VIEW_LOG : REQ_VIEW_DIFF;
726 warn("`tig %s' has been deprecated", subcommand);
733 /* XXX: This is vulnerable to the user overriding
734 * options required for the main view parser. */
735 string_copy(opt_cmd, TIG_MAIN_BASE);
737 string_format(opt_cmd, "git %s", subcommand);
739 buf_size = strlen(opt_cmd);
741 for (i = 1 + !!subcommand; i < argc; i++) {
742 const char *opt = argv[i];
744 if (seen_dashdash || !strcmp(opt, "--")) {
745 seen_dashdash = TRUE;
747 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
748 printf("tig version %s\n", TIG_VERSION);
751 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
752 printf("%s\n", usage);
756 opt_cmd[buf_size++] = ' ';
757 buf_size = sq_quote(opt_cmd, buf_size, opt);
758 if (buf_size >= sizeof(opt_cmd))
759 die("command too long");
762 opt_cmd[buf_size] = 0;
769 * Line-oriented content detection.
773 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
774 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
775 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
776 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
777 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
778 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
779 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
780 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
781 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
782 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
783 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
784 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
785 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
786 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
787 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
788 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
789 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
790 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
791 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
792 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
793 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
794 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
795 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
796 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
797 LINE(AUTHOR, "author ", COLOR_CYAN, COLOR_DEFAULT, 0), \
798 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
799 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
800 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
801 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
802 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
803 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
804 LINE(DELIMITER, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
805 LINE(DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
806 LINE(LINE_NUMBER, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
807 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
808 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
809 LINE(MAIN_AUTHOR, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
810 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
811 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
812 LINE(MAIN_LOCAL_TAG,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
813 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
814 LINE(MAIN_TRACKED, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
815 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
816 LINE(MAIN_HEAD, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
817 LINE(MAIN_REVGRAPH,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
818 LINE(TREE_DIR, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
819 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
820 LINE(STAT_HEAD, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
821 LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
822 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
823 LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
824 LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
825 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
826 LINE(BLAME_ID, "", COLOR_MAGENTA, COLOR_DEFAULT, 0)
829 #define LINE(type, line, fg, bg, attr) \
837 const char *name; /* Option name. */
838 int namelen; /* Size of option name. */
839 const char *line; /* The start of line to match. */
840 int linelen; /* Size of string to match. */
841 int fg, bg, attr; /* Color and text attributes for the lines. */
844 static struct line_info line_info[] = {
845 #define LINE(type, line, fg, bg, attr) \
846 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
851 static enum line_type
852 get_line_type(const char *line)
854 int linelen = strlen(line);
857 for (type = 0; type < ARRAY_SIZE(line_info); type++)
858 /* Case insensitive search matches Signed-off-by lines better. */
859 if (linelen >= line_info[type].linelen &&
860 !strncasecmp(line_info[type].line, line, line_info[type].linelen))
867 get_line_attr(enum line_type type)
869 assert(type < ARRAY_SIZE(line_info));
870 return COLOR_PAIR(type) | line_info[type].attr;
873 static struct line_info *
874 get_line_info(const char *name)
876 size_t namelen = strlen(name);
879 for (type = 0; type < ARRAY_SIZE(line_info); type++)
880 if (namelen == line_info[type].namelen &&
881 !string_enum_compare(line_info[type].name, name, namelen))
882 return &line_info[type];
890 int default_bg = line_info[LINE_DEFAULT].bg;
891 int default_fg = line_info[LINE_DEFAULT].fg;
896 if (assume_default_colors(default_fg, default_bg) == ERR) {
897 default_bg = COLOR_BLACK;
898 default_fg = COLOR_WHITE;
901 for (type = 0; type < ARRAY_SIZE(line_info); type++) {
902 struct line_info *info = &line_info[type];
903 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
904 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
906 init_pair(type, fg, bg);
914 unsigned int selected:1;
915 unsigned int dirty:1;
917 void *data; /* User data */
927 enum request request;
930 static struct keybinding default_keybindings[] = {
932 { 'm', REQ_VIEW_MAIN },
933 { 'd', REQ_VIEW_DIFF },
934 { 'l', REQ_VIEW_LOG },
935 { 't', REQ_VIEW_TREE },
936 { 'f', REQ_VIEW_BLOB },
937 { 'B', REQ_VIEW_BLAME },
938 { 'p', REQ_VIEW_PAGER },
939 { 'h', REQ_VIEW_HELP },
940 { 'S', REQ_VIEW_STATUS },
941 { 'c', REQ_VIEW_STAGE },
943 /* View manipulation */
944 { 'q', REQ_VIEW_CLOSE },
945 { KEY_TAB, REQ_VIEW_NEXT },
946 { KEY_RETURN, REQ_ENTER },
947 { KEY_UP, REQ_PREVIOUS },
948 { KEY_DOWN, REQ_NEXT },
949 { 'R', REQ_REFRESH },
950 { KEY_F(5), REQ_REFRESH },
951 { 'O', REQ_MAXIMIZE },
953 /* Cursor navigation */
954 { 'k', REQ_MOVE_UP },
955 { 'j', REQ_MOVE_DOWN },
956 { KEY_HOME, REQ_MOVE_FIRST_LINE },
957 { KEY_END, REQ_MOVE_LAST_LINE },
958 { KEY_NPAGE, REQ_MOVE_PAGE_DOWN },
959 { ' ', REQ_MOVE_PAGE_DOWN },
960 { KEY_PPAGE, REQ_MOVE_PAGE_UP },
961 { 'b', REQ_MOVE_PAGE_UP },
962 { '-', REQ_MOVE_PAGE_UP },
965 { KEY_IC, REQ_SCROLL_LINE_UP },
966 { KEY_DC, REQ_SCROLL_LINE_DOWN },
967 { 'w', REQ_SCROLL_PAGE_UP },
968 { 's', REQ_SCROLL_PAGE_DOWN },
972 { '?', REQ_SEARCH_BACK },
973 { 'n', REQ_FIND_NEXT },
974 { 'N', REQ_FIND_PREV },
978 { 'z', REQ_STOP_LOADING },
979 { 'v', REQ_SHOW_VERSION },
980 { 'r', REQ_SCREEN_REDRAW },
981 { '.', REQ_TOGGLE_LINENO },
982 { 'D', REQ_TOGGLE_DATE },
983 { 'A', REQ_TOGGLE_AUTHOR },
984 { 'g', REQ_TOGGLE_REV_GRAPH },
985 { 'F', REQ_TOGGLE_REFS },
987 { 'u', REQ_STATUS_UPDATE },
988 { '!', REQ_STATUS_REVERT },
989 { 'M', REQ_STATUS_MERGE },
990 { '@', REQ_STAGE_NEXT },
991 { ',', REQ_TREE_PARENT },
994 /* Using the ncurses SIGWINCH handler. */
995 { KEY_RESIZE, REQ_SCREEN_RESIZE },
998 #define KEYMAP_INFO \
1012 #define KEYMAP_(name) KEYMAP_##name
1017 static struct int_map keymap_table[] = {
1018 #define KEYMAP_(name) { #name, STRING_SIZE(#name), KEYMAP_##name }
1023 #define set_keymap(map, name) \
1024 set_from_int_map(keymap_table, ARRAY_SIZE(keymap_table), map, name, strlen(name))
1026 struct keybinding_table {
1027 struct keybinding *data;
1031 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1034 add_keybinding(enum keymap keymap, enum request request, int key)
1036 struct keybinding_table *table = &keybindings[keymap];
1038 table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1040 die("Failed to allocate keybinding");
1041 table->data[table->size].alias = key;
1042 table->data[table->size++].request = request;
1045 /* Looks for a key binding first in the given map, then in the generic map, and
1046 * lastly in the default keybindings. */
1048 get_keybinding(enum keymap keymap, int key)
1052 for (i = 0; i < keybindings[keymap].size; i++)
1053 if (keybindings[keymap].data[i].alias == key)
1054 return keybindings[keymap].data[i].request;
1056 for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1057 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1058 return keybindings[KEYMAP_GENERIC].data[i].request;
1060 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1061 if (default_keybindings[i].alias == key)
1062 return default_keybindings[i].request;
1064 return (enum request) key;
1073 static struct key key_table[] = {
1074 { "Enter", KEY_RETURN },
1076 { "Backspace", KEY_BACKSPACE },
1078 { "Escape", KEY_ESC },
1079 { "Left", KEY_LEFT },
1080 { "Right", KEY_RIGHT },
1082 { "Down", KEY_DOWN },
1083 { "Insert", KEY_IC },
1084 { "Delete", KEY_DC },
1086 { "Home", KEY_HOME },
1088 { "PageUp", KEY_PPAGE },
1089 { "PageDown", KEY_NPAGE },
1099 { "F10", KEY_F(10) },
1100 { "F11", KEY_F(11) },
1101 { "F12", KEY_F(12) },
1105 get_key_value(const char *name)
1109 for (i = 0; i < ARRAY_SIZE(key_table); i++)
1110 if (!strcasecmp(key_table[i].name, name))
1111 return key_table[i].value;
1113 if (strlen(name) == 1 && isprint(*name))
1120 get_key_name(int key_value)
1122 static char key_char[] = "'X'";
1123 const char *seq = NULL;
1126 for (key = 0; key < ARRAY_SIZE(key_table); key++)
1127 if (key_table[key].value == key_value)
1128 seq = key_table[key].name;
1132 isprint(key_value)) {
1133 key_char[1] = (char) key_value;
1137 return seq ? seq : "(no key)";
1141 get_key(enum request request)
1143 static char buf[BUFSIZ];
1150 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1151 struct keybinding *keybinding = &default_keybindings[i];
1153 if (keybinding->request != request)
1156 if (!string_format_from(buf, &pos, "%s%s", sep,
1157 get_key_name(keybinding->alias)))
1158 return "Too many keybindings!";
1165 struct run_request {
1168 const char *argv[SIZEOF_ARG];
1171 static struct run_request *run_request;
1172 static size_t run_requests;
1175 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1177 struct run_request *req;
1179 if (argc >= ARRAY_SIZE(req->argv) - 1)
1182 req = realloc(run_request, (run_requests + 1) * sizeof(*run_request));
1187 req = &run_request[run_requests];
1188 req->keymap = keymap;
1190 req->argv[0] = NULL;
1192 if (!format_argv(req->argv, argv, FORMAT_NONE))
1195 return REQ_NONE + ++run_requests;
1198 static struct run_request *
1199 get_run_request(enum request request)
1201 if (request <= REQ_NONE)
1203 return &run_request[request - REQ_NONE - 1];
1207 add_builtin_run_requests(void)
1209 const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1210 const char *gc[] = { "git", "gc", NULL };
1217 { KEYMAP_MAIN, 'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1218 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1222 for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1225 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1226 if (req != REQ_NONE)
1227 add_keybinding(reqs[i].keymap, req, reqs[i].key);
1232 * User config file handling.
1235 static struct int_map color_map[] = {
1236 #define COLOR_MAP(name) { #name, STRING_SIZE(#name), COLOR_##name }
1248 #define set_color(color, name) \
1249 set_from_int_map(color_map, ARRAY_SIZE(color_map), color, name, strlen(name))
1251 static struct int_map attr_map[] = {
1252 #define ATTR_MAP(name) { #name, STRING_SIZE(#name), A_##name }
1259 ATTR_MAP(UNDERLINE),
1262 #define set_attribute(attr, name) \
1263 set_from_int_map(attr_map, ARRAY_SIZE(attr_map), attr, name, strlen(name))
1265 static int config_lineno;
1266 static bool config_errors;
1267 static const char *config_msg;
1269 /* Wants: object fgcolor bgcolor [attr] */
1271 option_color_command(int argc, const char *argv[])
1273 struct line_info *info;
1275 if (argc != 3 && argc != 4) {
1276 config_msg = "Wrong number of arguments given to color command";
1280 info = get_line_info(argv[0]);
1282 if (!string_enum_compare(argv[0], "main-delim", strlen("main-delim"))) {
1283 info = get_line_info("delimiter");
1285 } else if (!string_enum_compare(argv[0], "main-date", strlen("main-date"))) {
1286 info = get_line_info("date");
1289 config_msg = "Unknown color name";
1294 if (set_color(&info->fg, argv[1]) == ERR ||
1295 set_color(&info->bg, argv[2]) == ERR) {
1296 config_msg = "Unknown color";
1300 if (argc == 4 && set_attribute(&info->attr, argv[3]) == ERR) {
1301 config_msg = "Unknown attribute";
1308 static bool parse_bool(const char *s)
1310 return (!strcmp(s, "1") || !strcmp(s, "true") ||
1311 !strcmp(s, "yes")) ? TRUE : FALSE;
1315 parse_int(const char *s, int default_value, int min, int max)
1317 int value = atoi(s);
1319 return (value < min || value > max) ? default_value : value;
1322 /* Wants: name = value */
1324 option_set_command(int argc, const char *argv[])
1327 config_msg = "Wrong number of arguments given to set command";
1331 if (strcmp(argv[1], "=")) {
1332 config_msg = "No value assigned";
1336 if (!strcmp(argv[0], "show-author")) {
1337 opt_author = parse_bool(argv[2]);
1341 if (!strcmp(argv[0], "show-date")) {
1342 opt_date = parse_bool(argv[2]);
1346 if (!strcmp(argv[0], "show-rev-graph")) {
1347 opt_rev_graph = parse_bool(argv[2]);
1351 if (!strcmp(argv[0], "show-refs")) {
1352 opt_show_refs = parse_bool(argv[2]);
1356 if (!strcmp(argv[0], "show-line-numbers")) {
1357 opt_line_number = parse_bool(argv[2]);
1361 if (!strcmp(argv[0], "line-graphics")) {
1362 opt_line_graphics = parse_bool(argv[2]);
1366 if (!strcmp(argv[0], "line-number-interval")) {
1367 opt_num_interval = parse_int(argv[2], opt_num_interval, 1, 1024);
1371 if (!strcmp(argv[0], "author-width")) {
1372 opt_author_cols = parse_int(argv[2], opt_author_cols, 0, 1024);
1376 if (!strcmp(argv[0], "tab-size")) {
1377 opt_tab_size = parse_int(argv[2], opt_tab_size, 1, 1024);
1381 if (!strcmp(argv[0], "commit-encoding")) {
1382 const char *arg = argv[2];
1383 int arglen = strlen(arg);
1388 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1389 config_msg = "Unmatched quotation";
1392 arg += 1; arglen -= 2;
1394 string_ncopy(opt_encoding, arg, strlen(arg));
1399 config_msg = "Unknown variable name";
1403 /* Wants: mode request key */
1405 option_bind_command(int argc, const char *argv[])
1407 enum request request;
1412 config_msg = "Wrong number of arguments given to bind command";
1416 if (set_keymap(&keymap, argv[0]) == ERR) {
1417 config_msg = "Unknown key map";
1421 key = get_key_value(argv[1]);
1423 config_msg = "Unknown key";
1427 request = get_request(argv[2]);
1428 if (request == REQ_NONE) {
1429 const char *obsolete[] = { "cherry-pick" };
1430 size_t namelen = strlen(argv[2]);
1433 for (i = 0; i < ARRAY_SIZE(obsolete); i++) {
1434 if (namelen == strlen(obsolete[i]) &&
1435 !string_enum_compare(obsolete[i], argv[2], namelen)) {
1436 config_msg = "Obsolete request name";
1441 if (request == REQ_NONE && *argv[2]++ == '!')
1442 request = add_run_request(keymap, key, argc - 2, argv + 2);
1443 if (request == REQ_NONE) {
1444 config_msg = "Unknown request name";
1448 add_keybinding(keymap, request, key);
1454 set_option(const char *opt, char *value)
1456 const char *argv[SIZEOF_ARG];
1459 if (!argv_from_string(argv, &argc, value)) {
1460 config_msg = "Too many option arguments";
1464 if (!strcmp(opt, "color"))
1465 return option_color_command(argc, argv);
1467 if (!strcmp(opt, "set"))
1468 return option_set_command(argc, argv);
1470 if (!strcmp(opt, "bind"))
1471 return option_bind_command(argc, argv);
1473 config_msg = "Unknown option command";
1478 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1483 config_msg = "Internal error";
1485 /* Check for comment markers, since read_properties() will
1486 * only ensure opt and value are split at first " \t". */
1487 optlen = strcspn(opt, "#");
1491 if (opt[optlen] != 0) {
1492 config_msg = "No option value";
1496 /* Look for comment endings in the value. */
1497 size_t len = strcspn(value, "#");
1499 if (len < valuelen) {
1501 value[valuelen] = 0;
1504 status = set_option(opt, value);
1507 if (status == ERR) {
1508 fprintf(stderr, "Error on line %d, near '%.*s': %s\n",
1509 config_lineno, (int) optlen, opt, config_msg);
1510 config_errors = TRUE;
1513 /* Always keep going if errors are encountered. */
1518 load_option_file(const char *path)
1522 /* It's ok that the file doesn't exist. */
1523 file = fopen(path, "r");
1528 config_errors = FALSE;
1530 if (read_properties(file, " \t", read_option) == ERR ||
1531 config_errors == TRUE)
1532 fprintf(stderr, "Errors while loading %s.\n", path);
1538 const char *home = getenv("HOME");
1539 const char *tigrc_user = getenv("TIGRC_USER");
1540 const char *tigrc_system = getenv("TIGRC_SYSTEM");
1541 char buf[SIZEOF_STR];
1543 add_builtin_run_requests();
1545 if (!tigrc_system) {
1546 if (!string_format(buf, "%s/tigrc", SYSCONFDIR))
1550 load_option_file(tigrc_system);
1553 if (!home || !string_format(buf, "%s/.tigrc", home))
1557 load_option_file(tigrc_user);
1570 /* The display array of active views and the index of the current view. */
1571 static struct view *display[2];
1572 static unsigned int current_view;
1574 /* Reading from the prompt? */
1575 static bool input_mode = FALSE;
1577 #define foreach_displayed_view(view, i) \
1578 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1580 #define displayed_views() (display[1] != NULL ? 2 : 1)
1582 /* Current head and commit ID */
1583 static char ref_blob[SIZEOF_REF] = "";
1584 static char ref_commit[SIZEOF_REF] = "HEAD";
1585 static char ref_head[SIZEOF_REF] = "HEAD";
1588 const char *name; /* View name */
1589 const char *cmd_fmt; /* Default command line format */
1590 const char *cmd_env; /* Command line set via environment */
1591 const char *id; /* Points to either of ref_{head,commit,blob} */
1593 struct view_ops *ops; /* View operations */
1595 enum keymap keymap; /* What keymap does this view have */
1596 bool git_dir; /* Whether the view requires a git directory. */
1598 char ref[SIZEOF_REF]; /* Hovered commit reference */
1599 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
1601 int height, width; /* The width and height of the main window */
1602 WINDOW *win; /* The main window */
1603 WINDOW *title; /* The title window living below the main window */
1606 unsigned long offset; /* Offset of the window top */
1607 unsigned long lineno; /* Current line number */
1610 char grep[SIZEOF_STR]; /* Search string */
1611 regex_t *regex; /* Pre-compiled regex */
1613 /* If non-NULL, points to the view that opened this view. If this view
1614 * is closed tig will switch back to the parent view. */
1615 struct view *parent;
1618 size_t lines; /* Total number of lines */
1619 struct line *line; /* Line index */
1620 size_t line_alloc; /* Total number of allocated lines */
1621 size_t line_size; /* Total number of used lines */
1622 unsigned int digits; /* Number of digits in the lines member. */
1625 struct line *curline; /* Line currently being drawn. */
1626 enum line_type curtype; /* Attribute currently used for drawing. */
1627 unsigned long col; /* Column when drawing. */
1636 /* What type of content being displayed. Used in the title bar. */
1638 /* Open and reads in all view content. */
1639 bool (*open)(struct view *view);
1640 /* Read one line; updates view->line. */
1641 bool (*read)(struct view *view, char *data);
1642 /* Draw one line; @lineno must be < view->height. */
1643 bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
1644 /* Depending on view handle a special requests. */
1645 enum request (*request)(struct view *view, enum request request, struct line *line);
1646 /* Search for regex in a line. */
1647 bool (*grep)(struct view *view, struct line *line);
1649 void (*select)(struct view *view, struct line *line);
1652 static struct view_ops blame_ops;
1653 static struct view_ops blob_ops;
1654 static struct view_ops help_ops;
1655 static struct view_ops log_ops;
1656 static struct view_ops main_ops;
1657 static struct view_ops pager_ops;
1658 static struct view_ops stage_ops;
1659 static struct view_ops status_ops;
1660 static struct view_ops tree_ops;
1662 #define VIEW_STR(name, cmd, env, ref, ops, map, git) \
1663 { name, cmd, #env, ref, ops, map, git }
1665 #define VIEW_(id, name, ops, git, ref) \
1666 VIEW_STR(name, TIG_##id##_CMD, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
1669 static struct view views[] = {
1670 VIEW_(MAIN, "main", &main_ops, TRUE, ref_head),
1671 VIEW_(DIFF, "diff", &pager_ops, TRUE, ref_commit),
1672 VIEW_(LOG, "log", &log_ops, TRUE, ref_head),
1673 VIEW_(TREE, "tree", &tree_ops, TRUE, ref_commit),
1674 VIEW_(BLOB, "blob", &blob_ops, TRUE, ref_blob),
1675 VIEW_(BLAME, "blame", &blame_ops, TRUE, ref_commit),
1676 VIEW_(HELP, "help", &help_ops, FALSE, ""),
1677 VIEW_(PAGER, "pager", &pager_ops, FALSE, "stdin"),
1678 VIEW_(STATUS, "status", &status_ops, TRUE, ""),
1679 VIEW_(STAGE, "stage", &stage_ops, TRUE, ""),
1682 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
1683 #define VIEW_REQ(view) ((view) - views + REQ_OFFSET + 1)
1685 #define foreach_view(view, i) \
1686 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1688 #define view_is_displayed(view) \
1689 (view == display[0] || view == display[1])
1696 static int line_graphics[] = {
1697 /* LINE_GRAPHIC_VLINE: */ '|'
1701 set_view_attr(struct view *view, enum line_type type)
1703 if (!view->curline->selected && view->curtype != type) {
1704 wattrset(view->win, get_line_attr(type));
1705 wchgat(view->win, -1, 0, type, NULL);
1706 view->curtype = type;
1711 draw_chars(struct view *view, enum line_type type, const char *string,
1712 int max_len, bool use_tilde)
1716 int trimmed = FALSE;
1722 len = utf8_length(string, &col, max_len, &trimmed, use_tilde);
1724 col = len = strlen(string);
1725 if (len > max_len) {
1729 col = len = max_len;
1734 set_view_attr(view, type);
1735 waddnstr(view->win, string, len);
1736 if (trimmed && use_tilde) {
1737 set_view_attr(view, LINE_DELIMITER);
1738 waddch(view->win, '~');
1746 draw_space(struct view *view, enum line_type type, int max, int spaces)
1748 static char space[] = " ";
1751 spaces = MIN(max, spaces);
1753 while (spaces > 0) {
1754 int len = MIN(spaces, sizeof(space) - 1);
1756 col += draw_chars(view, type, space, spaces, FALSE);
1764 draw_lineno(struct view *view, unsigned int lineno)
1767 int digits3 = view->digits < 3 ? 3 : view->digits;
1768 int max_number = MIN(digits3, STRING_SIZE(number));
1769 int max = view->width - view->col;
1772 if (max < max_number)
1775 lineno += view->offset + 1;
1776 if (lineno == 1 || (lineno % opt_num_interval) == 0) {
1777 static char fmt[] = "%1ld";
1779 if (view->digits <= 9)
1780 fmt[1] = '0' + digits3;
1782 if (!string_format(number, fmt, lineno))
1784 col = draw_chars(view, LINE_LINE_NUMBER, number, max_number, TRUE);
1786 col = draw_space(view, LINE_LINE_NUMBER, max_number, max_number);
1790 set_view_attr(view, LINE_DEFAULT);
1791 waddch(view->win, line_graphics[LINE_GRAPHIC_VLINE]);
1796 col += draw_space(view, LINE_DEFAULT, max - col, 1);
1799 return view->width - view->col <= 0;
1803 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
1805 view->col += draw_chars(view, type, string, view->width - view->col, trim);
1806 return view->width - view->col <= 0;
1810 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
1812 int max = view->width - view->col;
1818 set_view_attr(view, type);
1819 /* Using waddch() instead of waddnstr() ensures that
1820 * they'll be rendered correctly for the cursor line. */
1821 for (i = 0; i < size; i++)
1822 waddch(view->win, graphic[i]);
1826 waddch(view->win, ' ');
1830 return view->width - view->col <= 0;
1834 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
1836 int max = MIN(view->width - view->col, len);
1840 col = draw_chars(view, type, text, max - 1, trim);
1842 col = draw_space(view, type, max - 1, max - 1);
1844 view->col += col + draw_space(view, LINE_DEFAULT, max - col, max - col);
1845 return view->width - view->col <= 0;
1849 draw_date(struct view *view, struct tm *time)
1851 char buf[DATE_COLS];
1856 timelen = strftime(buf, sizeof(buf), DATE_FORMAT, time);
1857 date = timelen ? buf : NULL;
1859 return draw_field(view, LINE_DATE, date, DATE_COLS, FALSE);
1863 draw_view_line(struct view *view, unsigned int lineno)
1866 bool selected = (view->offset + lineno == view->lineno);
1869 assert(view_is_displayed(view));
1871 if (view->offset + lineno >= view->lines)
1874 line = &view->line[view->offset + lineno];
1876 wmove(view->win, lineno, 0);
1878 view->curline = line;
1879 view->curtype = LINE_NONE;
1880 line->selected = FALSE;
1883 set_view_attr(view, LINE_CURSOR);
1884 line->selected = TRUE;
1885 view->ops->select(view, line);
1886 } else if (line->selected) {
1887 wclrtoeol(view->win);
1890 scrollok(view->win, FALSE);
1891 draw_ok = view->ops->draw(view, line, lineno);
1892 scrollok(view->win, TRUE);
1898 redraw_view_dirty(struct view *view)
1903 for (lineno = 0; lineno < view->height; lineno++) {
1904 struct line *line = &view->line[view->offset + lineno];
1910 if (!draw_view_line(view, lineno))
1916 redrawwin(view->win);
1918 wnoutrefresh(view->win);
1920 wrefresh(view->win);
1924 redraw_view_from(struct view *view, int lineno)
1926 assert(0 <= lineno && lineno < view->height);
1928 for (; lineno < view->height; lineno++) {
1929 if (!draw_view_line(view, lineno))
1933 redrawwin(view->win);
1935 wnoutrefresh(view->win);
1937 wrefresh(view->win);
1941 redraw_view(struct view *view)
1944 redraw_view_from(view, 0);
1949 update_view_title(struct view *view)
1951 char buf[SIZEOF_STR];
1952 char state[SIZEOF_STR];
1953 size_t bufpos = 0, statelen = 0;
1955 assert(view_is_displayed(view));
1957 if (view != VIEW(REQ_VIEW_STATUS) && (view->lines || view->pipe)) {
1958 unsigned int view_lines = view->offset + view->height;
1959 unsigned int lines = view->lines
1960 ? MIN(view_lines, view->lines) * 100 / view->lines
1963 string_format_from(state, &statelen, "- %s %d of %d (%d%%)",
1970 time_t secs = time(NULL) - view->start_time;
1972 /* Three git seconds are a long time ... */
1974 string_format_from(state, &statelen, " %lds", secs);
1978 string_format_from(buf, &bufpos, "[%s]", view->name);
1979 if (*view->ref && bufpos < view->width) {
1980 size_t refsize = strlen(view->ref);
1981 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
1983 if (minsize < view->width)
1984 refsize = view->width - minsize + 7;
1985 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
1988 if (statelen && bufpos < view->width) {
1989 string_format_from(buf, &bufpos, " %s", state);
1992 if (view == display[current_view])
1993 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
1995 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
1997 mvwaddnstr(view->title, 0, 0, buf, bufpos);
1998 wclrtoeol(view->title);
1999 wmove(view->title, 0, view->width - 1);
2002 wnoutrefresh(view->title);
2004 wrefresh(view->title);
2008 resize_display(void)
2011 struct view *base = display[0];
2012 struct view *view = display[1] ? display[1] : display[0];
2014 /* Setup window dimensions */
2016 getmaxyx(stdscr, base->height, base->width);
2018 /* Make room for the status window. */
2022 /* Horizontal split. */
2023 view->width = base->width;
2024 view->height = SCALE_SPLIT_VIEW(base->height);
2025 base->height -= view->height;
2027 /* Make room for the title bar. */
2031 /* Make room for the title bar. */
2036 foreach_displayed_view (view, i) {
2038 view->win = newwin(view->height, 0, offset, 0);
2040 die("Failed to create %s view", view->name);
2042 scrollok(view->win, TRUE);
2044 view->title = newwin(1, 0, offset + view->height, 0);
2046 die("Failed to create title window");
2049 wresize(view->win, view->height, view->width);
2050 mvwin(view->win, offset, 0);
2051 mvwin(view->title, offset + view->height, 0);
2054 offset += view->height + 1;
2059 redraw_display(void)
2064 foreach_displayed_view (view, i) {
2066 update_view_title(view);
2071 update_display_cursor(struct view *view)
2073 /* Move the cursor to the right-most column of the cursor line.
2075 * XXX: This could turn out to be a bit expensive, but it ensures that
2076 * the cursor does not jump around. */
2078 wmove(view->win, view->lineno - view->offset, view->width - 1);
2079 wrefresh(view->win);
2087 /* Scrolling backend */
2089 do_scroll_view(struct view *view, int lines)
2091 bool redraw_current_line = FALSE;
2093 /* The rendering expects the new offset. */
2094 view->offset += lines;
2096 assert(0 <= view->offset && view->offset < view->lines);
2099 /* Move current line into the view. */
2100 if (view->lineno < view->offset) {
2101 view->lineno = view->offset;
2102 redraw_current_line = TRUE;
2103 } else if (view->lineno >= view->offset + view->height) {
2104 view->lineno = view->offset + view->height - 1;
2105 redraw_current_line = TRUE;
2108 assert(view->offset <= view->lineno && view->lineno < view->lines);
2110 /* Redraw the whole screen if scrolling is pointless. */
2111 if (view->height < ABS(lines)) {
2115 int line = lines > 0 ? view->height - lines : 0;
2116 int end = line + ABS(lines);
2118 wscrl(view->win, lines);
2120 for (; line < end; line++) {
2121 if (!draw_view_line(view, line))
2125 if (redraw_current_line)
2126 draw_view_line(view, view->lineno - view->offset);
2129 redrawwin(view->win);
2130 wrefresh(view->win);
2134 /* Scroll frontend */
2136 scroll_view(struct view *view, enum request request)
2140 assert(view_is_displayed(view));
2143 case REQ_SCROLL_PAGE_DOWN:
2144 lines = view->height;
2145 case REQ_SCROLL_LINE_DOWN:
2146 if (view->offset + lines > view->lines)
2147 lines = view->lines - view->offset;
2149 if (lines == 0 || view->offset + view->height >= view->lines) {
2150 report("Cannot scroll beyond the last line");
2155 case REQ_SCROLL_PAGE_UP:
2156 lines = view->height;
2157 case REQ_SCROLL_LINE_UP:
2158 if (lines > view->offset)
2159 lines = view->offset;
2162 report("Cannot scroll beyond the first line");
2170 die("request %d not handled in switch", request);
2173 do_scroll_view(view, lines);
2178 move_view(struct view *view, enum request request)
2180 int scroll_steps = 0;
2184 case REQ_MOVE_FIRST_LINE:
2185 steps = -view->lineno;
2188 case REQ_MOVE_LAST_LINE:
2189 steps = view->lines - view->lineno - 1;
2192 case REQ_MOVE_PAGE_UP:
2193 steps = view->height > view->lineno
2194 ? -view->lineno : -view->height;
2197 case REQ_MOVE_PAGE_DOWN:
2198 steps = view->lineno + view->height >= view->lines
2199 ? view->lines - view->lineno - 1 : view->height;
2211 die("request %d not handled in switch", request);
2214 if (steps <= 0 && view->lineno == 0) {
2215 report("Cannot move beyond the first line");
2218 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2219 report("Cannot move beyond the last line");
2223 /* Move the current line */
2224 view->lineno += steps;
2225 assert(0 <= view->lineno && view->lineno < view->lines);
2227 /* Check whether the view needs to be scrolled */
2228 if (view->lineno < view->offset ||
2229 view->lineno >= view->offset + view->height) {
2230 scroll_steps = steps;
2231 if (steps < 0 && -steps > view->offset) {
2232 scroll_steps = -view->offset;
2234 } else if (steps > 0) {
2235 if (view->lineno == view->lines - 1 &&
2236 view->lines > view->height) {
2237 scroll_steps = view->lines - view->offset - 1;
2238 if (scroll_steps >= view->height)
2239 scroll_steps -= view->height - 1;
2244 if (!view_is_displayed(view)) {
2245 view->offset += scroll_steps;
2246 assert(0 <= view->offset && view->offset < view->lines);
2247 view->ops->select(view, &view->line[view->lineno]);
2251 /* Repaint the old "current" line if we be scrolling */
2252 if (ABS(steps) < view->height)
2253 draw_view_line(view, view->lineno - steps - view->offset);
2256 do_scroll_view(view, scroll_steps);
2260 /* Draw the current line */
2261 draw_view_line(view, view->lineno - view->offset);
2263 redrawwin(view->win);
2264 wrefresh(view->win);
2273 static void search_view(struct view *view, enum request request);
2276 find_next_line(struct view *view, unsigned long lineno, struct line *line)
2278 assert(view_is_displayed(view));
2280 if (!view->ops->grep(view, line))
2283 if (lineno - view->offset >= view->height) {
2284 view->offset = lineno;
2285 view->lineno = lineno;
2289 unsigned long old_lineno = view->lineno - view->offset;
2291 view->lineno = lineno;
2292 draw_view_line(view, old_lineno);
2294 draw_view_line(view, view->lineno - view->offset);
2295 redrawwin(view->win);
2296 wrefresh(view->win);
2299 report("Line %ld matches '%s'", lineno + 1, view->grep);
2304 find_next(struct view *view, enum request request)
2306 unsigned long lineno = view->lineno;
2311 report("No previous search");
2313 search_view(view, request);
2323 case REQ_SEARCH_BACK:
2332 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2333 lineno += direction;
2335 /* Note, lineno is unsigned long so will wrap around in which case it
2336 * will become bigger than view->lines. */
2337 for (; lineno < view->lines; lineno += direction) {
2338 struct line *line = &view->line[lineno];
2340 if (find_next_line(view, lineno, line))
2344 report("No match found for '%s'", view->grep);
2348 search_view(struct view *view, enum request request)
2353 regfree(view->regex);
2356 view->regex = calloc(1, sizeof(*view->regex));
2361 regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2362 if (regex_err != 0) {
2363 char buf[SIZEOF_STR] = "unknown error";
2365 regerror(regex_err, view->regex, buf, sizeof(buf));
2366 report("Search failed: %s", buf);
2370 string_copy(view->grep, opt_search);
2372 find_next(view, request);
2376 * Incremental updating
2380 reset_view(struct view *view)
2384 for (i = 0; i < view->lines; i++)
2385 free(view->line[i].data);
2392 view->line_size = 0;
2393 view->line_alloc = 0;
2398 free_argv(const char *argv[])
2402 for (argc = 0; argv[argc]; argc++)
2403 free((void *) argv[argc]);
2407 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
2409 char buf[SIZEOF_STR];
2411 bool noreplace = flags == FORMAT_NONE;
2413 free_argv(dst_argv);
2415 for (argc = 0; src_argv[argc]; argc++) {
2416 const char *arg = src_argv[argc];
2420 char *next = strstr(arg, "%(");
2421 int len = next - arg;
2424 if (!next || noreplace) {
2425 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
2430 } else if (!prefixcmp(next, "%(directory)")) {
2433 } else if (!prefixcmp(next, "%(file)")) {
2436 } else if (!prefixcmp(next, "%(ref)")) {
2437 value = *opt_ref ? opt_ref : "HEAD";
2439 } else if (!prefixcmp(next, "%(head)")) {
2442 } else if (!prefixcmp(next, "%(commit)")) {
2445 } else if (!prefixcmp(next, "%(blob)")) {
2449 report("Unknown replacement: `%s`", next);
2453 if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
2456 arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
2459 dst_argv[argc] = strdup(buf);
2460 if (!dst_argv[argc])
2464 dst_argv[argc] = NULL;
2466 return src_argv[argc] == NULL;
2470 format_command(char dst[], const char *src_argv[], enum format_flags flags)
2472 const char *dst_argv[SIZEOF_ARG * 2] = { NULL };
2476 if (!format_argv(dst_argv, src_argv, flags)) {
2477 free_argv(dst_argv);
2481 for (argc = 0; dst_argv[argc] && bufsize < SIZEOF_STR; argc++) {
2483 dst[bufsize++] = ' ';
2484 bufsize = sq_quote(dst, bufsize, dst_argv[argc]);
2487 if (bufsize < SIZEOF_STR)
2489 free_argv(dst_argv);
2491 return src_argv[argc] == NULL && bufsize < SIZEOF_STR;
2495 end_update(struct view *view, bool force)
2499 while (!view->ops->read(view, NULL))
2502 set_nonblocking_input(FALSE);
2503 done_io(view->pipe);
2508 setup_update(struct view *view, const char *vid)
2510 set_nonblocking_input(TRUE);
2512 string_copy_rev(view->vid, vid);
2513 view->pipe = &view->io;
2514 view->start_time = time(NULL);
2518 begin_update(struct view *view, bool refresh)
2520 if (init_io_fd(&view->io, opt_pipe)) {
2523 } else if (opt_cmd[0]) {
2524 if (!run_io(&view->io, IO_RD, opt_cmd))
2526 /* When running random commands, initially show the
2527 * command in the title. However, it maybe later be
2528 * overwritten if a commit line is selected. */
2529 if (view == VIEW(REQ_VIEW_PAGER))
2530 string_copy(view->ref, opt_cmd);
2535 } else if (refresh) {
2536 if (!start_io(&view->io))
2539 } else if (view == VIEW(REQ_VIEW_TREE)) {
2540 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
2541 char path[SIZEOF_STR];
2543 if (strcmp(view->vid, view->id))
2544 opt_path[0] = path[0] = 0;
2545 else if (sq_quote(path, 0, opt_path) >= sizeof(path))
2548 if (!run_io_format(&view->io, format, view->id, path))
2552 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
2553 const char *id = view->id;
2555 if (!run_io_format(&view->io, format, id, id, id, id, id))
2558 /* Put the current ref_* value to the view title ref
2559 * member. This is needed by the blob view. Most other
2560 * views sets it automatically after loading because the
2561 * first line is a commit line. */
2562 string_copy_rev(view->ref, view->id);
2565 setup_update(view, view->id);
2570 #define ITEM_CHUNK_SIZE 256
2572 realloc_items(void *mem, size_t *size, size_t new_size, size_t item_size)
2574 size_t num_chunks = *size / ITEM_CHUNK_SIZE;
2575 size_t num_chunks_new = (new_size + ITEM_CHUNK_SIZE - 1) / ITEM_CHUNK_SIZE;
2577 if (mem == NULL || num_chunks != num_chunks_new) {
2578 *size = num_chunks_new * ITEM_CHUNK_SIZE;
2579 mem = realloc(mem, *size * item_size);
2585 static struct line *
2586 realloc_lines(struct view *view, size_t line_size)
2588 size_t alloc = view->line_alloc;
2589 struct line *tmp = realloc_items(view->line, &alloc, line_size,
2590 sizeof(*view->line));
2596 view->line_alloc = alloc;
2597 view->line_size = line_size;
2602 update_view(struct view *view)
2604 char out_buffer[BUFSIZ * 2];
2606 /* The number of lines to read. If too low it will cause too much
2607 * redrawing (and possible flickering), if too high responsiveness
2609 unsigned long lines = view->height;
2610 int redraw_from = -1;
2615 /* Only redraw if lines are visible. */
2616 if (view->offset + view->height >= view->lines)
2617 redraw_from = view->lines - view->offset;
2619 /* FIXME: This is probably not perfect for backgrounded views. */
2620 if (!realloc_lines(view, view->lines + lines))
2623 while ((line = io_gets(view->pipe))) {
2624 size_t linelen = strlen(line);
2627 line[linelen - 1] = 0;
2629 if (opt_iconv != ICONV_NONE) {
2630 ICONV_CONST char *inbuf = line;
2631 size_t inlen = linelen;
2633 char *outbuf = out_buffer;
2634 size_t outlen = sizeof(out_buffer);
2638 ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2639 if (ret != (size_t) -1) {
2641 linelen = strlen(out_buffer);
2645 if (!view->ops->read(view, line))
2655 lines = view->lines;
2656 for (digits = 0; lines; digits++)
2659 /* Keep the displayed view in sync with line number scaling. */
2660 if (digits != view->digits) {
2661 view->digits = digits;
2666 if (io_error(view->pipe)) {
2667 report("Failed to read: %s", io_strerror(view->pipe));
2668 end_update(view, TRUE);
2670 } else if (io_eof(view->pipe)) {
2672 end_update(view, FALSE);
2675 if (!view_is_displayed(view))
2678 if (view == VIEW(REQ_VIEW_TREE)) {
2679 /* Clear the view and redraw everything since the tree sorting
2680 * might have rearranged things. */
2683 } else if (redraw_from >= 0) {
2684 /* If this is an incremental update, redraw the previous line
2685 * since for commits some members could have changed when
2686 * loading the main view. */
2687 if (redraw_from > 0)
2690 /* Since revision graph visualization requires knowledge
2691 * about the parent commit, it causes a further one-off
2692 * needed to be redrawn for incremental updates. */
2693 if (redraw_from > 0 && opt_rev_graph)
2696 /* Incrementally draw avoids flickering. */
2697 redraw_view_from(view, redraw_from);
2700 if (view == VIEW(REQ_VIEW_BLAME))
2701 redraw_view_dirty(view);
2703 /* Update the title _after_ the redraw so that if the redraw picks up a
2704 * commit reference in view->ref it'll be available here. */
2705 update_view_title(view);
2709 report("Allocation failure");
2710 end_update(view, TRUE);
2714 static struct line *
2715 add_line_data(struct view *view, void *data, enum line_type type)
2717 struct line *line = &view->line[view->lines++];
2719 memset(line, 0, sizeof(*line));
2726 static struct line *
2727 add_line_text(struct view *view, const char *text, enum line_type type)
2729 char *data = text ? strdup(text) : NULL;
2731 return data ? add_line_data(view, data, type) : NULL;
2740 OPEN_DEFAULT = 0, /* Use default view switching. */
2741 OPEN_SPLIT = 1, /* Split current view. */
2742 OPEN_BACKGROUNDED = 2, /* Backgrounded. */
2743 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
2744 OPEN_NOMAXIMIZE = 8, /* Do not maximize the current view. */
2745 OPEN_REFRESH = 16, /* Refresh view using previous command. */
2749 open_view(struct view *prev, enum request request, enum open_flags flags)
2751 bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
2752 bool split = !!(flags & OPEN_SPLIT);
2753 bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH));
2754 bool nomaximize = !!(flags & (OPEN_NOMAXIMIZE | OPEN_REFRESH));
2755 struct view *view = VIEW(request);
2756 int nviews = displayed_views();
2757 struct view *base_view = display[0];
2759 if (view == prev && nviews == 1 && !reload) {
2760 report("Already in %s view", view->name);
2764 if (view->git_dir && !opt_git_dir[0]) {
2765 report("The %s view is disabled in pager view", view->name);
2773 } else if (!nomaximize) {
2774 /* Maximize the current view. */
2775 memset(display, 0, sizeof(display));
2777 display[current_view] = view;
2780 /* Resize the view when switching between split- and full-screen,
2781 * or when switching between two different full-screen views. */
2782 if (nviews != displayed_views() ||
2783 (nviews == 1 && base_view != display[0]))
2787 end_update(view, TRUE);
2789 if (view->ops->open) {
2790 if (!view->ops->open(view)) {
2791 report("Failed to load %s view", view->name);
2795 } else if ((reload || strcmp(view->vid, view->id)) &&
2796 !begin_update(view, flags & OPEN_REFRESH)) {
2797 report("Failed to load %s view", view->name);
2801 if (split && prev->lineno - prev->offset >= prev->height) {
2802 /* Take the title line into account. */
2803 int lines = prev->lineno - prev->offset - prev->height + 1;
2805 /* Scroll the view that was split if the current line is
2806 * outside the new limited view. */
2807 do_scroll_view(prev, lines);
2810 if (prev && view != prev) {
2811 if (split && !backgrounded) {
2812 /* "Blur" the previous view. */
2813 update_view_title(prev);
2816 view->parent = prev;
2819 if (view->pipe && view->lines == 0) {
2820 /* Clear the old view and let the incremental updating refill
2824 } else if (view_is_displayed(view)) {
2829 /* If the view is backgrounded the above calls to report()
2830 * won't redraw the view title. */
2832 update_view_title(view);
2836 run_confirm(const char *cmd, const char *prompt)
2838 bool confirmation = prompt_yesno(prompt);
2843 return confirmation;
2847 open_external_viewer(const char *argv[], const char *dir)
2849 def_prog_mode(); /* save current tty modes */
2850 endwin(); /* restore original tty modes */
2851 run_io_fg(argv, dir);
2852 fprintf(stderr, "Press Enter to continue");
2859 open_mergetool(const char *file)
2861 const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
2863 open_external_viewer(mergetool_argv, NULL);
2867 open_editor(bool from_root, const char *file)
2869 const char *editor_argv[] = { "vi", file, NULL };
2872 editor = getenv("GIT_EDITOR");
2873 if (!editor && *opt_editor)
2874 editor = opt_editor;
2876 editor = getenv("VISUAL");
2878 editor = getenv("EDITOR");
2882 editor_argv[0] = editor;
2883 open_external_viewer(editor_argv, from_root ? opt_cdup : NULL);
2887 open_run_request(enum request request)
2889 struct run_request *req = get_run_request(request);
2890 const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
2893 report("Unknown run request");
2897 if (format_argv(argv, req->argv, FORMAT_ALL))
2898 open_external_viewer(argv, NULL);
2903 * User request switch noodle
2907 view_driver(struct view *view, enum request request)
2911 if (request == REQ_NONE) {
2916 if (request > REQ_NONE) {
2917 open_run_request(request);
2918 /* FIXME: When all views can refresh always do this. */
2919 if (view == VIEW(REQ_VIEW_STATUS) ||
2920 view == VIEW(REQ_VIEW_MAIN) ||
2921 view == VIEW(REQ_VIEW_LOG) ||
2922 view == VIEW(REQ_VIEW_STAGE))
2923 request = REQ_REFRESH;
2928 if (view && view->lines) {
2929 request = view->ops->request(view, request, &view->line[view->lineno]);
2930 if (request == REQ_NONE)
2937 case REQ_MOVE_PAGE_UP:
2938 case REQ_MOVE_PAGE_DOWN:
2939 case REQ_MOVE_FIRST_LINE:
2940 case REQ_MOVE_LAST_LINE:
2941 move_view(view, request);
2944 case REQ_SCROLL_LINE_DOWN:
2945 case REQ_SCROLL_LINE_UP:
2946 case REQ_SCROLL_PAGE_DOWN:
2947 case REQ_SCROLL_PAGE_UP:
2948 scroll_view(view, request);
2951 case REQ_VIEW_BLAME:
2953 report("No file chosen, press %s to open tree view",
2954 get_key(REQ_VIEW_TREE));
2957 open_view(view, request, OPEN_DEFAULT);
2962 report("No file chosen, press %s to open tree view",
2963 get_key(REQ_VIEW_TREE));
2966 open_view(view, request, OPEN_DEFAULT);
2969 case REQ_VIEW_PAGER:
2970 if (!opt_pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
2971 report("No pager content, press %s to run command from prompt",
2972 get_key(REQ_PROMPT));
2975 open_view(view, request, OPEN_DEFAULT);
2978 case REQ_VIEW_STAGE:
2979 if (!VIEW(REQ_VIEW_STAGE)->lines) {
2980 report("No stage content, press %s to open the status view and choose file",
2981 get_key(REQ_VIEW_STATUS));
2984 open_view(view, request, OPEN_DEFAULT);
2987 case REQ_VIEW_STATUS:
2988 if (opt_is_inside_work_tree == FALSE) {
2989 report("The status view requires a working tree");
2992 open_view(view, request, OPEN_DEFAULT);
3000 open_view(view, request, OPEN_DEFAULT);
3005 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3007 if ((view == VIEW(REQ_VIEW_DIFF) &&
3008 view->parent == VIEW(REQ_VIEW_MAIN)) ||
3009 (view == VIEW(REQ_VIEW_DIFF) &&
3010 view->parent == VIEW(REQ_VIEW_BLAME)) ||
3011 (view == VIEW(REQ_VIEW_STAGE) &&
3012 view->parent == VIEW(REQ_VIEW_STATUS)) ||
3013 (view == VIEW(REQ_VIEW_BLOB) &&
3014 view->parent == VIEW(REQ_VIEW_TREE))) {
3017 view = view->parent;
3018 line = view->lineno;
3019 move_view(view, request);
3020 if (view_is_displayed(view))
3021 update_view_title(view);
3022 if (line != view->lineno)
3023 view->ops->request(view, REQ_ENTER,
3024 &view->line[view->lineno]);
3027 move_view(view, request);
3033 int nviews = displayed_views();
3034 int next_view = (current_view + 1) % nviews;
3036 if (next_view == current_view) {
3037 report("Only one view is displayed");
3041 current_view = next_view;
3042 /* Blur out the title of the previous view. */
3043 update_view_title(view);
3048 report("Refreshing is not yet supported for the %s view", view->name);
3052 if (displayed_views() == 2)
3053 open_view(view, VIEW_REQ(view), OPEN_DEFAULT);
3056 case REQ_TOGGLE_LINENO:
3057 opt_line_number = !opt_line_number;
3061 case REQ_TOGGLE_DATE:
3062 opt_date = !opt_date;
3066 case REQ_TOGGLE_AUTHOR:
3067 opt_author = !opt_author;
3071 case REQ_TOGGLE_REV_GRAPH:
3072 opt_rev_graph = !opt_rev_graph;
3076 case REQ_TOGGLE_REFS:
3077 opt_show_refs = !opt_show_refs;
3082 case REQ_SEARCH_BACK:
3083 search_view(view, request);
3088 find_next(view, request);
3091 case REQ_STOP_LOADING:
3092 for (i = 0; i < ARRAY_SIZE(views); i++) {
3095 report("Stopped loading the %s view", view->name),
3096 end_update(view, TRUE);
3100 case REQ_SHOW_VERSION:
3101 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3104 case REQ_SCREEN_RESIZE:
3107 case REQ_SCREEN_REDRAW:
3112 report("Nothing to edit");
3116 report("Nothing to enter");
3119 case REQ_VIEW_CLOSE:
3120 /* XXX: Mark closed views by letting view->parent point to the
3121 * view itself. Parents to closed view should never be
3124 view->parent->parent != view->parent) {
3125 memset(display, 0, sizeof(display));
3127 display[current_view] = view->parent;
3128 view->parent = view;
3139 report("Unknown key, press 'h' for help");
3152 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3154 char *text = line->data;
3156 if (opt_line_number && draw_lineno(view, lineno))
3159 draw_text(view, line->type, text, TRUE);
3164 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3166 char refbuf[SIZEOF_STR];
3170 if (!string_format(refbuf, "git describe %s 2>/dev/null", commit_id))
3173 pipe = popen(refbuf, "r");
3177 if ((ref = fgets(refbuf, sizeof(refbuf), pipe)))
3178 ref = chomp_string(ref);
3184 /* This is the only fatal call, since it can "corrupt" the buffer. */
3185 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
3192 add_pager_refs(struct view *view, struct line *line)
3194 char buf[SIZEOF_STR];
3195 char *commit_id = (char *)line->data + STRING_SIZE("commit ");
3197 size_t bufpos = 0, refpos = 0;
3198 const char *sep = "Refs: ";
3199 bool is_tag = FALSE;
3201 assert(line->type == LINE_COMMIT);
3203 refs = get_refs(commit_id);
3205 if (view == VIEW(REQ_VIEW_DIFF))
3206 goto try_add_describe_ref;
3211 struct ref *ref = refs[refpos];
3212 const char *fmt = ref->tag ? "%s[%s]" :
3213 ref->remote ? "%s<%s>" : "%s%s";
3215 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
3220 } while (refs[refpos++]->next);
3222 if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
3223 try_add_describe_ref:
3224 /* Add <tag>-g<commit_id> "fake" reference. */
3225 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
3232 if (!realloc_lines(view, view->line_size + 1))
3235 add_line_text(view, buf, LINE_PP_REFS);
3239 pager_read(struct view *view, char *data)
3246 line = add_line_text(view, data, get_line_type(data));
3250 if (line->type == LINE_COMMIT &&
3251 (view == VIEW(REQ_VIEW_DIFF) ||
3252 view == VIEW(REQ_VIEW_LOG)))
3253 add_pager_refs(view, line);
3259 pager_request(struct view *view, enum request request, struct line *line)
3263 if (request != REQ_ENTER)
3266 if (line->type == LINE_COMMIT &&
3267 (view == VIEW(REQ_VIEW_LOG) ||
3268 view == VIEW(REQ_VIEW_PAGER))) {
3269 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3273 /* Always scroll the view even if it was split. That way
3274 * you can use Enter to scroll through the log view and
3275 * split open each commit diff. */
3276 scroll_view(view, REQ_SCROLL_LINE_DOWN);
3278 /* FIXME: A minor workaround. Scrolling the view will call report("")
3279 * but if we are scrolling a non-current view this won't properly
3280 * update the view title. */
3282 update_view_title(view);
3288 pager_grep(struct view *view, struct line *line)
3291 char *text = line->data;
3296 if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
3303 pager_select(struct view *view, struct line *line)
3305 if (line->type == LINE_COMMIT) {
3306 char *text = (char *)line->data + STRING_SIZE("commit ");
3308 if (view != VIEW(REQ_VIEW_PAGER))
3309 string_copy_rev(view->ref, text);
3310 string_copy_rev(ref_commit, text);
3314 static struct view_ops pager_ops = {
3325 log_request(struct view *view, enum request request, struct line *line)
3330 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
3333 return pager_request(view, request, line);
3337 static struct view_ops log_ops = {
3353 help_open(struct view *view)
3356 int lines = ARRAY_SIZE(req_info) + 2;
3359 if (view->lines > 0)
3362 for (i = 0; i < ARRAY_SIZE(req_info); i++)
3363 if (!req_info[i].request)
3366 lines += run_requests + 1;
3368 view->line = calloc(lines, sizeof(*view->line));
3372 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
3374 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
3377 if (req_info[i].request == REQ_NONE)
3380 if (!req_info[i].request) {
3381 add_line_text(view, "", LINE_DEFAULT);
3382 add_line_text(view, req_info[i].help, LINE_DEFAULT);
3386 key = get_key(req_info[i].request);
3388 key = "(no key defined)";
3390 if (!string_format(buf, " %-25s %s", key, req_info[i].help))
3393 add_line_text(view, buf, LINE_DEFAULT);
3397 add_line_text(view, "", LINE_DEFAULT);
3398 add_line_text(view, "External commands:", LINE_DEFAULT);
3401 for (i = 0; i < run_requests; i++) {
3402 struct run_request *req = get_run_request(REQ_NONE + i + 1);
3404 char cmd[SIZEOF_STR];
3411 key = get_key_name(req->key);
3413 key = "(no key defined)";
3415 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
3416 if (!string_format_from(cmd, &bufpos, "%s%s",
3417 argc ? " " : "", req->argv[argc]))
3420 if (!string_format(buf, " %-10s %-14s `%s`",
3421 keymap_table[req->keymap].name, key, cmd))
3424 add_line_text(view, buf, LINE_DEFAULT);
3430 static struct view_ops help_ops = {
3445 struct tree_stack_entry {
3446 struct tree_stack_entry *prev; /* Entry below this in the stack */
3447 unsigned long lineno; /* Line number to restore */
3448 char *name; /* Position of name in opt_path */
3451 /* The top of the path stack. */
3452 static struct tree_stack_entry *tree_stack = NULL;
3453 unsigned long tree_lineno = 0;
3456 pop_tree_stack_entry(void)
3458 struct tree_stack_entry *entry = tree_stack;
3460 tree_lineno = entry->lineno;
3462 tree_stack = entry->prev;
3467 push_tree_stack_entry(const char *name, unsigned long lineno)
3469 struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
3470 size_t pathlen = strlen(opt_path);
3475 entry->prev = tree_stack;
3476 entry->name = opt_path + pathlen;
3479 if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
3480 pop_tree_stack_entry();
3484 /* Move the current line to the first tree entry. */
3486 entry->lineno = lineno;
3489 /* Parse output from git-ls-tree(1):
3491 * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile
3492 * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README
3493 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
3494 * 100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38 web.conf
3497 #define SIZEOF_TREE_ATTR \
3498 STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t")
3500 #define TREE_UP_FORMAT "040000 tree %s\t.."
3503 tree_compare_entry(enum line_type type1, const char *name1,
3504 enum line_type type2, const char *name2)
3506 if (type1 != type2) {
3507 if (type1 == LINE_TREE_DIR)
3512 return strcmp(name1, name2);
3516 tree_path(struct line *line)
3518 const char *path = line->data;
3520 return path + SIZEOF_TREE_ATTR;
3524 tree_read(struct view *view, char *text)
3526 size_t textlen = text ? strlen(text) : 0;
3527 char buf[SIZEOF_STR];
3529 enum line_type type;
3530 bool first_read = view->lines == 0;
3534 if (textlen <= SIZEOF_TREE_ATTR)
3537 type = text[STRING_SIZE("100644 ")] == 't'
3538 ? LINE_TREE_DIR : LINE_TREE_FILE;
3541 /* Add path info line */
3542 if (!string_format(buf, "Directory path /%s", opt_path) ||
3543 !realloc_lines(view, view->line_size + 1) ||
3544 !add_line_text(view, buf, LINE_DEFAULT))
3547 /* Insert "link" to parent directory. */
3549 if (!string_format(buf, TREE_UP_FORMAT, view->ref) ||
3550 !realloc_lines(view, view->line_size + 1) ||
3551 !add_line_text(view, buf, LINE_TREE_DIR))
3556 /* Strip the path part ... */
3558 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
3559 size_t striplen = strlen(opt_path);
3560 char *path = text + SIZEOF_TREE_ATTR;
3562 if (pathlen > striplen)
3563 memmove(path, path + striplen,
3564 pathlen - striplen + 1);
3567 /* Skip "Directory ..." and ".." line. */
3568 for (pos = 1 + !!*opt_path; pos < view->lines; pos++) {
3569 struct line *line = &view->line[pos];
3570 const char *path1 = tree_path(line);
3571 char *path2 = text + SIZEOF_TREE_ATTR;
3572 int cmp = tree_compare_entry(line->type, path1, type, path2);
3577 text = strdup(text);
3581 if (view->lines > pos)
3582 memmove(&view->line[pos + 1], &view->line[pos],
3583 (view->lines - pos) * sizeof(*line));
3585 line = &view->line[pos];
3592 if (!add_line_text(view, text, type))
3595 if (tree_lineno > view->lineno) {
3596 view->lineno = tree_lineno;
3604 tree_request(struct view *view, enum request request, struct line *line)
3606 enum open_flags flags;
3609 case REQ_VIEW_BLAME:
3610 if (line->type != LINE_TREE_FILE) {
3611 report("Blame only supported for files");
3615 string_copy(opt_ref, view->vid);
3619 if (line->type != LINE_TREE_FILE) {
3620 report("Edit only supported for files");
3621 } else if (!is_head_commit(view->vid)) {
3622 report("Edit only supported for files in the current work tree");
3624 open_editor(TRUE, opt_file);
3628 case REQ_TREE_PARENT:
3630 /* quit view if at top of tree */
3631 return REQ_VIEW_CLOSE;
3634 line = &view->line[1];
3644 /* Cleanup the stack if the tree view is at a different tree. */
3645 while (!*opt_path && tree_stack)
3646 pop_tree_stack_entry();
3648 switch (line->type) {
3650 /* Depending on whether it is a subdir or parent (updir?) link
3651 * mangle the path buffer. */
3652 if (line == &view->line[1] && *opt_path) {
3653 pop_tree_stack_entry();
3656 const char *basename = tree_path(line);
3658 push_tree_stack_entry(basename, view->lineno);
3661 /* Trees and subtrees share the same ID, so they are not not
3662 * unique like blobs. */
3663 flags = OPEN_RELOAD;
3664 request = REQ_VIEW_TREE;
3667 case LINE_TREE_FILE:
3668 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
3669 request = REQ_VIEW_BLOB;
3676 open_view(view, request, flags);
3677 if (request == REQ_VIEW_TREE) {
3678 view->lineno = tree_lineno;
3685 tree_select(struct view *view, struct line *line)
3687 char *text = (char *)line->data + STRING_SIZE("100644 blob ");
3689 if (line->type == LINE_TREE_FILE) {
3690 string_copy_rev(ref_blob, text);
3691 string_format(opt_file, "%s%s", opt_path, tree_path(line));
3693 } else if (line->type != LINE_TREE_DIR) {
3697 string_copy_rev(view->ref, text);
3700 static struct view_ops tree_ops = {
3711 blob_read(struct view *view, char *line)
3715 return add_line_text(view, line, LINE_DEFAULT) != NULL;
3718 static struct view_ops blob_ops = {
3731 * Loading the blame view is a two phase job:
3733 * 1. File content is read either using opt_file from the
3734 * filesystem or using git-cat-file.
3735 * 2. Then blame information is incrementally added by
3736 * reading output from git-blame.
3739 struct blame_commit {
3740 char id[SIZEOF_REV]; /* SHA1 ID. */
3741 char title[128]; /* First line of the commit message. */
3742 char author[75]; /* Author of the commit. */
3743 struct tm time; /* Date from the author ident. */
3744 char filename[128]; /* Name of file. */
3748 struct blame_commit *commit;
3752 #define BLAME_CAT_FILE_CMD "git cat-file blob %s:%s"
3753 #define BLAME_INCREMENTAL_CMD "git blame --incremental %s -- %s"
3756 blame_open(struct view *view)
3758 char path[SIZEOF_STR];
3759 char ref[SIZEOF_STR] = "";
3761 if (sq_quote(path, 0, opt_file) >= sizeof(path))
3764 if (*opt_ref && sq_quote(ref, 0, opt_ref) >= sizeof(ref))
3767 if (*opt_ref || !init_io_fd(&view->io, fopen(opt_file, "r"))) {
3768 const char *id = *opt_ref ? ref : "HEAD";
3770 if (!run_io_format(&view->io, BLAME_CAT_FILE_CMD, id, path))
3774 setup_update(view, opt_file);
3775 string_format(view->ref, "%s ...", opt_file);
3780 static struct blame_commit *
3781 get_blame_commit(struct view *view, const char *id)
3785 for (i = 0; i < view->lines; i++) {
3786 struct blame *blame = view->line[i].data;
3791 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
3792 return blame->commit;
3796 struct blame_commit *commit = calloc(1, sizeof(*commit));
3799 string_ncopy(commit->id, id, SIZEOF_REV);
3805 parse_number(const char **posref, size_t *number, size_t min, size_t max)
3807 const char *pos = *posref;
3810 pos = strchr(pos + 1, ' ');
3811 if (!pos || !isdigit(pos[1]))
3813 *number = atoi(pos + 1);
3814 if (*number < min || *number > max)
3821 static struct blame_commit *
3822 parse_blame_commit(struct view *view, const char *text, int *blamed)
3824 struct blame_commit *commit;
3825 struct blame *blame;
3826 const char *pos = text + SIZEOF_REV - 1;
3830 if (strlen(text) <= SIZEOF_REV || *pos != ' ')
3833 if (!parse_number(&pos, &lineno, 1, view->lines) ||
3834 !parse_number(&pos, &group, 1, view->lines - lineno + 1))
3837 commit = get_blame_commit(view, text);
3843 struct line *line = &view->line[lineno + group - 1];
3846 blame->commit = commit;
3854 blame_read_file(struct view *view, const char *line, bool *read_file)
3857 char ref[SIZEOF_STR] = "";
3858 char path[SIZEOF_STR];
3861 if (view->lines == 0 && !view->parent)
3862 die("No blame exist for %s", view->vid);
3864 if (view->lines == 0 ||
3865 sq_quote(path, 0, opt_file) >= sizeof(path) ||
3866 (*opt_ref && sq_quote(ref, 0, opt_ref) >= sizeof(ref)) ||
3867 !run_io_format(&io, BLAME_INCREMENTAL_CMD, ref, path)) {
3868 report("Failed to load blame data");
3872 done_io(view->pipe);
3878 size_t linelen = strlen(line);
3879 struct blame *blame = malloc(sizeof(*blame) + linelen);
3881 blame->commit = NULL;
3882 strncpy(blame->text, line, linelen);
3883 blame->text[linelen] = 0;
3884 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
3889 match_blame_header(const char *name, char **line)
3891 size_t namelen = strlen(name);
3892 bool matched = !strncmp(name, *line, namelen);
3901 blame_read(struct view *view, char *line)
3903 static struct blame_commit *commit = NULL;
3904 static int blamed = 0;
3905 static time_t author_time;
3906 static bool read_file = TRUE;
3909 return blame_read_file(view, line, &read_file);
3916 string_format(view->ref, "%s", view->vid);
3917 if (view_is_displayed(view)) {
3918 update_view_title(view);
3919 redraw_view_from(view, 0);
3925 commit = parse_blame_commit(view, line, &blamed);
3926 string_format(view->ref, "%s %2d%%", view->vid,
3927 blamed * 100 / view->lines);
3929 } else if (match_blame_header("author ", &line)) {
3930 string_ncopy(commit->author, line, strlen(line));
3932 } else if (match_blame_header("author-time ", &line)) {
3933 author_time = (time_t) atol(line);
3935 } else if (match_blame_header("author-tz ", &line)) {
3938 tz = ('0' - line[1]) * 60 * 60 * 10;
3939 tz += ('0' - line[2]) * 60 * 60;
3940 tz += ('0' - line[3]) * 60;
3941 tz += ('0' - line[4]) * 60;
3947 gmtime_r(&author_time, &commit->time);
3949 } else if (match_blame_header("summary ", &line)) {
3950 string_ncopy(commit->title, line, strlen(line));
3952 } else if (match_blame_header("filename ", &line)) {
3953 string_ncopy(commit->filename, line, strlen(line));
3961 blame_draw(struct view *view, struct line *line, unsigned int lineno)
3963 struct blame *blame = line->data;
3964 struct tm *time = NULL;
3965 const char *id = NULL, *author = NULL;
3967 if (blame->commit && *blame->commit->filename) {
3968 id = blame->commit->id;
3969 author = blame->commit->author;
3970 time = &blame->commit->time;
3973 if (opt_date && draw_date(view, time))
3977 draw_field(view, LINE_MAIN_AUTHOR, author, opt_author_cols, TRUE))
3980 if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
3983 if (draw_lineno(view, lineno))
3986 draw_text(view, LINE_DEFAULT, blame->text, TRUE);
3991 blame_request(struct view *view, enum request request, struct line *line)
3993 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
3994 struct blame *blame = line->data;
3997 case REQ_VIEW_BLAME:
3998 if (!blame->commit || !strcmp(blame->commit->id, NULL_ID)) {
3999 report("Commit ID unknown");
4002 string_copy(opt_ref, blame->commit->id);
4003 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4007 if (!blame->commit) {
4008 report("No commit loaded yet");
4012 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
4013 !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
4016 if (!strcmp(blame->commit->id, NULL_ID)) {
4017 char path[SIZEOF_STR];
4019 if (sq_quote(path, 0, view->vid) >= sizeof(path))
4021 string_format(opt_cmd, "git diff-index --root --patch-with-stat -C -M --cached HEAD -- %s 2>/dev/null", path);
4024 open_view(view, REQ_VIEW_DIFF, flags);
4035 blame_grep(struct view *view, struct line *line)
4037 struct blame *blame = line->data;
4038 struct blame_commit *commit = blame->commit;
4041 #define MATCH(text, on) \
4042 (on && *text && regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4045 char buf[DATE_COLS + 1];
4047 if (MATCH(commit->title, 1) ||
4048 MATCH(commit->author, opt_author) ||
4049 MATCH(commit->id, opt_date))
4052 if (strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time) &&
4057 return MATCH(blame->text, 1);
4063 blame_select(struct view *view, struct line *line)
4065 struct blame *blame = line->data;
4066 struct blame_commit *commit = blame->commit;
4071 if (!strcmp(commit->id, NULL_ID))
4072 string_ncopy(ref_commit, "HEAD", 4);
4074 string_copy_rev(ref_commit, commit->id);
4077 static struct view_ops blame_ops = {
4095 char rev[SIZEOF_REV];
4096 char name[SIZEOF_STR];
4100 char rev[SIZEOF_REV];
4101 char name[SIZEOF_STR];
4105 static char status_onbranch[SIZEOF_STR];
4106 static struct status stage_status;
4107 static enum line_type stage_line_type;
4108 static size_t stage_chunks;
4109 static int *stage_chunk;
4111 /* This should work even for the "On branch" line. */
4113 status_has_none(struct view *view, struct line *line)
4115 return line < view->line + view->lines && !line[1].data;
4118 /* Get fields from the diff line:
4119 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
4122 status_get_diff(struct status *file, const char *buf, size_t bufsize)
4124 const char *old_mode = buf + 1;
4125 const char *new_mode = buf + 8;
4126 const char *old_rev = buf + 15;
4127 const char *new_rev = buf + 56;
4128 const char *status = buf + 97;
4131 old_mode[-1] != ':' ||
4132 new_mode[-1] != ' ' ||
4133 old_rev[-1] != ' ' ||
4134 new_rev[-1] != ' ' ||
4138 file->status = *status;
4140 string_copy_rev(file->old.rev, old_rev);
4141 string_copy_rev(file->new.rev, new_rev);
4143 file->old.mode = strtoul(old_mode, NULL, 8);
4144 file->new.mode = strtoul(new_mode, NULL, 8);
4146 file->old.name[0] = file->new.name[0] = 0;
4152 status_run(struct view *view, const char cmd[], char status, enum line_type type)
4154 struct status *file = NULL;
4155 struct status *unmerged = NULL;
4156 char buf[SIZEOF_STR * 4];
4160 pipe = popen(cmd, "r");
4164 add_line_data(view, NULL, type);
4166 while (!feof(pipe) && !ferror(pipe)) {
4170 readsize = fread(buf + bufsize, 1, sizeof(buf) - bufsize, pipe);
4173 bufsize += readsize;
4175 /* Process while we have NUL chars. */
4176 while ((sep = memchr(buf, 0, bufsize))) {
4177 size_t sepsize = sep - buf + 1;
4180 if (!realloc_lines(view, view->line_size + 1))
4183 file = calloc(1, sizeof(*file));
4187 add_line_data(view, file, type);
4190 /* Parse diff info part. */
4192 file->status = status;
4194 string_copy(file->old.rev, NULL_ID);
4196 } else if (!file->status) {
4197 if (!status_get_diff(file, buf, sepsize))
4201 memmove(buf, sep + 1, bufsize);
4203 sep = memchr(buf, 0, bufsize);
4206 sepsize = sep - buf + 1;
4208 /* Collapse all 'M'odified entries that
4209 * follow a associated 'U'nmerged entry.
4211 if (file->status == 'U') {
4214 } else if (unmerged) {
4215 int collapse = !strcmp(buf, unmerged->new.name);
4226 /* Grab the old name for rename/copy. */
4227 if (!*file->old.name &&
4228 (file->status == 'R' || file->status == 'C')) {
4229 sepsize = sep - buf + 1;
4230 string_ncopy(file->old.name, buf, sepsize);
4232 memmove(buf, sep + 1, bufsize);
4234 sep = memchr(buf, 0, bufsize);
4237 sepsize = sep - buf + 1;
4240 /* git-ls-files just delivers a NUL separated
4241 * list of file names similar to the second half
4242 * of the git-diff-* output. */
4243 string_ncopy(file->new.name, buf, sepsize);
4244 if (!*file->old.name)
4245 string_copy(file->old.name, file->new.name);
4247 memmove(buf, sep + 1, bufsize);
4258 if (!view->line[view->lines - 1].data)
4259 add_line_data(view, NULL, LINE_STAT_NONE);
4265 /* Don't show unmerged entries in the staged section. */
4266 #define STATUS_DIFF_INDEX_CMD "git diff-index -z --diff-filter=ACDMRTXB --cached -M HEAD"
4267 #define STATUS_DIFF_FILES_CMD "git diff-files -z"
4268 #define STATUS_LIST_OTHER_CMD \
4269 "git ls-files -z --others --exclude-standard"
4270 #define STATUS_LIST_NO_HEAD_CMD \
4271 "git ls-files -z --cached --exclude-standard"
4273 #define STATUS_DIFF_INDEX_SHOW_CMD \
4274 "git diff-index --root --patch-with-stat -C -M --cached HEAD -- %s %s 2>/dev/null"
4276 #define STATUS_DIFF_FILES_SHOW_CMD \
4277 "git diff-files --root --patch-with-stat -C -M -- %s %s 2>/dev/null"
4279 #define STATUS_DIFF_NO_HEAD_SHOW_CMD \
4280 "git diff --no-color --patch-with-stat /dev/null %s 2>/dev/null"
4282 /* First parse staged info using git-diff-index(1), then parse unstaged
4283 * info using git-diff-files(1), and finally untracked files using
4284 * git-ls-files(1). */
4286 status_open(struct view *view)
4288 unsigned long prev_lineno = view->lineno;
4292 if (!realloc_lines(view, view->line_size + 7))
4295 add_line_data(view, NULL, LINE_STAT_HEAD);
4296 if (is_initial_commit())
4297 string_copy(status_onbranch, "Initial commit");
4298 else if (!*opt_head)
4299 string_copy(status_onbranch, "Not currently on any branch");
4300 else if (!string_format(status_onbranch, "On branch %s", opt_head))
4303 system("git update-index -q --refresh >/dev/null 2>/dev/null");
4305 if (is_initial_commit()) {
4306 if (!status_run(view, STATUS_LIST_NO_HEAD_CMD, 'A', LINE_STAT_STAGED))
4308 } else if (!status_run(view, STATUS_DIFF_INDEX_CMD, 0, LINE_STAT_STAGED)) {
4312 if (!status_run(view, STATUS_DIFF_FILES_CMD, 0, LINE_STAT_UNSTAGED) ||
4313 !status_run(view, STATUS_LIST_OTHER_CMD, '?', LINE_STAT_UNTRACKED))
4316 /* If all went well restore the previous line number to stay in
4317 * the context or select a line with something that can be
4319 if (prev_lineno >= view->lines)
4320 prev_lineno = view->lines - 1;
4321 while (prev_lineno < view->lines && !view->line[prev_lineno].data)
4323 while (prev_lineno > 0 && !view->line[prev_lineno].data)
4326 /* If the above fails, always skip the "On branch" line. */
4327 if (prev_lineno < view->lines)
4328 view->lineno = prev_lineno;
4332 if (view->lineno < view->offset)
4333 view->offset = view->lineno;
4334 else if (view->offset + view->height <= view->lineno)
4335 view->offset = view->lineno - view->height + 1;
4341 status_draw(struct view *view, struct line *line, unsigned int lineno)
4343 struct status *status = line->data;
4344 enum line_type type;
4348 switch (line->type) {
4349 case LINE_STAT_STAGED:
4350 type = LINE_STAT_SECTION;
4351 text = "Changes to be committed:";
4354 case LINE_STAT_UNSTAGED:
4355 type = LINE_STAT_SECTION;
4356 text = "Changed but not updated:";
4359 case LINE_STAT_UNTRACKED:
4360 type = LINE_STAT_SECTION;
4361 text = "Untracked files:";
4364 case LINE_STAT_NONE:
4365 type = LINE_DEFAULT;
4366 text = " (no files)";
4369 case LINE_STAT_HEAD:
4370 type = LINE_STAT_HEAD;
4371 text = status_onbranch;
4378 static char buf[] = { '?', ' ', ' ', ' ', 0 };
4380 buf[0] = status->status;
4381 if (draw_text(view, line->type, buf, TRUE))
4383 type = LINE_DEFAULT;
4384 text = status->new.name;
4387 draw_text(view, type, text, TRUE);
4392 status_enter(struct view *view, struct line *line)
4394 struct status *status = line->data;
4395 char oldpath[SIZEOF_STR] = "";
4396 char newpath[SIZEOF_STR] = "";
4399 enum open_flags split;
4401 if (line->type == LINE_STAT_NONE ||
4402 (!status && line[1].type == LINE_STAT_NONE)) {
4403 report("No file to diff");
4408 if (sq_quote(oldpath, 0, status->old.name) >= sizeof(oldpath))
4410 /* Diffs for unmerged entries are empty when pasing the
4411 * new path, so leave it empty. */
4412 if (status->status != 'U' &&
4413 sq_quote(newpath, 0, status->new.name) >= sizeof(newpath))
4418 line->type != LINE_STAT_UNTRACKED &&
4419 !string_format_from(opt_cmd, &cmdsize, "cd %s;", opt_cdup))
4422 switch (line->type) {
4423 case LINE_STAT_STAGED:
4424 if (is_initial_commit()) {
4425 if (!string_format_from(opt_cmd, &cmdsize,
4426 STATUS_DIFF_NO_HEAD_SHOW_CMD,
4430 if (!string_format_from(opt_cmd, &cmdsize,
4431 STATUS_DIFF_INDEX_SHOW_CMD,
4437 info = "Staged changes to %s";
4439 info = "Staged changes";
4442 case LINE_STAT_UNSTAGED:
4443 if (!string_format_from(opt_cmd, &cmdsize,
4444 STATUS_DIFF_FILES_SHOW_CMD, oldpath, newpath))
4447 info = "Unstaged changes to %s";
4449 info = "Unstaged changes";
4452 case LINE_STAT_UNTRACKED:
4457 report("No file to show");
4461 if (!suffixcmp(status->new.name, -1, "/")) {
4462 report("Cannot display a directory");
4466 opt_pipe = fopen(status->new.name, "r");
4467 info = "Untracked file %s";
4470 case LINE_STAT_HEAD:
4474 die("line type %d not handled in switch", line->type);
4477 split = view_is_displayed(view) ? OPEN_SPLIT : 0;
4478 open_view(view, REQ_VIEW_STAGE, OPEN_RELOAD | split);
4479 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
4481 stage_status = *status;
4483 memset(&stage_status, 0, sizeof(stage_status));
4486 stage_line_type = line->type;
4488 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
4495 status_exists(struct status *status, enum line_type type)
4497 struct view *view = VIEW(REQ_VIEW_STATUS);
4500 for (line = view->line; line < view->line + view->lines; line++) {
4501 struct status *pos = line->data;
4503 if (line->type == type && pos &&
4504 !strcmp(status->new.name, pos->new.name))
4513 status_update_prepare(enum line_type type)
4515 char cmd[SIZEOF_STR];
4519 type != LINE_STAT_UNTRACKED &&
4520 !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
4524 case LINE_STAT_STAGED:
4525 string_add(cmd, cmdsize, "git update-index -z --index-info");
4528 case LINE_STAT_UNSTAGED:
4529 case LINE_STAT_UNTRACKED:
4530 string_add(cmd, cmdsize, "git update-index -z --add --remove --stdin");
4534 die("line type %d not handled in switch", type);
4537 return popen(cmd, "w");
4541 status_update_write(FILE *pipe, struct status *status, enum line_type type)
4543 char buf[SIZEOF_STR];
4548 case LINE_STAT_STAGED:
4549 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
4552 status->old.name, 0))
4556 case LINE_STAT_UNSTAGED:
4557 case LINE_STAT_UNTRACKED:
4558 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
4563 die("line type %d not handled in switch", type);
4566 while (!ferror(pipe) && written < bufsize) {
4567 written += fwrite(buf + written, 1, bufsize - written, pipe);
4570 return written == bufsize;
4574 status_update_file(struct status *status, enum line_type type)
4576 FILE *pipe = status_update_prepare(type);
4582 result = status_update_write(pipe, status, type);
4588 status_update_files(struct view *view, struct line *line)
4590 FILE *pipe = status_update_prepare(line->type);
4592 struct line *pos = view->line + view->lines;
4599 for (pos = line; pos < view->line + view->lines && pos->data; pos++)
4602 for (file = 0, done = 0; result && file < files; line++, file++) {
4603 int almost_done = file * 100 / files;
4605 if (almost_done > done) {
4607 string_format(view->ref, "updating file %u of %u (%d%% done)",
4609 update_view_title(view);
4611 result = status_update_write(pipe, line->data, line->type);
4619 status_update(struct view *view)
4621 struct line *line = &view->line[view->lineno];
4623 assert(view->lines);
4626 /* This should work even for the "On branch" line. */
4627 if (line < view->line + view->lines && !line[1].data) {
4628 report("Nothing to update");
4632 if (!status_update_files(view, line + 1)) {
4633 report("Failed to update file status");
4637 } else if (!status_update_file(line->data, line->type)) {
4638 report("Failed to update file status");
4646 status_revert(struct status *status, enum line_type type, bool has_none)
4648 if (!status || type != LINE_STAT_UNSTAGED) {
4649 if (type == LINE_STAT_STAGED) {
4650 report("Cannot revert changes to staged files");
4651 } else if (type == LINE_STAT_UNTRACKED) {
4652 report("Cannot revert changes to untracked files");
4653 } else if (has_none) {
4654 report("Nothing to revert");
4656 report("Cannot revert changes to multiple files");
4661 char cmd[SIZEOF_STR];
4662 char file_sq[SIZEOF_STR];
4664 if (sq_quote(file_sq, 0, status->old.name) >= sizeof(file_sq) ||
4665 !string_format(cmd, "git checkout -- %s%s", opt_cdup, file_sq))
4668 return run_confirm(cmd, "Are you sure you want to overwrite any changes?");
4673 status_request(struct view *view, enum request request, struct line *line)
4675 struct status *status = line->data;
4678 case REQ_STATUS_UPDATE:
4679 if (!status_update(view))
4683 case REQ_STATUS_REVERT:
4684 if (!status_revert(status, line->type, status_has_none(view, line)))
4688 case REQ_STATUS_MERGE:
4689 if (!status || status->status != 'U') {
4690 report("Merging only possible for files with unmerged status ('U').");
4693 open_mergetool(status->new.name);
4699 if (status->status == 'D') {
4700 report("File has been deleted.");
4704 open_editor(status->status != '?', status->new.name);
4707 case REQ_VIEW_BLAME:
4709 string_copy(opt_file, status->new.name);
4715 /* After returning the status view has been split to
4716 * show the stage view. No further reloading is
4718 status_enter(view, line);
4722 /* Simply reload the view. */
4729 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
4735 status_select(struct view *view, struct line *line)
4737 struct status *status = line->data;
4738 char file[SIZEOF_STR] = "all files";
4742 if (status && !string_format(file, "'%s'", status->new.name))
4745 if (!status && line[1].type == LINE_STAT_NONE)
4748 switch (line->type) {
4749 case LINE_STAT_STAGED:
4750 text = "Press %s to unstage %s for commit";
4753 case LINE_STAT_UNSTAGED:
4754 text = "Press %s to stage %s for commit";
4757 case LINE_STAT_UNTRACKED:
4758 text = "Press %s to stage %s for addition";
4761 case LINE_STAT_HEAD:
4762 case LINE_STAT_NONE:
4763 text = "Nothing to update";
4767 die("line type %d not handled in switch", line->type);
4770 if (status && status->status == 'U') {
4771 text = "Press %s to resolve conflict in %s";
4772 key = get_key(REQ_STATUS_MERGE);
4775 key = get_key(REQ_STATUS_UPDATE);
4778 string_format(view->ref, text, key, file);
4782 status_grep(struct view *view, struct line *line)
4784 struct status *status = line->data;
4785 enum { S_STATUS, S_NAME, S_END } state;
4792 for (state = S_STATUS; state < S_END; state++) {
4796 case S_NAME: text = status->new.name; break;
4798 buf[0] = status->status;
4806 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4813 static struct view_ops status_ops = {
4825 stage_diff_line(FILE *pipe, struct line *line)
4827 const char *buf = line->data;
4828 size_t bufsize = strlen(buf);
4831 while (!ferror(pipe) && written < bufsize) {
4832 written += fwrite(buf + written, 1, bufsize - written, pipe);
4837 return written == bufsize;
4841 stage_diff_write(FILE *pipe, struct line *line, struct line *end)
4843 while (line < end) {
4844 if (!stage_diff_line(pipe, line++))
4846 if (line->type == LINE_DIFF_CHUNK ||
4847 line->type == LINE_DIFF_HEADER)
4854 static struct line *
4855 stage_diff_find(struct view *view, struct line *line, enum line_type type)
4857 for (; view->line < line; line--)
4858 if (line->type == type)
4865 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
4867 char cmd[SIZEOF_STR];
4869 struct line *diff_hdr;
4872 diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
4877 !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
4880 if (!string_format_from(cmd, &cmdsize,
4881 "git apply --whitespace=nowarn %s %s - && "
4882 "git update-index -q --unmerged --refresh 2>/dev/null",
4883 revert ? "" : "--cached",
4884 revert || stage_line_type == LINE_STAT_STAGED ? "-R" : ""))
4887 pipe = popen(cmd, "w");
4891 if (!stage_diff_write(pipe, diff_hdr, chunk) ||
4892 !stage_diff_write(pipe, chunk, view->line + view->lines))
4897 return chunk ? TRUE : FALSE;
4901 stage_update(struct view *view, struct line *line)
4903 struct line *chunk = NULL;
4905 if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
4906 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
4909 if (!stage_apply_chunk(view, chunk, FALSE)) {
4910 report("Failed to apply chunk");
4914 } else if (!stage_status.status) {
4915 view = VIEW(REQ_VIEW_STATUS);
4917 for (line = view->line; line < view->line + view->lines; line++)
4918 if (line->type == stage_line_type)
4921 if (!status_update_files(view, line + 1)) {
4922 report("Failed to update files");
4926 } else if (!status_update_file(&stage_status, stage_line_type)) {
4927 report("Failed to update file");
4935 stage_revert(struct view *view, struct line *line)
4937 struct line *chunk = NULL;
4939 if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
4940 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
4943 if (!prompt_yesno("Are you sure you want to revert changes?"))
4946 if (!stage_apply_chunk(view, chunk, TRUE)) {
4947 report("Failed to revert chunk");
4953 return status_revert(stage_status.status ? &stage_status : NULL,
4954 stage_line_type, FALSE);
4960 stage_next(struct view *view, struct line *line)
4964 if (!stage_chunks) {
4965 static size_t alloc = 0;
4968 for (line = view->line; line < view->line + view->lines; line++) {
4969 if (line->type != LINE_DIFF_CHUNK)
4972 tmp = realloc_items(stage_chunk, &alloc,
4973 stage_chunks, sizeof(*tmp));
4975 report("Allocation failure");
4980 stage_chunk[stage_chunks++] = line - view->line;
4984 for (i = 0; i < stage_chunks; i++) {
4985 if (stage_chunk[i] > view->lineno) {
4986 do_scroll_view(view, stage_chunk[i] - view->lineno);
4987 report("Chunk %d of %d", i + 1, stage_chunks);
4992 report("No next chunk found");
4996 stage_request(struct view *view, enum request request, struct line *line)
4999 case REQ_STATUS_UPDATE:
5000 if (!stage_update(view, line))
5004 case REQ_STATUS_REVERT:
5005 if (!stage_revert(view, line))
5009 case REQ_STAGE_NEXT:
5010 if (stage_line_type == LINE_STAT_UNTRACKED) {
5011 report("File is untracked; press %s to add",
5012 get_key(REQ_STATUS_UPDATE));
5015 stage_next(view, line);
5019 if (!stage_status.new.name[0])
5021 if (stage_status.status == 'D') {
5022 report("File has been deleted.");
5026 open_editor(stage_status.status != '?', stage_status.new.name);
5030 /* Reload everything ... */
5033 case REQ_VIEW_BLAME:
5034 if (stage_status.new.name[0]) {
5035 string_copy(opt_file, stage_status.new.name);
5041 return pager_request(view, request, line);
5047 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD | OPEN_NOMAXIMIZE);
5049 /* Check whether the staged entry still exists, and close the
5050 * stage view if it doesn't. */
5051 if (!status_exists(&stage_status, stage_line_type))
5052 return REQ_VIEW_CLOSE;
5054 if (stage_line_type == LINE_STAT_UNTRACKED) {
5055 if (!suffixcmp(stage_status.new.name, -1, "/")) {
5056 report("Cannot display a directory");
5060 opt_pipe = fopen(stage_status.new.name, "r");
5062 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
5067 static struct view_ops stage_ops = {
5083 char id[SIZEOF_REV]; /* SHA1 ID. */
5084 char title[128]; /* First line of the commit message. */
5085 char author[75]; /* Author of the commit. */
5086 struct tm time; /* Date from the author ident. */
5087 struct ref **refs; /* Repository references. */
5088 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
5089 size_t graph_size; /* The width of the graph array. */
5090 bool has_parents; /* Rewritten --parents seen. */
5093 /* Size of rev graph with no "padding" columns */
5094 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
5097 struct rev_graph *prev, *next, *parents;
5098 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
5100 struct commit *commit;
5102 unsigned int boundary:1;
5105 /* Parents of the commit being visualized. */
5106 static struct rev_graph graph_parents[4];
5108 /* The current stack of revisions on the graph. */
5109 static struct rev_graph graph_stacks[4] = {
5110 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
5111 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
5112 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
5113 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
5117 graph_parent_is_merge(struct rev_graph *graph)
5119 return graph->parents->size > 1;
5123 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
5125 struct commit *commit = graph->commit;
5127 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
5128 commit->graph[commit->graph_size++] = symbol;
5132 clear_rev_graph(struct rev_graph *graph)
5134 graph->boundary = 0;
5135 graph->size = graph->pos = 0;
5136 graph->commit = NULL;
5137 memset(graph->parents, 0, sizeof(*graph->parents));
5141 done_rev_graph(struct rev_graph *graph)
5143 if (graph_parent_is_merge(graph) &&
5144 graph->pos < graph->size - 1 &&
5145 graph->next->size == graph->size + graph->parents->size - 1) {
5146 size_t i = graph->pos + graph->parents->size - 1;
5148 graph->commit->graph_size = i * 2;
5149 while (i < graph->next->size - 1) {
5150 append_to_rev_graph(graph, ' ');
5151 append_to_rev_graph(graph, '\\');
5156 clear_rev_graph(graph);
5160 push_rev_graph(struct rev_graph *graph, const char *parent)
5164 /* "Collapse" duplicate parents lines.
5166 * FIXME: This needs to also update update the drawn graph but
5167 * for now it just serves as a method for pruning graph lines. */
5168 for (i = 0; i < graph->size; i++)
5169 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
5172 if (graph->size < SIZEOF_REVITEMS) {
5173 string_copy_rev(graph->rev[graph->size++], parent);
5178 get_rev_graph_symbol(struct rev_graph *graph)
5182 if (graph->boundary)
5183 symbol = REVGRAPH_BOUND;
5184 else if (graph->parents->size == 0)
5185 symbol = REVGRAPH_INIT;
5186 else if (graph_parent_is_merge(graph))
5187 symbol = REVGRAPH_MERGE;
5188 else if (graph->pos >= graph->size)
5189 symbol = REVGRAPH_BRANCH;
5191 symbol = REVGRAPH_COMMIT;
5197 draw_rev_graph(struct rev_graph *graph)
5200 chtype separator, line;
5202 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
5203 static struct rev_filler fillers[] = {
5209 chtype symbol = get_rev_graph_symbol(graph);
5210 struct rev_filler *filler;
5213 if (opt_line_graphics)
5214 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
5216 filler = &fillers[DEFAULT];
5218 for (i = 0; i < graph->pos; i++) {
5219 append_to_rev_graph(graph, filler->line);
5220 if (graph_parent_is_merge(graph->prev) &&
5221 graph->prev->pos == i)
5222 filler = &fillers[RSHARP];
5224 append_to_rev_graph(graph, filler->separator);
5227 /* Place the symbol for this revision. */
5228 append_to_rev_graph(graph, symbol);
5230 if (graph->prev->size > graph->size)
5231 filler = &fillers[RDIAG];
5233 filler = &fillers[DEFAULT];
5237 for (; i < graph->size; i++) {
5238 append_to_rev_graph(graph, filler->separator);
5239 append_to_rev_graph(graph, filler->line);
5240 if (graph_parent_is_merge(graph->prev) &&
5241 i < graph->prev->pos + graph->parents->size)
5242 filler = &fillers[RSHARP];
5243 if (graph->prev->size > graph->size)
5244 filler = &fillers[LDIAG];
5247 if (graph->prev->size > graph->size) {
5248 append_to_rev_graph(graph, filler->separator);
5249 if (filler->line != ' ')
5250 append_to_rev_graph(graph, filler->line);
5254 /* Prepare the next rev graph */
5256 prepare_rev_graph(struct rev_graph *graph)
5260 /* First, traverse all lines of revisions up to the active one. */
5261 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
5262 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
5265 push_rev_graph(graph->next, graph->rev[graph->pos]);
5268 /* Interleave the new revision parent(s). */
5269 for (i = 0; !graph->boundary && i < graph->parents->size; i++)
5270 push_rev_graph(graph->next, graph->parents->rev[i]);
5272 /* Lastly, put any remaining revisions. */
5273 for (i = graph->pos + 1; i < graph->size; i++)
5274 push_rev_graph(graph->next, graph->rev[i]);
5278 update_rev_graph(struct rev_graph *graph)
5280 /* If this is the finalizing update ... */
5282 prepare_rev_graph(graph);
5284 /* Graph visualization needs a one rev look-ahead,
5285 * so the first update doesn't visualize anything. */
5286 if (!graph->prev->commit)
5289 draw_rev_graph(graph->prev);
5290 done_rev_graph(graph->prev->prev);
5299 main_draw(struct view *view, struct line *line, unsigned int lineno)
5301 struct commit *commit = line->data;
5303 if (!*commit->author)
5306 if (opt_date && draw_date(view, &commit->time))
5310 draw_field(view, LINE_MAIN_AUTHOR, commit->author, opt_author_cols, TRUE))
5313 if (opt_rev_graph && commit->graph_size &&
5314 draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
5317 if (opt_show_refs && commit->refs) {
5321 enum line_type type;
5323 if (commit->refs[i]->head)
5324 type = LINE_MAIN_HEAD;
5325 else if (commit->refs[i]->ltag)
5326 type = LINE_MAIN_LOCAL_TAG;
5327 else if (commit->refs[i]->tag)
5328 type = LINE_MAIN_TAG;
5329 else if (commit->refs[i]->tracked)
5330 type = LINE_MAIN_TRACKED;
5331 else if (commit->refs[i]->remote)
5332 type = LINE_MAIN_REMOTE;
5334 type = LINE_MAIN_REF;
5336 if (draw_text(view, type, "[", TRUE) ||
5337 draw_text(view, type, commit->refs[i]->name, TRUE) ||
5338 draw_text(view, type, "]", TRUE))
5341 if (draw_text(view, LINE_DEFAULT, " ", TRUE))
5343 } while (commit->refs[i++]->next);
5346 draw_text(view, LINE_DEFAULT, commit->title, TRUE);
5350 /* Reads git log --pretty=raw output and parses it into the commit struct. */
5352 main_read(struct view *view, char *line)
5354 static struct rev_graph *graph = graph_stacks;
5355 enum line_type type;
5356 struct commit *commit;
5361 if (!view->lines && !view->parent)
5362 die("No revisions match the given arguments.");
5363 if (view->lines > 0) {
5364 commit = view->line[view->lines - 1].data;
5365 if (!*commit->author) {
5368 graph->commit = NULL;
5371 update_rev_graph(graph);
5373 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
5374 clear_rev_graph(&graph_stacks[i]);
5378 type = get_line_type(line);
5379 if (type == LINE_COMMIT) {
5380 commit = calloc(1, sizeof(struct commit));
5384 line += STRING_SIZE("commit ");
5386 graph->boundary = 1;
5390 string_copy_rev(commit->id, line);
5391 commit->refs = get_refs(commit->id);
5392 graph->commit = commit;
5393 add_line_data(view, commit, LINE_MAIN_COMMIT);
5395 while ((line = strchr(line, ' '))) {
5397 push_rev_graph(graph->parents, line);
5398 commit->has_parents = TRUE;
5405 commit = view->line[view->lines - 1].data;
5409 if (commit->has_parents)
5411 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
5416 /* Parse author lines where the name may be empty:
5417 * author <email@address.tld> 1138474660 +0100
5419 char *ident = line + STRING_SIZE("author ");
5420 char *nameend = strchr(ident, '<');
5421 char *emailend = strchr(ident, '>');
5423 if (!nameend || !emailend)
5426 update_rev_graph(graph);
5427 graph = graph->next;
5429 *nameend = *emailend = 0;
5430 ident = chomp_string(ident);
5432 ident = chomp_string(nameend + 1);
5437 string_ncopy(commit->author, ident, strlen(ident));
5439 /* Parse epoch and timezone */
5440 if (emailend[1] == ' ') {
5441 char *secs = emailend + 2;
5442 char *zone = strchr(secs, ' ');
5443 time_t time = (time_t) atol(secs);
5445 if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
5449 tz = ('0' - zone[1]) * 60 * 60 * 10;
5450 tz += ('0' - zone[2]) * 60 * 60;
5451 tz += ('0' - zone[3]) * 60;
5452 tz += ('0' - zone[4]) * 60;
5460 gmtime_r(&time, &commit->time);
5465 /* Fill in the commit title if it has not already been set. */
5466 if (commit->title[0])
5469 /* Require titles to start with a non-space character at the
5470 * offset used by git log. */
5471 if (strncmp(line, " ", 4))
5474 /* Well, if the title starts with a whitespace character,
5475 * try to be forgiving. Otherwise we end up with no title. */
5476 while (isspace(*line))
5480 /* FIXME: More graceful handling of titles; append "..." to
5481 * shortened titles, etc. */
5483 string_ncopy(commit->title, line, strlen(line));
5490 main_request(struct view *view, enum request request, struct line *line)
5492 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
5496 open_view(view, REQ_VIEW_DIFF, flags);
5500 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
5510 grep_refs(struct ref **refs, regex_t *regex)
5518 if (regexec(regex, refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
5520 } while (refs[i++]->next);
5526 main_grep(struct view *view, struct line *line)
5528 struct commit *commit = line->data;
5529 enum { S_TITLE, S_AUTHOR, S_DATE, S_REFS, S_END } state;
5530 char buf[DATE_COLS + 1];
5533 for (state = S_TITLE; state < S_END; state++) {
5537 case S_TITLE: text = commit->title; break;
5541 text = commit->author;
5546 if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
5553 if (grep_refs(commit->refs, view->regex) == TRUE)
5560 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
5568 main_select(struct view *view, struct line *line)
5570 struct commit *commit = line->data;
5572 string_copy_rev(view->ref, commit->id);
5573 string_copy_rev(ref_commit, view->ref);
5576 static struct view_ops main_ops = {
5588 * Unicode / UTF-8 handling
5590 * NOTE: Much of the following code for dealing with unicode is derived from
5591 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
5592 * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
5595 /* I've (over)annotated a lot of code snippets because I am not entirely
5596 * confident that the approach taken by this small UTF-8 interface is correct.
5600 unicode_width(unsigned long c)
5603 (c <= 0x115f /* Hangul Jamo */
5606 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
5608 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
5609 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
5610 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
5611 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
5612 || (c >= 0xffe0 && c <= 0xffe6)
5613 || (c >= 0x20000 && c <= 0x2fffd)
5614 || (c >= 0x30000 && c <= 0x3fffd)))
5618 return opt_tab_size;
5623 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
5624 * Illegal bytes are set one. */
5625 static const unsigned char utf8_bytes[256] = {
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 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
5628 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
5629 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
5630 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
5631 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
5632 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,
5633 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,
5636 /* Decode UTF-8 multi-byte representation into a unicode character. */
5637 static inline unsigned long
5638 utf8_to_unicode(const char *string, size_t length)
5640 unsigned long unicode;
5644 unicode = string[0];
5647 unicode = (string[0] & 0x1f) << 6;
5648 unicode += (string[1] & 0x3f);
5651 unicode = (string[0] & 0x0f) << 12;
5652 unicode += ((string[1] & 0x3f) << 6);
5653 unicode += (string[2] & 0x3f);
5656 unicode = (string[0] & 0x0f) << 18;
5657 unicode += ((string[1] & 0x3f) << 12);
5658 unicode += ((string[2] & 0x3f) << 6);
5659 unicode += (string[3] & 0x3f);
5662 unicode = (string[0] & 0x0f) << 24;
5663 unicode += ((string[1] & 0x3f) << 18);
5664 unicode += ((string[2] & 0x3f) << 12);
5665 unicode += ((string[3] & 0x3f) << 6);
5666 unicode += (string[4] & 0x3f);
5669 unicode = (string[0] & 0x01) << 30;
5670 unicode += ((string[1] & 0x3f) << 24);
5671 unicode += ((string[2] & 0x3f) << 18);
5672 unicode += ((string[3] & 0x3f) << 12);
5673 unicode += ((string[4] & 0x3f) << 6);
5674 unicode += (string[5] & 0x3f);
5677 die("Invalid unicode length");
5680 /* Invalid characters could return the special 0xfffd value but NUL
5681 * should be just as good. */
5682 return unicode > 0xffff ? 0 : unicode;
5685 /* Calculates how much of string can be shown within the given maximum width
5686 * and sets trimmed parameter to non-zero value if all of string could not be
5687 * shown. If the reserve flag is TRUE, it will reserve at least one
5688 * trailing character, which can be useful when drawing a delimiter.
5690 * Returns the number of bytes to output from string to satisfy max_width. */
5692 utf8_length(const char *string, int *width, size_t max_width, int *trimmed, bool reserve)
5694 const char *start = string;
5695 const char *end = strchr(string, '\0');
5696 unsigned char last_bytes = 0;
5697 size_t last_ucwidth = 0;
5702 while (string < end) {
5703 int c = *(unsigned char *) string;
5704 unsigned char bytes = utf8_bytes[c];
5706 unsigned long unicode;
5708 if (string + bytes > end)
5711 /* Change representation to figure out whether
5712 * it is a single- or double-width character. */
5714 unicode = utf8_to_unicode(string, bytes);
5715 /* FIXME: Graceful handling of invalid unicode character. */
5719 ucwidth = unicode_width(unicode);
5721 if (*width > max_width) {
5724 if (reserve && *width == max_width) {
5725 string -= last_bytes;
5726 *width -= last_ucwidth;
5733 last_ucwidth = ucwidth;
5736 return string - start;
5744 /* Whether or not the curses interface has been initialized. */
5745 static bool cursed = FALSE;
5747 /* The status window is used for polling keystrokes. */
5748 static WINDOW *status_win;
5750 static bool status_empty = TRUE;
5752 /* Update status and title window. */
5754 report(const char *msg, ...)
5756 struct view *view = display[current_view];
5762 char buf[SIZEOF_STR];
5765 va_start(args, msg);
5766 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
5767 buf[sizeof(buf) - 1] = 0;
5768 buf[sizeof(buf) - 2] = '.';
5769 buf[sizeof(buf) - 3] = '.';
5770 buf[sizeof(buf) - 4] = '.';
5776 if (!status_empty || *msg) {
5779 va_start(args, msg);
5781 wmove(status_win, 0, 0);
5783 vwprintw(status_win, msg, args);
5784 status_empty = FALSE;
5786 status_empty = TRUE;
5788 wclrtoeol(status_win);
5789 wrefresh(status_win);
5794 update_view_title(view);
5795 update_display_cursor(view);
5798 /* Controls when nodelay should be in effect when polling user input. */
5800 set_nonblocking_input(bool loading)
5802 static unsigned int loading_views;
5804 if ((loading == FALSE && loading_views-- == 1) ||
5805 (loading == TRUE && loading_views++ == 0))
5806 nodelay(status_win, loading);
5814 /* Initialize the curses library */
5815 if (isatty(STDIN_FILENO)) {
5816 cursed = !!initscr();
5819 /* Leave stdin and stdout alone when acting as a pager. */
5820 opt_tty = fopen("/dev/tty", "r+");
5822 die("Failed to open /dev/tty");
5823 cursed = !!newterm(NULL, opt_tty, opt_tty);
5827 die("Failed to initialize curses");
5829 nonl(); /* Tell curses not to do NL->CR/NL on output */
5830 cbreak(); /* Take input chars one at a time, no wait for \n */
5831 noecho(); /* Don't echo input */
5832 leaveok(stdscr, TRUE);
5837 getmaxyx(stdscr, y, x);
5838 status_win = newwin(1, 0, y - 1, 0);
5840 die("Failed to create status window");
5842 /* Enable keyboard mapping */
5843 keypad(status_win, TRUE);
5844 wbkgdset(status_win, get_line_attr(LINE_STATUS));
5846 TABSIZE = opt_tab_size;
5847 if (opt_line_graphics) {
5848 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
5853 prompt_yesno(const char *prompt)
5855 enum { WAIT, STOP, CANCEL } status = WAIT;
5856 bool answer = FALSE;
5858 while (status == WAIT) {
5864 foreach_view (view, i)
5869 mvwprintw(status_win, 0, 0, "%s [Yy]/[Nn]", prompt);
5870 wclrtoeol(status_win);
5872 /* Refresh, accept single keystroke of input */
5873 key = wgetch(status_win);
5897 /* Clear the status window */
5898 status_empty = FALSE;
5905 read_prompt(const char *prompt)
5907 enum { READING, STOP, CANCEL } status = READING;
5908 static char buf[sizeof(opt_cmd) - STRING_SIZE("git \0")];
5911 while (status == READING) {
5917 foreach_view (view, i)
5922 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
5923 wclrtoeol(status_win);
5925 /* Refresh, accept single keystroke of input */
5926 key = wgetch(status_win);
5931 status = pos ? STOP : CANCEL;
5949 if (pos >= sizeof(buf)) {
5950 report("Input string too long");
5955 buf[pos++] = (char) key;
5959 /* Clear the status window */
5960 status_empty = FALSE;
5963 if (status == CANCEL)
5972 * Repository references
5975 static struct ref *refs = NULL;
5976 static size_t refs_alloc = 0;
5977 static size_t refs_size = 0;
5979 /* Id <-> ref store */
5980 static struct ref ***id_refs = NULL;
5981 static size_t id_refs_alloc = 0;
5982 static size_t id_refs_size = 0;
5985 compare_refs(const void *ref1_, const void *ref2_)
5987 const struct ref *ref1 = *(const struct ref **)ref1_;
5988 const struct ref *ref2 = *(const struct ref **)ref2_;
5990 if (ref1->tag != ref2->tag)
5991 return ref2->tag - ref1->tag;
5992 if (ref1->ltag != ref2->ltag)
5993 return ref2->ltag - ref2->ltag;
5994 if (ref1->head != ref2->head)
5995 return ref2->head - ref1->head;
5996 if (ref1->tracked != ref2->tracked)
5997 return ref2->tracked - ref1->tracked;
5998 if (ref1->remote != ref2->remote)
5999 return ref2->remote - ref1->remote;
6000 return strcmp(ref1->name, ref2->name);
6003 static struct ref **
6004 get_refs(const char *id)
6006 struct ref ***tmp_id_refs;
6007 struct ref **ref_list = NULL;
6008 size_t ref_list_alloc = 0;
6009 size_t ref_list_size = 0;
6012 for (i = 0; i < id_refs_size; i++)
6013 if (!strcmp(id, id_refs[i][0]->id))
6016 tmp_id_refs = realloc_items(id_refs, &id_refs_alloc, id_refs_size + 1,
6021 id_refs = tmp_id_refs;
6023 for (i = 0; i < refs_size; i++) {
6026 if (strcmp(id, refs[i].id))
6029 tmp = realloc_items(ref_list, &ref_list_alloc,
6030 ref_list_size + 1, sizeof(*ref_list));
6038 ref_list[ref_list_size] = &refs[i];
6039 /* XXX: The properties of the commit chains ensures that we can
6040 * safely modify the shared ref. The repo references will
6041 * always be similar for the same id. */
6042 ref_list[ref_list_size]->next = 1;
6048 qsort(ref_list, ref_list_size, sizeof(*ref_list), compare_refs);
6049 ref_list[ref_list_size - 1]->next = 0;
6050 id_refs[id_refs_size++] = ref_list;
6057 read_ref(char *id, size_t idlen, char *name, size_t namelen)
6062 bool remote = FALSE;
6063 bool tracked = FALSE;
6064 bool check_replace = FALSE;
6067 if (!prefixcmp(name, "refs/tags/")) {
6068 if (!suffixcmp(name, namelen, "^{}")) {
6071 if (refs_size > 0 && refs[refs_size - 1].ltag == TRUE)
6072 check_replace = TRUE;
6078 namelen -= STRING_SIZE("refs/tags/");
6079 name += STRING_SIZE("refs/tags/");
6081 } else if (!prefixcmp(name, "refs/remotes/")) {
6083 namelen -= STRING_SIZE("refs/remotes/");
6084 name += STRING_SIZE("refs/remotes/");
6085 tracked = !strcmp(opt_remote, name);
6087 } else if (!prefixcmp(name, "refs/heads/")) {
6088 namelen -= STRING_SIZE("refs/heads/");
6089 name += STRING_SIZE("refs/heads/");
6090 head = !strncmp(opt_head, name, namelen);
6092 } else if (!strcmp(name, "HEAD")) {
6093 string_ncopy(opt_head_rev, id, idlen);
6097 if (check_replace && !strcmp(name, refs[refs_size - 1].name)) {
6098 /* it's an annotated tag, replace the previous sha1 with the
6099 * resolved commit id; relies on the fact git-ls-remote lists
6100 * the commit id of an annotated tag right before the commit id
6102 refs[refs_size - 1].ltag = ltag;
6103 string_copy_rev(refs[refs_size - 1].id, id);
6107 refs = realloc_items(refs, &refs_alloc, refs_size + 1, sizeof(*refs));
6111 ref = &refs[refs_size++];
6112 ref->name = malloc(namelen + 1);
6116 strncpy(ref->name, name, namelen);
6117 ref->name[namelen] = 0;
6121 ref->remote = remote;
6122 ref->tracked = tracked;
6123 string_copy_rev(ref->id, id);
6131 const char *cmd_env = getenv("TIG_LS_REMOTE");
6132 const char *cmd = cmd_env && *cmd_env ? cmd_env : TIG_LS_REMOTE;
6137 while (refs_size > 0)
6138 free(refs[--refs_size].name);
6139 while (id_refs_size > 0)
6140 free(id_refs[--id_refs_size]);
6142 return read_properties(popen(cmd, "r"), "\t", read_ref);
6146 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
6148 if (!strcmp(name, "i18n.commitencoding"))
6149 string_ncopy(opt_encoding, value, valuelen);
6151 if (!strcmp(name, "core.editor"))
6152 string_ncopy(opt_editor, value, valuelen);
6154 /* branch.<head>.remote */
6156 !strncmp(name, "branch.", 7) &&
6157 !strncmp(name + 7, opt_head, strlen(opt_head)) &&
6158 !strcmp(name + 7 + strlen(opt_head), ".remote"))
6159 string_ncopy(opt_remote, value, valuelen);
6161 if (*opt_head && *opt_remote &&
6162 !strncmp(name, "branch.", 7) &&
6163 !strncmp(name + 7, opt_head, strlen(opt_head)) &&
6164 !strcmp(name + 7 + strlen(opt_head), ".merge")) {
6165 size_t from = strlen(opt_remote);
6167 if (!prefixcmp(value, "refs/heads/")) {
6168 value += STRING_SIZE("refs/heads/");
6169 valuelen -= STRING_SIZE("refs/heads/");
6172 if (!string_format_from(opt_remote, &from, "/%s", value))
6180 load_git_config(void)
6182 return read_properties(popen("git " GIT_CONFIG " --list", "r"),
6183 "=", read_repo_config_option);
6187 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
6189 if (!opt_git_dir[0]) {
6190 string_ncopy(opt_git_dir, name, namelen);
6192 } else if (opt_is_inside_work_tree == -1) {
6193 /* This can be 3 different values depending on the
6194 * version of git being used. If git-rev-parse does not
6195 * understand --is-inside-work-tree it will simply echo
6196 * the option else either "true" or "false" is printed.
6197 * Default to true for the unknown case. */
6198 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
6200 } else if (opt_cdup[0] == ' ') {
6201 string_ncopy(opt_cdup, name, namelen);
6203 if (!prefixcmp(name, "refs/heads/")) {
6204 namelen -= STRING_SIZE("refs/heads/");
6205 name += STRING_SIZE("refs/heads/");
6206 string_ncopy(opt_head, name, namelen);
6214 load_repo_info(void)
6217 FILE *pipe = popen("(git rev-parse --git-dir --is-inside-work-tree "
6218 " --show-cdup; git symbolic-ref HEAD) 2>/dev/null", "r");
6220 /* XXX: The line outputted by "--show-cdup" can be empty so
6221 * initialize it to something invalid to make it possible to
6222 * detect whether it has been set or not. */
6225 result = read_properties(pipe, "=", read_repo_info);
6226 if (opt_cdup[0] == ' ')
6233 read_properties(FILE *pipe, const char *separators,
6234 int (*read_property)(char *, size_t, char *, size_t))
6236 char buffer[BUFSIZ];
6243 while (state == OK && (name = fgets(buffer, sizeof(buffer), pipe))) {
6248 name = chomp_string(name);
6249 namelen = strcspn(name, separators);
6251 if (name[namelen]) {
6253 value = chomp_string(name + namelen + 1);
6254 valuelen = strlen(value);
6261 state = read_property(name, namelen, value, valuelen);
6264 if (state != ERR && ferror(pipe))
6277 static void __NORETURN
6280 /* XXX: Restore tty modes and let the OS cleanup the rest! */
6286 static void __NORETURN
6287 die(const char *err, ...)
6293 va_start(args, err);
6294 fputs("tig: ", stderr);
6295 vfprintf(stderr, err, args);
6296 fputs("\n", stderr);
6303 warn(const char *msg, ...)
6307 va_start(args, msg);
6308 fputs("tig warning: ", stderr);
6309 vfprintf(stderr, msg, args);
6310 fputs("\n", stderr);
6315 main(int argc, const char *argv[])
6318 enum request request;
6321 signal(SIGINT, quit);
6323 if (setlocale(LC_ALL, "")) {
6324 char *codeset = nl_langinfo(CODESET);
6326 string_ncopy(opt_codeset, codeset, strlen(codeset));
6329 if (load_repo_info() == ERR)
6330 die("Failed to load repo info.");
6332 if (load_options() == ERR)
6333 die("Failed to load user config.");
6335 if (load_git_config() == ERR)
6336 die("Failed to load repo config.");
6338 request = parse_options(argc, argv);
6339 if (request == REQ_NONE)
6342 /* Require a git repository unless when running in pager mode. */
6343 if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
6344 die("Not a git repository");
6346 if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
6349 if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
6350 opt_iconv = iconv_open(opt_codeset, opt_encoding);
6351 if (opt_iconv == ICONV_NONE)
6352 die("Failed to initialize character set conversion");
6355 if (load_refs() == ERR)
6356 die("Failed to load refs.");
6358 foreach_view (view, i)
6359 view->cmd_env = getenv(view->cmd_env);
6363 while (view_driver(display[current_view], request)) {
6367 foreach_view (view, i)
6369 view = display[current_view];
6371 /* Refresh, accept single keystroke of input */
6372 key = wgetch(status_win);
6374 /* wgetch() with nodelay() enabled returns ERR when there's no
6381 request = get_keybinding(view->keymap, key);
6383 /* Some low-level request handling. This keeps access to
6384 * status_win restricted. */
6388 char *cmd = read_prompt(":");
6390 if (cmd && string_format(opt_cmd, "git %s", cmd)) {
6391 if (strncmp(cmd, "show", 4) && isspace(cmd[4])) {
6392 request = REQ_VIEW_DIFF;
6394 request = REQ_VIEW_PAGER;
6397 /* Always reload^Wrerun commands from the prompt. */
6398 open_view(view, request, OPEN_RELOAD);
6405 case REQ_SEARCH_BACK:
6407 const char *prompt = request == REQ_SEARCH ? "/" : "?";
6408 char *search = read_prompt(prompt);
6411 string_ncopy(opt_search, search, strlen(search));
6416 case REQ_SCREEN_RESIZE:
6420 getmaxyx(stdscr, height, width);
6422 /* Resize the status view and let the view driver take
6423 * care of resizing the displayed views. */
6424 wresize(status_win, 1, width);
6425 mvwin(status_win, height - 1, 0);
6426 wrefresh(status_win);