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_MAIN_BASE \
126 "git log --no-color --pretty=raw --parents --topo-order"
128 /* Some ascii-shorthands fitted into the ncurses namespace. */
130 #define KEY_RETURN '\r'
135 char *name; /* Ref name; tag or head names are shortened. */
136 char id[SIZEOF_REV]; /* Commit SHA1 ID */
137 unsigned int head:1; /* Is it the current HEAD? */
138 unsigned int tag:1; /* Is it a tag? */
139 unsigned int ltag:1; /* If so, is the tag local? */
140 unsigned int remote:1; /* Is it a remote ref? */
141 unsigned int tracked:1; /* Is it the remote for the current HEAD? */
142 unsigned int next:1; /* For ref lists: are there more refs? */
145 static struct ref **get_refs(const char *id);
148 FORMAT_ALL, /* Perform replacement in all arguments. */
149 FORMAT_DASH, /* Perform replacement up until "--". */
150 FORMAT_NONE /* No replacement should be performed. */
153 static bool format_command(char dst[], const char *src[], enum format_flags flags);
154 static bool format_argv(const char *dst[], const char *src[], enum format_flags flags);
163 set_from_int_map(struct int_map *map, size_t map_size,
164 int *value, const char *name, int namelen)
169 for (i = 0; i < map_size; i++)
170 if (namelen == map[i].namelen &&
171 !strncasecmp(name, map[i].name, namelen)) {
172 *value = map[i].value;
185 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
187 if (srclen > dstlen - 1)
190 strncpy(dst, src, srclen);
194 /* Shorthands for safely copying into a fixed buffer. */
196 #define string_copy(dst, src) \
197 string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
199 #define string_ncopy(dst, src, srclen) \
200 string_ncopy_do(dst, sizeof(dst), src, srclen)
202 #define string_copy_rev(dst, src) \
203 string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
205 #define string_add(dst, from, src) \
206 string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
209 chomp_string(char *name)
213 while (isspace(*name))
216 namelen = strlen(name) - 1;
217 while (namelen > 0 && isspace(name[namelen]))
224 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
227 size_t pos = bufpos ? *bufpos : 0;
230 pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
236 return pos >= bufsize ? FALSE : TRUE;
239 #define string_format(buf, fmt, args...) \
240 string_nformat(buf, sizeof(buf), NULL, fmt, args)
242 #define string_format_from(buf, from, fmt, args...) \
243 string_nformat(buf, sizeof(buf), from, fmt, args)
246 string_enum_compare(const char *str1, const char *str2, int len)
250 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
252 /* Diff-Header == DIFF_HEADER */
253 for (i = 0; i < len; i++) {
254 if (toupper(str1[i]) == toupper(str2[i]))
257 if (string_enum_sep(str1[i]) &&
258 string_enum_sep(str2[i]))
261 return str1[i] - str2[i];
267 #define prefixcmp(str1, str2) \
268 strncmp(str1, str2, STRING_SIZE(str2))
271 suffixcmp(const char *str, int slen, const char *suffix)
273 size_t len = slen >= 0 ? slen : strlen(str);
274 size_t suffixlen = strlen(suffix);
276 return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
281 * NOTE: The following is a slightly modified copy of the git project's shell
282 * quoting routines found in the quote.c file.
284 * Help to copy the thing properly quoted for the shell safety. any single
285 * quote is replaced with '\'', any exclamation point is replaced with '\!',
286 * and the whole thing is enclosed in a
289 * original sq_quote result
290 * name ==> name ==> 'name'
291 * a b ==> a b ==> 'a b'
292 * a'b ==> a'\''b ==> 'a'\''b'
293 * a!b ==> a'\!'b ==> 'a'\!'b'
297 sq_quote(char buf[SIZEOF_STR], size_t bufsize, const char *src)
301 #define BUFPUT(x) do { if (bufsize < SIZEOF_STR) buf[bufsize++] = (x); } while (0)
304 while ((c = *src++)) {
305 if (c == '\'' || c == '!') {
316 if (bufsize < SIZEOF_STR)
323 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
327 while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
328 bool advance = cmd[valuelen] != 0;
331 argv[(*argc)++] = chomp_string(cmd);
332 cmd += valuelen + advance;
335 if (*argc < SIZEOF_ARG)
337 return *argc < SIZEOF_ARG;
341 argv_from_env(const char **argv, const char *name)
343 char *env = argv ? getenv(name) : NULL;
348 if (env && !argv_from_string(argv, &argc, env))
349 die("Too many arguments in the `%s` environment variable", name);
354 * Executing external commands.
358 IO_FD, /* File descriptor based IO. */
359 IO_FG, /* Execute command with same std{in,out,err}. */
360 IO_RD, /* Read only fork+exec IO. */
361 IO_WR, /* Write only fork+exec IO. */
365 enum io_type type; /* The requested type of pipe. */
366 const char *dir; /* Directory from which to execute. */
367 FILE *pipe; /* Pipe for reading or writing. */
368 int error; /* Error status. */
369 char sh[SIZEOF_STR]; /* Shell command buffer. */
370 char *buf; /* Read/write buffer. */
371 size_t bufalloc; /* Allocated buffer size. */
375 reset_io(struct io *io)
384 init_io(struct io *io, const char *dir, enum io_type type)
392 init_io_rd(struct io *io, const char *argv[], const char *dir,
393 enum format_flags flags)
395 init_io(io, dir, IO_RD);
396 return format_command(io->sh, argv, flags);
400 init_io_fd(struct io *io, FILE *pipe)
402 init_io(io, NULL, IO_FD);
404 return io->pipe != NULL;
408 done_io(struct io *io)
411 if (io->type == IO_FD)
413 else if (io->type == IO_RD || io->type == IO_WR)
420 start_io(struct io *io)
422 char buf[SIZEOF_STR * 2];
425 if (io->dir && *io->dir &&
426 !string_format_from(buf, &bufpos, "cd %s;", io->dir))
429 if (!string_format_from(buf, &bufpos, "%s", io->sh))
432 if (io->type == IO_FG)
433 return system(buf) == 0;
435 io->pipe = popen(io->sh, io->type == IO_RD ? "r" : "w");
436 return io->pipe != NULL;
440 run_io(struct io *io, enum io_type type, const char *cmd)
442 init_io(io, NULL, type);
443 string_ncopy(io->sh, cmd, strlen(cmd));
448 run_io_do(struct io *io)
450 return start_io(io) && done_io(io);
454 run_io_fg(const char **argv, const char *dir)
458 init_io(&io, dir, IO_FG);
459 if (!format_command(io.sh, argv, FORMAT_NONE))
461 return run_io_do(&io);
465 run_io_format(struct io *io, const char *cmd, ...)
470 init_io(io, NULL, IO_RD);
472 if (vsnprintf(io->sh, sizeof(io->sh), cmd, args) >= sizeof(io->sh))
476 return io->sh[0] ? start_io(io) : FALSE;
480 run_io_rd(struct io *io, const char **argv, enum format_flags flags)
482 return init_io_rd(io, argv, NULL, flags) && start_io(io);
486 io_eof(struct io *io)
488 return feof(io->pipe);
492 io_error(struct io *io)
498 io_strerror(struct io *io)
500 return strerror(io->error);
504 io_gets(struct io *io)
507 io->buf = malloc(BUFSIZ);
510 io->bufalloc = BUFSIZ;
513 if (!fgets(io->buf, io->bufalloc, io->pipe)) {
514 if (ferror(io->pipe))
528 /* XXX: Keep the view request first and in sync with views[]. */ \
529 REQ_GROUP("View switching") \
530 REQ_(VIEW_MAIN, "Show main view"), \
531 REQ_(VIEW_DIFF, "Show diff view"), \
532 REQ_(VIEW_LOG, "Show log view"), \
533 REQ_(VIEW_TREE, "Show tree view"), \
534 REQ_(VIEW_BLOB, "Show blob view"), \
535 REQ_(VIEW_BLAME, "Show blame view"), \
536 REQ_(VIEW_HELP, "Show help page"), \
537 REQ_(VIEW_PAGER, "Show pager view"), \
538 REQ_(VIEW_STATUS, "Show status view"), \
539 REQ_(VIEW_STAGE, "Show stage view"), \
541 REQ_GROUP("View manipulation") \
542 REQ_(ENTER, "Enter current line and scroll"), \
543 REQ_(NEXT, "Move to next"), \
544 REQ_(PREVIOUS, "Move to previous"), \
545 REQ_(VIEW_NEXT, "Move focus to next view"), \
546 REQ_(REFRESH, "Reload and refresh"), \
547 REQ_(MAXIMIZE, "Maximize the current view"), \
548 REQ_(VIEW_CLOSE, "Close the current view"), \
549 REQ_(QUIT, "Close all views and quit"), \
551 REQ_GROUP("View specific requests") \
552 REQ_(STATUS_UPDATE, "Update file status"), \
553 REQ_(STATUS_REVERT, "Revert file changes"), \
554 REQ_(STATUS_MERGE, "Merge file using external tool"), \
555 REQ_(STAGE_NEXT, "Find next chunk to stage"), \
556 REQ_(TREE_PARENT, "Switch to parent directory in tree view"), \
558 REQ_GROUP("Cursor navigation") \
559 REQ_(MOVE_UP, "Move cursor one line up"), \
560 REQ_(MOVE_DOWN, "Move cursor one line down"), \
561 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
562 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
563 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
564 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
566 REQ_GROUP("Scrolling") \
567 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
568 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
569 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
570 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
572 REQ_GROUP("Searching") \
573 REQ_(SEARCH, "Search the view"), \
574 REQ_(SEARCH_BACK, "Search backwards in the view"), \
575 REQ_(FIND_NEXT, "Find next search match"), \
576 REQ_(FIND_PREV, "Find previous search match"), \
578 REQ_GROUP("Option manipulation") \
579 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
580 REQ_(TOGGLE_DATE, "Toggle date display"), \
581 REQ_(TOGGLE_AUTHOR, "Toggle author display"), \
582 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
583 REQ_(TOGGLE_REFS, "Toggle reference display (tags/branches)"), \
586 REQ_(PROMPT, "Bring up the prompt"), \
587 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
588 REQ_(SCREEN_RESIZE, "Resize the screen"), \
589 REQ_(SHOW_VERSION, "Show version information"), \
590 REQ_(STOP_LOADING, "Stop all loading views"), \
591 REQ_(EDIT, "Open in editor"), \
592 REQ_(NONE, "Do nothing")
595 /* User action requests. */
597 #define REQ_GROUP(help)
598 #define REQ_(req, help) REQ_##req
600 /* Offset all requests to avoid conflicts with ncurses getch values. */
601 REQ_OFFSET = KEY_MAX + 1,
608 struct request_info {
609 enum request request;
615 static struct request_info req_info[] = {
616 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
617 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
624 get_request(const char *name)
626 int namelen = strlen(name);
629 for (i = 0; i < ARRAY_SIZE(req_info); i++)
630 if (req_info[i].namelen == namelen &&
631 !string_enum_compare(req_info[i].name, name, namelen))
632 return req_info[i].request;
642 static const char usage[] =
643 "tig " TIG_VERSION " (" __DATE__ ")\n"
645 "Usage: tig [options] [revs] [--] [paths]\n"
646 " or: tig show [options] [revs] [--] [paths]\n"
647 " or: tig blame [rev] path\n"
649 " or: tig < [git command output]\n"
652 " -v, --version Show version and exit\n"
653 " -h, --help Show help message and exit";
655 /* Option and state variables. */
656 static bool opt_date = TRUE;
657 static bool opt_author = TRUE;
658 static bool opt_line_number = FALSE;
659 static bool opt_line_graphics = TRUE;
660 static bool opt_rev_graph = FALSE;
661 static bool opt_show_refs = TRUE;
662 static int opt_num_interval = NUMBER_INTERVAL;
663 static int opt_tab_size = TAB_SIZE;
664 static int opt_author_cols = AUTHOR_COLS-1;
665 static char opt_cmd[SIZEOF_STR] = "";
666 static char opt_path[SIZEOF_STR] = "";
667 static char opt_file[SIZEOF_STR] = "";
668 static char opt_ref[SIZEOF_REF] = "";
669 static char opt_head[SIZEOF_REF] = "";
670 static char opt_head_rev[SIZEOF_REV] = "";
671 static char opt_remote[SIZEOF_REF] = "";
672 static FILE *opt_pipe = NULL;
673 static char opt_encoding[20] = "UTF-8";
674 static bool opt_utf8 = TRUE;
675 static char opt_codeset[20] = "UTF-8";
676 static iconv_t opt_iconv = ICONV_NONE;
677 static char opt_search[SIZEOF_STR] = "";
678 static char opt_cdup[SIZEOF_STR] = "";
679 static char opt_git_dir[SIZEOF_STR] = "";
680 static signed char opt_is_inside_work_tree = -1; /* set to TRUE or FALSE */
681 static char opt_editor[SIZEOF_STR] = "";
682 static FILE *opt_tty = NULL;
684 #define is_initial_commit() (!*opt_head_rev)
685 #define is_head_commit(rev) (!strcmp((rev), "HEAD") || !strcmp(opt_head_rev, (rev)))
688 parse_options(int argc, const char *argv[])
690 enum request request = REQ_VIEW_MAIN;
692 const char *subcommand;
693 bool seen_dashdash = FALSE;
696 if (!isatty(STDIN_FILENO)) {
698 return REQ_VIEW_PAGER;
702 return REQ_VIEW_MAIN;
704 subcommand = argv[1];
705 if (!strcmp(subcommand, "status") || !strcmp(subcommand, "-S")) {
706 if (!strcmp(subcommand, "-S"))
707 warn("`-S' has been deprecated; use `tig status' instead");
709 warn("ignoring arguments after `%s'", subcommand);
710 return REQ_VIEW_STATUS;
712 } else if (!strcmp(subcommand, "blame")) {
713 if (argc <= 2 || argc > 4)
714 die("invalid number of options to blame\n\n%s", usage);
718 string_ncopy(opt_ref, argv[i], strlen(argv[i]));
722 string_ncopy(opt_file, argv[i], strlen(argv[i]));
723 return REQ_VIEW_BLAME;
725 } else if (!strcmp(subcommand, "show")) {
726 request = REQ_VIEW_DIFF;
728 } else if (!strcmp(subcommand, "log") || !strcmp(subcommand, "diff")) {
729 request = subcommand[0] == 'l' ? REQ_VIEW_LOG : REQ_VIEW_DIFF;
730 warn("`tig %s' has been deprecated", subcommand);
737 /* XXX: This is vulnerable to the user overriding
738 * options required for the main view parser. */
739 string_copy(opt_cmd, TIG_MAIN_BASE);
741 string_format(opt_cmd, "git %s", subcommand);
743 buf_size = strlen(opt_cmd);
745 for (i = 1 + !!subcommand; i < argc; i++) {
746 const char *opt = argv[i];
748 if (seen_dashdash || !strcmp(opt, "--")) {
749 seen_dashdash = TRUE;
751 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
752 printf("tig version %s\n", TIG_VERSION);
755 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
756 printf("%s\n", usage);
760 opt_cmd[buf_size++] = ' ';
761 buf_size = sq_quote(opt_cmd, buf_size, opt);
762 if (buf_size >= sizeof(opt_cmd))
763 die("command too long");
766 opt_cmd[buf_size] = 0;
773 * Line-oriented content detection.
777 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
778 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
779 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
780 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
781 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
782 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
783 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
784 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
785 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
786 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
787 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
788 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
789 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
790 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
791 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
792 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
793 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
794 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
795 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
796 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
797 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
798 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
799 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
800 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
801 LINE(AUTHOR, "author ", COLOR_CYAN, COLOR_DEFAULT, 0), \
802 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
803 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
804 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
805 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
806 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
807 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
808 LINE(DELIMITER, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
809 LINE(DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
810 LINE(LINE_NUMBER, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
811 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
812 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
813 LINE(MAIN_AUTHOR, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
814 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
815 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
816 LINE(MAIN_LOCAL_TAG,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
817 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
818 LINE(MAIN_TRACKED, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
819 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
820 LINE(MAIN_HEAD, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
821 LINE(MAIN_REVGRAPH,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
822 LINE(TREE_DIR, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
823 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
824 LINE(STAT_HEAD, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
825 LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
826 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
827 LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
828 LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
829 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
830 LINE(BLAME_ID, "", COLOR_MAGENTA, COLOR_DEFAULT, 0)
833 #define LINE(type, line, fg, bg, attr) \
841 const char *name; /* Option name. */
842 int namelen; /* Size of option name. */
843 const char *line; /* The start of line to match. */
844 int linelen; /* Size of string to match. */
845 int fg, bg, attr; /* Color and text attributes for the lines. */
848 static struct line_info line_info[] = {
849 #define LINE(type, line, fg, bg, attr) \
850 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
855 static enum line_type
856 get_line_type(const char *line)
858 int linelen = strlen(line);
861 for (type = 0; type < ARRAY_SIZE(line_info); type++)
862 /* Case insensitive search matches Signed-off-by lines better. */
863 if (linelen >= line_info[type].linelen &&
864 !strncasecmp(line_info[type].line, line, line_info[type].linelen))
871 get_line_attr(enum line_type type)
873 assert(type < ARRAY_SIZE(line_info));
874 return COLOR_PAIR(type) | line_info[type].attr;
877 static struct line_info *
878 get_line_info(const char *name)
880 size_t namelen = strlen(name);
883 for (type = 0; type < ARRAY_SIZE(line_info); type++)
884 if (namelen == line_info[type].namelen &&
885 !string_enum_compare(line_info[type].name, name, namelen))
886 return &line_info[type];
894 int default_bg = line_info[LINE_DEFAULT].bg;
895 int default_fg = line_info[LINE_DEFAULT].fg;
900 if (assume_default_colors(default_fg, default_bg) == ERR) {
901 default_bg = COLOR_BLACK;
902 default_fg = COLOR_WHITE;
905 for (type = 0; type < ARRAY_SIZE(line_info); type++) {
906 struct line_info *info = &line_info[type];
907 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
908 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
910 init_pair(type, fg, bg);
918 unsigned int selected:1;
919 unsigned int dirty:1;
921 void *data; /* User data */
931 enum request request;
934 static struct keybinding default_keybindings[] = {
936 { 'm', REQ_VIEW_MAIN },
937 { 'd', REQ_VIEW_DIFF },
938 { 'l', REQ_VIEW_LOG },
939 { 't', REQ_VIEW_TREE },
940 { 'f', REQ_VIEW_BLOB },
941 { 'B', REQ_VIEW_BLAME },
942 { 'p', REQ_VIEW_PAGER },
943 { 'h', REQ_VIEW_HELP },
944 { 'S', REQ_VIEW_STATUS },
945 { 'c', REQ_VIEW_STAGE },
947 /* View manipulation */
948 { 'q', REQ_VIEW_CLOSE },
949 { KEY_TAB, REQ_VIEW_NEXT },
950 { KEY_RETURN, REQ_ENTER },
951 { KEY_UP, REQ_PREVIOUS },
952 { KEY_DOWN, REQ_NEXT },
953 { 'R', REQ_REFRESH },
954 { KEY_F(5), REQ_REFRESH },
955 { 'O', REQ_MAXIMIZE },
957 /* Cursor navigation */
958 { 'k', REQ_MOVE_UP },
959 { 'j', REQ_MOVE_DOWN },
960 { KEY_HOME, REQ_MOVE_FIRST_LINE },
961 { KEY_END, REQ_MOVE_LAST_LINE },
962 { KEY_NPAGE, REQ_MOVE_PAGE_DOWN },
963 { ' ', REQ_MOVE_PAGE_DOWN },
964 { KEY_PPAGE, REQ_MOVE_PAGE_UP },
965 { 'b', REQ_MOVE_PAGE_UP },
966 { '-', REQ_MOVE_PAGE_UP },
969 { KEY_IC, REQ_SCROLL_LINE_UP },
970 { KEY_DC, REQ_SCROLL_LINE_DOWN },
971 { 'w', REQ_SCROLL_PAGE_UP },
972 { 's', REQ_SCROLL_PAGE_DOWN },
976 { '?', REQ_SEARCH_BACK },
977 { 'n', REQ_FIND_NEXT },
978 { 'N', REQ_FIND_PREV },
982 { 'z', REQ_STOP_LOADING },
983 { 'v', REQ_SHOW_VERSION },
984 { 'r', REQ_SCREEN_REDRAW },
985 { '.', REQ_TOGGLE_LINENO },
986 { 'D', REQ_TOGGLE_DATE },
987 { 'A', REQ_TOGGLE_AUTHOR },
988 { 'g', REQ_TOGGLE_REV_GRAPH },
989 { 'F', REQ_TOGGLE_REFS },
991 { 'u', REQ_STATUS_UPDATE },
992 { '!', REQ_STATUS_REVERT },
993 { 'M', REQ_STATUS_MERGE },
994 { '@', REQ_STAGE_NEXT },
995 { ',', REQ_TREE_PARENT },
998 /* Using the ncurses SIGWINCH handler. */
999 { KEY_RESIZE, REQ_SCREEN_RESIZE },
1002 #define KEYMAP_INFO \
1016 #define KEYMAP_(name) KEYMAP_##name
1021 static struct int_map keymap_table[] = {
1022 #define KEYMAP_(name) { #name, STRING_SIZE(#name), KEYMAP_##name }
1027 #define set_keymap(map, name) \
1028 set_from_int_map(keymap_table, ARRAY_SIZE(keymap_table), map, name, strlen(name))
1030 struct keybinding_table {
1031 struct keybinding *data;
1035 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1038 add_keybinding(enum keymap keymap, enum request request, int key)
1040 struct keybinding_table *table = &keybindings[keymap];
1042 table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1044 die("Failed to allocate keybinding");
1045 table->data[table->size].alias = key;
1046 table->data[table->size++].request = request;
1049 /* Looks for a key binding first in the given map, then in the generic map, and
1050 * lastly in the default keybindings. */
1052 get_keybinding(enum keymap keymap, int key)
1056 for (i = 0; i < keybindings[keymap].size; i++)
1057 if (keybindings[keymap].data[i].alias == key)
1058 return keybindings[keymap].data[i].request;
1060 for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1061 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1062 return keybindings[KEYMAP_GENERIC].data[i].request;
1064 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1065 if (default_keybindings[i].alias == key)
1066 return default_keybindings[i].request;
1068 return (enum request) key;
1077 static struct key key_table[] = {
1078 { "Enter", KEY_RETURN },
1080 { "Backspace", KEY_BACKSPACE },
1082 { "Escape", KEY_ESC },
1083 { "Left", KEY_LEFT },
1084 { "Right", KEY_RIGHT },
1086 { "Down", KEY_DOWN },
1087 { "Insert", KEY_IC },
1088 { "Delete", KEY_DC },
1090 { "Home", KEY_HOME },
1092 { "PageUp", KEY_PPAGE },
1093 { "PageDown", KEY_NPAGE },
1103 { "F10", KEY_F(10) },
1104 { "F11", KEY_F(11) },
1105 { "F12", KEY_F(12) },
1109 get_key_value(const char *name)
1113 for (i = 0; i < ARRAY_SIZE(key_table); i++)
1114 if (!strcasecmp(key_table[i].name, name))
1115 return key_table[i].value;
1117 if (strlen(name) == 1 && isprint(*name))
1124 get_key_name(int key_value)
1126 static char key_char[] = "'X'";
1127 const char *seq = NULL;
1130 for (key = 0; key < ARRAY_SIZE(key_table); key++)
1131 if (key_table[key].value == key_value)
1132 seq = key_table[key].name;
1136 isprint(key_value)) {
1137 key_char[1] = (char) key_value;
1141 return seq ? seq : "(no key)";
1145 get_key(enum request request)
1147 static char buf[BUFSIZ];
1154 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1155 struct keybinding *keybinding = &default_keybindings[i];
1157 if (keybinding->request != request)
1160 if (!string_format_from(buf, &pos, "%s%s", sep,
1161 get_key_name(keybinding->alias)))
1162 return "Too many keybindings!";
1169 struct run_request {
1172 const char *argv[SIZEOF_ARG];
1175 static struct run_request *run_request;
1176 static size_t run_requests;
1179 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1181 struct run_request *req;
1183 if (argc >= ARRAY_SIZE(req->argv) - 1)
1186 req = realloc(run_request, (run_requests + 1) * sizeof(*run_request));
1191 req = &run_request[run_requests];
1192 req->keymap = keymap;
1194 req->argv[0] = NULL;
1196 if (!format_argv(req->argv, argv, FORMAT_NONE))
1199 return REQ_NONE + ++run_requests;
1202 static struct run_request *
1203 get_run_request(enum request request)
1205 if (request <= REQ_NONE)
1207 return &run_request[request - REQ_NONE - 1];
1211 add_builtin_run_requests(void)
1213 const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1214 const char *gc[] = { "git", "gc", NULL };
1221 { KEYMAP_MAIN, 'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1222 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1226 for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1229 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1230 if (req != REQ_NONE)
1231 add_keybinding(reqs[i].keymap, req, reqs[i].key);
1236 * User config file handling.
1239 static struct int_map color_map[] = {
1240 #define COLOR_MAP(name) { #name, STRING_SIZE(#name), COLOR_##name }
1252 #define set_color(color, name) \
1253 set_from_int_map(color_map, ARRAY_SIZE(color_map), color, name, strlen(name))
1255 static struct int_map attr_map[] = {
1256 #define ATTR_MAP(name) { #name, STRING_SIZE(#name), A_##name }
1263 ATTR_MAP(UNDERLINE),
1266 #define set_attribute(attr, name) \
1267 set_from_int_map(attr_map, ARRAY_SIZE(attr_map), attr, name, strlen(name))
1269 static int config_lineno;
1270 static bool config_errors;
1271 static const char *config_msg;
1273 /* Wants: object fgcolor bgcolor [attr] */
1275 option_color_command(int argc, const char *argv[])
1277 struct line_info *info;
1279 if (argc != 3 && argc != 4) {
1280 config_msg = "Wrong number of arguments given to color command";
1284 info = get_line_info(argv[0]);
1286 if (!string_enum_compare(argv[0], "main-delim", strlen("main-delim"))) {
1287 info = get_line_info("delimiter");
1289 } else if (!string_enum_compare(argv[0], "main-date", strlen("main-date"))) {
1290 info = get_line_info("date");
1293 config_msg = "Unknown color name";
1298 if (set_color(&info->fg, argv[1]) == ERR ||
1299 set_color(&info->bg, argv[2]) == ERR) {
1300 config_msg = "Unknown color";
1304 if (argc == 4 && set_attribute(&info->attr, argv[3]) == ERR) {
1305 config_msg = "Unknown attribute";
1312 static bool parse_bool(const char *s)
1314 return (!strcmp(s, "1") || !strcmp(s, "true") ||
1315 !strcmp(s, "yes")) ? TRUE : FALSE;
1319 parse_int(const char *s, int default_value, int min, int max)
1321 int value = atoi(s);
1323 return (value < min || value > max) ? default_value : value;
1326 /* Wants: name = value */
1328 option_set_command(int argc, const char *argv[])
1331 config_msg = "Wrong number of arguments given to set command";
1335 if (strcmp(argv[1], "=")) {
1336 config_msg = "No value assigned";
1340 if (!strcmp(argv[0], "show-author")) {
1341 opt_author = parse_bool(argv[2]);
1345 if (!strcmp(argv[0], "show-date")) {
1346 opt_date = parse_bool(argv[2]);
1350 if (!strcmp(argv[0], "show-rev-graph")) {
1351 opt_rev_graph = parse_bool(argv[2]);
1355 if (!strcmp(argv[0], "show-refs")) {
1356 opt_show_refs = parse_bool(argv[2]);
1360 if (!strcmp(argv[0], "show-line-numbers")) {
1361 opt_line_number = parse_bool(argv[2]);
1365 if (!strcmp(argv[0], "line-graphics")) {
1366 opt_line_graphics = parse_bool(argv[2]);
1370 if (!strcmp(argv[0], "line-number-interval")) {
1371 opt_num_interval = parse_int(argv[2], opt_num_interval, 1, 1024);
1375 if (!strcmp(argv[0], "author-width")) {
1376 opt_author_cols = parse_int(argv[2], opt_author_cols, 0, 1024);
1380 if (!strcmp(argv[0], "tab-size")) {
1381 opt_tab_size = parse_int(argv[2], opt_tab_size, 1, 1024);
1385 if (!strcmp(argv[0], "commit-encoding")) {
1386 const char *arg = argv[2];
1387 int arglen = strlen(arg);
1392 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1393 config_msg = "Unmatched quotation";
1396 arg += 1; arglen -= 2;
1398 string_ncopy(opt_encoding, arg, strlen(arg));
1403 config_msg = "Unknown variable name";
1407 /* Wants: mode request key */
1409 option_bind_command(int argc, const char *argv[])
1411 enum request request;
1416 config_msg = "Wrong number of arguments given to bind command";
1420 if (set_keymap(&keymap, argv[0]) == ERR) {
1421 config_msg = "Unknown key map";
1425 key = get_key_value(argv[1]);
1427 config_msg = "Unknown key";
1431 request = get_request(argv[2]);
1432 if (request == REQ_NONE) {
1433 const char *obsolete[] = { "cherry-pick" };
1434 size_t namelen = strlen(argv[2]);
1437 for (i = 0; i < ARRAY_SIZE(obsolete); i++) {
1438 if (namelen == strlen(obsolete[i]) &&
1439 !string_enum_compare(obsolete[i], argv[2], namelen)) {
1440 config_msg = "Obsolete request name";
1445 if (request == REQ_NONE && *argv[2]++ == '!')
1446 request = add_run_request(keymap, key, argc - 2, argv + 2);
1447 if (request == REQ_NONE) {
1448 config_msg = "Unknown request name";
1452 add_keybinding(keymap, request, key);
1458 set_option(const char *opt, char *value)
1460 const char *argv[SIZEOF_ARG];
1463 if (!argv_from_string(argv, &argc, value)) {
1464 config_msg = "Too many option arguments";
1468 if (!strcmp(opt, "color"))
1469 return option_color_command(argc, argv);
1471 if (!strcmp(opt, "set"))
1472 return option_set_command(argc, argv);
1474 if (!strcmp(opt, "bind"))
1475 return option_bind_command(argc, argv);
1477 config_msg = "Unknown option command";
1482 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1487 config_msg = "Internal error";
1489 /* Check for comment markers, since read_properties() will
1490 * only ensure opt and value are split at first " \t". */
1491 optlen = strcspn(opt, "#");
1495 if (opt[optlen] != 0) {
1496 config_msg = "No option value";
1500 /* Look for comment endings in the value. */
1501 size_t len = strcspn(value, "#");
1503 if (len < valuelen) {
1505 value[valuelen] = 0;
1508 status = set_option(opt, value);
1511 if (status == ERR) {
1512 fprintf(stderr, "Error on line %d, near '%.*s': %s\n",
1513 config_lineno, (int) optlen, opt, config_msg);
1514 config_errors = TRUE;
1517 /* Always keep going if errors are encountered. */
1522 load_option_file(const char *path)
1526 /* It's ok that the file doesn't exist. */
1527 file = fopen(path, "r");
1532 config_errors = FALSE;
1534 if (read_properties(file, " \t", read_option) == ERR ||
1535 config_errors == TRUE)
1536 fprintf(stderr, "Errors while loading %s.\n", path);
1542 const char *home = getenv("HOME");
1543 const char *tigrc_user = getenv("TIGRC_USER");
1544 const char *tigrc_system = getenv("TIGRC_SYSTEM");
1545 char buf[SIZEOF_STR];
1547 add_builtin_run_requests();
1549 if (!tigrc_system) {
1550 if (!string_format(buf, "%s/tigrc", SYSCONFDIR))
1554 load_option_file(tigrc_system);
1557 if (!home || !string_format(buf, "%s/.tigrc", home))
1561 load_option_file(tigrc_user);
1574 /* The display array of active views and the index of the current view. */
1575 static struct view *display[2];
1576 static unsigned int current_view;
1578 /* Reading from the prompt? */
1579 static bool input_mode = FALSE;
1581 #define foreach_displayed_view(view, i) \
1582 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1584 #define displayed_views() (display[1] != NULL ? 2 : 1)
1586 /* Current head and commit ID */
1587 static char ref_blob[SIZEOF_REF] = "";
1588 static char ref_commit[SIZEOF_REF] = "HEAD";
1589 static char ref_head[SIZEOF_REF] = "HEAD";
1592 const char *name; /* View name */
1593 const char *cmd_env; /* Command line set via environment */
1594 const char *id; /* Points to either of ref_{head,commit,blob} */
1596 struct view_ops *ops; /* View operations */
1598 enum keymap keymap; /* What keymap does this view have */
1599 bool git_dir; /* Whether the view requires a git directory. */
1601 char ref[SIZEOF_REF]; /* Hovered commit reference */
1602 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
1604 int height, width; /* The width and height of the main window */
1605 WINDOW *win; /* The main window */
1606 WINDOW *title; /* The title window living below the main window */
1609 unsigned long offset; /* Offset of the window top */
1610 unsigned long lineno; /* Current line number */
1613 char grep[SIZEOF_STR]; /* Search string */
1614 regex_t *regex; /* Pre-compiled regex */
1616 /* If non-NULL, points to the view that opened this view. If this view
1617 * is closed tig will switch back to the parent view. */
1618 struct view *parent;
1621 size_t lines; /* Total number of lines */
1622 struct line *line; /* Line index */
1623 size_t line_alloc; /* Total number of allocated lines */
1624 size_t line_size; /* Total number of used lines */
1625 unsigned int digits; /* Number of digits in the lines member. */
1628 struct line *curline; /* Line currently being drawn. */
1629 enum line_type curtype; /* Attribute currently used for drawing. */
1630 unsigned long col; /* Column when drawing. */
1639 /* What type of content being displayed. Used in the title bar. */
1641 /* Default command arguments. */
1643 /* Open and reads in all view content. */
1644 bool (*open)(struct view *view);
1645 /* Read one line; updates view->line. */
1646 bool (*read)(struct view *view, char *data);
1647 /* Draw one line; @lineno must be < view->height. */
1648 bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
1649 /* Depending on view handle a special requests. */
1650 enum request (*request)(struct view *view, enum request request, struct line *line);
1651 /* Search for regex in a line. */
1652 bool (*grep)(struct view *view, struct line *line);
1654 void (*select)(struct view *view, struct line *line);
1657 static struct view_ops blame_ops;
1658 static struct view_ops blob_ops;
1659 static struct view_ops diff_ops;
1660 static struct view_ops help_ops;
1661 static struct view_ops log_ops;
1662 static struct view_ops main_ops;
1663 static struct view_ops pager_ops;
1664 static struct view_ops stage_ops;
1665 static struct view_ops status_ops;
1666 static struct view_ops tree_ops;
1668 #define VIEW_STR(name, env, ref, ops, map, git) \
1669 { name, #env, ref, ops, map, git }
1671 #define VIEW_(id, name, ops, git, ref) \
1672 VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
1675 static struct view views[] = {
1676 VIEW_(MAIN, "main", &main_ops, TRUE, ref_head),
1677 VIEW_(DIFF, "diff", &diff_ops, TRUE, ref_commit),
1678 VIEW_(LOG, "log", &log_ops, TRUE, ref_head),
1679 VIEW_(TREE, "tree", &tree_ops, TRUE, ref_commit),
1680 VIEW_(BLOB, "blob", &blob_ops, TRUE, ref_blob),
1681 VIEW_(BLAME, "blame", &blame_ops, TRUE, ref_commit),
1682 VIEW_(HELP, "help", &help_ops, FALSE, ""),
1683 VIEW_(PAGER, "pager", &pager_ops, FALSE, "stdin"),
1684 VIEW_(STATUS, "status", &status_ops, TRUE, ""),
1685 VIEW_(STAGE, "stage", &stage_ops, TRUE, ""),
1688 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
1689 #define VIEW_REQ(view) ((view) - views + REQ_OFFSET + 1)
1691 #define foreach_view(view, i) \
1692 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1694 #define view_is_displayed(view) \
1695 (view == display[0] || view == display[1])
1702 static int line_graphics[] = {
1703 /* LINE_GRAPHIC_VLINE: */ '|'
1707 set_view_attr(struct view *view, enum line_type type)
1709 if (!view->curline->selected && view->curtype != type) {
1710 wattrset(view->win, get_line_attr(type));
1711 wchgat(view->win, -1, 0, type, NULL);
1712 view->curtype = type;
1717 draw_chars(struct view *view, enum line_type type, const char *string,
1718 int max_len, bool use_tilde)
1722 int trimmed = FALSE;
1728 len = utf8_length(string, &col, max_len, &trimmed, use_tilde);
1730 col = len = strlen(string);
1731 if (len > max_len) {
1735 col = len = max_len;
1740 set_view_attr(view, type);
1741 waddnstr(view->win, string, len);
1742 if (trimmed && use_tilde) {
1743 set_view_attr(view, LINE_DELIMITER);
1744 waddch(view->win, '~');
1752 draw_space(struct view *view, enum line_type type, int max, int spaces)
1754 static char space[] = " ";
1757 spaces = MIN(max, spaces);
1759 while (spaces > 0) {
1760 int len = MIN(spaces, sizeof(space) - 1);
1762 col += draw_chars(view, type, space, spaces, FALSE);
1770 draw_lineno(struct view *view, unsigned int lineno)
1773 int digits3 = view->digits < 3 ? 3 : view->digits;
1774 int max_number = MIN(digits3, STRING_SIZE(number));
1775 int max = view->width - view->col;
1778 if (max < max_number)
1781 lineno += view->offset + 1;
1782 if (lineno == 1 || (lineno % opt_num_interval) == 0) {
1783 static char fmt[] = "%1ld";
1785 if (view->digits <= 9)
1786 fmt[1] = '0' + digits3;
1788 if (!string_format(number, fmt, lineno))
1790 col = draw_chars(view, LINE_LINE_NUMBER, number, max_number, TRUE);
1792 col = draw_space(view, LINE_LINE_NUMBER, max_number, max_number);
1796 set_view_attr(view, LINE_DEFAULT);
1797 waddch(view->win, line_graphics[LINE_GRAPHIC_VLINE]);
1802 col += draw_space(view, LINE_DEFAULT, max - col, 1);
1805 return view->width - view->col <= 0;
1809 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
1811 view->col += draw_chars(view, type, string, view->width - view->col, trim);
1812 return view->width - view->col <= 0;
1816 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
1818 int max = view->width - view->col;
1824 set_view_attr(view, type);
1825 /* Using waddch() instead of waddnstr() ensures that
1826 * they'll be rendered correctly for the cursor line. */
1827 for (i = 0; i < size; i++)
1828 waddch(view->win, graphic[i]);
1832 waddch(view->win, ' ');
1836 return view->width - view->col <= 0;
1840 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
1842 int max = MIN(view->width - view->col, len);
1846 col = draw_chars(view, type, text, max - 1, trim);
1848 col = draw_space(view, type, max - 1, max - 1);
1850 view->col += col + draw_space(view, LINE_DEFAULT, max - col, max - col);
1851 return view->width - view->col <= 0;
1855 draw_date(struct view *view, struct tm *time)
1857 char buf[DATE_COLS];
1862 timelen = strftime(buf, sizeof(buf), DATE_FORMAT, time);
1863 date = timelen ? buf : NULL;
1865 return draw_field(view, LINE_DATE, date, DATE_COLS, FALSE);
1869 draw_view_line(struct view *view, unsigned int lineno)
1872 bool selected = (view->offset + lineno == view->lineno);
1875 assert(view_is_displayed(view));
1877 if (view->offset + lineno >= view->lines)
1880 line = &view->line[view->offset + lineno];
1882 wmove(view->win, lineno, 0);
1884 view->curline = line;
1885 view->curtype = LINE_NONE;
1886 line->selected = FALSE;
1889 set_view_attr(view, LINE_CURSOR);
1890 line->selected = TRUE;
1891 view->ops->select(view, line);
1892 } else if (line->selected) {
1893 wclrtoeol(view->win);
1896 scrollok(view->win, FALSE);
1897 draw_ok = view->ops->draw(view, line, lineno);
1898 scrollok(view->win, TRUE);
1904 redraw_view_dirty(struct view *view)
1909 for (lineno = 0; lineno < view->height; lineno++) {
1910 struct line *line = &view->line[view->offset + lineno];
1916 if (!draw_view_line(view, lineno))
1922 redrawwin(view->win);
1924 wnoutrefresh(view->win);
1926 wrefresh(view->win);
1930 redraw_view_from(struct view *view, int lineno)
1932 assert(0 <= lineno && lineno < view->height);
1934 for (; lineno < view->height; lineno++) {
1935 if (!draw_view_line(view, lineno))
1939 redrawwin(view->win);
1941 wnoutrefresh(view->win);
1943 wrefresh(view->win);
1947 redraw_view(struct view *view)
1950 redraw_view_from(view, 0);
1955 update_view_title(struct view *view)
1957 char buf[SIZEOF_STR];
1958 char state[SIZEOF_STR];
1959 size_t bufpos = 0, statelen = 0;
1961 assert(view_is_displayed(view));
1963 if (view != VIEW(REQ_VIEW_STATUS) && (view->lines || view->pipe)) {
1964 unsigned int view_lines = view->offset + view->height;
1965 unsigned int lines = view->lines
1966 ? MIN(view_lines, view->lines) * 100 / view->lines
1969 string_format_from(state, &statelen, "- %s %d of %d (%d%%)",
1976 time_t secs = time(NULL) - view->start_time;
1978 /* Three git seconds are a long time ... */
1980 string_format_from(state, &statelen, " %lds", secs);
1984 string_format_from(buf, &bufpos, "[%s]", view->name);
1985 if (*view->ref && bufpos < view->width) {
1986 size_t refsize = strlen(view->ref);
1987 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
1989 if (minsize < view->width)
1990 refsize = view->width - minsize + 7;
1991 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
1994 if (statelen && bufpos < view->width) {
1995 string_format_from(buf, &bufpos, " %s", state);
1998 if (view == display[current_view])
1999 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2001 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2003 mvwaddnstr(view->title, 0, 0, buf, bufpos);
2004 wclrtoeol(view->title);
2005 wmove(view->title, 0, view->width - 1);
2008 wnoutrefresh(view->title);
2010 wrefresh(view->title);
2014 resize_display(void)
2017 struct view *base = display[0];
2018 struct view *view = display[1] ? display[1] : display[0];
2020 /* Setup window dimensions */
2022 getmaxyx(stdscr, base->height, base->width);
2024 /* Make room for the status window. */
2028 /* Horizontal split. */
2029 view->width = base->width;
2030 view->height = SCALE_SPLIT_VIEW(base->height);
2031 base->height -= view->height;
2033 /* Make room for the title bar. */
2037 /* Make room for the title bar. */
2042 foreach_displayed_view (view, i) {
2044 view->win = newwin(view->height, 0, offset, 0);
2046 die("Failed to create %s view", view->name);
2048 scrollok(view->win, TRUE);
2050 view->title = newwin(1, 0, offset + view->height, 0);
2052 die("Failed to create title window");
2055 wresize(view->win, view->height, view->width);
2056 mvwin(view->win, offset, 0);
2057 mvwin(view->title, offset + view->height, 0);
2060 offset += view->height + 1;
2065 redraw_display(void)
2070 foreach_displayed_view (view, i) {
2072 update_view_title(view);
2077 update_display_cursor(struct view *view)
2079 /* Move the cursor to the right-most column of the cursor line.
2081 * XXX: This could turn out to be a bit expensive, but it ensures that
2082 * the cursor does not jump around. */
2084 wmove(view->win, view->lineno - view->offset, view->width - 1);
2085 wrefresh(view->win);
2093 /* Scrolling backend */
2095 do_scroll_view(struct view *view, int lines)
2097 bool redraw_current_line = FALSE;
2099 /* The rendering expects the new offset. */
2100 view->offset += lines;
2102 assert(0 <= view->offset && view->offset < view->lines);
2105 /* Move current line into the view. */
2106 if (view->lineno < view->offset) {
2107 view->lineno = view->offset;
2108 redraw_current_line = TRUE;
2109 } else if (view->lineno >= view->offset + view->height) {
2110 view->lineno = view->offset + view->height - 1;
2111 redraw_current_line = TRUE;
2114 assert(view->offset <= view->lineno && view->lineno < view->lines);
2116 /* Redraw the whole screen if scrolling is pointless. */
2117 if (view->height < ABS(lines)) {
2121 int line = lines > 0 ? view->height - lines : 0;
2122 int end = line + ABS(lines);
2124 wscrl(view->win, lines);
2126 for (; line < end; line++) {
2127 if (!draw_view_line(view, line))
2131 if (redraw_current_line)
2132 draw_view_line(view, view->lineno - view->offset);
2135 redrawwin(view->win);
2136 wrefresh(view->win);
2140 /* Scroll frontend */
2142 scroll_view(struct view *view, enum request request)
2146 assert(view_is_displayed(view));
2149 case REQ_SCROLL_PAGE_DOWN:
2150 lines = view->height;
2151 case REQ_SCROLL_LINE_DOWN:
2152 if (view->offset + lines > view->lines)
2153 lines = view->lines - view->offset;
2155 if (lines == 0 || view->offset + view->height >= view->lines) {
2156 report("Cannot scroll beyond the last line");
2161 case REQ_SCROLL_PAGE_UP:
2162 lines = view->height;
2163 case REQ_SCROLL_LINE_UP:
2164 if (lines > view->offset)
2165 lines = view->offset;
2168 report("Cannot scroll beyond the first line");
2176 die("request %d not handled in switch", request);
2179 do_scroll_view(view, lines);
2184 move_view(struct view *view, enum request request)
2186 int scroll_steps = 0;
2190 case REQ_MOVE_FIRST_LINE:
2191 steps = -view->lineno;
2194 case REQ_MOVE_LAST_LINE:
2195 steps = view->lines - view->lineno - 1;
2198 case REQ_MOVE_PAGE_UP:
2199 steps = view->height > view->lineno
2200 ? -view->lineno : -view->height;
2203 case REQ_MOVE_PAGE_DOWN:
2204 steps = view->lineno + view->height >= view->lines
2205 ? view->lines - view->lineno - 1 : view->height;
2217 die("request %d not handled in switch", request);
2220 if (steps <= 0 && view->lineno == 0) {
2221 report("Cannot move beyond the first line");
2224 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2225 report("Cannot move beyond the last line");
2229 /* Move the current line */
2230 view->lineno += steps;
2231 assert(0 <= view->lineno && view->lineno < view->lines);
2233 /* Check whether the view needs to be scrolled */
2234 if (view->lineno < view->offset ||
2235 view->lineno >= view->offset + view->height) {
2236 scroll_steps = steps;
2237 if (steps < 0 && -steps > view->offset) {
2238 scroll_steps = -view->offset;
2240 } else if (steps > 0) {
2241 if (view->lineno == view->lines - 1 &&
2242 view->lines > view->height) {
2243 scroll_steps = view->lines - view->offset - 1;
2244 if (scroll_steps >= view->height)
2245 scroll_steps -= view->height - 1;
2250 if (!view_is_displayed(view)) {
2251 view->offset += scroll_steps;
2252 assert(0 <= view->offset && view->offset < view->lines);
2253 view->ops->select(view, &view->line[view->lineno]);
2257 /* Repaint the old "current" line if we be scrolling */
2258 if (ABS(steps) < view->height)
2259 draw_view_line(view, view->lineno - steps - view->offset);
2262 do_scroll_view(view, scroll_steps);
2266 /* Draw the current line */
2267 draw_view_line(view, view->lineno - view->offset);
2269 redrawwin(view->win);
2270 wrefresh(view->win);
2279 static void search_view(struct view *view, enum request request);
2282 find_next_line(struct view *view, unsigned long lineno, struct line *line)
2284 assert(view_is_displayed(view));
2286 if (!view->ops->grep(view, line))
2289 if (lineno - view->offset >= view->height) {
2290 view->offset = lineno;
2291 view->lineno = lineno;
2295 unsigned long old_lineno = view->lineno - view->offset;
2297 view->lineno = lineno;
2298 draw_view_line(view, old_lineno);
2300 draw_view_line(view, view->lineno - view->offset);
2301 redrawwin(view->win);
2302 wrefresh(view->win);
2305 report("Line %ld matches '%s'", lineno + 1, view->grep);
2310 find_next(struct view *view, enum request request)
2312 unsigned long lineno = view->lineno;
2317 report("No previous search");
2319 search_view(view, request);
2329 case REQ_SEARCH_BACK:
2338 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2339 lineno += direction;
2341 /* Note, lineno is unsigned long so will wrap around in which case it
2342 * will become bigger than view->lines. */
2343 for (; lineno < view->lines; lineno += direction) {
2344 struct line *line = &view->line[lineno];
2346 if (find_next_line(view, lineno, line))
2350 report("No match found for '%s'", view->grep);
2354 search_view(struct view *view, enum request request)
2359 regfree(view->regex);
2362 view->regex = calloc(1, sizeof(*view->regex));
2367 regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2368 if (regex_err != 0) {
2369 char buf[SIZEOF_STR] = "unknown error";
2371 regerror(regex_err, view->regex, buf, sizeof(buf));
2372 report("Search failed: %s", buf);
2376 string_copy(view->grep, opt_search);
2378 find_next(view, request);
2382 * Incremental updating
2386 reset_view(struct view *view)
2390 for (i = 0; i < view->lines; i++)
2391 free(view->line[i].data);
2398 view->line_size = 0;
2399 view->line_alloc = 0;
2404 free_argv(const char *argv[])
2408 for (argc = 0; argv[argc]; argc++)
2409 free((void *) argv[argc]);
2413 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
2415 char buf[SIZEOF_STR];
2417 bool noreplace = flags == FORMAT_NONE;
2419 free_argv(dst_argv);
2421 for (argc = 0; src_argv[argc]; argc++) {
2422 const char *arg = src_argv[argc];
2426 char *next = strstr(arg, "%(");
2427 int len = next - arg;
2430 if (!next || noreplace) {
2431 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
2436 } else if (!prefixcmp(next, "%(directory)")) {
2439 } else if (!prefixcmp(next, "%(file)")) {
2442 } else if (!prefixcmp(next, "%(ref)")) {
2443 value = *opt_ref ? opt_ref : "HEAD";
2445 } else if (!prefixcmp(next, "%(head)")) {
2448 } else if (!prefixcmp(next, "%(commit)")) {
2451 } else if (!prefixcmp(next, "%(blob)")) {
2455 report("Unknown replacement: `%s`", next);
2459 if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
2462 arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
2465 dst_argv[argc] = strdup(buf);
2466 if (!dst_argv[argc])
2470 dst_argv[argc] = NULL;
2472 return src_argv[argc] == NULL;
2476 format_command(char dst[], const char *src_argv[], enum format_flags flags)
2478 const char *dst_argv[SIZEOF_ARG * 2] = { NULL };
2482 if (!format_argv(dst_argv, src_argv, flags)) {
2483 free_argv(dst_argv);
2487 for (argc = 0; dst_argv[argc] && bufsize < SIZEOF_STR; argc++) {
2489 dst[bufsize++] = ' ';
2490 bufsize = sq_quote(dst, bufsize, dst_argv[argc]);
2493 if (bufsize < SIZEOF_STR)
2495 free_argv(dst_argv);
2497 return src_argv[argc] == NULL && bufsize < SIZEOF_STR;
2501 end_update(struct view *view, bool force)
2505 while (!view->ops->read(view, NULL))
2508 set_nonblocking_input(FALSE);
2509 done_io(view->pipe);
2514 setup_update(struct view *view, const char *vid)
2516 set_nonblocking_input(TRUE);
2518 string_copy_rev(view->vid, vid);
2519 view->pipe = &view->io;
2520 view->start_time = time(NULL);
2524 prepare_update(struct view *view, const char *argv[], const char *dir,
2525 enum format_flags flags)
2528 end_update(view, TRUE);
2529 return init_io_rd(&view->io, argv, dir, flags);
2533 begin_update(struct view *view, bool refresh)
2535 if (init_io_fd(&view->io, opt_pipe)) {
2538 } else if (opt_cmd[0]) {
2539 if (!run_io(&view->io, IO_RD, opt_cmd))
2544 } else if (refresh) {
2545 if (!start_io(&view->io))
2548 } else if (view == VIEW(REQ_VIEW_TREE)) {
2549 if (strcmp(view->vid, view->id))
2552 if (!run_io_rd(&view->io, view->ops->argv, FORMAT_ALL))
2556 if (!run_io_rd(&view->io, view->ops->argv, FORMAT_ALL))
2559 /* Put the current ref_* value to the view title ref
2560 * member. This is needed by the blob view. Most other
2561 * views sets it automatically after loading because the
2562 * first line is a commit line. */
2563 string_copy_rev(view->ref, view->id);
2566 setup_update(view, view->id);
2571 #define ITEM_CHUNK_SIZE 256
2573 realloc_items(void *mem, size_t *size, size_t new_size, size_t item_size)
2575 size_t num_chunks = *size / ITEM_CHUNK_SIZE;
2576 size_t num_chunks_new = (new_size + ITEM_CHUNK_SIZE - 1) / ITEM_CHUNK_SIZE;
2578 if (mem == NULL || num_chunks != num_chunks_new) {
2579 *size = num_chunks_new * ITEM_CHUNK_SIZE;
2580 mem = realloc(mem, *size * item_size);
2586 static struct line *
2587 realloc_lines(struct view *view, size_t line_size)
2589 size_t alloc = view->line_alloc;
2590 struct line *tmp = realloc_items(view->line, &alloc, line_size,
2591 sizeof(*view->line));
2597 view->line_alloc = alloc;
2598 view->line_size = line_size;
2603 update_view(struct view *view)
2605 char out_buffer[BUFSIZ * 2];
2607 /* The number of lines to read. If too low it will cause too much
2608 * redrawing (and possible flickering), if too high responsiveness
2610 unsigned long lines = view->height;
2611 int redraw_from = -1;
2616 /* Only redraw if lines are visible. */
2617 if (view->offset + view->height >= view->lines)
2618 redraw_from = view->lines - view->offset;
2620 /* FIXME: This is probably not perfect for backgrounded views. */
2621 if (!realloc_lines(view, view->lines + lines))
2624 while ((line = io_gets(view->pipe))) {
2625 size_t linelen = strlen(line);
2628 line[linelen - 1] = 0;
2630 if (opt_iconv != ICONV_NONE) {
2631 ICONV_CONST char *inbuf = line;
2632 size_t inlen = linelen;
2634 char *outbuf = out_buffer;
2635 size_t outlen = sizeof(out_buffer);
2639 ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2640 if (ret != (size_t) -1) {
2642 linelen = strlen(out_buffer);
2646 if (!view->ops->read(view, line))
2656 lines = view->lines;
2657 for (digits = 0; lines; digits++)
2660 /* Keep the displayed view in sync with line number scaling. */
2661 if (digits != view->digits) {
2662 view->digits = digits;
2667 if (io_error(view->pipe)) {
2668 report("Failed to read: %s", io_strerror(view->pipe));
2669 end_update(view, TRUE);
2671 } else if (io_eof(view->pipe)) {
2673 end_update(view, FALSE);
2676 if (!view_is_displayed(view))
2679 if (view == VIEW(REQ_VIEW_TREE)) {
2680 /* Clear the view and redraw everything since the tree sorting
2681 * might have rearranged things. */
2684 } else if (redraw_from >= 0) {
2685 /* If this is an incremental update, redraw the previous line
2686 * since for commits some members could have changed when
2687 * loading the main view. */
2688 if (redraw_from > 0)
2691 /* Since revision graph visualization requires knowledge
2692 * about the parent commit, it causes a further one-off
2693 * needed to be redrawn for incremental updates. */
2694 if (redraw_from > 0 && opt_rev_graph)
2697 /* Incrementally draw avoids flickering. */
2698 redraw_view_from(view, redraw_from);
2701 if (view == VIEW(REQ_VIEW_BLAME))
2702 redraw_view_dirty(view);
2704 /* Update the title _after_ the redraw so that if the redraw picks up a
2705 * commit reference in view->ref it'll be available here. */
2706 update_view_title(view);
2710 report("Allocation failure");
2711 end_update(view, TRUE);
2715 static struct line *
2716 add_line_data(struct view *view, void *data, enum line_type type)
2718 struct line *line = &view->line[view->lines++];
2720 memset(line, 0, sizeof(*line));
2727 static struct line *
2728 add_line_text(struct view *view, const char *text, enum line_type type)
2730 char *data = text ? strdup(text) : NULL;
2732 return data ? add_line_data(view, data, type) : NULL;
2741 OPEN_DEFAULT = 0, /* Use default view switching. */
2742 OPEN_SPLIT = 1, /* Split current view. */
2743 OPEN_BACKGROUNDED = 2, /* Backgrounded. */
2744 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
2745 OPEN_NOMAXIMIZE = 8, /* Do not maximize the current view. */
2746 OPEN_REFRESH = 16, /* Refresh view using previous command. */
2747 OPEN_PREPARED = 32, /* Open already prepared command. */
2751 open_view(struct view *prev, enum request request, enum open_flags flags)
2753 bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
2754 bool split = !!(flags & OPEN_SPLIT);
2755 bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
2756 bool nomaximize = !!(flags & (OPEN_NOMAXIMIZE | OPEN_REFRESH));
2757 struct view *view = VIEW(request);
2758 int nviews = displayed_views();
2759 struct view *base_view = display[0];
2761 if (view == prev && nviews == 1 && !reload) {
2762 report("Already in %s view", view->name);
2766 if (view->git_dir && !opt_git_dir[0]) {
2767 report("The %s view is disabled in pager view", view->name);
2775 } else if (!nomaximize) {
2776 /* Maximize the current view. */
2777 memset(display, 0, sizeof(display));
2779 display[current_view] = view;
2782 /* Resize the view when switching between split- and full-screen,
2783 * or when switching between two different full-screen views. */
2784 if (nviews != displayed_views() ||
2785 (nviews == 1 && base_view != display[0]))
2789 end_update(view, TRUE);
2791 if (view->ops->open) {
2792 if (!view->ops->open(view)) {
2793 report("Failed to load %s view", view->name);
2797 } else if ((reload || strcmp(view->vid, view->id)) &&
2798 !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
2799 report("Failed to load %s view", view->name);
2803 if (split && prev->lineno - prev->offset >= prev->height) {
2804 /* Take the title line into account. */
2805 int lines = prev->lineno - prev->offset - prev->height + 1;
2807 /* Scroll the view that was split if the current line is
2808 * outside the new limited view. */
2809 do_scroll_view(prev, lines);
2812 if (prev && view != prev) {
2813 if (split && !backgrounded) {
2814 /* "Blur" the previous view. */
2815 update_view_title(prev);
2818 view->parent = prev;
2821 if (view->pipe && view->lines == 0) {
2822 /* Clear the old view and let the incremental updating refill
2826 } else if (view_is_displayed(view)) {
2831 /* If the view is backgrounded the above calls to report()
2832 * won't redraw the view title. */
2834 update_view_title(view);
2838 open_external_viewer(const char *argv[], const char *dir)
2840 def_prog_mode(); /* save current tty modes */
2841 endwin(); /* restore original tty modes */
2842 run_io_fg(argv, dir);
2843 fprintf(stderr, "Press Enter to continue");
2850 open_mergetool(const char *file)
2852 const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
2854 open_external_viewer(mergetool_argv, NULL);
2858 open_editor(bool from_root, const char *file)
2860 const char *editor_argv[] = { "vi", file, NULL };
2863 editor = getenv("GIT_EDITOR");
2864 if (!editor && *opt_editor)
2865 editor = opt_editor;
2867 editor = getenv("VISUAL");
2869 editor = getenv("EDITOR");
2873 editor_argv[0] = editor;
2874 open_external_viewer(editor_argv, from_root ? opt_cdup : NULL);
2878 open_run_request(enum request request)
2880 struct run_request *req = get_run_request(request);
2881 const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
2884 report("Unknown run request");
2888 if (format_argv(argv, req->argv, FORMAT_ALL))
2889 open_external_viewer(argv, NULL);
2894 * User request switch noodle
2898 view_driver(struct view *view, enum request request)
2902 if (request == REQ_NONE) {
2907 if (request > REQ_NONE) {
2908 open_run_request(request);
2909 /* FIXME: When all views can refresh always do this. */
2910 if (view == VIEW(REQ_VIEW_STATUS) ||
2911 view == VIEW(REQ_VIEW_MAIN) ||
2912 view == VIEW(REQ_VIEW_LOG) ||
2913 view == VIEW(REQ_VIEW_STAGE))
2914 request = REQ_REFRESH;
2919 if (view && view->lines) {
2920 request = view->ops->request(view, request, &view->line[view->lineno]);
2921 if (request == REQ_NONE)
2928 case REQ_MOVE_PAGE_UP:
2929 case REQ_MOVE_PAGE_DOWN:
2930 case REQ_MOVE_FIRST_LINE:
2931 case REQ_MOVE_LAST_LINE:
2932 move_view(view, request);
2935 case REQ_SCROLL_LINE_DOWN:
2936 case REQ_SCROLL_LINE_UP:
2937 case REQ_SCROLL_PAGE_DOWN:
2938 case REQ_SCROLL_PAGE_UP:
2939 scroll_view(view, request);
2942 case REQ_VIEW_BLAME:
2944 report("No file chosen, press %s to open tree view",
2945 get_key(REQ_VIEW_TREE));
2948 open_view(view, request, OPEN_DEFAULT);
2953 report("No file chosen, press %s to open tree view",
2954 get_key(REQ_VIEW_TREE));
2957 open_view(view, request, OPEN_DEFAULT);
2960 case REQ_VIEW_PAGER:
2961 if (!opt_pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
2962 report("No pager content, press %s to run command from prompt",
2963 get_key(REQ_PROMPT));
2966 open_view(view, request, OPEN_DEFAULT);
2969 case REQ_VIEW_STAGE:
2970 if (!VIEW(REQ_VIEW_STAGE)->lines) {
2971 report("No stage content, press %s to open the status view and choose file",
2972 get_key(REQ_VIEW_STATUS));
2975 open_view(view, request, OPEN_DEFAULT);
2978 case REQ_VIEW_STATUS:
2979 if (opt_is_inside_work_tree == FALSE) {
2980 report("The status view requires a working tree");
2983 open_view(view, request, OPEN_DEFAULT);
2991 open_view(view, request, OPEN_DEFAULT);
2996 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
2998 if ((view == VIEW(REQ_VIEW_DIFF) &&
2999 view->parent == VIEW(REQ_VIEW_MAIN)) ||
3000 (view == VIEW(REQ_VIEW_DIFF) &&
3001 view->parent == VIEW(REQ_VIEW_BLAME)) ||
3002 (view == VIEW(REQ_VIEW_STAGE) &&
3003 view->parent == VIEW(REQ_VIEW_STATUS)) ||
3004 (view == VIEW(REQ_VIEW_BLOB) &&
3005 view->parent == VIEW(REQ_VIEW_TREE))) {
3008 view = view->parent;
3009 line = view->lineno;
3010 move_view(view, request);
3011 if (view_is_displayed(view))
3012 update_view_title(view);
3013 if (line != view->lineno)
3014 view->ops->request(view, REQ_ENTER,
3015 &view->line[view->lineno]);
3018 move_view(view, request);
3024 int nviews = displayed_views();
3025 int next_view = (current_view + 1) % nviews;
3027 if (next_view == current_view) {
3028 report("Only one view is displayed");
3032 current_view = next_view;
3033 /* Blur out the title of the previous view. */
3034 update_view_title(view);
3039 report("Refreshing is not yet supported for the %s view", view->name);
3043 if (displayed_views() == 2)
3044 open_view(view, VIEW_REQ(view), OPEN_DEFAULT);
3047 case REQ_TOGGLE_LINENO:
3048 opt_line_number = !opt_line_number;
3052 case REQ_TOGGLE_DATE:
3053 opt_date = !opt_date;
3057 case REQ_TOGGLE_AUTHOR:
3058 opt_author = !opt_author;
3062 case REQ_TOGGLE_REV_GRAPH:
3063 opt_rev_graph = !opt_rev_graph;
3067 case REQ_TOGGLE_REFS:
3068 opt_show_refs = !opt_show_refs;
3073 case REQ_SEARCH_BACK:
3074 search_view(view, request);
3079 find_next(view, request);
3082 case REQ_STOP_LOADING:
3083 for (i = 0; i < ARRAY_SIZE(views); i++) {
3086 report("Stopped loading the %s view", view->name),
3087 end_update(view, TRUE);
3091 case REQ_SHOW_VERSION:
3092 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3095 case REQ_SCREEN_RESIZE:
3098 case REQ_SCREEN_REDRAW:
3103 report("Nothing to edit");
3107 report("Nothing to enter");
3110 case REQ_VIEW_CLOSE:
3111 /* XXX: Mark closed views by letting view->parent point to the
3112 * view itself. Parents to closed view should never be
3115 view->parent->parent != view->parent) {
3116 memset(display, 0, sizeof(display));
3118 display[current_view] = view->parent;
3119 view->parent = view;
3130 report("Unknown key, press 'h' for help");
3143 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3145 char *text = line->data;
3147 if (opt_line_number && draw_lineno(view, lineno))
3150 draw_text(view, line->type, text, TRUE);
3155 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3157 char refbuf[SIZEOF_STR];
3161 if (!string_format(refbuf, "git describe %s 2>/dev/null", commit_id))
3164 pipe = popen(refbuf, "r");
3168 if ((ref = fgets(refbuf, sizeof(refbuf), pipe)))
3169 ref = chomp_string(ref);
3175 /* This is the only fatal call, since it can "corrupt" the buffer. */
3176 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
3183 add_pager_refs(struct view *view, struct line *line)
3185 char buf[SIZEOF_STR];
3186 char *commit_id = (char *)line->data + STRING_SIZE("commit ");
3188 size_t bufpos = 0, refpos = 0;
3189 const char *sep = "Refs: ";
3190 bool is_tag = FALSE;
3192 assert(line->type == LINE_COMMIT);
3194 refs = get_refs(commit_id);
3196 if (view == VIEW(REQ_VIEW_DIFF))
3197 goto try_add_describe_ref;
3202 struct ref *ref = refs[refpos];
3203 const char *fmt = ref->tag ? "%s[%s]" :
3204 ref->remote ? "%s<%s>" : "%s%s";
3206 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
3211 } while (refs[refpos++]->next);
3213 if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
3214 try_add_describe_ref:
3215 /* Add <tag>-g<commit_id> "fake" reference. */
3216 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
3223 if (!realloc_lines(view, view->line_size + 1))
3226 add_line_text(view, buf, LINE_PP_REFS);
3230 pager_read(struct view *view, char *data)
3237 line = add_line_text(view, data, get_line_type(data));
3241 if (line->type == LINE_COMMIT &&
3242 (view == VIEW(REQ_VIEW_DIFF) ||
3243 view == VIEW(REQ_VIEW_LOG)))
3244 add_pager_refs(view, line);
3250 pager_request(struct view *view, enum request request, struct line *line)
3254 if (request != REQ_ENTER)
3257 if (line->type == LINE_COMMIT &&
3258 (view == VIEW(REQ_VIEW_LOG) ||
3259 view == VIEW(REQ_VIEW_PAGER))) {
3260 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3264 /* Always scroll the view even if it was split. That way
3265 * you can use Enter to scroll through the log view and
3266 * split open each commit diff. */
3267 scroll_view(view, REQ_SCROLL_LINE_DOWN);
3269 /* FIXME: A minor workaround. Scrolling the view will call report("")
3270 * but if we are scrolling a non-current view this won't properly
3271 * update the view title. */
3273 update_view_title(view);
3279 pager_grep(struct view *view, struct line *line)
3282 char *text = line->data;
3287 if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
3294 pager_select(struct view *view, struct line *line)
3296 if (line->type == LINE_COMMIT) {
3297 char *text = (char *)line->data + STRING_SIZE("commit ");
3299 if (view != VIEW(REQ_VIEW_PAGER))
3300 string_copy_rev(view->ref, text);
3301 string_copy_rev(ref_commit, text);
3305 static struct view_ops pager_ops = {
3316 static const char *log_argv[SIZEOF_ARG] = {
3317 "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3321 log_request(struct view *view, enum request request, struct line *line)
3326 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
3329 return pager_request(view, request, line);
3333 static struct view_ops log_ops = {
3344 static const char *diff_argv[SIZEOF_ARG] = {
3345 "git", "show", "--pretty=fuller", "--no-color", "--root",
3346 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
3349 static struct view_ops diff_ops = {
3365 help_open(struct view *view)
3368 int lines = ARRAY_SIZE(req_info) + 2;
3371 if (view->lines > 0)
3374 for (i = 0; i < ARRAY_SIZE(req_info); i++)
3375 if (!req_info[i].request)
3378 lines += run_requests + 1;
3380 view->line = calloc(lines, sizeof(*view->line));
3384 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
3386 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
3389 if (req_info[i].request == REQ_NONE)
3392 if (!req_info[i].request) {
3393 add_line_text(view, "", LINE_DEFAULT);
3394 add_line_text(view, req_info[i].help, LINE_DEFAULT);
3398 key = get_key(req_info[i].request);
3400 key = "(no key defined)";
3402 if (!string_format(buf, " %-25s %s", key, req_info[i].help))
3405 add_line_text(view, buf, LINE_DEFAULT);
3409 add_line_text(view, "", LINE_DEFAULT);
3410 add_line_text(view, "External commands:", LINE_DEFAULT);
3413 for (i = 0; i < run_requests; i++) {
3414 struct run_request *req = get_run_request(REQ_NONE + i + 1);
3416 char cmd[SIZEOF_STR];
3423 key = get_key_name(req->key);
3425 key = "(no key defined)";
3427 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
3428 if (!string_format_from(cmd, &bufpos, "%s%s",
3429 argc ? " " : "", req->argv[argc]))
3432 if (!string_format(buf, " %-10s %-14s `%s`",
3433 keymap_table[req->keymap].name, key, cmd))
3436 add_line_text(view, buf, LINE_DEFAULT);
3442 static struct view_ops help_ops = {
3458 struct tree_stack_entry {
3459 struct tree_stack_entry *prev; /* Entry below this in the stack */
3460 unsigned long lineno; /* Line number to restore */
3461 char *name; /* Position of name in opt_path */
3464 /* The top of the path stack. */
3465 static struct tree_stack_entry *tree_stack = NULL;
3466 unsigned long tree_lineno = 0;
3469 pop_tree_stack_entry(void)
3471 struct tree_stack_entry *entry = tree_stack;
3473 tree_lineno = entry->lineno;
3475 tree_stack = entry->prev;
3480 push_tree_stack_entry(const char *name, unsigned long lineno)
3482 struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
3483 size_t pathlen = strlen(opt_path);
3488 entry->prev = tree_stack;
3489 entry->name = opt_path + pathlen;
3492 if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
3493 pop_tree_stack_entry();
3497 /* Move the current line to the first tree entry. */
3499 entry->lineno = lineno;
3502 /* Parse output from git-ls-tree(1):
3504 * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile
3505 * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README
3506 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
3507 * 100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38 web.conf
3510 #define SIZEOF_TREE_ATTR \
3511 STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t")
3513 #define TREE_UP_FORMAT "040000 tree %s\t.."
3516 tree_compare_entry(enum line_type type1, const char *name1,
3517 enum line_type type2, const char *name2)
3519 if (type1 != type2) {
3520 if (type1 == LINE_TREE_DIR)
3525 return strcmp(name1, name2);
3529 tree_path(struct line *line)
3531 const char *path = line->data;
3533 return path + SIZEOF_TREE_ATTR;
3537 tree_read(struct view *view, char *text)
3539 size_t textlen = text ? strlen(text) : 0;
3540 char buf[SIZEOF_STR];
3542 enum line_type type;
3543 bool first_read = view->lines == 0;
3547 if (textlen <= SIZEOF_TREE_ATTR)
3550 type = text[STRING_SIZE("100644 ")] == 't'
3551 ? LINE_TREE_DIR : LINE_TREE_FILE;
3554 /* Add path info line */
3555 if (!string_format(buf, "Directory path /%s", opt_path) ||
3556 !realloc_lines(view, view->line_size + 1) ||
3557 !add_line_text(view, buf, LINE_DEFAULT))
3560 /* Insert "link" to parent directory. */
3562 if (!string_format(buf, TREE_UP_FORMAT, view->ref) ||
3563 !realloc_lines(view, view->line_size + 1) ||
3564 !add_line_text(view, buf, LINE_TREE_DIR))
3569 /* Strip the path part ... */
3571 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
3572 size_t striplen = strlen(opt_path);
3573 char *path = text + SIZEOF_TREE_ATTR;
3575 if (pathlen > striplen)
3576 memmove(path, path + striplen,
3577 pathlen - striplen + 1);
3580 /* Skip "Directory ..." and ".." line. */
3581 for (pos = 1 + !!*opt_path; pos < view->lines; pos++) {
3582 struct line *line = &view->line[pos];
3583 const char *path1 = tree_path(line);
3584 char *path2 = text + SIZEOF_TREE_ATTR;
3585 int cmp = tree_compare_entry(line->type, path1, type, path2);
3590 text = strdup(text);
3594 if (view->lines > pos)
3595 memmove(&view->line[pos + 1], &view->line[pos],
3596 (view->lines - pos) * sizeof(*line));
3598 line = &view->line[pos];
3605 if (!add_line_text(view, text, type))
3608 if (tree_lineno > view->lineno) {
3609 view->lineno = tree_lineno;
3617 tree_request(struct view *view, enum request request, struct line *line)
3619 enum open_flags flags;
3622 case REQ_VIEW_BLAME:
3623 if (line->type != LINE_TREE_FILE) {
3624 report("Blame only supported for files");
3628 string_copy(opt_ref, view->vid);
3632 if (line->type != LINE_TREE_FILE) {
3633 report("Edit only supported for files");
3634 } else if (!is_head_commit(view->vid)) {
3635 report("Edit only supported for files in the current work tree");
3637 open_editor(TRUE, opt_file);
3641 case REQ_TREE_PARENT:
3643 /* quit view if at top of tree */
3644 return REQ_VIEW_CLOSE;
3647 line = &view->line[1];
3657 /* Cleanup the stack if the tree view is at a different tree. */
3658 while (!*opt_path && tree_stack)
3659 pop_tree_stack_entry();
3661 switch (line->type) {
3663 /* Depending on whether it is a subdir or parent (updir?) link
3664 * mangle the path buffer. */
3665 if (line == &view->line[1] && *opt_path) {
3666 pop_tree_stack_entry();
3669 const char *basename = tree_path(line);
3671 push_tree_stack_entry(basename, view->lineno);
3674 /* Trees and subtrees share the same ID, so they are not not
3675 * unique like blobs. */
3676 flags = OPEN_RELOAD;
3677 request = REQ_VIEW_TREE;
3680 case LINE_TREE_FILE:
3681 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
3682 request = REQ_VIEW_BLOB;
3689 open_view(view, request, flags);
3690 if (request == REQ_VIEW_TREE) {
3691 view->lineno = tree_lineno;
3698 tree_select(struct view *view, struct line *line)
3700 char *text = (char *)line->data + STRING_SIZE("100644 blob ");
3702 if (line->type == LINE_TREE_FILE) {
3703 string_copy_rev(ref_blob, text);
3704 string_format(opt_file, "%s%s", opt_path, tree_path(line));
3706 } else if (line->type != LINE_TREE_DIR) {
3710 string_copy_rev(view->ref, text);
3713 static const char *tree_argv[SIZEOF_ARG] = {
3714 "git", "ls-tree", "%(commit)", "%(directory)", NULL
3717 static struct view_ops tree_ops = {
3729 blob_read(struct view *view, char *line)
3733 return add_line_text(view, line, LINE_DEFAULT) != NULL;
3736 static const char *blob_argv[SIZEOF_ARG] = {
3737 "git", "cat-file", "blob", "%(blob)", NULL
3740 static struct view_ops blob_ops = {
3754 * Loading the blame view is a two phase job:
3756 * 1. File content is read either using opt_file from the
3757 * filesystem or using git-cat-file.
3758 * 2. Then blame information is incrementally added by
3759 * reading output from git-blame.
3762 struct blame_commit {
3763 char id[SIZEOF_REV]; /* SHA1 ID. */
3764 char title[128]; /* First line of the commit message. */
3765 char author[75]; /* Author of the commit. */
3766 struct tm time; /* Date from the author ident. */
3767 char filename[128]; /* Name of file. */
3771 struct blame_commit *commit;
3775 #define BLAME_CAT_FILE_CMD "git cat-file blob %s:%s"
3776 #define BLAME_INCREMENTAL_CMD "git blame --incremental %s -- %s"
3779 blame_open(struct view *view)
3781 char path[SIZEOF_STR];
3782 char ref[SIZEOF_STR] = "";
3784 if (sq_quote(path, 0, opt_file) >= sizeof(path))
3787 if (*opt_ref && sq_quote(ref, 0, opt_ref) >= sizeof(ref))
3790 if (*opt_ref || !init_io_fd(&view->io, fopen(opt_file, "r"))) {
3791 const char *id = *opt_ref ? ref : "HEAD";
3793 if (!run_io_format(&view->io, BLAME_CAT_FILE_CMD, id, path))
3797 setup_update(view, opt_file);
3798 string_format(view->ref, "%s ...", opt_file);
3803 static struct blame_commit *
3804 get_blame_commit(struct view *view, const char *id)
3808 for (i = 0; i < view->lines; i++) {
3809 struct blame *blame = view->line[i].data;
3814 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
3815 return blame->commit;
3819 struct blame_commit *commit = calloc(1, sizeof(*commit));
3822 string_ncopy(commit->id, id, SIZEOF_REV);
3828 parse_number(const char **posref, size_t *number, size_t min, size_t max)
3830 const char *pos = *posref;
3833 pos = strchr(pos + 1, ' ');
3834 if (!pos || !isdigit(pos[1]))
3836 *number = atoi(pos + 1);
3837 if (*number < min || *number > max)
3844 static struct blame_commit *
3845 parse_blame_commit(struct view *view, const char *text, int *blamed)
3847 struct blame_commit *commit;
3848 struct blame *blame;
3849 const char *pos = text + SIZEOF_REV - 1;
3853 if (strlen(text) <= SIZEOF_REV || *pos != ' ')
3856 if (!parse_number(&pos, &lineno, 1, view->lines) ||
3857 !parse_number(&pos, &group, 1, view->lines - lineno + 1))
3860 commit = get_blame_commit(view, text);
3866 struct line *line = &view->line[lineno + group - 1];
3869 blame->commit = commit;
3877 blame_read_file(struct view *view, const char *line, bool *read_file)
3880 char ref[SIZEOF_STR] = "";
3881 char path[SIZEOF_STR];
3884 if (view->lines == 0 && !view->parent)
3885 die("No blame exist for %s", view->vid);
3887 if (view->lines == 0 ||
3888 sq_quote(path, 0, opt_file) >= sizeof(path) ||
3889 (*opt_ref && sq_quote(ref, 0, opt_ref) >= sizeof(ref)) ||
3890 !run_io_format(&io, BLAME_INCREMENTAL_CMD, ref, path)) {
3891 report("Failed to load blame data");
3895 done_io(view->pipe);
3901 size_t linelen = strlen(line);
3902 struct blame *blame = malloc(sizeof(*blame) + linelen);
3904 blame->commit = NULL;
3905 strncpy(blame->text, line, linelen);
3906 blame->text[linelen] = 0;
3907 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
3912 match_blame_header(const char *name, char **line)
3914 size_t namelen = strlen(name);
3915 bool matched = !strncmp(name, *line, namelen);
3924 blame_read(struct view *view, char *line)
3926 static struct blame_commit *commit = NULL;
3927 static int blamed = 0;
3928 static time_t author_time;
3929 static bool read_file = TRUE;
3932 return blame_read_file(view, line, &read_file);
3939 string_format(view->ref, "%s", view->vid);
3940 if (view_is_displayed(view)) {
3941 update_view_title(view);
3942 redraw_view_from(view, 0);
3948 commit = parse_blame_commit(view, line, &blamed);
3949 string_format(view->ref, "%s %2d%%", view->vid,
3950 blamed * 100 / view->lines);
3952 } else if (match_blame_header("author ", &line)) {
3953 string_ncopy(commit->author, line, strlen(line));
3955 } else if (match_blame_header("author-time ", &line)) {
3956 author_time = (time_t) atol(line);
3958 } else if (match_blame_header("author-tz ", &line)) {
3961 tz = ('0' - line[1]) * 60 * 60 * 10;
3962 tz += ('0' - line[2]) * 60 * 60;
3963 tz += ('0' - line[3]) * 60;
3964 tz += ('0' - line[4]) * 60;
3970 gmtime_r(&author_time, &commit->time);
3972 } else if (match_blame_header("summary ", &line)) {
3973 string_ncopy(commit->title, line, strlen(line));
3975 } else if (match_blame_header("filename ", &line)) {
3976 string_ncopy(commit->filename, line, strlen(line));
3984 blame_draw(struct view *view, struct line *line, unsigned int lineno)
3986 struct blame *blame = line->data;
3987 struct tm *time = NULL;
3988 const char *id = NULL, *author = NULL;
3990 if (blame->commit && *blame->commit->filename) {
3991 id = blame->commit->id;
3992 author = blame->commit->author;
3993 time = &blame->commit->time;
3996 if (opt_date && draw_date(view, time))
4000 draw_field(view, LINE_MAIN_AUTHOR, author, opt_author_cols, TRUE))
4003 if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
4006 if (draw_lineno(view, lineno))
4009 draw_text(view, LINE_DEFAULT, blame->text, TRUE);
4014 blame_request(struct view *view, enum request request, struct line *line)
4016 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4017 struct blame *blame = line->data;
4020 case REQ_VIEW_BLAME:
4021 if (!blame->commit || !strcmp(blame->commit->id, NULL_ID)) {
4022 report("Commit ID unknown");
4025 string_copy(opt_ref, blame->commit->id);
4026 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4030 if (!blame->commit) {
4031 report("No commit loaded yet");
4035 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
4036 !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
4039 if (!strcmp(blame->commit->id, NULL_ID)) {
4040 char path[SIZEOF_STR];
4042 if (sq_quote(path, 0, view->vid) >= sizeof(path))
4044 string_format(opt_cmd, "git diff-index --root --patch-with-stat -C -M --cached HEAD -- %s 2>/dev/null", path);
4047 open_view(view, REQ_VIEW_DIFF, flags);
4058 blame_grep(struct view *view, struct line *line)
4060 struct blame *blame = line->data;
4061 struct blame_commit *commit = blame->commit;
4064 #define MATCH(text, on) \
4065 (on && *text && regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4068 char buf[DATE_COLS + 1];
4070 if (MATCH(commit->title, 1) ||
4071 MATCH(commit->author, opt_author) ||
4072 MATCH(commit->id, opt_date))
4075 if (strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time) &&
4080 return MATCH(blame->text, 1);
4086 blame_select(struct view *view, struct line *line)
4088 struct blame *blame = line->data;
4089 struct blame_commit *commit = blame->commit;
4094 if (!strcmp(commit->id, NULL_ID))
4095 string_ncopy(ref_commit, "HEAD", 4);
4097 string_copy_rev(ref_commit, commit->id);
4100 static struct view_ops blame_ops = {
4119 char rev[SIZEOF_REV];
4120 char name[SIZEOF_STR];
4124 char rev[SIZEOF_REV];
4125 char name[SIZEOF_STR];
4129 static char status_onbranch[SIZEOF_STR];
4130 static struct status stage_status;
4131 static enum line_type stage_line_type;
4132 static size_t stage_chunks;
4133 static int *stage_chunk;
4135 /* This should work even for the "On branch" line. */
4137 status_has_none(struct view *view, struct line *line)
4139 return line < view->line + view->lines && !line[1].data;
4142 /* Get fields from the diff line:
4143 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
4146 status_get_diff(struct status *file, const char *buf, size_t bufsize)
4148 const char *old_mode = buf + 1;
4149 const char *new_mode = buf + 8;
4150 const char *old_rev = buf + 15;
4151 const char *new_rev = buf + 56;
4152 const char *status = buf + 97;
4155 old_mode[-1] != ':' ||
4156 new_mode[-1] != ' ' ||
4157 old_rev[-1] != ' ' ||
4158 new_rev[-1] != ' ' ||
4162 file->status = *status;
4164 string_copy_rev(file->old.rev, old_rev);
4165 string_copy_rev(file->new.rev, new_rev);
4167 file->old.mode = strtoul(old_mode, NULL, 8);
4168 file->new.mode = strtoul(new_mode, NULL, 8);
4170 file->old.name[0] = file->new.name[0] = 0;
4176 status_run(struct view *view, const char cmd[], char status, enum line_type type)
4178 struct status *file = NULL;
4179 struct status *unmerged = NULL;
4180 char buf[SIZEOF_STR * 4];
4184 pipe = popen(cmd, "r");
4188 add_line_data(view, NULL, type);
4190 while (!feof(pipe) && !ferror(pipe)) {
4194 readsize = fread(buf + bufsize, 1, sizeof(buf) - bufsize, pipe);
4197 bufsize += readsize;
4199 /* Process while we have NUL chars. */
4200 while ((sep = memchr(buf, 0, bufsize))) {
4201 size_t sepsize = sep - buf + 1;
4204 if (!realloc_lines(view, view->line_size + 1))
4207 file = calloc(1, sizeof(*file));
4211 add_line_data(view, file, type);
4214 /* Parse diff info part. */
4216 file->status = status;
4218 string_copy(file->old.rev, NULL_ID);
4220 } else if (!file->status) {
4221 if (!status_get_diff(file, buf, sepsize))
4225 memmove(buf, sep + 1, bufsize);
4227 sep = memchr(buf, 0, bufsize);
4230 sepsize = sep - buf + 1;
4232 /* Collapse all 'M'odified entries that
4233 * follow a associated 'U'nmerged entry.
4235 if (file->status == 'U') {
4238 } else if (unmerged) {
4239 int collapse = !strcmp(buf, unmerged->new.name);
4250 /* Grab the old name for rename/copy. */
4251 if (!*file->old.name &&
4252 (file->status == 'R' || file->status == 'C')) {
4253 sepsize = sep - buf + 1;
4254 string_ncopy(file->old.name, buf, sepsize);
4256 memmove(buf, sep + 1, bufsize);
4258 sep = memchr(buf, 0, bufsize);
4261 sepsize = sep - buf + 1;
4264 /* git-ls-files just delivers a NUL separated
4265 * list of file names similar to the second half
4266 * of the git-diff-* output. */
4267 string_ncopy(file->new.name, buf, sepsize);
4268 if (!*file->old.name)
4269 string_copy(file->old.name, file->new.name);
4271 memmove(buf, sep + 1, bufsize);
4282 if (!view->line[view->lines - 1].data)
4283 add_line_data(view, NULL, LINE_STAT_NONE);
4289 /* Don't show unmerged entries in the staged section. */
4290 #define STATUS_DIFF_INDEX_CMD "git diff-index -z --diff-filter=ACDMRTXB --cached -M HEAD"
4291 #define STATUS_DIFF_FILES_CMD "git diff-files -z"
4292 #define STATUS_LIST_OTHER_CMD \
4293 "git ls-files -z --others --exclude-standard"
4294 #define STATUS_LIST_NO_HEAD_CMD \
4295 "git ls-files -z --cached --exclude-standard"
4297 #define STATUS_DIFF_INDEX_SHOW_CMD \
4298 "git diff-index --root --patch-with-stat -C -M --cached HEAD -- %s %s 2>/dev/null"
4300 #define STATUS_DIFF_FILES_SHOW_CMD \
4301 "git diff-files --root --patch-with-stat -C -M -- %s %s 2>/dev/null"
4303 #define STATUS_DIFF_NO_HEAD_SHOW_CMD \
4304 "git diff --no-color --patch-with-stat /dev/null %s 2>/dev/null"
4306 /* First parse staged info using git-diff-index(1), then parse unstaged
4307 * info using git-diff-files(1), and finally untracked files using
4308 * git-ls-files(1). */
4310 status_open(struct view *view)
4312 unsigned long prev_lineno = view->lineno;
4316 if (!realloc_lines(view, view->line_size + 7))
4319 add_line_data(view, NULL, LINE_STAT_HEAD);
4320 if (is_initial_commit())
4321 string_copy(status_onbranch, "Initial commit");
4322 else if (!*opt_head)
4323 string_copy(status_onbranch, "Not currently on any branch");
4324 else if (!string_format(status_onbranch, "On branch %s", opt_head))
4327 system("git update-index -q --refresh >/dev/null 2>/dev/null");
4329 if (is_initial_commit()) {
4330 if (!status_run(view, STATUS_LIST_NO_HEAD_CMD, 'A', LINE_STAT_STAGED))
4332 } else if (!status_run(view, STATUS_DIFF_INDEX_CMD, 0, LINE_STAT_STAGED)) {
4336 if (!status_run(view, STATUS_DIFF_FILES_CMD, 0, LINE_STAT_UNSTAGED) ||
4337 !status_run(view, STATUS_LIST_OTHER_CMD, '?', LINE_STAT_UNTRACKED))
4340 /* If all went well restore the previous line number to stay in
4341 * the context or select a line with something that can be
4343 if (prev_lineno >= view->lines)
4344 prev_lineno = view->lines - 1;
4345 while (prev_lineno < view->lines && !view->line[prev_lineno].data)
4347 while (prev_lineno > 0 && !view->line[prev_lineno].data)
4350 /* If the above fails, always skip the "On branch" line. */
4351 if (prev_lineno < view->lines)
4352 view->lineno = prev_lineno;
4356 if (view->lineno < view->offset)
4357 view->offset = view->lineno;
4358 else if (view->offset + view->height <= view->lineno)
4359 view->offset = view->lineno - view->height + 1;
4365 status_draw(struct view *view, struct line *line, unsigned int lineno)
4367 struct status *status = line->data;
4368 enum line_type type;
4372 switch (line->type) {
4373 case LINE_STAT_STAGED:
4374 type = LINE_STAT_SECTION;
4375 text = "Changes to be committed:";
4378 case LINE_STAT_UNSTAGED:
4379 type = LINE_STAT_SECTION;
4380 text = "Changed but not updated:";
4383 case LINE_STAT_UNTRACKED:
4384 type = LINE_STAT_SECTION;
4385 text = "Untracked files:";
4388 case LINE_STAT_NONE:
4389 type = LINE_DEFAULT;
4390 text = " (no files)";
4393 case LINE_STAT_HEAD:
4394 type = LINE_STAT_HEAD;
4395 text = status_onbranch;
4402 static char buf[] = { '?', ' ', ' ', ' ', 0 };
4404 buf[0] = status->status;
4405 if (draw_text(view, line->type, buf, TRUE))
4407 type = LINE_DEFAULT;
4408 text = status->new.name;
4411 draw_text(view, type, text, TRUE);
4416 status_enter(struct view *view, struct line *line)
4418 struct status *status = line->data;
4419 char oldpath[SIZEOF_STR] = "";
4420 char newpath[SIZEOF_STR] = "";
4423 enum open_flags split;
4425 if (line->type == LINE_STAT_NONE ||
4426 (!status && line[1].type == LINE_STAT_NONE)) {
4427 report("No file to diff");
4432 if (sq_quote(oldpath, 0, status->old.name) >= sizeof(oldpath))
4434 /* Diffs for unmerged entries are empty when pasing the
4435 * new path, so leave it empty. */
4436 if (status->status != 'U' &&
4437 sq_quote(newpath, 0, status->new.name) >= sizeof(newpath))
4442 line->type != LINE_STAT_UNTRACKED &&
4443 !string_format_from(opt_cmd, &cmdsize, "cd %s;", opt_cdup))
4446 switch (line->type) {
4447 case LINE_STAT_STAGED:
4448 if (is_initial_commit()) {
4449 if (!string_format_from(opt_cmd, &cmdsize,
4450 STATUS_DIFF_NO_HEAD_SHOW_CMD,
4454 if (!string_format_from(opt_cmd, &cmdsize,
4455 STATUS_DIFF_INDEX_SHOW_CMD,
4461 info = "Staged changes to %s";
4463 info = "Staged changes";
4466 case LINE_STAT_UNSTAGED:
4467 if (!string_format_from(opt_cmd, &cmdsize,
4468 STATUS_DIFF_FILES_SHOW_CMD, oldpath, newpath))
4471 info = "Unstaged changes to %s";
4473 info = "Unstaged changes";
4476 case LINE_STAT_UNTRACKED:
4481 report("No file to show");
4485 if (!suffixcmp(status->new.name, -1, "/")) {
4486 report("Cannot display a directory");
4490 opt_pipe = fopen(status->new.name, "r");
4491 info = "Untracked file %s";
4494 case LINE_STAT_HEAD:
4498 die("line type %d not handled in switch", line->type);
4501 split = view_is_displayed(view) ? OPEN_SPLIT : 0;
4502 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH | split);
4503 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
4505 stage_status = *status;
4507 memset(&stage_status, 0, sizeof(stage_status));
4510 stage_line_type = line->type;
4512 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
4519 status_exists(struct status *status, enum line_type type)
4521 struct view *view = VIEW(REQ_VIEW_STATUS);
4524 for (line = view->line; line < view->line + view->lines; line++) {
4525 struct status *pos = line->data;
4527 if (line->type == type && pos &&
4528 !strcmp(status->new.name, pos->new.name))
4537 status_update_prepare(enum line_type type)
4539 char cmd[SIZEOF_STR];
4543 type != LINE_STAT_UNTRACKED &&
4544 !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
4548 case LINE_STAT_STAGED:
4549 string_add(cmd, cmdsize, "git update-index -z --index-info");
4552 case LINE_STAT_UNSTAGED:
4553 case LINE_STAT_UNTRACKED:
4554 string_add(cmd, cmdsize, "git update-index -z --add --remove --stdin");
4558 die("line type %d not handled in switch", type);
4561 return popen(cmd, "w");
4565 status_update_write(FILE *pipe, struct status *status, enum line_type type)
4567 char buf[SIZEOF_STR];
4572 case LINE_STAT_STAGED:
4573 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
4576 status->old.name, 0))
4580 case LINE_STAT_UNSTAGED:
4581 case LINE_STAT_UNTRACKED:
4582 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
4587 die("line type %d not handled in switch", type);
4590 while (!ferror(pipe) && written < bufsize) {
4591 written += fwrite(buf + written, 1, bufsize - written, pipe);
4594 return written == bufsize;
4598 status_update_file(struct status *status, enum line_type type)
4600 FILE *pipe = status_update_prepare(type);
4606 result = status_update_write(pipe, status, type);
4612 status_update_files(struct view *view, struct line *line)
4614 FILE *pipe = status_update_prepare(line->type);
4616 struct line *pos = view->line + view->lines;
4623 for (pos = line; pos < view->line + view->lines && pos->data; pos++)
4626 for (file = 0, done = 0; result && file < files; line++, file++) {
4627 int almost_done = file * 100 / files;
4629 if (almost_done > done) {
4631 string_format(view->ref, "updating file %u of %u (%d%% done)",
4633 update_view_title(view);
4635 result = status_update_write(pipe, line->data, line->type);
4643 status_update(struct view *view)
4645 struct line *line = &view->line[view->lineno];
4647 assert(view->lines);
4650 /* This should work even for the "On branch" line. */
4651 if (line < view->line + view->lines && !line[1].data) {
4652 report("Nothing to update");
4656 if (!status_update_files(view, line + 1)) {
4657 report("Failed to update file status");
4661 } else if (!status_update_file(line->data, line->type)) {
4662 report("Failed to update file status");
4670 status_revert(struct status *status, enum line_type type, bool has_none)
4672 if (!status || type != LINE_STAT_UNSTAGED) {
4673 if (type == LINE_STAT_STAGED) {
4674 report("Cannot revert changes to staged files");
4675 } else if (type == LINE_STAT_UNTRACKED) {
4676 report("Cannot revert changes to untracked files");
4677 } else if (has_none) {
4678 report("Nothing to revert");
4680 report("Cannot revert changes to multiple files");
4685 const char *checkout_argv[] = {
4686 "git", "checkout", "--", status->old.name, NULL
4689 if (!prompt_yesno("Are you sure you want to overwrite any changes?"))
4691 return run_io_fg(checkout_argv, opt_cdup);
4696 status_request(struct view *view, enum request request, struct line *line)
4698 struct status *status = line->data;
4701 case REQ_STATUS_UPDATE:
4702 if (!status_update(view))
4706 case REQ_STATUS_REVERT:
4707 if (!status_revert(status, line->type, status_has_none(view, line)))
4711 case REQ_STATUS_MERGE:
4712 if (!status || status->status != 'U') {
4713 report("Merging only possible for files with unmerged status ('U').");
4716 open_mergetool(status->new.name);
4722 if (status->status == 'D') {
4723 report("File has been deleted.");
4727 open_editor(status->status != '?', status->new.name);
4730 case REQ_VIEW_BLAME:
4732 string_copy(opt_file, status->new.name);
4738 /* After returning the status view has been split to
4739 * show the stage view. No further reloading is
4741 status_enter(view, line);
4745 /* Simply reload the view. */
4752 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
4758 status_select(struct view *view, struct line *line)
4760 struct status *status = line->data;
4761 char file[SIZEOF_STR] = "all files";
4765 if (status && !string_format(file, "'%s'", status->new.name))
4768 if (!status && line[1].type == LINE_STAT_NONE)
4771 switch (line->type) {
4772 case LINE_STAT_STAGED:
4773 text = "Press %s to unstage %s for commit";
4776 case LINE_STAT_UNSTAGED:
4777 text = "Press %s to stage %s for commit";
4780 case LINE_STAT_UNTRACKED:
4781 text = "Press %s to stage %s for addition";
4784 case LINE_STAT_HEAD:
4785 case LINE_STAT_NONE:
4786 text = "Nothing to update";
4790 die("line type %d not handled in switch", line->type);
4793 if (status && status->status == 'U') {
4794 text = "Press %s to resolve conflict in %s";
4795 key = get_key(REQ_STATUS_MERGE);
4798 key = get_key(REQ_STATUS_UPDATE);
4801 string_format(view->ref, text, key, file);
4805 status_grep(struct view *view, struct line *line)
4807 struct status *status = line->data;
4808 enum { S_STATUS, S_NAME, S_END } state;
4815 for (state = S_STATUS; state < S_END; state++) {
4819 case S_NAME: text = status->new.name; break;
4821 buf[0] = status->status;
4829 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4836 static struct view_ops status_ops = {
4849 stage_diff_line(FILE *pipe, struct line *line)
4851 const char *buf = line->data;
4852 size_t bufsize = strlen(buf);
4855 while (!ferror(pipe) && written < bufsize) {
4856 written += fwrite(buf + written, 1, bufsize - written, pipe);
4861 return written == bufsize;
4865 stage_diff_write(FILE *pipe, struct line *line, struct line *end)
4867 while (line < end) {
4868 if (!stage_diff_line(pipe, line++))
4870 if (line->type == LINE_DIFF_CHUNK ||
4871 line->type == LINE_DIFF_HEADER)
4878 static struct line *
4879 stage_diff_find(struct view *view, struct line *line, enum line_type type)
4881 for (; view->line < line; line--)
4882 if (line->type == type)
4889 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
4891 char cmd[SIZEOF_STR];
4893 struct line *diff_hdr;
4896 diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
4901 !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
4904 if (!string_format_from(cmd, &cmdsize,
4905 "git apply --whitespace=nowarn %s %s - && "
4906 "git update-index -q --unmerged --refresh 2>/dev/null",
4907 revert ? "" : "--cached",
4908 revert || stage_line_type == LINE_STAT_STAGED ? "-R" : ""))
4911 pipe = popen(cmd, "w");
4915 if (!stage_diff_write(pipe, diff_hdr, chunk) ||
4916 !stage_diff_write(pipe, chunk, view->line + view->lines))
4921 return chunk ? TRUE : FALSE;
4925 stage_update(struct view *view, struct line *line)
4927 struct line *chunk = NULL;
4929 if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
4930 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
4933 if (!stage_apply_chunk(view, chunk, FALSE)) {
4934 report("Failed to apply chunk");
4938 } else if (!stage_status.status) {
4939 view = VIEW(REQ_VIEW_STATUS);
4941 for (line = view->line; line < view->line + view->lines; line++)
4942 if (line->type == stage_line_type)
4945 if (!status_update_files(view, line + 1)) {
4946 report("Failed to update files");
4950 } else if (!status_update_file(&stage_status, stage_line_type)) {
4951 report("Failed to update file");
4959 stage_revert(struct view *view, struct line *line)
4961 struct line *chunk = NULL;
4963 if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
4964 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
4967 if (!prompt_yesno("Are you sure you want to revert changes?"))
4970 if (!stage_apply_chunk(view, chunk, TRUE)) {
4971 report("Failed to revert chunk");
4977 return status_revert(stage_status.status ? &stage_status : NULL,
4978 stage_line_type, FALSE);
4984 stage_next(struct view *view, struct line *line)
4988 if (!stage_chunks) {
4989 static size_t alloc = 0;
4992 for (line = view->line; line < view->line + view->lines; line++) {
4993 if (line->type != LINE_DIFF_CHUNK)
4996 tmp = realloc_items(stage_chunk, &alloc,
4997 stage_chunks, sizeof(*tmp));
4999 report("Allocation failure");
5004 stage_chunk[stage_chunks++] = line - view->line;
5008 for (i = 0; i < stage_chunks; i++) {
5009 if (stage_chunk[i] > view->lineno) {
5010 do_scroll_view(view, stage_chunk[i] - view->lineno);
5011 report("Chunk %d of %d", i + 1, stage_chunks);
5016 report("No next chunk found");
5020 stage_request(struct view *view, enum request request, struct line *line)
5023 case REQ_STATUS_UPDATE:
5024 if (!stage_update(view, line))
5028 case REQ_STATUS_REVERT:
5029 if (!stage_revert(view, line))
5033 case REQ_STAGE_NEXT:
5034 if (stage_line_type == LINE_STAT_UNTRACKED) {
5035 report("File is untracked; press %s to add",
5036 get_key(REQ_STATUS_UPDATE));
5039 stage_next(view, line);
5043 if (!stage_status.new.name[0])
5045 if (stage_status.status == 'D') {
5046 report("File has been deleted.");
5050 open_editor(stage_status.status != '?', stage_status.new.name);
5054 /* Reload everything ... */
5057 case REQ_VIEW_BLAME:
5058 if (stage_status.new.name[0]) {
5059 string_copy(opt_file, stage_status.new.name);
5065 return pager_request(view, request, line);
5071 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD | OPEN_NOMAXIMIZE);
5073 /* Check whether the staged entry still exists, and close the
5074 * stage view if it doesn't. */
5075 if (!status_exists(&stage_status, stage_line_type))
5076 return REQ_VIEW_CLOSE;
5078 if (stage_line_type == LINE_STAT_UNTRACKED) {
5079 if (!suffixcmp(stage_status.new.name, -1, "/")) {
5080 report("Cannot display a directory");
5084 opt_pipe = fopen(stage_status.new.name, "r");
5086 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
5091 static struct view_ops stage_ops = {
5108 char id[SIZEOF_REV]; /* SHA1 ID. */
5109 char title[128]; /* First line of the commit message. */
5110 char author[75]; /* Author of the commit. */
5111 struct tm time; /* Date from the author ident. */
5112 struct ref **refs; /* Repository references. */
5113 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
5114 size_t graph_size; /* The width of the graph array. */
5115 bool has_parents; /* Rewritten --parents seen. */
5118 /* Size of rev graph with no "padding" columns */
5119 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
5122 struct rev_graph *prev, *next, *parents;
5123 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
5125 struct commit *commit;
5127 unsigned int boundary:1;
5130 /* Parents of the commit being visualized. */
5131 static struct rev_graph graph_parents[4];
5133 /* The current stack of revisions on the graph. */
5134 static struct rev_graph graph_stacks[4] = {
5135 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
5136 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
5137 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
5138 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
5142 graph_parent_is_merge(struct rev_graph *graph)
5144 return graph->parents->size > 1;
5148 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
5150 struct commit *commit = graph->commit;
5152 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
5153 commit->graph[commit->graph_size++] = symbol;
5157 clear_rev_graph(struct rev_graph *graph)
5159 graph->boundary = 0;
5160 graph->size = graph->pos = 0;
5161 graph->commit = NULL;
5162 memset(graph->parents, 0, sizeof(*graph->parents));
5166 done_rev_graph(struct rev_graph *graph)
5168 if (graph_parent_is_merge(graph) &&
5169 graph->pos < graph->size - 1 &&
5170 graph->next->size == graph->size + graph->parents->size - 1) {
5171 size_t i = graph->pos + graph->parents->size - 1;
5173 graph->commit->graph_size = i * 2;
5174 while (i < graph->next->size - 1) {
5175 append_to_rev_graph(graph, ' ');
5176 append_to_rev_graph(graph, '\\');
5181 clear_rev_graph(graph);
5185 push_rev_graph(struct rev_graph *graph, const char *parent)
5189 /* "Collapse" duplicate parents lines.
5191 * FIXME: This needs to also update update the drawn graph but
5192 * for now it just serves as a method for pruning graph lines. */
5193 for (i = 0; i < graph->size; i++)
5194 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
5197 if (graph->size < SIZEOF_REVITEMS) {
5198 string_copy_rev(graph->rev[graph->size++], parent);
5203 get_rev_graph_symbol(struct rev_graph *graph)
5207 if (graph->boundary)
5208 symbol = REVGRAPH_BOUND;
5209 else if (graph->parents->size == 0)
5210 symbol = REVGRAPH_INIT;
5211 else if (graph_parent_is_merge(graph))
5212 symbol = REVGRAPH_MERGE;
5213 else if (graph->pos >= graph->size)
5214 symbol = REVGRAPH_BRANCH;
5216 symbol = REVGRAPH_COMMIT;
5222 draw_rev_graph(struct rev_graph *graph)
5225 chtype separator, line;
5227 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
5228 static struct rev_filler fillers[] = {
5234 chtype symbol = get_rev_graph_symbol(graph);
5235 struct rev_filler *filler;
5238 if (opt_line_graphics)
5239 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
5241 filler = &fillers[DEFAULT];
5243 for (i = 0; i < graph->pos; i++) {
5244 append_to_rev_graph(graph, filler->line);
5245 if (graph_parent_is_merge(graph->prev) &&
5246 graph->prev->pos == i)
5247 filler = &fillers[RSHARP];
5249 append_to_rev_graph(graph, filler->separator);
5252 /* Place the symbol for this revision. */
5253 append_to_rev_graph(graph, symbol);
5255 if (graph->prev->size > graph->size)
5256 filler = &fillers[RDIAG];
5258 filler = &fillers[DEFAULT];
5262 for (; i < graph->size; i++) {
5263 append_to_rev_graph(graph, filler->separator);
5264 append_to_rev_graph(graph, filler->line);
5265 if (graph_parent_is_merge(graph->prev) &&
5266 i < graph->prev->pos + graph->parents->size)
5267 filler = &fillers[RSHARP];
5268 if (graph->prev->size > graph->size)
5269 filler = &fillers[LDIAG];
5272 if (graph->prev->size > graph->size) {
5273 append_to_rev_graph(graph, filler->separator);
5274 if (filler->line != ' ')
5275 append_to_rev_graph(graph, filler->line);
5279 /* Prepare the next rev graph */
5281 prepare_rev_graph(struct rev_graph *graph)
5285 /* First, traverse all lines of revisions up to the active one. */
5286 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
5287 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
5290 push_rev_graph(graph->next, graph->rev[graph->pos]);
5293 /* Interleave the new revision parent(s). */
5294 for (i = 0; !graph->boundary && i < graph->parents->size; i++)
5295 push_rev_graph(graph->next, graph->parents->rev[i]);
5297 /* Lastly, put any remaining revisions. */
5298 for (i = graph->pos + 1; i < graph->size; i++)
5299 push_rev_graph(graph->next, graph->rev[i]);
5303 update_rev_graph(struct rev_graph *graph)
5305 /* If this is the finalizing update ... */
5307 prepare_rev_graph(graph);
5309 /* Graph visualization needs a one rev look-ahead,
5310 * so the first update doesn't visualize anything. */
5311 if (!graph->prev->commit)
5314 draw_rev_graph(graph->prev);
5315 done_rev_graph(graph->prev->prev);
5323 static const char *main_argv[SIZEOF_ARG] = {
5324 "git", "log", "--no-color", "--pretty=raw", "--parents",
5325 "--topo-order", "%(head)", NULL
5329 main_draw(struct view *view, struct line *line, unsigned int lineno)
5331 struct commit *commit = line->data;
5333 if (!*commit->author)
5336 if (opt_date && draw_date(view, &commit->time))
5340 draw_field(view, LINE_MAIN_AUTHOR, commit->author, opt_author_cols, TRUE))
5343 if (opt_rev_graph && commit->graph_size &&
5344 draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
5347 if (opt_show_refs && commit->refs) {
5351 enum line_type type;
5353 if (commit->refs[i]->head)
5354 type = LINE_MAIN_HEAD;
5355 else if (commit->refs[i]->ltag)
5356 type = LINE_MAIN_LOCAL_TAG;
5357 else if (commit->refs[i]->tag)
5358 type = LINE_MAIN_TAG;
5359 else if (commit->refs[i]->tracked)
5360 type = LINE_MAIN_TRACKED;
5361 else if (commit->refs[i]->remote)
5362 type = LINE_MAIN_REMOTE;
5364 type = LINE_MAIN_REF;
5366 if (draw_text(view, type, "[", TRUE) ||
5367 draw_text(view, type, commit->refs[i]->name, TRUE) ||
5368 draw_text(view, type, "]", TRUE))
5371 if (draw_text(view, LINE_DEFAULT, " ", TRUE))
5373 } while (commit->refs[i++]->next);
5376 draw_text(view, LINE_DEFAULT, commit->title, TRUE);
5380 /* Reads git log --pretty=raw output and parses it into the commit struct. */
5382 main_read(struct view *view, char *line)
5384 static struct rev_graph *graph = graph_stacks;
5385 enum line_type type;
5386 struct commit *commit;
5391 if (!view->lines && !view->parent)
5392 die("No revisions match the given arguments.");
5393 if (view->lines > 0) {
5394 commit = view->line[view->lines - 1].data;
5395 if (!*commit->author) {
5398 graph->commit = NULL;
5401 update_rev_graph(graph);
5403 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
5404 clear_rev_graph(&graph_stacks[i]);
5408 type = get_line_type(line);
5409 if (type == LINE_COMMIT) {
5410 commit = calloc(1, sizeof(struct commit));
5414 line += STRING_SIZE("commit ");
5416 graph->boundary = 1;
5420 string_copy_rev(commit->id, line);
5421 commit->refs = get_refs(commit->id);
5422 graph->commit = commit;
5423 add_line_data(view, commit, LINE_MAIN_COMMIT);
5425 while ((line = strchr(line, ' '))) {
5427 push_rev_graph(graph->parents, line);
5428 commit->has_parents = TRUE;
5435 commit = view->line[view->lines - 1].data;
5439 if (commit->has_parents)
5441 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
5446 /* Parse author lines where the name may be empty:
5447 * author <email@address.tld> 1138474660 +0100
5449 char *ident = line + STRING_SIZE("author ");
5450 char *nameend = strchr(ident, '<');
5451 char *emailend = strchr(ident, '>');
5453 if (!nameend || !emailend)
5456 update_rev_graph(graph);
5457 graph = graph->next;
5459 *nameend = *emailend = 0;
5460 ident = chomp_string(ident);
5462 ident = chomp_string(nameend + 1);
5467 string_ncopy(commit->author, ident, strlen(ident));
5469 /* Parse epoch and timezone */
5470 if (emailend[1] == ' ') {
5471 char *secs = emailend + 2;
5472 char *zone = strchr(secs, ' ');
5473 time_t time = (time_t) atol(secs);
5475 if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
5479 tz = ('0' - zone[1]) * 60 * 60 * 10;
5480 tz += ('0' - zone[2]) * 60 * 60;
5481 tz += ('0' - zone[3]) * 60;
5482 tz += ('0' - zone[4]) * 60;
5490 gmtime_r(&time, &commit->time);
5495 /* Fill in the commit title if it has not already been set. */
5496 if (commit->title[0])
5499 /* Require titles to start with a non-space character at the
5500 * offset used by git log. */
5501 if (strncmp(line, " ", 4))
5504 /* Well, if the title starts with a whitespace character,
5505 * try to be forgiving. Otherwise we end up with no title. */
5506 while (isspace(*line))
5510 /* FIXME: More graceful handling of titles; append "..." to
5511 * shortened titles, etc. */
5513 string_ncopy(commit->title, line, strlen(line));
5520 main_request(struct view *view, enum request request, struct line *line)
5522 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
5526 open_view(view, REQ_VIEW_DIFF, flags);
5530 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
5540 grep_refs(struct ref **refs, regex_t *regex)
5548 if (regexec(regex, refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
5550 } while (refs[i++]->next);
5556 main_grep(struct view *view, struct line *line)
5558 struct commit *commit = line->data;
5559 enum { S_TITLE, S_AUTHOR, S_DATE, S_REFS, S_END } state;
5560 char buf[DATE_COLS + 1];
5563 for (state = S_TITLE; state < S_END; state++) {
5567 case S_TITLE: text = commit->title; break;
5571 text = commit->author;
5576 if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
5583 if (grep_refs(commit->refs, view->regex) == TRUE)
5590 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
5598 main_select(struct view *view, struct line *line)
5600 struct commit *commit = line->data;
5602 string_copy_rev(view->ref, commit->id);
5603 string_copy_rev(ref_commit, view->ref);
5606 static struct view_ops main_ops = {
5619 * Unicode / UTF-8 handling
5621 * NOTE: Much of the following code for dealing with unicode is derived from
5622 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
5623 * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
5626 /* I've (over)annotated a lot of code snippets because I am not entirely
5627 * confident that the approach taken by this small UTF-8 interface is correct.
5631 unicode_width(unsigned long c)
5634 (c <= 0x115f /* Hangul Jamo */
5637 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
5639 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
5640 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
5641 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
5642 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
5643 || (c >= 0xffe0 && c <= 0xffe6)
5644 || (c >= 0x20000 && c <= 0x2fffd)
5645 || (c >= 0x30000 && c <= 0x3fffd)))
5649 return opt_tab_size;
5654 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
5655 * Illegal bytes are set one. */
5656 static const unsigned char utf8_bytes[256] = {
5657 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,
5658 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,
5659 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,
5660 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,
5661 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,
5662 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,
5663 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,
5664 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,
5667 /* Decode UTF-8 multi-byte representation into a unicode character. */
5668 static inline unsigned long
5669 utf8_to_unicode(const char *string, size_t length)
5671 unsigned long unicode;
5675 unicode = string[0];
5678 unicode = (string[0] & 0x1f) << 6;
5679 unicode += (string[1] & 0x3f);
5682 unicode = (string[0] & 0x0f) << 12;
5683 unicode += ((string[1] & 0x3f) << 6);
5684 unicode += (string[2] & 0x3f);
5687 unicode = (string[0] & 0x0f) << 18;
5688 unicode += ((string[1] & 0x3f) << 12);
5689 unicode += ((string[2] & 0x3f) << 6);
5690 unicode += (string[3] & 0x3f);
5693 unicode = (string[0] & 0x0f) << 24;
5694 unicode += ((string[1] & 0x3f) << 18);
5695 unicode += ((string[2] & 0x3f) << 12);
5696 unicode += ((string[3] & 0x3f) << 6);
5697 unicode += (string[4] & 0x3f);
5700 unicode = (string[0] & 0x01) << 30;
5701 unicode += ((string[1] & 0x3f) << 24);
5702 unicode += ((string[2] & 0x3f) << 18);
5703 unicode += ((string[3] & 0x3f) << 12);
5704 unicode += ((string[4] & 0x3f) << 6);
5705 unicode += (string[5] & 0x3f);
5708 die("Invalid unicode length");
5711 /* Invalid characters could return the special 0xfffd value but NUL
5712 * should be just as good. */
5713 return unicode > 0xffff ? 0 : unicode;
5716 /* Calculates how much of string can be shown within the given maximum width
5717 * and sets trimmed parameter to non-zero value if all of string could not be
5718 * shown. If the reserve flag is TRUE, it will reserve at least one
5719 * trailing character, which can be useful when drawing a delimiter.
5721 * Returns the number of bytes to output from string to satisfy max_width. */
5723 utf8_length(const char *string, int *width, size_t max_width, int *trimmed, bool reserve)
5725 const char *start = string;
5726 const char *end = strchr(string, '\0');
5727 unsigned char last_bytes = 0;
5728 size_t last_ucwidth = 0;
5733 while (string < end) {
5734 int c = *(unsigned char *) string;
5735 unsigned char bytes = utf8_bytes[c];
5737 unsigned long unicode;
5739 if (string + bytes > end)
5742 /* Change representation to figure out whether
5743 * it is a single- or double-width character. */
5745 unicode = utf8_to_unicode(string, bytes);
5746 /* FIXME: Graceful handling of invalid unicode character. */
5750 ucwidth = unicode_width(unicode);
5752 if (*width > max_width) {
5755 if (reserve && *width == max_width) {
5756 string -= last_bytes;
5757 *width -= last_ucwidth;
5764 last_ucwidth = ucwidth;
5767 return string - start;
5775 /* Whether or not the curses interface has been initialized. */
5776 static bool cursed = FALSE;
5778 /* The status window is used for polling keystrokes. */
5779 static WINDOW *status_win;
5781 static bool status_empty = TRUE;
5783 /* Update status and title window. */
5785 report(const char *msg, ...)
5787 struct view *view = display[current_view];
5793 char buf[SIZEOF_STR];
5796 va_start(args, msg);
5797 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
5798 buf[sizeof(buf) - 1] = 0;
5799 buf[sizeof(buf) - 2] = '.';
5800 buf[sizeof(buf) - 3] = '.';
5801 buf[sizeof(buf) - 4] = '.';
5807 if (!status_empty || *msg) {
5810 va_start(args, msg);
5812 wmove(status_win, 0, 0);
5814 vwprintw(status_win, msg, args);
5815 status_empty = FALSE;
5817 status_empty = TRUE;
5819 wclrtoeol(status_win);
5820 wrefresh(status_win);
5825 update_view_title(view);
5826 update_display_cursor(view);
5829 /* Controls when nodelay should be in effect when polling user input. */
5831 set_nonblocking_input(bool loading)
5833 static unsigned int loading_views;
5835 if ((loading == FALSE && loading_views-- == 1) ||
5836 (loading == TRUE && loading_views++ == 0))
5837 nodelay(status_win, loading);
5845 /* Initialize the curses library */
5846 if (isatty(STDIN_FILENO)) {
5847 cursed = !!initscr();
5850 /* Leave stdin and stdout alone when acting as a pager. */
5851 opt_tty = fopen("/dev/tty", "r+");
5853 die("Failed to open /dev/tty");
5854 cursed = !!newterm(NULL, opt_tty, opt_tty);
5858 die("Failed to initialize curses");
5860 nonl(); /* Tell curses not to do NL->CR/NL on output */
5861 cbreak(); /* Take input chars one at a time, no wait for \n */
5862 noecho(); /* Don't echo input */
5863 leaveok(stdscr, TRUE);
5868 getmaxyx(stdscr, y, x);
5869 status_win = newwin(1, 0, y - 1, 0);
5871 die("Failed to create status window");
5873 /* Enable keyboard mapping */
5874 keypad(status_win, TRUE);
5875 wbkgdset(status_win, get_line_attr(LINE_STATUS));
5877 TABSIZE = opt_tab_size;
5878 if (opt_line_graphics) {
5879 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
5884 prompt_yesno(const char *prompt)
5886 enum { WAIT, STOP, CANCEL } status = WAIT;
5887 bool answer = FALSE;
5889 while (status == WAIT) {
5895 foreach_view (view, i)
5900 mvwprintw(status_win, 0, 0, "%s [Yy]/[Nn]", prompt);
5901 wclrtoeol(status_win);
5903 /* Refresh, accept single keystroke of input */
5904 key = wgetch(status_win);
5928 /* Clear the status window */
5929 status_empty = FALSE;
5936 read_prompt(const char *prompt)
5938 enum { READING, STOP, CANCEL } status = READING;
5939 static char buf[SIZEOF_STR];
5942 while (status == READING) {
5948 foreach_view (view, i)
5953 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
5954 wclrtoeol(status_win);
5956 /* Refresh, accept single keystroke of input */
5957 key = wgetch(status_win);
5962 status = pos ? STOP : CANCEL;
5980 if (pos >= sizeof(buf)) {
5981 report("Input string too long");
5986 buf[pos++] = (char) key;
5990 /* Clear the status window */
5991 status_empty = FALSE;
5994 if (status == CANCEL)
6003 * Repository references
6006 static struct ref *refs = NULL;
6007 static size_t refs_alloc = 0;
6008 static size_t refs_size = 0;
6010 /* Id <-> ref store */
6011 static struct ref ***id_refs = NULL;
6012 static size_t id_refs_alloc = 0;
6013 static size_t id_refs_size = 0;
6016 compare_refs(const void *ref1_, const void *ref2_)
6018 const struct ref *ref1 = *(const struct ref **)ref1_;
6019 const struct ref *ref2 = *(const struct ref **)ref2_;
6021 if (ref1->tag != ref2->tag)
6022 return ref2->tag - ref1->tag;
6023 if (ref1->ltag != ref2->ltag)
6024 return ref2->ltag - ref2->ltag;
6025 if (ref1->head != ref2->head)
6026 return ref2->head - ref1->head;
6027 if (ref1->tracked != ref2->tracked)
6028 return ref2->tracked - ref1->tracked;
6029 if (ref1->remote != ref2->remote)
6030 return ref2->remote - ref1->remote;
6031 return strcmp(ref1->name, ref2->name);
6034 static struct ref **
6035 get_refs(const char *id)
6037 struct ref ***tmp_id_refs;
6038 struct ref **ref_list = NULL;
6039 size_t ref_list_alloc = 0;
6040 size_t ref_list_size = 0;
6043 for (i = 0; i < id_refs_size; i++)
6044 if (!strcmp(id, id_refs[i][0]->id))
6047 tmp_id_refs = realloc_items(id_refs, &id_refs_alloc, id_refs_size + 1,
6052 id_refs = tmp_id_refs;
6054 for (i = 0; i < refs_size; i++) {
6057 if (strcmp(id, refs[i].id))
6060 tmp = realloc_items(ref_list, &ref_list_alloc,
6061 ref_list_size + 1, sizeof(*ref_list));
6069 ref_list[ref_list_size] = &refs[i];
6070 /* XXX: The properties of the commit chains ensures that we can
6071 * safely modify the shared ref. The repo references will
6072 * always be similar for the same id. */
6073 ref_list[ref_list_size]->next = 1;
6079 qsort(ref_list, ref_list_size, sizeof(*ref_list), compare_refs);
6080 ref_list[ref_list_size - 1]->next = 0;
6081 id_refs[id_refs_size++] = ref_list;
6088 read_ref(char *id, size_t idlen, char *name, size_t namelen)
6093 bool remote = FALSE;
6094 bool tracked = FALSE;
6095 bool check_replace = FALSE;
6098 if (!prefixcmp(name, "refs/tags/")) {
6099 if (!suffixcmp(name, namelen, "^{}")) {
6102 if (refs_size > 0 && refs[refs_size - 1].ltag == TRUE)
6103 check_replace = TRUE;
6109 namelen -= STRING_SIZE("refs/tags/");
6110 name += STRING_SIZE("refs/tags/");
6112 } else if (!prefixcmp(name, "refs/remotes/")) {
6114 namelen -= STRING_SIZE("refs/remotes/");
6115 name += STRING_SIZE("refs/remotes/");
6116 tracked = !strcmp(opt_remote, name);
6118 } else if (!prefixcmp(name, "refs/heads/")) {
6119 namelen -= STRING_SIZE("refs/heads/");
6120 name += STRING_SIZE("refs/heads/");
6121 head = !strncmp(opt_head, name, namelen);
6123 } else if (!strcmp(name, "HEAD")) {
6124 string_ncopy(opt_head_rev, id, idlen);
6128 if (check_replace && !strcmp(name, refs[refs_size - 1].name)) {
6129 /* it's an annotated tag, replace the previous sha1 with the
6130 * resolved commit id; relies on the fact git-ls-remote lists
6131 * the commit id of an annotated tag right before the commit id
6133 refs[refs_size - 1].ltag = ltag;
6134 string_copy_rev(refs[refs_size - 1].id, id);
6138 refs = realloc_items(refs, &refs_alloc, refs_size + 1, sizeof(*refs));
6142 ref = &refs[refs_size++];
6143 ref->name = malloc(namelen + 1);
6147 strncpy(ref->name, name, namelen);
6148 ref->name[namelen] = 0;
6152 ref->remote = remote;
6153 ref->tracked = tracked;
6154 string_copy_rev(ref->id, id);
6162 const char *cmd_env = getenv("TIG_LS_REMOTE");
6163 const char *cmd = cmd_env && *cmd_env ? cmd_env : TIG_LS_REMOTE;
6168 while (refs_size > 0)
6169 free(refs[--refs_size].name);
6170 while (id_refs_size > 0)
6171 free(id_refs[--id_refs_size]);
6173 return read_properties(popen(cmd, "r"), "\t", read_ref);
6177 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
6179 if (!strcmp(name, "i18n.commitencoding"))
6180 string_ncopy(opt_encoding, value, valuelen);
6182 if (!strcmp(name, "core.editor"))
6183 string_ncopy(opt_editor, value, valuelen);
6185 /* branch.<head>.remote */
6187 !strncmp(name, "branch.", 7) &&
6188 !strncmp(name + 7, opt_head, strlen(opt_head)) &&
6189 !strcmp(name + 7 + strlen(opt_head), ".remote"))
6190 string_ncopy(opt_remote, value, valuelen);
6192 if (*opt_head && *opt_remote &&
6193 !strncmp(name, "branch.", 7) &&
6194 !strncmp(name + 7, opt_head, strlen(opt_head)) &&
6195 !strcmp(name + 7 + strlen(opt_head), ".merge")) {
6196 size_t from = strlen(opt_remote);
6198 if (!prefixcmp(value, "refs/heads/")) {
6199 value += STRING_SIZE("refs/heads/");
6200 valuelen -= STRING_SIZE("refs/heads/");
6203 if (!string_format_from(opt_remote, &from, "/%s", value))
6211 load_git_config(void)
6213 return read_properties(popen("git " GIT_CONFIG " --list", "r"),
6214 "=", read_repo_config_option);
6218 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
6220 if (!opt_git_dir[0]) {
6221 string_ncopy(opt_git_dir, name, namelen);
6223 } else if (opt_is_inside_work_tree == -1) {
6224 /* This can be 3 different values depending on the
6225 * version of git being used. If git-rev-parse does not
6226 * understand --is-inside-work-tree it will simply echo
6227 * the option else either "true" or "false" is printed.
6228 * Default to true for the unknown case. */
6229 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
6231 } else if (opt_cdup[0] == ' ') {
6232 string_ncopy(opt_cdup, name, namelen);
6234 if (!prefixcmp(name, "refs/heads/")) {
6235 namelen -= STRING_SIZE("refs/heads/");
6236 name += STRING_SIZE("refs/heads/");
6237 string_ncopy(opt_head, name, namelen);
6245 load_repo_info(void)
6248 FILE *pipe = popen("(git rev-parse --git-dir --is-inside-work-tree "
6249 " --show-cdup; git symbolic-ref HEAD) 2>/dev/null", "r");
6251 /* XXX: The line outputted by "--show-cdup" can be empty so
6252 * initialize it to something invalid to make it possible to
6253 * detect whether it has been set or not. */
6256 result = read_properties(pipe, "=", read_repo_info);
6257 if (opt_cdup[0] == ' ')
6264 read_properties(FILE *pipe, const char *separators,
6265 int (*read_property)(char *, size_t, char *, size_t))
6267 char buffer[BUFSIZ];
6274 while (state == OK && (name = fgets(buffer, sizeof(buffer), pipe))) {
6279 name = chomp_string(name);
6280 namelen = strcspn(name, separators);
6282 if (name[namelen]) {
6284 value = chomp_string(name + namelen + 1);
6285 valuelen = strlen(value);
6292 state = read_property(name, namelen, value, valuelen);
6295 if (state != ERR && ferror(pipe))
6308 static void __NORETURN
6311 /* XXX: Restore tty modes and let the OS cleanup the rest! */
6317 static void __NORETURN
6318 die(const char *err, ...)
6324 va_start(args, err);
6325 fputs("tig: ", stderr);
6326 vfprintf(stderr, err, args);
6327 fputs("\n", stderr);
6334 warn(const char *msg, ...)
6338 va_start(args, msg);
6339 fputs("tig warning: ", stderr);
6340 vfprintf(stderr, msg, args);
6341 fputs("\n", stderr);
6346 main(int argc, const char *argv[])
6349 enum request request;
6352 signal(SIGINT, quit);
6354 if (setlocale(LC_ALL, "")) {
6355 char *codeset = nl_langinfo(CODESET);
6357 string_ncopy(opt_codeset, codeset, strlen(codeset));
6360 if (load_repo_info() == ERR)
6361 die("Failed to load repo info.");
6363 if (load_options() == ERR)
6364 die("Failed to load user config.");
6366 if (load_git_config() == ERR)
6367 die("Failed to load repo config.");
6369 request = parse_options(argc, argv);
6370 if (request == REQ_NONE)
6373 /* Require a git repository unless when running in pager mode. */
6374 if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
6375 die("Not a git repository");
6377 if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
6380 if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
6381 opt_iconv = iconv_open(opt_codeset, opt_encoding);
6382 if (opt_iconv == ICONV_NONE)
6383 die("Failed to initialize character set conversion");
6386 if (load_refs() == ERR)
6387 die("Failed to load refs.");
6389 foreach_view (view, i)
6390 argv_from_env(view->ops->argv, view->cmd_env);
6394 while (view_driver(display[current_view], request)) {
6398 foreach_view (view, i)
6400 view = display[current_view];
6402 /* Refresh, accept single keystroke of input */
6403 key = wgetch(status_win);
6405 /* wgetch() with nodelay() enabled returns ERR when there's no
6412 request = get_keybinding(view->keymap, key);
6414 /* Some low-level request handling. This keeps access to
6415 * status_win restricted. */
6419 char *cmd = read_prompt(":");
6422 struct view *next = VIEW(REQ_VIEW_PAGER);
6423 const char *argv[SIZEOF_ARG] = { "git" };
6426 /* When running random commands, initially show the
6427 * command in the title. However, it maybe later be
6428 * overwritten if a commit line is selected. */
6429 string_ncopy(next->ref, cmd, strlen(cmd));
6431 if (!argv_from_string(argv, &argc, cmd)) {
6432 report("Too many arguments");
6433 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
6434 report("Failed to format command");
6436 open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
6444 case REQ_SEARCH_BACK:
6446 const char *prompt = request == REQ_SEARCH ? "/" : "?";
6447 char *search = read_prompt(prompt);
6450 string_ncopy(opt_search, search, strlen(search));
6455 case REQ_SCREEN_RESIZE:
6459 getmaxyx(stdscr, height, width);
6461 /* Resize the status view and let the view driver take
6462 * care of resizing the displayed views. */
6463 wresize(status_win, 1, width);
6464 mvwin(status_win, height - 1, 0);
6465 wrefresh(status_win);