1 /* Copyright (c) 2006-2008 Jonas Fonseca <fonseca@diku.dk>
3 * This program is free software; you can redistribute it and/or
4 * modify it under the terms of the GNU General Public License as
5 * published by the Free Software Foundation; either version 2 of
6 * the License, or (at your option) any later version.
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
19 #define TIG_VERSION "unknown-version"
34 #include <sys/types.h>
45 /* ncurses(3): Must be defined to have extended wide-character functions. */
46 #define _XOPEN_SOURCE_EXTENDED
48 #ifdef HAVE_NCURSESW_NCURSES_H
49 #include <ncursesw/ncurses.h>
51 #ifdef HAVE_NCURSES_NCURSES_H
52 #include <ncurses/ncurses.h>
59 #define __NORETURN __attribute__((__noreturn__))
64 static void __NORETURN die(const char *err, ...);
65 static void warn(const char *msg, ...);
66 static void report(const char *msg, ...);
67 static int read_properties(FILE *pipe, const char *separators, int (*read)(char *, size_t, char *, size_t));
68 static void set_nonblocking_input(bool loading);
69 static size_t utf8_length(const char *string, int *width, size_t max_width, int *trimmed, bool reserve);
70 static bool prompt_yesno(const char *prompt);
71 static int load_refs(void);
73 #define ABS(x) ((x) >= 0 ? (x) : -(x))
74 #define MIN(x, y) ((x) < (y) ? (x) : (y))
76 #define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
77 #define STRING_SIZE(x) (sizeof(x) - 1)
79 #define SIZEOF_STR 1024 /* Default string size. */
80 #define SIZEOF_REF 256 /* Size of symbolic or SHA1 ID. */
81 #define SIZEOF_REV 41 /* Holds a SHA-1 and an ending NUL. */
82 #define SIZEOF_ARG 32 /* Default argument array size. */
86 #define REVGRAPH_INIT 'I'
87 #define REVGRAPH_MERGE 'M'
88 #define REVGRAPH_BRANCH '+'
89 #define REVGRAPH_COMMIT '*'
90 #define REVGRAPH_BOUND '^'
92 #define SIZEOF_REVGRAPH 19 /* Size of revision ancestry graphics. */
94 /* This color name can be used to refer to the default term colors. */
95 #define COLOR_DEFAULT (-1)
97 #define ICONV_NONE ((iconv_t) -1)
99 #define ICONV_CONST /* nothing */
102 /* The format and size of the date column in the main view. */
103 #define DATE_FORMAT "%Y-%m-%d %H:%M"
104 #define DATE_COLS STRING_SIZE("2006-04-29 14:21 ")
106 #define AUTHOR_COLS 20
109 /* The default interval between line numbers. */
110 #define NUMBER_INTERVAL 5
114 #define SCALE_SPLIT_VIEW(height) ((height) * 2 / 3)
116 #define NULL_ID "0000000000000000000000000000000000000000"
119 #define GIT_CONFIG "config"
122 #define TIG_LS_REMOTE \
123 "git ls-remote . 2>/dev/null"
125 #define TIG_MAIN_BASE \
126 "git log --no-color --pretty=raw --parents --topo-order"
128 /* Some ascii-shorthands fitted into the ncurses namespace. */
130 #define KEY_RETURN '\r'
135 char *name; /* Ref name; tag or head names are shortened. */
136 char id[SIZEOF_REV]; /* Commit SHA1 ID */
137 unsigned int head:1; /* Is it the current HEAD? */
138 unsigned int tag:1; /* Is it a tag? */
139 unsigned int ltag:1; /* If so, is the tag local? */
140 unsigned int remote:1; /* Is it a remote ref? */
141 unsigned int tracked:1; /* Is it the remote for the current HEAD? */
142 unsigned int next:1; /* For ref lists: are there more refs? */
145 static struct ref **get_refs(const char *id);
148 FORMAT_ALL, /* Perform replacement in all arguments. */
149 FORMAT_DASH, /* Perform replacement up until "--". */
150 FORMAT_NONE /* No replacement should be performed. */
153 static bool format_command(char dst[], const char *src[], enum format_flags flags);
154 static bool format_argv(const char *dst[], const char *src[], enum format_flags flags);
163 set_from_int_map(struct int_map *map, size_t map_size,
164 int *value, const char *name, int namelen)
169 for (i = 0; i < map_size; i++)
170 if (namelen == map[i].namelen &&
171 !strncasecmp(name, map[i].name, namelen)) {
172 *value = map[i].value;
185 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
187 if (srclen > dstlen - 1)
190 strncpy(dst, src, srclen);
194 /* Shorthands for safely copying into a fixed buffer. */
196 #define string_copy(dst, src) \
197 string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
199 #define string_ncopy(dst, src, srclen) \
200 string_ncopy_do(dst, sizeof(dst), src, srclen)
202 #define string_copy_rev(dst, src) \
203 string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
205 #define string_add(dst, from, src) \
206 string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
209 chomp_string(char *name)
213 while (isspace(*name))
216 namelen = strlen(name) - 1;
217 while (namelen > 0 && isspace(name[namelen]))
224 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
227 size_t pos = bufpos ? *bufpos : 0;
230 pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
236 return pos >= bufsize ? FALSE : TRUE;
239 #define string_format(buf, fmt, args...) \
240 string_nformat(buf, sizeof(buf), NULL, fmt, args)
242 #define string_format_from(buf, from, fmt, args...) \
243 string_nformat(buf, sizeof(buf), from, fmt, args)
246 string_enum_compare(const char *str1, const char *str2, int len)
250 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
252 /* Diff-Header == DIFF_HEADER */
253 for (i = 0; i < len; i++) {
254 if (toupper(str1[i]) == toupper(str2[i]))
257 if (string_enum_sep(str1[i]) &&
258 string_enum_sep(str2[i]))
261 return str1[i] - str2[i];
267 #define prefixcmp(str1, str2) \
268 strncmp(str1, str2, STRING_SIZE(str2))
271 suffixcmp(const char *str, int slen, const char *suffix)
273 size_t len = slen >= 0 ? slen : strlen(str);
274 size_t suffixlen = strlen(suffix);
276 return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
281 * NOTE: The following is a slightly modified copy of the git project's shell
282 * quoting routines found in the quote.c file.
284 * Help to copy the thing properly quoted for the shell safety. any single
285 * quote is replaced with '\'', any exclamation point is replaced with '\!',
286 * and the whole thing is enclosed in a
289 * original sq_quote result
290 * name ==> name ==> 'name'
291 * a b ==> a b ==> 'a b'
292 * a'b ==> a'\''b ==> 'a'\''b'
293 * a!b ==> a'\!'b ==> 'a'\!'b'
297 sq_quote(char buf[SIZEOF_STR], size_t bufsize, const char *src)
301 #define BUFPUT(x) do { if (bufsize < SIZEOF_STR) buf[bufsize++] = (x); } while (0)
304 while ((c = *src++)) {
305 if (c == '\'' || c == '!') {
316 if (bufsize < SIZEOF_STR)
323 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
327 while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
328 bool advance = cmd[valuelen] != 0;
331 argv[(*argc)++] = chomp_string(cmd);
332 cmd += valuelen + advance;
335 if (*argc < SIZEOF_ARG)
337 return *argc < SIZEOF_ARG;
341 argv_from_env(const char **argv, const char *name)
343 char *env = argv ? getenv(name) : NULL;
348 if (env && !argv_from_string(argv, &argc, env))
349 die("Too many arguments in the `%s` environment variable", name);
354 * Executing external commands.
358 IO_FD, /* File descriptor based IO. */
359 IO_FG, /* Execute command with same std{in,out,err}. */
360 IO_RD, /* Read only fork+exec IO. */
361 IO_WR, /* Write only fork+exec IO. */
365 enum io_type type; /* The requested type of pipe. */
366 const char *dir; /* Directory from which to execute. */
367 FILE *pipe; /* Pipe for reading or writing. */
368 int error; /* Error status. */
369 char sh[SIZEOF_STR]; /* Shell command buffer. */
370 char *buf; /* Read/write buffer. */
371 size_t bufalloc; /* Allocated buffer size. */
375 reset_io(struct io *io)
384 init_io(struct io *io, const char *dir, enum io_type type)
392 init_io_rd(struct io *io, const char *argv[], const char *dir,
393 enum format_flags flags)
395 init_io(io, dir, IO_RD);
396 return format_command(io->sh, argv, flags);
400 init_io_fd(struct io *io, FILE *pipe)
402 init_io(io, NULL, IO_FD);
404 return io->pipe != NULL;
408 done_io(struct io *io)
411 if (io->type == IO_FD)
413 else if (io->type == IO_RD || io->type == IO_WR)
420 start_io(struct io *io)
422 char buf[SIZEOF_STR * 2];
425 if (io->dir && *io->dir &&
426 !string_format_from(buf, &bufpos, "cd %s;", io->dir))
429 if (!string_format_from(buf, &bufpos, "%s", io->sh))
432 if (io->type == IO_FG)
433 return system(buf) == 0;
435 io->pipe = popen(io->sh, io->type == IO_RD ? "r" : "w");
436 return io->pipe != NULL;
440 run_io(struct io *io, enum io_type type, const char *cmd)
442 init_io(io, NULL, type);
443 string_ncopy(io->sh, cmd, strlen(cmd));
448 run_io_do(struct io *io)
450 return start_io(io) && done_io(io);
454 run_io_fg(const char **argv, const char *dir)
458 init_io(&io, dir, IO_FG);
459 if (!format_command(io.sh, argv, FORMAT_NONE))
461 return run_io_do(&io);
465 run_io_rd(struct io *io, const char **argv, enum format_flags flags)
467 return init_io_rd(io, argv, NULL, flags) && start_io(io);
471 io_eof(struct io *io)
473 return feof(io->pipe);
477 io_error(struct io *io)
483 io_strerror(struct io *io)
485 return strerror(io->error);
489 io_gets(struct io *io)
492 io->buf = malloc(BUFSIZ);
495 io->bufalloc = BUFSIZ;
498 if (!fgets(io->buf, io->bufalloc, io->pipe)) {
499 if (ferror(io->pipe))
513 /* XXX: Keep the view request first and in sync with views[]. */ \
514 REQ_GROUP("View switching") \
515 REQ_(VIEW_MAIN, "Show main view"), \
516 REQ_(VIEW_DIFF, "Show diff view"), \
517 REQ_(VIEW_LOG, "Show log view"), \
518 REQ_(VIEW_TREE, "Show tree view"), \
519 REQ_(VIEW_BLOB, "Show blob view"), \
520 REQ_(VIEW_BLAME, "Show blame view"), \
521 REQ_(VIEW_HELP, "Show help page"), \
522 REQ_(VIEW_PAGER, "Show pager view"), \
523 REQ_(VIEW_STATUS, "Show status view"), \
524 REQ_(VIEW_STAGE, "Show stage view"), \
526 REQ_GROUP("View manipulation") \
527 REQ_(ENTER, "Enter current line and scroll"), \
528 REQ_(NEXT, "Move to next"), \
529 REQ_(PREVIOUS, "Move to previous"), \
530 REQ_(VIEW_NEXT, "Move focus to next view"), \
531 REQ_(REFRESH, "Reload and refresh"), \
532 REQ_(MAXIMIZE, "Maximize the current view"), \
533 REQ_(VIEW_CLOSE, "Close the current view"), \
534 REQ_(QUIT, "Close all views and quit"), \
536 REQ_GROUP("View specific requests") \
537 REQ_(STATUS_UPDATE, "Update file status"), \
538 REQ_(STATUS_REVERT, "Revert file changes"), \
539 REQ_(STATUS_MERGE, "Merge file using external tool"), \
540 REQ_(STAGE_NEXT, "Find next chunk to stage"), \
541 REQ_(TREE_PARENT, "Switch to parent directory in tree view"), \
543 REQ_GROUP("Cursor navigation") \
544 REQ_(MOVE_UP, "Move cursor one line up"), \
545 REQ_(MOVE_DOWN, "Move cursor one line down"), \
546 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
547 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
548 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
549 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
551 REQ_GROUP("Scrolling") \
552 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
553 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
554 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
555 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
557 REQ_GROUP("Searching") \
558 REQ_(SEARCH, "Search the view"), \
559 REQ_(SEARCH_BACK, "Search backwards in the view"), \
560 REQ_(FIND_NEXT, "Find next search match"), \
561 REQ_(FIND_PREV, "Find previous search match"), \
563 REQ_GROUP("Option manipulation") \
564 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
565 REQ_(TOGGLE_DATE, "Toggle date display"), \
566 REQ_(TOGGLE_AUTHOR, "Toggle author display"), \
567 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
568 REQ_(TOGGLE_REFS, "Toggle reference display (tags/branches)"), \
571 REQ_(PROMPT, "Bring up the prompt"), \
572 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
573 REQ_(SCREEN_RESIZE, "Resize the screen"), \
574 REQ_(SHOW_VERSION, "Show version information"), \
575 REQ_(STOP_LOADING, "Stop all loading views"), \
576 REQ_(EDIT, "Open in editor"), \
577 REQ_(NONE, "Do nothing")
580 /* User action requests. */
582 #define REQ_GROUP(help)
583 #define REQ_(req, help) REQ_##req
585 /* Offset all requests to avoid conflicts with ncurses getch values. */
586 REQ_OFFSET = KEY_MAX + 1,
593 struct request_info {
594 enum request request;
600 static struct request_info req_info[] = {
601 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
602 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
609 get_request(const char *name)
611 int namelen = strlen(name);
614 for (i = 0; i < ARRAY_SIZE(req_info); i++)
615 if (req_info[i].namelen == namelen &&
616 !string_enum_compare(req_info[i].name, name, namelen))
617 return req_info[i].request;
627 static const char usage[] =
628 "tig " TIG_VERSION " (" __DATE__ ")\n"
630 "Usage: tig [options] [revs] [--] [paths]\n"
631 " or: tig show [options] [revs] [--] [paths]\n"
632 " or: tig blame [rev] path\n"
634 " or: tig < [git command output]\n"
637 " -v, --version Show version and exit\n"
638 " -h, --help Show help message and exit";
640 /* Option and state variables. */
641 static bool opt_date = TRUE;
642 static bool opt_author = TRUE;
643 static bool opt_line_number = FALSE;
644 static bool opt_line_graphics = TRUE;
645 static bool opt_rev_graph = FALSE;
646 static bool opt_show_refs = TRUE;
647 static int opt_num_interval = NUMBER_INTERVAL;
648 static int opt_tab_size = TAB_SIZE;
649 static int opt_author_cols = AUTHOR_COLS-1;
650 static char opt_cmd[SIZEOF_STR] = "";
651 static char opt_path[SIZEOF_STR] = "";
652 static char opt_file[SIZEOF_STR] = "";
653 static char opt_ref[SIZEOF_REF] = "";
654 static char opt_head[SIZEOF_REF] = "";
655 static char opt_head_rev[SIZEOF_REV] = "";
656 static char opt_remote[SIZEOF_REF] = "";
657 static FILE *opt_pipe = NULL;
658 static char opt_encoding[20] = "UTF-8";
659 static bool opt_utf8 = TRUE;
660 static char opt_codeset[20] = "UTF-8";
661 static iconv_t opt_iconv = ICONV_NONE;
662 static char opt_search[SIZEOF_STR] = "";
663 static char opt_cdup[SIZEOF_STR] = "";
664 static char opt_git_dir[SIZEOF_STR] = "";
665 static signed char opt_is_inside_work_tree = -1; /* set to TRUE or FALSE */
666 static char opt_editor[SIZEOF_STR] = "";
667 static FILE *opt_tty = NULL;
669 #define is_initial_commit() (!*opt_head_rev)
670 #define is_head_commit(rev) (!strcmp((rev), "HEAD") || !strcmp(opt_head_rev, (rev)))
673 parse_options(int argc, const char *argv[])
675 enum request request = REQ_VIEW_MAIN;
677 const char *subcommand;
678 bool seen_dashdash = FALSE;
681 if (!isatty(STDIN_FILENO)) {
683 return REQ_VIEW_PAGER;
687 return REQ_VIEW_MAIN;
689 subcommand = argv[1];
690 if (!strcmp(subcommand, "status") || !strcmp(subcommand, "-S")) {
691 if (!strcmp(subcommand, "-S"))
692 warn("`-S' has been deprecated; use `tig status' instead");
694 warn("ignoring arguments after `%s'", subcommand);
695 return REQ_VIEW_STATUS;
697 } else if (!strcmp(subcommand, "blame")) {
698 if (argc <= 2 || argc > 4)
699 die("invalid number of options to blame\n\n%s", usage);
703 string_ncopy(opt_ref, argv[i], strlen(argv[i]));
707 string_ncopy(opt_file, argv[i], strlen(argv[i]));
708 return REQ_VIEW_BLAME;
710 } else if (!strcmp(subcommand, "show")) {
711 request = REQ_VIEW_DIFF;
713 } else if (!strcmp(subcommand, "log") || !strcmp(subcommand, "diff")) {
714 request = subcommand[0] == 'l' ? REQ_VIEW_LOG : REQ_VIEW_DIFF;
715 warn("`tig %s' has been deprecated", subcommand);
722 /* XXX: This is vulnerable to the user overriding
723 * options required for the main view parser. */
724 string_copy(opt_cmd, TIG_MAIN_BASE);
726 string_format(opt_cmd, "git %s", subcommand);
728 buf_size = strlen(opt_cmd);
730 for (i = 1 + !!subcommand; i < argc; i++) {
731 const char *opt = argv[i];
733 if (seen_dashdash || !strcmp(opt, "--")) {
734 seen_dashdash = TRUE;
736 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
737 printf("tig version %s\n", TIG_VERSION);
740 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
741 printf("%s\n", usage);
745 opt_cmd[buf_size++] = ' ';
746 buf_size = sq_quote(opt_cmd, buf_size, opt);
747 if (buf_size >= sizeof(opt_cmd))
748 die("command too long");
751 opt_cmd[buf_size] = 0;
758 * Line-oriented content detection.
762 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
763 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
764 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
765 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
766 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
767 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
768 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
769 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
770 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
771 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
772 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
773 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
774 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
775 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
776 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
777 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
778 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
779 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
780 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
781 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
782 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
783 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
784 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
785 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
786 LINE(AUTHOR, "author ", COLOR_CYAN, COLOR_DEFAULT, 0), \
787 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
788 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
789 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
790 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
791 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
792 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
793 LINE(DELIMITER, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
794 LINE(DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
795 LINE(LINE_NUMBER, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
796 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
797 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
798 LINE(MAIN_AUTHOR, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
799 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
800 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
801 LINE(MAIN_LOCAL_TAG,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
802 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
803 LINE(MAIN_TRACKED, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
804 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
805 LINE(MAIN_HEAD, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
806 LINE(MAIN_REVGRAPH,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
807 LINE(TREE_DIR, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
808 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
809 LINE(STAT_HEAD, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
810 LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
811 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
812 LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
813 LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
814 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
815 LINE(BLAME_ID, "", COLOR_MAGENTA, COLOR_DEFAULT, 0)
818 #define LINE(type, line, fg, bg, attr) \
826 const char *name; /* Option name. */
827 int namelen; /* Size of option name. */
828 const char *line; /* The start of line to match. */
829 int linelen; /* Size of string to match. */
830 int fg, bg, attr; /* Color and text attributes for the lines. */
833 static struct line_info line_info[] = {
834 #define LINE(type, line, fg, bg, attr) \
835 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
840 static enum line_type
841 get_line_type(const char *line)
843 int linelen = strlen(line);
846 for (type = 0; type < ARRAY_SIZE(line_info); type++)
847 /* Case insensitive search matches Signed-off-by lines better. */
848 if (linelen >= line_info[type].linelen &&
849 !strncasecmp(line_info[type].line, line, line_info[type].linelen))
856 get_line_attr(enum line_type type)
858 assert(type < ARRAY_SIZE(line_info));
859 return COLOR_PAIR(type) | line_info[type].attr;
862 static struct line_info *
863 get_line_info(const char *name)
865 size_t namelen = strlen(name);
868 for (type = 0; type < ARRAY_SIZE(line_info); type++)
869 if (namelen == line_info[type].namelen &&
870 !string_enum_compare(line_info[type].name, name, namelen))
871 return &line_info[type];
879 int default_bg = line_info[LINE_DEFAULT].bg;
880 int default_fg = line_info[LINE_DEFAULT].fg;
885 if (assume_default_colors(default_fg, default_bg) == ERR) {
886 default_bg = COLOR_BLACK;
887 default_fg = COLOR_WHITE;
890 for (type = 0; type < ARRAY_SIZE(line_info); type++) {
891 struct line_info *info = &line_info[type];
892 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
893 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
895 init_pair(type, fg, bg);
903 unsigned int selected:1;
904 unsigned int dirty:1;
906 void *data; /* User data */
916 enum request request;
919 static struct keybinding default_keybindings[] = {
921 { 'm', REQ_VIEW_MAIN },
922 { 'd', REQ_VIEW_DIFF },
923 { 'l', REQ_VIEW_LOG },
924 { 't', REQ_VIEW_TREE },
925 { 'f', REQ_VIEW_BLOB },
926 { 'B', REQ_VIEW_BLAME },
927 { 'p', REQ_VIEW_PAGER },
928 { 'h', REQ_VIEW_HELP },
929 { 'S', REQ_VIEW_STATUS },
930 { 'c', REQ_VIEW_STAGE },
932 /* View manipulation */
933 { 'q', REQ_VIEW_CLOSE },
934 { KEY_TAB, REQ_VIEW_NEXT },
935 { KEY_RETURN, REQ_ENTER },
936 { KEY_UP, REQ_PREVIOUS },
937 { KEY_DOWN, REQ_NEXT },
938 { 'R', REQ_REFRESH },
939 { KEY_F(5), REQ_REFRESH },
940 { 'O', REQ_MAXIMIZE },
942 /* Cursor navigation */
943 { 'k', REQ_MOVE_UP },
944 { 'j', REQ_MOVE_DOWN },
945 { KEY_HOME, REQ_MOVE_FIRST_LINE },
946 { KEY_END, REQ_MOVE_LAST_LINE },
947 { KEY_NPAGE, REQ_MOVE_PAGE_DOWN },
948 { ' ', REQ_MOVE_PAGE_DOWN },
949 { KEY_PPAGE, REQ_MOVE_PAGE_UP },
950 { 'b', REQ_MOVE_PAGE_UP },
951 { '-', REQ_MOVE_PAGE_UP },
954 { KEY_IC, REQ_SCROLL_LINE_UP },
955 { KEY_DC, REQ_SCROLL_LINE_DOWN },
956 { 'w', REQ_SCROLL_PAGE_UP },
957 { 's', REQ_SCROLL_PAGE_DOWN },
961 { '?', REQ_SEARCH_BACK },
962 { 'n', REQ_FIND_NEXT },
963 { 'N', REQ_FIND_PREV },
967 { 'z', REQ_STOP_LOADING },
968 { 'v', REQ_SHOW_VERSION },
969 { 'r', REQ_SCREEN_REDRAW },
970 { '.', REQ_TOGGLE_LINENO },
971 { 'D', REQ_TOGGLE_DATE },
972 { 'A', REQ_TOGGLE_AUTHOR },
973 { 'g', REQ_TOGGLE_REV_GRAPH },
974 { 'F', REQ_TOGGLE_REFS },
976 { 'u', REQ_STATUS_UPDATE },
977 { '!', REQ_STATUS_REVERT },
978 { 'M', REQ_STATUS_MERGE },
979 { '@', REQ_STAGE_NEXT },
980 { ',', REQ_TREE_PARENT },
983 /* Using the ncurses SIGWINCH handler. */
984 { KEY_RESIZE, REQ_SCREEN_RESIZE },
987 #define KEYMAP_INFO \
1001 #define KEYMAP_(name) KEYMAP_##name
1006 static struct int_map keymap_table[] = {
1007 #define KEYMAP_(name) { #name, STRING_SIZE(#name), KEYMAP_##name }
1012 #define set_keymap(map, name) \
1013 set_from_int_map(keymap_table, ARRAY_SIZE(keymap_table), map, name, strlen(name))
1015 struct keybinding_table {
1016 struct keybinding *data;
1020 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1023 add_keybinding(enum keymap keymap, enum request request, int key)
1025 struct keybinding_table *table = &keybindings[keymap];
1027 table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1029 die("Failed to allocate keybinding");
1030 table->data[table->size].alias = key;
1031 table->data[table->size++].request = request;
1034 /* Looks for a key binding first in the given map, then in the generic map, and
1035 * lastly in the default keybindings. */
1037 get_keybinding(enum keymap keymap, int key)
1041 for (i = 0; i < keybindings[keymap].size; i++)
1042 if (keybindings[keymap].data[i].alias == key)
1043 return keybindings[keymap].data[i].request;
1045 for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1046 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1047 return keybindings[KEYMAP_GENERIC].data[i].request;
1049 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1050 if (default_keybindings[i].alias == key)
1051 return default_keybindings[i].request;
1053 return (enum request) key;
1062 static struct key key_table[] = {
1063 { "Enter", KEY_RETURN },
1065 { "Backspace", KEY_BACKSPACE },
1067 { "Escape", KEY_ESC },
1068 { "Left", KEY_LEFT },
1069 { "Right", KEY_RIGHT },
1071 { "Down", KEY_DOWN },
1072 { "Insert", KEY_IC },
1073 { "Delete", KEY_DC },
1075 { "Home", KEY_HOME },
1077 { "PageUp", KEY_PPAGE },
1078 { "PageDown", KEY_NPAGE },
1088 { "F10", KEY_F(10) },
1089 { "F11", KEY_F(11) },
1090 { "F12", KEY_F(12) },
1094 get_key_value(const char *name)
1098 for (i = 0; i < ARRAY_SIZE(key_table); i++)
1099 if (!strcasecmp(key_table[i].name, name))
1100 return key_table[i].value;
1102 if (strlen(name) == 1 && isprint(*name))
1109 get_key_name(int key_value)
1111 static char key_char[] = "'X'";
1112 const char *seq = NULL;
1115 for (key = 0; key < ARRAY_SIZE(key_table); key++)
1116 if (key_table[key].value == key_value)
1117 seq = key_table[key].name;
1121 isprint(key_value)) {
1122 key_char[1] = (char) key_value;
1126 return seq ? seq : "(no key)";
1130 get_key(enum request request)
1132 static char buf[BUFSIZ];
1139 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1140 struct keybinding *keybinding = &default_keybindings[i];
1142 if (keybinding->request != request)
1145 if (!string_format_from(buf, &pos, "%s%s", sep,
1146 get_key_name(keybinding->alias)))
1147 return "Too many keybindings!";
1154 struct run_request {
1157 const char *argv[SIZEOF_ARG];
1160 static struct run_request *run_request;
1161 static size_t run_requests;
1164 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1166 struct run_request *req;
1168 if (argc >= ARRAY_SIZE(req->argv) - 1)
1171 req = realloc(run_request, (run_requests + 1) * sizeof(*run_request));
1176 req = &run_request[run_requests];
1177 req->keymap = keymap;
1179 req->argv[0] = NULL;
1181 if (!format_argv(req->argv, argv, FORMAT_NONE))
1184 return REQ_NONE + ++run_requests;
1187 static struct run_request *
1188 get_run_request(enum request request)
1190 if (request <= REQ_NONE)
1192 return &run_request[request - REQ_NONE - 1];
1196 add_builtin_run_requests(void)
1198 const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1199 const char *gc[] = { "git", "gc", NULL };
1206 { KEYMAP_MAIN, 'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1207 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1211 for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1214 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1215 if (req != REQ_NONE)
1216 add_keybinding(reqs[i].keymap, req, reqs[i].key);
1221 * User config file handling.
1224 static struct int_map color_map[] = {
1225 #define COLOR_MAP(name) { #name, STRING_SIZE(#name), COLOR_##name }
1237 #define set_color(color, name) \
1238 set_from_int_map(color_map, ARRAY_SIZE(color_map), color, name, strlen(name))
1240 static struct int_map attr_map[] = {
1241 #define ATTR_MAP(name) { #name, STRING_SIZE(#name), A_##name }
1248 ATTR_MAP(UNDERLINE),
1251 #define set_attribute(attr, name) \
1252 set_from_int_map(attr_map, ARRAY_SIZE(attr_map), attr, name, strlen(name))
1254 static int config_lineno;
1255 static bool config_errors;
1256 static const char *config_msg;
1258 /* Wants: object fgcolor bgcolor [attr] */
1260 option_color_command(int argc, const char *argv[])
1262 struct line_info *info;
1264 if (argc != 3 && argc != 4) {
1265 config_msg = "Wrong number of arguments given to color command";
1269 info = get_line_info(argv[0]);
1271 if (!string_enum_compare(argv[0], "main-delim", strlen("main-delim"))) {
1272 info = get_line_info("delimiter");
1274 } else if (!string_enum_compare(argv[0], "main-date", strlen("main-date"))) {
1275 info = get_line_info("date");
1278 config_msg = "Unknown color name";
1283 if (set_color(&info->fg, argv[1]) == ERR ||
1284 set_color(&info->bg, argv[2]) == ERR) {
1285 config_msg = "Unknown color";
1289 if (argc == 4 && set_attribute(&info->attr, argv[3]) == ERR) {
1290 config_msg = "Unknown attribute";
1297 static bool parse_bool(const char *s)
1299 return (!strcmp(s, "1") || !strcmp(s, "true") ||
1300 !strcmp(s, "yes")) ? TRUE : FALSE;
1304 parse_int(const char *s, int default_value, int min, int max)
1306 int value = atoi(s);
1308 return (value < min || value > max) ? default_value : value;
1311 /* Wants: name = value */
1313 option_set_command(int argc, const char *argv[])
1316 config_msg = "Wrong number of arguments given to set command";
1320 if (strcmp(argv[1], "=")) {
1321 config_msg = "No value assigned";
1325 if (!strcmp(argv[0], "show-author")) {
1326 opt_author = parse_bool(argv[2]);
1330 if (!strcmp(argv[0], "show-date")) {
1331 opt_date = parse_bool(argv[2]);
1335 if (!strcmp(argv[0], "show-rev-graph")) {
1336 opt_rev_graph = parse_bool(argv[2]);
1340 if (!strcmp(argv[0], "show-refs")) {
1341 opt_show_refs = parse_bool(argv[2]);
1345 if (!strcmp(argv[0], "show-line-numbers")) {
1346 opt_line_number = parse_bool(argv[2]);
1350 if (!strcmp(argv[0], "line-graphics")) {
1351 opt_line_graphics = parse_bool(argv[2]);
1355 if (!strcmp(argv[0], "line-number-interval")) {
1356 opt_num_interval = parse_int(argv[2], opt_num_interval, 1, 1024);
1360 if (!strcmp(argv[0], "author-width")) {
1361 opt_author_cols = parse_int(argv[2], opt_author_cols, 0, 1024);
1365 if (!strcmp(argv[0], "tab-size")) {
1366 opt_tab_size = parse_int(argv[2], opt_tab_size, 1, 1024);
1370 if (!strcmp(argv[0], "commit-encoding")) {
1371 const char *arg = argv[2];
1372 int arglen = strlen(arg);
1377 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1378 config_msg = "Unmatched quotation";
1381 arg += 1; arglen -= 2;
1383 string_ncopy(opt_encoding, arg, strlen(arg));
1388 config_msg = "Unknown variable name";
1392 /* Wants: mode request key */
1394 option_bind_command(int argc, const char *argv[])
1396 enum request request;
1401 config_msg = "Wrong number of arguments given to bind command";
1405 if (set_keymap(&keymap, argv[0]) == ERR) {
1406 config_msg = "Unknown key map";
1410 key = get_key_value(argv[1]);
1412 config_msg = "Unknown key";
1416 request = get_request(argv[2]);
1417 if (request == REQ_NONE) {
1418 const char *obsolete[] = { "cherry-pick" };
1419 size_t namelen = strlen(argv[2]);
1422 for (i = 0; i < ARRAY_SIZE(obsolete); i++) {
1423 if (namelen == strlen(obsolete[i]) &&
1424 !string_enum_compare(obsolete[i], argv[2], namelen)) {
1425 config_msg = "Obsolete request name";
1430 if (request == REQ_NONE && *argv[2]++ == '!')
1431 request = add_run_request(keymap, key, argc - 2, argv + 2);
1432 if (request == REQ_NONE) {
1433 config_msg = "Unknown request name";
1437 add_keybinding(keymap, request, key);
1443 set_option(const char *opt, char *value)
1445 const char *argv[SIZEOF_ARG];
1448 if (!argv_from_string(argv, &argc, value)) {
1449 config_msg = "Too many option arguments";
1453 if (!strcmp(opt, "color"))
1454 return option_color_command(argc, argv);
1456 if (!strcmp(opt, "set"))
1457 return option_set_command(argc, argv);
1459 if (!strcmp(opt, "bind"))
1460 return option_bind_command(argc, argv);
1462 config_msg = "Unknown option command";
1467 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1472 config_msg = "Internal error";
1474 /* Check for comment markers, since read_properties() will
1475 * only ensure opt and value are split at first " \t". */
1476 optlen = strcspn(opt, "#");
1480 if (opt[optlen] != 0) {
1481 config_msg = "No option value";
1485 /* Look for comment endings in the value. */
1486 size_t len = strcspn(value, "#");
1488 if (len < valuelen) {
1490 value[valuelen] = 0;
1493 status = set_option(opt, value);
1496 if (status == ERR) {
1497 fprintf(stderr, "Error on line %d, near '%.*s': %s\n",
1498 config_lineno, (int) optlen, opt, config_msg);
1499 config_errors = TRUE;
1502 /* Always keep going if errors are encountered. */
1507 load_option_file(const char *path)
1511 /* It's ok that the file doesn't exist. */
1512 file = fopen(path, "r");
1517 config_errors = FALSE;
1519 if (read_properties(file, " \t", read_option) == ERR ||
1520 config_errors == TRUE)
1521 fprintf(stderr, "Errors while loading %s.\n", path);
1527 const char *home = getenv("HOME");
1528 const char *tigrc_user = getenv("TIGRC_USER");
1529 const char *tigrc_system = getenv("TIGRC_SYSTEM");
1530 char buf[SIZEOF_STR];
1532 add_builtin_run_requests();
1534 if (!tigrc_system) {
1535 if (!string_format(buf, "%s/tigrc", SYSCONFDIR))
1539 load_option_file(tigrc_system);
1542 if (!home || !string_format(buf, "%s/.tigrc", home))
1546 load_option_file(tigrc_user);
1559 /* The display array of active views and the index of the current view. */
1560 static struct view *display[2];
1561 static unsigned int current_view;
1563 /* Reading from the prompt? */
1564 static bool input_mode = FALSE;
1566 #define foreach_displayed_view(view, i) \
1567 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1569 #define displayed_views() (display[1] != NULL ? 2 : 1)
1571 /* Current head and commit ID */
1572 static char ref_blob[SIZEOF_REF] = "";
1573 static char ref_commit[SIZEOF_REF] = "HEAD";
1574 static char ref_head[SIZEOF_REF] = "HEAD";
1577 const char *name; /* View name */
1578 const char *cmd_env; /* Command line set via environment */
1579 const char *id; /* Points to either of ref_{head,commit,blob} */
1581 struct view_ops *ops; /* View operations */
1583 enum keymap keymap; /* What keymap does this view have */
1584 bool git_dir; /* Whether the view requires a git directory. */
1586 char ref[SIZEOF_REF]; /* Hovered commit reference */
1587 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
1589 int height, width; /* The width and height of the main window */
1590 WINDOW *win; /* The main window */
1591 WINDOW *title; /* The title window living below the main window */
1594 unsigned long offset; /* Offset of the window top */
1595 unsigned long lineno; /* Current line number */
1598 char grep[SIZEOF_STR]; /* Search string */
1599 regex_t *regex; /* Pre-compiled regex */
1601 /* If non-NULL, points to the view that opened this view. If this view
1602 * is closed tig will switch back to the parent view. */
1603 struct view *parent;
1606 size_t lines; /* Total number of lines */
1607 struct line *line; /* Line index */
1608 size_t line_alloc; /* Total number of allocated lines */
1609 size_t line_size; /* Total number of used lines */
1610 unsigned int digits; /* Number of digits in the lines member. */
1613 struct line *curline; /* Line currently being drawn. */
1614 enum line_type curtype; /* Attribute currently used for drawing. */
1615 unsigned long col; /* Column when drawing. */
1624 /* What type of content being displayed. Used in the title bar. */
1626 /* Default command arguments. */
1628 /* Open and reads in all view content. */
1629 bool (*open)(struct view *view);
1630 /* Read one line; updates view->line. */
1631 bool (*read)(struct view *view, char *data);
1632 /* Draw one line; @lineno must be < view->height. */
1633 bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
1634 /* Depending on view handle a special requests. */
1635 enum request (*request)(struct view *view, enum request request, struct line *line);
1636 /* Search for regex in a line. */
1637 bool (*grep)(struct view *view, struct line *line);
1639 void (*select)(struct view *view, struct line *line);
1642 static struct view_ops blame_ops;
1643 static struct view_ops blob_ops;
1644 static struct view_ops diff_ops;
1645 static struct view_ops help_ops;
1646 static struct view_ops log_ops;
1647 static struct view_ops main_ops;
1648 static struct view_ops pager_ops;
1649 static struct view_ops stage_ops;
1650 static struct view_ops status_ops;
1651 static struct view_ops tree_ops;
1653 #define VIEW_STR(name, env, ref, ops, map, git) \
1654 { name, #env, ref, ops, map, git }
1656 #define VIEW_(id, name, ops, git, ref) \
1657 VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
1660 static struct view views[] = {
1661 VIEW_(MAIN, "main", &main_ops, TRUE, ref_head),
1662 VIEW_(DIFF, "diff", &diff_ops, TRUE, ref_commit),
1663 VIEW_(LOG, "log", &log_ops, TRUE, ref_head),
1664 VIEW_(TREE, "tree", &tree_ops, TRUE, ref_commit),
1665 VIEW_(BLOB, "blob", &blob_ops, TRUE, ref_blob),
1666 VIEW_(BLAME, "blame", &blame_ops, TRUE, ref_commit),
1667 VIEW_(HELP, "help", &help_ops, FALSE, ""),
1668 VIEW_(PAGER, "pager", &pager_ops, FALSE, "stdin"),
1669 VIEW_(STATUS, "status", &status_ops, TRUE, ""),
1670 VIEW_(STAGE, "stage", &stage_ops, TRUE, ""),
1673 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
1674 #define VIEW_REQ(view) ((view) - views + REQ_OFFSET + 1)
1676 #define foreach_view(view, i) \
1677 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1679 #define view_is_displayed(view) \
1680 (view == display[0] || view == display[1])
1687 static int line_graphics[] = {
1688 /* LINE_GRAPHIC_VLINE: */ '|'
1692 set_view_attr(struct view *view, enum line_type type)
1694 if (!view->curline->selected && view->curtype != type) {
1695 wattrset(view->win, get_line_attr(type));
1696 wchgat(view->win, -1, 0, type, NULL);
1697 view->curtype = type;
1702 draw_chars(struct view *view, enum line_type type, const char *string,
1703 int max_len, bool use_tilde)
1707 int trimmed = FALSE;
1713 len = utf8_length(string, &col, max_len, &trimmed, use_tilde);
1715 col = len = strlen(string);
1716 if (len > max_len) {
1720 col = len = max_len;
1725 set_view_attr(view, type);
1726 waddnstr(view->win, string, len);
1727 if (trimmed && use_tilde) {
1728 set_view_attr(view, LINE_DELIMITER);
1729 waddch(view->win, '~');
1737 draw_space(struct view *view, enum line_type type, int max, int spaces)
1739 static char space[] = " ";
1742 spaces = MIN(max, spaces);
1744 while (spaces > 0) {
1745 int len = MIN(spaces, sizeof(space) - 1);
1747 col += draw_chars(view, type, space, spaces, FALSE);
1755 draw_lineno(struct view *view, unsigned int lineno)
1758 int digits3 = view->digits < 3 ? 3 : view->digits;
1759 int max_number = MIN(digits3, STRING_SIZE(number));
1760 int max = view->width - view->col;
1763 if (max < max_number)
1766 lineno += view->offset + 1;
1767 if (lineno == 1 || (lineno % opt_num_interval) == 0) {
1768 static char fmt[] = "%1ld";
1770 if (view->digits <= 9)
1771 fmt[1] = '0' + digits3;
1773 if (!string_format(number, fmt, lineno))
1775 col = draw_chars(view, LINE_LINE_NUMBER, number, max_number, TRUE);
1777 col = draw_space(view, LINE_LINE_NUMBER, max_number, max_number);
1781 set_view_attr(view, LINE_DEFAULT);
1782 waddch(view->win, line_graphics[LINE_GRAPHIC_VLINE]);
1787 col += draw_space(view, LINE_DEFAULT, max - col, 1);
1790 return view->width - view->col <= 0;
1794 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
1796 view->col += draw_chars(view, type, string, view->width - view->col, trim);
1797 return view->width - view->col <= 0;
1801 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
1803 int max = view->width - view->col;
1809 set_view_attr(view, type);
1810 /* Using waddch() instead of waddnstr() ensures that
1811 * they'll be rendered correctly for the cursor line. */
1812 for (i = 0; i < size; i++)
1813 waddch(view->win, graphic[i]);
1817 waddch(view->win, ' ');
1821 return view->width - view->col <= 0;
1825 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
1827 int max = MIN(view->width - view->col, len);
1831 col = draw_chars(view, type, text, max - 1, trim);
1833 col = draw_space(view, type, max - 1, max - 1);
1835 view->col += col + draw_space(view, LINE_DEFAULT, max - col, max - col);
1836 return view->width - view->col <= 0;
1840 draw_date(struct view *view, struct tm *time)
1842 char buf[DATE_COLS];
1847 timelen = strftime(buf, sizeof(buf), DATE_FORMAT, time);
1848 date = timelen ? buf : NULL;
1850 return draw_field(view, LINE_DATE, date, DATE_COLS, FALSE);
1854 draw_view_line(struct view *view, unsigned int lineno)
1857 bool selected = (view->offset + lineno == view->lineno);
1860 assert(view_is_displayed(view));
1862 if (view->offset + lineno >= view->lines)
1865 line = &view->line[view->offset + lineno];
1867 wmove(view->win, lineno, 0);
1869 view->curline = line;
1870 view->curtype = LINE_NONE;
1871 line->selected = FALSE;
1874 set_view_attr(view, LINE_CURSOR);
1875 line->selected = TRUE;
1876 view->ops->select(view, line);
1877 } else if (line->selected) {
1878 wclrtoeol(view->win);
1881 scrollok(view->win, FALSE);
1882 draw_ok = view->ops->draw(view, line, lineno);
1883 scrollok(view->win, TRUE);
1889 redraw_view_dirty(struct view *view)
1894 for (lineno = 0; lineno < view->height; lineno++) {
1895 struct line *line = &view->line[view->offset + lineno];
1901 if (!draw_view_line(view, lineno))
1907 redrawwin(view->win);
1909 wnoutrefresh(view->win);
1911 wrefresh(view->win);
1915 redraw_view_from(struct view *view, int lineno)
1917 assert(0 <= lineno && lineno < view->height);
1919 for (; lineno < view->height; lineno++) {
1920 if (!draw_view_line(view, lineno))
1924 redrawwin(view->win);
1926 wnoutrefresh(view->win);
1928 wrefresh(view->win);
1932 redraw_view(struct view *view)
1935 redraw_view_from(view, 0);
1940 update_view_title(struct view *view)
1942 char buf[SIZEOF_STR];
1943 char state[SIZEOF_STR];
1944 size_t bufpos = 0, statelen = 0;
1946 assert(view_is_displayed(view));
1948 if (view != VIEW(REQ_VIEW_STATUS) && (view->lines || view->pipe)) {
1949 unsigned int view_lines = view->offset + view->height;
1950 unsigned int lines = view->lines
1951 ? MIN(view_lines, view->lines) * 100 / view->lines
1954 string_format_from(state, &statelen, "- %s %d of %d (%d%%)",
1961 time_t secs = time(NULL) - view->start_time;
1963 /* Three git seconds are a long time ... */
1965 string_format_from(state, &statelen, " %lds", secs);
1969 string_format_from(buf, &bufpos, "[%s]", view->name);
1970 if (*view->ref && bufpos < view->width) {
1971 size_t refsize = strlen(view->ref);
1972 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
1974 if (minsize < view->width)
1975 refsize = view->width - minsize + 7;
1976 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
1979 if (statelen && bufpos < view->width) {
1980 string_format_from(buf, &bufpos, " %s", state);
1983 if (view == display[current_view])
1984 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
1986 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
1988 mvwaddnstr(view->title, 0, 0, buf, bufpos);
1989 wclrtoeol(view->title);
1990 wmove(view->title, 0, view->width - 1);
1993 wnoutrefresh(view->title);
1995 wrefresh(view->title);
1999 resize_display(void)
2002 struct view *base = display[0];
2003 struct view *view = display[1] ? display[1] : display[0];
2005 /* Setup window dimensions */
2007 getmaxyx(stdscr, base->height, base->width);
2009 /* Make room for the status window. */
2013 /* Horizontal split. */
2014 view->width = base->width;
2015 view->height = SCALE_SPLIT_VIEW(base->height);
2016 base->height -= view->height;
2018 /* Make room for the title bar. */
2022 /* Make room for the title bar. */
2027 foreach_displayed_view (view, i) {
2029 view->win = newwin(view->height, 0, offset, 0);
2031 die("Failed to create %s view", view->name);
2033 scrollok(view->win, TRUE);
2035 view->title = newwin(1, 0, offset + view->height, 0);
2037 die("Failed to create title window");
2040 wresize(view->win, view->height, view->width);
2041 mvwin(view->win, offset, 0);
2042 mvwin(view->title, offset + view->height, 0);
2045 offset += view->height + 1;
2050 redraw_display(void)
2055 foreach_displayed_view (view, i) {
2057 update_view_title(view);
2062 update_display_cursor(struct view *view)
2064 /* Move the cursor to the right-most column of the cursor line.
2066 * XXX: This could turn out to be a bit expensive, but it ensures that
2067 * the cursor does not jump around. */
2069 wmove(view->win, view->lineno - view->offset, view->width - 1);
2070 wrefresh(view->win);
2078 /* Scrolling backend */
2080 do_scroll_view(struct view *view, int lines)
2082 bool redraw_current_line = FALSE;
2084 /* The rendering expects the new offset. */
2085 view->offset += lines;
2087 assert(0 <= view->offset && view->offset < view->lines);
2090 /* Move current line into the view. */
2091 if (view->lineno < view->offset) {
2092 view->lineno = view->offset;
2093 redraw_current_line = TRUE;
2094 } else if (view->lineno >= view->offset + view->height) {
2095 view->lineno = view->offset + view->height - 1;
2096 redraw_current_line = TRUE;
2099 assert(view->offset <= view->lineno && view->lineno < view->lines);
2101 /* Redraw the whole screen if scrolling is pointless. */
2102 if (view->height < ABS(lines)) {
2106 int line = lines > 0 ? view->height - lines : 0;
2107 int end = line + ABS(lines);
2109 wscrl(view->win, lines);
2111 for (; line < end; line++) {
2112 if (!draw_view_line(view, line))
2116 if (redraw_current_line)
2117 draw_view_line(view, view->lineno - view->offset);
2120 redrawwin(view->win);
2121 wrefresh(view->win);
2125 /* Scroll frontend */
2127 scroll_view(struct view *view, enum request request)
2131 assert(view_is_displayed(view));
2134 case REQ_SCROLL_PAGE_DOWN:
2135 lines = view->height;
2136 case REQ_SCROLL_LINE_DOWN:
2137 if (view->offset + lines > view->lines)
2138 lines = view->lines - view->offset;
2140 if (lines == 0 || view->offset + view->height >= view->lines) {
2141 report("Cannot scroll beyond the last line");
2146 case REQ_SCROLL_PAGE_UP:
2147 lines = view->height;
2148 case REQ_SCROLL_LINE_UP:
2149 if (lines > view->offset)
2150 lines = view->offset;
2153 report("Cannot scroll beyond the first line");
2161 die("request %d not handled in switch", request);
2164 do_scroll_view(view, lines);
2169 move_view(struct view *view, enum request request)
2171 int scroll_steps = 0;
2175 case REQ_MOVE_FIRST_LINE:
2176 steps = -view->lineno;
2179 case REQ_MOVE_LAST_LINE:
2180 steps = view->lines - view->lineno - 1;
2183 case REQ_MOVE_PAGE_UP:
2184 steps = view->height > view->lineno
2185 ? -view->lineno : -view->height;
2188 case REQ_MOVE_PAGE_DOWN:
2189 steps = view->lineno + view->height >= view->lines
2190 ? view->lines - view->lineno - 1 : view->height;
2202 die("request %d not handled in switch", request);
2205 if (steps <= 0 && view->lineno == 0) {
2206 report("Cannot move beyond the first line");
2209 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2210 report("Cannot move beyond the last line");
2214 /* Move the current line */
2215 view->lineno += steps;
2216 assert(0 <= view->lineno && view->lineno < view->lines);
2218 /* Check whether the view needs to be scrolled */
2219 if (view->lineno < view->offset ||
2220 view->lineno >= view->offset + view->height) {
2221 scroll_steps = steps;
2222 if (steps < 0 && -steps > view->offset) {
2223 scroll_steps = -view->offset;
2225 } else if (steps > 0) {
2226 if (view->lineno == view->lines - 1 &&
2227 view->lines > view->height) {
2228 scroll_steps = view->lines - view->offset - 1;
2229 if (scroll_steps >= view->height)
2230 scroll_steps -= view->height - 1;
2235 if (!view_is_displayed(view)) {
2236 view->offset += scroll_steps;
2237 assert(0 <= view->offset && view->offset < view->lines);
2238 view->ops->select(view, &view->line[view->lineno]);
2242 /* Repaint the old "current" line if we be scrolling */
2243 if (ABS(steps) < view->height)
2244 draw_view_line(view, view->lineno - steps - view->offset);
2247 do_scroll_view(view, scroll_steps);
2251 /* Draw the current line */
2252 draw_view_line(view, view->lineno - view->offset);
2254 redrawwin(view->win);
2255 wrefresh(view->win);
2264 static void search_view(struct view *view, enum request request);
2267 find_next_line(struct view *view, unsigned long lineno, struct line *line)
2269 assert(view_is_displayed(view));
2271 if (!view->ops->grep(view, line))
2274 if (lineno - view->offset >= view->height) {
2275 view->offset = lineno;
2276 view->lineno = lineno;
2280 unsigned long old_lineno = view->lineno - view->offset;
2282 view->lineno = lineno;
2283 draw_view_line(view, old_lineno);
2285 draw_view_line(view, view->lineno - view->offset);
2286 redrawwin(view->win);
2287 wrefresh(view->win);
2290 report("Line %ld matches '%s'", lineno + 1, view->grep);
2295 find_next(struct view *view, enum request request)
2297 unsigned long lineno = view->lineno;
2302 report("No previous search");
2304 search_view(view, request);
2314 case REQ_SEARCH_BACK:
2323 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2324 lineno += direction;
2326 /* Note, lineno is unsigned long so will wrap around in which case it
2327 * will become bigger than view->lines. */
2328 for (; lineno < view->lines; lineno += direction) {
2329 struct line *line = &view->line[lineno];
2331 if (find_next_line(view, lineno, line))
2335 report("No match found for '%s'", view->grep);
2339 search_view(struct view *view, enum request request)
2344 regfree(view->regex);
2347 view->regex = calloc(1, sizeof(*view->regex));
2352 regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2353 if (regex_err != 0) {
2354 char buf[SIZEOF_STR] = "unknown error";
2356 regerror(regex_err, view->regex, buf, sizeof(buf));
2357 report("Search failed: %s", buf);
2361 string_copy(view->grep, opt_search);
2363 find_next(view, request);
2367 * Incremental updating
2371 reset_view(struct view *view)
2375 for (i = 0; i < view->lines; i++)
2376 free(view->line[i].data);
2383 view->line_size = 0;
2384 view->line_alloc = 0;
2389 free_argv(const char *argv[])
2393 for (argc = 0; argv[argc]; argc++)
2394 free((void *) argv[argc]);
2398 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
2400 char buf[SIZEOF_STR];
2402 bool noreplace = flags == FORMAT_NONE;
2404 free_argv(dst_argv);
2406 for (argc = 0; src_argv[argc]; argc++) {
2407 const char *arg = src_argv[argc];
2411 char *next = strstr(arg, "%(");
2412 int len = next - arg;
2415 if (!next || noreplace) {
2416 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
2421 } else if (!prefixcmp(next, "%(directory)")) {
2424 } else if (!prefixcmp(next, "%(file)")) {
2427 } else if (!prefixcmp(next, "%(ref)")) {
2428 value = *opt_ref ? opt_ref : "HEAD";
2430 } else if (!prefixcmp(next, "%(head)")) {
2433 } else if (!prefixcmp(next, "%(commit)")) {
2436 } else if (!prefixcmp(next, "%(blob)")) {
2440 report("Unknown replacement: `%s`", next);
2444 if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
2447 arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
2450 dst_argv[argc] = strdup(buf);
2451 if (!dst_argv[argc])
2455 dst_argv[argc] = NULL;
2457 return src_argv[argc] == NULL;
2461 format_command(char dst[], const char *src_argv[], enum format_flags flags)
2463 const char *dst_argv[SIZEOF_ARG * 2] = { NULL };
2467 if (!format_argv(dst_argv, src_argv, flags)) {
2468 free_argv(dst_argv);
2472 for (argc = 0; dst_argv[argc] && bufsize < SIZEOF_STR; argc++) {
2474 dst[bufsize++] = ' ';
2475 bufsize = sq_quote(dst, bufsize, dst_argv[argc]);
2478 if (bufsize < SIZEOF_STR)
2480 free_argv(dst_argv);
2482 return src_argv[argc] == NULL && bufsize < SIZEOF_STR;
2486 end_update(struct view *view, bool force)
2490 while (!view->ops->read(view, NULL))
2493 set_nonblocking_input(FALSE);
2494 done_io(view->pipe);
2499 setup_update(struct view *view, const char *vid)
2501 set_nonblocking_input(TRUE);
2503 string_copy_rev(view->vid, vid);
2504 view->pipe = &view->io;
2505 view->start_time = time(NULL);
2509 prepare_update(struct view *view, const char *argv[], const char *dir,
2510 enum format_flags flags)
2513 end_update(view, TRUE);
2514 return init_io_rd(&view->io, argv, dir, flags);
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))
2529 } else if (refresh) {
2530 if (!start_io(&view->io))
2534 if (view == VIEW(REQ_VIEW_TREE) && strcmp(view->vid, view->id))
2537 if (!run_io_rd(&view->io, view->ops->argv, FORMAT_ALL))
2540 /* Put the current ref_* value to the view title ref
2541 * member. This is needed by the blob view. Most other
2542 * views sets it automatically after loading because the
2543 * first line is a commit line. */
2544 string_copy_rev(view->ref, view->id);
2547 setup_update(view, view->id);
2552 #define ITEM_CHUNK_SIZE 256
2554 realloc_items(void *mem, size_t *size, size_t new_size, size_t item_size)
2556 size_t num_chunks = *size / ITEM_CHUNK_SIZE;
2557 size_t num_chunks_new = (new_size + ITEM_CHUNK_SIZE - 1) / ITEM_CHUNK_SIZE;
2559 if (mem == NULL || num_chunks != num_chunks_new) {
2560 *size = num_chunks_new * ITEM_CHUNK_SIZE;
2561 mem = realloc(mem, *size * item_size);
2567 static struct line *
2568 realloc_lines(struct view *view, size_t line_size)
2570 size_t alloc = view->line_alloc;
2571 struct line *tmp = realloc_items(view->line, &alloc, line_size,
2572 sizeof(*view->line));
2578 view->line_alloc = alloc;
2579 view->line_size = line_size;
2584 update_view(struct view *view)
2586 char out_buffer[BUFSIZ * 2];
2588 /* The number of lines to read. If too low it will cause too much
2589 * redrawing (and possible flickering), if too high responsiveness
2591 unsigned long lines = view->height;
2592 int redraw_from = -1;
2597 /* Only redraw if lines are visible. */
2598 if (view->offset + view->height >= view->lines)
2599 redraw_from = view->lines - view->offset;
2601 /* FIXME: This is probably not perfect for backgrounded views. */
2602 if (!realloc_lines(view, view->lines + lines))
2605 while ((line = io_gets(view->pipe))) {
2606 size_t linelen = strlen(line);
2609 line[linelen - 1] = 0;
2611 if (opt_iconv != ICONV_NONE) {
2612 ICONV_CONST char *inbuf = line;
2613 size_t inlen = linelen;
2615 char *outbuf = out_buffer;
2616 size_t outlen = sizeof(out_buffer);
2620 ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2621 if (ret != (size_t) -1) {
2623 linelen = strlen(out_buffer);
2627 if (!view->ops->read(view, line))
2637 lines = view->lines;
2638 for (digits = 0; lines; digits++)
2641 /* Keep the displayed view in sync with line number scaling. */
2642 if (digits != view->digits) {
2643 view->digits = digits;
2648 if (io_error(view->pipe)) {
2649 report("Failed to read: %s", io_strerror(view->pipe));
2650 end_update(view, TRUE);
2652 } else if (io_eof(view->pipe)) {
2654 end_update(view, FALSE);
2657 if (!view_is_displayed(view))
2660 if (view == VIEW(REQ_VIEW_TREE)) {
2661 /* Clear the view and redraw everything since the tree sorting
2662 * might have rearranged things. */
2665 } else if (redraw_from >= 0) {
2666 /* If this is an incremental update, redraw the previous line
2667 * since for commits some members could have changed when
2668 * loading the main view. */
2669 if (redraw_from > 0)
2672 /* Since revision graph visualization requires knowledge
2673 * about the parent commit, it causes a further one-off
2674 * needed to be redrawn for incremental updates. */
2675 if (redraw_from > 0 && opt_rev_graph)
2678 /* Incrementally draw avoids flickering. */
2679 redraw_view_from(view, redraw_from);
2682 if (view == VIEW(REQ_VIEW_BLAME))
2683 redraw_view_dirty(view);
2685 /* Update the title _after_ the redraw so that if the redraw picks up a
2686 * commit reference in view->ref it'll be available here. */
2687 update_view_title(view);
2691 report("Allocation failure");
2692 end_update(view, TRUE);
2696 static struct line *
2697 add_line_data(struct view *view, void *data, enum line_type type)
2699 struct line *line = &view->line[view->lines++];
2701 memset(line, 0, sizeof(*line));
2708 static struct line *
2709 add_line_text(struct view *view, const char *text, enum line_type type)
2711 char *data = text ? strdup(text) : NULL;
2713 return data ? add_line_data(view, data, type) : NULL;
2722 OPEN_DEFAULT = 0, /* Use default view switching. */
2723 OPEN_SPLIT = 1, /* Split current view. */
2724 OPEN_BACKGROUNDED = 2, /* Backgrounded. */
2725 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
2726 OPEN_NOMAXIMIZE = 8, /* Do not maximize the current view. */
2727 OPEN_REFRESH = 16, /* Refresh view using previous command. */
2728 OPEN_PREPARED = 32, /* Open already prepared command. */
2732 open_view(struct view *prev, enum request request, enum open_flags flags)
2734 bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
2735 bool split = !!(flags & OPEN_SPLIT);
2736 bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
2737 bool nomaximize = !!(flags & (OPEN_NOMAXIMIZE | OPEN_REFRESH));
2738 struct view *view = VIEW(request);
2739 int nviews = displayed_views();
2740 struct view *base_view = display[0];
2742 if (view == prev && nviews == 1 && !reload) {
2743 report("Already in %s view", view->name);
2747 if (view->git_dir && !opt_git_dir[0]) {
2748 report("The %s view is disabled in pager view", view->name);
2756 } else if (!nomaximize) {
2757 /* Maximize the current view. */
2758 memset(display, 0, sizeof(display));
2760 display[current_view] = view;
2763 /* Resize the view when switching between split- and full-screen,
2764 * or when switching between two different full-screen views. */
2765 if (nviews != displayed_views() ||
2766 (nviews == 1 && base_view != display[0]))
2770 end_update(view, TRUE);
2772 if (view->ops->open) {
2773 if (!view->ops->open(view)) {
2774 report("Failed to load %s view", view->name);
2778 } else if ((reload || strcmp(view->vid, view->id)) &&
2779 !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
2780 report("Failed to load %s view", view->name);
2784 if (split && prev->lineno - prev->offset >= prev->height) {
2785 /* Take the title line into account. */
2786 int lines = prev->lineno - prev->offset - prev->height + 1;
2788 /* Scroll the view that was split if the current line is
2789 * outside the new limited view. */
2790 do_scroll_view(prev, lines);
2793 if (prev && view != prev) {
2794 if (split && !backgrounded) {
2795 /* "Blur" the previous view. */
2796 update_view_title(prev);
2799 view->parent = prev;
2802 if (view->pipe && view->lines == 0) {
2803 /* Clear the old view and let the incremental updating refill
2807 } else if (view_is_displayed(view)) {
2812 /* If the view is backgrounded the above calls to report()
2813 * won't redraw the view title. */
2815 update_view_title(view);
2819 open_external_viewer(const char *argv[], const char *dir)
2821 def_prog_mode(); /* save current tty modes */
2822 endwin(); /* restore original tty modes */
2823 run_io_fg(argv, dir);
2824 fprintf(stderr, "Press Enter to continue");
2831 open_mergetool(const char *file)
2833 const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
2835 open_external_viewer(mergetool_argv, NULL);
2839 open_editor(bool from_root, const char *file)
2841 const char *editor_argv[] = { "vi", file, NULL };
2844 editor = getenv("GIT_EDITOR");
2845 if (!editor && *opt_editor)
2846 editor = opt_editor;
2848 editor = getenv("VISUAL");
2850 editor = getenv("EDITOR");
2854 editor_argv[0] = editor;
2855 open_external_viewer(editor_argv, from_root ? opt_cdup : NULL);
2859 open_run_request(enum request request)
2861 struct run_request *req = get_run_request(request);
2862 const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
2865 report("Unknown run request");
2869 if (format_argv(argv, req->argv, FORMAT_ALL))
2870 open_external_viewer(argv, NULL);
2875 * User request switch noodle
2879 view_driver(struct view *view, enum request request)
2883 if (request == REQ_NONE) {
2888 if (request > REQ_NONE) {
2889 open_run_request(request);
2890 /* FIXME: When all views can refresh always do this. */
2891 if (view == VIEW(REQ_VIEW_STATUS) ||
2892 view == VIEW(REQ_VIEW_MAIN) ||
2893 view == VIEW(REQ_VIEW_LOG) ||
2894 view == VIEW(REQ_VIEW_STAGE))
2895 request = REQ_REFRESH;
2900 if (view && view->lines) {
2901 request = view->ops->request(view, request, &view->line[view->lineno]);
2902 if (request == REQ_NONE)
2909 case REQ_MOVE_PAGE_UP:
2910 case REQ_MOVE_PAGE_DOWN:
2911 case REQ_MOVE_FIRST_LINE:
2912 case REQ_MOVE_LAST_LINE:
2913 move_view(view, request);
2916 case REQ_SCROLL_LINE_DOWN:
2917 case REQ_SCROLL_LINE_UP:
2918 case REQ_SCROLL_PAGE_DOWN:
2919 case REQ_SCROLL_PAGE_UP:
2920 scroll_view(view, request);
2923 case REQ_VIEW_BLAME:
2925 report("No file chosen, press %s to open tree view",
2926 get_key(REQ_VIEW_TREE));
2929 open_view(view, request, OPEN_DEFAULT);
2934 report("No file chosen, press %s to open tree view",
2935 get_key(REQ_VIEW_TREE));
2938 open_view(view, request, OPEN_DEFAULT);
2941 case REQ_VIEW_PAGER:
2942 if (!opt_pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
2943 report("No pager content, press %s to run command from prompt",
2944 get_key(REQ_PROMPT));
2947 open_view(view, request, OPEN_DEFAULT);
2950 case REQ_VIEW_STAGE:
2951 if (!VIEW(REQ_VIEW_STAGE)->lines) {
2952 report("No stage content, press %s to open the status view and choose file",
2953 get_key(REQ_VIEW_STATUS));
2956 open_view(view, request, OPEN_DEFAULT);
2959 case REQ_VIEW_STATUS:
2960 if (opt_is_inside_work_tree == FALSE) {
2961 report("The status view requires a working tree");
2964 open_view(view, request, OPEN_DEFAULT);
2972 open_view(view, request, OPEN_DEFAULT);
2977 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
2979 if ((view == VIEW(REQ_VIEW_DIFF) &&
2980 view->parent == VIEW(REQ_VIEW_MAIN)) ||
2981 (view == VIEW(REQ_VIEW_DIFF) &&
2982 view->parent == VIEW(REQ_VIEW_BLAME)) ||
2983 (view == VIEW(REQ_VIEW_STAGE) &&
2984 view->parent == VIEW(REQ_VIEW_STATUS)) ||
2985 (view == VIEW(REQ_VIEW_BLOB) &&
2986 view->parent == VIEW(REQ_VIEW_TREE))) {
2989 view = view->parent;
2990 line = view->lineno;
2991 move_view(view, request);
2992 if (view_is_displayed(view))
2993 update_view_title(view);
2994 if (line != view->lineno)
2995 view->ops->request(view, REQ_ENTER,
2996 &view->line[view->lineno]);
2999 move_view(view, request);
3005 int nviews = displayed_views();
3006 int next_view = (current_view + 1) % nviews;
3008 if (next_view == current_view) {
3009 report("Only one view is displayed");
3013 current_view = next_view;
3014 /* Blur out the title of the previous view. */
3015 update_view_title(view);
3020 report("Refreshing is not yet supported for the %s view", view->name);
3024 if (displayed_views() == 2)
3025 open_view(view, VIEW_REQ(view), OPEN_DEFAULT);
3028 case REQ_TOGGLE_LINENO:
3029 opt_line_number = !opt_line_number;
3033 case REQ_TOGGLE_DATE:
3034 opt_date = !opt_date;
3038 case REQ_TOGGLE_AUTHOR:
3039 opt_author = !opt_author;
3043 case REQ_TOGGLE_REV_GRAPH:
3044 opt_rev_graph = !opt_rev_graph;
3048 case REQ_TOGGLE_REFS:
3049 opt_show_refs = !opt_show_refs;
3054 case REQ_SEARCH_BACK:
3055 search_view(view, request);
3060 find_next(view, request);
3063 case REQ_STOP_LOADING:
3064 for (i = 0; i < ARRAY_SIZE(views); i++) {
3067 report("Stopped loading the %s view", view->name),
3068 end_update(view, TRUE);
3072 case REQ_SHOW_VERSION:
3073 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3076 case REQ_SCREEN_RESIZE:
3079 case REQ_SCREEN_REDRAW:
3084 report("Nothing to edit");
3088 report("Nothing to enter");
3091 case REQ_VIEW_CLOSE:
3092 /* XXX: Mark closed views by letting view->parent point to the
3093 * view itself. Parents to closed view should never be
3096 view->parent->parent != view->parent) {
3097 memset(display, 0, sizeof(display));
3099 display[current_view] = view->parent;
3100 view->parent = view;
3111 report("Unknown key, press 'h' for help");
3124 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3126 char *text = line->data;
3128 if (opt_line_number && draw_lineno(view, lineno))
3131 draw_text(view, line->type, text, TRUE);
3136 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3138 char refbuf[SIZEOF_STR];
3142 if (!string_format(refbuf, "git describe %s 2>/dev/null", commit_id))
3145 pipe = popen(refbuf, "r");
3149 if ((ref = fgets(refbuf, sizeof(refbuf), pipe)))
3150 ref = chomp_string(ref);
3156 /* This is the only fatal call, since it can "corrupt" the buffer. */
3157 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
3164 add_pager_refs(struct view *view, struct line *line)
3166 char buf[SIZEOF_STR];
3167 char *commit_id = (char *)line->data + STRING_SIZE("commit ");
3169 size_t bufpos = 0, refpos = 0;
3170 const char *sep = "Refs: ";
3171 bool is_tag = FALSE;
3173 assert(line->type == LINE_COMMIT);
3175 refs = get_refs(commit_id);
3177 if (view == VIEW(REQ_VIEW_DIFF))
3178 goto try_add_describe_ref;
3183 struct ref *ref = refs[refpos];
3184 const char *fmt = ref->tag ? "%s[%s]" :
3185 ref->remote ? "%s<%s>" : "%s%s";
3187 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
3192 } while (refs[refpos++]->next);
3194 if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
3195 try_add_describe_ref:
3196 /* Add <tag>-g<commit_id> "fake" reference. */
3197 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
3204 if (!realloc_lines(view, view->line_size + 1))
3207 add_line_text(view, buf, LINE_PP_REFS);
3211 pager_read(struct view *view, char *data)
3218 line = add_line_text(view, data, get_line_type(data));
3222 if (line->type == LINE_COMMIT &&
3223 (view == VIEW(REQ_VIEW_DIFF) ||
3224 view == VIEW(REQ_VIEW_LOG)))
3225 add_pager_refs(view, line);
3231 pager_request(struct view *view, enum request request, struct line *line)
3235 if (request != REQ_ENTER)
3238 if (line->type == LINE_COMMIT &&
3239 (view == VIEW(REQ_VIEW_LOG) ||
3240 view == VIEW(REQ_VIEW_PAGER))) {
3241 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3245 /* Always scroll the view even if it was split. That way
3246 * you can use Enter to scroll through the log view and
3247 * split open each commit diff. */
3248 scroll_view(view, REQ_SCROLL_LINE_DOWN);
3250 /* FIXME: A minor workaround. Scrolling the view will call report("")
3251 * but if we are scrolling a non-current view this won't properly
3252 * update the view title. */
3254 update_view_title(view);
3260 pager_grep(struct view *view, struct line *line)
3263 char *text = line->data;
3268 if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
3275 pager_select(struct view *view, struct line *line)
3277 if (line->type == LINE_COMMIT) {
3278 char *text = (char *)line->data + STRING_SIZE("commit ");
3280 if (view != VIEW(REQ_VIEW_PAGER))
3281 string_copy_rev(view->ref, text);
3282 string_copy_rev(ref_commit, text);
3286 static struct view_ops pager_ops = {
3297 static const char *log_argv[SIZEOF_ARG] = {
3298 "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3302 log_request(struct view *view, enum request request, struct line *line)
3307 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
3310 return pager_request(view, request, line);
3314 static struct view_ops log_ops = {
3325 static const char *diff_argv[SIZEOF_ARG] = {
3326 "git", "show", "--pretty=fuller", "--no-color", "--root",
3327 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
3330 static struct view_ops diff_ops = {
3346 help_open(struct view *view)
3349 int lines = ARRAY_SIZE(req_info) + 2;
3352 if (view->lines > 0)
3355 for (i = 0; i < ARRAY_SIZE(req_info); i++)
3356 if (!req_info[i].request)
3359 lines += run_requests + 1;
3361 view->line = calloc(lines, sizeof(*view->line));
3365 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
3367 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
3370 if (req_info[i].request == REQ_NONE)
3373 if (!req_info[i].request) {
3374 add_line_text(view, "", LINE_DEFAULT);
3375 add_line_text(view, req_info[i].help, LINE_DEFAULT);
3379 key = get_key(req_info[i].request);
3381 key = "(no key defined)";
3383 if (!string_format(buf, " %-25s %s", key, req_info[i].help))
3386 add_line_text(view, buf, LINE_DEFAULT);
3390 add_line_text(view, "", LINE_DEFAULT);
3391 add_line_text(view, "External commands:", LINE_DEFAULT);
3394 for (i = 0; i < run_requests; i++) {
3395 struct run_request *req = get_run_request(REQ_NONE + i + 1);
3397 char cmd[SIZEOF_STR];
3404 key = get_key_name(req->key);
3406 key = "(no key defined)";
3408 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
3409 if (!string_format_from(cmd, &bufpos, "%s%s",
3410 argc ? " " : "", req->argv[argc]))
3413 if (!string_format(buf, " %-10s %-14s `%s`",
3414 keymap_table[req->keymap].name, key, cmd))
3417 add_line_text(view, buf, LINE_DEFAULT);
3423 static struct view_ops help_ops = {
3439 struct tree_stack_entry {
3440 struct tree_stack_entry *prev; /* Entry below this in the stack */
3441 unsigned long lineno; /* Line number to restore */
3442 char *name; /* Position of name in opt_path */
3445 /* The top of the path stack. */
3446 static struct tree_stack_entry *tree_stack = NULL;
3447 unsigned long tree_lineno = 0;
3450 pop_tree_stack_entry(void)
3452 struct tree_stack_entry *entry = tree_stack;
3454 tree_lineno = entry->lineno;
3456 tree_stack = entry->prev;
3461 push_tree_stack_entry(const char *name, unsigned long lineno)
3463 struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
3464 size_t pathlen = strlen(opt_path);
3469 entry->prev = tree_stack;
3470 entry->name = opt_path + pathlen;
3473 if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
3474 pop_tree_stack_entry();
3478 /* Move the current line to the first tree entry. */
3480 entry->lineno = lineno;
3483 /* Parse output from git-ls-tree(1):
3485 * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile
3486 * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README
3487 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
3488 * 100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38 web.conf
3491 #define SIZEOF_TREE_ATTR \
3492 STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t")
3494 #define TREE_UP_FORMAT "040000 tree %s\t.."
3497 tree_compare_entry(enum line_type type1, const char *name1,
3498 enum line_type type2, const char *name2)
3500 if (type1 != type2) {
3501 if (type1 == LINE_TREE_DIR)
3506 return strcmp(name1, name2);
3510 tree_path(struct line *line)
3512 const char *path = line->data;
3514 return path + SIZEOF_TREE_ATTR;
3518 tree_read(struct view *view, char *text)
3520 size_t textlen = text ? strlen(text) : 0;
3521 char buf[SIZEOF_STR];
3523 enum line_type type;
3524 bool first_read = view->lines == 0;
3528 if (textlen <= SIZEOF_TREE_ATTR)
3531 type = text[STRING_SIZE("100644 ")] == 't'
3532 ? LINE_TREE_DIR : LINE_TREE_FILE;
3535 /* Add path info line */
3536 if (!string_format(buf, "Directory path /%s", opt_path) ||
3537 !realloc_lines(view, view->line_size + 1) ||
3538 !add_line_text(view, buf, LINE_DEFAULT))
3541 /* Insert "link" to parent directory. */
3543 if (!string_format(buf, TREE_UP_FORMAT, view->ref) ||
3544 !realloc_lines(view, view->line_size + 1) ||
3545 !add_line_text(view, buf, LINE_TREE_DIR))
3550 /* Strip the path part ... */
3552 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
3553 size_t striplen = strlen(opt_path);
3554 char *path = text + SIZEOF_TREE_ATTR;
3556 if (pathlen > striplen)
3557 memmove(path, path + striplen,
3558 pathlen - striplen + 1);
3561 /* Skip "Directory ..." and ".." line. */
3562 for (pos = 1 + !!*opt_path; pos < view->lines; pos++) {
3563 struct line *line = &view->line[pos];
3564 const char *path1 = tree_path(line);
3565 char *path2 = text + SIZEOF_TREE_ATTR;
3566 int cmp = tree_compare_entry(line->type, path1, type, path2);
3571 text = strdup(text);
3575 if (view->lines > pos)
3576 memmove(&view->line[pos + 1], &view->line[pos],
3577 (view->lines - pos) * sizeof(*line));
3579 line = &view->line[pos];
3586 if (!add_line_text(view, text, type))
3589 if (tree_lineno > view->lineno) {
3590 view->lineno = tree_lineno;
3598 tree_request(struct view *view, enum request request, struct line *line)
3600 enum open_flags flags;
3603 case REQ_VIEW_BLAME:
3604 if (line->type != LINE_TREE_FILE) {
3605 report("Blame only supported for files");
3609 string_copy(opt_ref, view->vid);
3613 if (line->type != LINE_TREE_FILE) {
3614 report("Edit only supported for files");
3615 } else if (!is_head_commit(view->vid)) {
3616 report("Edit only supported for files in the current work tree");
3618 open_editor(TRUE, opt_file);
3622 case REQ_TREE_PARENT:
3624 /* quit view if at top of tree */
3625 return REQ_VIEW_CLOSE;
3628 line = &view->line[1];
3638 /* Cleanup the stack if the tree view is at a different tree. */
3639 while (!*opt_path && tree_stack)
3640 pop_tree_stack_entry();
3642 switch (line->type) {
3644 /* Depending on whether it is a subdir or parent (updir?) link
3645 * mangle the path buffer. */
3646 if (line == &view->line[1] && *opt_path) {
3647 pop_tree_stack_entry();
3650 const char *basename = tree_path(line);
3652 push_tree_stack_entry(basename, view->lineno);
3655 /* Trees and subtrees share the same ID, so they are not not
3656 * unique like blobs. */
3657 flags = OPEN_RELOAD;
3658 request = REQ_VIEW_TREE;
3661 case LINE_TREE_FILE:
3662 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
3663 request = REQ_VIEW_BLOB;
3670 open_view(view, request, flags);
3671 if (request == REQ_VIEW_TREE) {
3672 view->lineno = tree_lineno;
3679 tree_select(struct view *view, struct line *line)
3681 char *text = (char *)line->data + STRING_SIZE("100644 blob ");
3683 if (line->type == LINE_TREE_FILE) {
3684 string_copy_rev(ref_blob, text);
3685 string_format(opt_file, "%s%s", opt_path, tree_path(line));
3687 } else if (line->type != LINE_TREE_DIR) {
3691 string_copy_rev(view->ref, text);
3694 static const char *tree_argv[SIZEOF_ARG] = {
3695 "git", "ls-tree", "%(commit)", "%(directory)", NULL
3698 static struct view_ops tree_ops = {
3710 blob_read(struct view *view, char *line)
3714 return add_line_text(view, line, LINE_DEFAULT) != NULL;
3717 static const char *blob_argv[SIZEOF_ARG] = {
3718 "git", "cat-file", "blob", "%(blob)", NULL
3721 static struct view_ops blob_ops = {
3735 * Loading the blame view is a two phase job:
3737 * 1. File content is read either using opt_file from the
3738 * filesystem or using git-cat-file.
3739 * 2. Then blame information is incrementally added by
3740 * reading output from git-blame.
3743 static const char *blame_head_argv[] = {
3744 "git", "blame", "--incremental", "--", "%(file)", NULL
3747 static const char *blame_ref_argv[] = {
3748 "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
3751 static const char *blame_cat_file_argv[] = {
3752 "git", "cat-file", "blob", "%(ref):%(file)", NULL
3755 struct blame_commit {
3756 char id[SIZEOF_REV]; /* SHA1 ID. */
3757 char title[128]; /* First line of the commit message. */
3758 char author[75]; /* Author of the commit. */
3759 struct tm time; /* Date from the author ident. */
3760 char filename[128]; /* Name of file. */
3764 struct blame_commit *commit;
3769 blame_open(struct view *view)
3771 if (*opt_ref || !init_io_fd(&view->io, fopen(opt_file, "r"))) {
3772 if (!run_io_rd(&view->io, blame_cat_file_argv, FORMAT_ALL))
3776 setup_update(view, opt_file);
3777 string_format(view->ref, "%s ...", opt_file);
3782 static struct blame_commit *
3783 get_blame_commit(struct view *view, const char *id)
3787 for (i = 0; i < view->lines; i++) {
3788 struct blame *blame = view->line[i].data;
3793 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
3794 return blame->commit;
3798 struct blame_commit *commit = calloc(1, sizeof(*commit));
3801 string_ncopy(commit->id, id, SIZEOF_REV);
3807 parse_number(const char **posref, size_t *number, size_t min, size_t max)
3809 const char *pos = *posref;
3812 pos = strchr(pos + 1, ' ');
3813 if (!pos || !isdigit(pos[1]))
3815 *number = atoi(pos + 1);
3816 if (*number < min || *number > max)
3823 static struct blame_commit *
3824 parse_blame_commit(struct view *view, const char *text, int *blamed)
3826 struct blame_commit *commit;
3827 struct blame *blame;
3828 const char *pos = text + SIZEOF_REV - 1;
3832 if (strlen(text) <= SIZEOF_REV || *pos != ' ')
3835 if (!parse_number(&pos, &lineno, 1, view->lines) ||
3836 !parse_number(&pos, &group, 1, view->lines - lineno + 1))
3839 commit = get_blame_commit(view, text);
3845 struct line *line = &view->line[lineno + group - 1];
3848 blame->commit = commit;
3856 blame_read_file(struct view *view, const char *line, bool *read_file)
3859 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
3862 if (view->lines == 0 && !view->parent)
3863 die("No blame exist for %s", view->vid);
3865 if (view->lines == 0 || !run_io_rd(&io, argv, FORMAT_ALL)) {
3866 report("Failed to load blame data");
3870 done_io(view->pipe);
3876 size_t linelen = strlen(line);
3877 struct blame *blame = malloc(sizeof(*blame) + linelen);
3879 blame->commit = NULL;
3880 strncpy(blame->text, line, linelen);
3881 blame->text[linelen] = 0;
3882 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
3887 match_blame_header(const char *name, char **line)
3889 size_t namelen = strlen(name);
3890 bool matched = !strncmp(name, *line, namelen);
3899 blame_read(struct view *view, char *line)
3901 static struct blame_commit *commit = NULL;
3902 static int blamed = 0;
3903 static time_t author_time;
3904 static bool read_file = TRUE;
3907 return blame_read_file(view, line, &read_file);
3914 string_format(view->ref, "%s", view->vid);
3915 if (view_is_displayed(view)) {
3916 update_view_title(view);
3917 redraw_view_from(view, 0);
3923 commit = parse_blame_commit(view, line, &blamed);
3924 string_format(view->ref, "%s %2d%%", view->vid,
3925 blamed * 100 / view->lines);
3927 } else if (match_blame_header("author ", &line)) {
3928 string_ncopy(commit->author, line, strlen(line));
3930 } else if (match_blame_header("author-time ", &line)) {
3931 author_time = (time_t) atol(line);
3933 } else if (match_blame_header("author-tz ", &line)) {
3936 tz = ('0' - line[1]) * 60 * 60 * 10;
3937 tz += ('0' - line[2]) * 60 * 60;
3938 tz += ('0' - line[3]) * 60;
3939 tz += ('0' - line[4]) * 60;
3945 gmtime_r(&author_time, &commit->time);
3947 } else if (match_blame_header("summary ", &line)) {
3948 string_ncopy(commit->title, line, strlen(line));
3950 } else if (match_blame_header("filename ", &line)) {
3951 string_ncopy(commit->filename, line, strlen(line));
3959 blame_draw(struct view *view, struct line *line, unsigned int lineno)
3961 struct blame *blame = line->data;
3962 struct tm *time = NULL;
3963 const char *id = NULL, *author = NULL;
3965 if (blame->commit && *blame->commit->filename) {
3966 id = blame->commit->id;
3967 author = blame->commit->author;
3968 time = &blame->commit->time;
3971 if (opt_date && draw_date(view, time))
3975 draw_field(view, LINE_MAIN_AUTHOR, author, opt_author_cols, TRUE))
3978 if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
3981 if (draw_lineno(view, lineno))
3984 draw_text(view, LINE_DEFAULT, blame->text, TRUE);
3989 blame_request(struct view *view, enum request request, struct line *line)
3991 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
3992 struct blame *blame = line->data;
3995 case REQ_VIEW_BLAME:
3996 if (!blame->commit || !strcmp(blame->commit->id, NULL_ID)) {
3997 report("Commit ID unknown");
4000 string_copy(opt_ref, blame->commit->id);
4001 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4005 if (!blame->commit) {
4006 report("No commit loaded yet");
4010 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
4011 !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
4014 if (!strcmp(blame->commit->id, NULL_ID)) {
4015 struct view *diff = VIEW(REQ_VIEW_DIFF);
4016 const char *diff_index_argv[] = {
4017 "git", "diff-index", "--root", "--cached",
4018 "--patch-with-stat", "-C", "-M",
4019 "HEAD", "--", view->vid, NULL
4022 if (!prepare_update(diff, diff_index_argv, NULL, FORMAT_DASH)) {
4023 report("Failed to allocate diff command");
4026 flags |= OPEN_PREPARED;
4029 open_view(view, REQ_VIEW_DIFF, flags);
4040 blame_grep(struct view *view, struct line *line)
4042 struct blame *blame = line->data;
4043 struct blame_commit *commit = blame->commit;
4046 #define MATCH(text, on) \
4047 (on && *text && regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4050 char buf[DATE_COLS + 1];
4052 if (MATCH(commit->title, 1) ||
4053 MATCH(commit->author, opt_author) ||
4054 MATCH(commit->id, opt_date))
4057 if (strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time) &&
4062 return MATCH(blame->text, 1);
4068 blame_select(struct view *view, struct line *line)
4070 struct blame *blame = line->data;
4071 struct blame_commit *commit = blame->commit;
4076 if (!strcmp(commit->id, NULL_ID))
4077 string_ncopy(ref_commit, "HEAD", 4);
4079 string_copy_rev(ref_commit, commit->id);
4082 static struct view_ops blame_ops = {
4101 char rev[SIZEOF_REV];
4102 char name[SIZEOF_STR];
4106 char rev[SIZEOF_REV];
4107 char name[SIZEOF_STR];
4111 static char status_onbranch[SIZEOF_STR];
4112 static struct status stage_status;
4113 static enum line_type stage_line_type;
4114 static size_t stage_chunks;
4115 static int *stage_chunk;
4117 /* This should work even for the "On branch" line. */
4119 status_has_none(struct view *view, struct line *line)
4121 return line < view->line + view->lines && !line[1].data;
4124 /* Get fields from the diff line:
4125 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
4128 status_get_diff(struct status *file, const char *buf, size_t bufsize)
4130 const char *old_mode = buf + 1;
4131 const char *new_mode = buf + 8;
4132 const char *old_rev = buf + 15;
4133 const char *new_rev = buf + 56;
4134 const char *status = buf + 97;
4137 old_mode[-1] != ':' ||
4138 new_mode[-1] != ' ' ||
4139 old_rev[-1] != ' ' ||
4140 new_rev[-1] != ' ' ||
4144 file->status = *status;
4146 string_copy_rev(file->old.rev, old_rev);
4147 string_copy_rev(file->new.rev, new_rev);
4149 file->old.mode = strtoul(old_mode, NULL, 8);
4150 file->new.mode = strtoul(new_mode, NULL, 8);
4152 file->old.name[0] = file->new.name[0] = 0;
4158 status_run(struct view *view, const char cmd[], char status, enum line_type type)
4160 struct status *file = NULL;
4161 struct status *unmerged = NULL;
4162 char buf[SIZEOF_STR * 4];
4166 pipe = popen(cmd, "r");
4170 add_line_data(view, NULL, type);
4172 while (!feof(pipe) && !ferror(pipe)) {
4176 readsize = fread(buf + bufsize, 1, sizeof(buf) - bufsize, pipe);
4179 bufsize += readsize;
4181 /* Process while we have NUL chars. */
4182 while ((sep = memchr(buf, 0, bufsize))) {
4183 size_t sepsize = sep - buf + 1;
4186 if (!realloc_lines(view, view->line_size + 1))
4189 file = calloc(1, sizeof(*file));
4193 add_line_data(view, file, type);
4196 /* Parse diff info part. */
4198 file->status = status;
4200 string_copy(file->old.rev, NULL_ID);
4202 } else if (!file->status) {
4203 if (!status_get_diff(file, buf, sepsize))
4207 memmove(buf, sep + 1, bufsize);
4209 sep = memchr(buf, 0, bufsize);
4212 sepsize = sep - buf + 1;
4214 /* Collapse all 'M'odified entries that
4215 * follow a associated 'U'nmerged entry.
4217 if (file->status == 'U') {
4220 } else if (unmerged) {
4221 int collapse = !strcmp(buf, unmerged->new.name);
4232 /* Grab the old name for rename/copy. */
4233 if (!*file->old.name &&
4234 (file->status == 'R' || file->status == 'C')) {
4235 sepsize = sep - buf + 1;
4236 string_ncopy(file->old.name, buf, sepsize);
4238 memmove(buf, sep + 1, bufsize);
4240 sep = memchr(buf, 0, bufsize);
4243 sepsize = sep - buf + 1;
4246 /* git-ls-files just delivers a NUL separated
4247 * list of file names similar to the second half
4248 * of the git-diff-* output. */
4249 string_ncopy(file->new.name, buf, sepsize);
4250 if (!*file->old.name)
4251 string_copy(file->old.name, file->new.name);
4253 memmove(buf, sep + 1, bufsize);
4264 if (!view->line[view->lines - 1].data)
4265 add_line_data(view, NULL, LINE_STAT_NONE);
4271 /* Don't show unmerged entries in the staged section. */
4272 #define STATUS_DIFF_INDEX_CMD "git diff-index -z --diff-filter=ACDMRTXB --cached -M HEAD"
4273 #define STATUS_DIFF_FILES_CMD "git diff-files -z"
4274 #define STATUS_LIST_OTHER_CMD \
4275 "git ls-files -z --others --exclude-standard"
4276 #define STATUS_LIST_NO_HEAD_CMD \
4277 "git ls-files -z --cached --exclude-standard"
4279 #define STATUS_DIFF_INDEX_SHOW_CMD \
4280 "git diff-index --root --patch-with-stat -C -M --cached HEAD -- %s %s 2>/dev/null"
4282 #define STATUS_DIFF_FILES_SHOW_CMD \
4283 "git diff-files --root --patch-with-stat -C -M -- %s %s 2>/dev/null"
4285 #define STATUS_DIFF_NO_HEAD_SHOW_CMD \
4286 "git diff --no-color --patch-with-stat /dev/null %s 2>/dev/null"
4288 /* First parse staged info using git-diff-index(1), then parse unstaged
4289 * info using git-diff-files(1), and finally untracked files using
4290 * git-ls-files(1). */
4292 status_open(struct view *view)
4294 unsigned long prev_lineno = view->lineno;
4298 if (!realloc_lines(view, view->line_size + 7))
4301 add_line_data(view, NULL, LINE_STAT_HEAD);
4302 if (is_initial_commit())
4303 string_copy(status_onbranch, "Initial commit");
4304 else if (!*opt_head)
4305 string_copy(status_onbranch, "Not currently on any branch");
4306 else if (!string_format(status_onbranch, "On branch %s", opt_head))
4309 system("git update-index -q --refresh >/dev/null 2>/dev/null");
4311 if (is_initial_commit()) {
4312 if (!status_run(view, STATUS_LIST_NO_HEAD_CMD, 'A', LINE_STAT_STAGED))
4314 } else if (!status_run(view, STATUS_DIFF_INDEX_CMD, 0, LINE_STAT_STAGED)) {
4318 if (!status_run(view, STATUS_DIFF_FILES_CMD, 0, LINE_STAT_UNSTAGED) ||
4319 !status_run(view, STATUS_LIST_OTHER_CMD, '?', LINE_STAT_UNTRACKED))
4322 /* If all went well restore the previous line number to stay in
4323 * the context or select a line with something that can be
4325 if (prev_lineno >= view->lines)
4326 prev_lineno = view->lines - 1;
4327 while (prev_lineno < view->lines && !view->line[prev_lineno].data)
4329 while (prev_lineno > 0 && !view->line[prev_lineno].data)
4332 /* If the above fails, always skip the "On branch" line. */
4333 if (prev_lineno < view->lines)
4334 view->lineno = prev_lineno;
4338 if (view->lineno < view->offset)
4339 view->offset = view->lineno;
4340 else if (view->offset + view->height <= view->lineno)
4341 view->offset = view->lineno - view->height + 1;
4347 status_draw(struct view *view, struct line *line, unsigned int lineno)
4349 struct status *status = line->data;
4350 enum line_type type;
4354 switch (line->type) {
4355 case LINE_STAT_STAGED:
4356 type = LINE_STAT_SECTION;
4357 text = "Changes to be committed:";
4360 case LINE_STAT_UNSTAGED:
4361 type = LINE_STAT_SECTION;
4362 text = "Changed but not updated:";
4365 case LINE_STAT_UNTRACKED:
4366 type = LINE_STAT_SECTION;
4367 text = "Untracked files:";
4370 case LINE_STAT_NONE:
4371 type = LINE_DEFAULT;
4372 text = " (no files)";
4375 case LINE_STAT_HEAD:
4376 type = LINE_STAT_HEAD;
4377 text = status_onbranch;
4384 static char buf[] = { '?', ' ', ' ', ' ', 0 };
4386 buf[0] = status->status;
4387 if (draw_text(view, line->type, buf, TRUE))
4389 type = LINE_DEFAULT;
4390 text = status->new.name;
4393 draw_text(view, type, text, TRUE);
4398 status_enter(struct view *view, struct line *line)
4400 struct status *status = line->data;
4401 char oldpath[SIZEOF_STR] = "";
4402 char newpath[SIZEOF_STR] = "";
4405 enum open_flags split;
4407 if (line->type == LINE_STAT_NONE ||
4408 (!status && line[1].type == LINE_STAT_NONE)) {
4409 report("No file to diff");
4414 if (sq_quote(oldpath, 0, status->old.name) >= sizeof(oldpath))
4416 /* Diffs for unmerged entries are empty when pasing the
4417 * new path, so leave it empty. */
4418 if (status->status != 'U' &&
4419 sq_quote(newpath, 0, status->new.name) >= sizeof(newpath))
4424 line->type != LINE_STAT_UNTRACKED &&
4425 !string_format_from(opt_cmd, &cmdsize, "cd %s;", opt_cdup))
4428 switch (line->type) {
4429 case LINE_STAT_STAGED:
4430 if (is_initial_commit()) {
4431 if (!string_format_from(opt_cmd, &cmdsize,
4432 STATUS_DIFF_NO_HEAD_SHOW_CMD,
4436 if (!string_format_from(opt_cmd, &cmdsize,
4437 STATUS_DIFF_INDEX_SHOW_CMD,
4443 info = "Staged changes to %s";
4445 info = "Staged changes";
4448 case LINE_STAT_UNSTAGED:
4449 if (!string_format_from(opt_cmd, &cmdsize,
4450 STATUS_DIFF_FILES_SHOW_CMD, oldpath, newpath))
4453 info = "Unstaged changes to %s";
4455 info = "Unstaged changes";
4458 case LINE_STAT_UNTRACKED:
4463 report("No file to show");
4467 if (!suffixcmp(status->new.name, -1, "/")) {
4468 report("Cannot display a directory");
4472 opt_pipe = fopen(status->new.name, "r");
4473 info = "Untracked file %s";
4476 case LINE_STAT_HEAD:
4480 die("line type %d not handled in switch", line->type);
4483 split = view_is_displayed(view) ? OPEN_SPLIT : 0;
4484 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH | split);
4485 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
4487 stage_status = *status;
4489 memset(&stage_status, 0, sizeof(stage_status));
4492 stage_line_type = line->type;
4494 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
4501 status_exists(struct status *status, enum line_type type)
4503 struct view *view = VIEW(REQ_VIEW_STATUS);
4506 for (line = view->line; line < view->line + view->lines; line++) {
4507 struct status *pos = line->data;
4509 if (line->type == type && pos &&
4510 !strcmp(status->new.name, pos->new.name))
4519 status_update_prepare(enum line_type type)
4521 char cmd[SIZEOF_STR];
4525 type != LINE_STAT_UNTRACKED &&
4526 !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
4530 case LINE_STAT_STAGED:
4531 string_add(cmd, cmdsize, "git update-index -z --index-info");
4534 case LINE_STAT_UNSTAGED:
4535 case LINE_STAT_UNTRACKED:
4536 string_add(cmd, cmdsize, "git update-index -z --add --remove --stdin");
4540 die("line type %d not handled in switch", type);
4543 return popen(cmd, "w");
4547 status_update_write(FILE *pipe, struct status *status, enum line_type type)
4549 char buf[SIZEOF_STR];
4554 case LINE_STAT_STAGED:
4555 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
4558 status->old.name, 0))
4562 case LINE_STAT_UNSTAGED:
4563 case LINE_STAT_UNTRACKED:
4564 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
4569 die("line type %d not handled in switch", type);
4572 while (!ferror(pipe) && written < bufsize) {
4573 written += fwrite(buf + written, 1, bufsize - written, pipe);
4576 return written == bufsize;
4580 status_update_file(struct status *status, enum line_type type)
4582 FILE *pipe = status_update_prepare(type);
4588 result = status_update_write(pipe, status, type);
4594 status_update_files(struct view *view, struct line *line)
4596 FILE *pipe = status_update_prepare(line->type);
4598 struct line *pos = view->line + view->lines;
4605 for (pos = line; pos < view->line + view->lines && pos->data; pos++)
4608 for (file = 0, done = 0; result && file < files; line++, file++) {
4609 int almost_done = file * 100 / files;
4611 if (almost_done > done) {
4613 string_format(view->ref, "updating file %u of %u (%d%% done)",
4615 update_view_title(view);
4617 result = status_update_write(pipe, line->data, line->type);
4625 status_update(struct view *view)
4627 struct line *line = &view->line[view->lineno];
4629 assert(view->lines);
4632 /* This should work even for the "On branch" line. */
4633 if (line < view->line + view->lines && !line[1].data) {
4634 report("Nothing to update");
4638 if (!status_update_files(view, line + 1)) {
4639 report("Failed to update file status");
4643 } else if (!status_update_file(line->data, line->type)) {
4644 report("Failed to update file status");
4652 status_revert(struct status *status, enum line_type type, bool has_none)
4654 if (!status || type != LINE_STAT_UNSTAGED) {
4655 if (type == LINE_STAT_STAGED) {
4656 report("Cannot revert changes to staged files");
4657 } else if (type == LINE_STAT_UNTRACKED) {
4658 report("Cannot revert changes to untracked files");
4659 } else if (has_none) {
4660 report("Nothing to revert");
4662 report("Cannot revert changes to multiple files");
4667 const char *checkout_argv[] = {
4668 "git", "checkout", "--", status->old.name, NULL
4671 if (!prompt_yesno("Are you sure you want to overwrite any changes?"))
4673 return run_io_fg(checkout_argv, opt_cdup);
4678 status_request(struct view *view, enum request request, struct line *line)
4680 struct status *status = line->data;
4683 case REQ_STATUS_UPDATE:
4684 if (!status_update(view))
4688 case REQ_STATUS_REVERT:
4689 if (!status_revert(status, line->type, status_has_none(view, line)))
4693 case REQ_STATUS_MERGE:
4694 if (!status || status->status != 'U') {
4695 report("Merging only possible for files with unmerged status ('U').");
4698 open_mergetool(status->new.name);
4704 if (status->status == 'D') {
4705 report("File has been deleted.");
4709 open_editor(status->status != '?', status->new.name);
4712 case REQ_VIEW_BLAME:
4714 string_copy(opt_file, status->new.name);
4720 /* After returning the status view has been split to
4721 * show the stage view. No further reloading is
4723 status_enter(view, line);
4727 /* Simply reload the view. */
4734 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
4740 status_select(struct view *view, struct line *line)
4742 struct status *status = line->data;
4743 char file[SIZEOF_STR] = "all files";
4747 if (status && !string_format(file, "'%s'", status->new.name))
4750 if (!status && line[1].type == LINE_STAT_NONE)
4753 switch (line->type) {
4754 case LINE_STAT_STAGED:
4755 text = "Press %s to unstage %s for commit";
4758 case LINE_STAT_UNSTAGED:
4759 text = "Press %s to stage %s for commit";
4762 case LINE_STAT_UNTRACKED:
4763 text = "Press %s to stage %s for addition";
4766 case LINE_STAT_HEAD:
4767 case LINE_STAT_NONE:
4768 text = "Nothing to update";
4772 die("line type %d not handled in switch", line->type);
4775 if (status && status->status == 'U') {
4776 text = "Press %s to resolve conflict in %s";
4777 key = get_key(REQ_STATUS_MERGE);
4780 key = get_key(REQ_STATUS_UPDATE);
4783 string_format(view->ref, text, key, file);
4787 status_grep(struct view *view, struct line *line)
4789 struct status *status = line->data;
4790 enum { S_STATUS, S_NAME, S_END } state;
4797 for (state = S_STATUS; state < S_END; state++) {
4801 case S_NAME: text = status->new.name; break;
4803 buf[0] = status->status;
4811 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4818 static struct view_ops status_ops = {
4831 stage_diff_line(FILE *pipe, struct line *line)
4833 const char *buf = line->data;
4834 size_t bufsize = strlen(buf);
4837 while (!ferror(pipe) && written < bufsize) {
4838 written += fwrite(buf + written, 1, bufsize - written, pipe);
4843 return written == bufsize;
4847 stage_diff_write(FILE *pipe, struct line *line, struct line *end)
4849 while (line < end) {
4850 if (!stage_diff_line(pipe, line++))
4852 if (line->type == LINE_DIFF_CHUNK ||
4853 line->type == LINE_DIFF_HEADER)
4860 static struct line *
4861 stage_diff_find(struct view *view, struct line *line, enum line_type type)
4863 for (; view->line < line; line--)
4864 if (line->type == type)
4871 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
4873 char cmd[SIZEOF_STR];
4875 struct line *diff_hdr;
4878 diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
4883 !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
4886 if (!string_format_from(cmd, &cmdsize,
4887 "git apply --whitespace=nowarn %s %s - && "
4888 "git update-index -q --unmerged --refresh 2>/dev/null",
4889 revert ? "" : "--cached",
4890 revert || stage_line_type == LINE_STAT_STAGED ? "-R" : ""))
4893 pipe = popen(cmd, "w");
4897 if (!stage_diff_write(pipe, diff_hdr, chunk) ||
4898 !stage_diff_write(pipe, chunk, view->line + view->lines))
4903 return chunk ? TRUE : FALSE;
4907 stage_update(struct view *view, struct line *line)
4909 struct line *chunk = NULL;
4911 if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
4912 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
4915 if (!stage_apply_chunk(view, chunk, FALSE)) {
4916 report("Failed to apply chunk");
4920 } else if (!stage_status.status) {
4921 view = VIEW(REQ_VIEW_STATUS);
4923 for (line = view->line; line < view->line + view->lines; line++)
4924 if (line->type == stage_line_type)
4927 if (!status_update_files(view, line + 1)) {
4928 report("Failed to update files");
4932 } else if (!status_update_file(&stage_status, stage_line_type)) {
4933 report("Failed to update file");
4941 stage_revert(struct view *view, struct line *line)
4943 struct line *chunk = NULL;
4945 if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
4946 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
4949 if (!prompt_yesno("Are you sure you want to revert changes?"))
4952 if (!stage_apply_chunk(view, chunk, TRUE)) {
4953 report("Failed to revert chunk");
4959 return status_revert(stage_status.status ? &stage_status : NULL,
4960 stage_line_type, FALSE);
4966 stage_next(struct view *view, struct line *line)
4970 if (!stage_chunks) {
4971 static size_t alloc = 0;
4974 for (line = view->line; line < view->line + view->lines; line++) {
4975 if (line->type != LINE_DIFF_CHUNK)
4978 tmp = realloc_items(stage_chunk, &alloc,
4979 stage_chunks, sizeof(*tmp));
4981 report("Allocation failure");
4986 stage_chunk[stage_chunks++] = line - view->line;
4990 for (i = 0; i < stage_chunks; i++) {
4991 if (stage_chunk[i] > view->lineno) {
4992 do_scroll_view(view, stage_chunk[i] - view->lineno);
4993 report("Chunk %d of %d", i + 1, stage_chunks);
4998 report("No next chunk found");
5002 stage_request(struct view *view, enum request request, struct line *line)
5005 case REQ_STATUS_UPDATE:
5006 if (!stage_update(view, line))
5010 case REQ_STATUS_REVERT:
5011 if (!stage_revert(view, line))
5015 case REQ_STAGE_NEXT:
5016 if (stage_line_type == LINE_STAT_UNTRACKED) {
5017 report("File is untracked; press %s to add",
5018 get_key(REQ_STATUS_UPDATE));
5021 stage_next(view, line);
5025 if (!stage_status.new.name[0])
5027 if (stage_status.status == 'D') {
5028 report("File has been deleted.");
5032 open_editor(stage_status.status != '?', stage_status.new.name);
5036 /* Reload everything ... */
5039 case REQ_VIEW_BLAME:
5040 if (stage_status.new.name[0]) {
5041 string_copy(opt_file, stage_status.new.name);
5047 return pager_request(view, request, line);
5053 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD | OPEN_NOMAXIMIZE);
5055 /* Check whether the staged entry still exists, and close the
5056 * stage view if it doesn't. */
5057 if (!status_exists(&stage_status, stage_line_type))
5058 return REQ_VIEW_CLOSE;
5060 if (stage_line_type == LINE_STAT_UNTRACKED) {
5061 if (!suffixcmp(stage_status.new.name, -1, "/")) {
5062 report("Cannot display a directory");
5066 opt_pipe = fopen(stage_status.new.name, "r");
5068 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
5073 static struct view_ops stage_ops = {
5090 char id[SIZEOF_REV]; /* SHA1 ID. */
5091 char title[128]; /* First line of the commit message. */
5092 char author[75]; /* Author of the commit. */
5093 struct tm time; /* Date from the author ident. */
5094 struct ref **refs; /* Repository references. */
5095 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
5096 size_t graph_size; /* The width of the graph array. */
5097 bool has_parents; /* Rewritten --parents seen. */
5100 /* Size of rev graph with no "padding" columns */
5101 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
5104 struct rev_graph *prev, *next, *parents;
5105 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
5107 struct commit *commit;
5109 unsigned int boundary:1;
5112 /* Parents of the commit being visualized. */
5113 static struct rev_graph graph_parents[4];
5115 /* The current stack of revisions on the graph. */
5116 static struct rev_graph graph_stacks[4] = {
5117 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
5118 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
5119 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
5120 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
5124 graph_parent_is_merge(struct rev_graph *graph)
5126 return graph->parents->size > 1;
5130 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
5132 struct commit *commit = graph->commit;
5134 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
5135 commit->graph[commit->graph_size++] = symbol;
5139 clear_rev_graph(struct rev_graph *graph)
5141 graph->boundary = 0;
5142 graph->size = graph->pos = 0;
5143 graph->commit = NULL;
5144 memset(graph->parents, 0, sizeof(*graph->parents));
5148 done_rev_graph(struct rev_graph *graph)
5150 if (graph_parent_is_merge(graph) &&
5151 graph->pos < graph->size - 1 &&
5152 graph->next->size == graph->size + graph->parents->size - 1) {
5153 size_t i = graph->pos + graph->parents->size - 1;
5155 graph->commit->graph_size = i * 2;
5156 while (i < graph->next->size - 1) {
5157 append_to_rev_graph(graph, ' ');
5158 append_to_rev_graph(graph, '\\');
5163 clear_rev_graph(graph);
5167 push_rev_graph(struct rev_graph *graph, const char *parent)
5171 /* "Collapse" duplicate parents lines.
5173 * FIXME: This needs to also update update the drawn graph but
5174 * for now it just serves as a method for pruning graph lines. */
5175 for (i = 0; i < graph->size; i++)
5176 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
5179 if (graph->size < SIZEOF_REVITEMS) {
5180 string_copy_rev(graph->rev[graph->size++], parent);
5185 get_rev_graph_symbol(struct rev_graph *graph)
5189 if (graph->boundary)
5190 symbol = REVGRAPH_BOUND;
5191 else if (graph->parents->size == 0)
5192 symbol = REVGRAPH_INIT;
5193 else if (graph_parent_is_merge(graph))
5194 symbol = REVGRAPH_MERGE;
5195 else if (graph->pos >= graph->size)
5196 symbol = REVGRAPH_BRANCH;
5198 symbol = REVGRAPH_COMMIT;
5204 draw_rev_graph(struct rev_graph *graph)
5207 chtype separator, line;
5209 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
5210 static struct rev_filler fillers[] = {
5216 chtype symbol = get_rev_graph_symbol(graph);
5217 struct rev_filler *filler;
5220 if (opt_line_graphics)
5221 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
5223 filler = &fillers[DEFAULT];
5225 for (i = 0; i < graph->pos; i++) {
5226 append_to_rev_graph(graph, filler->line);
5227 if (graph_parent_is_merge(graph->prev) &&
5228 graph->prev->pos == i)
5229 filler = &fillers[RSHARP];
5231 append_to_rev_graph(graph, filler->separator);
5234 /* Place the symbol for this revision. */
5235 append_to_rev_graph(graph, symbol);
5237 if (graph->prev->size > graph->size)
5238 filler = &fillers[RDIAG];
5240 filler = &fillers[DEFAULT];
5244 for (; i < graph->size; i++) {
5245 append_to_rev_graph(graph, filler->separator);
5246 append_to_rev_graph(graph, filler->line);
5247 if (graph_parent_is_merge(graph->prev) &&
5248 i < graph->prev->pos + graph->parents->size)
5249 filler = &fillers[RSHARP];
5250 if (graph->prev->size > graph->size)
5251 filler = &fillers[LDIAG];
5254 if (graph->prev->size > graph->size) {
5255 append_to_rev_graph(graph, filler->separator);
5256 if (filler->line != ' ')
5257 append_to_rev_graph(graph, filler->line);
5261 /* Prepare the next rev graph */
5263 prepare_rev_graph(struct rev_graph *graph)
5267 /* First, traverse all lines of revisions up to the active one. */
5268 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
5269 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
5272 push_rev_graph(graph->next, graph->rev[graph->pos]);
5275 /* Interleave the new revision parent(s). */
5276 for (i = 0; !graph->boundary && i < graph->parents->size; i++)
5277 push_rev_graph(graph->next, graph->parents->rev[i]);
5279 /* Lastly, put any remaining revisions. */
5280 for (i = graph->pos + 1; i < graph->size; i++)
5281 push_rev_graph(graph->next, graph->rev[i]);
5285 update_rev_graph(struct rev_graph *graph)
5287 /* If this is the finalizing update ... */
5289 prepare_rev_graph(graph);
5291 /* Graph visualization needs a one rev look-ahead,
5292 * so the first update doesn't visualize anything. */
5293 if (!graph->prev->commit)
5296 draw_rev_graph(graph->prev);
5297 done_rev_graph(graph->prev->prev);
5305 static const char *main_argv[SIZEOF_ARG] = {
5306 "git", "log", "--no-color", "--pretty=raw", "--parents",
5307 "--topo-order", "%(head)", NULL
5311 main_draw(struct view *view, struct line *line, unsigned int lineno)
5313 struct commit *commit = line->data;
5315 if (!*commit->author)
5318 if (opt_date && draw_date(view, &commit->time))
5322 draw_field(view, LINE_MAIN_AUTHOR, commit->author, opt_author_cols, TRUE))
5325 if (opt_rev_graph && commit->graph_size &&
5326 draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
5329 if (opt_show_refs && commit->refs) {
5333 enum line_type type;
5335 if (commit->refs[i]->head)
5336 type = LINE_MAIN_HEAD;
5337 else if (commit->refs[i]->ltag)
5338 type = LINE_MAIN_LOCAL_TAG;
5339 else if (commit->refs[i]->tag)
5340 type = LINE_MAIN_TAG;
5341 else if (commit->refs[i]->tracked)
5342 type = LINE_MAIN_TRACKED;
5343 else if (commit->refs[i]->remote)
5344 type = LINE_MAIN_REMOTE;
5346 type = LINE_MAIN_REF;
5348 if (draw_text(view, type, "[", TRUE) ||
5349 draw_text(view, type, commit->refs[i]->name, TRUE) ||
5350 draw_text(view, type, "]", TRUE))
5353 if (draw_text(view, LINE_DEFAULT, " ", TRUE))
5355 } while (commit->refs[i++]->next);
5358 draw_text(view, LINE_DEFAULT, commit->title, TRUE);
5362 /* Reads git log --pretty=raw output and parses it into the commit struct. */
5364 main_read(struct view *view, char *line)
5366 static struct rev_graph *graph = graph_stacks;
5367 enum line_type type;
5368 struct commit *commit;
5373 if (!view->lines && !view->parent)
5374 die("No revisions match the given arguments.");
5375 if (view->lines > 0) {
5376 commit = view->line[view->lines - 1].data;
5377 if (!*commit->author) {
5380 graph->commit = NULL;
5383 update_rev_graph(graph);
5385 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
5386 clear_rev_graph(&graph_stacks[i]);
5390 type = get_line_type(line);
5391 if (type == LINE_COMMIT) {
5392 commit = calloc(1, sizeof(struct commit));
5396 line += STRING_SIZE("commit ");
5398 graph->boundary = 1;
5402 string_copy_rev(commit->id, line);
5403 commit->refs = get_refs(commit->id);
5404 graph->commit = commit;
5405 add_line_data(view, commit, LINE_MAIN_COMMIT);
5407 while ((line = strchr(line, ' '))) {
5409 push_rev_graph(graph->parents, line);
5410 commit->has_parents = TRUE;
5417 commit = view->line[view->lines - 1].data;
5421 if (commit->has_parents)
5423 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
5428 /* Parse author lines where the name may be empty:
5429 * author <email@address.tld> 1138474660 +0100
5431 char *ident = line + STRING_SIZE("author ");
5432 char *nameend = strchr(ident, '<');
5433 char *emailend = strchr(ident, '>');
5435 if (!nameend || !emailend)
5438 update_rev_graph(graph);
5439 graph = graph->next;
5441 *nameend = *emailend = 0;
5442 ident = chomp_string(ident);
5444 ident = chomp_string(nameend + 1);
5449 string_ncopy(commit->author, ident, strlen(ident));
5451 /* Parse epoch and timezone */
5452 if (emailend[1] == ' ') {
5453 char *secs = emailend + 2;
5454 char *zone = strchr(secs, ' ');
5455 time_t time = (time_t) atol(secs);
5457 if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
5461 tz = ('0' - zone[1]) * 60 * 60 * 10;
5462 tz += ('0' - zone[2]) * 60 * 60;
5463 tz += ('0' - zone[3]) * 60;
5464 tz += ('0' - zone[4]) * 60;
5472 gmtime_r(&time, &commit->time);
5477 /* Fill in the commit title if it has not already been set. */
5478 if (commit->title[0])
5481 /* Require titles to start with a non-space character at the
5482 * offset used by git log. */
5483 if (strncmp(line, " ", 4))
5486 /* Well, if the title starts with a whitespace character,
5487 * try to be forgiving. Otherwise we end up with no title. */
5488 while (isspace(*line))
5492 /* FIXME: More graceful handling of titles; append "..." to
5493 * shortened titles, etc. */
5495 string_ncopy(commit->title, line, strlen(line));
5502 main_request(struct view *view, enum request request, struct line *line)
5504 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
5508 open_view(view, REQ_VIEW_DIFF, flags);
5512 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
5522 grep_refs(struct ref **refs, regex_t *regex)
5530 if (regexec(regex, refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
5532 } while (refs[i++]->next);
5538 main_grep(struct view *view, struct line *line)
5540 struct commit *commit = line->data;
5541 enum { S_TITLE, S_AUTHOR, S_DATE, S_REFS, S_END } state;
5542 char buf[DATE_COLS + 1];
5545 for (state = S_TITLE; state < S_END; state++) {
5549 case S_TITLE: text = commit->title; break;
5553 text = commit->author;
5558 if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
5565 if (grep_refs(commit->refs, view->regex) == TRUE)
5572 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
5580 main_select(struct view *view, struct line *line)
5582 struct commit *commit = line->data;
5584 string_copy_rev(view->ref, commit->id);
5585 string_copy_rev(ref_commit, view->ref);
5588 static struct view_ops main_ops = {
5601 * Unicode / UTF-8 handling
5603 * NOTE: Much of the following code for dealing with unicode is derived from
5604 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
5605 * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
5608 /* I've (over)annotated a lot of code snippets because I am not entirely
5609 * confident that the approach taken by this small UTF-8 interface is correct.
5613 unicode_width(unsigned long c)
5616 (c <= 0x115f /* Hangul Jamo */
5619 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
5621 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
5622 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
5623 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
5624 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
5625 || (c >= 0xffe0 && c <= 0xffe6)
5626 || (c >= 0x20000 && c <= 0x2fffd)
5627 || (c >= 0x30000 && c <= 0x3fffd)))
5631 return opt_tab_size;
5636 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
5637 * Illegal bytes are set one. */
5638 static const unsigned char utf8_bytes[256] = {
5639 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,
5640 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,
5641 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,
5642 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,
5643 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,
5644 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,
5645 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,
5646 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,
5649 /* Decode UTF-8 multi-byte representation into a unicode character. */
5650 static inline unsigned long
5651 utf8_to_unicode(const char *string, size_t length)
5653 unsigned long unicode;
5657 unicode = string[0];
5660 unicode = (string[0] & 0x1f) << 6;
5661 unicode += (string[1] & 0x3f);
5664 unicode = (string[0] & 0x0f) << 12;
5665 unicode += ((string[1] & 0x3f) << 6);
5666 unicode += (string[2] & 0x3f);
5669 unicode = (string[0] & 0x0f) << 18;
5670 unicode += ((string[1] & 0x3f) << 12);
5671 unicode += ((string[2] & 0x3f) << 6);
5672 unicode += (string[3] & 0x3f);
5675 unicode = (string[0] & 0x0f) << 24;
5676 unicode += ((string[1] & 0x3f) << 18);
5677 unicode += ((string[2] & 0x3f) << 12);
5678 unicode += ((string[3] & 0x3f) << 6);
5679 unicode += (string[4] & 0x3f);
5682 unicode = (string[0] & 0x01) << 30;
5683 unicode += ((string[1] & 0x3f) << 24);
5684 unicode += ((string[2] & 0x3f) << 18);
5685 unicode += ((string[3] & 0x3f) << 12);
5686 unicode += ((string[4] & 0x3f) << 6);
5687 unicode += (string[5] & 0x3f);
5690 die("Invalid unicode length");
5693 /* Invalid characters could return the special 0xfffd value but NUL
5694 * should be just as good. */
5695 return unicode > 0xffff ? 0 : unicode;
5698 /* Calculates how much of string can be shown within the given maximum width
5699 * and sets trimmed parameter to non-zero value if all of string could not be
5700 * shown. If the reserve flag is TRUE, it will reserve at least one
5701 * trailing character, which can be useful when drawing a delimiter.
5703 * Returns the number of bytes to output from string to satisfy max_width. */
5705 utf8_length(const char *string, int *width, size_t max_width, int *trimmed, bool reserve)
5707 const char *start = string;
5708 const char *end = strchr(string, '\0');
5709 unsigned char last_bytes = 0;
5710 size_t last_ucwidth = 0;
5715 while (string < end) {
5716 int c = *(unsigned char *) string;
5717 unsigned char bytes = utf8_bytes[c];
5719 unsigned long unicode;
5721 if (string + bytes > end)
5724 /* Change representation to figure out whether
5725 * it is a single- or double-width character. */
5727 unicode = utf8_to_unicode(string, bytes);
5728 /* FIXME: Graceful handling of invalid unicode character. */
5732 ucwidth = unicode_width(unicode);
5734 if (*width > max_width) {
5737 if (reserve && *width == max_width) {
5738 string -= last_bytes;
5739 *width -= last_ucwidth;
5746 last_ucwidth = ucwidth;
5749 return string - start;
5757 /* Whether or not the curses interface has been initialized. */
5758 static bool cursed = FALSE;
5760 /* The status window is used for polling keystrokes. */
5761 static WINDOW *status_win;
5763 static bool status_empty = TRUE;
5765 /* Update status and title window. */
5767 report(const char *msg, ...)
5769 struct view *view = display[current_view];
5775 char buf[SIZEOF_STR];
5778 va_start(args, msg);
5779 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
5780 buf[sizeof(buf) - 1] = 0;
5781 buf[sizeof(buf) - 2] = '.';
5782 buf[sizeof(buf) - 3] = '.';
5783 buf[sizeof(buf) - 4] = '.';
5789 if (!status_empty || *msg) {
5792 va_start(args, msg);
5794 wmove(status_win, 0, 0);
5796 vwprintw(status_win, msg, args);
5797 status_empty = FALSE;
5799 status_empty = TRUE;
5801 wclrtoeol(status_win);
5802 wrefresh(status_win);
5807 update_view_title(view);
5808 update_display_cursor(view);
5811 /* Controls when nodelay should be in effect when polling user input. */
5813 set_nonblocking_input(bool loading)
5815 static unsigned int loading_views;
5817 if ((loading == FALSE && loading_views-- == 1) ||
5818 (loading == TRUE && loading_views++ == 0))
5819 nodelay(status_win, loading);
5827 /* Initialize the curses library */
5828 if (isatty(STDIN_FILENO)) {
5829 cursed = !!initscr();
5832 /* Leave stdin and stdout alone when acting as a pager. */
5833 opt_tty = fopen("/dev/tty", "r+");
5835 die("Failed to open /dev/tty");
5836 cursed = !!newterm(NULL, opt_tty, opt_tty);
5840 die("Failed to initialize curses");
5842 nonl(); /* Tell curses not to do NL->CR/NL on output */
5843 cbreak(); /* Take input chars one at a time, no wait for \n */
5844 noecho(); /* Don't echo input */
5845 leaveok(stdscr, TRUE);
5850 getmaxyx(stdscr, y, x);
5851 status_win = newwin(1, 0, y - 1, 0);
5853 die("Failed to create status window");
5855 /* Enable keyboard mapping */
5856 keypad(status_win, TRUE);
5857 wbkgdset(status_win, get_line_attr(LINE_STATUS));
5859 TABSIZE = opt_tab_size;
5860 if (opt_line_graphics) {
5861 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
5866 prompt_yesno(const char *prompt)
5868 enum { WAIT, STOP, CANCEL } status = WAIT;
5869 bool answer = FALSE;
5871 while (status == WAIT) {
5877 foreach_view (view, i)
5882 mvwprintw(status_win, 0, 0, "%s [Yy]/[Nn]", prompt);
5883 wclrtoeol(status_win);
5885 /* Refresh, accept single keystroke of input */
5886 key = wgetch(status_win);
5910 /* Clear the status window */
5911 status_empty = FALSE;
5918 read_prompt(const char *prompt)
5920 enum { READING, STOP, CANCEL } status = READING;
5921 static char buf[SIZEOF_STR];
5924 while (status == READING) {
5930 foreach_view (view, i)
5935 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
5936 wclrtoeol(status_win);
5938 /* Refresh, accept single keystroke of input */
5939 key = wgetch(status_win);
5944 status = pos ? STOP : CANCEL;
5962 if (pos >= sizeof(buf)) {
5963 report("Input string too long");
5968 buf[pos++] = (char) key;
5972 /* Clear the status window */
5973 status_empty = FALSE;
5976 if (status == CANCEL)
5985 * Repository references
5988 static struct ref *refs = NULL;
5989 static size_t refs_alloc = 0;
5990 static size_t refs_size = 0;
5992 /* Id <-> ref store */
5993 static struct ref ***id_refs = NULL;
5994 static size_t id_refs_alloc = 0;
5995 static size_t id_refs_size = 0;
5998 compare_refs(const void *ref1_, const void *ref2_)
6000 const struct ref *ref1 = *(const struct ref **)ref1_;
6001 const struct ref *ref2 = *(const struct ref **)ref2_;
6003 if (ref1->tag != ref2->tag)
6004 return ref2->tag - ref1->tag;
6005 if (ref1->ltag != ref2->ltag)
6006 return ref2->ltag - ref2->ltag;
6007 if (ref1->head != ref2->head)
6008 return ref2->head - ref1->head;
6009 if (ref1->tracked != ref2->tracked)
6010 return ref2->tracked - ref1->tracked;
6011 if (ref1->remote != ref2->remote)
6012 return ref2->remote - ref1->remote;
6013 return strcmp(ref1->name, ref2->name);
6016 static struct ref **
6017 get_refs(const char *id)
6019 struct ref ***tmp_id_refs;
6020 struct ref **ref_list = NULL;
6021 size_t ref_list_alloc = 0;
6022 size_t ref_list_size = 0;
6025 for (i = 0; i < id_refs_size; i++)
6026 if (!strcmp(id, id_refs[i][0]->id))
6029 tmp_id_refs = realloc_items(id_refs, &id_refs_alloc, id_refs_size + 1,
6034 id_refs = tmp_id_refs;
6036 for (i = 0; i < refs_size; i++) {
6039 if (strcmp(id, refs[i].id))
6042 tmp = realloc_items(ref_list, &ref_list_alloc,
6043 ref_list_size + 1, sizeof(*ref_list));
6051 ref_list[ref_list_size] = &refs[i];
6052 /* XXX: The properties of the commit chains ensures that we can
6053 * safely modify the shared ref. The repo references will
6054 * always be similar for the same id. */
6055 ref_list[ref_list_size]->next = 1;
6061 qsort(ref_list, ref_list_size, sizeof(*ref_list), compare_refs);
6062 ref_list[ref_list_size - 1]->next = 0;
6063 id_refs[id_refs_size++] = ref_list;
6070 read_ref(char *id, size_t idlen, char *name, size_t namelen)
6075 bool remote = FALSE;
6076 bool tracked = FALSE;
6077 bool check_replace = FALSE;
6080 if (!prefixcmp(name, "refs/tags/")) {
6081 if (!suffixcmp(name, namelen, "^{}")) {
6084 if (refs_size > 0 && refs[refs_size - 1].ltag == TRUE)
6085 check_replace = TRUE;
6091 namelen -= STRING_SIZE("refs/tags/");
6092 name += STRING_SIZE("refs/tags/");
6094 } else if (!prefixcmp(name, "refs/remotes/")) {
6096 namelen -= STRING_SIZE("refs/remotes/");
6097 name += STRING_SIZE("refs/remotes/");
6098 tracked = !strcmp(opt_remote, name);
6100 } else if (!prefixcmp(name, "refs/heads/")) {
6101 namelen -= STRING_SIZE("refs/heads/");
6102 name += STRING_SIZE("refs/heads/");
6103 head = !strncmp(opt_head, name, namelen);
6105 } else if (!strcmp(name, "HEAD")) {
6106 string_ncopy(opt_head_rev, id, idlen);
6110 if (check_replace && !strcmp(name, refs[refs_size - 1].name)) {
6111 /* it's an annotated tag, replace the previous sha1 with the
6112 * resolved commit id; relies on the fact git-ls-remote lists
6113 * the commit id of an annotated tag right before the commit id
6115 refs[refs_size - 1].ltag = ltag;
6116 string_copy_rev(refs[refs_size - 1].id, id);
6120 refs = realloc_items(refs, &refs_alloc, refs_size + 1, sizeof(*refs));
6124 ref = &refs[refs_size++];
6125 ref->name = malloc(namelen + 1);
6129 strncpy(ref->name, name, namelen);
6130 ref->name[namelen] = 0;
6134 ref->remote = remote;
6135 ref->tracked = tracked;
6136 string_copy_rev(ref->id, id);
6144 const char *cmd_env = getenv("TIG_LS_REMOTE");
6145 const char *cmd = cmd_env && *cmd_env ? cmd_env : TIG_LS_REMOTE;
6150 while (refs_size > 0)
6151 free(refs[--refs_size].name);
6152 while (id_refs_size > 0)
6153 free(id_refs[--id_refs_size]);
6155 return read_properties(popen(cmd, "r"), "\t", read_ref);
6159 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
6161 if (!strcmp(name, "i18n.commitencoding"))
6162 string_ncopy(opt_encoding, value, valuelen);
6164 if (!strcmp(name, "core.editor"))
6165 string_ncopy(opt_editor, value, valuelen);
6167 /* branch.<head>.remote */
6169 !strncmp(name, "branch.", 7) &&
6170 !strncmp(name + 7, opt_head, strlen(opt_head)) &&
6171 !strcmp(name + 7 + strlen(opt_head), ".remote"))
6172 string_ncopy(opt_remote, value, valuelen);
6174 if (*opt_head && *opt_remote &&
6175 !strncmp(name, "branch.", 7) &&
6176 !strncmp(name + 7, opt_head, strlen(opt_head)) &&
6177 !strcmp(name + 7 + strlen(opt_head), ".merge")) {
6178 size_t from = strlen(opt_remote);
6180 if (!prefixcmp(value, "refs/heads/")) {
6181 value += STRING_SIZE("refs/heads/");
6182 valuelen -= STRING_SIZE("refs/heads/");
6185 if (!string_format_from(opt_remote, &from, "/%s", value))
6193 load_git_config(void)
6195 return read_properties(popen("git " GIT_CONFIG " --list", "r"),
6196 "=", read_repo_config_option);
6200 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
6202 if (!opt_git_dir[0]) {
6203 string_ncopy(opt_git_dir, name, namelen);
6205 } else if (opt_is_inside_work_tree == -1) {
6206 /* This can be 3 different values depending on the
6207 * version of git being used. If git-rev-parse does not
6208 * understand --is-inside-work-tree it will simply echo
6209 * the option else either "true" or "false" is printed.
6210 * Default to true for the unknown case. */
6211 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
6213 } else if (opt_cdup[0] == ' ') {
6214 string_ncopy(opt_cdup, name, namelen);
6216 if (!prefixcmp(name, "refs/heads/")) {
6217 namelen -= STRING_SIZE("refs/heads/");
6218 name += STRING_SIZE("refs/heads/");
6219 string_ncopy(opt_head, name, namelen);
6227 load_repo_info(void)
6230 FILE *pipe = popen("(git rev-parse --git-dir --is-inside-work-tree "
6231 " --show-cdup; git symbolic-ref HEAD) 2>/dev/null", "r");
6233 /* XXX: The line outputted by "--show-cdup" can be empty so
6234 * initialize it to something invalid to make it possible to
6235 * detect whether it has been set or not. */
6238 result = read_properties(pipe, "=", read_repo_info);
6239 if (opt_cdup[0] == ' ')
6246 read_properties(FILE *pipe, const char *separators,
6247 int (*read_property)(char *, size_t, char *, size_t))
6249 char buffer[BUFSIZ];
6256 while (state == OK && (name = fgets(buffer, sizeof(buffer), pipe))) {
6261 name = chomp_string(name);
6262 namelen = strcspn(name, separators);
6264 if (name[namelen]) {
6266 value = chomp_string(name + namelen + 1);
6267 valuelen = strlen(value);
6274 state = read_property(name, namelen, value, valuelen);
6277 if (state != ERR && ferror(pipe))
6290 static void __NORETURN
6293 /* XXX: Restore tty modes and let the OS cleanup the rest! */
6299 static void __NORETURN
6300 die(const char *err, ...)
6306 va_start(args, err);
6307 fputs("tig: ", stderr);
6308 vfprintf(stderr, err, args);
6309 fputs("\n", stderr);
6316 warn(const char *msg, ...)
6320 va_start(args, msg);
6321 fputs("tig warning: ", stderr);
6322 vfprintf(stderr, msg, args);
6323 fputs("\n", stderr);
6328 main(int argc, const char *argv[])
6331 enum request request;
6334 signal(SIGINT, quit);
6336 if (setlocale(LC_ALL, "")) {
6337 char *codeset = nl_langinfo(CODESET);
6339 string_ncopy(opt_codeset, codeset, strlen(codeset));
6342 if (load_repo_info() == ERR)
6343 die("Failed to load repo info.");
6345 if (load_options() == ERR)
6346 die("Failed to load user config.");
6348 if (load_git_config() == ERR)
6349 die("Failed to load repo config.");
6351 request = parse_options(argc, argv);
6352 if (request == REQ_NONE)
6355 /* Require a git repository unless when running in pager mode. */
6356 if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
6357 die("Not a git repository");
6359 if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
6362 if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
6363 opt_iconv = iconv_open(opt_codeset, opt_encoding);
6364 if (opt_iconv == ICONV_NONE)
6365 die("Failed to initialize character set conversion");
6368 if (load_refs() == ERR)
6369 die("Failed to load refs.");
6371 foreach_view (view, i)
6372 argv_from_env(view->ops->argv, view->cmd_env);
6376 while (view_driver(display[current_view], request)) {
6380 foreach_view (view, i)
6382 view = display[current_view];
6384 /* Refresh, accept single keystroke of input */
6385 key = wgetch(status_win);
6387 /* wgetch() with nodelay() enabled returns ERR when there's no
6394 request = get_keybinding(view->keymap, key);
6396 /* Some low-level request handling. This keeps access to
6397 * status_win restricted. */
6401 char *cmd = read_prompt(":");
6404 struct view *next = VIEW(REQ_VIEW_PAGER);
6405 const char *argv[SIZEOF_ARG] = { "git" };
6408 /* When running random commands, initially show the
6409 * command in the title. However, it maybe later be
6410 * overwritten if a commit line is selected. */
6411 string_ncopy(next->ref, cmd, strlen(cmd));
6413 if (!argv_from_string(argv, &argc, cmd)) {
6414 report("Too many arguments");
6415 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
6416 report("Failed to format command");
6418 open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
6426 case REQ_SEARCH_BACK:
6428 const char *prompt = request == REQ_SEARCH ? "/" : "?";
6429 char *search = read_prompt(prompt);
6432 string_ncopy(opt_search, search, strlen(search));
6437 case REQ_SCREEN_RESIZE:
6441 getmaxyx(stdscr, height, width);
6443 /* Resize the status view and let the view driver take
6444 * care of resizing the displayed views. */
6445 wresize(status_win, 1, width);
6446 mvwin(status_win, height - 1, 0);
6447 wrefresh(status_win);