1 /* Copyright (c) 2006-2008 Jonas Fonseca <fonseca@diku.dk>
3 * This program is free software; you can redistribute it and/or
4 * modify it under the terms of the GNU General Public License as
5 * published by the Free Software Foundation; either version 2 of
6 * the License, or (at your option) any later version.
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
19 #define TIG_VERSION "unknown-version"
34 #include <sys/types.h>
45 /* ncurses(3): Must be defined to have extended wide-character functions. */
46 #define _XOPEN_SOURCE_EXTENDED
48 #ifdef HAVE_NCURSESW_NCURSES_H
49 #include <ncursesw/ncurses.h>
51 #ifdef HAVE_NCURSES_NCURSES_H
52 #include <ncurses/ncurses.h>
59 #define __NORETURN __attribute__((__noreturn__))
64 static void __NORETURN die(const char *err, ...);
65 static void warn(const char *msg, ...);
66 static void report(const char *msg, ...);
67 static int read_properties(FILE *pipe, const char *separators, int (*read)(char *, size_t, char *, size_t));
68 static void set_nonblocking_input(bool loading);
69 static size_t utf8_length(const char *string, int *width, size_t max_width, int *trimmed, bool reserve);
70 static bool prompt_yesno(const char *prompt);
71 static int load_refs(void);
73 #define ABS(x) ((x) >= 0 ? (x) : -(x))
74 #define MIN(x, y) ((x) < (y) ? (x) : (y))
76 #define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
77 #define STRING_SIZE(x) (sizeof(x) - 1)
79 #define SIZEOF_STR 1024 /* Default string size. */
80 #define SIZEOF_REF 256 /* Size of symbolic or SHA1 ID. */
81 #define SIZEOF_REV 41 /* Holds a SHA-1 and an ending NUL. */
82 #define SIZEOF_ARG 32 /* Default argument array size. */
86 #define REVGRAPH_INIT 'I'
87 #define REVGRAPH_MERGE 'M'
88 #define REVGRAPH_BRANCH '+'
89 #define REVGRAPH_COMMIT '*'
90 #define REVGRAPH_BOUND '^'
92 #define SIZEOF_REVGRAPH 19 /* Size of revision ancestry graphics. */
94 /* This color name can be used to refer to the default term colors. */
95 #define COLOR_DEFAULT (-1)
97 #define ICONV_NONE ((iconv_t) -1)
99 #define ICONV_CONST /* nothing */
102 /* The format and size of the date column in the main view. */
103 #define DATE_FORMAT "%Y-%m-%d %H:%M"
104 #define DATE_COLS STRING_SIZE("2006-04-29 14:21 ")
106 #define AUTHOR_COLS 20
109 /* The default interval between line numbers. */
110 #define NUMBER_INTERVAL 5
114 #define SCALE_SPLIT_VIEW(height) ((height) * 2 / 3)
116 #define NULL_ID "0000000000000000000000000000000000000000"
119 #define GIT_CONFIG "config"
122 #define TIG_LS_REMOTE \
123 "git ls-remote . 2>/dev/null"
125 #define TIG_DIFF_CMD \
126 "git show --pretty=fuller --no-color --root --patch-with-stat --find-copies-harder -C %s 2>/dev/null"
128 #define TIG_LOG_CMD \
129 "git log --no-color --cc --stat -n100 %s 2>/dev/null"
131 #define TIG_MAIN_BASE \
132 "git log --no-color --pretty=raw --parents --topo-order"
134 #define TIG_MAIN_CMD \
135 TIG_MAIN_BASE " %s 2>/dev/null"
137 #define TIG_TREE_CMD \
140 #define TIG_BLOB_CMD \
141 "git cat-file blob %s"
143 /* XXX: Needs to be defined to the empty string. */
144 #define TIG_HELP_CMD ""
145 #define TIG_PAGER_CMD ""
146 #define TIG_STATUS_CMD ""
147 #define TIG_STAGE_CMD ""
148 #define TIG_BLAME_CMD ""
150 /* Some ascii-shorthands fitted into the ncurses namespace. */
152 #define KEY_RETURN '\r'
157 char *name; /* Ref name; tag or head names are shortened. */
158 char id[SIZEOF_REV]; /* Commit SHA1 ID */
159 unsigned int head:1; /* Is it the current HEAD? */
160 unsigned int tag:1; /* Is it a tag? */
161 unsigned int ltag:1; /* If so, is the tag local? */
162 unsigned int remote:1; /* Is it a remote ref? */
163 unsigned int tracked:1; /* Is it the remote for the current HEAD? */
164 unsigned int next:1; /* For ref lists: are there more refs? */
167 static struct ref **get_refs(const char *id);
170 FORMAT_ALL, /* Perform replacement in all arguments. */
171 FORMAT_DASH, /* Perform replacement up until "--". */
172 FORMAT_NONE /* No replacement should be performed. */
175 static bool format_command(char dst[], const char *src[], enum format_flags flags);
176 static bool format_argv(const char *dst[], const char *src[], enum format_flags flags);
185 set_from_int_map(struct int_map *map, size_t map_size,
186 int *value, const char *name, int namelen)
191 for (i = 0; i < map_size; i++)
192 if (namelen == map[i].namelen &&
193 !strncasecmp(name, map[i].name, namelen)) {
194 *value = map[i].value;
207 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
209 if (srclen > dstlen - 1)
212 strncpy(dst, src, srclen);
216 /* Shorthands for safely copying into a fixed buffer. */
218 #define string_copy(dst, src) \
219 string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
221 #define string_ncopy(dst, src, srclen) \
222 string_ncopy_do(dst, sizeof(dst), src, srclen)
224 #define string_copy_rev(dst, src) \
225 string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
227 #define string_add(dst, from, src) \
228 string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
231 chomp_string(char *name)
235 while (isspace(*name))
238 namelen = strlen(name) - 1;
239 while (namelen > 0 && isspace(name[namelen]))
246 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
249 size_t pos = bufpos ? *bufpos : 0;
252 pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
258 return pos >= bufsize ? FALSE : TRUE;
261 #define string_format(buf, fmt, args...) \
262 string_nformat(buf, sizeof(buf), NULL, fmt, args)
264 #define string_format_from(buf, from, fmt, args...) \
265 string_nformat(buf, sizeof(buf), from, fmt, args)
268 string_enum_compare(const char *str1, const char *str2, int len)
272 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
274 /* Diff-Header == DIFF_HEADER */
275 for (i = 0; i < len; i++) {
276 if (toupper(str1[i]) == toupper(str2[i]))
279 if (string_enum_sep(str1[i]) &&
280 string_enum_sep(str2[i]))
283 return str1[i] - str2[i];
289 #define prefixcmp(str1, str2) \
290 strncmp(str1, str2, STRING_SIZE(str2))
293 suffixcmp(const char *str, int slen, const char *suffix)
295 size_t len = slen >= 0 ? slen : strlen(str);
296 size_t suffixlen = strlen(suffix);
298 return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
303 * NOTE: The following is a slightly modified copy of the git project's shell
304 * quoting routines found in the quote.c file.
306 * Help to copy the thing properly quoted for the shell safety. any single
307 * quote is replaced with '\'', any exclamation point is replaced with '\!',
308 * and the whole thing is enclosed in a
311 * original sq_quote result
312 * name ==> name ==> 'name'
313 * a b ==> a b ==> 'a b'
314 * a'b ==> a'\''b ==> 'a'\''b'
315 * a!b ==> a'\!'b ==> 'a'\!'b'
319 sq_quote(char buf[SIZEOF_STR], size_t bufsize, const char *src)
323 #define BUFPUT(x) do { if (bufsize < SIZEOF_STR) buf[bufsize++] = (x); } while (0)
326 while ((c = *src++)) {
327 if (c == '\'' || c == '!') {
338 if (bufsize < SIZEOF_STR)
345 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
349 while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
350 bool advance = cmd[valuelen] != 0;
353 argv[(*argc)++] = chomp_string(cmd);
354 cmd += valuelen + advance;
357 if (*argc < SIZEOF_ARG)
359 return *argc < SIZEOF_ARG;
364 * Executing external commands.
368 IO_FD, /* File descriptor based IO. */
369 IO_FG, /* Execute command with same std{in,out,err}. */
370 IO_RD, /* Read only fork+exec IO. */
371 IO_WR, /* Write only fork+exec IO. */
375 enum io_type type; /* The requested type of pipe. */
376 const char *dir; /* Directory from which to execute. */
377 FILE *pipe; /* Pipe for reading or writing. */
378 int error; /* Error status. */
379 char sh[SIZEOF_STR]; /* Shell command buffer. */
380 char *buf; /* Read/write buffer. */
381 size_t bufalloc; /* Allocated buffer size. */
385 reset_io(struct io *io)
394 init_io(struct io *io, const char *dir, enum io_type type)
402 init_io_fd(struct io *io, FILE *pipe)
404 init_io(io, NULL, IO_FD);
406 return io->pipe != NULL;
410 done_io(struct io *io)
413 if (io->type == IO_FD)
415 else if (io->type == IO_RD || io->type == IO_WR)
422 start_io(struct io *io)
424 char buf[SIZEOF_STR * 2];
427 if (io->dir && *io->dir &&
428 !string_format_from(buf, &bufpos, "cd %s;", io->dir))
431 if (!string_format_from(buf, &bufpos, "%s", io->sh))
434 if (io->type == IO_FG)
435 return system(buf) == 0;
437 io->pipe = popen(io->sh, io->type == IO_RD ? "r" : "w");
438 return io->pipe != NULL;
442 run_io(struct io *io, enum io_type type, const char *cmd)
444 init_io(io, NULL, type);
445 string_ncopy(io->sh, cmd, strlen(cmd));
450 run_io_do(struct io *io)
452 return start_io(io) && done_io(io);
456 run_io_fg(const char **argv, const char *dir)
460 init_io(&io, dir, IO_FG);
461 if (!format_command(io.sh, argv, FORMAT_NONE))
463 return run_io_do(&io);
467 run_io_format(struct io *io, const char *cmd, ...)
472 init_io(io, NULL, IO_RD);
474 if (vsnprintf(io->sh, sizeof(io->sh), cmd, args) >= sizeof(io->sh))
478 return io->sh[0] ? start_io(io) : FALSE;
482 io_eof(struct io *io)
484 return feof(io->pipe);
488 io_error(struct io *io)
494 io_strerror(struct io *io)
496 return strerror(io->error);
500 io_gets(struct io *io)
503 io->buf = malloc(BUFSIZ);
506 io->bufalloc = BUFSIZ;
509 if (!fgets(io->buf, io->bufalloc, io->pipe)) {
510 if (ferror(io->pipe))
524 /* XXX: Keep the view request first and in sync with views[]. */ \
525 REQ_GROUP("View switching") \
526 REQ_(VIEW_MAIN, "Show main view"), \
527 REQ_(VIEW_DIFF, "Show diff view"), \
528 REQ_(VIEW_LOG, "Show log view"), \
529 REQ_(VIEW_TREE, "Show tree view"), \
530 REQ_(VIEW_BLOB, "Show blob view"), \
531 REQ_(VIEW_BLAME, "Show blame view"), \
532 REQ_(VIEW_HELP, "Show help page"), \
533 REQ_(VIEW_PAGER, "Show pager view"), \
534 REQ_(VIEW_STATUS, "Show status view"), \
535 REQ_(VIEW_STAGE, "Show stage view"), \
537 REQ_GROUP("View manipulation") \
538 REQ_(ENTER, "Enter current line and scroll"), \
539 REQ_(NEXT, "Move to next"), \
540 REQ_(PREVIOUS, "Move to previous"), \
541 REQ_(VIEW_NEXT, "Move focus to next view"), \
542 REQ_(REFRESH, "Reload and refresh"), \
543 REQ_(MAXIMIZE, "Maximize the current view"), \
544 REQ_(VIEW_CLOSE, "Close the current view"), \
545 REQ_(QUIT, "Close all views and quit"), \
547 REQ_GROUP("View specific requests") \
548 REQ_(STATUS_UPDATE, "Update file status"), \
549 REQ_(STATUS_REVERT, "Revert file changes"), \
550 REQ_(STATUS_MERGE, "Merge file using external tool"), \
551 REQ_(STAGE_NEXT, "Find next chunk to stage"), \
552 REQ_(TREE_PARENT, "Switch to parent directory in tree view"), \
554 REQ_GROUP("Cursor navigation") \
555 REQ_(MOVE_UP, "Move cursor one line up"), \
556 REQ_(MOVE_DOWN, "Move cursor one line down"), \
557 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
558 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
559 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
560 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
562 REQ_GROUP("Scrolling") \
563 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
564 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
565 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
566 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
568 REQ_GROUP("Searching") \
569 REQ_(SEARCH, "Search the view"), \
570 REQ_(SEARCH_BACK, "Search backwards in the view"), \
571 REQ_(FIND_NEXT, "Find next search match"), \
572 REQ_(FIND_PREV, "Find previous search match"), \
574 REQ_GROUP("Option manipulation") \
575 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
576 REQ_(TOGGLE_DATE, "Toggle date display"), \
577 REQ_(TOGGLE_AUTHOR, "Toggle author display"), \
578 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
579 REQ_(TOGGLE_REFS, "Toggle reference display (tags/branches)"), \
582 REQ_(PROMPT, "Bring up the prompt"), \
583 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
584 REQ_(SCREEN_RESIZE, "Resize the screen"), \
585 REQ_(SHOW_VERSION, "Show version information"), \
586 REQ_(STOP_LOADING, "Stop all loading views"), \
587 REQ_(EDIT, "Open in editor"), \
588 REQ_(NONE, "Do nothing")
591 /* User action requests. */
593 #define REQ_GROUP(help)
594 #define REQ_(req, help) REQ_##req
596 /* Offset all requests to avoid conflicts with ncurses getch values. */
597 REQ_OFFSET = KEY_MAX + 1,
604 struct request_info {
605 enum request request;
611 static struct request_info req_info[] = {
612 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
613 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
620 get_request(const char *name)
622 int namelen = strlen(name);
625 for (i = 0; i < ARRAY_SIZE(req_info); i++)
626 if (req_info[i].namelen == namelen &&
627 !string_enum_compare(req_info[i].name, name, namelen))
628 return req_info[i].request;
638 static const char usage[] =
639 "tig " TIG_VERSION " (" __DATE__ ")\n"
641 "Usage: tig [options] [revs] [--] [paths]\n"
642 " or: tig show [options] [revs] [--] [paths]\n"
643 " or: tig blame [rev] path\n"
645 " or: tig < [git command output]\n"
648 " -v, --version Show version and exit\n"
649 " -h, --help Show help message and exit";
651 /* Option and state variables. */
652 static bool opt_date = TRUE;
653 static bool opt_author = TRUE;
654 static bool opt_line_number = FALSE;
655 static bool opt_line_graphics = TRUE;
656 static bool opt_rev_graph = FALSE;
657 static bool opt_show_refs = TRUE;
658 static int opt_num_interval = NUMBER_INTERVAL;
659 static int opt_tab_size = TAB_SIZE;
660 static int opt_author_cols = AUTHOR_COLS-1;
661 static char opt_cmd[SIZEOF_STR] = "";
662 static char opt_path[SIZEOF_STR] = "";
663 static char opt_file[SIZEOF_STR] = "";
664 static char opt_ref[SIZEOF_REF] = "";
665 static char opt_head[SIZEOF_REF] = "";
666 static char opt_head_rev[SIZEOF_REV] = "";
667 static char opt_remote[SIZEOF_REF] = "";
668 static FILE *opt_pipe = NULL;
669 static char opt_encoding[20] = "UTF-8";
670 static bool opt_utf8 = TRUE;
671 static char opt_codeset[20] = "UTF-8";
672 static iconv_t opt_iconv = ICONV_NONE;
673 static char opt_search[SIZEOF_STR] = "";
674 static char opt_cdup[SIZEOF_STR] = "";
675 static char opt_git_dir[SIZEOF_STR] = "";
676 static signed char opt_is_inside_work_tree = -1; /* set to TRUE or FALSE */
677 static char opt_editor[SIZEOF_STR] = "";
678 static FILE *opt_tty = NULL;
680 #define is_initial_commit() (!*opt_head_rev)
681 #define is_head_commit(rev) (!strcmp((rev), "HEAD") || !strcmp(opt_head_rev, (rev)))
684 parse_options(int argc, const char *argv[])
686 enum request request = REQ_VIEW_MAIN;
688 const char *subcommand;
689 bool seen_dashdash = FALSE;
692 if (!isatty(STDIN_FILENO)) {
694 return REQ_VIEW_PAGER;
698 return REQ_VIEW_MAIN;
700 subcommand = argv[1];
701 if (!strcmp(subcommand, "status") || !strcmp(subcommand, "-S")) {
702 if (!strcmp(subcommand, "-S"))
703 warn("`-S' has been deprecated; use `tig status' instead");
705 warn("ignoring arguments after `%s'", subcommand);
706 return REQ_VIEW_STATUS;
708 } else if (!strcmp(subcommand, "blame")) {
709 if (argc <= 2 || argc > 4)
710 die("invalid number of options to blame\n\n%s", usage);
714 string_ncopy(opt_ref, argv[i], strlen(argv[i]));
718 string_ncopy(opt_file, argv[i], strlen(argv[i]));
719 return REQ_VIEW_BLAME;
721 } else if (!strcmp(subcommand, "show")) {
722 request = REQ_VIEW_DIFF;
724 } else if (!strcmp(subcommand, "log") || !strcmp(subcommand, "diff")) {
725 request = subcommand[0] == 'l' ? REQ_VIEW_LOG : REQ_VIEW_DIFF;
726 warn("`tig %s' has been deprecated", subcommand);
733 /* XXX: This is vulnerable to the user overriding
734 * options required for the main view parser. */
735 string_copy(opt_cmd, TIG_MAIN_BASE);
737 string_format(opt_cmd, "git %s", subcommand);
739 buf_size = strlen(opt_cmd);
741 for (i = 1 + !!subcommand; i < argc; i++) {
742 const char *opt = argv[i];
744 if (seen_dashdash || !strcmp(opt, "--")) {
745 seen_dashdash = TRUE;
747 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
748 printf("tig version %s\n", TIG_VERSION);
751 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
752 printf("%s\n", usage);
756 opt_cmd[buf_size++] = ' ';
757 buf_size = sq_quote(opt_cmd, buf_size, opt);
758 if (buf_size >= sizeof(opt_cmd))
759 die("command too long");
762 opt_cmd[buf_size] = 0;
769 * Line-oriented content detection.
773 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
774 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
775 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
776 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
777 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
778 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
779 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
780 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
781 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
782 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
783 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
784 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
785 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
786 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
787 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
788 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
789 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
790 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
791 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
792 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
793 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
794 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
795 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
796 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
797 LINE(AUTHOR, "author ", COLOR_CYAN, COLOR_DEFAULT, 0), \
798 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
799 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
800 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
801 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
802 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
803 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
804 LINE(DELIMITER, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
805 LINE(DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
806 LINE(LINE_NUMBER, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
807 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
808 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
809 LINE(MAIN_AUTHOR, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
810 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
811 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
812 LINE(MAIN_LOCAL_TAG,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
813 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
814 LINE(MAIN_TRACKED, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
815 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
816 LINE(MAIN_HEAD, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
817 LINE(MAIN_REVGRAPH,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
818 LINE(TREE_DIR, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
819 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
820 LINE(STAT_HEAD, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
821 LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
822 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
823 LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
824 LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
825 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
826 LINE(BLAME_ID, "", COLOR_MAGENTA, COLOR_DEFAULT, 0)
829 #define LINE(type, line, fg, bg, attr) \
837 const char *name; /* Option name. */
838 int namelen; /* Size of option name. */
839 const char *line; /* The start of line to match. */
840 int linelen; /* Size of string to match. */
841 int fg, bg, attr; /* Color and text attributes for the lines. */
844 static struct line_info line_info[] = {
845 #define LINE(type, line, fg, bg, attr) \
846 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
851 static enum line_type
852 get_line_type(const char *line)
854 int linelen = strlen(line);
857 for (type = 0; type < ARRAY_SIZE(line_info); type++)
858 /* Case insensitive search matches Signed-off-by lines better. */
859 if (linelen >= line_info[type].linelen &&
860 !strncasecmp(line_info[type].line, line, line_info[type].linelen))
867 get_line_attr(enum line_type type)
869 assert(type < ARRAY_SIZE(line_info));
870 return COLOR_PAIR(type) | line_info[type].attr;
873 static struct line_info *
874 get_line_info(const char *name)
876 size_t namelen = strlen(name);
879 for (type = 0; type < ARRAY_SIZE(line_info); type++)
880 if (namelen == line_info[type].namelen &&
881 !string_enum_compare(line_info[type].name, name, namelen))
882 return &line_info[type];
890 int default_bg = line_info[LINE_DEFAULT].bg;
891 int default_fg = line_info[LINE_DEFAULT].fg;
896 if (assume_default_colors(default_fg, default_bg) == ERR) {
897 default_bg = COLOR_BLACK;
898 default_fg = COLOR_WHITE;
901 for (type = 0; type < ARRAY_SIZE(line_info); type++) {
902 struct line_info *info = &line_info[type];
903 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
904 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
906 init_pair(type, fg, bg);
914 unsigned int selected:1;
915 unsigned int dirty:1;
917 void *data; /* User data */
927 enum request request;
930 static struct keybinding default_keybindings[] = {
932 { 'm', REQ_VIEW_MAIN },
933 { 'd', REQ_VIEW_DIFF },
934 { 'l', REQ_VIEW_LOG },
935 { 't', REQ_VIEW_TREE },
936 { 'f', REQ_VIEW_BLOB },
937 { 'B', REQ_VIEW_BLAME },
938 { 'p', REQ_VIEW_PAGER },
939 { 'h', REQ_VIEW_HELP },
940 { 'S', REQ_VIEW_STATUS },
941 { 'c', REQ_VIEW_STAGE },
943 /* View manipulation */
944 { 'q', REQ_VIEW_CLOSE },
945 { KEY_TAB, REQ_VIEW_NEXT },
946 { KEY_RETURN, REQ_ENTER },
947 { KEY_UP, REQ_PREVIOUS },
948 { KEY_DOWN, REQ_NEXT },
949 { 'R', REQ_REFRESH },
950 { KEY_F(5), REQ_REFRESH },
951 { 'O', REQ_MAXIMIZE },
953 /* Cursor navigation */
954 { 'k', REQ_MOVE_UP },
955 { 'j', REQ_MOVE_DOWN },
956 { KEY_HOME, REQ_MOVE_FIRST_LINE },
957 { KEY_END, REQ_MOVE_LAST_LINE },
958 { KEY_NPAGE, REQ_MOVE_PAGE_DOWN },
959 { ' ', REQ_MOVE_PAGE_DOWN },
960 { KEY_PPAGE, REQ_MOVE_PAGE_UP },
961 { 'b', REQ_MOVE_PAGE_UP },
962 { '-', REQ_MOVE_PAGE_UP },
965 { KEY_IC, REQ_SCROLL_LINE_UP },
966 { KEY_DC, REQ_SCROLL_LINE_DOWN },
967 { 'w', REQ_SCROLL_PAGE_UP },
968 { 's', REQ_SCROLL_PAGE_DOWN },
972 { '?', REQ_SEARCH_BACK },
973 { 'n', REQ_FIND_NEXT },
974 { 'N', REQ_FIND_PREV },
978 { 'z', REQ_STOP_LOADING },
979 { 'v', REQ_SHOW_VERSION },
980 { 'r', REQ_SCREEN_REDRAW },
981 { '.', REQ_TOGGLE_LINENO },
982 { 'D', REQ_TOGGLE_DATE },
983 { 'A', REQ_TOGGLE_AUTHOR },
984 { 'g', REQ_TOGGLE_REV_GRAPH },
985 { 'F', REQ_TOGGLE_REFS },
987 { 'u', REQ_STATUS_UPDATE },
988 { '!', REQ_STATUS_REVERT },
989 { 'M', REQ_STATUS_MERGE },
990 { '@', REQ_STAGE_NEXT },
991 { ',', REQ_TREE_PARENT },
994 /* Using the ncurses SIGWINCH handler. */
995 { KEY_RESIZE, REQ_SCREEN_RESIZE },
998 #define KEYMAP_INFO \
1012 #define KEYMAP_(name) KEYMAP_##name
1017 static struct int_map keymap_table[] = {
1018 #define KEYMAP_(name) { #name, STRING_SIZE(#name), KEYMAP_##name }
1023 #define set_keymap(map, name) \
1024 set_from_int_map(keymap_table, ARRAY_SIZE(keymap_table), map, name, strlen(name))
1026 struct keybinding_table {
1027 struct keybinding *data;
1031 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1034 add_keybinding(enum keymap keymap, enum request request, int key)
1036 struct keybinding_table *table = &keybindings[keymap];
1038 table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1040 die("Failed to allocate keybinding");
1041 table->data[table->size].alias = key;
1042 table->data[table->size++].request = request;
1045 /* Looks for a key binding first in the given map, then in the generic map, and
1046 * lastly in the default keybindings. */
1048 get_keybinding(enum keymap keymap, int key)
1052 for (i = 0; i < keybindings[keymap].size; i++)
1053 if (keybindings[keymap].data[i].alias == key)
1054 return keybindings[keymap].data[i].request;
1056 for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1057 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1058 return keybindings[KEYMAP_GENERIC].data[i].request;
1060 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1061 if (default_keybindings[i].alias == key)
1062 return default_keybindings[i].request;
1064 return (enum request) key;
1073 static struct key key_table[] = {
1074 { "Enter", KEY_RETURN },
1076 { "Backspace", KEY_BACKSPACE },
1078 { "Escape", KEY_ESC },
1079 { "Left", KEY_LEFT },
1080 { "Right", KEY_RIGHT },
1082 { "Down", KEY_DOWN },
1083 { "Insert", KEY_IC },
1084 { "Delete", KEY_DC },
1086 { "Home", KEY_HOME },
1088 { "PageUp", KEY_PPAGE },
1089 { "PageDown", KEY_NPAGE },
1099 { "F10", KEY_F(10) },
1100 { "F11", KEY_F(11) },
1101 { "F12", KEY_F(12) },
1105 get_key_value(const char *name)
1109 for (i = 0; i < ARRAY_SIZE(key_table); i++)
1110 if (!strcasecmp(key_table[i].name, name))
1111 return key_table[i].value;
1113 if (strlen(name) == 1 && isprint(*name))
1120 get_key_name(int key_value)
1122 static char key_char[] = "'X'";
1123 const char *seq = NULL;
1126 for (key = 0; key < ARRAY_SIZE(key_table); key++)
1127 if (key_table[key].value == key_value)
1128 seq = key_table[key].name;
1132 isprint(key_value)) {
1133 key_char[1] = (char) key_value;
1137 return seq ? seq : "(no key)";
1141 get_key(enum request request)
1143 static char buf[BUFSIZ];
1150 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1151 struct keybinding *keybinding = &default_keybindings[i];
1153 if (keybinding->request != request)
1156 if (!string_format_from(buf, &pos, "%s%s", sep,
1157 get_key_name(keybinding->alias)))
1158 return "Too many keybindings!";
1165 struct run_request {
1168 const char *argv[SIZEOF_ARG];
1171 static struct run_request *run_request;
1172 static size_t run_requests;
1175 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1177 struct run_request *req;
1179 if (argc >= ARRAY_SIZE(req->argv) - 1)
1182 req = realloc(run_request, (run_requests + 1) * sizeof(*run_request));
1187 req = &run_request[run_requests];
1188 req->keymap = keymap;
1190 req->argv[0] = NULL;
1192 if (!format_argv(req->argv, argv, FORMAT_NONE))
1195 return REQ_NONE + ++run_requests;
1198 static struct run_request *
1199 get_run_request(enum request request)
1201 if (request <= REQ_NONE)
1203 return &run_request[request - REQ_NONE - 1];
1207 add_builtin_run_requests(void)
1209 const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1210 const char *gc[] = { "git", "gc", NULL };
1217 { KEYMAP_MAIN, 'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1218 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1222 for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1225 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1226 if (req != REQ_NONE)
1227 add_keybinding(reqs[i].keymap, req, reqs[i].key);
1232 * User config file handling.
1235 static struct int_map color_map[] = {
1236 #define COLOR_MAP(name) { #name, STRING_SIZE(#name), COLOR_##name }
1248 #define set_color(color, name) \
1249 set_from_int_map(color_map, ARRAY_SIZE(color_map), color, name, strlen(name))
1251 static struct int_map attr_map[] = {
1252 #define ATTR_MAP(name) { #name, STRING_SIZE(#name), A_##name }
1259 ATTR_MAP(UNDERLINE),
1262 #define set_attribute(attr, name) \
1263 set_from_int_map(attr_map, ARRAY_SIZE(attr_map), attr, name, strlen(name))
1265 static int config_lineno;
1266 static bool config_errors;
1267 static const char *config_msg;
1269 /* Wants: object fgcolor bgcolor [attr] */
1271 option_color_command(int argc, const char *argv[])
1273 struct line_info *info;
1275 if (argc != 3 && argc != 4) {
1276 config_msg = "Wrong number of arguments given to color command";
1280 info = get_line_info(argv[0]);
1282 if (!string_enum_compare(argv[0], "main-delim", strlen("main-delim"))) {
1283 info = get_line_info("delimiter");
1285 } else if (!string_enum_compare(argv[0], "main-date", strlen("main-date"))) {
1286 info = get_line_info("date");
1289 config_msg = "Unknown color name";
1294 if (set_color(&info->fg, argv[1]) == ERR ||
1295 set_color(&info->bg, argv[2]) == ERR) {
1296 config_msg = "Unknown color";
1300 if (argc == 4 && set_attribute(&info->attr, argv[3]) == ERR) {
1301 config_msg = "Unknown attribute";
1308 static bool parse_bool(const char *s)
1310 return (!strcmp(s, "1") || !strcmp(s, "true") ||
1311 !strcmp(s, "yes")) ? TRUE : FALSE;
1315 parse_int(const char *s, int default_value, int min, int max)
1317 int value = atoi(s);
1319 return (value < min || value > max) ? default_value : value;
1322 /* Wants: name = value */
1324 option_set_command(int argc, const char *argv[])
1327 config_msg = "Wrong number of arguments given to set command";
1331 if (strcmp(argv[1], "=")) {
1332 config_msg = "No value assigned";
1336 if (!strcmp(argv[0], "show-author")) {
1337 opt_author = parse_bool(argv[2]);
1341 if (!strcmp(argv[0], "show-date")) {
1342 opt_date = parse_bool(argv[2]);
1346 if (!strcmp(argv[0], "show-rev-graph")) {
1347 opt_rev_graph = parse_bool(argv[2]);
1351 if (!strcmp(argv[0], "show-refs")) {
1352 opt_show_refs = parse_bool(argv[2]);
1356 if (!strcmp(argv[0], "show-line-numbers")) {
1357 opt_line_number = parse_bool(argv[2]);
1361 if (!strcmp(argv[0], "line-graphics")) {
1362 opt_line_graphics = parse_bool(argv[2]);
1366 if (!strcmp(argv[0], "line-number-interval")) {
1367 opt_num_interval = parse_int(argv[2], opt_num_interval, 1, 1024);
1371 if (!strcmp(argv[0], "author-width")) {
1372 opt_author_cols = parse_int(argv[2], opt_author_cols, 0, 1024);
1376 if (!strcmp(argv[0], "tab-size")) {
1377 opt_tab_size = parse_int(argv[2], opt_tab_size, 1, 1024);
1381 if (!strcmp(argv[0], "commit-encoding")) {
1382 const char *arg = argv[2];
1383 int arglen = strlen(arg);
1388 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1389 config_msg = "Unmatched quotation";
1392 arg += 1; arglen -= 2;
1394 string_ncopy(opt_encoding, arg, strlen(arg));
1399 config_msg = "Unknown variable name";
1403 /* Wants: mode request key */
1405 option_bind_command(int argc, const char *argv[])
1407 enum request request;
1412 config_msg = "Wrong number of arguments given to bind command";
1416 if (set_keymap(&keymap, argv[0]) == ERR) {
1417 config_msg = "Unknown key map";
1421 key = get_key_value(argv[1]);
1423 config_msg = "Unknown key";
1427 request = get_request(argv[2]);
1428 if (request == REQ_NONE) {
1429 const char *obsolete[] = { "cherry-pick" };
1430 size_t namelen = strlen(argv[2]);
1433 for (i = 0; i < ARRAY_SIZE(obsolete); i++) {
1434 if (namelen == strlen(obsolete[i]) &&
1435 !string_enum_compare(obsolete[i], argv[2], namelen)) {
1436 config_msg = "Obsolete request name";
1441 if (request == REQ_NONE && *argv[2]++ == '!')
1442 request = add_run_request(keymap, key, argc - 2, argv + 2);
1443 if (request == REQ_NONE) {
1444 config_msg = "Unknown request name";
1448 add_keybinding(keymap, request, key);
1454 set_option(const char *opt, char *value)
1456 const char *argv[SIZEOF_ARG];
1459 if (!argv_from_string(argv, &argc, value)) {
1460 config_msg = "Too many option arguments";
1464 if (!strcmp(opt, "color"))
1465 return option_color_command(argc, argv);
1467 if (!strcmp(opt, "set"))
1468 return option_set_command(argc, argv);
1470 if (!strcmp(opt, "bind"))
1471 return option_bind_command(argc, argv);
1473 config_msg = "Unknown option command";
1478 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1483 config_msg = "Internal error";
1485 /* Check for comment markers, since read_properties() will
1486 * only ensure opt and value are split at first " \t". */
1487 optlen = strcspn(opt, "#");
1491 if (opt[optlen] != 0) {
1492 config_msg = "No option value";
1496 /* Look for comment endings in the value. */
1497 size_t len = strcspn(value, "#");
1499 if (len < valuelen) {
1501 value[valuelen] = 0;
1504 status = set_option(opt, value);
1507 if (status == ERR) {
1508 fprintf(stderr, "Error on line %d, near '%.*s': %s\n",
1509 config_lineno, (int) optlen, opt, config_msg);
1510 config_errors = TRUE;
1513 /* Always keep going if errors are encountered. */
1518 load_option_file(const char *path)
1522 /* It's ok that the file doesn't exist. */
1523 file = fopen(path, "r");
1528 config_errors = FALSE;
1530 if (read_properties(file, " \t", read_option) == ERR ||
1531 config_errors == TRUE)
1532 fprintf(stderr, "Errors while loading %s.\n", path);
1538 const char *home = getenv("HOME");
1539 const char *tigrc_user = getenv("TIGRC_USER");
1540 const char *tigrc_system = getenv("TIGRC_SYSTEM");
1541 char buf[SIZEOF_STR];
1543 add_builtin_run_requests();
1545 if (!tigrc_system) {
1546 if (!string_format(buf, "%s/tigrc", SYSCONFDIR))
1550 load_option_file(tigrc_system);
1553 if (!home || !string_format(buf, "%s/.tigrc", home))
1557 load_option_file(tigrc_user);
1570 /* The display array of active views and the index of the current view. */
1571 static struct view *display[2];
1572 static unsigned int current_view;
1574 /* Reading from the prompt? */
1575 static bool input_mode = FALSE;
1577 #define foreach_displayed_view(view, i) \
1578 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1580 #define displayed_views() (display[1] != NULL ? 2 : 1)
1582 /* Current head and commit ID */
1583 static char ref_blob[SIZEOF_REF] = "";
1584 static char ref_commit[SIZEOF_REF] = "HEAD";
1585 static char ref_head[SIZEOF_REF] = "HEAD";
1588 const char *name; /* View name */
1589 const char *cmd_fmt; /* Default command line format */
1590 const char *cmd_env; /* Command line set via environment */
1591 const char *id; /* Points to either of ref_{head,commit,blob} */
1593 struct view_ops *ops; /* View operations */
1595 enum keymap keymap; /* What keymap does this view have */
1596 bool git_dir; /* Whether the view requires a git directory. */
1598 char ref[SIZEOF_REF]; /* Hovered commit reference */
1599 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
1601 int height, width; /* The width and height of the main window */
1602 WINDOW *win; /* The main window */
1603 WINDOW *title; /* The title window living below the main window */
1606 unsigned long offset; /* Offset of the window top */
1607 unsigned long lineno; /* Current line number */
1610 char grep[SIZEOF_STR]; /* Search string */
1611 regex_t *regex; /* Pre-compiled regex */
1613 /* If non-NULL, points to the view that opened this view. If this view
1614 * is closed tig will switch back to the parent view. */
1615 struct view *parent;
1618 size_t lines; /* Total number of lines */
1619 struct line *line; /* Line index */
1620 size_t line_alloc; /* Total number of allocated lines */
1621 size_t line_size; /* Total number of used lines */
1622 unsigned int digits; /* Number of digits in the lines member. */
1625 struct line *curline; /* Line currently being drawn. */
1626 enum line_type curtype; /* Attribute currently used for drawing. */
1627 unsigned long col; /* Column when drawing. */
1636 /* What type of content being displayed. Used in the title bar. */
1638 /* Open and reads in all view content. */
1639 bool (*open)(struct view *view);
1640 /* Read one line; updates view->line. */
1641 bool (*read)(struct view *view, char *data);
1642 /* Draw one line; @lineno must be < view->height. */
1643 bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
1644 /* Depending on view handle a special requests. */
1645 enum request (*request)(struct view *view, enum request request, struct line *line);
1646 /* Search for regex in a line. */
1647 bool (*grep)(struct view *view, struct line *line);
1649 void (*select)(struct view *view, struct line *line);
1652 static struct view_ops blame_ops;
1653 static struct view_ops blob_ops;
1654 static struct view_ops help_ops;
1655 static struct view_ops log_ops;
1656 static struct view_ops main_ops;
1657 static struct view_ops pager_ops;
1658 static struct view_ops stage_ops;
1659 static struct view_ops status_ops;
1660 static struct view_ops tree_ops;
1662 #define VIEW_STR(name, cmd, env, ref, ops, map, git) \
1663 { name, cmd, #env, ref, ops, map, git }
1665 #define VIEW_(id, name, ops, git, ref) \
1666 VIEW_STR(name, TIG_##id##_CMD, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
1669 static struct view views[] = {
1670 VIEW_(MAIN, "main", &main_ops, TRUE, ref_head),
1671 VIEW_(DIFF, "diff", &pager_ops, TRUE, ref_commit),
1672 VIEW_(LOG, "log", &log_ops, TRUE, ref_head),
1673 VIEW_(TREE, "tree", &tree_ops, TRUE, ref_commit),
1674 VIEW_(BLOB, "blob", &blob_ops, TRUE, ref_blob),
1675 VIEW_(BLAME, "blame", &blame_ops, TRUE, ref_commit),
1676 VIEW_(HELP, "help", &help_ops, FALSE, ""),
1677 VIEW_(PAGER, "pager", &pager_ops, FALSE, "stdin"),
1678 VIEW_(STATUS, "status", &status_ops, TRUE, ""),
1679 VIEW_(STAGE, "stage", &stage_ops, TRUE, ""),
1682 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
1683 #define VIEW_REQ(view) ((view) - views + REQ_OFFSET + 1)
1685 #define foreach_view(view, i) \
1686 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1688 #define view_is_displayed(view) \
1689 (view == display[0] || view == display[1])
1696 static int line_graphics[] = {
1697 /* LINE_GRAPHIC_VLINE: */ '|'
1701 set_view_attr(struct view *view, enum line_type type)
1703 if (!view->curline->selected && view->curtype != type) {
1704 wattrset(view->win, get_line_attr(type));
1705 wchgat(view->win, -1, 0, type, NULL);
1706 view->curtype = type;
1711 draw_chars(struct view *view, enum line_type type, const char *string,
1712 int max_len, bool use_tilde)
1716 int trimmed = FALSE;
1722 len = utf8_length(string, &col, max_len, &trimmed, use_tilde);
1724 col = len = strlen(string);
1725 if (len > max_len) {
1729 col = len = max_len;
1734 set_view_attr(view, type);
1735 waddnstr(view->win, string, len);
1736 if (trimmed && use_tilde) {
1737 set_view_attr(view, LINE_DELIMITER);
1738 waddch(view->win, '~');
1746 draw_space(struct view *view, enum line_type type, int max, int spaces)
1748 static char space[] = " ";
1751 spaces = MIN(max, spaces);
1753 while (spaces > 0) {
1754 int len = MIN(spaces, sizeof(space) - 1);
1756 col += draw_chars(view, type, space, spaces, FALSE);
1764 draw_lineno(struct view *view, unsigned int lineno)
1767 int digits3 = view->digits < 3 ? 3 : view->digits;
1768 int max_number = MIN(digits3, STRING_SIZE(number));
1769 int max = view->width - view->col;
1772 if (max < max_number)
1775 lineno += view->offset + 1;
1776 if (lineno == 1 || (lineno % opt_num_interval) == 0) {
1777 static char fmt[] = "%1ld";
1779 if (view->digits <= 9)
1780 fmt[1] = '0' + digits3;
1782 if (!string_format(number, fmt, lineno))
1784 col = draw_chars(view, LINE_LINE_NUMBER, number, max_number, TRUE);
1786 col = draw_space(view, LINE_LINE_NUMBER, max_number, max_number);
1790 set_view_attr(view, LINE_DEFAULT);
1791 waddch(view->win, line_graphics[LINE_GRAPHIC_VLINE]);
1796 col += draw_space(view, LINE_DEFAULT, max - col, 1);
1799 return view->width - view->col <= 0;
1803 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
1805 view->col += draw_chars(view, type, string, view->width - view->col, trim);
1806 return view->width - view->col <= 0;
1810 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
1812 int max = view->width - view->col;
1818 set_view_attr(view, type);
1819 /* Using waddch() instead of waddnstr() ensures that
1820 * they'll be rendered correctly for the cursor line. */
1821 for (i = 0; i < size; i++)
1822 waddch(view->win, graphic[i]);
1826 waddch(view->win, ' ');
1830 return view->width - view->col <= 0;
1834 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
1836 int max = MIN(view->width - view->col, len);
1840 col = draw_chars(view, type, text, max - 1, trim);
1842 col = draw_space(view, type, max - 1, max - 1);
1844 view->col += col + draw_space(view, LINE_DEFAULT, max - col, max - col);
1845 return view->width - view->col <= 0;
1849 draw_date(struct view *view, struct tm *time)
1851 char buf[DATE_COLS];
1856 timelen = strftime(buf, sizeof(buf), DATE_FORMAT, time);
1857 date = timelen ? buf : NULL;
1859 return draw_field(view, LINE_DATE, date, DATE_COLS, FALSE);
1863 draw_view_line(struct view *view, unsigned int lineno)
1866 bool selected = (view->offset + lineno == view->lineno);
1869 assert(view_is_displayed(view));
1871 if (view->offset + lineno >= view->lines)
1874 line = &view->line[view->offset + lineno];
1876 wmove(view->win, lineno, 0);
1878 view->curline = line;
1879 view->curtype = LINE_NONE;
1880 line->selected = FALSE;
1883 set_view_attr(view, LINE_CURSOR);
1884 line->selected = TRUE;
1885 view->ops->select(view, line);
1886 } else if (line->selected) {
1887 wclrtoeol(view->win);
1890 scrollok(view->win, FALSE);
1891 draw_ok = view->ops->draw(view, line, lineno);
1892 scrollok(view->win, TRUE);
1898 redraw_view_dirty(struct view *view)
1903 for (lineno = 0; lineno < view->height; lineno++) {
1904 struct line *line = &view->line[view->offset + lineno];
1910 if (!draw_view_line(view, lineno))
1916 redrawwin(view->win);
1918 wnoutrefresh(view->win);
1920 wrefresh(view->win);
1924 redraw_view_from(struct view *view, int lineno)
1926 assert(0 <= lineno && lineno < view->height);
1928 for (; lineno < view->height; lineno++) {
1929 if (!draw_view_line(view, lineno))
1933 redrawwin(view->win);
1935 wnoutrefresh(view->win);
1937 wrefresh(view->win);
1941 redraw_view(struct view *view)
1944 redraw_view_from(view, 0);
1949 update_view_title(struct view *view)
1951 char buf[SIZEOF_STR];
1952 char state[SIZEOF_STR];
1953 size_t bufpos = 0, statelen = 0;
1955 assert(view_is_displayed(view));
1957 if (view != VIEW(REQ_VIEW_STATUS) && (view->lines || view->pipe)) {
1958 unsigned int view_lines = view->offset + view->height;
1959 unsigned int lines = view->lines
1960 ? MIN(view_lines, view->lines) * 100 / view->lines
1963 string_format_from(state, &statelen, "- %s %d of %d (%d%%)",
1970 time_t secs = time(NULL) - view->start_time;
1972 /* Three git seconds are a long time ... */
1974 string_format_from(state, &statelen, " %lds", secs);
1978 string_format_from(buf, &bufpos, "[%s]", view->name);
1979 if (*view->ref && bufpos < view->width) {
1980 size_t refsize = strlen(view->ref);
1981 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
1983 if (minsize < view->width)
1984 refsize = view->width - minsize + 7;
1985 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
1988 if (statelen && bufpos < view->width) {
1989 string_format_from(buf, &bufpos, " %s", state);
1992 if (view == display[current_view])
1993 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
1995 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
1997 mvwaddnstr(view->title, 0, 0, buf, bufpos);
1998 wclrtoeol(view->title);
1999 wmove(view->title, 0, view->width - 1);
2002 wnoutrefresh(view->title);
2004 wrefresh(view->title);
2008 resize_display(void)
2011 struct view *base = display[0];
2012 struct view *view = display[1] ? display[1] : display[0];
2014 /* Setup window dimensions */
2016 getmaxyx(stdscr, base->height, base->width);
2018 /* Make room for the status window. */
2022 /* Horizontal split. */
2023 view->width = base->width;
2024 view->height = SCALE_SPLIT_VIEW(base->height);
2025 base->height -= view->height;
2027 /* Make room for the title bar. */
2031 /* Make room for the title bar. */
2036 foreach_displayed_view (view, i) {
2038 view->win = newwin(view->height, 0, offset, 0);
2040 die("Failed to create %s view", view->name);
2042 scrollok(view->win, TRUE);
2044 view->title = newwin(1, 0, offset + view->height, 0);
2046 die("Failed to create title window");
2049 wresize(view->win, view->height, view->width);
2050 mvwin(view->win, offset, 0);
2051 mvwin(view->title, offset + view->height, 0);
2054 offset += view->height + 1;
2059 redraw_display(void)
2064 foreach_displayed_view (view, i) {
2066 update_view_title(view);
2071 update_display_cursor(struct view *view)
2073 /* Move the cursor to the right-most column of the cursor line.
2075 * XXX: This could turn out to be a bit expensive, but it ensures that
2076 * the cursor does not jump around. */
2078 wmove(view->win, view->lineno - view->offset, view->width - 1);
2079 wrefresh(view->win);
2087 /* Scrolling backend */
2089 do_scroll_view(struct view *view, int lines)
2091 bool redraw_current_line = FALSE;
2093 /* The rendering expects the new offset. */
2094 view->offset += lines;
2096 assert(0 <= view->offset && view->offset < view->lines);
2099 /* Move current line into the view. */
2100 if (view->lineno < view->offset) {
2101 view->lineno = view->offset;
2102 redraw_current_line = TRUE;
2103 } else if (view->lineno >= view->offset + view->height) {
2104 view->lineno = view->offset + view->height - 1;
2105 redraw_current_line = TRUE;
2108 assert(view->offset <= view->lineno && view->lineno < view->lines);
2110 /* Redraw the whole screen if scrolling is pointless. */
2111 if (view->height < ABS(lines)) {
2115 int line = lines > 0 ? view->height - lines : 0;
2116 int end = line + ABS(lines);
2118 wscrl(view->win, lines);
2120 for (; line < end; line++) {
2121 if (!draw_view_line(view, line))
2125 if (redraw_current_line)
2126 draw_view_line(view, view->lineno - view->offset);
2129 redrawwin(view->win);
2130 wrefresh(view->win);
2134 /* Scroll frontend */
2136 scroll_view(struct view *view, enum request request)
2140 assert(view_is_displayed(view));
2143 case REQ_SCROLL_PAGE_DOWN:
2144 lines = view->height;
2145 case REQ_SCROLL_LINE_DOWN:
2146 if (view->offset + lines > view->lines)
2147 lines = view->lines - view->offset;
2149 if (lines == 0 || view->offset + view->height >= view->lines) {
2150 report("Cannot scroll beyond the last line");
2155 case REQ_SCROLL_PAGE_UP:
2156 lines = view->height;
2157 case REQ_SCROLL_LINE_UP:
2158 if (lines > view->offset)
2159 lines = view->offset;
2162 report("Cannot scroll beyond the first line");
2170 die("request %d not handled in switch", request);
2173 do_scroll_view(view, lines);
2178 move_view(struct view *view, enum request request)
2180 int scroll_steps = 0;
2184 case REQ_MOVE_FIRST_LINE:
2185 steps = -view->lineno;
2188 case REQ_MOVE_LAST_LINE:
2189 steps = view->lines - view->lineno - 1;
2192 case REQ_MOVE_PAGE_UP:
2193 steps = view->height > view->lineno
2194 ? -view->lineno : -view->height;
2197 case REQ_MOVE_PAGE_DOWN:
2198 steps = view->lineno + view->height >= view->lines
2199 ? view->lines - view->lineno - 1 : view->height;
2211 die("request %d not handled in switch", request);
2214 if (steps <= 0 && view->lineno == 0) {
2215 report("Cannot move beyond the first line");
2218 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2219 report("Cannot move beyond the last line");
2223 /* Move the current line */
2224 view->lineno += steps;
2225 assert(0 <= view->lineno && view->lineno < view->lines);
2227 /* Check whether the view needs to be scrolled */
2228 if (view->lineno < view->offset ||
2229 view->lineno >= view->offset + view->height) {
2230 scroll_steps = steps;
2231 if (steps < 0 && -steps > view->offset) {
2232 scroll_steps = -view->offset;
2234 } else if (steps > 0) {
2235 if (view->lineno == view->lines - 1 &&
2236 view->lines > view->height) {
2237 scroll_steps = view->lines - view->offset - 1;
2238 if (scroll_steps >= view->height)
2239 scroll_steps -= view->height - 1;
2244 if (!view_is_displayed(view)) {
2245 view->offset += scroll_steps;
2246 assert(0 <= view->offset && view->offset < view->lines);
2247 view->ops->select(view, &view->line[view->lineno]);
2251 /* Repaint the old "current" line if we be scrolling */
2252 if (ABS(steps) < view->height)
2253 draw_view_line(view, view->lineno - steps - view->offset);
2256 do_scroll_view(view, scroll_steps);
2260 /* Draw the current line */
2261 draw_view_line(view, view->lineno - view->offset);
2263 redrawwin(view->win);
2264 wrefresh(view->win);
2273 static void search_view(struct view *view, enum request request);
2276 find_next_line(struct view *view, unsigned long lineno, struct line *line)
2278 assert(view_is_displayed(view));
2280 if (!view->ops->grep(view, line))
2283 if (lineno - view->offset >= view->height) {
2284 view->offset = lineno;
2285 view->lineno = lineno;
2289 unsigned long old_lineno = view->lineno - view->offset;
2291 view->lineno = lineno;
2292 draw_view_line(view, old_lineno);
2294 draw_view_line(view, view->lineno - view->offset);
2295 redrawwin(view->win);
2296 wrefresh(view->win);
2299 report("Line %ld matches '%s'", lineno + 1, view->grep);
2304 find_next(struct view *view, enum request request)
2306 unsigned long lineno = view->lineno;
2311 report("No previous search");
2313 search_view(view, request);
2323 case REQ_SEARCH_BACK:
2332 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2333 lineno += direction;
2335 /* Note, lineno is unsigned long so will wrap around in which case it
2336 * will become bigger than view->lines. */
2337 for (; lineno < view->lines; lineno += direction) {
2338 struct line *line = &view->line[lineno];
2340 if (find_next_line(view, lineno, line))
2344 report("No match found for '%s'", view->grep);
2348 search_view(struct view *view, enum request request)
2353 regfree(view->regex);
2356 view->regex = calloc(1, sizeof(*view->regex));
2361 regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2362 if (regex_err != 0) {
2363 char buf[SIZEOF_STR] = "unknown error";
2365 regerror(regex_err, view->regex, buf, sizeof(buf));
2366 report("Search failed: %s", buf);
2370 string_copy(view->grep, opt_search);
2372 find_next(view, request);
2376 * Incremental updating
2380 reset_view(struct view *view)
2384 for (i = 0; i < view->lines; i++)
2385 free(view->line[i].data);
2392 view->line_size = 0;
2393 view->line_alloc = 0;
2398 free_argv(const char *argv[])
2402 for (argc = 0; argv[argc]; argc++)
2403 free((void *) argv[argc]);
2407 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
2409 char buf[SIZEOF_STR];
2411 bool noreplace = flags == FORMAT_NONE;
2413 free_argv(dst_argv);
2415 for (argc = 0; src_argv[argc]; argc++) {
2416 const char *arg = src_argv[argc];
2420 char *next = strstr(arg, "%(");
2421 int len = next - arg;
2424 if (!next || noreplace) {
2425 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
2430 } else if (!prefixcmp(next, "%(directory)")) {
2433 } else if (!prefixcmp(next, "%(file)")) {
2436 } else if (!prefixcmp(next, "%(ref)")) {
2437 value = *opt_ref ? opt_ref : "HEAD";
2439 } else if (!prefixcmp(next, "%(head)")) {
2442 } else if (!prefixcmp(next, "%(commit)")) {
2445 } else if (!prefixcmp(next, "%(blob)")) {
2449 report("Unknown replacement: `%s`", next);
2453 if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
2456 arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
2459 dst_argv[argc] = strdup(buf);
2460 if (!dst_argv[argc])
2464 dst_argv[argc] = NULL;
2466 return src_argv[argc] == NULL;
2470 format_command(char dst[], const char *src_argv[], enum format_flags flags)
2472 const char *dst_argv[SIZEOF_ARG * 2] = { NULL };
2476 if (!format_argv(dst_argv, src_argv, flags)) {
2477 free_argv(dst_argv);
2481 for (argc = 0; dst_argv[argc] && bufsize < SIZEOF_STR; argc++) {
2483 dst[bufsize++] = ' ';
2484 bufsize = sq_quote(dst, bufsize, dst_argv[argc]);
2487 if (bufsize < SIZEOF_STR)
2489 free_argv(dst_argv);
2491 return src_argv[argc] == NULL && bufsize < SIZEOF_STR;
2495 end_update(struct view *view, bool force)
2499 while (!view->ops->read(view, NULL))
2502 set_nonblocking_input(FALSE);
2503 done_io(view->pipe);
2508 setup_update(struct view *view, const char *vid)
2510 set_nonblocking_input(TRUE);
2512 string_copy_rev(view->vid, vid);
2513 view->pipe = &view->io;
2514 view->start_time = time(NULL);
2518 begin_update(struct view *view, bool refresh)
2520 if (init_io_fd(&view->io, opt_pipe)) {
2523 } else if (opt_cmd[0]) {
2524 if (!run_io(&view->io, IO_RD, opt_cmd))
2526 /* When running random commands, initially show the
2527 * command in the title. However, it maybe later be
2528 * overwritten if a commit line is selected. */
2529 if (view == VIEW(REQ_VIEW_PAGER))
2530 string_copy(view->ref, opt_cmd);
2535 } else if (refresh) {
2536 if (!start_io(&view->io))
2539 } else if (view == VIEW(REQ_VIEW_TREE)) {
2540 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
2541 char path[SIZEOF_STR];
2543 if (strcmp(view->vid, view->id))
2544 opt_path[0] = path[0] = 0;
2545 else if (sq_quote(path, 0, opt_path) >= sizeof(path))
2548 if (!run_io_format(&view->io, format, view->id, path))
2552 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
2553 const char *id = view->id;
2555 if (!run_io_format(&view->io, format, id, id, id, id, id))
2558 /* Put the current ref_* value to the view title ref
2559 * member. This is needed by the blob view. Most other
2560 * views sets it automatically after loading because the
2561 * first line is a commit line. */
2562 string_copy_rev(view->ref, view->id);
2565 setup_update(view, view->id);
2570 #define ITEM_CHUNK_SIZE 256
2572 realloc_items(void *mem, size_t *size, size_t new_size, size_t item_size)
2574 size_t num_chunks = *size / ITEM_CHUNK_SIZE;
2575 size_t num_chunks_new = (new_size + ITEM_CHUNK_SIZE - 1) / ITEM_CHUNK_SIZE;
2577 if (mem == NULL || num_chunks != num_chunks_new) {
2578 *size = num_chunks_new * ITEM_CHUNK_SIZE;
2579 mem = realloc(mem, *size * item_size);
2585 static struct line *
2586 realloc_lines(struct view *view, size_t line_size)
2588 size_t alloc = view->line_alloc;
2589 struct line *tmp = realloc_items(view->line, &alloc, line_size,
2590 sizeof(*view->line));
2596 view->line_alloc = alloc;
2597 view->line_size = line_size;
2602 update_view(struct view *view)
2604 char out_buffer[BUFSIZ * 2];
2606 /* The number of lines to read. If too low it will cause too much
2607 * redrawing (and possible flickering), if too high responsiveness
2609 unsigned long lines = view->height;
2610 int redraw_from = -1;
2615 /* Only redraw if lines are visible. */
2616 if (view->offset + view->height >= view->lines)
2617 redraw_from = view->lines - view->offset;
2619 /* FIXME: This is probably not perfect for backgrounded views. */
2620 if (!realloc_lines(view, view->lines + lines))
2623 while ((line = io_gets(view->pipe))) {
2624 size_t linelen = strlen(line);
2627 line[linelen - 1] = 0;
2629 if (opt_iconv != ICONV_NONE) {
2630 ICONV_CONST char *inbuf = line;
2631 size_t inlen = linelen;
2633 char *outbuf = out_buffer;
2634 size_t outlen = sizeof(out_buffer);
2638 ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2639 if (ret != (size_t) -1) {
2641 linelen = strlen(out_buffer);
2645 if (!view->ops->read(view, line))
2655 lines = view->lines;
2656 for (digits = 0; lines; digits++)
2659 /* Keep the displayed view in sync with line number scaling. */
2660 if (digits != view->digits) {
2661 view->digits = digits;
2666 if (io_error(view->pipe)) {
2667 report("Failed to read: %s", io_strerror(view->pipe));
2668 end_update(view, TRUE);
2670 } else if (io_eof(view->pipe)) {
2672 end_update(view, FALSE);
2675 if (!view_is_displayed(view))
2678 if (view == VIEW(REQ_VIEW_TREE)) {
2679 /* Clear the view and redraw everything since the tree sorting
2680 * might have rearranged things. */
2683 } else if (redraw_from >= 0) {
2684 /* If this is an incremental update, redraw the previous line
2685 * since for commits some members could have changed when
2686 * loading the main view. */
2687 if (redraw_from > 0)
2690 /* Since revision graph visualization requires knowledge
2691 * about the parent commit, it causes a further one-off
2692 * needed to be redrawn for incremental updates. */
2693 if (redraw_from > 0 && opt_rev_graph)
2696 /* Incrementally draw avoids flickering. */
2697 redraw_view_from(view, redraw_from);
2700 if (view == VIEW(REQ_VIEW_BLAME))
2701 redraw_view_dirty(view);
2703 /* Update the title _after_ the redraw so that if the redraw picks up a
2704 * commit reference in view->ref it'll be available here. */
2705 update_view_title(view);
2709 report("Allocation failure");
2710 end_update(view, TRUE);
2714 static struct line *
2715 add_line_data(struct view *view, void *data, enum line_type type)
2717 struct line *line = &view->line[view->lines++];
2719 memset(line, 0, sizeof(*line));
2726 static struct line *
2727 add_line_text(struct view *view, const char *text, enum line_type type)
2729 char *data = text ? strdup(text) : NULL;
2731 return data ? add_line_data(view, data, type) : NULL;
2740 OPEN_DEFAULT = 0, /* Use default view switching. */
2741 OPEN_SPLIT = 1, /* Split current view. */
2742 OPEN_BACKGROUNDED = 2, /* Backgrounded. */
2743 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
2744 OPEN_NOMAXIMIZE = 8, /* Do not maximize the current view. */
2745 OPEN_REFRESH = 16, /* Refresh view using previous command. */
2749 open_view(struct view *prev, enum request request, enum open_flags flags)
2751 bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
2752 bool split = !!(flags & OPEN_SPLIT);
2753 bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH));
2754 bool nomaximize = !!(flags & (OPEN_NOMAXIMIZE | OPEN_REFRESH));
2755 struct view *view = VIEW(request);
2756 int nviews = displayed_views();
2757 struct view *base_view = display[0];
2759 if (view == prev && nviews == 1 && !reload) {
2760 report("Already in %s view", view->name);
2764 if (view->git_dir && !opt_git_dir[0]) {
2765 report("The %s view is disabled in pager view", view->name);
2773 } else if (!nomaximize) {
2774 /* Maximize the current view. */
2775 memset(display, 0, sizeof(display));
2777 display[current_view] = view;
2780 /* Resize the view when switching between split- and full-screen,
2781 * or when switching between two different full-screen views. */
2782 if (nviews != displayed_views() ||
2783 (nviews == 1 && base_view != display[0]))
2787 end_update(view, TRUE);
2789 if (view->ops->open) {
2790 if (!view->ops->open(view)) {
2791 report("Failed to load %s view", view->name);
2795 } else if ((reload || strcmp(view->vid, view->id)) &&
2796 !begin_update(view, flags & OPEN_REFRESH)) {
2797 report("Failed to load %s view", view->name);
2801 if (split && prev->lineno - prev->offset >= prev->height) {
2802 /* Take the title line into account. */
2803 int lines = prev->lineno - prev->offset - prev->height + 1;
2805 /* Scroll the view that was split if the current line is
2806 * outside the new limited view. */
2807 do_scroll_view(prev, lines);
2810 if (prev && view != prev) {
2811 if (split && !backgrounded) {
2812 /* "Blur" the previous view. */
2813 update_view_title(prev);
2816 view->parent = prev;
2819 if (view->pipe && view->lines == 0) {
2820 /* Clear the old view and let the incremental updating refill
2824 } else if (view_is_displayed(view)) {
2829 /* If the view is backgrounded the above calls to report()
2830 * won't redraw the view title. */
2832 update_view_title(view);
2836 open_external_viewer(const char *argv[], const char *dir)
2838 def_prog_mode(); /* save current tty modes */
2839 endwin(); /* restore original tty modes */
2840 run_io_fg(argv, dir);
2841 fprintf(stderr, "Press Enter to continue");
2848 open_mergetool(const char *file)
2850 const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
2852 open_external_viewer(mergetool_argv, NULL);
2856 open_editor(bool from_root, const char *file)
2858 const char *editor_argv[] = { "vi", file, NULL };
2861 editor = getenv("GIT_EDITOR");
2862 if (!editor && *opt_editor)
2863 editor = opt_editor;
2865 editor = getenv("VISUAL");
2867 editor = getenv("EDITOR");
2871 editor_argv[0] = editor;
2872 open_external_viewer(editor_argv, from_root ? opt_cdup : NULL);
2876 open_run_request(enum request request)
2878 struct run_request *req = get_run_request(request);
2879 const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
2882 report("Unknown run request");
2886 if (format_argv(argv, req->argv, FORMAT_ALL))
2887 open_external_viewer(argv, NULL);
2892 * User request switch noodle
2896 view_driver(struct view *view, enum request request)
2900 if (request == REQ_NONE) {
2905 if (request > REQ_NONE) {
2906 open_run_request(request);
2907 /* FIXME: When all views can refresh always do this. */
2908 if (view == VIEW(REQ_VIEW_STATUS) ||
2909 view == VIEW(REQ_VIEW_MAIN) ||
2910 view == VIEW(REQ_VIEW_LOG) ||
2911 view == VIEW(REQ_VIEW_STAGE))
2912 request = REQ_REFRESH;
2917 if (view && view->lines) {
2918 request = view->ops->request(view, request, &view->line[view->lineno]);
2919 if (request == REQ_NONE)
2926 case REQ_MOVE_PAGE_UP:
2927 case REQ_MOVE_PAGE_DOWN:
2928 case REQ_MOVE_FIRST_LINE:
2929 case REQ_MOVE_LAST_LINE:
2930 move_view(view, request);
2933 case REQ_SCROLL_LINE_DOWN:
2934 case REQ_SCROLL_LINE_UP:
2935 case REQ_SCROLL_PAGE_DOWN:
2936 case REQ_SCROLL_PAGE_UP:
2937 scroll_view(view, request);
2940 case REQ_VIEW_BLAME:
2942 report("No file chosen, press %s to open tree view",
2943 get_key(REQ_VIEW_TREE));
2946 open_view(view, request, OPEN_DEFAULT);
2951 report("No file chosen, press %s to open tree view",
2952 get_key(REQ_VIEW_TREE));
2955 open_view(view, request, OPEN_DEFAULT);
2958 case REQ_VIEW_PAGER:
2959 if (!opt_pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
2960 report("No pager content, press %s to run command from prompt",
2961 get_key(REQ_PROMPT));
2964 open_view(view, request, OPEN_DEFAULT);
2967 case REQ_VIEW_STAGE:
2968 if (!VIEW(REQ_VIEW_STAGE)->lines) {
2969 report("No stage content, press %s to open the status view and choose file",
2970 get_key(REQ_VIEW_STATUS));
2973 open_view(view, request, OPEN_DEFAULT);
2976 case REQ_VIEW_STATUS:
2977 if (opt_is_inside_work_tree == FALSE) {
2978 report("The status view requires a working tree");
2981 open_view(view, request, OPEN_DEFAULT);
2989 open_view(view, request, OPEN_DEFAULT);
2994 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
2996 if ((view == VIEW(REQ_VIEW_DIFF) &&
2997 view->parent == VIEW(REQ_VIEW_MAIN)) ||
2998 (view == VIEW(REQ_VIEW_DIFF) &&
2999 view->parent == VIEW(REQ_VIEW_BLAME)) ||
3000 (view == VIEW(REQ_VIEW_STAGE) &&
3001 view->parent == VIEW(REQ_VIEW_STATUS)) ||
3002 (view == VIEW(REQ_VIEW_BLOB) &&
3003 view->parent == VIEW(REQ_VIEW_TREE))) {
3006 view = view->parent;
3007 line = view->lineno;
3008 move_view(view, request);
3009 if (view_is_displayed(view))
3010 update_view_title(view);
3011 if (line != view->lineno)
3012 view->ops->request(view, REQ_ENTER,
3013 &view->line[view->lineno]);
3016 move_view(view, request);
3022 int nviews = displayed_views();
3023 int next_view = (current_view + 1) % nviews;
3025 if (next_view == current_view) {
3026 report("Only one view is displayed");
3030 current_view = next_view;
3031 /* Blur out the title of the previous view. */
3032 update_view_title(view);
3037 report("Refreshing is not yet supported for the %s view", view->name);
3041 if (displayed_views() == 2)
3042 open_view(view, VIEW_REQ(view), OPEN_DEFAULT);
3045 case REQ_TOGGLE_LINENO:
3046 opt_line_number = !opt_line_number;
3050 case REQ_TOGGLE_DATE:
3051 opt_date = !opt_date;
3055 case REQ_TOGGLE_AUTHOR:
3056 opt_author = !opt_author;
3060 case REQ_TOGGLE_REV_GRAPH:
3061 opt_rev_graph = !opt_rev_graph;
3065 case REQ_TOGGLE_REFS:
3066 opt_show_refs = !opt_show_refs;
3071 case REQ_SEARCH_BACK:
3072 search_view(view, request);
3077 find_next(view, request);
3080 case REQ_STOP_LOADING:
3081 for (i = 0; i < ARRAY_SIZE(views); i++) {
3084 report("Stopped loading the %s view", view->name),
3085 end_update(view, TRUE);
3089 case REQ_SHOW_VERSION:
3090 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3093 case REQ_SCREEN_RESIZE:
3096 case REQ_SCREEN_REDRAW:
3101 report("Nothing to edit");
3105 report("Nothing to enter");
3108 case REQ_VIEW_CLOSE:
3109 /* XXX: Mark closed views by letting view->parent point to the
3110 * view itself. Parents to closed view should never be
3113 view->parent->parent != view->parent) {
3114 memset(display, 0, sizeof(display));
3116 display[current_view] = view->parent;
3117 view->parent = view;
3128 report("Unknown key, press 'h' for help");
3141 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3143 char *text = line->data;
3145 if (opt_line_number && draw_lineno(view, lineno))
3148 draw_text(view, line->type, text, TRUE);
3153 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3155 char refbuf[SIZEOF_STR];
3159 if (!string_format(refbuf, "git describe %s 2>/dev/null", commit_id))
3162 pipe = popen(refbuf, "r");
3166 if ((ref = fgets(refbuf, sizeof(refbuf), pipe)))
3167 ref = chomp_string(ref);
3173 /* This is the only fatal call, since it can "corrupt" the buffer. */
3174 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
3181 add_pager_refs(struct view *view, struct line *line)
3183 char buf[SIZEOF_STR];
3184 char *commit_id = (char *)line->data + STRING_SIZE("commit ");
3186 size_t bufpos = 0, refpos = 0;
3187 const char *sep = "Refs: ";
3188 bool is_tag = FALSE;
3190 assert(line->type == LINE_COMMIT);
3192 refs = get_refs(commit_id);
3194 if (view == VIEW(REQ_VIEW_DIFF))
3195 goto try_add_describe_ref;
3200 struct ref *ref = refs[refpos];
3201 const char *fmt = ref->tag ? "%s[%s]" :
3202 ref->remote ? "%s<%s>" : "%s%s";
3204 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
3209 } while (refs[refpos++]->next);
3211 if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
3212 try_add_describe_ref:
3213 /* Add <tag>-g<commit_id> "fake" reference. */
3214 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
3221 if (!realloc_lines(view, view->line_size + 1))
3224 add_line_text(view, buf, LINE_PP_REFS);
3228 pager_read(struct view *view, char *data)
3235 line = add_line_text(view, data, get_line_type(data));
3239 if (line->type == LINE_COMMIT &&
3240 (view == VIEW(REQ_VIEW_DIFF) ||
3241 view == VIEW(REQ_VIEW_LOG)))
3242 add_pager_refs(view, line);
3248 pager_request(struct view *view, enum request request, struct line *line)
3252 if (request != REQ_ENTER)
3255 if (line->type == LINE_COMMIT &&
3256 (view == VIEW(REQ_VIEW_LOG) ||
3257 view == VIEW(REQ_VIEW_PAGER))) {
3258 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3262 /* Always scroll the view even if it was split. That way
3263 * you can use Enter to scroll through the log view and
3264 * split open each commit diff. */
3265 scroll_view(view, REQ_SCROLL_LINE_DOWN);
3267 /* FIXME: A minor workaround. Scrolling the view will call report("")
3268 * but if we are scrolling a non-current view this won't properly
3269 * update the view title. */
3271 update_view_title(view);
3277 pager_grep(struct view *view, struct line *line)
3280 char *text = line->data;
3285 if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
3292 pager_select(struct view *view, struct line *line)
3294 if (line->type == LINE_COMMIT) {
3295 char *text = (char *)line->data + STRING_SIZE("commit ");
3297 if (view != VIEW(REQ_VIEW_PAGER))
3298 string_copy_rev(view->ref, text);
3299 string_copy_rev(ref_commit, text);
3303 static struct view_ops pager_ops = {
3314 log_request(struct view *view, enum request request, struct line *line)
3319 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
3322 return pager_request(view, request, line);
3326 static struct view_ops log_ops = {
3342 help_open(struct view *view)
3345 int lines = ARRAY_SIZE(req_info) + 2;
3348 if (view->lines > 0)
3351 for (i = 0; i < ARRAY_SIZE(req_info); i++)
3352 if (!req_info[i].request)
3355 lines += run_requests + 1;
3357 view->line = calloc(lines, sizeof(*view->line));
3361 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
3363 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
3366 if (req_info[i].request == REQ_NONE)
3369 if (!req_info[i].request) {
3370 add_line_text(view, "", LINE_DEFAULT);
3371 add_line_text(view, req_info[i].help, LINE_DEFAULT);
3375 key = get_key(req_info[i].request);
3377 key = "(no key defined)";
3379 if (!string_format(buf, " %-25s %s", key, req_info[i].help))
3382 add_line_text(view, buf, LINE_DEFAULT);
3386 add_line_text(view, "", LINE_DEFAULT);
3387 add_line_text(view, "External commands:", LINE_DEFAULT);
3390 for (i = 0; i < run_requests; i++) {
3391 struct run_request *req = get_run_request(REQ_NONE + i + 1);
3393 char cmd[SIZEOF_STR];
3400 key = get_key_name(req->key);
3402 key = "(no key defined)";
3404 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
3405 if (!string_format_from(cmd, &bufpos, "%s%s",
3406 argc ? " " : "", req->argv[argc]))
3409 if (!string_format(buf, " %-10s %-14s `%s`",
3410 keymap_table[req->keymap].name, key, cmd))
3413 add_line_text(view, buf, LINE_DEFAULT);
3419 static struct view_ops help_ops = {
3434 struct tree_stack_entry {
3435 struct tree_stack_entry *prev; /* Entry below this in the stack */
3436 unsigned long lineno; /* Line number to restore */
3437 char *name; /* Position of name in opt_path */
3440 /* The top of the path stack. */
3441 static struct tree_stack_entry *tree_stack = NULL;
3442 unsigned long tree_lineno = 0;
3445 pop_tree_stack_entry(void)
3447 struct tree_stack_entry *entry = tree_stack;
3449 tree_lineno = entry->lineno;
3451 tree_stack = entry->prev;
3456 push_tree_stack_entry(const char *name, unsigned long lineno)
3458 struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
3459 size_t pathlen = strlen(opt_path);
3464 entry->prev = tree_stack;
3465 entry->name = opt_path + pathlen;
3468 if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
3469 pop_tree_stack_entry();
3473 /* Move the current line to the first tree entry. */
3475 entry->lineno = lineno;
3478 /* Parse output from git-ls-tree(1):
3480 * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile
3481 * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README
3482 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
3483 * 100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38 web.conf
3486 #define SIZEOF_TREE_ATTR \
3487 STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t")
3489 #define TREE_UP_FORMAT "040000 tree %s\t.."
3492 tree_compare_entry(enum line_type type1, const char *name1,
3493 enum line_type type2, const char *name2)
3495 if (type1 != type2) {
3496 if (type1 == LINE_TREE_DIR)
3501 return strcmp(name1, name2);
3505 tree_path(struct line *line)
3507 const char *path = line->data;
3509 return path + SIZEOF_TREE_ATTR;
3513 tree_read(struct view *view, char *text)
3515 size_t textlen = text ? strlen(text) : 0;
3516 char buf[SIZEOF_STR];
3518 enum line_type type;
3519 bool first_read = view->lines == 0;
3523 if (textlen <= SIZEOF_TREE_ATTR)
3526 type = text[STRING_SIZE("100644 ")] == 't'
3527 ? LINE_TREE_DIR : LINE_TREE_FILE;
3530 /* Add path info line */
3531 if (!string_format(buf, "Directory path /%s", opt_path) ||
3532 !realloc_lines(view, view->line_size + 1) ||
3533 !add_line_text(view, buf, LINE_DEFAULT))
3536 /* Insert "link" to parent directory. */
3538 if (!string_format(buf, TREE_UP_FORMAT, view->ref) ||
3539 !realloc_lines(view, view->line_size + 1) ||
3540 !add_line_text(view, buf, LINE_TREE_DIR))
3545 /* Strip the path part ... */
3547 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
3548 size_t striplen = strlen(opt_path);
3549 char *path = text + SIZEOF_TREE_ATTR;
3551 if (pathlen > striplen)
3552 memmove(path, path + striplen,
3553 pathlen - striplen + 1);
3556 /* Skip "Directory ..." and ".." line. */
3557 for (pos = 1 + !!*opt_path; pos < view->lines; pos++) {
3558 struct line *line = &view->line[pos];
3559 const char *path1 = tree_path(line);
3560 char *path2 = text + SIZEOF_TREE_ATTR;
3561 int cmp = tree_compare_entry(line->type, path1, type, path2);
3566 text = strdup(text);
3570 if (view->lines > pos)
3571 memmove(&view->line[pos + 1], &view->line[pos],
3572 (view->lines - pos) * sizeof(*line));
3574 line = &view->line[pos];
3581 if (!add_line_text(view, text, type))
3584 if (tree_lineno > view->lineno) {
3585 view->lineno = tree_lineno;
3593 tree_request(struct view *view, enum request request, struct line *line)
3595 enum open_flags flags;
3598 case REQ_VIEW_BLAME:
3599 if (line->type != LINE_TREE_FILE) {
3600 report("Blame only supported for files");
3604 string_copy(opt_ref, view->vid);
3608 if (line->type != LINE_TREE_FILE) {
3609 report("Edit only supported for files");
3610 } else if (!is_head_commit(view->vid)) {
3611 report("Edit only supported for files in the current work tree");
3613 open_editor(TRUE, opt_file);
3617 case REQ_TREE_PARENT:
3619 /* quit view if at top of tree */
3620 return REQ_VIEW_CLOSE;
3623 line = &view->line[1];
3633 /* Cleanup the stack if the tree view is at a different tree. */
3634 while (!*opt_path && tree_stack)
3635 pop_tree_stack_entry();
3637 switch (line->type) {
3639 /* Depending on whether it is a subdir or parent (updir?) link
3640 * mangle the path buffer. */
3641 if (line == &view->line[1] && *opt_path) {
3642 pop_tree_stack_entry();
3645 const char *basename = tree_path(line);
3647 push_tree_stack_entry(basename, view->lineno);
3650 /* Trees and subtrees share the same ID, so they are not not
3651 * unique like blobs. */
3652 flags = OPEN_RELOAD;
3653 request = REQ_VIEW_TREE;
3656 case LINE_TREE_FILE:
3657 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
3658 request = REQ_VIEW_BLOB;
3665 open_view(view, request, flags);
3666 if (request == REQ_VIEW_TREE) {
3667 view->lineno = tree_lineno;
3674 tree_select(struct view *view, struct line *line)
3676 char *text = (char *)line->data + STRING_SIZE("100644 blob ");
3678 if (line->type == LINE_TREE_FILE) {
3679 string_copy_rev(ref_blob, text);
3680 string_format(opt_file, "%s%s", opt_path, tree_path(line));
3682 } else if (line->type != LINE_TREE_DIR) {
3686 string_copy_rev(view->ref, text);
3689 static struct view_ops tree_ops = {
3700 blob_read(struct view *view, char *line)
3704 return add_line_text(view, line, LINE_DEFAULT) != NULL;
3707 static struct view_ops blob_ops = {
3720 * Loading the blame view is a two phase job:
3722 * 1. File content is read either using opt_file from the
3723 * filesystem or using git-cat-file.
3724 * 2. Then blame information is incrementally added by
3725 * reading output from git-blame.
3728 struct blame_commit {
3729 char id[SIZEOF_REV]; /* SHA1 ID. */
3730 char title[128]; /* First line of the commit message. */
3731 char author[75]; /* Author of the commit. */
3732 struct tm time; /* Date from the author ident. */
3733 char filename[128]; /* Name of file. */
3737 struct blame_commit *commit;
3741 #define BLAME_CAT_FILE_CMD "git cat-file blob %s:%s"
3742 #define BLAME_INCREMENTAL_CMD "git blame --incremental %s -- %s"
3745 blame_open(struct view *view)
3747 char path[SIZEOF_STR];
3748 char ref[SIZEOF_STR] = "";
3750 if (sq_quote(path, 0, opt_file) >= sizeof(path))
3753 if (*opt_ref && sq_quote(ref, 0, opt_ref) >= sizeof(ref))
3756 if (*opt_ref || !init_io_fd(&view->io, fopen(opt_file, "r"))) {
3757 const char *id = *opt_ref ? ref : "HEAD";
3759 if (!run_io_format(&view->io, BLAME_CAT_FILE_CMD, id, path))
3763 setup_update(view, opt_file);
3764 string_format(view->ref, "%s ...", opt_file);
3769 static struct blame_commit *
3770 get_blame_commit(struct view *view, const char *id)
3774 for (i = 0; i < view->lines; i++) {
3775 struct blame *blame = view->line[i].data;
3780 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
3781 return blame->commit;
3785 struct blame_commit *commit = calloc(1, sizeof(*commit));
3788 string_ncopy(commit->id, id, SIZEOF_REV);
3794 parse_number(const char **posref, size_t *number, size_t min, size_t max)
3796 const char *pos = *posref;
3799 pos = strchr(pos + 1, ' ');
3800 if (!pos || !isdigit(pos[1]))
3802 *number = atoi(pos + 1);
3803 if (*number < min || *number > max)
3810 static struct blame_commit *
3811 parse_blame_commit(struct view *view, const char *text, int *blamed)
3813 struct blame_commit *commit;
3814 struct blame *blame;
3815 const char *pos = text + SIZEOF_REV - 1;
3819 if (strlen(text) <= SIZEOF_REV || *pos != ' ')
3822 if (!parse_number(&pos, &lineno, 1, view->lines) ||
3823 !parse_number(&pos, &group, 1, view->lines - lineno + 1))
3826 commit = get_blame_commit(view, text);
3832 struct line *line = &view->line[lineno + group - 1];
3835 blame->commit = commit;
3843 blame_read_file(struct view *view, const char *line, bool *read_file)
3846 char ref[SIZEOF_STR] = "";
3847 char path[SIZEOF_STR];
3850 if (view->lines == 0 && !view->parent)
3851 die("No blame exist for %s", view->vid);
3853 if (view->lines == 0 ||
3854 sq_quote(path, 0, opt_file) >= sizeof(path) ||
3855 (*opt_ref && sq_quote(ref, 0, opt_ref) >= sizeof(ref)) ||
3856 !run_io_format(&io, BLAME_INCREMENTAL_CMD, ref, path)) {
3857 report("Failed to load blame data");
3861 done_io(view->pipe);
3867 size_t linelen = strlen(line);
3868 struct blame *blame = malloc(sizeof(*blame) + linelen);
3870 blame->commit = NULL;
3871 strncpy(blame->text, line, linelen);
3872 blame->text[linelen] = 0;
3873 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
3878 match_blame_header(const char *name, char **line)
3880 size_t namelen = strlen(name);
3881 bool matched = !strncmp(name, *line, namelen);
3890 blame_read(struct view *view, char *line)
3892 static struct blame_commit *commit = NULL;
3893 static int blamed = 0;
3894 static time_t author_time;
3895 static bool read_file = TRUE;
3898 return blame_read_file(view, line, &read_file);
3905 string_format(view->ref, "%s", view->vid);
3906 if (view_is_displayed(view)) {
3907 update_view_title(view);
3908 redraw_view_from(view, 0);
3914 commit = parse_blame_commit(view, line, &blamed);
3915 string_format(view->ref, "%s %2d%%", view->vid,
3916 blamed * 100 / view->lines);
3918 } else if (match_blame_header("author ", &line)) {
3919 string_ncopy(commit->author, line, strlen(line));
3921 } else if (match_blame_header("author-time ", &line)) {
3922 author_time = (time_t) atol(line);
3924 } else if (match_blame_header("author-tz ", &line)) {
3927 tz = ('0' - line[1]) * 60 * 60 * 10;
3928 tz += ('0' - line[2]) * 60 * 60;
3929 tz += ('0' - line[3]) * 60;
3930 tz += ('0' - line[4]) * 60;
3936 gmtime_r(&author_time, &commit->time);
3938 } else if (match_blame_header("summary ", &line)) {
3939 string_ncopy(commit->title, line, strlen(line));
3941 } else if (match_blame_header("filename ", &line)) {
3942 string_ncopy(commit->filename, line, strlen(line));
3950 blame_draw(struct view *view, struct line *line, unsigned int lineno)
3952 struct blame *blame = line->data;
3953 struct tm *time = NULL;
3954 const char *id = NULL, *author = NULL;
3956 if (blame->commit && *blame->commit->filename) {
3957 id = blame->commit->id;
3958 author = blame->commit->author;
3959 time = &blame->commit->time;
3962 if (opt_date && draw_date(view, time))
3966 draw_field(view, LINE_MAIN_AUTHOR, author, opt_author_cols, TRUE))
3969 if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
3972 if (draw_lineno(view, lineno))
3975 draw_text(view, LINE_DEFAULT, blame->text, TRUE);
3980 blame_request(struct view *view, enum request request, struct line *line)
3982 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
3983 struct blame *blame = line->data;
3986 case REQ_VIEW_BLAME:
3987 if (!blame->commit || !strcmp(blame->commit->id, NULL_ID)) {
3988 report("Commit ID unknown");
3991 string_copy(opt_ref, blame->commit->id);
3992 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
3996 if (!blame->commit) {
3997 report("No commit loaded yet");
4001 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
4002 !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
4005 if (!strcmp(blame->commit->id, NULL_ID)) {
4006 char path[SIZEOF_STR];
4008 if (sq_quote(path, 0, view->vid) >= sizeof(path))
4010 string_format(opt_cmd, "git diff-index --root --patch-with-stat -C -M --cached HEAD -- %s 2>/dev/null", path);
4013 open_view(view, REQ_VIEW_DIFF, flags);
4024 blame_grep(struct view *view, struct line *line)
4026 struct blame *blame = line->data;
4027 struct blame_commit *commit = blame->commit;
4030 #define MATCH(text, on) \
4031 (on && *text && regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4034 char buf[DATE_COLS + 1];
4036 if (MATCH(commit->title, 1) ||
4037 MATCH(commit->author, opt_author) ||
4038 MATCH(commit->id, opt_date))
4041 if (strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time) &&
4046 return MATCH(blame->text, 1);
4052 blame_select(struct view *view, struct line *line)
4054 struct blame *blame = line->data;
4055 struct blame_commit *commit = blame->commit;
4060 if (!strcmp(commit->id, NULL_ID))
4061 string_ncopy(ref_commit, "HEAD", 4);
4063 string_copy_rev(ref_commit, commit->id);
4066 static struct view_ops blame_ops = {
4084 char rev[SIZEOF_REV];
4085 char name[SIZEOF_STR];
4089 char rev[SIZEOF_REV];
4090 char name[SIZEOF_STR];
4094 static char status_onbranch[SIZEOF_STR];
4095 static struct status stage_status;
4096 static enum line_type stage_line_type;
4097 static size_t stage_chunks;
4098 static int *stage_chunk;
4100 /* This should work even for the "On branch" line. */
4102 status_has_none(struct view *view, struct line *line)
4104 return line < view->line + view->lines && !line[1].data;
4107 /* Get fields from the diff line:
4108 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
4111 status_get_diff(struct status *file, const char *buf, size_t bufsize)
4113 const char *old_mode = buf + 1;
4114 const char *new_mode = buf + 8;
4115 const char *old_rev = buf + 15;
4116 const char *new_rev = buf + 56;
4117 const char *status = buf + 97;
4120 old_mode[-1] != ':' ||
4121 new_mode[-1] != ' ' ||
4122 old_rev[-1] != ' ' ||
4123 new_rev[-1] != ' ' ||
4127 file->status = *status;
4129 string_copy_rev(file->old.rev, old_rev);
4130 string_copy_rev(file->new.rev, new_rev);
4132 file->old.mode = strtoul(old_mode, NULL, 8);
4133 file->new.mode = strtoul(new_mode, NULL, 8);
4135 file->old.name[0] = file->new.name[0] = 0;
4141 status_run(struct view *view, const char cmd[], char status, enum line_type type)
4143 struct status *file = NULL;
4144 struct status *unmerged = NULL;
4145 char buf[SIZEOF_STR * 4];
4149 pipe = popen(cmd, "r");
4153 add_line_data(view, NULL, type);
4155 while (!feof(pipe) && !ferror(pipe)) {
4159 readsize = fread(buf + bufsize, 1, sizeof(buf) - bufsize, pipe);
4162 bufsize += readsize;
4164 /* Process while we have NUL chars. */
4165 while ((sep = memchr(buf, 0, bufsize))) {
4166 size_t sepsize = sep - buf + 1;
4169 if (!realloc_lines(view, view->line_size + 1))
4172 file = calloc(1, sizeof(*file));
4176 add_line_data(view, file, type);
4179 /* Parse diff info part. */
4181 file->status = status;
4183 string_copy(file->old.rev, NULL_ID);
4185 } else if (!file->status) {
4186 if (!status_get_diff(file, buf, sepsize))
4190 memmove(buf, sep + 1, bufsize);
4192 sep = memchr(buf, 0, bufsize);
4195 sepsize = sep - buf + 1;
4197 /* Collapse all 'M'odified entries that
4198 * follow a associated 'U'nmerged entry.
4200 if (file->status == 'U') {
4203 } else if (unmerged) {
4204 int collapse = !strcmp(buf, unmerged->new.name);
4215 /* Grab the old name for rename/copy. */
4216 if (!*file->old.name &&
4217 (file->status == 'R' || file->status == 'C')) {
4218 sepsize = sep - buf + 1;
4219 string_ncopy(file->old.name, buf, sepsize);
4221 memmove(buf, sep + 1, bufsize);
4223 sep = memchr(buf, 0, bufsize);
4226 sepsize = sep - buf + 1;
4229 /* git-ls-files just delivers a NUL separated
4230 * list of file names similar to the second half
4231 * of the git-diff-* output. */
4232 string_ncopy(file->new.name, buf, sepsize);
4233 if (!*file->old.name)
4234 string_copy(file->old.name, file->new.name);
4236 memmove(buf, sep + 1, bufsize);
4247 if (!view->line[view->lines - 1].data)
4248 add_line_data(view, NULL, LINE_STAT_NONE);
4254 /* Don't show unmerged entries in the staged section. */
4255 #define STATUS_DIFF_INDEX_CMD "git diff-index -z --diff-filter=ACDMRTXB --cached -M HEAD"
4256 #define STATUS_DIFF_FILES_CMD "git diff-files -z"
4257 #define STATUS_LIST_OTHER_CMD \
4258 "git ls-files -z --others --exclude-standard"
4259 #define STATUS_LIST_NO_HEAD_CMD \
4260 "git ls-files -z --cached --exclude-standard"
4262 #define STATUS_DIFF_INDEX_SHOW_CMD \
4263 "git diff-index --root --patch-with-stat -C -M --cached HEAD -- %s %s 2>/dev/null"
4265 #define STATUS_DIFF_FILES_SHOW_CMD \
4266 "git diff-files --root --patch-with-stat -C -M -- %s %s 2>/dev/null"
4268 #define STATUS_DIFF_NO_HEAD_SHOW_CMD \
4269 "git diff --no-color --patch-with-stat /dev/null %s 2>/dev/null"
4271 /* First parse staged info using git-diff-index(1), then parse unstaged
4272 * info using git-diff-files(1), and finally untracked files using
4273 * git-ls-files(1). */
4275 status_open(struct view *view)
4277 unsigned long prev_lineno = view->lineno;
4281 if (!realloc_lines(view, view->line_size + 7))
4284 add_line_data(view, NULL, LINE_STAT_HEAD);
4285 if (is_initial_commit())
4286 string_copy(status_onbranch, "Initial commit");
4287 else if (!*opt_head)
4288 string_copy(status_onbranch, "Not currently on any branch");
4289 else if (!string_format(status_onbranch, "On branch %s", opt_head))
4292 system("git update-index -q --refresh >/dev/null 2>/dev/null");
4294 if (is_initial_commit()) {
4295 if (!status_run(view, STATUS_LIST_NO_HEAD_CMD, 'A', LINE_STAT_STAGED))
4297 } else if (!status_run(view, STATUS_DIFF_INDEX_CMD, 0, LINE_STAT_STAGED)) {
4301 if (!status_run(view, STATUS_DIFF_FILES_CMD, 0, LINE_STAT_UNSTAGED) ||
4302 !status_run(view, STATUS_LIST_OTHER_CMD, '?', LINE_STAT_UNTRACKED))
4305 /* If all went well restore the previous line number to stay in
4306 * the context or select a line with something that can be
4308 if (prev_lineno >= view->lines)
4309 prev_lineno = view->lines - 1;
4310 while (prev_lineno < view->lines && !view->line[prev_lineno].data)
4312 while (prev_lineno > 0 && !view->line[prev_lineno].data)
4315 /* If the above fails, always skip the "On branch" line. */
4316 if (prev_lineno < view->lines)
4317 view->lineno = prev_lineno;
4321 if (view->lineno < view->offset)
4322 view->offset = view->lineno;
4323 else if (view->offset + view->height <= view->lineno)
4324 view->offset = view->lineno - view->height + 1;
4330 status_draw(struct view *view, struct line *line, unsigned int lineno)
4332 struct status *status = line->data;
4333 enum line_type type;
4337 switch (line->type) {
4338 case LINE_STAT_STAGED:
4339 type = LINE_STAT_SECTION;
4340 text = "Changes to be committed:";
4343 case LINE_STAT_UNSTAGED:
4344 type = LINE_STAT_SECTION;
4345 text = "Changed but not updated:";
4348 case LINE_STAT_UNTRACKED:
4349 type = LINE_STAT_SECTION;
4350 text = "Untracked files:";
4353 case LINE_STAT_NONE:
4354 type = LINE_DEFAULT;
4355 text = " (no files)";
4358 case LINE_STAT_HEAD:
4359 type = LINE_STAT_HEAD;
4360 text = status_onbranch;
4367 static char buf[] = { '?', ' ', ' ', ' ', 0 };
4369 buf[0] = status->status;
4370 if (draw_text(view, line->type, buf, TRUE))
4372 type = LINE_DEFAULT;
4373 text = status->new.name;
4376 draw_text(view, type, text, TRUE);
4381 status_enter(struct view *view, struct line *line)
4383 struct status *status = line->data;
4384 char oldpath[SIZEOF_STR] = "";
4385 char newpath[SIZEOF_STR] = "";
4388 enum open_flags split;
4390 if (line->type == LINE_STAT_NONE ||
4391 (!status && line[1].type == LINE_STAT_NONE)) {
4392 report("No file to diff");
4397 if (sq_quote(oldpath, 0, status->old.name) >= sizeof(oldpath))
4399 /* Diffs for unmerged entries are empty when pasing the
4400 * new path, so leave it empty. */
4401 if (status->status != 'U' &&
4402 sq_quote(newpath, 0, status->new.name) >= sizeof(newpath))
4407 line->type != LINE_STAT_UNTRACKED &&
4408 !string_format_from(opt_cmd, &cmdsize, "cd %s;", opt_cdup))
4411 switch (line->type) {
4412 case LINE_STAT_STAGED:
4413 if (is_initial_commit()) {
4414 if (!string_format_from(opt_cmd, &cmdsize,
4415 STATUS_DIFF_NO_HEAD_SHOW_CMD,
4419 if (!string_format_from(opt_cmd, &cmdsize,
4420 STATUS_DIFF_INDEX_SHOW_CMD,
4426 info = "Staged changes to %s";
4428 info = "Staged changes";
4431 case LINE_STAT_UNSTAGED:
4432 if (!string_format_from(opt_cmd, &cmdsize,
4433 STATUS_DIFF_FILES_SHOW_CMD, oldpath, newpath))
4436 info = "Unstaged changes to %s";
4438 info = "Unstaged changes";
4441 case LINE_STAT_UNTRACKED:
4446 report("No file to show");
4450 if (!suffixcmp(status->new.name, -1, "/")) {
4451 report("Cannot display a directory");
4455 opt_pipe = fopen(status->new.name, "r");
4456 info = "Untracked file %s";
4459 case LINE_STAT_HEAD:
4463 die("line type %d not handled in switch", line->type);
4466 split = view_is_displayed(view) ? OPEN_SPLIT : 0;
4467 open_view(view, REQ_VIEW_STAGE, OPEN_RELOAD | split);
4468 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
4470 stage_status = *status;
4472 memset(&stage_status, 0, sizeof(stage_status));
4475 stage_line_type = line->type;
4477 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
4484 status_exists(struct status *status, enum line_type type)
4486 struct view *view = VIEW(REQ_VIEW_STATUS);
4489 for (line = view->line; line < view->line + view->lines; line++) {
4490 struct status *pos = line->data;
4492 if (line->type == type && pos &&
4493 !strcmp(status->new.name, pos->new.name))
4502 status_update_prepare(enum line_type type)
4504 char cmd[SIZEOF_STR];
4508 type != LINE_STAT_UNTRACKED &&
4509 !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
4513 case LINE_STAT_STAGED:
4514 string_add(cmd, cmdsize, "git update-index -z --index-info");
4517 case LINE_STAT_UNSTAGED:
4518 case LINE_STAT_UNTRACKED:
4519 string_add(cmd, cmdsize, "git update-index -z --add --remove --stdin");
4523 die("line type %d not handled in switch", type);
4526 return popen(cmd, "w");
4530 status_update_write(FILE *pipe, struct status *status, enum line_type type)
4532 char buf[SIZEOF_STR];
4537 case LINE_STAT_STAGED:
4538 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
4541 status->old.name, 0))
4545 case LINE_STAT_UNSTAGED:
4546 case LINE_STAT_UNTRACKED:
4547 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
4552 die("line type %d not handled in switch", type);
4555 while (!ferror(pipe) && written < bufsize) {
4556 written += fwrite(buf + written, 1, bufsize - written, pipe);
4559 return written == bufsize;
4563 status_update_file(struct status *status, enum line_type type)
4565 FILE *pipe = status_update_prepare(type);
4571 result = status_update_write(pipe, status, type);
4577 status_update_files(struct view *view, struct line *line)
4579 FILE *pipe = status_update_prepare(line->type);
4581 struct line *pos = view->line + view->lines;
4588 for (pos = line; pos < view->line + view->lines && pos->data; pos++)
4591 for (file = 0, done = 0; result && file < files; line++, file++) {
4592 int almost_done = file * 100 / files;
4594 if (almost_done > done) {
4596 string_format(view->ref, "updating file %u of %u (%d%% done)",
4598 update_view_title(view);
4600 result = status_update_write(pipe, line->data, line->type);
4608 status_update(struct view *view)
4610 struct line *line = &view->line[view->lineno];
4612 assert(view->lines);
4615 /* This should work even for the "On branch" line. */
4616 if (line < view->line + view->lines && !line[1].data) {
4617 report("Nothing to update");
4621 if (!status_update_files(view, line + 1)) {
4622 report("Failed to update file status");
4626 } else if (!status_update_file(line->data, line->type)) {
4627 report("Failed to update file status");
4635 status_revert(struct status *status, enum line_type type, bool has_none)
4637 if (!status || type != LINE_STAT_UNSTAGED) {
4638 if (type == LINE_STAT_STAGED) {
4639 report("Cannot revert changes to staged files");
4640 } else if (type == LINE_STAT_UNTRACKED) {
4641 report("Cannot revert changes to untracked files");
4642 } else if (has_none) {
4643 report("Nothing to revert");
4645 report("Cannot revert changes to multiple files");
4650 const char *checkout_argv[] = {
4651 "git", "checkout", "--", status->old.name, NULL
4654 if (!prompt_yesno("Are you sure you want to overwrite any changes?"))
4656 return run_io_fg(checkout_argv, opt_cdup);
4661 status_request(struct view *view, enum request request, struct line *line)
4663 struct status *status = line->data;
4666 case REQ_STATUS_UPDATE:
4667 if (!status_update(view))
4671 case REQ_STATUS_REVERT:
4672 if (!status_revert(status, line->type, status_has_none(view, line)))
4676 case REQ_STATUS_MERGE:
4677 if (!status || status->status != 'U') {
4678 report("Merging only possible for files with unmerged status ('U').");
4681 open_mergetool(status->new.name);
4687 if (status->status == 'D') {
4688 report("File has been deleted.");
4692 open_editor(status->status != '?', status->new.name);
4695 case REQ_VIEW_BLAME:
4697 string_copy(opt_file, status->new.name);
4703 /* After returning the status view has been split to
4704 * show the stage view. No further reloading is
4706 status_enter(view, line);
4710 /* Simply reload the view. */
4717 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
4723 status_select(struct view *view, struct line *line)
4725 struct status *status = line->data;
4726 char file[SIZEOF_STR] = "all files";
4730 if (status && !string_format(file, "'%s'", status->new.name))
4733 if (!status && line[1].type == LINE_STAT_NONE)
4736 switch (line->type) {
4737 case LINE_STAT_STAGED:
4738 text = "Press %s to unstage %s for commit";
4741 case LINE_STAT_UNSTAGED:
4742 text = "Press %s to stage %s for commit";
4745 case LINE_STAT_UNTRACKED:
4746 text = "Press %s to stage %s for addition";
4749 case LINE_STAT_HEAD:
4750 case LINE_STAT_NONE:
4751 text = "Nothing to update";
4755 die("line type %d not handled in switch", line->type);
4758 if (status && status->status == 'U') {
4759 text = "Press %s to resolve conflict in %s";
4760 key = get_key(REQ_STATUS_MERGE);
4763 key = get_key(REQ_STATUS_UPDATE);
4766 string_format(view->ref, text, key, file);
4770 status_grep(struct view *view, struct line *line)
4772 struct status *status = line->data;
4773 enum { S_STATUS, S_NAME, S_END } state;
4780 for (state = S_STATUS; state < S_END; state++) {
4784 case S_NAME: text = status->new.name; break;
4786 buf[0] = status->status;
4794 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4801 static struct view_ops status_ops = {
4813 stage_diff_line(FILE *pipe, struct line *line)
4815 const char *buf = line->data;
4816 size_t bufsize = strlen(buf);
4819 while (!ferror(pipe) && written < bufsize) {
4820 written += fwrite(buf + written, 1, bufsize - written, pipe);
4825 return written == bufsize;
4829 stage_diff_write(FILE *pipe, struct line *line, struct line *end)
4831 while (line < end) {
4832 if (!stage_diff_line(pipe, line++))
4834 if (line->type == LINE_DIFF_CHUNK ||
4835 line->type == LINE_DIFF_HEADER)
4842 static struct line *
4843 stage_diff_find(struct view *view, struct line *line, enum line_type type)
4845 for (; view->line < line; line--)
4846 if (line->type == type)
4853 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
4855 char cmd[SIZEOF_STR];
4857 struct line *diff_hdr;
4860 diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
4865 !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
4868 if (!string_format_from(cmd, &cmdsize,
4869 "git apply --whitespace=nowarn %s %s - && "
4870 "git update-index -q --unmerged --refresh 2>/dev/null",
4871 revert ? "" : "--cached",
4872 revert || stage_line_type == LINE_STAT_STAGED ? "-R" : ""))
4875 pipe = popen(cmd, "w");
4879 if (!stage_diff_write(pipe, diff_hdr, chunk) ||
4880 !stage_diff_write(pipe, chunk, view->line + view->lines))
4885 return chunk ? TRUE : FALSE;
4889 stage_update(struct view *view, struct line *line)
4891 struct line *chunk = NULL;
4893 if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
4894 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
4897 if (!stage_apply_chunk(view, chunk, FALSE)) {
4898 report("Failed to apply chunk");
4902 } else if (!stage_status.status) {
4903 view = VIEW(REQ_VIEW_STATUS);
4905 for (line = view->line; line < view->line + view->lines; line++)
4906 if (line->type == stage_line_type)
4909 if (!status_update_files(view, line + 1)) {
4910 report("Failed to update files");
4914 } else if (!status_update_file(&stage_status, stage_line_type)) {
4915 report("Failed to update file");
4923 stage_revert(struct view *view, struct line *line)
4925 struct line *chunk = NULL;
4927 if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
4928 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
4931 if (!prompt_yesno("Are you sure you want to revert changes?"))
4934 if (!stage_apply_chunk(view, chunk, TRUE)) {
4935 report("Failed to revert chunk");
4941 return status_revert(stage_status.status ? &stage_status : NULL,
4942 stage_line_type, FALSE);
4948 stage_next(struct view *view, struct line *line)
4952 if (!stage_chunks) {
4953 static size_t alloc = 0;
4956 for (line = view->line; line < view->line + view->lines; line++) {
4957 if (line->type != LINE_DIFF_CHUNK)
4960 tmp = realloc_items(stage_chunk, &alloc,
4961 stage_chunks, sizeof(*tmp));
4963 report("Allocation failure");
4968 stage_chunk[stage_chunks++] = line - view->line;
4972 for (i = 0; i < stage_chunks; i++) {
4973 if (stage_chunk[i] > view->lineno) {
4974 do_scroll_view(view, stage_chunk[i] - view->lineno);
4975 report("Chunk %d of %d", i + 1, stage_chunks);
4980 report("No next chunk found");
4984 stage_request(struct view *view, enum request request, struct line *line)
4987 case REQ_STATUS_UPDATE:
4988 if (!stage_update(view, line))
4992 case REQ_STATUS_REVERT:
4993 if (!stage_revert(view, line))
4997 case REQ_STAGE_NEXT:
4998 if (stage_line_type == LINE_STAT_UNTRACKED) {
4999 report("File is untracked; press %s to add",
5000 get_key(REQ_STATUS_UPDATE));
5003 stage_next(view, line);
5007 if (!stage_status.new.name[0])
5009 if (stage_status.status == 'D') {
5010 report("File has been deleted.");
5014 open_editor(stage_status.status != '?', stage_status.new.name);
5018 /* Reload everything ... */
5021 case REQ_VIEW_BLAME:
5022 if (stage_status.new.name[0]) {
5023 string_copy(opt_file, stage_status.new.name);
5029 return pager_request(view, request, line);
5035 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD | OPEN_NOMAXIMIZE);
5037 /* Check whether the staged entry still exists, and close the
5038 * stage view if it doesn't. */
5039 if (!status_exists(&stage_status, stage_line_type))
5040 return REQ_VIEW_CLOSE;
5042 if (stage_line_type == LINE_STAT_UNTRACKED) {
5043 if (!suffixcmp(stage_status.new.name, -1, "/")) {
5044 report("Cannot display a directory");
5048 opt_pipe = fopen(stage_status.new.name, "r");
5050 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
5055 static struct view_ops stage_ops = {
5071 char id[SIZEOF_REV]; /* SHA1 ID. */
5072 char title[128]; /* First line of the commit message. */
5073 char author[75]; /* Author of the commit. */
5074 struct tm time; /* Date from the author ident. */
5075 struct ref **refs; /* Repository references. */
5076 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
5077 size_t graph_size; /* The width of the graph array. */
5078 bool has_parents; /* Rewritten --parents seen. */
5081 /* Size of rev graph with no "padding" columns */
5082 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
5085 struct rev_graph *prev, *next, *parents;
5086 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
5088 struct commit *commit;
5090 unsigned int boundary:1;
5093 /* Parents of the commit being visualized. */
5094 static struct rev_graph graph_parents[4];
5096 /* The current stack of revisions on the graph. */
5097 static struct rev_graph graph_stacks[4] = {
5098 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
5099 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
5100 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
5101 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
5105 graph_parent_is_merge(struct rev_graph *graph)
5107 return graph->parents->size > 1;
5111 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
5113 struct commit *commit = graph->commit;
5115 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
5116 commit->graph[commit->graph_size++] = symbol;
5120 clear_rev_graph(struct rev_graph *graph)
5122 graph->boundary = 0;
5123 graph->size = graph->pos = 0;
5124 graph->commit = NULL;
5125 memset(graph->parents, 0, sizeof(*graph->parents));
5129 done_rev_graph(struct rev_graph *graph)
5131 if (graph_parent_is_merge(graph) &&
5132 graph->pos < graph->size - 1 &&
5133 graph->next->size == graph->size + graph->parents->size - 1) {
5134 size_t i = graph->pos + graph->parents->size - 1;
5136 graph->commit->graph_size = i * 2;
5137 while (i < graph->next->size - 1) {
5138 append_to_rev_graph(graph, ' ');
5139 append_to_rev_graph(graph, '\\');
5144 clear_rev_graph(graph);
5148 push_rev_graph(struct rev_graph *graph, const char *parent)
5152 /* "Collapse" duplicate parents lines.
5154 * FIXME: This needs to also update update the drawn graph but
5155 * for now it just serves as a method for pruning graph lines. */
5156 for (i = 0; i < graph->size; i++)
5157 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
5160 if (graph->size < SIZEOF_REVITEMS) {
5161 string_copy_rev(graph->rev[graph->size++], parent);
5166 get_rev_graph_symbol(struct rev_graph *graph)
5170 if (graph->boundary)
5171 symbol = REVGRAPH_BOUND;
5172 else if (graph->parents->size == 0)
5173 symbol = REVGRAPH_INIT;
5174 else if (graph_parent_is_merge(graph))
5175 symbol = REVGRAPH_MERGE;
5176 else if (graph->pos >= graph->size)
5177 symbol = REVGRAPH_BRANCH;
5179 symbol = REVGRAPH_COMMIT;
5185 draw_rev_graph(struct rev_graph *graph)
5188 chtype separator, line;
5190 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
5191 static struct rev_filler fillers[] = {
5197 chtype symbol = get_rev_graph_symbol(graph);
5198 struct rev_filler *filler;
5201 if (opt_line_graphics)
5202 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
5204 filler = &fillers[DEFAULT];
5206 for (i = 0; i < graph->pos; i++) {
5207 append_to_rev_graph(graph, filler->line);
5208 if (graph_parent_is_merge(graph->prev) &&
5209 graph->prev->pos == i)
5210 filler = &fillers[RSHARP];
5212 append_to_rev_graph(graph, filler->separator);
5215 /* Place the symbol for this revision. */
5216 append_to_rev_graph(graph, symbol);
5218 if (graph->prev->size > graph->size)
5219 filler = &fillers[RDIAG];
5221 filler = &fillers[DEFAULT];
5225 for (; i < graph->size; i++) {
5226 append_to_rev_graph(graph, filler->separator);
5227 append_to_rev_graph(graph, filler->line);
5228 if (graph_parent_is_merge(graph->prev) &&
5229 i < graph->prev->pos + graph->parents->size)
5230 filler = &fillers[RSHARP];
5231 if (graph->prev->size > graph->size)
5232 filler = &fillers[LDIAG];
5235 if (graph->prev->size > graph->size) {
5236 append_to_rev_graph(graph, filler->separator);
5237 if (filler->line != ' ')
5238 append_to_rev_graph(graph, filler->line);
5242 /* Prepare the next rev graph */
5244 prepare_rev_graph(struct rev_graph *graph)
5248 /* First, traverse all lines of revisions up to the active one. */
5249 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
5250 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
5253 push_rev_graph(graph->next, graph->rev[graph->pos]);
5256 /* Interleave the new revision parent(s). */
5257 for (i = 0; !graph->boundary && i < graph->parents->size; i++)
5258 push_rev_graph(graph->next, graph->parents->rev[i]);
5260 /* Lastly, put any remaining revisions. */
5261 for (i = graph->pos + 1; i < graph->size; i++)
5262 push_rev_graph(graph->next, graph->rev[i]);
5266 update_rev_graph(struct rev_graph *graph)
5268 /* If this is the finalizing update ... */
5270 prepare_rev_graph(graph);
5272 /* Graph visualization needs a one rev look-ahead,
5273 * so the first update doesn't visualize anything. */
5274 if (!graph->prev->commit)
5277 draw_rev_graph(graph->prev);
5278 done_rev_graph(graph->prev->prev);
5287 main_draw(struct view *view, struct line *line, unsigned int lineno)
5289 struct commit *commit = line->data;
5291 if (!*commit->author)
5294 if (opt_date && draw_date(view, &commit->time))
5298 draw_field(view, LINE_MAIN_AUTHOR, commit->author, opt_author_cols, TRUE))
5301 if (opt_rev_graph && commit->graph_size &&
5302 draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
5305 if (opt_show_refs && commit->refs) {
5309 enum line_type type;
5311 if (commit->refs[i]->head)
5312 type = LINE_MAIN_HEAD;
5313 else if (commit->refs[i]->ltag)
5314 type = LINE_MAIN_LOCAL_TAG;
5315 else if (commit->refs[i]->tag)
5316 type = LINE_MAIN_TAG;
5317 else if (commit->refs[i]->tracked)
5318 type = LINE_MAIN_TRACKED;
5319 else if (commit->refs[i]->remote)
5320 type = LINE_MAIN_REMOTE;
5322 type = LINE_MAIN_REF;
5324 if (draw_text(view, type, "[", TRUE) ||
5325 draw_text(view, type, commit->refs[i]->name, TRUE) ||
5326 draw_text(view, type, "]", TRUE))
5329 if (draw_text(view, LINE_DEFAULT, " ", TRUE))
5331 } while (commit->refs[i++]->next);
5334 draw_text(view, LINE_DEFAULT, commit->title, TRUE);
5338 /* Reads git log --pretty=raw output and parses it into the commit struct. */
5340 main_read(struct view *view, char *line)
5342 static struct rev_graph *graph = graph_stacks;
5343 enum line_type type;
5344 struct commit *commit;
5349 if (!view->lines && !view->parent)
5350 die("No revisions match the given arguments.");
5351 if (view->lines > 0) {
5352 commit = view->line[view->lines - 1].data;
5353 if (!*commit->author) {
5356 graph->commit = NULL;
5359 update_rev_graph(graph);
5361 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
5362 clear_rev_graph(&graph_stacks[i]);
5366 type = get_line_type(line);
5367 if (type == LINE_COMMIT) {
5368 commit = calloc(1, sizeof(struct commit));
5372 line += STRING_SIZE("commit ");
5374 graph->boundary = 1;
5378 string_copy_rev(commit->id, line);
5379 commit->refs = get_refs(commit->id);
5380 graph->commit = commit;
5381 add_line_data(view, commit, LINE_MAIN_COMMIT);
5383 while ((line = strchr(line, ' '))) {
5385 push_rev_graph(graph->parents, line);
5386 commit->has_parents = TRUE;
5393 commit = view->line[view->lines - 1].data;
5397 if (commit->has_parents)
5399 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
5404 /* Parse author lines where the name may be empty:
5405 * author <email@address.tld> 1138474660 +0100
5407 char *ident = line + STRING_SIZE("author ");
5408 char *nameend = strchr(ident, '<');
5409 char *emailend = strchr(ident, '>');
5411 if (!nameend || !emailend)
5414 update_rev_graph(graph);
5415 graph = graph->next;
5417 *nameend = *emailend = 0;
5418 ident = chomp_string(ident);
5420 ident = chomp_string(nameend + 1);
5425 string_ncopy(commit->author, ident, strlen(ident));
5427 /* Parse epoch and timezone */
5428 if (emailend[1] == ' ') {
5429 char *secs = emailend + 2;
5430 char *zone = strchr(secs, ' ');
5431 time_t time = (time_t) atol(secs);
5433 if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
5437 tz = ('0' - zone[1]) * 60 * 60 * 10;
5438 tz += ('0' - zone[2]) * 60 * 60;
5439 tz += ('0' - zone[3]) * 60;
5440 tz += ('0' - zone[4]) * 60;
5448 gmtime_r(&time, &commit->time);
5453 /* Fill in the commit title if it has not already been set. */
5454 if (commit->title[0])
5457 /* Require titles to start with a non-space character at the
5458 * offset used by git log. */
5459 if (strncmp(line, " ", 4))
5462 /* Well, if the title starts with a whitespace character,
5463 * try to be forgiving. Otherwise we end up with no title. */
5464 while (isspace(*line))
5468 /* FIXME: More graceful handling of titles; append "..." to
5469 * shortened titles, etc. */
5471 string_ncopy(commit->title, line, strlen(line));
5478 main_request(struct view *view, enum request request, struct line *line)
5480 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
5484 open_view(view, REQ_VIEW_DIFF, flags);
5488 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
5498 grep_refs(struct ref **refs, regex_t *regex)
5506 if (regexec(regex, refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
5508 } while (refs[i++]->next);
5514 main_grep(struct view *view, struct line *line)
5516 struct commit *commit = line->data;
5517 enum { S_TITLE, S_AUTHOR, S_DATE, S_REFS, S_END } state;
5518 char buf[DATE_COLS + 1];
5521 for (state = S_TITLE; state < S_END; state++) {
5525 case S_TITLE: text = commit->title; break;
5529 text = commit->author;
5534 if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
5541 if (grep_refs(commit->refs, view->regex) == TRUE)
5548 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
5556 main_select(struct view *view, struct line *line)
5558 struct commit *commit = line->data;
5560 string_copy_rev(view->ref, commit->id);
5561 string_copy_rev(ref_commit, view->ref);
5564 static struct view_ops main_ops = {
5576 * Unicode / UTF-8 handling
5578 * NOTE: Much of the following code for dealing with unicode is derived from
5579 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
5580 * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
5583 /* I've (over)annotated a lot of code snippets because I am not entirely
5584 * confident that the approach taken by this small UTF-8 interface is correct.
5588 unicode_width(unsigned long c)
5591 (c <= 0x115f /* Hangul Jamo */
5594 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
5596 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
5597 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
5598 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
5599 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
5600 || (c >= 0xffe0 && c <= 0xffe6)
5601 || (c >= 0x20000 && c <= 0x2fffd)
5602 || (c >= 0x30000 && c <= 0x3fffd)))
5606 return opt_tab_size;
5611 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
5612 * Illegal bytes are set one. */
5613 static const unsigned char utf8_bytes[256] = {
5614 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,
5615 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,
5616 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,
5617 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,
5618 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,
5619 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,
5620 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,
5621 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,
5624 /* Decode UTF-8 multi-byte representation into a unicode character. */
5625 static inline unsigned long
5626 utf8_to_unicode(const char *string, size_t length)
5628 unsigned long unicode;
5632 unicode = string[0];
5635 unicode = (string[0] & 0x1f) << 6;
5636 unicode += (string[1] & 0x3f);
5639 unicode = (string[0] & 0x0f) << 12;
5640 unicode += ((string[1] & 0x3f) << 6);
5641 unicode += (string[2] & 0x3f);
5644 unicode = (string[0] & 0x0f) << 18;
5645 unicode += ((string[1] & 0x3f) << 12);
5646 unicode += ((string[2] & 0x3f) << 6);
5647 unicode += (string[3] & 0x3f);
5650 unicode = (string[0] & 0x0f) << 24;
5651 unicode += ((string[1] & 0x3f) << 18);
5652 unicode += ((string[2] & 0x3f) << 12);
5653 unicode += ((string[3] & 0x3f) << 6);
5654 unicode += (string[4] & 0x3f);
5657 unicode = (string[0] & 0x01) << 30;
5658 unicode += ((string[1] & 0x3f) << 24);
5659 unicode += ((string[2] & 0x3f) << 18);
5660 unicode += ((string[3] & 0x3f) << 12);
5661 unicode += ((string[4] & 0x3f) << 6);
5662 unicode += (string[5] & 0x3f);
5665 die("Invalid unicode length");
5668 /* Invalid characters could return the special 0xfffd value but NUL
5669 * should be just as good. */
5670 return unicode > 0xffff ? 0 : unicode;
5673 /* Calculates how much of string can be shown within the given maximum width
5674 * and sets trimmed parameter to non-zero value if all of string could not be
5675 * shown. If the reserve flag is TRUE, it will reserve at least one
5676 * trailing character, which can be useful when drawing a delimiter.
5678 * Returns the number of bytes to output from string to satisfy max_width. */
5680 utf8_length(const char *string, int *width, size_t max_width, int *trimmed, bool reserve)
5682 const char *start = string;
5683 const char *end = strchr(string, '\0');
5684 unsigned char last_bytes = 0;
5685 size_t last_ucwidth = 0;
5690 while (string < end) {
5691 int c = *(unsigned char *) string;
5692 unsigned char bytes = utf8_bytes[c];
5694 unsigned long unicode;
5696 if (string + bytes > end)
5699 /* Change representation to figure out whether
5700 * it is a single- or double-width character. */
5702 unicode = utf8_to_unicode(string, bytes);
5703 /* FIXME: Graceful handling of invalid unicode character. */
5707 ucwidth = unicode_width(unicode);
5709 if (*width > max_width) {
5712 if (reserve && *width == max_width) {
5713 string -= last_bytes;
5714 *width -= last_ucwidth;
5721 last_ucwidth = ucwidth;
5724 return string - start;
5732 /* Whether or not the curses interface has been initialized. */
5733 static bool cursed = FALSE;
5735 /* The status window is used for polling keystrokes. */
5736 static WINDOW *status_win;
5738 static bool status_empty = TRUE;
5740 /* Update status and title window. */
5742 report(const char *msg, ...)
5744 struct view *view = display[current_view];
5750 char buf[SIZEOF_STR];
5753 va_start(args, msg);
5754 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
5755 buf[sizeof(buf) - 1] = 0;
5756 buf[sizeof(buf) - 2] = '.';
5757 buf[sizeof(buf) - 3] = '.';
5758 buf[sizeof(buf) - 4] = '.';
5764 if (!status_empty || *msg) {
5767 va_start(args, msg);
5769 wmove(status_win, 0, 0);
5771 vwprintw(status_win, msg, args);
5772 status_empty = FALSE;
5774 status_empty = TRUE;
5776 wclrtoeol(status_win);
5777 wrefresh(status_win);
5782 update_view_title(view);
5783 update_display_cursor(view);
5786 /* Controls when nodelay should be in effect when polling user input. */
5788 set_nonblocking_input(bool loading)
5790 static unsigned int loading_views;
5792 if ((loading == FALSE && loading_views-- == 1) ||
5793 (loading == TRUE && loading_views++ == 0))
5794 nodelay(status_win, loading);
5802 /* Initialize the curses library */
5803 if (isatty(STDIN_FILENO)) {
5804 cursed = !!initscr();
5807 /* Leave stdin and stdout alone when acting as a pager. */
5808 opt_tty = fopen("/dev/tty", "r+");
5810 die("Failed to open /dev/tty");
5811 cursed = !!newterm(NULL, opt_tty, opt_tty);
5815 die("Failed to initialize curses");
5817 nonl(); /* Tell curses not to do NL->CR/NL on output */
5818 cbreak(); /* Take input chars one at a time, no wait for \n */
5819 noecho(); /* Don't echo input */
5820 leaveok(stdscr, TRUE);
5825 getmaxyx(stdscr, y, x);
5826 status_win = newwin(1, 0, y - 1, 0);
5828 die("Failed to create status window");
5830 /* Enable keyboard mapping */
5831 keypad(status_win, TRUE);
5832 wbkgdset(status_win, get_line_attr(LINE_STATUS));
5834 TABSIZE = opt_tab_size;
5835 if (opt_line_graphics) {
5836 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
5841 prompt_yesno(const char *prompt)
5843 enum { WAIT, STOP, CANCEL } status = WAIT;
5844 bool answer = FALSE;
5846 while (status == WAIT) {
5852 foreach_view (view, i)
5857 mvwprintw(status_win, 0, 0, "%s [Yy]/[Nn]", prompt);
5858 wclrtoeol(status_win);
5860 /* Refresh, accept single keystroke of input */
5861 key = wgetch(status_win);
5885 /* Clear the status window */
5886 status_empty = FALSE;
5893 read_prompt(const char *prompt)
5895 enum { READING, STOP, CANCEL } status = READING;
5896 static char buf[sizeof(opt_cmd) - STRING_SIZE("git \0")];
5899 while (status == READING) {
5905 foreach_view (view, i)
5910 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
5911 wclrtoeol(status_win);
5913 /* Refresh, accept single keystroke of input */
5914 key = wgetch(status_win);
5919 status = pos ? STOP : CANCEL;
5937 if (pos >= sizeof(buf)) {
5938 report("Input string too long");
5943 buf[pos++] = (char) key;
5947 /* Clear the status window */
5948 status_empty = FALSE;
5951 if (status == CANCEL)
5960 * Repository references
5963 static struct ref *refs = NULL;
5964 static size_t refs_alloc = 0;
5965 static size_t refs_size = 0;
5967 /* Id <-> ref store */
5968 static struct ref ***id_refs = NULL;
5969 static size_t id_refs_alloc = 0;
5970 static size_t id_refs_size = 0;
5973 compare_refs(const void *ref1_, const void *ref2_)
5975 const struct ref *ref1 = *(const struct ref **)ref1_;
5976 const struct ref *ref2 = *(const struct ref **)ref2_;
5978 if (ref1->tag != ref2->tag)
5979 return ref2->tag - ref1->tag;
5980 if (ref1->ltag != ref2->ltag)
5981 return ref2->ltag - ref2->ltag;
5982 if (ref1->head != ref2->head)
5983 return ref2->head - ref1->head;
5984 if (ref1->tracked != ref2->tracked)
5985 return ref2->tracked - ref1->tracked;
5986 if (ref1->remote != ref2->remote)
5987 return ref2->remote - ref1->remote;
5988 return strcmp(ref1->name, ref2->name);
5991 static struct ref **
5992 get_refs(const char *id)
5994 struct ref ***tmp_id_refs;
5995 struct ref **ref_list = NULL;
5996 size_t ref_list_alloc = 0;
5997 size_t ref_list_size = 0;
6000 for (i = 0; i < id_refs_size; i++)
6001 if (!strcmp(id, id_refs[i][0]->id))
6004 tmp_id_refs = realloc_items(id_refs, &id_refs_alloc, id_refs_size + 1,
6009 id_refs = tmp_id_refs;
6011 for (i = 0; i < refs_size; i++) {
6014 if (strcmp(id, refs[i].id))
6017 tmp = realloc_items(ref_list, &ref_list_alloc,
6018 ref_list_size + 1, sizeof(*ref_list));
6026 ref_list[ref_list_size] = &refs[i];
6027 /* XXX: The properties of the commit chains ensures that we can
6028 * safely modify the shared ref. The repo references will
6029 * always be similar for the same id. */
6030 ref_list[ref_list_size]->next = 1;
6036 qsort(ref_list, ref_list_size, sizeof(*ref_list), compare_refs);
6037 ref_list[ref_list_size - 1]->next = 0;
6038 id_refs[id_refs_size++] = ref_list;
6045 read_ref(char *id, size_t idlen, char *name, size_t namelen)
6050 bool remote = FALSE;
6051 bool tracked = FALSE;
6052 bool check_replace = FALSE;
6055 if (!prefixcmp(name, "refs/tags/")) {
6056 if (!suffixcmp(name, namelen, "^{}")) {
6059 if (refs_size > 0 && refs[refs_size - 1].ltag == TRUE)
6060 check_replace = TRUE;
6066 namelen -= STRING_SIZE("refs/tags/");
6067 name += STRING_SIZE("refs/tags/");
6069 } else if (!prefixcmp(name, "refs/remotes/")) {
6071 namelen -= STRING_SIZE("refs/remotes/");
6072 name += STRING_SIZE("refs/remotes/");
6073 tracked = !strcmp(opt_remote, name);
6075 } else if (!prefixcmp(name, "refs/heads/")) {
6076 namelen -= STRING_SIZE("refs/heads/");
6077 name += STRING_SIZE("refs/heads/");
6078 head = !strncmp(opt_head, name, namelen);
6080 } else if (!strcmp(name, "HEAD")) {
6081 string_ncopy(opt_head_rev, id, idlen);
6085 if (check_replace && !strcmp(name, refs[refs_size - 1].name)) {
6086 /* it's an annotated tag, replace the previous sha1 with the
6087 * resolved commit id; relies on the fact git-ls-remote lists
6088 * the commit id of an annotated tag right before the commit id
6090 refs[refs_size - 1].ltag = ltag;
6091 string_copy_rev(refs[refs_size - 1].id, id);
6095 refs = realloc_items(refs, &refs_alloc, refs_size + 1, sizeof(*refs));
6099 ref = &refs[refs_size++];
6100 ref->name = malloc(namelen + 1);
6104 strncpy(ref->name, name, namelen);
6105 ref->name[namelen] = 0;
6109 ref->remote = remote;
6110 ref->tracked = tracked;
6111 string_copy_rev(ref->id, id);
6119 const char *cmd_env = getenv("TIG_LS_REMOTE");
6120 const char *cmd = cmd_env && *cmd_env ? cmd_env : TIG_LS_REMOTE;
6125 while (refs_size > 0)
6126 free(refs[--refs_size].name);
6127 while (id_refs_size > 0)
6128 free(id_refs[--id_refs_size]);
6130 return read_properties(popen(cmd, "r"), "\t", read_ref);
6134 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
6136 if (!strcmp(name, "i18n.commitencoding"))
6137 string_ncopy(opt_encoding, value, valuelen);
6139 if (!strcmp(name, "core.editor"))
6140 string_ncopy(opt_editor, value, valuelen);
6142 /* branch.<head>.remote */
6144 !strncmp(name, "branch.", 7) &&
6145 !strncmp(name + 7, opt_head, strlen(opt_head)) &&
6146 !strcmp(name + 7 + strlen(opt_head), ".remote"))
6147 string_ncopy(opt_remote, value, valuelen);
6149 if (*opt_head && *opt_remote &&
6150 !strncmp(name, "branch.", 7) &&
6151 !strncmp(name + 7, opt_head, strlen(opt_head)) &&
6152 !strcmp(name + 7 + strlen(opt_head), ".merge")) {
6153 size_t from = strlen(opt_remote);
6155 if (!prefixcmp(value, "refs/heads/")) {
6156 value += STRING_SIZE("refs/heads/");
6157 valuelen -= STRING_SIZE("refs/heads/");
6160 if (!string_format_from(opt_remote, &from, "/%s", value))
6168 load_git_config(void)
6170 return read_properties(popen("git " GIT_CONFIG " --list", "r"),
6171 "=", read_repo_config_option);
6175 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
6177 if (!opt_git_dir[0]) {
6178 string_ncopy(opt_git_dir, name, namelen);
6180 } else if (opt_is_inside_work_tree == -1) {
6181 /* This can be 3 different values depending on the
6182 * version of git being used. If git-rev-parse does not
6183 * understand --is-inside-work-tree it will simply echo
6184 * the option else either "true" or "false" is printed.
6185 * Default to true for the unknown case. */
6186 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
6188 } else if (opt_cdup[0] == ' ') {
6189 string_ncopy(opt_cdup, name, namelen);
6191 if (!prefixcmp(name, "refs/heads/")) {
6192 namelen -= STRING_SIZE("refs/heads/");
6193 name += STRING_SIZE("refs/heads/");
6194 string_ncopy(opt_head, name, namelen);
6202 load_repo_info(void)
6205 FILE *pipe = popen("(git rev-parse --git-dir --is-inside-work-tree "
6206 " --show-cdup; git symbolic-ref HEAD) 2>/dev/null", "r");
6208 /* XXX: The line outputted by "--show-cdup" can be empty so
6209 * initialize it to something invalid to make it possible to
6210 * detect whether it has been set or not. */
6213 result = read_properties(pipe, "=", read_repo_info);
6214 if (opt_cdup[0] == ' ')
6221 read_properties(FILE *pipe, const char *separators,
6222 int (*read_property)(char *, size_t, char *, size_t))
6224 char buffer[BUFSIZ];
6231 while (state == OK && (name = fgets(buffer, sizeof(buffer), pipe))) {
6236 name = chomp_string(name);
6237 namelen = strcspn(name, separators);
6239 if (name[namelen]) {
6241 value = chomp_string(name + namelen + 1);
6242 valuelen = strlen(value);
6249 state = read_property(name, namelen, value, valuelen);
6252 if (state != ERR && ferror(pipe))
6265 static void __NORETURN
6268 /* XXX: Restore tty modes and let the OS cleanup the rest! */
6274 static void __NORETURN
6275 die(const char *err, ...)
6281 va_start(args, err);
6282 fputs("tig: ", stderr);
6283 vfprintf(stderr, err, args);
6284 fputs("\n", stderr);
6291 warn(const char *msg, ...)
6295 va_start(args, msg);
6296 fputs("tig warning: ", stderr);
6297 vfprintf(stderr, msg, args);
6298 fputs("\n", stderr);
6303 main(int argc, const char *argv[])
6306 enum request request;
6309 signal(SIGINT, quit);
6311 if (setlocale(LC_ALL, "")) {
6312 char *codeset = nl_langinfo(CODESET);
6314 string_ncopy(opt_codeset, codeset, strlen(codeset));
6317 if (load_repo_info() == ERR)
6318 die("Failed to load repo info.");
6320 if (load_options() == ERR)
6321 die("Failed to load user config.");
6323 if (load_git_config() == ERR)
6324 die("Failed to load repo config.");
6326 request = parse_options(argc, argv);
6327 if (request == REQ_NONE)
6330 /* Require a git repository unless when running in pager mode. */
6331 if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
6332 die("Not a git repository");
6334 if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
6337 if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
6338 opt_iconv = iconv_open(opt_codeset, opt_encoding);
6339 if (opt_iconv == ICONV_NONE)
6340 die("Failed to initialize character set conversion");
6343 if (load_refs() == ERR)
6344 die("Failed to load refs.");
6346 foreach_view (view, i)
6347 view->cmd_env = getenv(view->cmd_env);
6351 while (view_driver(display[current_view], request)) {
6355 foreach_view (view, i)
6357 view = display[current_view];
6359 /* Refresh, accept single keystroke of input */
6360 key = wgetch(status_win);
6362 /* wgetch() with nodelay() enabled returns ERR when there's no
6369 request = get_keybinding(view->keymap, key);
6371 /* Some low-level request handling. This keeps access to
6372 * status_win restricted. */
6376 char *cmd = read_prompt(":");
6378 if (cmd && string_format(opt_cmd, "git %s", cmd)) {
6379 if (strncmp(cmd, "show", 4) && isspace(cmd[4])) {
6380 request = REQ_VIEW_DIFF;
6382 request = REQ_VIEW_PAGER;
6385 /* Always reload^Wrerun commands from the prompt. */
6386 open_view(view, request, OPEN_RELOAD);
6393 case REQ_SEARCH_BACK:
6395 const char *prompt = request == REQ_SEARCH ? "/" : "?";
6396 char *search = read_prompt(prompt);
6399 string_ncopy(opt_search, search, strlen(search));
6404 case REQ_SCREEN_RESIZE:
6408 getmaxyx(stdscr, height, width);
6410 /* Resize the status view and let the view driver take
6411 * care of resizing the displayed views. */
6412 wresize(status_win, 1, width);
6413 mvwin(status_win, height - 1, 0);
6414 wrefresh(status_win);