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_rd(struct io *io, const char *argv[], const char *dir,
403 enum format_flags flags)
405 init_io(io, dir, IO_RD);
406 return format_command(io->sh, argv, flags);
410 init_io_fd(struct io *io, FILE *pipe)
412 init_io(io, NULL, IO_FD);
414 return io->pipe != NULL;
418 done_io(struct io *io)
421 if (io->type == IO_FD)
423 else if (io->type == IO_RD || io->type == IO_WR)
430 start_io(struct io *io)
432 char buf[SIZEOF_STR * 2];
435 if (io->dir && *io->dir &&
436 !string_format_from(buf, &bufpos, "cd %s;", io->dir))
439 if (!string_format_from(buf, &bufpos, "%s", io->sh))
442 if (io->type == IO_FG)
443 return system(buf) == 0;
445 io->pipe = popen(io->sh, io->type == IO_RD ? "r" : "w");
446 return io->pipe != NULL;
450 run_io(struct io *io, enum io_type type, const char *cmd)
452 init_io(io, NULL, type);
453 string_ncopy(io->sh, cmd, strlen(cmd));
458 run_io_do(struct io *io)
460 return start_io(io) && done_io(io);
464 run_io_fg(const char **argv, const char *dir)
468 init_io(&io, dir, IO_FG);
469 if (!format_command(io.sh, argv, FORMAT_NONE))
471 return run_io_do(&io);
475 run_io_format(struct io *io, const char *cmd, ...)
480 init_io(io, NULL, IO_RD);
482 if (vsnprintf(io->sh, sizeof(io->sh), cmd, args) >= sizeof(io->sh))
486 return io->sh[0] ? start_io(io) : FALSE;
490 io_eof(struct io *io)
492 return feof(io->pipe);
496 io_error(struct io *io)
502 io_strerror(struct io *io)
504 return strerror(io->error);
508 io_gets(struct io *io)
511 io->buf = malloc(BUFSIZ);
514 io->bufalloc = BUFSIZ;
517 if (!fgets(io->buf, io->bufalloc, io->pipe)) {
518 if (ferror(io->pipe))
532 /* XXX: Keep the view request first and in sync with views[]. */ \
533 REQ_GROUP("View switching") \
534 REQ_(VIEW_MAIN, "Show main view"), \
535 REQ_(VIEW_DIFF, "Show diff view"), \
536 REQ_(VIEW_LOG, "Show log view"), \
537 REQ_(VIEW_TREE, "Show tree view"), \
538 REQ_(VIEW_BLOB, "Show blob view"), \
539 REQ_(VIEW_BLAME, "Show blame view"), \
540 REQ_(VIEW_HELP, "Show help page"), \
541 REQ_(VIEW_PAGER, "Show pager view"), \
542 REQ_(VIEW_STATUS, "Show status view"), \
543 REQ_(VIEW_STAGE, "Show stage view"), \
545 REQ_GROUP("View manipulation") \
546 REQ_(ENTER, "Enter current line and scroll"), \
547 REQ_(NEXT, "Move to next"), \
548 REQ_(PREVIOUS, "Move to previous"), \
549 REQ_(VIEW_NEXT, "Move focus to next view"), \
550 REQ_(REFRESH, "Reload and refresh"), \
551 REQ_(MAXIMIZE, "Maximize the current view"), \
552 REQ_(VIEW_CLOSE, "Close the current view"), \
553 REQ_(QUIT, "Close all views and quit"), \
555 REQ_GROUP("View specific requests") \
556 REQ_(STATUS_UPDATE, "Update file status"), \
557 REQ_(STATUS_REVERT, "Revert file changes"), \
558 REQ_(STATUS_MERGE, "Merge file using external tool"), \
559 REQ_(STAGE_NEXT, "Find next chunk to stage"), \
560 REQ_(TREE_PARENT, "Switch to parent directory in tree view"), \
562 REQ_GROUP("Cursor navigation") \
563 REQ_(MOVE_UP, "Move cursor one line up"), \
564 REQ_(MOVE_DOWN, "Move cursor one line down"), \
565 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
566 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
567 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
568 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
570 REQ_GROUP("Scrolling") \
571 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
572 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
573 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
574 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
576 REQ_GROUP("Searching") \
577 REQ_(SEARCH, "Search the view"), \
578 REQ_(SEARCH_BACK, "Search backwards in the view"), \
579 REQ_(FIND_NEXT, "Find next search match"), \
580 REQ_(FIND_PREV, "Find previous search match"), \
582 REQ_GROUP("Option manipulation") \
583 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
584 REQ_(TOGGLE_DATE, "Toggle date display"), \
585 REQ_(TOGGLE_AUTHOR, "Toggle author display"), \
586 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
587 REQ_(TOGGLE_REFS, "Toggle reference display (tags/branches)"), \
590 REQ_(PROMPT, "Bring up the prompt"), \
591 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
592 REQ_(SCREEN_RESIZE, "Resize the screen"), \
593 REQ_(SHOW_VERSION, "Show version information"), \
594 REQ_(STOP_LOADING, "Stop all loading views"), \
595 REQ_(EDIT, "Open in editor"), \
596 REQ_(NONE, "Do nothing")
599 /* User action requests. */
601 #define REQ_GROUP(help)
602 #define REQ_(req, help) REQ_##req
604 /* Offset all requests to avoid conflicts with ncurses getch values. */
605 REQ_OFFSET = KEY_MAX + 1,
612 struct request_info {
613 enum request request;
619 static struct request_info req_info[] = {
620 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
621 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
628 get_request(const char *name)
630 int namelen = strlen(name);
633 for (i = 0; i < ARRAY_SIZE(req_info); i++)
634 if (req_info[i].namelen == namelen &&
635 !string_enum_compare(req_info[i].name, name, namelen))
636 return req_info[i].request;
646 static const char usage[] =
647 "tig " TIG_VERSION " (" __DATE__ ")\n"
649 "Usage: tig [options] [revs] [--] [paths]\n"
650 " or: tig show [options] [revs] [--] [paths]\n"
651 " or: tig blame [rev] path\n"
653 " or: tig < [git command output]\n"
656 " -v, --version Show version and exit\n"
657 " -h, --help Show help message and exit";
659 /* Option and state variables. */
660 static bool opt_date = TRUE;
661 static bool opt_author = TRUE;
662 static bool opt_line_number = FALSE;
663 static bool opt_line_graphics = TRUE;
664 static bool opt_rev_graph = FALSE;
665 static bool opt_show_refs = TRUE;
666 static int opt_num_interval = NUMBER_INTERVAL;
667 static int opt_tab_size = TAB_SIZE;
668 static int opt_author_cols = AUTHOR_COLS-1;
669 static char opt_cmd[SIZEOF_STR] = "";
670 static char opt_path[SIZEOF_STR] = "";
671 static char opt_file[SIZEOF_STR] = "";
672 static char opt_ref[SIZEOF_REF] = "";
673 static char opt_head[SIZEOF_REF] = "";
674 static char opt_head_rev[SIZEOF_REV] = "";
675 static char opt_remote[SIZEOF_REF] = "";
676 static FILE *opt_pipe = NULL;
677 static char opt_encoding[20] = "UTF-8";
678 static bool opt_utf8 = TRUE;
679 static char opt_codeset[20] = "UTF-8";
680 static iconv_t opt_iconv = ICONV_NONE;
681 static char opt_search[SIZEOF_STR] = "";
682 static char opt_cdup[SIZEOF_STR] = "";
683 static char opt_git_dir[SIZEOF_STR] = "";
684 static signed char opt_is_inside_work_tree = -1; /* set to TRUE or FALSE */
685 static char opt_editor[SIZEOF_STR] = "";
686 static FILE *opt_tty = NULL;
688 #define is_initial_commit() (!*opt_head_rev)
689 #define is_head_commit(rev) (!strcmp((rev), "HEAD") || !strcmp(opt_head_rev, (rev)))
692 parse_options(int argc, const char *argv[])
694 enum request request = REQ_VIEW_MAIN;
696 const char *subcommand;
697 bool seen_dashdash = FALSE;
700 if (!isatty(STDIN_FILENO)) {
702 return REQ_VIEW_PAGER;
706 return REQ_VIEW_MAIN;
708 subcommand = argv[1];
709 if (!strcmp(subcommand, "status") || !strcmp(subcommand, "-S")) {
710 if (!strcmp(subcommand, "-S"))
711 warn("`-S' has been deprecated; use `tig status' instead");
713 warn("ignoring arguments after `%s'", subcommand);
714 return REQ_VIEW_STATUS;
716 } else if (!strcmp(subcommand, "blame")) {
717 if (argc <= 2 || argc > 4)
718 die("invalid number of options to blame\n\n%s", usage);
722 string_ncopy(opt_ref, argv[i], strlen(argv[i]));
726 string_ncopy(opt_file, argv[i], strlen(argv[i]));
727 return REQ_VIEW_BLAME;
729 } else if (!strcmp(subcommand, "show")) {
730 request = REQ_VIEW_DIFF;
732 } else if (!strcmp(subcommand, "log") || !strcmp(subcommand, "diff")) {
733 request = subcommand[0] == 'l' ? REQ_VIEW_LOG : REQ_VIEW_DIFF;
734 warn("`tig %s' has been deprecated", subcommand);
741 /* XXX: This is vulnerable to the user overriding
742 * options required for the main view parser. */
743 string_copy(opt_cmd, TIG_MAIN_BASE);
745 string_format(opt_cmd, "git %s", subcommand);
747 buf_size = strlen(opt_cmd);
749 for (i = 1 + !!subcommand; i < argc; i++) {
750 const char *opt = argv[i];
752 if (seen_dashdash || !strcmp(opt, "--")) {
753 seen_dashdash = TRUE;
755 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
756 printf("tig version %s\n", TIG_VERSION);
759 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
760 printf("%s\n", usage);
764 opt_cmd[buf_size++] = ' ';
765 buf_size = sq_quote(opt_cmd, buf_size, opt);
766 if (buf_size >= sizeof(opt_cmd))
767 die("command too long");
770 opt_cmd[buf_size] = 0;
777 * Line-oriented content detection.
781 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
782 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
783 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
784 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
785 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
786 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
787 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
788 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
789 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
790 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
791 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
792 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
793 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
794 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
795 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
796 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
797 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
798 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
799 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
800 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
801 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
802 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
803 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
804 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
805 LINE(AUTHOR, "author ", COLOR_CYAN, COLOR_DEFAULT, 0), \
806 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
807 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
808 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
809 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
810 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
811 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
812 LINE(DELIMITER, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
813 LINE(DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
814 LINE(LINE_NUMBER, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
815 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
816 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
817 LINE(MAIN_AUTHOR, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
818 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
819 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
820 LINE(MAIN_LOCAL_TAG,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
821 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
822 LINE(MAIN_TRACKED, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
823 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
824 LINE(MAIN_HEAD, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
825 LINE(MAIN_REVGRAPH,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
826 LINE(TREE_DIR, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
827 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
828 LINE(STAT_HEAD, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
829 LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
830 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
831 LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
832 LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
833 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
834 LINE(BLAME_ID, "", COLOR_MAGENTA, COLOR_DEFAULT, 0)
837 #define LINE(type, line, fg, bg, attr) \
845 const char *name; /* Option name. */
846 int namelen; /* Size of option name. */
847 const char *line; /* The start of line to match. */
848 int linelen; /* Size of string to match. */
849 int fg, bg, attr; /* Color and text attributes for the lines. */
852 static struct line_info line_info[] = {
853 #define LINE(type, line, fg, bg, attr) \
854 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
859 static enum line_type
860 get_line_type(const char *line)
862 int linelen = strlen(line);
865 for (type = 0; type < ARRAY_SIZE(line_info); type++)
866 /* Case insensitive search matches Signed-off-by lines better. */
867 if (linelen >= line_info[type].linelen &&
868 !strncasecmp(line_info[type].line, line, line_info[type].linelen))
875 get_line_attr(enum line_type type)
877 assert(type < ARRAY_SIZE(line_info));
878 return COLOR_PAIR(type) | line_info[type].attr;
881 static struct line_info *
882 get_line_info(const char *name)
884 size_t namelen = strlen(name);
887 for (type = 0; type < ARRAY_SIZE(line_info); type++)
888 if (namelen == line_info[type].namelen &&
889 !string_enum_compare(line_info[type].name, name, namelen))
890 return &line_info[type];
898 int default_bg = line_info[LINE_DEFAULT].bg;
899 int default_fg = line_info[LINE_DEFAULT].fg;
904 if (assume_default_colors(default_fg, default_bg) == ERR) {
905 default_bg = COLOR_BLACK;
906 default_fg = COLOR_WHITE;
909 for (type = 0; type < ARRAY_SIZE(line_info); type++) {
910 struct line_info *info = &line_info[type];
911 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
912 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
914 init_pair(type, fg, bg);
922 unsigned int selected:1;
923 unsigned int dirty:1;
925 void *data; /* User data */
935 enum request request;
938 static struct keybinding default_keybindings[] = {
940 { 'm', REQ_VIEW_MAIN },
941 { 'd', REQ_VIEW_DIFF },
942 { 'l', REQ_VIEW_LOG },
943 { 't', REQ_VIEW_TREE },
944 { 'f', REQ_VIEW_BLOB },
945 { 'B', REQ_VIEW_BLAME },
946 { 'p', REQ_VIEW_PAGER },
947 { 'h', REQ_VIEW_HELP },
948 { 'S', REQ_VIEW_STATUS },
949 { 'c', REQ_VIEW_STAGE },
951 /* View manipulation */
952 { 'q', REQ_VIEW_CLOSE },
953 { KEY_TAB, REQ_VIEW_NEXT },
954 { KEY_RETURN, REQ_ENTER },
955 { KEY_UP, REQ_PREVIOUS },
956 { KEY_DOWN, REQ_NEXT },
957 { 'R', REQ_REFRESH },
958 { KEY_F(5), REQ_REFRESH },
959 { 'O', REQ_MAXIMIZE },
961 /* Cursor navigation */
962 { 'k', REQ_MOVE_UP },
963 { 'j', REQ_MOVE_DOWN },
964 { KEY_HOME, REQ_MOVE_FIRST_LINE },
965 { KEY_END, REQ_MOVE_LAST_LINE },
966 { KEY_NPAGE, REQ_MOVE_PAGE_DOWN },
967 { ' ', REQ_MOVE_PAGE_DOWN },
968 { KEY_PPAGE, REQ_MOVE_PAGE_UP },
969 { 'b', REQ_MOVE_PAGE_UP },
970 { '-', REQ_MOVE_PAGE_UP },
973 { KEY_IC, REQ_SCROLL_LINE_UP },
974 { KEY_DC, REQ_SCROLL_LINE_DOWN },
975 { 'w', REQ_SCROLL_PAGE_UP },
976 { 's', REQ_SCROLL_PAGE_DOWN },
980 { '?', REQ_SEARCH_BACK },
981 { 'n', REQ_FIND_NEXT },
982 { 'N', REQ_FIND_PREV },
986 { 'z', REQ_STOP_LOADING },
987 { 'v', REQ_SHOW_VERSION },
988 { 'r', REQ_SCREEN_REDRAW },
989 { '.', REQ_TOGGLE_LINENO },
990 { 'D', REQ_TOGGLE_DATE },
991 { 'A', REQ_TOGGLE_AUTHOR },
992 { 'g', REQ_TOGGLE_REV_GRAPH },
993 { 'F', REQ_TOGGLE_REFS },
995 { 'u', REQ_STATUS_UPDATE },
996 { '!', REQ_STATUS_REVERT },
997 { 'M', REQ_STATUS_MERGE },
998 { '@', REQ_STAGE_NEXT },
999 { ',', REQ_TREE_PARENT },
1002 /* Using the ncurses SIGWINCH handler. */
1003 { KEY_RESIZE, REQ_SCREEN_RESIZE },
1006 #define KEYMAP_INFO \
1020 #define KEYMAP_(name) KEYMAP_##name
1025 static struct int_map keymap_table[] = {
1026 #define KEYMAP_(name) { #name, STRING_SIZE(#name), KEYMAP_##name }
1031 #define set_keymap(map, name) \
1032 set_from_int_map(keymap_table, ARRAY_SIZE(keymap_table), map, name, strlen(name))
1034 struct keybinding_table {
1035 struct keybinding *data;
1039 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1042 add_keybinding(enum keymap keymap, enum request request, int key)
1044 struct keybinding_table *table = &keybindings[keymap];
1046 table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1048 die("Failed to allocate keybinding");
1049 table->data[table->size].alias = key;
1050 table->data[table->size++].request = request;
1053 /* Looks for a key binding first in the given map, then in the generic map, and
1054 * lastly in the default keybindings. */
1056 get_keybinding(enum keymap keymap, int key)
1060 for (i = 0; i < keybindings[keymap].size; i++)
1061 if (keybindings[keymap].data[i].alias == key)
1062 return keybindings[keymap].data[i].request;
1064 for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1065 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1066 return keybindings[KEYMAP_GENERIC].data[i].request;
1068 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1069 if (default_keybindings[i].alias == key)
1070 return default_keybindings[i].request;
1072 return (enum request) key;
1081 static struct key key_table[] = {
1082 { "Enter", KEY_RETURN },
1084 { "Backspace", KEY_BACKSPACE },
1086 { "Escape", KEY_ESC },
1087 { "Left", KEY_LEFT },
1088 { "Right", KEY_RIGHT },
1090 { "Down", KEY_DOWN },
1091 { "Insert", KEY_IC },
1092 { "Delete", KEY_DC },
1094 { "Home", KEY_HOME },
1096 { "PageUp", KEY_PPAGE },
1097 { "PageDown", KEY_NPAGE },
1107 { "F10", KEY_F(10) },
1108 { "F11", KEY_F(11) },
1109 { "F12", KEY_F(12) },
1113 get_key_value(const char *name)
1117 for (i = 0; i < ARRAY_SIZE(key_table); i++)
1118 if (!strcasecmp(key_table[i].name, name))
1119 return key_table[i].value;
1121 if (strlen(name) == 1 && isprint(*name))
1128 get_key_name(int key_value)
1130 static char key_char[] = "'X'";
1131 const char *seq = NULL;
1134 for (key = 0; key < ARRAY_SIZE(key_table); key++)
1135 if (key_table[key].value == key_value)
1136 seq = key_table[key].name;
1140 isprint(key_value)) {
1141 key_char[1] = (char) key_value;
1145 return seq ? seq : "(no key)";
1149 get_key(enum request request)
1151 static char buf[BUFSIZ];
1158 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1159 struct keybinding *keybinding = &default_keybindings[i];
1161 if (keybinding->request != request)
1164 if (!string_format_from(buf, &pos, "%s%s", sep,
1165 get_key_name(keybinding->alias)))
1166 return "Too many keybindings!";
1173 struct run_request {
1176 const char *argv[SIZEOF_ARG];
1179 static struct run_request *run_request;
1180 static size_t run_requests;
1183 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1185 struct run_request *req;
1187 if (argc >= ARRAY_SIZE(req->argv) - 1)
1190 req = realloc(run_request, (run_requests + 1) * sizeof(*run_request));
1195 req = &run_request[run_requests];
1196 req->keymap = keymap;
1198 req->argv[0] = NULL;
1200 if (!format_argv(req->argv, argv, FORMAT_NONE))
1203 return REQ_NONE + ++run_requests;
1206 static struct run_request *
1207 get_run_request(enum request request)
1209 if (request <= REQ_NONE)
1211 return &run_request[request - REQ_NONE - 1];
1215 add_builtin_run_requests(void)
1217 const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1218 const char *gc[] = { "git", "gc", NULL };
1225 { KEYMAP_MAIN, 'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1226 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1230 for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1233 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1234 if (req != REQ_NONE)
1235 add_keybinding(reqs[i].keymap, req, reqs[i].key);
1240 * User config file handling.
1243 static struct int_map color_map[] = {
1244 #define COLOR_MAP(name) { #name, STRING_SIZE(#name), COLOR_##name }
1256 #define set_color(color, name) \
1257 set_from_int_map(color_map, ARRAY_SIZE(color_map), color, name, strlen(name))
1259 static struct int_map attr_map[] = {
1260 #define ATTR_MAP(name) { #name, STRING_SIZE(#name), A_##name }
1267 ATTR_MAP(UNDERLINE),
1270 #define set_attribute(attr, name) \
1271 set_from_int_map(attr_map, ARRAY_SIZE(attr_map), attr, name, strlen(name))
1273 static int config_lineno;
1274 static bool config_errors;
1275 static const char *config_msg;
1277 /* Wants: object fgcolor bgcolor [attr] */
1279 option_color_command(int argc, const char *argv[])
1281 struct line_info *info;
1283 if (argc != 3 && argc != 4) {
1284 config_msg = "Wrong number of arguments given to color command";
1288 info = get_line_info(argv[0]);
1290 if (!string_enum_compare(argv[0], "main-delim", strlen("main-delim"))) {
1291 info = get_line_info("delimiter");
1293 } else if (!string_enum_compare(argv[0], "main-date", strlen("main-date"))) {
1294 info = get_line_info("date");
1297 config_msg = "Unknown color name";
1302 if (set_color(&info->fg, argv[1]) == ERR ||
1303 set_color(&info->bg, argv[2]) == ERR) {
1304 config_msg = "Unknown color";
1308 if (argc == 4 && set_attribute(&info->attr, argv[3]) == ERR) {
1309 config_msg = "Unknown attribute";
1316 static bool parse_bool(const char *s)
1318 return (!strcmp(s, "1") || !strcmp(s, "true") ||
1319 !strcmp(s, "yes")) ? TRUE : FALSE;
1323 parse_int(const char *s, int default_value, int min, int max)
1325 int value = atoi(s);
1327 return (value < min || value > max) ? default_value : value;
1330 /* Wants: name = value */
1332 option_set_command(int argc, const char *argv[])
1335 config_msg = "Wrong number of arguments given to set command";
1339 if (strcmp(argv[1], "=")) {
1340 config_msg = "No value assigned";
1344 if (!strcmp(argv[0], "show-author")) {
1345 opt_author = parse_bool(argv[2]);
1349 if (!strcmp(argv[0], "show-date")) {
1350 opt_date = parse_bool(argv[2]);
1354 if (!strcmp(argv[0], "show-rev-graph")) {
1355 opt_rev_graph = parse_bool(argv[2]);
1359 if (!strcmp(argv[0], "show-refs")) {
1360 opt_show_refs = parse_bool(argv[2]);
1364 if (!strcmp(argv[0], "show-line-numbers")) {
1365 opt_line_number = parse_bool(argv[2]);
1369 if (!strcmp(argv[0], "line-graphics")) {
1370 opt_line_graphics = parse_bool(argv[2]);
1374 if (!strcmp(argv[0], "line-number-interval")) {
1375 opt_num_interval = parse_int(argv[2], opt_num_interval, 1, 1024);
1379 if (!strcmp(argv[0], "author-width")) {
1380 opt_author_cols = parse_int(argv[2], opt_author_cols, 0, 1024);
1384 if (!strcmp(argv[0], "tab-size")) {
1385 opt_tab_size = parse_int(argv[2], opt_tab_size, 1, 1024);
1389 if (!strcmp(argv[0], "commit-encoding")) {
1390 const char *arg = argv[2];
1391 int arglen = strlen(arg);
1396 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1397 config_msg = "Unmatched quotation";
1400 arg += 1; arglen -= 2;
1402 string_ncopy(opt_encoding, arg, strlen(arg));
1407 config_msg = "Unknown variable name";
1411 /* Wants: mode request key */
1413 option_bind_command(int argc, const char *argv[])
1415 enum request request;
1420 config_msg = "Wrong number of arguments given to bind command";
1424 if (set_keymap(&keymap, argv[0]) == ERR) {
1425 config_msg = "Unknown key map";
1429 key = get_key_value(argv[1]);
1431 config_msg = "Unknown key";
1435 request = get_request(argv[2]);
1436 if (request == REQ_NONE) {
1437 const char *obsolete[] = { "cherry-pick" };
1438 size_t namelen = strlen(argv[2]);
1441 for (i = 0; i < ARRAY_SIZE(obsolete); i++) {
1442 if (namelen == strlen(obsolete[i]) &&
1443 !string_enum_compare(obsolete[i], argv[2], namelen)) {
1444 config_msg = "Obsolete request name";
1449 if (request == REQ_NONE && *argv[2]++ == '!')
1450 request = add_run_request(keymap, key, argc - 2, argv + 2);
1451 if (request == REQ_NONE) {
1452 config_msg = "Unknown request name";
1456 add_keybinding(keymap, request, key);
1462 set_option(const char *opt, char *value)
1464 const char *argv[SIZEOF_ARG];
1467 if (!argv_from_string(argv, &argc, value)) {
1468 config_msg = "Too many option arguments";
1472 if (!strcmp(opt, "color"))
1473 return option_color_command(argc, argv);
1475 if (!strcmp(opt, "set"))
1476 return option_set_command(argc, argv);
1478 if (!strcmp(opt, "bind"))
1479 return option_bind_command(argc, argv);
1481 config_msg = "Unknown option command";
1486 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1491 config_msg = "Internal error";
1493 /* Check for comment markers, since read_properties() will
1494 * only ensure opt and value are split at first " \t". */
1495 optlen = strcspn(opt, "#");
1499 if (opt[optlen] != 0) {
1500 config_msg = "No option value";
1504 /* Look for comment endings in the value. */
1505 size_t len = strcspn(value, "#");
1507 if (len < valuelen) {
1509 value[valuelen] = 0;
1512 status = set_option(opt, value);
1515 if (status == ERR) {
1516 fprintf(stderr, "Error on line %d, near '%.*s': %s\n",
1517 config_lineno, (int) optlen, opt, config_msg);
1518 config_errors = TRUE;
1521 /* Always keep going if errors are encountered. */
1526 load_option_file(const char *path)
1530 /* It's ok that the file doesn't exist. */
1531 file = fopen(path, "r");
1536 config_errors = FALSE;
1538 if (read_properties(file, " \t", read_option) == ERR ||
1539 config_errors == TRUE)
1540 fprintf(stderr, "Errors while loading %s.\n", path);
1546 const char *home = getenv("HOME");
1547 const char *tigrc_user = getenv("TIGRC_USER");
1548 const char *tigrc_system = getenv("TIGRC_SYSTEM");
1549 char buf[SIZEOF_STR];
1551 add_builtin_run_requests();
1553 if (!tigrc_system) {
1554 if (!string_format(buf, "%s/tigrc", SYSCONFDIR))
1558 load_option_file(tigrc_system);
1561 if (!home || !string_format(buf, "%s/.tigrc", home))
1565 load_option_file(tigrc_user);
1578 /* The display array of active views and the index of the current view. */
1579 static struct view *display[2];
1580 static unsigned int current_view;
1582 /* Reading from the prompt? */
1583 static bool input_mode = FALSE;
1585 #define foreach_displayed_view(view, i) \
1586 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1588 #define displayed_views() (display[1] != NULL ? 2 : 1)
1590 /* Current head and commit ID */
1591 static char ref_blob[SIZEOF_REF] = "";
1592 static char ref_commit[SIZEOF_REF] = "HEAD";
1593 static char ref_head[SIZEOF_REF] = "HEAD";
1596 const char *name; /* View name */
1597 const char *cmd_fmt; /* Default command line format */
1598 const char *cmd_env; /* Command line set via environment */
1599 const char *id; /* Points to either of ref_{head,commit,blob} */
1601 struct view_ops *ops; /* View operations */
1603 enum keymap keymap; /* What keymap does this view have */
1604 bool git_dir; /* Whether the view requires a git directory. */
1606 char ref[SIZEOF_REF]; /* Hovered commit reference */
1607 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
1609 int height, width; /* The width and height of the main window */
1610 WINDOW *win; /* The main window */
1611 WINDOW *title; /* The title window living below the main window */
1614 unsigned long offset; /* Offset of the window top */
1615 unsigned long lineno; /* Current line number */
1618 char grep[SIZEOF_STR]; /* Search string */
1619 regex_t *regex; /* Pre-compiled regex */
1621 /* If non-NULL, points to the view that opened this view. If this view
1622 * is closed tig will switch back to the parent view. */
1623 struct view *parent;
1626 size_t lines; /* Total number of lines */
1627 struct line *line; /* Line index */
1628 size_t line_alloc; /* Total number of allocated lines */
1629 size_t line_size; /* Total number of used lines */
1630 unsigned int digits; /* Number of digits in the lines member. */
1633 struct line *curline; /* Line currently being drawn. */
1634 enum line_type curtype; /* Attribute currently used for drawing. */
1635 unsigned long col; /* Column when drawing. */
1644 /* What type of content being displayed. Used in the title bar. */
1646 /* Open and reads in all view content. */
1647 bool (*open)(struct view *view);
1648 /* Read one line; updates view->line. */
1649 bool (*read)(struct view *view, char *data);
1650 /* Draw one line; @lineno must be < view->height. */
1651 bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
1652 /* Depending on view handle a special requests. */
1653 enum request (*request)(struct view *view, enum request request, struct line *line);
1654 /* Search for regex in a line. */
1655 bool (*grep)(struct view *view, struct line *line);
1657 void (*select)(struct view *view, struct line *line);
1660 static struct view_ops blame_ops;
1661 static struct view_ops blob_ops;
1662 static struct view_ops help_ops;
1663 static struct view_ops log_ops;
1664 static struct view_ops main_ops;
1665 static struct view_ops pager_ops;
1666 static struct view_ops stage_ops;
1667 static struct view_ops status_ops;
1668 static struct view_ops tree_ops;
1670 #define VIEW_STR(name, cmd, env, ref, ops, map, git) \
1671 { name, cmd, #env, ref, ops, map, git }
1673 #define VIEW_(id, name, ops, git, ref) \
1674 VIEW_STR(name, TIG_##id##_CMD, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
1677 static struct view views[] = {
1678 VIEW_(MAIN, "main", &main_ops, TRUE, ref_head),
1679 VIEW_(DIFF, "diff", &pager_ops, TRUE, ref_commit),
1680 VIEW_(LOG, "log", &log_ops, TRUE, ref_head),
1681 VIEW_(TREE, "tree", &tree_ops, TRUE, ref_commit),
1682 VIEW_(BLOB, "blob", &blob_ops, TRUE, ref_blob),
1683 VIEW_(BLAME, "blame", &blame_ops, TRUE, ref_commit),
1684 VIEW_(HELP, "help", &help_ops, FALSE, ""),
1685 VIEW_(PAGER, "pager", &pager_ops, FALSE, "stdin"),
1686 VIEW_(STATUS, "status", &status_ops, TRUE, ""),
1687 VIEW_(STAGE, "stage", &stage_ops, TRUE, ""),
1690 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
1691 #define VIEW_REQ(view) ((view) - views + REQ_OFFSET + 1)
1693 #define foreach_view(view, i) \
1694 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1696 #define view_is_displayed(view) \
1697 (view == display[0] || view == display[1])
1704 static int line_graphics[] = {
1705 /* LINE_GRAPHIC_VLINE: */ '|'
1709 set_view_attr(struct view *view, enum line_type type)
1711 if (!view->curline->selected && view->curtype != type) {
1712 wattrset(view->win, get_line_attr(type));
1713 wchgat(view->win, -1, 0, type, NULL);
1714 view->curtype = type;
1719 draw_chars(struct view *view, enum line_type type, const char *string,
1720 int max_len, bool use_tilde)
1724 int trimmed = FALSE;
1730 len = utf8_length(string, &col, max_len, &trimmed, use_tilde);
1732 col = len = strlen(string);
1733 if (len > max_len) {
1737 col = len = max_len;
1742 set_view_attr(view, type);
1743 waddnstr(view->win, string, len);
1744 if (trimmed && use_tilde) {
1745 set_view_attr(view, LINE_DELIMITER);
1746 waddch(view->win, '~');
1754 draw_space(struct view *view, enum line_type type, int max, int spaces)
1756 static char space[] = " ";
1759 spaces = MIN(max, spaces);
1761 while (spaces > 0) {
1762 int len = MIN(spaces, sizeof(space) - 1);
1764 col += draw_chars(view, type, space, spaces, FALSE);
1772 draw_lineno(struct view *view, unsigned int lineno)
1775 int digits3 = view->digits < 3 ? 3 : view->digits;
1776 int max_number = MIN(digits3, STRING_SIZE(number));
1777 int max = view->width - view->col;
1780 if (max < max_number)
1783 lineno += view->offset + 1;
1784 if (lineno == 1 || (lineno % opt_num_interval) == 0) {
1785 static char fmt[] = "%1ld";
1787 if (view->digits <= 9)
1788 fmt[1] = '0' + digits3;
1790 if (!string_format(number, fmt, lineno))
1792 col = draw_chars(view, LINE_LINE_NUMBER, number, max_number, TRUE);
1794 col = draw_space(view, LINE_LINE_NUMBER, max_number, max_number);
1798 set_view_attr(view, LINE_DEFAULT);
1799 waddch(view->win, line_graphics[LINE_GRAPHIC_VLINE]);
1804 col += draw_space(view, LINE_DEFAULT, max - col, 1);
1807 return view->width - view->col <= 0;
1811 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
1813 view->col += draw_chars(view, type, string, view->width - view->col, trim);
1814 return view->width - view->col <= 0;
1818 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
1820 int max = view->width - view->col;
1826 set_view_attr(view, type);
1827 /* Using waddch() instead of waddnstr() ensures that
1828 * they'll be rendered correctly for the cursor line. */
1829 for (i = 0; i < size; i++)
1830 waddch(view->win, graphic[i]);
1834 waddch(view->win, ' ');
1838 return view->width - view->col <= 0;
1842 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
1844 int max = MIN(view->width - view->col, len);
1848 col = draw_chars(view, type, text, max - 1, trim);
1850 col = draw_space(view, type, max - 1, max - 1);
1852 view->col += col + draw_space(view, LINE_DEFAULT, max - col, max - col);
1853 return view->width - view->col <= 0;
1857 draw_date(struct view *view, struct tm *time)
1859 char buf[DATE_COLS];
1864 timelen = strftime(buf, sizeof(buf), DATE_FORMAT, time);
1865 date = timelen ? buf : NULL;
1867 return draw_field(view, LINE_DATE, date, DATE_COLS, FALSE);
1871 draw_view_line(struct view *view, unsigned int lineno)
1874 bool selected = (view->offset + lineno == view->lineno);
1877 assert(view_is_displayed(view));
1879 if (view->offset + lineno >= view->lines)
1882 line = &view->line[view->offset + lineno];
1884 wmove(view->win, lineno, 0);
1886 view->curline = line;
1887 view->curtype = LINE_NONE;
1888 line->selected = FALSE;
1891 set_view_attr(view, LINE_CURSOR);
1892 line->selected = TRUE;
1893 view->ops->select(view, line);
1894 } else if (line->selected) {
1895 wclrtoeol(view->win);
1898 scrollok(view->win, FALSE);
1899 draw_ok = view->ops->draw(view, line, lineno);
1900 scrollok(view->win, TRUE);
1906 redraw_view_dirty(struct view *view)
1911 for (lineno = 0; lineno < view->height; lineno++) {
1912 struct line *line = &view->line[view->offset + lineno];
1918 if (!draw_view_line(view, lineno))
1924 redrawwin(view->win);
1926 wnoutrefresh(view->win);
1928 wrefresh(view->win);
1932 redraw_view_from(struct view *view, int lineno)
1934 assert(0 <= lineno && lineno < view->height);
1936 for (; lineno < view->height; lineno++) {
1937 if (!draw_view_line(view, lineno))
1941 redrawwin(view->win);
1943 wnoutrefresh(view->win);
1945 wrefresh(view->win);
1949 redraw_view(struct view *view)
1952 redraw_view_from(view, 0);
1957 update_view_title(struct view *view)
1959 char buf[SIZEOF_STR];
1960 char state[SIZEOF_STR];
1961 size_t bufpos = 0, statelen = 0;
1963 assert(view_is_displayed(view));
1965 if (view != VIEW(REQ_VIEW_STATUS) && (view->lines || view->pipe)) {
1966 unsigned int view_lines = view->offset + view->height;
1967 unsigned int lines = view->lines
1968 ? MIN(view_lines, view->lines) * 100 / view->lines
1971 string_format_from(state, &statelen, "- %s %d of %d (%d%%)",
1978 time_t secs = time(NULL) - view->start_time;
1980 /* Three git seconds are a long time ... */
1982 string_format_from(state, &statelen, " %lds", secs);
1986 string_format_from(buf, &bufpos, "[%s]", view->name);
1987 if (*view->ref && bufpos < view->width) {
1988 size_t refsize = strlen(view->ref);
1989 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
1991 if (minsize < view->width)
1992 refsize = view->width - minsize + 7;
1993 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
1996 if (statelen && bufpos < view->width) {
1997 string_format_from(buf, &bufpos, " %s", state);
2000 if (view == display[current_view])
2001 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2003 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2005 mvwaddnstr(view->title, 0, 0, buf, bufpos);
2006 wclrtoeol(view->title);
2007 wmove(view->title, 0, view->width - 1);
2010 wnoutrefresh(view->title);
2012 wrefresh(view->title);
2016 resize_display(void)
2019 struct view *base = display[0];
2020 struct view *view = display[1] ? display[1] : display[0];
2022 /* Setup window dimensions */
2024 getmaxyx(stdscr, base->height, base->width);
2026 /* Make room for the status window. */
2030 /* Horizontal split. */
2031 view->width = base->width;
2032 view->height = SCALE_SPLIT_VIEW(base->height);
2033 base->height -= view->height;
2035 /* Make room for the title bar. */
2039 /* Make room for the title bar. */
2044 foreach_displayed_view (view, i) {
2046 view->win = newwin(view->height, 0, offset, 0);
2048 die("Failed to create %s view", view->name);
2050 scrollok(view->win, TRUE);
2052 view->title = newwin(1, 0, offset + view->height, 0);
2054 die("Failed to create title window");
2057 wresize(view->win, view->height, view->width);
2058 mvwin(view->win, offset, 0);
2059 mvwin(view->title, offset + view->height, 0);
2062 offset += view->height + 1;
2067 redraw_display(void)
2072 foreach_displayed_view (view, i) {
2074 update_view_title(view);
2079 update_display_cursor(struct view *view)
2081 /* Move the cursor to the right-most column of the cursor line.
2083 * XXX: This could turn out to be a bit expensive, but it ensures that
2084 * the cursor does not jump around. */
2086 wmove(view->win, view->lineno - view->offset, view->width - 1);
2087 wrefresh(view->win);
2095 /* Scrolling backend */
2097 do_scroll_view(struct view *view, int lines)
2099 bool redraw_current_line = FALSE;
2101 /* The rendering expects the new offset. */
2102 view->offset += lines;
2104 assert(0 <= view->offset && view->offset < view->lines);
2107 /* Move current line into the view. */
2108 if (view->lineno < view->offset) {
2109 view->lineno = view->offset;
2110 redraw_current_line = TRUE;
2111 } else if (view->lineno >= view->offset + view->height) {
2112 view->lineno = view->offset + view->height - 1;
2113 redraw_current_line = TRUE;
2116 assert(view->offset <= view->lineno && view->lineno < view->lines);
2118 /* Redraw the whole screen if scrolling is pointless. */
2119 if (view->height < ABS(lines)) {
2123 int line = lines > 0 ? view->height - lines : 0;
2124 int end = line + ABS(lines);
2126 wscrl(view->win, lines);
2128 for (; line < end; line++) {
2129 if (!draw_view_line(view, line))
2133 if (redraw_current_line)
2134 draw_view_line(view, view->lineno - view->offset);
2137 redrawwin(view->win);
2138 wrefresh(view->win);
2142 /* Scroll frontend */
2144 scroll_view(struct view *view, enum request request)
2148 assert(view_is_displayed(view));
2151 case REQ_SCROLL_PAGE_DOWN:
2152 lines = view->height;
2153 case REQ_SCROLL_LINE_DOWN:
2154 if (view->offset + lines > view->lines)
2155 lines = view->lines - view->offset;
2157 if (lines == 0 || view->offset + view->height >= view->lines) {
2158 report("Cannot scroll beyond the last line");
2163 case REQ_SCROLL_PAGE_UP:
2164 lines = view->height;
2165 case REQ_SCROLL_LINE_UP:
2166 if (lines > view->offset)
2167 lines = view->offset;
2170 report("Cannot scroll beyond the first line");
2178 die("request %d not handled in switch", request);
2181 do_scroll_view(view, lines);
2186 move_view(struct view *view, enum request request)
2188 int scroll_steps = 0;
2192 case REQ_MOVE_FIRST_LINE:
2193 steps = -view->lineno;
2196 case REQ_MOVE_LAST_LINE:
2197 steps = view->lines - view->lineno - 1;
2200 case REQ_MOVE_PAGE_UP:
2201 steps = view->height > view->lineno
2202 ? -view->lineno : -view->height;
2205 case REQ_MOVE_PAGE_DOWN:
2206 steps = view->lineno + view->height >= view->lines
2207 ? view->lines - view->lineno - 1 : view->height;
2219 die("request %d not handled in switch", request);
2222 if (steps <= 0 && view->lineno == 0) {
2223 report("Cannot move beyond the first line");
2226 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2227 report("Cannot move beyond the last line");
2231 /* Move the current line */
2232 view->lineno += steps;
2233 assert(0 <= view->lineno && view->lineno < view->lines);
2235 /* Check whether the view needs to be scrolled */
2236 if (view->lineno < view->offset ||
2237 view->lineno >= view->offset + view->height) {
2238 scroll_steps = steps;
2239 if (steps < 0 && -steps > view->offset) {
2240 scroll_steps = -view->offset;
2242 } else if (steps > 0) {
2243 if (view->lineno == view->lines - 1 &&
2244 view->lines > view->height) {
2245 scroll_steps = view->lines - view->offset - 1;
2246 if (scroll_steps >= view->height)
2247 scroll_steps -= view->height - 1;
2252 if (!view_is_displayed(view)) {
2253 view->offset += scroll_steps;
2254 assert(0 <= view->offset && view->offset < view->lines);
2255 view->ops->select(view, &view->line[view->lineno]);
2259 /* Repaint the old "current" line if we be scrolling */
2260 if (ABS(steps) < view->height)
2261 draw_view_line(view, view->lineno - steps - view->offset);
2264 do_scroll_view(view, scroll_steps);
2268 /* Draw the current line */
2269 draw_view_line(view, view->lineno - view->offset);
2271 redrawwin(view->win);
2272 wrefresh(view->win);
2281 static void search_view(struct view *view, enum request request);
2284 find_next_line(struct view *view, unsigned long lineno, struct line *line)
2286 assert(view_is_displayed(view));
2288 if (!view->ops->grep(view, line))
2291 if (lineno - view->offset >= view->height) {
2292 view->offset = lineno;
2293 view->lineno = lineno;
2297 unsigned long old_lineno = view->lineno - view->offset;
2299 view->lineno = lineno;
2300 draw_view_line(view, old_lineno);
2302 draw_view_line(view, view->lineno - view->offset);
2303 redrawwin(view->win);
2304 wrefresh(view->win);
2307 report("Line %ld matches '%s'", lineno + 1, view->grep);
2312 find_next(struct view *view, enum request request)
2314 unsigned long lineno = view->lineno;
2319 report("No previous search");
2321 search_view(view, request);
2331 case REQ_SEARCH_BACK:
2340 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2341 lineno += direction;
2343 /* Note, lineno is unsigned long so will wrap around in which case it
2344 * will become bigger than view->lines. */
2345 for (; lineno < view->lines; lineno += direction) {
2346 struct line *line = &view->line[lineno];
2348 if (find_next_line(view, lineno, line))
2352 report("No match found for '%s'", view->grep);
2356 search_view(struct view *view, enum request request)
2361 regfree(view->regex);
2364 view->regex = calloc(1, sizeof(*view->regex));
2369 regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2370 if (regex_err != 0) {
2371 char buf[SIZEOF_STR] = "unknown error";
2373 regerror(regex_err, view->regex, buf, sizeof(buf));
2374 report("Search failed: %s", buf);
2378 string_copy(view->grep, opt_search);
2380 find_next(view, request);
2384 * Incremental updating
2388 reset_view(struct view *view)
2392 for (i = 0; i < view->lines; i++)
2393 free(view->line[i].data);
2400 view->line_size = 0;
2401 view->line_alloc = 0;
2406 free_argv(const char *argv[])
2410 for (argc = 0; argv[argc]; argc++)
2411 free((void *) argv[argc]);
2415 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
2417 char buf[SIZEOF_STR];
2419 bool noreplace = flags == FORMAT_NONE;
2421 free_argv(dst_argv);
2423 for (argc = 0; src_argv[argc]; argc++) {
2424 const char *arg = src_argv[argc];
2428 char *next = strstr(arg, "%(");
2429 int len = next - arg;
2432 if (!next || noreplace) {
2433 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
2438 } else if (!prefixcmp(next, "%(directory)")) {
2441 } else if (!prefixcmp(next, "%(file)")) {
2444 } else if (!prefixcmp(next, "%(ref)")) {
2445 value = *opt_ref ? opt_ref : "HEAD";
2447 } else if (!prefixcmp(next, "%(head)")) {
2450 } else if (!prefixcmp(next, "%(commit)")) {
2453 } else if (!prefixcmp(next, "%(blob)")) {
2457 report("Unknown replacement: `%s`", next);
2461 if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
2464 arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
2467 dst_argv[argc] = strdup(buf);
2468 if (!dst_argv[argc])
2472 dst_argv[argc] = NULL;
2474 return src_argv[argc] == NULL;
2478 format_command(char dst[], const char *src_argv[], enum format_flags flags)
2480 const char *dst_argv[SIZEOF_ARG * 2] = { NULL };
2484 if (!format_argv(dst_argv, src_argv, flags)) {
2485 free_argv(dst_argv);
2489 for (argc = 0; dst_argv[argc] && bufsize < SIZEOF_STR; argc++) {
2491 dst[bufsize++] = ' ';
2492 bufsize = sq_quote(dst, bufsize, dst_argv[argc]);
2495 if (bufsize < SIZEOF_STR)
2497 free_argv(dst_argv);
2499 return src_argv[argc] == NULL && bufsize < SIZEOF_STR;
2503 end_update(struct view *view, bool force)
2507 while (!view->ops->read(view, NULL))
2510 set_nonblocking_input(FALSE);
2511 done_io(view->pipe);
2516 setup_update(struct view *view, const char *vid)
2518 set_nonblocking_input(TRUE);
2520 string_copy_rev(view->vid, vid);
2521 view->pipe = &view->io;
2522 view->start_time = time(NULL);
2526 prepare_update(struct view *view, const char *argv[], const char *dir,
2527 enum format_flags flags)
2530 end_update(view, TRUE);
2531 return init_io_rd(&view->io, argv, dir, flags);
2535 begin_update(struct view *view, bool refresh)
2537 if (init_io_fd(&view->io, opt_pipe)) {
2540 } else if (opt_cmd[0]) {
2541 if (!run_io(&view->io, IO_RD, opt_cmd))
2546 } else if (refresh) {
2547 if (!start_io(&view->io))
2550 } else if (view == VIEW(REQ_VIEW_TREE)) {
2551 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
2552 char path[SIZEOF_STR];
2554 if (strcmp(view->vid, view->id))
2555 opt_path[0] = path[0] = 0;
2556 else if (sq_quote(path, 0, opt_path) >= sizeof(path))
2559 if (!run_io_format(&view->io, format, view->id, path))
2563 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
2564 const char *id = view->id;
2566 if (!run_io_format(&view->io, format, id, id, id, id, id))
2569 /* Put the current ref_* value to the view title ref
2570 * member. This is needed by the blob view. Most other
2571 * views sets it automatically after loading because the
2572 * first line is a commit line. */
2573 string_copy_rev(view->ref, view->id);
2576 setup_update(view, view->id);
2581 #define ITEM_CHUNK_SIZE 256
2583 realloc_items(void *mem, size_t *size, size_t new_size, size_t item_size)
2585 size_t num_chunks = *size / ITEM_CHUNK_SIZE;
2586 size_t num_chunks_new = (new_size + ITEM_CHUNK_SIZE - 1) / ITEM_CHUNK_SIZE;
2588 if (mem == NULL || num_chunks != num_chunks_new) {
2589 *size = num_chunks_new * ITEM_CHUNK_SIZE;
2590 mem = realloc(mem, *size * item_size);
2596 static struct line *
2597 realloc_lines(struct view *view, size_t line_size)
2599 size_t alloc = view->line_alloc;
2600 struct line *tmp = realloc_items(view->line, &alloc, line_size,
2601 sizeof(*view->line));
2607 view->line_alloc = alloc;
2608 view->line_size = line_size;
2613 update_view(struct view *view)
2615 char out_buffer[BUFSIZ * 2];
2617 /* The number of lines to read. If too low it will cause too much
2618 * redrawing (and possible flickering), if too high responsiveness
2620 unsigned long lines = view->height;
2621 int redraw_from = -1;
2626 /* Only redraw if lines are visible. */
2627 if (view->offset + view->height >= view->lines)
2628 redraw_from = view->lines - view->offset;
2630 /* FIXME: This is probably not perfect for backgrounded views. */
2631 if (!realloc_lines(view, view->lines + lines))
2634 while ((line = io_gets(view->pipe))) {
2635 size_t linelen = strlen(line);
2638 line[linelen - 1] = 0;
2640 if (opt_iconv != ICONV_NONE) {
2641 ICONV_CONST char *inbuf = line;
2642 size_t inlen = linelen;
2644 char *outbuf = out_buffer;
2645 size_t outlen = sizeof(out_buffer);
2649 ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2650 if (ret != (size_t) -1) {
2652 linelen = strlen(out_buffer);
2656 if (!view->ops->read(view, line))
2666 lines = view->lines;
2667 for (digits = 0; lines; digits++)
2670 /* Keep the displayed view in sync with line number scaling. */
2671 if (digits != view->digits) {
2672 view->digits = digits;
2677 if (io_error(view->pipe)) {
2678 report("Failed to read: %s", io_strerror(view->pipe));
2679 end_update(view, TRUE);
2681 } else if (io_eof(view->pipe)) {
2683 end_update(view, FALSE);
2686 if (!view_is_displayed(view))
2689 if (view == VIEW(REQ_VIEW_TREE)) {
2690 /* Clear the view and redraw everything since the tree sorting
2691 * might have rearranged things. */
2694 } else if (redraw_from >= 0) {
2695 /* If this is an incremental update, redraw the previous line
2696 * since for commits some members could have changed when
2697 * loading the main view. */
2698 if (redraw_from > 0)
2701 /* Since revision graph visualization requires knowledge
2702 * about the parent commit, it causes a further one-off
2703 * needed to be redrawn for incremental updates. */
2704 if (redraw_from > 0 && opt_rev_graph)
2707 /* Incrementally draw avoids flickering. */
2708 redraw_view_from(view, redraw_from);
2711 if (view == VIEW(REQ_VIEW_BLAME))
2712 redraw_view_dirty(view);
2714 /* Update the title _after_ the redraw so that if the redraw picks up a
2715 * commit reference in view->ref it'll be available here. */
2716 update_view_title(view);
2720 report("Allocation failure");
2721 end_update(view, TRUE);
2725 static struct line *
2726 add_line_data(struct view *view, void *data, enum line_type type)
2728 struct line *line = &view->line[view->lines++];
2730 memset(line, 0, sizeof(*line));
2737 static struct line *
2738 add_line_text(struct view *view, const char *text, enum line_type type)
2740 char *data = text ? strdup(text) : NULL;
2742 return data ? add_line_data(view, data, type) : NULL;
2751 OPEN_DEFAULT = 0, /* Use default view switching. */
2752 OPEN_SPLIT = 1, /* Split current view. */
2753 OPEN_BACKGROUNDED = 2, /* Backgrounded. */
2754 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
2755 OPEN_NOMAXIMIZE = 8, /* Do not maximize the current view. */
2756 OPEN_REFRESH = 16, /* Refresh view using previous command. */
2757 OPEN_PREPARED = 32, /* Open already prepared command. */
2761 open_view(struct view *prev, enum request request, enum open_flags flags)
2763 bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
2764 bool split = !!(flags & OPEN_SPLIT);
2765 bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
2766 bool nomaximize = !!(flags & (OPEN_NOMAXIMIZE | OPEN_REFRESH));
2767 struct view *view = VIEW(request);
2768 int nviews = displayed_views();
2769 struct view *base_view = display[0];
2771 if (view == prev && nviews == 1 && !reload) {
2772 report("Already in %s view", view->name);
2776 if (view->git_dir && !opt_git_dir[0]) {
2777 report("The %s view is disabled in pager view", view->name);
2785 } else if (!nomaximize) {
2786 /* Maximize the current view. */
2787 memset(display, 0, sizeof(display));
2789 display[current_view] = view;
2792 /* Resize the view when switching between split- and full-screen,
2793 * or when switching between two different full-screen views. */
2794 if (nviews != displayed_views() ||
2795 (nviews == 1 && base_view != display[0]))
2799 end_update(view, TRUE);
2801 if (view->ops->open) {
2802 if (!view->ops->open(view)) {
2803 report("Failed to load %s view", view->name);
2807 } else if ((reload || strcmp(view->vid, view->id)) &&
2808 !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
2809 report("Failed to load %s view", view->name);
2813 if (split && prev->lineno - prev->offset >= prev->height) {
2814 /* Take the title line into account. */
2815 int lines = prev->lineno - prev->offset - prev->height + 1;
2817 /* Scroll the view that was split if the current line is
2818 * outside the new limited view. */
2819 do_scroll_view(prev, lines);
2822 if (prev && view != prev) {
2823 if (split && !backgrounded) {
2824 /* "Blur" the previous view. */
2825 update_view_title(prev);
2828 view->parent = prev;
2831 if (view->pipe && view->lines == 0) {
2832 /* Clear the old view and let the incremental updating refill
2836 } else if (view_is_displayed(view)) {
2841 /* If the view is backgrounded the above calls to report()
2842 * won't redraw the view title. */
2844 update_view_title(view);
2848 open_external_viewer(const char *argv[], const char *dir)
2850 def_prog_mode(); /* save current tty modes */
2851 endwin(); /* restore original tty modes */
2852 run_io_fg(argv, dir);
2853 fprintf(stderr, "Press Enter to continue");
2860 open_mergetool(const char *file)
2862 const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
2864 open_external_viewer(mergetool_argv, NULL);
2868 open_editor(bool from_root, const char *file)
2870 const char *editor_argv[] = { "vi", file, NULL };
2873 editor = getenv("GIT_EDITOR");
2874 if (!editor && *opt_editor)
2875 editor = opt_editor;
2877 editor = getenv("VISUAL");
2879 editor = getenv("EDITOR");
2883 editor_argv[0] = editor;
2884 open_external_viewer(editor_argv, from_root ? opt_cdup : NULL);
2888 open_run_request(enum request request)
2890 struct run_request *req = get_run_request(request);
2891 const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
2894 report("Unknown run request");
2898 if (format_argv(argv, req->argv, FORMAT_ALL))
2899 open_external_viewer(argv, NULL);
2904 * User request switch noodle
2908 view_driver(struct view *view, enum request request)
2912 if (request == REQ_NONE) {
2917 if (request > REQ_NONE) {
2918 open_run_request(request);
2919 /* FIXME: When all views can refresh always do this. */
2920 if (view == VIEW(REQ_VIEW_STATUS) ||
2921 view == VIEW(REQ_VIEW_MAIN) ||
2922 view == VIEW(REQ_VIEW_LOG) ||
2923 view == VIEW(REQ_VIEW_STAGE))
2924 request = REQ_REFRESH;
2929 if (view && view->lines) {
2930 request = view->ops->request(view, request, &view->line[view->lineno]);
2931 if (request == REQ_NONE)
2938 case REQ_MOVE_PAGE_UP:
2939 case REQ_MOVE_PAGE_DOWN:
2940 case REQ_MOVE_FIRST_LINE:
2941 case REQ_MOVE_LAST_LINE:
2942 move_view(view, request);
2945 case REQ_SCROLL_LINE_DOWN:
2946 case REQ_SCROLL_LINE_UP:
2947 case REQ_SCROLL_PAGE_DOWN:
2948 case REQ_SCROLL_PAGE_UP:
2949 scroll_view(view, request);
2952 case REQ_VIEW_BLAME:
2954 report("No file chosen, press %s to open tree view",
2955 get_key(REQ_VIEW_TREE));
2958 open_view(view, request, OPEN_DEFAULT);
2963 report("No file chosen, press %s to open tree view",
2964 get_key(REQ_VIEW_TREE));
2967 open_view(view, request, OPEN_DEFAULT);
2970 case REQ_VIEW_PAGER:
2971 if (!opt_pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
2972 report("No pager content, press %s to run command from prompt",
2973 get_key(REQ_PROMPT));
2976 open_view(view, request, OPEN_DEFAULT);
2979 case REQ_VIEW_STAGE:
2980 if (!VIEW(REQ_VIEW_STAGE)->lines) {
2981 report("No stage content, press %s to open the status view and choose file",
2982 get_key(REQ_VIEW_STATUS));
2985 open_view(view, request, OPEN_DEFAULT);
2988 case REQ_VIEW_STATUS:
2989 if (opt_is_inside_work_tree == FALSE) {
2990 report("The status view requires a working tree");
2993 open_view(view, request, OPEN_DEFAULT);
3001 open_view(view, request, OPEN_DEFAULT);
3006 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3008 if ((view == VIEW(REQ_VIEW_DIFF) &&
3009 view->parent == VIEW(REQ_VIEW_MAIN)) ||
3010 (view == VIEW(REQ_VIEW_DIFF) &&
3011 view->parent == VIEW(REQ_VIEW_BLAME)) ||
3012 (view == VIEW(REQ_VIEW_STAGE) &&
3013 view->parent == VIEW(REQ_VIEW_STATUS)) ||
3014 (view == VIEW(REQ_VIEW_BLOB) &&
3015 view->parent == VIEW(REQ_VIEW_TREE))) {
3018 view = view->parent;
3019 line = view->lineno;
3020 move_view(view, request);
3021 if (view_is_displayed(view))
3022 update_view_title(view);
3023 if (line != view->lineno)
3024 view->ops->request(view, REQ_ENTER,
3025 &view->line[view->lineno]);
3028 move_view(view, request);
3034 int nviews = displayed_views();
3035 int next_view = (current_view + 1) % nviews;
3037 if (next_view == current_view) {
3038 report("Only one view is displayed");
3042 current_view = next_view;
3043 /* Blur out the title of the previous view. */
3044 update_view_title(view);
3049 report("Refreshing is not yet supported for the %s view", view->name);
3053 if (displayed_views() == 2)
3054 open_view(view, VIEW_REQ(view), OPEN_DEFAULT);
3057 case REQ_TOGGLE_LINENO:
3058 opt_line_number = !opt_line_number;
3062 case REQ_TOGGLE_DATE:
3063 opt_date = !opt_date;
3067 case REQ_TOGGLE_AUTHOR:
3068 opt_author = !opt_author;
3072 case REQ_TOGGLE_REV_GRAPH:
3073 opt_rev_graph = !opt_rev_graph;
3077 case REQ_TOGGLE_REFS:
3078 opt_show_refs = !opt_show_refs;
3083 case REQ_SEARCH_BACK:
3084 search_view(view, request);
3089 find_next(view, request);
3092 case REQ_STOP_LOADING:
3093 for (i = 0; i < ARRAY_SIZE(views); i++) {
3096 report("Stopped loading the %s view", view->name),
3097 end_update(view, TRUE);
3101 case REQ_SHOW_VERSION:
3102 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3105 case REQ_SCREEN_RESIZE:
3108 case REQ_SCREEN_REDRAW:
3113 report("Nothing to edit");
3117 report("Nothing to enter");
3120 case REQ_VIEW_CLOSE:
3121 /* XXX: Mark closed views by letting view->parent point to the
3122 * view itself. Parents to closed view should never be
3125 view->parent->parent != view->parent) {
3126 memset(display, 0, sizeof(display));
3128 display[current_view] = view->parent;
3129 view->parent = view;
3140 report("Unknown key, press 'h' for help");
3153 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3155 char *text = line->data;
3157 if (opt_line_number && draw_lineno(view, lineno))
3160 draw_text(view, line->type, text, TRUE);
3165 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3167 char refbuf[SIZEOF_STR];
3171 if (!string_format(refbuf, "git describe %s 2>/dev/null", commit_id))
3174 pipe = popen(refbuf, "r");
3178 if ((ref = fgets(refbuf, sizeof(refbuf), pipe)))
3179 ref = chomp_string(ref);
3185 /* This is the only fatal call, since it can "corrupt" the buffer. */
3186 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
3193 add_pager_refs(struct view *view, struct line *line)
3195 char buf[SIZEOF_STR];
3196 char *commit_id = (char *)line->data + STRING_SIZE("commit ");
3198 size_t bufpos = 0, refpos = 0;
3199 const char *sep = "Refs: ";
3200 bool is_tag = FALSE;
3202 assert(line->type == LINE_COMMIT);
3204 refs = get_refs(commit_id);
3206 if (view == VIEW(REQ_VIEW_DIFF))
3207 goto try_add_describe_ref;
3212 struct ref *ref = refs[refpos];
3213 const char *fmt = ref->tag ? "%s[%s]" :
3214 ref->remote ? "%s<%s>" : "%s%s";
3216 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
3221 } while (refs[refpos++]->next);
3223 if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
3224 try_add_describe_ref:
3225 /* Add <tag>-g<commit_id> "fake" reference. */
3226 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
3233 if (!realloc_lines(view, view->line_size + 1))
3236 add_line_text(view, buf, LINE_PP_REFS);
3240 pager_read(struct view *view, char *data)
3247 line = add_line_text(view, data, get_line_type(data));
3251 if (line->type == LINE_COMMIT &&
3252 (view == VIEW(REQ_VIEW_DIFF) ||
3253 view == VIEW(REQ_VIEW_LOG)))
3254 add_pager_refs(view, line);
3260 pager_request(struct view *view, enum request request, struct line *line)
3264 if (request != REQ_ENTER)
3267 if (line->type == LINE_COMMIT &&
3268 (view == VIEW(REQ_VIEW_LOG) ||
3269 view == VIEW(REQ_VIEW_PAGER))) {
3270 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3274 /* Always scroll the view even if it was split. That way
3275 * you can use Enter to scroll through the log view and
3276 * split open each commit diff. */
3277 scroll_view(view, REQ_SCROLL_LINE_DOWN);
3279 /* FIXME: A minor workaround. Scrolling the view will call report("")
3280 * but if we are scrolling a non-current view this won't properly
3281 * update the view title. */
3283 update_view_title(view);
3289 pager_grep(struct view *view, struct line *line)
3292 char *text = line->data;
3297 if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
3304 pager_select(struct view *view, struct line *line)
3306 if (line->type == LINE_COMMIT) {
3307 char *text = (char *)line->data + STRING_SIZE("commit ");
3309 if (view != VIEW(REQ_VIEW_PAGER))
3310 string_copy_rev(view->ref, text);
3311 string_copy_rev(ref_commit, text);
3315 static struct view_ops pager_ops = {
3326 log_request(struct view *view, enum request request, struct line *line)
3331 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
3334 return pager_request(view, request, line);
3338 static struct view_ops log_ops = {
3354 help_open(struct view *view)
3357 int lines = ARRAY_SIZE(req_info) + 2;
3360 if (view->lines > 0)
3363 for (i = 0; i < ARRAY_SIZE(req_info); i++)
3364 if (!req_info[i].request)
3367 lines += run_requests + 1;
3369 view->line = calloc(lines, sizeof(*view->line));
3373 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
3375 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
3378 if (req_info[i].request == REQ_NONE)
3381 if (!req_info[i].request) {
3382 add_line_text(view, "", LINE_DEFAULT);
3383 add_line_text(view, req_info[i].help, LINE_DEFAULT);
3387 key = get_key(req_info[i].request);
3389 key = "(no key defined)";
3391 if (!string_format(buf, " %-25s %s", key, req_info[i].help))
3394 add_line_text(view, buf, LINE_DEFAULT);
3398 add_line_text(view, "", LINE_DEFAULT);
3399 add_line_text(view, "External commands:", LINE_DEFAULT);
3402 for (i = 0; i < run_requests; i++) {
3403 struct run_request *req = get_run_request(REQ_NONE + i + 1);
3405 char cmd[SIZEOF_STR];
3412 key = get_key_name(req->key);
3414 key = "(no key defined)";
3416 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
3417 if (!string_format_from(cmd, &bufpos, "%s%s",
3418 argc ? " " : "", req->argv[argc]))
3421 if (!string_format(buf, " %-10s %-14s `%s`",
3422 keymap_table[req->keymap].name, key, cmd))
3425 add_line_text(view, buf, LINE_DEFAULT);
3431 static struct view_ops help_ops = {
3446 struct tree_stack_entry {
3447 struct tree_stack_entry *prev; /* Entry below this in the stack */
3448 unsigned long lineno; /* Line number to restore */
3449 char *name; /* Position of name in opt_path */
3452 /* The top of the path stack. */
3453 static struct tree_stack_entry *tree_stack = NULL;
3454 unsigned long tree_lineno = 0;
3457 pop_tree_stack_entry(void)
3459 struct tree_stack_entry *entry = tree_stack;
3461 tree_lineno = entry->lineno;
3463 tree_stack = entry->prev;
3468 push_tree_stack_entry(const char *name, unsigned long lineno)
3470 struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
3471 size_t pathlen = strlen(opt_path);
3476 entry->prev = tree_stack;
3477 entry->name = opt_path + pathlen;
3480 if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
3481 pop_tree_stack_entry();
3485 /* Move the current line to the first tree entry. */
3487 entry->lineno = lineno;
3490 /* Parse output from git-ls-tree(1):
3492 * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile
3493 * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README
3494 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
3495 * 100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38 web.conf
3498 #define SIZEOF_TREE_ATTR \
3499 STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t")
3501 #define TREE_UP_FORMAT "040000 tree %s\t.."
3504 tree_compare_entry(enum line_type type1, const char *name1,
3505 enum line_type type2, const char *name2)
3507 if (type1 != type2) {
3508 if (type1 == LINE_TREE_DIR)
3513 return strcmp(name1, name2);
3517 tree_path(struct line *line)
3519 const char *path = line->data;
3521 return path + SIZEOF_TREE_ATTR;
3525 tree_read(struct view *view, char *text)
3527 size_t textlen = text ? strlen(text) : 0;
3528 char buf[SIZEOF_STR];
3530 enum line_type type;
3531 bool first_read = view->lines == 0;
3535 if (textlen <= SIZEOF_TREE_ATTR)
3538 type = text[STRING_SIZE("100644 ")] == 't'
3539 ? LINE_TREE_DIR : LINE_TREE_FILE;
3542 /* Add path info line */
3543 if (!string_format(buf, "Directory path /%s", opt_path) ||
3544 !realloc_lines(view, view->line_size + 1) ||
3545 !add_line_text(view, buf, LINE_DEFAULT))
3548 /* Insert "link" to parent directory. */
3550 if (!string_format(buf, TREE_UP_FORMAT, view->ref) ||
3551 !realloc_lines(view, view->line_size + 1) ||
3552 !add_line_text(view, buf, LINE_TREE_DIR))
3557 /* Strip the path part ... */
3559 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
3560 size_t striplen = strlen(opt_path);
3561 char *path = text + SIZEOF_TREE_ATTR;
3563 if (pathlen > striplen)
3564 memmove(path, path + striplen,
3565 pathlen - striplen + 1);
3568 /* Skip "Directory ..." and ".." line. */
3569 for (pos = 1 + !!*opt_path; pos < view->lines; pos++) {
3570 struct line *line = &view->line[pos];
3571 const char *path1 = tree_path(line);
3572 char *path2 = text + SIZEOF_TREE_ATTR;
3573 int cmp = tree_compare_entry(line->type, path1, type, path2);
3578 text = strdup(text);
3582 if (view->lines > pos)
3583 memmove(&view->line[pos + 1], &view->line[pos],
3584 (view->lines - pos) * sizeof(*line));
3586 line = &view->line[pos];
3593 if (!add_line_text(view, text, type))
3596 if (tree_lineno > view->lineno) {
3597 view->lineno = tree_lineno;
3605 tree_request(struct view *view, enum request request, struct line *line)
3607 enum open_flags flags;
3610 case REQ_VIEW_BLAME:
3611 if (line->type != LINE_TREE_FILE) {
3612 report("Blame only supported for files");
3616 string_copy(opt_ref, view->vid);
3620 if (line->type != LINE_TREE_FILE) {
3621 report("Edit only supported for files");
3622 } else if (!is_head_commit(view->vid)) {
3623 report("Edit only supported for files in the current work tree");
3625 open_editor(TRUE, opt_file);
3629 case REQ_TREE_PARENT:
3631 /* quit view if at top of tree */
3632 return REQ_VIEW_CLOSE;
3635 line = &view->line[1];
3645 /* Cleanup the stack if the tree view is at a different tree. */
3646 while (!*opt_path && tree_stack)
3647 pop_tree_stack_entry();
3649 switch (line->type) {
3651 /* Depending on whether it is a subdir or parent (updir?) link
3652 * mangle the path buffer. */
3653 if (line == &view->line[1] && *opt_path) {
3654 pop_tree_stack_entry();
3657 const char *basename = tree_path(line);
3659 push_tree_stack_entry(basename, view->lineno);
3662 /* Trees and subtrees share the same ID, so they are not not
3663 * unique like blobs. */
3664 flags = OPEN_RELOAD;
3665 request = REQ_VIEW_TREE;
3668 case LINE_TREE_FILE:
3669 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
3670 request = REQ_VIEW_BLOB;
3677 open_view(view, request, flags);
3678 if (request == REQ_VIEW_TREE) {
3679 view->lineno = tree_lineno;
3686 tree_select(struct view *view, struct line *line)
3688 char *text = (char *)line->data + STRING_SIZE("100644 blob ");
3690 if (line->type == LINE_TREE_FILE) {
3691 string_copy_rev(ref_blob, text);
3692 string_format(opt_file, "%s%s", opt_path, tree_path(line));
3694 } else if (line->type != LINE_TREE_DIR) {
3698 string_copy_rev(view->ref, text);
3701 static struct view_ops tree_ops = {
3712 blob_read(struct view *view, char *line)
3716 return add_line_text(view, line, LINE_DEFAULT) != NULL;
3719 static struct view_ops blob_ops = {
3732 * Loading the blame view is a two phase job:
3734 * 1. File content is read either using opt_file from the
3735 * filesystem or using git-cat-file.
3736 * 2. Then blame information is incrementally added by
3737 * reading output from git-blame.
3740 struct blame_commit {
3741 char id[SIZEOF_REV]; /* SHA1 ID. */
3742 char title[128]; /* First line of the commit message. */
3743 char author[75]; /* Author of the commit. */
3744 struct tm time; /* Date from the author ident. */
3745 char filename[128]; /* Name of file. */
3749 struct blame_commit *commit;
3753 #define BLAME_CAT_FILE_CMD "git cat-file blob %s:%s"
3754 #define BLAME_INCREMENTAL_CMD "git blame --incremental %s -- %s"
3757 blame_open(struct view *view)
3759 char path[SIZEOF_STR];
3760 char ref[SIZEOF_STR] = "";
3762 if (sq_quote(path, 0, opt_file) >= sizeof(path))
3765 if (*opt_ref && sq_quote(ref, 0, opt_ref) >= sizeof(ref))
3768 if (*opt_ref || !init_io_fd(&view->io, fopen(opt_file, "r"))) {
3769 const char *id = *opt_ref ? ref : "HEAD";
3771 if (!run_io_format(&view->io, BLAME_CAT_FILE_CMD, id, path))
3775 setup_update(view, opt_file);
3776 string_format(view->ref, "%s ...", opt_file);
3781 static struct blame_commit *
3782 get_blame_commit(struct view *view, const char *id)
3786 for (i = 0; i < view->lines; i++) {
3787 struct blame *blame = view->line[i].data;
3792 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
3793 return blame->commit;
3797 struct blame_commit *commit = calloc(1, sizeof(*commit));
3800 string_ncopy(commit->id, id, SIZEOF_REV);
3806 parse_number(const char **posref, size_t *number, size_t min, size_t max)
3808 const char *pos = *posref;
3811 pos = strchr(pos + 1, ' ');
3812 if (!pos || !isdigit(pos[1]))
3814 *number = atoi(pos + 1);
3815 if (*number < min || *number > max)
3822 static struct blame_commit *
3823 parse_blame_commit(struct view *view, const char *text, int *blamed)
3825 struct blame_commit *commit;
3826 struct blame *blame;
3827 const char *pos = text + SIZEOF_REV - 1;
3831 if (strlen(text) <= SIZEOF_REV || *pos != ' ')
3834 if (!parse_number(&pos, &lineno, 1, view->lines) ||
3835 !parse_number(&pos, &group, 1, view->lines - lineno + 1))
3838 commit = get_blame_commit(view, text);
3844 struct line *line = &view->line[lineno + group - 1];
3847 blame->commit = commit;
3855 blame_read_file(struct view *view, const char *line, bool *read_file)
3858 char ref[SIZEOF_STR] = "";
3859 char path[SIZEOF_STR];
3862 if (view->lines == 0 && !view->parent)
3863 die("No blame exist for %s", view->vid);
3865 if (view->lines == 0 ||
3866 sq_quote(path, 0, opt_file) >= sizeof(path) ||
3867 (*opt_ref && sq_quote(ref, 0, opt_ref) >= sizeof(ref)) ||
3868 !run_io_format(&io, BLAME_INCREMENTAL_CMD, ref, path)) {
3869 report("Failed to load blame data");
3873 done_io(view->pipe);
3879 size_t linelen = strlen(line);
3880 struct blame *blame = malloc(sizeof(*blame) + linelen);
3882 blame->commit = NULL;
3883 strncpy(blame->text, line, linelen);
3884 blame->text[linelen] = 0;
3885 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
3890 match_blame_header(const char *name, char **line)
3892 size_t namelen = strlen(name);
3893 bool matched = !strncmp(name, *line, namelen);
3902 blame_read(struct view *view, char *line)
3904 static struct blame_commit *commit = NULL;
3905 static int blamed = 0;
3906 static time_t author_time;
3907 static bool read_file = TRUE;
3910 return blame_read_file(view, line, &read_file);
3917 string_format(view->ref, "%s", view->vid);
3918 if (view_is_displayed(view)) {
3919 update_view_title(view);
3920 redraw_view_from(view, 0);
3926 commit = parse_blame_commit(view, line, &blamed);
3927 string_format(view->ref, "%s %2d%%", view->vid,
3928 blamed * 100 / view->lines);
3930 } else if (match_blame_header("author ", &line)) {
3931 string_ncopy(commit->author, line, strlen(line));
3933 } else if (match_blame_header("author-time ", &line)) {
3934 author_time = (time_t) atol(line);
3936 } else if (match_blame_header("author-tz ", &line)) {
3939 tz = ('0' - line[1]) * 60 * 60 * 10;
3940 tz += ('0' - line[2]) * 60 * 60;
3941 tz += ('0' - line[3]) * 60;
3942 tz += ('0' - line[4]) * 60;
3948 gmtime_r(&author_time, &commit->time);
3950 } else if (match_blame_header("summary ", &line)) {
3951 string_ncopy(commit->title, line, strlen(line));
3953 } else if (match_blame_header("filename ", &line)) {
3954 string_ncopy(commit->filename, line, strlen(line));
3962 blame_draw(struct view *view, struct line *line, unsigned int lineno)
3964 struct blame *blame = line->data;
3965 struct tm *time = NULL;
3966 const char *id = NULL, *author = NULL;
3968 if (blame->commit && *blame->commit->filename) {
3969 id = blame->commit->id;
3970 author = blame->commit->author;
3971 time = &blame->commit->time;
3974 if (opt_date && draw_date(view, time))
3978 draw_field(view, LINE_MAIN_AUTHOR, author, opt_author_cols, TRUE))
3981 if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
3984 if (draw_lineno(view, lineno))
3987 draw_text(view, LINE_DEFAULT, blame->text, TRUE);
3992 blame_request(struct view *view, enum request request, struct line *line)
3994 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
3995 struct blame *blame = line->data;
3998 case REQ_VIEW_BLAME:
3999 if (!blame->commit || !strcmp(blame->commit->id, NULL_ID)) {
4000 report("Commit ID unknown");
4003 string_copy(opt_ref, blame->commit->id);
4004 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4008 if (!blame->commit) {
4009 report("No commit loaded yet");
4013 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
4014 !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
4017 if (!strcmp(blame->commit->id, NULL_ID)) {
4018 char path[SIZEOF_STR];
4020 if (sq_quote(path, 0, view->vid) >= sizeof(path))
4022 string_format(opt_cmd, "git diff-index --root --patch-with-stat -C -M --cached HEAD -- %s 2>/dev/null", path);
4025 open_view(view, REQ_VIEW_DIFF, flags);
4036 blame_grep(struct view *view, struct line *line)
4038 struct blame *blame = line->data;
4039 struct blame_commit *commit = blame->commit;
4042 #define MATCH(text, on) \
4043 (on && *text && regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4046 char buf[DATE_COLS + 1];
4048 if (MATCH(commit->title, 1) ||
4049 MATCH(commit->author, opt_author) ||
4050 MATCH(commit->id, opt_date))
4053 if (strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time) &&
4058 return MATCH(blame->text, 1);
4064 blame_select(struct view *view, struct line *line)
4066 struct blame *blame = line->data;
4067 struct blame_commit *commit = blame->commit;
4072 if (!strcmp(commit->id, NULL_ID))
4073 string_ncopy(ref_commit, "HEAD", 4);
4075 string_copy_rev(ref_commit, commit->id);
4078 static struct view_ops blame_ops = {
4096 char rev[SIZEOF_REV];
4097 char name[SIZEOF_STR];
4101 char rev[SIZEOF_REV];
4102 char name[SIZEOF_STR];
4106 static char status_onbranch[SIZEOF_STR];
4107 static struct status stage_status;
4108 static enum line_type stage_line_type;
4109 static size_t stage_chunks;
4110 static int *stage_chunk;
4112 /* This should work even for the "On branch" line. */
4114 status_has_none(struct view *view, struct line *line)
4116 return line < view->line + view->lines && !line[1].data;
4119 /* Get fields from the diff line:
4120 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
4123 status_get_diff(struct status *file, const char *buf, size_t bufsize)
4125 const char *old_mode = buf + 1;
4126 const char *new_mode = buf + 8;
4127 const char *old_rev = buf + 15;
4128 const char *new_rev = buf + 56;
4129 const char *status = buf + 97;
4132 old_mode[-1] != ':' ||
4133 new_mode[-1] != ' ' ||
4134 old_rev[-1] != ' ' ||
4135 new_rev[-1] != ' ' ||
4139 file->status = *status;
4141 string_copy_rev(file->old.rev, old_rev);
4142 string_copy_rev(file->new.rev, new_rev);
4144 file->old.mode = strtoul(old_mode, NULL, 8);
4145 file->new.mode = strtoul(new_mode, NULL, 8);
4147 file->old.name[0] = file->new.name[0] = 0;
4153 status_run(struct view *view, const char cmd[], char status, enum line_type type)
4155 struct status *file = NULL;
4156 struct status *unmerged = NULL;
4157 char buf[SIZEOF_STR * 4];
4161 pipe = popen(cmd, "r");
4165 add_line_data(view, NULL, type);
4167 while (!feof(pipe) && !ferror(pipe)) {
4171 readsize = fread(buf + bufsize, 1, sizeof(buf) - bufsize, pipe);
4174 bufsize += readsize;
4176 /* Process while we have NUL chars. */
4177 while ((sep = memchr(buf, 0, bufsize))) {
4178 size_t sepsize = sep - buf + 1;
4181 if (!realloc_lines(view, view->line_size + 1))
4184 file = calloc(1, sizeof(*file));
4188 add_line_data(view, file, type);
4191 /* Parse diff info part. */
4193 file->status = status;
4195 string_copy(file->old.rev, NULL_ID);
4197 } else if (!file->status) {
4198 if (!status_get_diff(file, buf, sepsize))
4202 memmove(buf, sep + 1, bufsize);
4204 sep = memchr(buf, 0, bufsize);
4207 sepsize = sep - buf + 1;
4209 /* Collapse all 'M'odified entries that
4210 * follow a associated 'U'nmerged entry.
4212 if (file->status == 'U') {
4215 } else if (unmerged) {
4216 int collapse = !strcmp(buf, unmerged->new.name);
4227 /* Grab the old name for rename/copy. */
4228 if (!*file->old.name &&
4229 (file->status == 'R' || file->status == 'C')) {
4230 sepsize = sep - buf + 1;
4231 string_ncopy(file->old.name, buf, sepsize);
4233 memmove(buf, sep + 1, bufsize);
4235 sep = memchr(buf, 0, bufsize);
4238 sepsize = sep - buf + 1;
4241 /* git-ls-files just delivers a NUL separated
4242 * list of file names similar to the second half
4243 * of the git-diff-* output. */
4244 string_ncopy(file->new.name, buf, sepsize);
4245 if (!*file->old.name)
4246 string_copy(file->old.name, file->new.name);
4248 memmove(buf, sep + 1, bufsize);
4259 if (!view->line[view->lines - 1].data)
4260 add_line_data(view, NULL, LINE_STAT_NONE);
4266 /* Don't show unmerged entries in the staged section. */
4267 #define STATUS_DIFF_INDEX_CMD "git diff-index -z --diff-filter=ACDMRTXB --cached -M HEAD"
4268 #define STATUS_DIFF_FILES_CMD "git diff-files -z"
4269 #define STATUS_LIST_OTHER_CMD \
4270 "git ls-files -z --others --exclude-standard"
4271 #define STATUS_LIST_NO_HEAD_CMD \
4272 "git ls-files -z --cached --exclude-standard"
4274 #define STATUS_DIFF_INDEX_SHOW_CMD \
4275 "git diff-index --root --patch-with-stat -C -M --cached HEAD -- %s %s 2>/dev/null"
4277 #define STATUS_DIFF_FILES_SHOW_CMD \
4278 "git diff-files --root --patch-with-stat -C -M -- %s %s 2>/dev/null"
4280 #define STATUS_DIFF_NO_HEAD_SHOW_CMD \
4281 "git diff --no-color --patch-with-stat /dev/null %s 2>/dev/null"
4283 /* First parse staged info using git-diff-index(1), then parse unstaged
4284 * info using git-diff-files(1), and finally untracked files using
4285 * git-ls-files(1). */
4287 status_open(struct view *view)
4289 unsigned long prev_lineno = view->lineno;
4293 if (!realloc_lines(view, view->line_size + 7))
4296 add_line_data(view, NULL, LINE_STAT_HEAD);
4297 if (is_initial_commit())
4298 string_copy(status_onbranch, "Initial commit");
4299 else if (!*opt_head)
4300 string_copy(status_onbranch, "Not currently on any branch");
4301 else if (!string_format(status_onbranch, "On branch %s", opt_head))
4304 system("git update-index -q --refresh >/dev/null 2>/dev/null");
4306 if (is_initial_commit()) {
4307 if (!status_run(view, STATUS_LIST_NO_HEAD_CMD, 'A', LINE_STAT_STAGED))
4309 } else if (!status_run(view, STATUS_DIFF_INDEX_CMD, 0, LINE_STAT_STAGED)) {
4313 if (!status_run(view, STATUS_DIFF_FILES_CMD, 0, LINE_STAT_UNSTAGED) ||
4314 !status_run(view, STATUS_LIST_OTHER_CMD, '?', LINE_STAT_UNTRACKED))
4317 /* If all went well restore the previous line number to stay in
4318 * the context or select a line with something that can be
4320 if (prev_lineno >= view->lines)
4321 prev_lineno = view->lines - 1;
4322 while (prev_lineno < view->lines && !view->line[prev_lineno].data)
4324 while (prev_lineno > 0 && !view->line[prev_lineno].data)
4327 /* If the above fails, always skip the "On branch" line. */
4328 if (prev_lineno < view->lines)
4329 view->lineno = prev_lineno;
4333 if (view->lineno < view->offset)
4334 view->offset = view->lineno;
4335 else if (view->offset + view->height <= view->lineno)
4336 view->offset = view->lineno - view->height + 1;
4342 status_draw(struct view *view, struct line *line, unsigned int lineno)
4344 struct status *status = line->data;
4345 enum line_type type;
4349 switch (line->type) {
4350 case LINE_STAT_STAGED:
4351 type = LINE_STAT_SECTION;
4352 text = "Changes to be committed:";
4355 case LINE_STAT_UNSTAGED:
4356 type = LINE_STAT_SECTION;
4357 text = "Changed but not updated:";
4360 case LINE_STAT_UNTRACKED:
4361 type = LINE_STAT_SECTION;
4362 text = "Untracked files:";
4365 case LINE_STAT_NONE:
4366 type = LINE_DEFAULT;
4367 text = " (no files)";
4370 case LINE_STAT_HEAD:
4371 type = LINE_STAT_HEAD;
4372 text = status_onbranch;
4379 static char buf[] = { '?', ' ', ' ', ' ', 0 };
4381 buf[0] = status->status;
4382 if (draw_text(view, line->type, buf, TRUE))
4384 type = LINE_DEFAULT;
4385 text = status->new.name;
4388 draw_text(view, type, text, TRUE);
4393 status_enter(struct view *view, struct line *line)
4395 struct status *status = line->data;
4396 char oldpath[SIZEOF_STR] = "";
4397 char newpath[SIZEOF_STR] = "";
4400 enum open_flags split;
4402 if (line->type == LINE_STAT_NONE ||
4403 (!status && line[1].type == LINE_STAT_NONE)) {
4404 report("No file to diff");
4409 if (sq_quote(oldpath, 0, status->old.name) >= sizeof(oldpath))
4411 /* Diffs for unmerged entries are empty when pasing the
4412 * new path, so leave it empty. */
4413 if (status->status != 'U' &&
4414 sq_quote(newpath, 0, status->new.name) >= sizeof(newpath))
4419 line->type != LINE_STAT_UNTRACKED &&
4420 !string_format_from(opt_cmd, &cmdsize, "cd %s;", opt_cdup))
4423 switch (line->type) {
4424 case LINE_STAT_STAGED:
4425 if (is_initial_commit()) {
4426 if (!string_format_from(opt_cmd, &cmdsize,
4427 STATUS_DIFF_NO_HEAD_SHOW_CMD,
4431 if (!string_format_from(opt_cmd, &cmdsize,
4432 STATUS_DIFF_INDEX_SHOW_CMD,
4438 info = "Staged changes to %s";
4440 info = "Staged changes";
4443 case LINE_STAT_UNSTAGED:
4444 if (!string_format_from(opt_cmd, &cmdsize,
4445 STATUS_DIFF_FILES_SHOW_CMD, oldpath, newpath))
4448 info = "Unstaged changes to %s";
4450 info = "Unstaged changes";
4453 case LINE_STAT_UNTRACKED:
4458 report("No file to show");
4462 if (!suffixcmp(status->new.name, -1, "/")) {
4463 report("Cannot display a directory");
4467 opt_pipe = fopen(status->new.name, "r");
4468 info = "Untracked file %s";
4471 case LINE_STAT_HEAD:
4475 die("line type %d not handled in switch", line->type);
4478 split = view_is_displayed(view) ? OPEN_SPLIT : 0;
4479 open_view(view, REQ_VIEW_STAGE, OPEN_RELOAD | split);
4480 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
4482 stage_status = *status;
4484 memset(&stage_status, 0, sizeof(stage_status));
4487 stage_line_type = line->type;
4489 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
4496 status_exists(struct status *status, enum line_type type)
4498 struct view *view = VIEW(REQ_VIEW_STATUS);
4501 for (line = view->line; line < view->line + view->lines; line++) {
4502 struct status *pos = line->data;
4504 if (line->type == type && pos &&
4505 !strcmp(status->new.name, pos->new.name))
4514 status_update_prepare(enum line_type type)
4516 char cmd[SIZEOF_STR];
4520 type != LINE_STAT_UNTRACKED &&
4521 !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
4525 case LINE_STAT_STAGED:
4526 string_add(cmd, cmdsize, "git update-index -z --index-info");
4529 case LINE_STAT_UNSTAGED:
4530 case LINE_STAT_UNTRACKED:
4531 string_add(cmd, cmdsize, "git update-index -z --add --remove --stdin");
4535 die("line type %d not handled in switch", type);
4538 return popen(cmd, "w");
4542 status_update_write(FILE *pipe, struct status *status, enum line_type type)
4544 char buf[SIZEOF_STR];
4549 case LINE_STAT_STAGED:
4550 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
4553 status->old.name, 0))
4557 case LINE_STAT_UNSTAGED:
4558 case LINE_STAT_UNTRACKED:
4559 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
4564 die("line type %d not handled in switch", type);
4567 while (!ferror(pipe) && written < bufsize) {
4568 written += fwrite(buf + written, 1, bufsize - written, pipe);
4571 return written == bufsize;
4575 status_update_file(struct status *status, enum line_type type)
4577 FILE *pipe = status_update_prepare(type);
4583 result = status_update_write(pipe, status, type);
4589 status_update_files(struct view *view, struct line *line)
4591 FILE *pipe = status_update_prepare(line->type);
4593 struct line *pos = view->line + view->lines;
4600 for (pos = line; pos < view->line + view->lines && pos->data; pos++)
4603 for (file = 0, done = 0; result && file < files; line++, file++) {
4604 int almost_done = file * 100 / files;
4606 if (almost_done > done) {
4608 string_format(view->ref, "updating file %u of %u (%d%% done)",
4610 update_view_title(view);
4612 result = status_update_write(pipe, line->data, line->type);
4620 status_update(struct view *view)
4622 struct line *line = &view->line[view->lineno];
4624 assert(view->lines);
4627 /* This should work even for the "On branch" line. */
4628 if (line < view->line + view->lines && !line[1].data) {
4629 report("Nothing to update");
4633 if (!status_update_files(view, line + 1)) {
4634 report("Failed to update file status");
4638 } else if (!status_update_file(line->data, line->type)) {
4639 report("Failed to update file status");
4647 status_revert(struct status *status, enum line_type type, bool has_none)
4649 if (!status || type != LINE_STAT_UNSTAGED) {
4650 if (type == LINE_STAT_STAGED) {
4651 report("Cannot revert changes to staged files");
4652 } else if (type == LINE_STAT_UNTRACKED) {
4653 report("Cannot revert changes to untracked files");
4654 } else if (has_none) {
4655 report("Nothing to revert");
4657 report("Cannot revert changes to multiple files");
4662 const char *checkout_argv[] = {
4663 "git", "checkout", "--", status->old.name, NULL
4666 if (!prompt_yesno("Are you sure you want to overwrite any changes?"))
4668 return run_io_fg(checkout_argv, opt_cdup);
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_STR];
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(":");
6391 struct view *next = VIEW(REQ_VIEW_PAGER);
6392 const char *argv[SIZEOF_ARG] = { "git" };
6395 /* When running random commands, initially show the
6396 * command in the title. However, it maybe later be
6397 * overwritten if a commit line is selected. */
6398 string_ncopy(next->ref, cmd, strlen(cmd));
6400 if (!argv_from_string(argv, &argc, cmd)) {
6401 report("Too many arguments");
6402 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
6403 report("Failed to format command");
6405 open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
6413 case REQ_SEARCH_BACK:
6415 const char *prompt = request == REQ_SEARCH ? "/" : "?";
6416 char *search = read_prompt(prompt);
6419 string_ncopy(opt_search, search, strlen(search));
6424 case REQ_SCREEN_RESIZE:
6428 getmaxyx(stdscr, height, width);
6430 /* Resize the status view and let the view driver take
6431 * care of resizing the displayed views. */
6432 wresize(status_win, 1, width);
6433 mvwin(status_win, height - 1, 0);
6434 wrefresh(status_win);