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))
2549 if (view == VIEW(REQ_VIEW_TREE) && strcmp(view->vid, view->id))
2552 if (!run_io_rd(&view->io, view->ops->argv, FORMAT_ALL))
2555 /* Put the current ref_* value to the view title ref
2556 * member. This is needed by the blob view. Most other
2557 * views sets it automatically after loading because the
2558 * first line is a commit line. */
2559 string_copy_rev(view->ref, view->id);
2562 setup_update(view, view->id);
2567 #define ITEM_CHUNK_SIZE 256
2569 realloc_items(void *mem, size_t *size, size_t new_size, size_t item_size)
2571 size_t num_chunks = *size / ITEM_CHUNK_SIZE;
2572 size_t num_chunks_new = (new_size + ITEM_CHUNK_SIZE - 1) / ITEM_CHUNK_SIZE;
2574 if (mem == NULL || num_chunks != num_chunks_new) {
2575 *size = num_chunks_new * ITEM_CHUNK_SIZE;
2576 mem = realloc(mem, *size * item_size);
2582 static struct line *
2583 realloc_lines(struct view *view, size_t line_size)
2585 size_t alloc = view->line_alloc;
2586 struct line *tmp = realloc_items(view->line, &alloc, line_size,
2587 sizeof(*view->line));
2593 view->line_alloc = alloc;
2594 view->line_size = line_size;
2599 update_view(struct view *view)
2601 char out_buffer[BUFSIZ * 2];
2603 /* The number of lines to read. If too low it will cause too much
2604 * redrawing (and possible flickering), if too high responsiveness
2606 unsigned long lines = view->height;
2607 int redraw_from = -1;
2612 /* Only redraw if lines are visible. */
2613 if (view->offset + view->height >= view->lines)
2614 redraw_from = view->lines - view->offset;
2616 /* FIXME: This is probably not perfect for backgrounded views. */
2617 if (!realloc_lines(view, view->lines + lines))
2620 while ((line = io_gets(view->pipe))) {
2621 size_t linelen = strlen(line);
2624 line[linelen - 1] = 0;
2626 if (opt_iconv != ICONV_NONE) {
2627 ICONV_CONST char *inbuf = line;
2628 size_t inlen = linelen;
2630 char *outbuf = out_buffer;
2631 size_t outlen = sizeof(out_buffer);
2635 ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2636 if (ret != (size_t) -1) {
2638 linelen = strlen(out_buffer);
2642 if (!view->ops->read(view, line))
2652 lines = view->lines;
2653 for (digits = 0; lines; digits++)
2656 /* Keep the displayed view in sync with line number scaling. */
2657 if (digits != view->digits) {
2658 view->digits = digits;
2663 if (io_error(view->pipe)) {
2664 report("Failed to read: %s", io_strerror(view->pipe));
2665 end_update(view, TRUE);
2667 } else if (io_eof(view->pipe)) {
2669 end_update(view, FALSE);
2672 if (!view_is_displayed(view))
2675 if (view == VIEW(REQ_VIEW_TREE)) {
2676 /* Clear the view and redraw everything since the tree sorting
2677 * might have rearranged things. */
2680 } else if (redraw_from >= 0) {
2681 /* If this is an incremental update, redraw the previous line
2682 * since for commits some members could have changed when
2683 * loading the main view. */
2684 if (redraw_from > 0)
2687 /* Since revision graph visualization requires knowledge
2688 * about the parent commit, it causes a further one-off
2689 * needed to be redrawn for incremental updates. */
2690 if (redraw_from > 0 && opt_rev_graph)
2693 /* Incrementally draw avoids flickering. */
2694 redraw_view_from(view, redraw_from);
2697 if (view == VIEW(REQ_VIEW_BLAME))
2698 redraw_view_dirty(view);
2700 /* Update the title _after_ the redraw so that if the redraw picks up a
2701 * commit reference in view->ref it'll be available here. */
2702 update_view_title(view);
2706 report("Allocation failure");
2707 end_update(view, TRUE);
2711 static struct line *
2712 add_line_data(struct view *view, void *data, enum line_type type)
2714 struct line *line = &view->line[view->lines++];
2716 memset(line, 0, sizeof(*line));
2723 static struct line *
2724 add_line_text(struct view *view, const char *text, enum line_type type)
2726 char *data = text ? strdup(text) : NULL;
2728 return data ? add_line_data(view, data, type) : NULL;
2737 OPEN_DEFAULT = 0, /* Use default view switching. */
2738 OPEN_SPLIT = 1, /* Split current view. */
2739 OPEN_BACKGROUNDED = 2, /* Backgrounded. */
2740 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
2741 OPEN_NOMAXIMIZE = 8, /* Do not maximize the current view. */
2742 OPEN_REFRESH = 16, /* Refresh view using previous command. */
2743 OPEN_PREPARED = 32, /* Open already prepared command. */
2747 open_view(struct view *prev, enum request request, enum open_flags flags)
2749 bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
2750 bool split = !!(flags & OPEN_SPLIT);
2751 bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
2752 bool nomaximize = !!(flags & (OPEN_NOMAXIMIZE | OPEN_REFRESH));
2753 struct view *view = VIEW(request);
2754 int nviews = displayed_views();
2755 struct view *base_view = display[0];
2757 if (view == prev && nviews == 1 && !reload) {
2758 report("Already in %s view", view->name);
2762 if (view->git_dir && !opt_git_dir[0]) {
2763 report("The %s view is disabled in pager view", view->name);
2771 } else if (!nomaximize) {
2772 /* Maximize the current view. */
2773 memset(display, 0, sizeof(display));
2775 display[current_view] = view;
2778 /* Resize the view when switching between split- and full-screen,
2779 * or when switching between two different full-screen views. */
2780 if (nviews != displayed_views() ||
2781 (nviews == 1 && base_view != display[0]))
2785 end_update(view, TRUE);
2787 if (view->ops->open) {
2788 if (!view->ops->open(view)) {
2789 report("Failed to load %s view", view->name);
2793 } else if ((reload || strcmp(view->vid, view->id)) &&
2794 !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
2795 report("Failed to load %s view", view->name);
2799 if (split && prev->lineno - prev->offset >= prev->height) {
2800 /* Take the title line into account. */
2801 int lines = prev->lineno - prev->offset - prev->height + 1;
2803 /* Scroll the view that was split if the current line is
2804 * outside the new limited view. */
2805 do_scroll_view(prev, lines);
2808 if (prev && view != prev) {
2809 if (split && !backgrounded) {
2810 /* "Blur" the previous view. */
2811 update_view_title(prev);
2814 view->parent = prev;
2817 if (view->pipe && view->lines == 0) {
2818 /* Clear the old view and let the incremental updating refill
2822 } else if (view_is_displayed(view)) {
2827 /* If the view is backgrounded the above calls to report()
2828 * won't redraw the view title. */
2830 update_view_title(view);
2834 open_external_viewer(const char *argv[], const char *dir)
2836 def_prog_mode(); /* save current tty modes */
2837 endwin(); /* restore original tty modes */
2838 run_io_fg(argv, dir);
2839 fprintf(stderr, "Press Enter to continue");
2846 open_mergetool(const char *file)
2848 const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
2850 open_external_viewer(mergetool_argv, NULL);
2854 open_editor(bool from_root, const char *file)
2856 const char *editor_argv[] = { "vi", file, NULL };
2859 editor = getenv("GIT_EDITOR");
2860 if (!editor && *opt_editor)
2861 editor = opt_editor;
2863 editor = getenv("VISUAL");
2865 editor = getenv("EDITOR");
2869 editor_argv[0] = editor;
2870 open_external_viewer(editor_argv, from_root ? opt_cdup : NULL);
2874 open_run_request(enum request request)
2876 struct run_request *req = get_run_request(request);
2877 const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
2880 report("Unknown run request");
2884 if (format_argv(argv, req->argv, FORMAT_ALL))
2885 open_external_viewer(argv, NULL);
2890 * User request switch noodle
2894 view_driver(struct view *view, enum request request)
2898 if (request == REQ_NONE) {
2903 if (request > REQ_NONE) {
2904 open_run_request(request);
2905 /* FIXME: When all views can refresh always do this. */
2906 if (view == VIEW(REQ_VIEW_STATUS) ||
2907 view == VIEW(REQ_VIEW_MAIN) ||
2908 view == VIEW(REQ_VIEW_LOG) ||
2909 view == VIEW(REQ_VIEW_STAGE))
2910 request = REQ_REFRESH;
2915 if (view && view->lines) {
2916 request = view->ops->request(view, request, &view->line[view->lineno]);
2917 if (request == REQ_NONE)
2924 case REQ_MOVE_PAGE_UP:
2925 case REQ_MOVE_PAGE_DOWN:
2926 case REQ_MOVE_FIRST_LINE:
2927 case REQ_MOVE_LAST_LINE:
2928 move_view(view, request);
2931 case REQ_SCROLL_LINE_DOWN:
2932 case REQ_SCROLL_LINE_UP:
2933 case REQ_SCROLL_PAGE_DOWN:
2934 case REQ_SCROLL_PAGE_UP:
2935 scroll_view(view, request);
2938 case REQ_VIEW_BLAME:
2940 report("No file chosen, press %s to open tree view",
2941 get_key(REQ_VIEW_TREE));
2944 open_view(view, request, OPEN_DEFAULT);
2949 report("No file chosen, press %s to open tree view",
2950 get_key(REQ_VIEW_TREE));
2953 open_view(view, request, OPEN_DEFAULT);
2956 case REQ_VIEW_PAGER:
2957 if (!opt_pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
2958 report("No pager content, press %s to run command from prompt",
2959 get_key(REQ_PROMPT));
2962 open_view(view, request, OPEN_DEFAULT);
2965 case REQ_VIEW_STAGE:
2966 if (!VIEW(REQ_VIEW_STAGE)->lines) {
2967 report("No stage content, press %s to open the status view and choose file",
2968 get_key(REQ_VIEW_STATUS));
2971 open_view(view, request, OPEN_DEFAULT);
2974 case REQ_VIEW_STATUS:
2975 if (opt_is_inside_work_tree == FALSE) {
2976 report("The status view requires a working tree");
2979 open_view(view, request, OPEN_DEFAULT);
2987 open_view(view, request, OPEN_DEFAULT);
2992 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
2994 if ((view == VIEW(REQ_VIEW_DIFF) &&
2995 view->parent == VIEW(REQ_VIEW_MAIN)) ||
2996 (view == VIEW(REQ_VIEW_DIFF) &&
2997 view->parent == VIEW(REQ_VIEW_BLAME)) ||
2998 (view == VIEW(REQ_VIEW_STAGE) &&
2999 view->parent == VIEW(REQ_VIEW_STATUS)) ||
3000 (view == VIEW(REQ_VIEW_BLOB) &&
3001 view->parent == VIEW(REQ_VIEW_TREE))) {
3004 view = view->parent;
3005 line = view->lineno;
3006 move_view(view, request);
3007 if (view_is_displayed(view))
3008 update_view_title(view);
3009 if (line != view->lineno)
3010 view->ops->request(view, REQ_ENTER,
3011 &view->line[view->lineno]);
3014 move_view(view, request);
3020 int nviews = displayed_views();
3021 int next_view = (current_view + 1) % nviews;
3023 if (next_view == current_view) {
3024 report("Only one view is displayed");
3028 current_view = next_view;
3029 /* Blur out the title of the previous view. */
3030 update_view_title(view);
3035 report("Refreshing is not yet supported for the %s view", view->name);
3039 if (displayed_views() == 2)
3040 open_view(view, VIEW_REQ(view), OPEN_DEFAULT);
3043 case REQ_TOGGLE_LINENO:
3044 opt_line_number = !opt_line_number;
3048 case REQ_TOGGLE_DATE:
3049 opt_date = !opt_date;
3053 case REQ_TOGGLE_AUTHOR:
3054 opt_author = !opt_author;
3058 case REQ_TOGGLE_REV_GRAPH:
3059 opt_rev_graph = !opt_rev_graph;
3063 case REQ_TOGGLE_REFS:
3064 opt_show_refs = !opt_show_refs;
3069 case REQ_SEARCH_BACK:
3070 search_view(view, request);
3075 find_next(view, request);
3078 case REQ_STOP_LOADING:
3079 for (i = 0; i < ARRAY_SIZE(views); i++) {
3082 report("Stopped loading the %s view", view->name),
3083 end_update(view, TRUE);
3087 case REQ_SHOW_VERSION:
3088 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3091 case REQ_SCREEN_RESIZE:
3094 case REQ_SCREEN_REDRAW:
3099 report("Nothing to edit");
3103 report("Nothing to enter");
3106 case REQ_VIEW_CLOSE:
3107 /* XXX: Mark closed views by letting view->parent point to the
3108 * view itself. Parents to closed view should never be
3111 view->parent->parent != view->parent) {
3112 memset(display, 0, sizeof(display));
3114 display[current_view] = view->parent;
3115 view->parent = view;
3126 report("Unknown key, press 'h' for help");
3139 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3141 char *text = line->data;
3143 if (opt_line_number && draw_lineno(view, lineno))
3146 draw_text(view, line->type, text, TRUE);
3151 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3153 char refbuf[SIZEOF_STR];
3157 if (!string_format(refbuf, "git describe %s 2>/dev/null", commit_id))
3160 pipe = popen(refbuf, "r");
3164 if ((ref = fgets(refbuf, sizeof(refbuf), pipe)))
3165 ref = chomp_string(ref);
3171 /* This is the only fatal call, since it can "corrupt" the buffer. */
3172 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
3179 add_pager_refs(struct view *view, struct line *line)
3181 char buf[SIZEOF_STR];
3182 char *commit_id = (char *)line->data + STRING_SIZE("commit ");
3184 size_t bufpos = 0, refpos = 0;
3185 const char *sep = "Refs: ";
3186 bool is_tag = FALSE;
3188 assert(line->type == LINE_COMMIT);
3190 refs = get_refs(commit_id);
3192 if (view == VIEW(REQ_VIEW_DIFF))
3193 goto try_add_describe_ref;
3198 struct ref *ref = refs[refpos];
3199 const char *fmt = ref->tag ? "%s[%s]" :
3200 ref->remote ? "%s<%s>" : "%s%s";
3202 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
3207 } while (refs[refpos++]->next);
3209 if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
3210 try_add_describe_ref:
3211 /* Add <tag>-g<commit_id> "fake" reference. */
3212 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
3219 if (!realloc_lines(view, view->line_size + 1))
3222 add_line_text(view, buf, LINE_PP_REFS);
3226 pager_read(struct view *view, char *data)
3233 line = add_line_text(view, data, get_line_type(data));
3237 if (line->type == LINE_COMMIT &&
3238 (view == VIEW(REQ_VIEW_DIFF) ||
3239 view == VIEW(REQ_VIEW_LOG)))
3240 add_pager_refs(view, line);
3246 pager_request(struct view *view, enum request request, struct line *line)
3250 if (request != REQ_ENTER)
3253 if (line->type == LINE_COMMIT &&
3254 (view == VIEW(REQ_VIEW_LOG) ||
3255 view == VIEW(REQ_VIEW_PAGER))) {
3256 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3260 /* Always scroll the view even if it was split. That way
3261 * you can use Enter to scroll through the log view and
3262 * split open each commit diff. */
3263 scroll_view(view, REQ_SCROLL_LINE_DOWN);
3265 /* FIXME: A minor workaround. Scrolling the view will call report("")
3266 * but if we are scrolling a non-current view this won't properly
3267 * update the view title. */
3269 update_view_title(view);
3275 pager_grep(struct view *view, struct line *line)
3278 char *text = line->data;
3283 if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
3290 pager_select(struct view *view, struct line *line)
3292 if (line->type == LINE_COMMIT) {
3293 char *text = (char *)line->data + STRING_SIZE("commit ");
3295 if (view != VIEW(REQ_VIEW_PAGER))
3296 string_copy_rev(view->ref, text);
3297 string_copy_rev(ref_commit, text);
3301 static struct view_ops pager_ops = {
3312 static const char *log_argv[SIZEOF_ARG] = {
3313 "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3317 log_request(struct view *view, enum request request, struct line *line)
3322 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
3325 return pager_request(view, request, line);
3329 static struct view_ops log_ops = {
3340 static const char *diff_argv[SIZEOF_ARG] = {
3341 "git", "show", "--pretty=fuller", "--no-color", "--root",
3342 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
3345 static struct view_ops diff_ops = {
3361 help_open(struct view *view)
3364 int lines = ARRAY_SIZE(req_info) + 2;
3367 if (view->lines > 0)
3370 for (i = 0; i < ARRAY_SIZE(req_info); i++)
3371 if (!req_info[i].request)
3374 lines += run_requests + 1;
3376 view->line = calloc(lines, sizeof(*view->line));
3380 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
3382 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
3385 if (req_info[i].request == REQ_NONE)
3388 if (!req_info[i].request) {
3389 add_line_text(view, "", LINE_DEFAULT);
3390 add_line_text(view, req_info[i].help, LINE_DEFAULT);
3394 key = get_key(req_info[i].request);
3396 key = "(no key defined)";
3398 if (!string_format(buf, " %-25s %s", key, req_info[i].help))
3401 add_line_text(view, buf, LINE_DEFAULT);
3405 add_line_text(view, "", LINE_DEFAULT);
3406 add_line_text(view, "External commands:", LINE_DEFAULT);
3409 for (i = 0; i < run_requests; i++) {
3410 struct run_request *req = get_run_request(REQ_NONE + i + 1);
3412 char cmd[SIZEOF_STR];
3419 key = get_key_name(req->key);
3421 key = "(no key defined)";
3423 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
3424 if (!string_format_from(cmd, &bufpos, "%s%s",
3425 argc ? " " : "", req->argv[argc]))
3428 if (!string_format(buf, " %-10s %-14s `%s`",
3429 keymap_table[req->keymap].name, key, cmd))
3432 add_line_text(view, buf, LINE_DEFAULT);
3438 static struct view_ops help_ops = {
3454 struct tree_stack_entry {
3455 struct tree_stack_entry *prev; /* Entry below this in the stack */
3456 unsigned long lineno; /* Line number to restore */
3457 char *name; /* Position of name in opt_path */
3460 /* The top of the path stack. */
3461 static struct tree_stack_entry *tree_stack = NULL;
3462 unsigned long tree_lineno = 0;
3465 pop_tree_stack_entry(void)
3467 struct tree_stack_entry *entry = tree_stack;
3469 tree_lineno = entry->lineno;
3471 tree_stack = entry->prev;
3476 push_tree_stack_entry(const char *name, unsigned long lineno)
3478 struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
3479 size_t pathlen = strlen(opt_path);
3484 entry->prev = tree_stack;
3485 entry->name = opt_path + pathlen;
3488 if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
3489 pop_tree_stack_entry();
3493 /* Move the current line to the first tree entry. */
3495 entry->lineno = lineno;
3498 /* Parse output from git-ls-tree(1):
3500 * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile
3501 * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README
3502 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
3503 * 100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38 web.conf
3506 #define SIZEOF_TREE_ATTR \
3507 STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t")
3509 #define TREE_UP_FORMAT "040000 tree %s\t.."
3512 tree_compare_entry(enum line_type type1, const char *name1,
3513 enum line_type type2, const char *name2)
3515 if (type1 != type2) {
3516 if (type1 == LINE_TREE_DIR)
3521 return strcmp(name1, name2);
3525 tree_path(struct line *line)
3527 const char *path = line->data;
3529 return path + SIZEOF_TREE_ATTR;
3533 tree_read(struct view *view, char *text)
3535 size_t textlen = text ? strlen(text) : 0;
3536 char buf[SIZEOF_STR];
3538 enum line_type type;
3539 bool first_read = view->lines == 0;
3543 if (textlen <= SIZEOF_TREE_ATTR)
3546 type = text[STRING_SIZE("100644 ")] == 't'
3547 ? LINE_TREE_DIR : LINE_TREE_FILE;
3550 /* Add path info line */
3551 if (!string_format(buf, "Directory path /%s", opt_path) ||
3552 !realloc_lines(view, view->line_size + 1) ||
3553 !add_line_text(view, buf, LINE_DEFAULT))
3556 /* Insert "link" to parent directory. */
3558 if (!string_format(buf, TREE_UP_FORMAT, view->ref) ||
3559 !realloc_lines(view, view->line_size + 1) ||
3560 !add_line_text(view, buf, LINE_TREE_DIR))
3565 /* Strip the path part ... */
3567 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
3568 size_t striplen = strlen(opt_path);
3569 char *path = text + SIZEOF_TREE_ATTR;
3571 if (pathlen > striplen)
3572 memmove(path, path + striplen,
3573 pathlen - striplen + 1);
3576 /* Skip "Directory ..." and ".." line. */
3577 for (pos = 1 + !!*opt_path; pos < view->lines; pos++) {
3578 struct line *line = &view->line[pos];
3579 const char *path1 = tree_path(line);
3580 char *path2 = text + SIZEOF_TREE_ATTR;
3581 int cmp = tree_compare_entry(line->type, path1, type, path2);
3586 text = strdup(text);
3590 if (view->lines > pos)
3591 memmove(&view->line[pos + 1], &view->line[pos],
3592 (view->lines - pos) * sizeof(*line));
3594 line = &view->line[pos];
3601 if (!add_line_text(view, text, type))
3604 if (tree_lineno > view->lineno) {
3605 view->lineno = tree_lineno;
3613 tree_request(struct view *view, enum request request, struct line *line)
3615 enum open_flags flags;
3618 case REQ_VIEW_BLAME:
3619 if (line->type != LINE_TREE_FILE) {
3620 report("Blame only supported for files");
3624 string_copy(opt_ref, view->vid);
3628 if (line->type != LINE_TREE_FILE) {
3629 report("Edit only supported for files");
3630 } else if (!is_head_commit(view->vid)) {
3631 report("Edit only supported for files in the current work tree");
3633 open_editor(TRUE, opt_file);
3637 case REQ_TREE_PARENT:
3639 /* quit view if at top of tree */
3640 return REQ_VIEW_CLOSE;
3643 line = &view->line[1];
3653 /* Cleanup the stack if the tree view is at a different tree. */
3654 while (!*opt_path && tree_stack)
3655 pop_tree_stack_entry();
3657 switch (line->type) {
3659 /* Depending on whether it is a subdir or parent (updir?) link
3660 * mangle the path buffer. */
3661 if (line == &view->line[1] && *opt_path) {
3662 pop_tree_stack_entry();
3665 const char *basename = tree_path(line);
3667 push_tree_stack_entry(basename, view->lineno);
3670 /* Trees and subtrees share the same ID, so they are not not
3671 * unique like blobs. */
3672 flags = OPEN_RELOAD;
3673 request = REQ_VIEW_TREE;
3676 case LINE_TREE_FILE:
3677 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
3678 request = REQ_VIEW_BLOB;
3685 open_view(view, request, flags);
3686 if (request == REQ_VIEW_TREE) {
3687 view->lineno = tree_lineno;
3694 tree_select(struct view *view, struct line *line)
3696 char *text = (char *)line->data + STRING_SIZE("100644 blob ");
3698 if (line->type == LINE_TREE_FILE) {
3699 string_copy_rev(ref_blob, text);
3700 string_format(opt_file, "%s%s", opt_path, tree_path(line));
3702 } else if (line->type != LINE_TREE_DIR) {
3706 string_copy_rev(view->ref, text);
3709 static const char *tree_argv[SIZEOF_ARG] = {
3710 "git", "ls-tree", "%(commit)", "%(directory)", NULL
3713 static struct view_ops tree_ops = {
3725 blob_read(struct view *view, char *line)
3729 return add_line_text(view, line, LINE_DEFAULT) != NULL;
3732 static const char *blob_argv[SIZEOF_ARG] = {
3733 "git", "cat-file", "blob", "%(blob)", NULL
3736 static struct view_ops blob_ops = {
3750 * Loading the blame view is a two phase job:
3752 * 1. File content is read either using opt_file from the
3753 * filesystem or using git-cat-file.
3754 * 2. Then blame information is incrementally added by
3755 * reading output from git-blame.
3758 struct blame_commit {
3759 char id[SIZEOF_REV]; /* SHA1 ID. */
3760 char title[128]; /* First line of the commit message. */
3761 char author[75]; /* Author of the commit. */
3762 struct tm time; /* Date from the author ident. */
3763 char filename[128]; /* Name of file. */
3767 struct blame_commit *commit;
3771 #define BLAME_CAT_FILE_CMD "git cat-file blob %s:%s"
3772 #define BLAME_INCREMENTAL_CMD "git blame --incremental %s -- %s"
3775 blame_open(struct view *view)
3777 char path[SIZEOF_STR];
3778 char ref[SIZEOF_STR] = "";
3780 if (sq_quote(path, 0, opt_file) >= sizeof(path))
3783 if (*opt_ref && sq_quote(ref, 0, opt_ref) >= sizeof(ref))
3786 if (*opt_ref || !init_io_fd(&view->io, fopen(opt_file, "r"))) {
3787 const char *id = *opt_ref ? ref : "HEAD";
3789 if (!run_io_format(&view->io, BLAME_CAT_FILE_CMD, id, path))
3793 setup_update(view, opt_file);
3794 string_format(view->ref, "%s ...", opt_file);
3799 static struct blame_commit *
3800 get_blame_commit(struct view *view, const char *id)
3804 for (i = 0; i < view->lines; i++) {
3805 struct blame *blame = view->line[i].data;
3810 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
3811 return blame->commit;
3815 struct blame_commit *commit = calloc(1, sizeof(*commit));
3818 string_ncopy(commit->id, id, SIZEOF_REV);
3824 parse_number(const char **posref, size_t *number, size_t min, size_t max)
3826 const char *pos = *posref;
3829 pos = strchr(pos + 1, ' ');
3830 if (!pos || !isdigit(pos[1]))
3832 *number = atoi(pos + 1);
3833 if (*number < min || *number > max)
3840 static struct blame_commit *
3841 parse_blame_commit(struct view *view, const char *text, int *blamed)
3843 struct blame_commit *commit;
3844 struct blame *blame;
3845 const char *pos = text + SIZEOF_REV - 1;
3849 if (strlen(text) <= SIZEOF_REV || *pos != ' ')
3852 if (!parse_number(&pos, &lineno, 1, view->lines) ||
3853 !parse_number(&pos, &group, 1, view->lines - lineno + 1))
3856 commit = get_blame_commit(view, text);
3862 struct line *line = &view->line[lineno + group - 1];
3865 blame->commit = commit;
3873 blame_read_file(struct view *view, const char *line, bool *read_file)
3876 char ref[SIZEOF_STR] = "";
3877 char path[SIZEOF_STR];
3880 if (view->lines == 0 && !view->parent)
3881 die("No blame exist for %s", view->vid);
3883 if (view->lines == 0 ||
3884 sq_quote(path, 0, opt_file) >= sizeof(path) ||
3885 (*opt_ref && sq_quote(ref, 0, opt_ref) >= sizeof(ref)) ||
3886 !run_io_format(&io, BLAME_INCREMENTAL_CMD, ref, path)) {
3887 report("Failed to load blame data");
3891 done_io(view->pipe);
3897 size_t linelen = strlen(line);
3898 struct blame *blame = malloc(sizeof(*blame) + linelen);
3900 blame->commit = NULL;
3901 strncpy(blame->text, line, linelen);
3902 blame->text[linelen] = 0;
3903 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
3908 match_blame_header(const char *name, char **line)
3910 size_t namelen = strlen(name);
3911 bool matched = !strncmp(name, *line, namelen);
3920 blame_read(struct view *view, char *line)
3922 static struct blame_commit *commit = NULL;
3923 static int blamed = 0;
3924 static time_t author_time;
3925 static bool read_file = TRUE;
3928 return blame_read_file(view, line, &read_file);
3935 string_format(view->ref, "%s", view->vid);
3936 if (view_is_displayed(view)) {
3937 update_view_title(view);
3938 redraw_view_from(view, 0);
3944 commit = parse_blame_commit(view, line, &blamed);
3945 string_format(view->ref, "%s %2d%%", view->vid,
3946 blamed * 100 / view->lines);
3948 } else if (match_blame_header("author ", &line)) {
3949 string_ncopy(commit->author, line, strlen(line));
3951 } else if (match_blame_header("author-time ", &line)) {
3952 author_time = (time_t) atol(line);
3954 } else if (match_blame_header("author-tz ", &line)) {
3957 tz = ('0' - line[1]) * 60 * 60 * 10;
3958 tz += ('0' - line[2]) * 60 * 60;
3959 tz += ('0' - line[3]) * 60;
3960 tz += ('0' - line[4]) * 60;
3966 gmtime_r(&author_time, &commit->time);
3968 } else if (match_blame_header("summary ", &line)) {
3969 string_ncopy(commit->title, line, strlen(line));
3971 } else if (match_blame_header("filename ", &line)) {
3972 string_ncopy(commit->filename, line, strlen(line));
3980 blame_draw(struct view *view, struct line *line, unsigned int lineno)
3982 struct blame *blame = line->data;
3983 struct tm *time = NULL;
3984 const char *id = NULL, *author = NULL;
3986 if (blame->commit && *blame->commit->filename) {
3987 id = blame->commit->id;
3988 author = blame->commit->author;
3989 time = &blame->commit->time;
3992 if (opt_date && draw_date(view, time))
3996 draw_field(view, LINE_MAIN_AUTHOR, author, opt_author_cols, TRUE))
3999 if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
4002 if (draw_lineno(view, lineno))
4005 draw_text(view, LINE_DEFAULT, blame->text, TRUE);
4010 blame_request(struct view *view, enum request request, struct line *line)
4012 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4013 struct blame *blame = line->data;
4016 case REQ_VIEW_BLAME:
4017 if (!blame->commit || !strcmp(blame->commit->id, NULL_ID)) {
4018 report("Commit ID unknown");
4021 string_copy(opt_ref, blame->commit->id);
4022 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4026 if (!blame->commit) {
4027 report("No commit loaded yet");
4031 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
4032 !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
4035 if (!strcmp(blame->commit->id, NULL_ID)) {
4036 char path[SIZEOF_STR];
4038 if (sq_quote(path, 0, view->vid) >= sizeof(path))
4040 string_format(opt_cmd, "git diff-index --root --patch-with-stat -C -M --cached HEAD -- %s 2>/dev/null", path);
4043 open_view(view, REQ_VIEW_DIFF, flags);
4054 blame_grep(struct view *view, struct line *line)
4056 struct blame *blame = line->data;
4057 struct blame_commit *commit = blame->commit;
4060 #define MATCH(text, on) \
4061 (on && *text && regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4064 char buf[DATE_COLS + 1];
4066 if (MATCH(commit->title, 1) ||
4067 MATCH(commit->author, opt_author) ||
4068 MATCH(commit->id, opt_date))
4071 if (strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time) &&
4076 return MATCH(blame->text, 1);
4082 blame_select(struct view *view, struct line *line)
4084 struct blame *blame = line->data;
4085 struct blame_commit *commit = blame->commit;
4090 if (!strcmp(commit->id, NULL_ID))
4091 string_ncopy(ref_commit, "HEAD", 4);
4093 string_copy_rev(ref_commit, commit->id);
4096 static struct view_ops blame_ops = {
4115 char rev[SIZEOF_REV];
4116 char name[SIZEOF_STR];
4120 char rev[SIZEOF_REV];
4121 char name[SIZEOF_STR];
4125 static char status_onbranch[SIZEOF_STR];
4126 static struct status stage_status;
4127 static enum line_type stage_line_type;
4128 static size_t stage_chunks;
4129 static int *stage_chunk;
4131 /* This should work even for the "On branch" line. */
4133 status_has_none(struct view *view, struct line *line)
4135 return line < view->line + view->lines && !line[1].data;
4138 /* Get fields from the diff line:
4139 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
4142 status_get_diff(struct status *file, const char *buf, size_t bufsize)
4144 const char *old_mode = buf + 1;
4145 const char *new_mode = buf + 8;
4146 const char *old_rev = buf + 15;
4147 const char *new_rev = buf + 56;
4148 const char *status = buf + 97;
4151 old_mode[-1] != ':' ||
4152 new_mode[-1] != ' ' ||
4153 old_rev[-1] != ' ' ||
4154 new_rev[-1] != ' ' ||
4158 file->status = *status;
4160 string_copy_rev(file->old.rev, old_rev);
4161 string_copy_rev(file->new.rev, new_rev);
4163 file->old.mode = strtoul(old_mode, NULL, 8);
4164 file->new.mode = strtoul(new_mode, NULL, 8);
4166 file->old.name[0] = file->new.name[0] = 0;
4172 status_run(struct view *view, const char cmd[], char status, enum line_type type)
4174 struct status *file = NULL;
4175 struct status *unmerged = NULL;
4176 char buf[SIZEOF_STR * 4];
4180 pipe = popen(cmd, "r");
4184 add_line_data(view, NULL, type);
4186 while (!feof(pipe) && !ferror(pipe)) {
4190 readsize = fread(buf + bufsize, 1, sizeof(buf) - bufsize, pipe);
4193 bufsize += readsize;
4195 /* Process while we have NUL chars. */
4196 while ((sep = memchr(buf, 0, bufsize))) {
4197 size_t sepsize = sep - buf + 1;
4200 if (!realloc_lines(view, view->line_size + 1))
4203 file = calloc(1, sizeof(*file));
4207 add_line_data(view, file, type);
4210 /* Parse diff info part. */
4212 file->status = status;
4214 string_copy(file->old.rev, NULL_ID);
4216 } else if (!file->status) {
4217 if (!status_get_diff(file, buf, sepsize))
4221 memmove(buf, sep + 1, bufsize);
4223 sep = memchr(buf, 0, bufsize);
4226 sepsize = sep - buf + 1;
4228 /* Collapse all 'M'odified entries that
4229 * follow a associated 'U'nmerged entry.
4231 if (file->status == 'U') {
4234 } else if (unmerged) {
4235 int collapse = !strcmp(buf, unmerged->new.name);
4246 /* Grab the old name for rename/copy. */
4247 if (!*file->old.name &&
4248 (file->status == 'R' || file->status == 'C')) {
4249 sepsize = sep - buf + 1;
4250 string_ncopy(file->old.name, buf, sepsize);
4252 memmove(buf, sep + 1, bufsize);
4254 sep = memchr(buf, 0, bufsize);
4257 sepsize = sep - buf + 1;
4260 /* git-ls-files just delivers a NUL separated
4261 * list of file names similar to the second half
4262 * of the git-diff-* output. */
4263 string_ncopy(file->new.name, buf, sepsize);
4264 if (!*file->old.name)
4265 string_copy(file->old.name, file->new.name);
4267 memmove(buf, sep + 1, bufsize);
4278 if (!view->line[view->lines - 1].data)
4279 add_line_data(view, NULL, LINE_STAT_NONE);
4285 /* Don't show unmerged entries in the staged section. */
4286 #define STATUS_DIFF_INDEX_CMD "git diff-index -z --diff-filter=ACDMRTXB --cached -M HEAD"
4287 #define STATUS_DIFF_FILES_CMD "git diff-files -z"
4288 #define STATUS_LIST_OTHER_CMD \
4289 "git ls-files -z --others --exclude-standard"
4290 #define STATUS_LIST_NO_HEAD_CMD \
4291 "git ls-files -z --cached --exclude-standard"
4293 #define STATUS_DIFF_INDEX_SHOW_CMD \
4294 "git diff-index --root --patch-with-stat -C -M --cached HEAD -- %s %s 2>/dev/null"
4296 #define STATUS_DIFF_FILES_SHOW_CMD \
4297 "git diff-files --root --patch-with-stat -C -M -- %s %s 2>/dev/null"
4299 #define STATUS_DIFF_NO_HEAD_SHOW_CMD \
4300 "git diff --no-color --patch-with-stat /dev/null %s 2>/dev/null"
4302 /* First parse staged info using git-diff-index(1), then parse unstaged
4303 * info using git-diff-files(1), and finally untracked files using
4304 * git-ls-files(1). */
4306 status_open(struct view *view)
4308 unsigned long prev_lineno = view->lineno;
4312 if (!realloc_lines(view, view->line_size + 7))
4315 add_line_data(view, NULL, LINE_STAT_HEAD);
4316 if (is_initial_commit())
4317 string_copy(status_onbranch, "Initial commit");
4318 else if (!*opt_head)
4319 string_copy(status_onbranch, "Not currently on any branch");
4320 else if (!string_format(status_onbranch, "On branch %s", opt_head))
4323 system("git update-index -q --refresh >/dev/null 2>/dev/null");
4325 if (is_initial_commit()) {
4326 if (!status_run(view, STATUS_LIST_NO_HEAD_CMD, 'A', LINE_STAT_STAGED))
4328 } else if (!status_run(view, STATUS_DIFF_INDEX_CMD, 0, LINE_STAT_STAGED)) {
4332 if (!status_run(view, STATUS_DIFF_FILES_CMD, 0, LINE_STAT_UNSTAGED) ||
4333 !status_run(view, STATUS_LIST_OTHER_CMD, '?', LINE_STAT_UNTRACKED))
4336 /* If all went well restore the previous line number to stay in
4337 * the context or select a line with something that can be
4339 if (prev_lineno >= view->lines)
4340 prev_lineno = view->lines - 1;
4341 while (prev_lineno < view->lines && !view->line[prev_lineno].data)
4343 while (prev_lineno > 0 && !view->line[prev_lineno].data)
4346 /* If the above fails, always skip the "On branch" line. */
4347 if (prev_lineno < view->lines)
4348 view->lineno = prev_lineno;
4352 if (view->lineno < view->offset)
4353 view->offset = view->lineno;
4354 else if (view->offset + view->height <= view->lineno)
4355 view->offset = view->lineno - view->height + 1;
4361 status_draw(struct view *view, struct line *line, unsigned int lineno)
4363 struct status *status = line->data;
4364 enum line_type type;
4368 switch (line->type) {
4369 case LINE_STAT_STAGED:
4370 type = LINE_STAT_SECTION;
4371 text = "Changes to be committed:";
4374 case LINE_STAT_UNSTAGED:
4375 type = LINE_STAT_SECTION;
4376 text = "Changed but not updated:";
4379 case LINE_STAT_UNTRACKED:
4380 type = LINE_STAT_SECTION;
4381 text = "Untracked files:";
4384 case LINE_STAT_NONE:
4385 type = LINE_DEFAULT;
4386 text = " (no files)";
4389 case LINE_STAT_HEAD:
4390 type = LINE_STAT_HEAD;
4391 text = status_onbranch;
4398 static char buf[] = { '?', ' ', ' ', ' ', 0 };
4400 buf[0] = status->status;
4401 if (draw_text(view, line->type, buf, TRUE))
4403 type = LINE_DEFAULT;
4404 text = status->new.name;
4407 draw_text(view, type, text, TRUE);
4412 status_enter(struct view *view, struct line *line)
4414 struct status *status = line->data;
4415 char oldpath[SIZEOF_STR] = "";
4416 char newpath[SIZEOF_STR] = "";
4419 enum open_flags split;
4421 if (line->type == LINE_STAT_NONE ||
4422 (!status && line[1].type == LINE_STAT_NONE)) {
4423 report("No file to diff");
4428 if (sq_quote(oldpath, 0, status->old.name) >= sizeof(oldpath))
4430 /* Diffs for unmerged entries are empty when pasing the
4431 * new path, so leave it empty. */
4432 if (status->status != 'U' &&
4433 sq_quote(newpath, 0, status->new.name) >= sizeof(newpath))
4438 line->type != LINE_STAT_UNTRACKED &&
4439 !string_format_from(opt_cmd, &cmdsize, "cd %s;", opt_cdup))
4442 switch (line->type) {
4443 case LINE_STAT_STAGED:
4444 if (is_initial_commit()) {
4445 if (!string_format_from(opt_cmd, &cmdsize,
4446 STATUS_DIFF_NO_HEAD_SHOW_CMD,
4450 if (!string_format_from(opt_cmd, &cmdsize,
4451 STATUS_DIFF_INDEX_SHOW_CMD,
4457 info = "Staged changes to %s";
4459 info = "Staged changes";
4462 case LINE_STAT_UNSTAGED:
4463 if (!string_format_from(opt_cmd, &cmdsize,
4464 STATUS_DIFF_FILES_SHOW_CMD, oldpath, newpath))
4467 info = "Unstaged changes to %s";
4469 info = "Unstaged changes";
4472 case LINE_STAT_UNTRACKED:
4477 report("No file to show");
4481 if (!suffixcmp(status->new.name, -1, "/")) {
4482 report("Cannot display a directory");
4486 opt_pipe = fopen(status->new.name, "r");
4487 info = "Untracked file %s";
4490 case LINE_STAT_HEAD:
4494 die("line type %d not handled in switch", line->type);
4497 split = view_is_displayed(view) ? OPEN_SPLIT : 0;
4498 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH | split);
4499 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
4501 stage_status = *status;
4503 memset(&stage_status, 0, sizeof(stage_status));
4506 stage_line_type = line->type;
4508 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
4515 status_exists(struct status *status, enum line_type type)
4517 struct view *view = VIEW(REQ_VIEW_STATUS);
4520 for (line = view->line; line < view->line + view->lines; line++) {
4521 struct status *pos = line->data;
4523 if (line->type == type && pos &&
4524 !strcmp(status->new.name, pos->new.name))
4533 status_update_prepare(enum line_type type)
4535 char cmd[SIZEOF_STR];
4539 type != LINE_STAT_UNTRACKED &&
4540 !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
4544 case LINE_STAT_STAGED:
4545 string_add(cmd, cmdsize, "git update-index -z --index-info");
4548 case LINE_STAT_UNSTAGED:
4549 case LINE_STAT_UNTRACKED:
4550 string_add(cmd, cmdsize, "git update-index -z --add --remove --stdin");
4554 die("line type %d not handled in switch", type);
4557 return popen(cmd, "w");
4561 status_update_write(FILE *pipe, struct status *status, enum line_type type)
4563 char buf[SIZEOF_STR];
4568 case LINE_STAT_STAGED:
4569 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
4572 status->old.name, 0))
4576 case LINE_STAT_UNSTAGED:
4577 case LINE_STAT_UNTRACKED:
4578 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
4583 die("line type %d not handled in switch", type);
4586 while (!ferror(pipe) && written < bufsize) {
4587 written += fwrite(buf + written, 1, bufsize - written, pipe);
4590 return written == bufsize;
4594 status_update_file(struct status *status, enum line_type type)
4596 FILE *pipe = status_update_prepare(type);
4602 result = status_update_write(pipe, status, type);
4608 status_update_files(struct view *view, struct line *line)
4610 FILE *pipe = status_update_prepare(line->type);
4612 struct line *pos = view->line + view->lines;
4619 for (pos = line; pos < view->line + view->lines && pos->data; pos++)
4622 for (file = 0, done = 0; result && file < files; line++, file++) {
4623 int almost_done = file * 100 / files;
4625 if (almost_done > done) {
4627 string_format(view->ref, "updating file %u of %u (%d%% done)",
4629 update_view_title(view);
4631 result = status_update_write(pipe, line->data, line->type);
4639 status_update(struct view *view)
4641 struct line *line = &view->line[view->lineno];
4643 assert(view->lines);
4646 /* This should work even for the "On branch" line. */
4647 if (line < view->line + view->lines && !line[1].data) {
4648 report("Nothing to update");
4652 if (!status_update_files(view, line + 1)) {
4653 report("Failed to update file status");
4657 } else if (!status_update_file(line->data, line->type)) {
4658 report("Failed to update file status");
4666 status_revert(struct status *status, enum line_type type, bool has_none)
4668 if (!status || type != LINE_STAT_UNSTAGED) {
4669 if (type == LINE_STAT_STAGED) {
4670 report("Cannot revert changes to staged files");
4671 } else if (type == LINE_STAT_UNTRACKED) {
4672 report("Cannot revert changes to untracked files");
4673 } else if (has_none) {
4674 report("Nothing to revert");
4676 report("Cannot revert changes to multiple files");
4681 const char *checkout_argv[] = {
4682 "git", "checkout", "--", status->old.name, NULL
4685 if (!prompt_yesno("Are you sure you want to overwrite any changes?"))
4687 return run_io_fg(checkout_argv, opt_cdup);
4692 status_request(struct view *view, enum request request, struct line *line)
4694 struct status *status = line->data;
4697 case REQ_STATUS_UPDATE:
4698 if (!status_update(view))
4702 case REQ_STATUS_REVERT:
4703 if (!status_revert(status, line->type, status_has_none(view, line)))
4707 case REQ_STATUS_MERGE:
4708 if (!status || status->status != 'U') {
4709 report("Merging only possible for files with unmerged status ('U').");
4712 open_mergetool(status->new.name);
4718 if (status->status == 'D') {
4719 report("File has been deleted.");
4723 open_editor(status->status != '?', status->new.name);
4726 case REQ_VIEW_BLAME:
4728 string_copy(opt_file, status->new.name);
4734 /* After returning the status view has been split to
4735 * show the stage view. No further reloading is
4737 status_enter(view, line);
4741 /* Simply reload the view. */
4748 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
4754 status_select(struct view *view, struct line *line)
4756 struct status *status = line->data;
4757 char file[SIZEOF_STR] = "all files";
4761 if (status && !string_format(file, "'%s'", status->new.name))
4764 if (!status && line[1].type == LINE_STAT_NONE)
4767 switch (line->type) {
4768 case LINE_STAT_STAGED:
4769 text = "Press %s to unstage %s for commit";
4772 case LINE_STAT_UNSTAGED:
4773 text = "Press %s to stage %s for commit";
4776 case LINE_STAT_UNTRACKED:
4777 text = "Press %s to stage %s for addition";
4780 case LINE_STAT_HEAD:
4781 case LINE_STAT_NONE:
4782 text = "Nothing to update";
4786 die("line type %d not handled in switch", line->type);
4789 if (status && status->status == 'U') {
4790 text = "Press %s to resolve conflict in %s";
4791 key = get_key(REQ_STATUS_MERGE);
4794 key = get_key(REQ_STATUS_UPDATE);
4797 string_format(view->ref, text, key, file);
4801 status_grep(struct view *view, struct line *line)
4803 struct status *status = line->data;
4804 enum { S_STATUS, S_NAME, S_END } state;
4811 for (state = S_STATUS; state < S_END; state++) {
4815 case S_NAME: text = status->new.name; break;
4817 buf[0] = status->status;
4825 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4832 static struct view_ops status_ops = {
4845 stage_diff_line(FILE *pipe, struct line *line)
4847 const char *buf = line->data;
4848 size_t bufsize = strlen(buf);
4851 while (!ferror(pipe) && written < bufsize) {
4852 written += fwrite(buf + written, 1, bufsize - written, pipe);
4857 return written == bufsize;
4861 stage_diff_write(FILE *pipe, struct line *line, struct line *end)
4863 while (line < end) {
4864 if (!stage_diff_line(pipe, line++))
4866 if (line->type == LINE_DIFF_CHUNK ||
4867 line->type == LINE_DIFF_HEADER)
4874 static struct line *
4875 stage_diff_find(struct view *view, struct line *line, enum line_type type)
4877 for (; view->line < line; line--)
4878 if (line->type == type)
4885 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
4887 char cmd[SIZEOF_STR];
4889 struct line *diff_hdr;
4892 diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
4897 !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
4900 if (!string_format_from(cmd, &cmdsize,
4901 "git apply --whitespace=nowarn %s %s - && "
4902 "git update-index -q --unmerged --refresh 2>/dev/null",
4903 revert ? "" : "--cached",
4904 revert || stage_line_type == LINE_STAT_STAGED ? "-R" : ""))
4907 pipe = popen(cmd, "w");
4911 if (!stage_diff_write(pipe, diff_hdr, chunk) ||
4912 !stage_diff_write(pipe, chunk, view->line + view->lines))
4917 return chunk ? TRUE : FALSE;
4921 stage_update(struct view *view, struct line *line)
4923 struct line *chunk = NULL;
4925 if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
4926 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
4929 if (!stage_apply_chunk(view, chunk, FALSE)) {
4930 report("Failed to apply chunk");
4934 } else if (!stage_status.status) {
4935 view = VIEW(REQ_VIEW_STATUS);
4937 for (line = view->line; line < view->line + view->lines; line++)
4938 if (line->type == stage_line_type)
4941 if (!status_update_files(view, line + 1)) {
4942 report("Failed to update files");
4946 } else if (!status_update_file(&stage_status, stage_line_type)) {
4947 report("Failed to update file");
4955 stage_revert(struct view *view, struct line *line)
4957 struct line *chunk = NULL;
4959 if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
4960 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
4963 if (!prompt_yesno("Are you sure you want to revert changes?"))
4966 if (!stage_apply_chunk(view, chunk, TRUE)) {
4967 report("Failed to revert chunk");
4973 return status_revert(stage_status.status ? &stage_status : NULL,
4974 stage_line_type, FALSE);
4980 stage_next(struct view *view, struct line *line)
4984 if (!stage_chunks) {
4985 static size_t alloc = 0;
4988 for (line = view->line; line < view->line + view->lines; line++) {
4989 if (line->type != LINE_DIFF_CHUNK)
4992 tmp = realloc_items(stage_chunk, &alloc,
4993 stage_chunks, sizeof(*tmp));
4995 report("Allocation failure");
5000 stage_chunk[stage_chunks++] = line - view->line;
5004 for (i = 0; i < stage_chunks; i++) {
5005 if (stage_chunk[i] > view->lineno) {
5006 do_scroll_view(view, stage_chunk[i] - view->lineno);
5007 report("Chunk %d of %d", i + 1, stage_chunks);
5012 report("No next chunk found");
5016 stage_request(struct view *view, enum request request, struct line *line)
5019 case REQ_STATUS_UPDATE:
5020 if (!stage_update(view, line))
5024 case REQ_STATUS_REVERT:
5025 if (!stage_revert(view, line))
5029 case REQ_STAGE_NEXT:
5030 if (stage_line_type == LINE_STAT_UNTRACKED) {
5031 report("File is untracked; press %s to add",
5032 get_key(REQ_STATUS_UPDATE));
5035 stage_next(view, line);
5039 if (!stage_status.new.name[0])
5041 if (stage_status.status == 'D') {
5042 report("File has been deleted.");
5046 open_editor(stage_status.status != '?', stage_status.new.name);
5050 /* Reload everything ... */
5053 case REQ_VIEW_BLAME:
5054 if (stage_status.new.name[0]) {
5055 string_copy(opt_file, stage_status.new.name);
5061 return pager_request(view, request, line);
5067 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD | OPEN_NOMAXIMIZE);
5069 /* Check whether the staged entry still exists, and close the
5070 * stage view if it doesn't. */
5071 if (!status_exists(&stage_status, stage_line_type))
5072 return REQ_VIEW_CLOSE;
5074 if (stage_line_type == LINE_STAT_UNTRACKED) {
5075 if (!suffixcmp(stage_status.new.name, -1, "/")) {
5076 report("Cannot display a directory");
5080 opt_pipe = fopen(stage_status.new.name, "r");
5082 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
5087 static struct view_ops stage_ops = {
5104 char id[SIZEOF_REV]; /* SHA1 ID. */
5105 char title[128]; /* First line of the commit message. */
5106 char author[75]; /* Author of the commit. */
5107 struct tm time; /* Date from the author ident. */
5108 struct ref **refs; /* Repository references. */
5109 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
5110 size_t graph_size; /* The width of the graph array. */
5111 bool has_parents; /* Rewritten --parents seen. */
5114 /* Size of rev graph with no "padding" columns */
5115 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
5118 struct rev_graph *prev, *next, *parents;
5119 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
5121 struct commit *commit;
5123 unsigned int boundary:1;
5126 /* Parents of the commit being visualized. */
5127 static struct rev_graph graph_parents[4];
5129 /* The current stack of revisions on the graph. */
5130 static struct rev_graph graph_stacks[4] = {
5131 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
5132 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
5133 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
5134 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
5138 graph_parent_is_merge(struct rev_graph *graph)
5140 return graph->parents->size > 1;
5144 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
5146 struct commit *commit = graph->commit;
5148 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
5149 commit->graph[commit->graph_size++] = symbol;
5153 clear_rev_graph(struct rev_graph *graph)
5155 graph->boundary = 0;
5156 graph->size = graph->pos = 0;
5157 graph->commit = NULL;
5158 memset(graph->parents, 0, sizeof(*graph->parents));
5162 done_rev_graph(struct rev_graph *graph)
5164 if (graph_parent_is_merge(graph) &&
5165 graph->pos < graph->size - 1 &&
5166 graph->next->size == graph->size + graph->parents->size - 1) {
5167 size_t i = graph->pos + graph->parents->size - 1;
5169 graph->commit->graph_size = i * 2;
5170 while (i < graph->next->size - 1) {
5171 append_to_rev_graph(graph, ' ');
5172 append_to_rev_graph(graph, '\\');
5177 clear_rev_graph(graph);
5181 push_rev_graph(struct rev_graph *graph, const char *parent)
5185 /* "Collapse" duplicate parents lines.
5187 * FIXME: This needs to also update update the drawn graph but
5188 * for now it just serves as a method for pruning graph lines. */
5189 for (i = 0; i < graph->size; i++)
5190 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
5193 if (graph->size < SIZEOF_REVITEMS) {
5194 string_copy_rev(graph->rev[graph->size++], parent);
5199 get_rev_graph_symbol(struct rev_graph *graph)
5203 if (graph->boundary)
5204 symbol = REVGRAPH_BOUND;
5205 else if (graph->parents->size == 0)
5206 symbol = REVGRAPH_INIT;
5207 else if (graph_parent_is_merge(graph))
5208 symbol = REVGRAPH_MERGE;
5209 else if (graph->pos >= graph->size)
5210 symbol = REVGRAPH_BRANCH;
5212 symbol = REVGRAPH_COMMIT;
5218 draw_rev_graph(struct rev_graph *graph)
5221 chtype separator, line;
5223 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
5224 static struct rev_filler fillers[] = {
5230 chtype symbol = get_rev_graph_symbol(graph);
5231 struct rev_filler *filler;
5234 if (opt_line_graphics)
5235 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
5237 filler = &fillers[DEFAULT];
5239 for (i = 0; i < graph->pos; i++) {
5240 append_to_rev_graph(graph, filler->line);
5241 if (graph_parent_is_merge(graph->prev) &&
5242 graph->prev->pos == i)
5243 filler = &fillers[RSHARP];
5245 append_to_rev_graph(graph, filler->separator);
5248 /* Place the symbol for this revision. */
5249 append_to_rev_graph(graph, symbol);
5251 if (graph->prev->size > graph->size)
5252 filler = &fillers[RDIAG];
5254 filler = &fillers[DEFAULT];
5258 for (; i < graph->size; i++) {
5259 append_to_rev_graph(graph, filler->separator);
5260 append_to_rev_graph(graph, filler->line);
5261 if (graph_parent_is_merge(graph->prev) &&
5262 i < graph->prev->pos + graph->parents->size)
5263 filler = &fillers[RSHARP];
5264 if (graph->prev->size > graph->size)
5265 filler = &fillers[LDIAG];
5268 if (graph->prev->size > graph->size) {
5269 append_to_rev_graph(graph, filler->separator);
5270 if (filler->line != ' ')
5271 append_to_rev_graph(graph, filler->line);
5275 /* Prepare the next rev graph */
5277 prepare_rev_graph(struct rev_graph *graph)
5281 /* First, traverse all lines of revisions up to the active one. */
5282 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
5283 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
5286 push_rev_graph(graph->next, graph->rev[graph->pos]);
5289 /* Interleave the new revision parent(s). */
5290 for (i = 0; !graph->boundary && i < graph->parents->size; i++)
5291 push_rev_graph(graph->next, graph->parents->rev[i]);
5293 /* Lastly, put any remaining revisions. */
5294 for (i = graph->pos + 1; i < graph->size; i++)
5295 push_rev_graph(graph->next, graph->rev[i]);
5299 update_rev_graph(struct rev_graph *graph)
5301 /* If this is the finalizing update ... */
5303 prepare_rev_graph(graph);
5305 /* Graph visualization needs a one rev look-ahead,
5306 * so the first update doesn't visualize anything. */
5307 if (!graph->prev->commit)
5310 draw_rev_graph(graph->prev);
5311 done_rev_graph(graph->prev->prev);
5319 static const char *main_argv[SIZEOF_ARG] = {
5320 "git", "log", "--no-color", "--pretty=raw", "--parents",
5321 "--topo-order", "%(head)", NULL
5325 main_draw(struct view *view, struct line *line, unsigned int lineno)
5327 struct commit *commit = line->data;
5329 if (!*commit->author)
5332 if (opt_date && draw_date(view, &commit->time))
5336 draw_field(view, LINE_MAIN_AUTHOR, commit->author, opt_author_cols, TRUE))
5339 if (opt_rev_graph && commit->graph_size &&
5340 draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
5343 if (opt_show_refs && commit->refs) {
5347 enum line_type type;
5349 if (commit->refs[i]->head)
5350 type = LINE_MAIN_HEAD;
5351 else if (commit->refs[i]->ltag)
5352 type = LINE_MAIN_LOCAL_TAG;
5353 else if (commit->refs[i]->tag)
5354 type = LINE_MAIN_TAG;
5355 else if (commit->refs[i]->tracked)
5356 type = LINE_MAIN_TRACKED;
5357 else if (commit->refs[i]->remote)
5358 type = LINE_MAIN_REMOTE;
5360 type = LINE_MAIN_REF;
5362 if (draw_text(view, type, "[", TRUE) ||
5363 draw_text(view, type, commit->refs[i]->name, TRUE) ||
5364 draw_text(view, type, "]", TRUE))
5367 if (draw_text(view, LINE_DEFAULT, " ", TRUE))
5369 } while (commit->refs[i++]->next);
5372 draw_text(view, LINE_DEFAULT, commit->title, TRUE);
5376 /* Reads git log --pretty=raw output and parses it into the commit struct. */
5378 main_read(struct view *view, char *line)
5380 static struct rev_graph *graph = graph_stacks;
5381 enum line_type type;
5382 struct commit *commit;
5387 if (!view->lines && !view->parent)
5388 die("No revisions match the given arguments.");
5389 if (view->lines > 0) {
5390 commit = view->line[view->lines - 1].data;
5391 if (!*commit->author) {
5394 graph->commit = NULL;
5397 update_rev_graph(graph);
5399 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
5400 clear_rev_graph(&graph_stacks[i]);
5404 type = get_line_type(line);
5405 if (type == LINE_COMMIT) {
5406 commit = calloc(1, sizeof(struct commit));
5410 line += STRING_SIZE("commit ");
5412 graph->boundary = 1;
5416 string_copy_rev(commit->id, line);
5417 commit->refs = get_refs(commit->id);
5418 graph->commit = commit;
5419 add_line_data(view, commit, LINE_MAIN_COMMIT);
5421 while ((line = strchr(line, ' '))) {
5423 push_rev_graph(graph->parents, line);
5424 commit->has_parents = TRUE;
5431 commit = view->line[view->lines - 1].data;
5435 if (commit->has_parents)
5437 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
5442 /* Parse author lines where the name may be empty:
5443 * author <email@address.tld> 1138474660 +0100
5445 char *ident = line + STRING_SIZE("author ");
5446 char *nameend = strchr(ident, '<');
5447 char *emailend = strchr(ident, '>');
5449 if (!nameend || !emailend)
5452 update_rev_graph(graph);
5453 graph = graph->next;
5455 *nameend = *emailend = 0;
5456 ident = chomp_string(ident);
5458 ident = chomp_string(nameend + 1);
5463 string_ncopy(commit->author, ident, strlen(ident));
5465 /* Parse epoch and timezone */
5466 if (emailend[1] == ' ') {
5467 char *secs = emailend + 2;
5468 char *zone = strchr(secs, ' ');
5469 time_t time = (time_t) atol(secs);
5471 if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
5475 tz = ('0' - zone[1]) * 60 * 60 * 10;
5476 tz += ('0' - zone[2]) * 60 * 60;
5477 tz += ('0' - zone[3]) * 60;
5478 tz += ('0' - zone[4]) * 60;
5486 gmtime_r(&time, &commit->time);
5491 /* Fill in the commit title if it has not already been set. */
5492 if (commit->title[0])
5495 /* Require titles to start with a non-space character at the
5496 * offset used by git log. */
5497 if (strncmp(line, " ", 4))
5500 /* Well, if the title starts with a whitespace character,
5501 * try to be forgiving. Otherwise we end up with no title. */
5502 while (isspace(*line))
5506 /* FIXME: More graceful handling of titles; append "..." to
5507 * shortened titles, etc. */
5509 string_ncopy(commit->title, line, strlen(line));
5516 main_request(struct view *view, enum request request, struct line *line)
5518 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
5522 open_view(view, REQ_VIEW_DIFF, flags);
5526 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
5536 grep_refs(struct ref **refs, regex_t *regex)
5544 if (regexec(regex, refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
5546 } while (refs[i++]->next);
5552 main_grep(struct view *view, struct line *line)
5554 struct commit *commit = line->data;
5555 enum { S_TITLE, S_AUTHOR, S_DATE, S_REFS, S_END } state;
5556 char buf[DATE_COLS + 1];
5559 for (state = S_TITLE; state < S_END; state++) {
5563 case S_TITLE: text = commit->title; break;
5567 text = commit->author;
5572 if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
5579 if (grep_refs(commit->refs, view->regex) == TRUE)
5586 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
5594 main_select(struct view *view, struct line *line)
5596 struct commit *commit = line->data;
5598 string_copy_rev(view->ref, commit->id);
5599 string_copy_rev(ref_commit, view->ref);
5602 static struct view_ops main_ops = {
5615 * Unicode / UTF-8 handling
5617 * NOTE: Much of the following code for dealing with unicode is derived from
5618 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
5619 * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
5622 /* I've (over)annotated a lot of code snippets because I am not entirely
5623 * confident that the approach taken by this small UTF-8 interface is correct.
5627 unicode_width(unsigned long c)
5630 (c <= 0x115f /* Hangul Jamo */
5633 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
5635 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
5636 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
5637 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
5638 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
5639 || (c >= 0xffe0 && c <= 0xffe6)
5640 || (c >= 0x20000 && c <= 0x2fffd)
5641 || (c >= 0x30000 && c <= 0x3fffd)))
5645 return opt_tab_size;
5650 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
5651 * Illegal bytes are set one. */
5652 static const unsigned char utf8_bytes[256] = {
5653 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,
5654 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,
5655 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,
5656 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,
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 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,
5660 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,
5663 /* Decode UTF-8 multi-byte representation into a unicode character. */
5664 static inline unsigned long
5665 utf8_to_unicode(const char *string, size_t length)
5667 unsigned long unicode;
5671 unicode = string[0];
5674 unicode = (string[0] & 0x1f) << 6;
5675 unicode += (string[1] & 0x3f);
5678 unicode = (string[0] & 0x0f) << 12;
5679 unicode += ((string[1] & 0x3f) << 6);
5680 unicode += (string[2] & 0x3f);
5683 unicode = (string[0] & 0x0f) << 18;
5684 unicode += ((string[1] & 0x3f) << 12);
5685 unicode += ((string[2] & 0x3f) << 6);
5686 unicode += (string[3] & 0x3f);
5689 unicode = (string[0] & 0x0f) << 24;
5690 unicode += ((string[1] & 0x3f) << 18);
5691 unicode += ((string[2] & 0x3f) << 12);
5692 unicode += ((string[3] & 0x3f) << 6);
5693 unicode += (string[4] & 0x3f);
5696 unicode = (string[0] & 0x01) << 30;
5697 unicode += ((string[1] & 0x3f) << 24);
5698 unicode += ((string[2] & 0x3f) << 18);
5699 unicode += ((string[3] & 0x3f) << 12);
5700 unicode += ((string[4] & 0x3f) << 6);
5701 unicode += (string[5] & 0x3f);
5704 die("Invalid unicode length");
5707 /* Invalid characters could return the special 0xfffd value but NUL
5708 * should be just as good. */
5709 return unicode > 0xffff ? 0 : unicode;
5712 /* Calculates how much of string can be shown within the given maximum width
5713 * and sets trimmed parameter to non-zero value if all of string could not be
5714 * shown. If the reserve flag is TRUE, it will reserve at least one
5715 * trailing character, which can be useful when drawing a delimiter.
5717 * Returns the number of bytes to output from string to satisfy max_width. */
5719 utf8_length(const char *string, int *width, size_t max_width, int *trimmed, bool reserve)
5721 const char *start = string;
5722 const char *end = strchr(string, '\0');
5723 unsigned char last_bytes = 0;
5724 size_t last_ucwidth = 0;
5729 while (string < end) {
5730 int c = *(unsigned char *) string;
5731 unsigned char bytes = utf8_bytes[c];
5733 unsigned long unicode;
5735 if (string + bytes > end)
5738 /* Change representation to figure out whether
5739 * it is a single- or double-width character. */
5741 unicode = utf8_to_unicode(string, bytes);
5742 /* FIXME: Graceful handling of invalid unicode character. */
5746 ucwidth = unicode_width(unicode);
5748 if (*width > max_width) {
5751 if (reserve && *width == max_width) {
5752 string -= last_bytes;
5753 *width -= last_ucwidth;
5760 last_ucwidth = ucwidth;
5763 return string - start;
5771 /* Whether or not the curses interface has been initialized. */
5772 static bool cursed = FALSE;
5774 /* The status window is used for polling keystrokes. */
5775 static WINDOW *status_win;
5777 static bool status_empty = TRUE;
5779 /* Update status and title window. */
5781 report(const char *msg, ...)
5783 struct view *view = display[current_view];
5789 char buf[SIZEOF_STR];
5792 va_start(args, msg);
5793 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
5794 buf[sizeof(buf) - 1] = 0;
5795 buf[sizeof(buf) - 2] = '.';
5796 buf[sizeof(buf) - 3] = '.';
5797 buf[sizeof(buf) - 4] = '.';
5803 if (!status_empty || *msg) {
5806 va_start(args, msg);
5808 wmove(status_win, 0, 0);
5810 vwprintw(status_win, msg, args);
5811 status_empty = FALSE;
5813 status_empty = TRUE;
5815 wclrtoeol(status_win);
5816 wrefresh(status_win);
5821 update_view_title(view);
5822 update_display_cursor(view);
5825 /* Controls when nodelay should be in effect when polling user input. */
5827 set_nonblocking_input(bool loading)
5829 static unsigned int loading_views;
5831 if ((loading == FALSE && loading_views-- == 1) ||
5832 (loading == TRUE && loading_views++ == 0))
5833 nodelay(status_win, loading);
5841 /* Initialize the curses library */
5842 if (isatty(STDIN_FILENO)) {
5843 cursed = !!initscr();
5846 /* Leave stdin and stdout alone when acting as a pager. */
5847 opt_tty = fopen("/dev/tty", "r+");
5849 die("Failed to open /dev/tty");
5850 cursed = !!newterm(NULL, opt_tty, opt_tty);
5854 die("Failed to initialize curses");
5856 nonl(); /* Tell curses not to do NL->CR/NL on output */
5857 cbreak(); /* Take input chars one at a time, no wait for \n */
5858 noecho(); /* Don't echo input */
5859 leaveok(stdscr, TRUE);
5864 getmaxyx(stdscr, y, x);
5865 status_win = newwin(1, 0, y - 1, 0);
5867 die("Failed to create status window");
5869 /* Enable keyboard mapping */
5870 keypad(status_win, TRUE);
5871 wbkgdset(status_win, get_line_attr(LINE_STATUS));
5873 TABSIZE = opt_tab_size;
5874 if (opt_line_graphics) {
5875 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
5880 prompt_yesno(const char *prompt)
5882 enum { WAIT, STOP, CANCEL } status = WAIT;
5883 bool answer = FALSE;
5885 while (status == WAIT) {
5891 foreach_view (view, i)
5896 mvwprintw(status_win, 0, 0, "%s [Yy]/[Nn]", prompt);
5897 wclrtoeol(status_win);
5899 /* Refresh, accept single keystroke of input */
5900 key = wgetch(status_win);
5924 /* Clear the status window */
5925 status_empty = FALSE;
5932 read_prompt(const char *prompt)
5934 enum { READING, STOP, CANCEL } status = READING;
5935 static char buf[SIZEOF_STR];
5938 while (status == READING) {
5944 foreach_view (view, i)
5949 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
5950 wclrtoeol(status_win);
5952 /* Refresh, accept single keystroke of input */
5953 key = wgetch(status_win);
5958 status = pos ? STOP : CANCEL;
5976 if (pos >= sizeof(buf)) {
5977 report("Input string too long");
5982 buf[pos++] = (char) key;
5986 /* Clear the status window */
5987 status_empty = FALSE;
5990 if (status == CANCEL)
5999 * Repository references
6002 static struct ref *refs = NULL;
6003 static size_t refs_alloc = 0;
6004 static size_t refs_size = 0;
6006 /* Id <-> ref store */
6007 static struct ref ***id_refs = NULL;
6008 static size_t id_refs_alloc = 0;
6009 static size_t id_refs_size = 0;
6012 compare_refs(const void *ref1_, const void *ref2_)
6014 const struct ref *ref1 = *(const struct ref **)ref1_;
6015 const struct ref *ref2 = *(const struct ref **)ref2_;
6017 if (ref1->tag != ref2->tag)
6018 return ref2->tag - ref1->tag;
6019 if (ref1->ltag != ref2->ltag)
6020 return ref2->ltag - ref2->ltag;
6021 if (ref1->head != ref2->head)
6022 return ref2->head - ref1->head;
6023 if (ref1->tracked != ref2->tracked)
6024 return ref2->tracked - ref1->tracked;
6025 if (ref1->remote != ref2->remote)
6026 return ref2->remote - ref1->remote;
6027 return strcmp(ref1->name, ref2->name);
6030 static struct ref **
6031 get_refs(const char *id)
6033 struct ref ***tmp_id_refs;
6034 struct ref **ref_list = NULL;
6035 size_t ref_list_alloc = 0;
6036 size_t ref_list_size = 0;
6039 for (i = 0; i < id_refs_size; i++)
6040 if (!strcmp(id, id_refs[i][0]->id))
6043 tmp_id_refs = realloc_items(id_refs, &id_refs_alloc, id_refs_size + 1,
6048 id_refs = tmp_id_refs;
6050 for (i = 0; i < refs_size; i++) {
6053 if (strcmp(id, refs[i].id))
6056 tmp = realloc_items(ref_list, &ref_list_alloc,
6057 ref_list_size + 1, sizeof(*ref_list));
6065 ref_list[ref_list_size] = &refs[i];
6066 /* XXX: The properties of the commit chains ensures that we can
6067 * safely modify the shared ref. The repo references will
6068 * always be similar for the same id. */
6069 ref_list[ref_list_size]->next = 1;
6075 qsort(ref_list, ref_list_size, sizeof(*ref_list), compare_refs);
6076 ref_list[ref_list_size - 1]->next = 0;
6077 id_refs[id_refs_size++] = ref_list;
6084 read_ref(char *id, size_t idlen, char *name, size_t namelen)
6089 bool remote = FALSE;
6090 bool tracked = FALSE;
6091 bool check_replace = FALSE;
6094 if (!prefixcmp(name, "refs/tags/")) {
6095 if (!suffixcmp(name, namelen, "^{}")) {
6098 if (refs_size > 0 && refs[refs_size - 1].ltag == TRUE)
6099 check_replace = TRUE;
6105 namelen -= STRING_SIZE("refs/tags/");
6106 name += STRING_SIZE("refs/tags/");
6108 } else if (!prefixcmp(name, "refs/remotes/")) {
6110 namelen -= STRING_SIZE("refs/remotes/");
6111 name += STRING_SIZE("refs/remotes/");
6112 tracked = !strcmp(opt_remote, name);
6114 } else if (!prefixcmp(name, "refs/heads/")) {
6115 namelen -= STRING_SIZE("refs/heads/");
6116 name += STRING_SIZE("refs/heads/");
6117 head = !strncmp(opt_head, name, namelen);
6119 } else if (!strcmp(name, "HEAD")) {
6120 string_ncopy(opt_head_rev, id, idlen);
6124 if (check_replace && !strcmp(name, refs[refs_size - 1].name)) {
6125 /* it's an annotated tag, replace the previous sha1 with the
6126 * resolved commit id; relies on the fact git-ls-remote lists
6127 * the commit id of an annotated tag right before the commit id
6129 refs[refs_size - 1].ltag = ltag;
6130 string_copy_rev(refs[refs_size - 1].id, id);
6134 refs = realloc_items(refs, &refs_alloc, refs_size + 1, sizeof(*refs));
6138 ref = &refs[refs_size++];
6139 ref->name = malloc(namelen + 1);
6143 strncpy(ref->name, name, namelen);
6144 ref->name[namelen] = 0;
6148 ref->remote = remote;
6149 ref->tracked = tracked;
6150 string_copy_rev(ref->id, id);
6158 const char *cmd_env = getenv("TIG_LS_REMOTE");
6159 const char *cmd = cmd_env && *cmd_env ? cmd_env : TIG_LS_REMOTE;
6164 while (refs_size > 0)
6165 free(refs[--refs_size].name);
6166 while (id_refs_size > 0)
6167 free(id_refs[--id_refs_size]);
6169 return read_properties(popen(cmd, "r"), "\t", read_ref);
6173 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
6175 if (!strcmp(name, "i18n.commitencoding"))
6176 string_ncopy(opt_encoding, value, valuelen);
6178 if (!strcmp(name, "core.editor"))
6179 string_ncopy(opt_editor, value, valuelen);
6181 /* branch.<head>.remote */
6183 !strncmp(name, "branch.", 7) &&
6184 !strncmp(name + 7, opt_head, strlen(opt_head)) &&
6185 !strcmp(name + 7 + strlen(opt_head), ".remote"))
6186 string_ncopy(opt_remote, value, valuelen);
6188 if (*opt_head && *opt_remote &&
6189 !strncmp(name, "branch.", 7) &&
6190 !strncmp(name + 7, opt_head, strlen(opt_head)) &&
6191 !strcmp(name + 7 + strlen(opt_head), ".merge")) {
6192 size_t from = strlen(opt_remote);
6194 if (!prefixcmp(value, "refs/heads/")) {
6195 value += STRING_SIZE("refs/heads/");
6196 valuelen -= STRING_SIZE("refs/heads/");
6199 if (!string_format_from(opt_remote, &from, "/%s", value))
6207 load_git_config(void)
6209 return read_properties(popen("git " GIT_CONFIG " --list", "r"),
6210 "=", read_repo_config_option);
6214 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
6216 if (!opt_git_dir[0]) {
6217 string_ncopy(opt_git_dir, name, namelen);
6219 } else if (opt_is_inside_work_tree == -1) {
6220 /* This can be 3 different values depending on the
6221 * version of git being used. If git-rev-parse does not
6222 * understand --is-inside-work-tree it will simply echo
6223 * the option else either "true" or "false" is printed.
6224 * Default to true for the unknown case. */
6225 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
6227 } else if (opt_cdup[0] == ' ') {
6228 string_ncopy(opt_cdup, name, namelen);
6230 if (!prefixcmp(name, "refs/heads/")) {
6231 namelen -= STRING_SIZE("refs/heads/");
6232 name += STRING_SIZE("refs/heads/");
6233 string_ncopy(opt_head, name, namelen);
6241 load_repo_info(void)
6244 FILE *pipe = popen("(git rev-parse --git-dir --is-inside-work-tree "
6245 " --show-cdup; git symbolic-ref HEAD) 2>/dev/null", "r");
6247 /* XXX: The line outputted by "--show-cdup" can be empty so
6248 * initialize it to something invalid to make it possible to
6249 * detect whether it has been set or not. */
6252 result = read_properties(pipe, "=", read_repo_info);
6253 if (opt_cdup[0] == ' ')
6260 read_properties(FILE *pipe, const char *separators,
6261 int (*read_property)(char *, size_t, char *, size_t))
6263 char buffer[BUFSIZ];
6270 while (state == OK && (name = fgets(buffer, sizeof(buffer), pipe))) {
6275 name = chomp_string(name);
6276 namelen = strcspn(name, separators);
6278 if (name[namelen]) {
6280 value = chomp_string(name + namelen + 1);
6281 valuelen = strlen(value);
6288 state = read_property(name, namelen, value, valuelen);
6291 if (state != ERR && ferror(pipe))
6304 static void __NORETURN
6307 /* XXX: Restore tty modes and let the OS cleanup the rest! */
6313 static void __NORETURN
6314 die(const char *err, ...)
6320 va_start(args, err);
6321 fputs("tig: ", stderr);
6322 vfprintf(stderr, err, args);
6323 fputs("\n", stderr);
6330 warn(const char *msg, ...)
6334 va_start(args, msg);
6335 fputs("tig warning: ", stderr);
6336 vfprintf(stderr, msg, args);
6337 fputs("\n", stderr);
6342 main(int argc, const char *argv[])
6345 enum request request;
6348 signal(SIGINT, quit);
6350 if (setlocale(LC_ALL, "")) {
6351 char *codeset = nl_langinfo(CODESET);
6353 string_ncopy(opt_codeset, codeset, strlen(codeset));
6356 if (load_repo_info() == ERR)
6357 die("Failed to load repo info.");
6359 if (load_options() == ERR)
6360 die("Failed to load user config.");
6362 if (load_git_config() == ERR)
6363 die("Failed to load repo config.");
6365 request = parse_options(argc, argv);
6366 if (request == REQ_NONE)
6369 /* Require a git repository unless when running in pager mode. */
6370 if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
6371 die("Not a git repository");
6373 if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
6376 if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
6377 opt_iconv = iconv_open(opt_codeset, opt_encoding);
6378 if (opt_iconv == ICONV_NONE)
6379 die("Failed to initialize character set conversion");
6382 if (load_refs() == ERR)
6383 die("Failed to load refs.");
6385 foreach_view (view, i)
6386 argv_from_env(view->ops->argv, view->cmd_env);
6390 while (view_driver(display[current_view], request)) {
6394 foreach_view (view, i)
6396 view = display[current_view];
6398 /* Refresh, accept single keystroke of input */
6399 key = wgetch(status_win);
6401 /* wgetch() with nodelay() enabled returns ERR when there's no
6408 request = get_keybinding(view->keymap, key);
6410 /* Some low-level request handling. This keeps access to
6411 * status_win restricted. */
6415 char *cmd = read_prompt(":");
6418 struct view *next = VIEW(REQ_VIEW_PAGER);
6419 const char *argv[SIZEOF_ARG] = { "git" };
6422 /* When running random commands, initially show the
6423 * command in the title. However, it maybe later be
6424 * overwritten if a commit line is selected. */
6425 string_ncopy(next->ref, cmd, strlen(cmd));
6427 if (!argv_from_string(argv, &argc, cmd)) {
6428 report("Too many arguments");
6429 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
6430 report("Failed to format command");
6432 open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
6440 case REQ_SEARCH_BACK:
6442 const char *prompt = request == REQ_SEARCH ? "/" : "?";
6443 char *search = read_prompt(prompt);
6446 string_ncopy(opt_search, search, strlen(search));
6451 case REQ_SCREEN_RESIZE:
6455 getmaxyx(stdscr, height, width);
6457 /* Resize the status view and let the view driver take
6458 * care of resizing the displayed views. */
6459 wresize(status_win, 1, width);
6460 mvwin(status_win, height - 1, 0);
6461 wrefresh(status_win);