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 void set_nonblocking_input(bool loading);
68 static size_t utf8_length(const char *string, int *width, size_t max_width, int *trimmed, bool reserve);
69 static bool prompt_yesno(const char *prompt);
70 static int load_refs(void);
72 #define ABS(x) ((x) >= 0 ? (x) : -(x))
73 #define MIN(x, y) ((x) < (y) ? (x) : (y))
75 #define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
76 #define STRING_SIZE(x) (sizeof(x) - 1)
78 #define SIZEOF_STR 1024 /* Default string size. */
79 #define SIZEOF_REF 256 /* Size of symbolic or SHA1 ID. */
80 #define SIZEOF_REV 41 /* Holds a SHA-1 and an ending NUL. */
81 #define SIZEOF_ARG 32 /* Default argument array size. */
85 #define REVGRAPH_INIT 'I'
86 #define REVGRAPH_MERGE 'M'
87 #define REVGRAPH_BRANCH '+'
88 #define REVGRAPH_COMMIT '*'
89 #define REVGRAPH_BOUND '^'
91 #define SIZEOF_REVGRAPH 19 /* Size of revision ancestry graphics. */
93 /* This color name can be used to refer to the default term colors. */
94 #define COLOR_DEFAULT (-1)
96 #define ICONV_NONE ((iconv_t) -1)
98 #define ICONV_CONST /* nothing */
101 /* The format and size of the date column in the main view. */
102 #define DATE_FORMAT "%Y-%m-%d %H:%M"
103 #define DATE_COLS STRING_SIZE("2006-04-29 14:21 ")
105 #define AUTHOR_COLS 20
108 /* The default interval between line numbers. */
109 #define NUMBER_INTERVAL 5
113 #define SCALE_SPLIT_VIEW(height) ((height) * 2 / 3)
115 #define NULL_ID "0000000000000000000000000000000000000000"
118 #define GIT_CONFIG "config"
121 /* Some ascii-shorthands fitted into the ncurses namespace. */
123 #define KEY_RETURN '\r'
128 char *name; /* Ref name; tag or head names are shortened. */
129 char id[SIZEOF_REV]; /* Commit SHA1 ID */
130 unsigned int head:1; /* Is it the current HEAD? */
131 unsigned int tag:1; /* Is it a tag? */
132 unsigned int ltag:1; /* If so, is the tag local? */
133 unsigned int remote:1; /* Is it a remote ref? */
134 unsigned int tracked:1; /* Is it the remote for the current HEAD? */
135 unsigned int next:1; /* For ref lists: are there more refs? */
138 static struct ref **get_refs(const char *id);
141 FORMAT_ALL, /* Perform replacement in all arguments. */
142 FORMAT_DASH, /* Perform replacement up until "--". */
143 FORMAT_NONE /* No replacement should be performed. */
146 static bool format_command(char dst[], const char *src[], enum format_flags flags);
147 static bool format_argv(const char *dst[], const char *src[], enum format_flags flags);
156 set_from_int_map(struct int_map *map, size_t map_size,
157 int *value, const char *name, int namelen)
162 for (i = 0; i < map_size; i++)
163 if (namelen == map[i].namelen &&
164 !strncasecmp(name, map[i].name, namelen)) {
165 *value = map[i].value;
178 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
180 if (srclen > dstlen - 1)
183 strncpy(dst, src, srclen);
187 /* Shorthands for safely copying into a fixed buffer. */
189 #define string_copy(dst, src) \
190 string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
192 #define string_ncopy(dst, src, srclen) \
193 string_ncopy_do(dst, sizeof(dst), src, srclen)
195 #define string_copy_rev(dst, src) \
196 string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
198 #define string_add(dst, from, src) \
199 string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
202 chomp_string(char *name)
206 while (isspace(*name))
209 namelen = strlen(name) - 1;
210 while (namelen > 0 && isspace(name[namelen]))
217 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
220 size_t pos = bufpos ? *bufpos : 0;
223 pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
229 return pos >= bufsize ? FALSE : TRUE;
232 #define string_format(buf, fmt, args...) \
233 string_nformat(buf, sizeof(buf), NULL, fmt, args)
235 #define string_format_from(buf, from, fmt, args...) \
236 string_nformat(buf, sizeof(buf), from, fmt, args)
239 string_enum_compare(const char *str1, const char *str2, int len)
243 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
245 /* Diff-Header == DIFF_HEADER */
246 for (i = 0; i < len; i++) {
247 if (toupper(str1[i]) == toupper(str2[i]))
250 if (string_enum_sep(str1[i]) &&
251 string_enum_sep(str2[i]))
254 return str1[i] - str2[i];
260 #define prefixcmp(str1, str2) \
261 strncmp(str1, str2, STRING_SIZE(str2))
264 suffixcmp(const char *str, int slen, const char *suffix)
266 size_t len = slen >= 0 ? slen : strlen(str);
267 size_t suffixlen = strlen(suffix);
269 return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
274 * NOTE: The following is a slightly modified copy of the git project's shell
275 * quoting routines found in the quote.c file.
277 * Help to copy the thing properly quoted for the shell safety. any single
278 * quote is replaced with '\'', any exclamation point is replaced with '\!',
279 * and the whole thing is enclosed in a
282 * original sq_quote result
283 * name ==> name ==> 'name'
284 * a b ==> a b ==> 'a b'
285 * a'b ==> a'\''b ==> 'a'\''b'
286 * a!b ==> a'\!'b ==> 'a'\!'b'
290 sq_quote(char buf[SIZEOF_STR], size_t bufsize, const char *src)
294 #define BUFPUT(x) do { if (bufsize < SIZEOF_STR) buf[bufsize++] = (x); } while (0)
297 while ((c = *src++)) {
298 if (c == '\'' || c == '!') {
309 if (bufsize < SIZEOF_STR)
316 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
320 while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
321 bool advance = cmd[valuelen] != 0;
324 argv[(*argc)++] = chomp_string(cmd);
325 cmd += valuelen + advance;
328 if (*argc < SIZEOF_ARG)
330 return *argc < SIZEOF_ARG;
334 argv_from_env(const char **argv, const char *name)
336 char *env = argv ? getenv(name) : NULL;
341 if (env && !argv_from_string(argv, &argc, env))
342 die("Too many arguments in the `%s` environment variable", name);
347 * Executing external commands.
351 IO_FD, /* File descriptor based IO. */
352 IO_BG, /* Execute command in the background. */
353 IO_FG, /* Execute command with same std{in,out,err}. */
354 IO_RD, /* Read only fork+exec IO. */
355 IO_WR, /* Write only fork+exec IO. */
359 enum io_type type; /* The requested type of pipe. */
360 const char *dir; /* Directory from which to execute. */
361 FILE *pipe; /* Pipe for reading or writing. */
362 int error; /* Error status. */
363 char sh[SIZEOF_STR]; /* Shell command buffer. */
364 char *buf; /* Read/write buffer. */
365 size_t bufalloc; /* Allocated buffer size. */
369 reset_io(struct io *io)
378 init_io(struct io *io, const char *dir, enum io_type type)
386 init_io_rd(struct io *io, const char *argv[], const char *dir,
387 enum format_flags flags)
389 init_io(io, dir, IO_RD);
390 return format_command(io->sh, argv, flags);
394 init_io_fd(struct io *io, FILE *pipe)
396 init_io(io, NULL, IO_FD);
398 return io->pipe != NULL;
402 done_io(struct io *io)
405 if (io->type == IO_FD)
407 else if (io->type == IO_RD || io->type == IO_WR)
414 start_io(struct io *io)
416 char buf[SIZEOF_STR * 2];
419 if (io->type == IO_FD)
422 if (io->dir && *io->dir &&
423 !string_format_from(buf, &bufpos, "cd %s;", io->dir))
426 if (!string_format_from(buf, &bufpos, "%s", io->sh))
429 if (io->type == IO_FG || io->type == IO_BG)
430 return system(buf) == 0;
432 io->pipe = popen(io->sh, io->type == IO_RD ? "r" : "w");
433 return io->pipe != NULL;
437 run_io(struct io *io, const char **argv, const char *dir, enum io_type type)
439 init_io(io, dir, type);
440 if (!format_command(io->sh, argv, FORMAT_NONE))
446 run_io_do(struct io *io)
448 return start_io(io) && done_io(io);
452 run_io_bg(const char **argv)
456 init_io(&io, NULL, IO_BG);
457 if (!format_command(io.sh, argv, FORMAT_NONE))
459 return run_io_do(&io);
463 run_io_fg(const char **argv, const char *dir)
467 init_io(&io, dir, IO_FG);
468 if (!format_command(io.sh, argv, FORMAT_NONE))
470 return run_io_do(&io);
474 run_io_rd(struct io *io, const char **argv, enum format_flags flags)
476 return init_io_rd(io, argv, NULL, flags) && start_io(io);
480 io_eof(struct io *io)
482 return feof(io->pipe);
486 io_error(struct io *io)
492 io_strerror(struct io *io)
494 return strerror(io->error);
498 io_gets(struct io *io)
501 io->buf = malloc(BUFSIZ);
504 io->bufalloc = BUFSIZ;
507 if (!fgets(io->buf, io->bufalloc, io->pipe)) {
508 if (ferror(io->pipe))
517 io_write(struct io *io, const void *buf, size_t bufsize)
521 while (!io_error(io) && written < bufsize) {
522 written += fwrite(buf + written, 1, bufsize - written, io->pipe);
523 if (ferror(io->pipe))
527 return written == bufsize;
531 run_io_buf(const char **argv, char buf[], size_t bufsize)
536 if (!run_io_rd(&io, argv, FORMAT_NONE))
540 io.bufalloc = bufsize;
541 error = !io_gets(&io) && io_error(&io);
544 return done_io(&io) || error;
547 static int read_properties(struct io *io, const char *separators, int (*read)(char *, size_t, char *, size_t));
554 /* XXX: Keep the view request first and in sync with views[]. */ \
555 REQ_GROUP("View switching") \
556 REQ_(VIEW_MAIN, "Show main view"), \
557 REQ_(VIEW_DIFF, "Show diff view"), \
558 REQ_(VIEW_LOG, "Show log view"), \
559 REQ_(VIEW_TREE, "Show tree view"), \
560 REQ_(VIEW_BLOB, "Show blob view"), \
561 REQ_(VIEW_BLAME, "Show blame view"), \
562 REQ_(VIEW_HELP, "Show help page"), \
563 REQ_(VIEW_PAGER, "Show pager view"), \
564 REQ_(VIEW_STATUS, "Show status view"), \
565 REQ_(VIEW_STAGE, "Show stage view"), \
567 REQ_GROUP("View manipulation") \
568 REQ_(ENTER, "Enter current line and scroll"), \
569 REQ_(NEXT, "Move to next"), \
570 REQ_(PREVIOUS, "Move to previous"), \
571 REQ_(VIEW_NEXT, "Move focus to next view"), \
572 REQ_(REFRESH, "Reload and refresh"), \
573 REQ_(MAXIMIZE, "Maximize the current view"), \
574 REQ_(VIEW_CLOSE, "Close the current view"), \
575 REQ_(QUIT, "Close all views and quit"), \
577 REQ_GROUP("View specific requests") \
578 REQ_(STATUS_UPDATE, "Update file status"), \
579 REQ_(STATUS_REVERT, "Revert file changes"), \
580 REQ_(STATUS_MERGE, "Merge file using external tool"), \
581 REQ_(STAGE_NEXT, "Find next chunk to stage"), \
582 REQ_(TREE_PARENT, "Switch to parent directory in tree view"), \
584 REQ_GROUP("Cursor navigation") \
585 REQ_(MOVE_UP, "Move cursor one line up"), \
586 REQ_(MOVE_DOWN, "Move cursor one line down"), \
587 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
588 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
589 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
590 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
592 REQ_GROUP("Scrolling") \
593 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
594 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
595 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
596 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
598 REQ_GROUP("Searching") \
599 REQ_(SEARCH, "Search the view"), \
600 REQ_(SEARCH_BACK, "Search backwards in the view"), \
601 REQ_(FIND_NEXT, "Find next search match"), \
602 REQ_(FIND_PREV, "Find previous search match"), \
604 REQ_GROUP("Option manipulation") \
605 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
606 REQ_(TOGGLE_DATE, "Toggle date display"), \
607 REQ_(TOGGLE_AUTHOR, "Toggle author display"), \
608 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
609 REQ_(TOGGLE_REFS, "Toggle reference display (tags/branches)"), \
612 REQ_(PROMPT, "Bring up the prompt"), \
613 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
614 REQ_(SCREEN_RESIZE, "Resize the screen"), \
615 REQ_(SHOW_VERSION, "Show version information"), \
616 REQ_(STOP_LOADING, "Stop all loading views"), \
617 REQ_(EDIT, "Open in editor"), \
618 REQ_(NONE, "Do nothing")
621 /* User action requests. */
623 #define REQ_GROUP(help)
624 #define REQ_(req, help) REQ_##req
626 /* Offset all requests to avoid conflicts with ncurses getch values. */
627 REQ_OFFSET = KEY_MAX + 1,
634 struct request_info {
635 enum request request;
641 static struct request_info req_info[] = {
642 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
643 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
650 get_request(const char *name)
652 int namelen = strlen(name);
655 for (i = 0; i < ARRAY_SIZE(req_info); i++)
656 if (req_info[i].namelen == namelen &&
657 !string_enum_compare(req_info[i].name, name, namelen))
658 return req_info[i].request;
668 static const char usage[] =
669 "tig " TIG_VERSION " (" __DATE__ ")\n"
671 "Usage: tig [options] [revs] [--] [paths]\n"
672 " or: tig show [options] [revs] [--] [paths]\n"
673 " or: tig blame [rev] path\n"
675 " or: tig < [git command output]\n"
678 " -v, --version Show version and exit\n"
679 " -h, --help Show help message and exit";
681 /* Option and state variables. */
682 static bool opt_date = TRUE;
683 static bool opt_author = TRUE;
684 static bool opt_line_number = FALSE;
685 static bool opt_line_graphics = TRUE;
686 static bool opt_rev_graph = FALSE;
687 static bool opt_show_refs = TRUE;
688 static int opt_num_interval = NUMBER_INTERVAL;
689 static int opt_tab_size = TAB_SIZE;
690 static int opt_author_cols = AUTHOR_COLS-1;
691 static char opt_path[SIZEOF_STR] = "";
692 static char opt_file[SIZEOF_STR] = "";
693 static char opt_ref[SIZEOF_REF] = "";
694 static char opt_head[SIZEOF_REF] = "";
695 static char opt_head_rev[SIZEOF_REV] = "";
696 static char opt_remote[SIZEOF_REF] = "";
697 static char opt_encoding[20] = "UTF-8";
698 static bool opt_utf8 = TRUE;
699 static char opt_codeset[20] = "UTF-8";
700 static iconv_t opt_iconv = ICONV_NONE;
701 static char opt_search[SIZEOF_STR] = "";
702 static char opt_cdup[SIZEOF_STR] = "";
703 static char opt_git_dir[SIZEOF_STR] = "";
704 static signed char opt_is_inside_work_tree = -1; /* set to TRUE or FALSE */
705 static char opt_editor[SIZEOF_STR] = "";
706 static FILE *opt_tty = NULL;
708 #define is_initial_commit() (!*opt_head_rev)
709 #define is_head_commit(rev) (!strcmp((rev), "HEAD") || !strcmp(opt_head_rev, (rev)))
712 parse_options(int argc, const char *argv[], const char ***run_argv)
714 enum request request = REQ_VIEW_MAIN;
715 const char *subcommand;
716 bool seen_dashdash = FALSE;
717 /* XXX: This is vulnerable to the user overriding options
718 * required for the main view parser. */
719 const char *custom_argv[SIZEOF_ARG] = {
720 "git", "log", "--no-color", "--pretty=raw", "--parents",
725 if (!isatty(STDIN_FILENO))
726 return REQ_VIEW_PAGER;
729 return REQ_VIEW_MAIN;
731 subcommand = argv[1];
732 if (!strcmp(subcommand, "status") || !strcmp(subcommand, "-S")) {
733 if (!strcmp(subcommand, "-S"))
734 warn("`-S' has been deprecated; use `tig status' instead");
736 warn("ignoring arguments after `%s'", subcommand);
737 return REQ_VIEW_STATUS;
739 } else if (!strcmp(subcommand, "blame")) {
740 if (argc <= 2 || argc > 4)
741 die("invalid number of options to blame\n\n%s", usage);
745 string_ncopy(opt_ref, argv[i], strlen(argv[i]));
749 string_ncopy(opt_file, argv[i], strlen(argv[i]));
750 return REQ_VIEW_BLAME;
752 } else if (!strcmp(subcommand, "show")) {
753 request = REQ_VIEW_DIFF;
755 } else if (!strcmp(subcommand, "log") || !strcmp(subcommand, "diff")) {
756 request = subcommand[0] == 'l' ? REQ_VIEW_LOG : REQ_VIEW_DIFF;
757 warn("`tig %s' has been deprecated", subcommand);
764 custom_argv[1] = subcommand;
768 for (i = 1 + !!subcommand; i < argc; i++) {
769 const char *opt = argv[i];
771 if (seen_dashdash || !strcmp(opt, "--")) {
772 seen_dashdash = TRUE;
774 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
775 printf("tig version %s\n", TIG_VERSION);
778 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
779 printf("%s\n", usage);
783 custom_argv[j++] = opt;
784 if (j >= ARRAY_SIZE(custom_argv))
785 die("command too long");
788 custom_argv[j] = NULL;
789 *run_argv = custom_argv;
796 * Line-oriented content detection.
800 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
801 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
802 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
803 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
804 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
805 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
806 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
807 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
808 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
809 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
810 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
811 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
812 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
813 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
814 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
815 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
816 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
817 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
818 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
819 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
820 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
821 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
822 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
823 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
824 LINE(AUTHOR, "author ", COLOR_CYAN, COLOR_DEFAULT, 0), \
825 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
826 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
827 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
828 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
829 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
830 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
831 LINE(DELIMITER, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
832 LINE(DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
833 LINE(LINE_NUMBER, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
834 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
835 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
836 LINE(MAIN_AUTHOR, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
837 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
838 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
839 LINE(MAIN_LOCAL_TAG,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
840 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
841 LINE(MAIN_TRACKED, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
842 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
843 LINE(MAIN_HEAD, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
844 LINE(MAIN_REVGRAPH,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
845 LINE(TREE_DIR, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
846 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
847 LINE(STAT_HEAD, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
848 LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
849 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
850 LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
851 LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
852 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
853 LINE(BLAME_ID, "", COLOR_MAGENTA, COLOR_DEFAULT, 0)
856 #define LINE(type, line, fg, bg, attr) \
864 const char *name; /* Option name. */
865 int namelen; /* Size of option name. */
866 const char *line; /* The start of line to match. */
867 int linelen; /* Size of string to match. */
868 int fg, bg, attr; /* Color and text attributes for the lines. */
871 static struct line_info line_info[] = {
872 #define LINE(type, line, fg, bg, attr) \
873 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
878 static enum line_type
879 get_line_type(const char *line)
881 int linelen = strlen(line);
884 for (type = 0; type < ARRAY_SIZE(line_info); type++)
885 /* Case insensitive search matches Signed-off-by lines better. */
886 if (linelen >= line_info[type].linelen &&
887 !strncasecmp(line_info[type].line, line, line_info[type].linelen))
894 get_line_attr(enum line_type type)
896 assert(type < ARRAY_SIZE(line_info));
897 return COLOR_PAIR(type) | line_info[type].attr;
900 static struct line_info *
901 get_line_info(const char *name)
903 size_t namelen = strlen(name);
906 for (type = 0; type < ARRAY_SIZE(line_info); type++)
907 if (namelen == line_info[type].namelen &&
908 !string_enum_compare(line_info[type].name, name, namelen))
909 return &line_info[type];
917 int default_bg = line_info[LINE_DEFAULT].bg;
918 int default_fg = line_info[LINE_DEFAULT].fg;
923 if (assume_default_colors(default_fg, default_bg) == ERR) {
924 default_bg = COLOR_BLACK;
925 default_fg = COLOR_WHITE;
928 for (type = 0; type < ARRAY_SIZE(line_info); type++) {
929 struct line_info *info = &line_info[type];
930 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
931 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
933 init_pair(type, fg, bg);
941 unsigned int selected:1;
942 unsigned int dirty:1;
944 void *data; /* User data */
954 enum request request;
957 static struct keybinding default_keybindings[] = {
959 { 'm', REQ_VIEW_MAIN },
960 { 'd', REQ_VIEW_DIFF },
961 { 'l', REQ_VIEW_LOG },
962 { 't', REQ_VIEW_TREE },
963 { 'f', REQ_VIEW_BLOB },
964 { 'B', REQ_VIEW_BLAME },
965 { 'p', REQ_VIEW_PAGER },
966 { 'h', REQ_VIEW_HELP },
967 { 'S', REQ_VIEW_STATUS },
968 { 'c', REQ_VIEW_STAGE },
970 /* View manipulation */
971 { 'q', REQ_VIEW_CLOSE },
972 { KEY_TAB, REQ_VIEW_NEXT },
973 { KEY_RETURN, REQ_ENTER },
974 { KEY_UP, REQ_PREVIOUS },
975 { KEY_DOWN, REQ_NEXT },
976 { 'R', REQ_REFRESH },
977 { KEY_F(5), REQ_REFRESH },
978 { 'O', REQ_MAXIMIZE },
980 /* Cursor navigation */
981 { 'k', REQ_MOVE_UP },
982 { 'j', REQ_MOVE_DOWN },
983 { KEY_HOME, REQ_MOVE_FIRST_LINE },
984 { KEY_END, REQ_MOVE_LAST_LINE },
985 { KEY_NPAGE, REQ_MOVE_PAGE_DOWN },
986 { ' ', REQ_MOVE_PAGE_DOWN },
987 { KEY_PPAGE, REQ_MOVE_PAGE_UP },
988 { 'b', REQ_MOVE_PAGE_UP },
989 { '-', REQ_MOVE_PAGE_UP },
992 { KEY_IC, REQ_SCROLL_LINE_UP },
993 { KEY_DC, REQ_SCROLL_LINE_DOWN },
994 { 'w', REQ_SCROLL_PAGE_UP },
995 { 's', REQ_SCROLL_PAGE_DOWN },
999 { '?', REQ_SEARCH_BACK },
1000 { 'n', REQ_FIND_NEXT },
1001 { 'N', REQ_FIND_PREV },
1005 { 'z', REQ_STOP_LOADING },
1006 { 'v', REQ_SHOW_VERSION },
1007 { 'r', REQ_SCREEN_REDRAW },
1008 { '.', REQ_TOGGLE_LINENO },
1009 { 'D', REQ_TOGGLE_DATE },
1010 { 'A', REQ_TOGGLE_AUTHOR },
1011 { 'g', REQ_TOGGLE_REV_GRAPH },
1012 { 'F', REQ_TOGGLE_REFS },
1013 { ':', REQ_PROMPT },
1014 { 'u', REQ_STATUS_UPDATE },
1015 { '!', REQ_STATUS_REVERT },
1016 { 'M', REQ_STATUS_MERGE },
1017 { '@', REQ_STAGE_NEXT },
1018 { ',', REQ_TREE_PARENT },
1021 /* Using the ncurses SIGWINCH handler. */
1022 { KEY_RESIZE, REQ_SCREEN_RESIZE },
1025 #define KEYMAP_INFO \
1039 #define KEYMAP_(name) KEYMAP_##name
1044 static struct int_map keymap_table[] = {
1045 #define KEYMAP_(name) { #name, STRING_SIZE(#name), KEYMAP_##name }
1050 #define set_keymap(map, name) \
1051 set_from_int_map(keymap_table, ARRAY_SIZE(keymap_table), map, name, strlen(name))
1053 struct keybinding_table {
1054 struct keybinding *data;
1058 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1061 add_keybinding(enum keymap keymap, enum request request, int key)
1063 struct keybinding_table *table = &keybindings[keymap];
1065 table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1067 die("Failed to allocate keybinding");
1068 table->data[table->size].alias = key;
1069 table->data[table->size++].request = request;
1072 /* Looks for a key binding first in the given map, then in the generic map, and
1073 * lastly in the default keybindings. */
1075 get_keybinding(enum keymap keymap, int key)
1079 for (i = 0; i < keybindings[keymap].size; i++)
1080 if (keybindings[keymap].data[i].alias == key)
1081 return keybindings[keymap].data[i].request;
1083 for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1084 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1085 return keybindings[KEYMAP_GENERIC].data[i].request;
1087 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1088 if (default_keybindings[i].alias == key)
1089 return default_keybindings[i].request;
1091 return (enum request) key;
1100 static struct key key_table[] = {
1101 { "Enter", KEY_RETURN },
1103 { "Backspace", KEY_BACKSPACE },
1105 { "Escape", KEY_ESC },
1106 { "Left", KEY_LEFT },
1107 { "Right", KEY_RIGHT },
1109 { "Down", KEY_DOWN },
1110 { "Insert", KEY_IC },
1111 { "Delete", KEY_DC },
1113 { "Home", KEY_HOME },
1115 { "PageUp", KEY_PPAGE },
1116 { "PageDown", KEY_NPAGE },
1126 { "F10", KEY_F(10) },
1127 { "F11", KEY_F(11) },
1128 { "F12", KEY_F(12) },
1132 get_key_value(const char *name)
1136 for (i = 0; i < ARRAY_SIZE(key_table); i++)
1137 if (!strcasecmp(key_table[i].name, name))
1138 return key_table[i].value;
1140 if (strlen(name) == 1 && isprint(*name))
1147 get_key_name(int key_value)
1149 static char key_char[] = "'X'";
1150 const char *seq = NULL;
1153 for (key = 0; key < ARRAY_SIZE(key_table); key++)
1154 if (key_table[key].value == key_value)
1155 seq = key_table[key].name;
1159 isprint(key_value)) {
1160 key_char[1] = (char) key_value;
1164 return seq ? seq : "(no key)";
1168 get_key(enum request request)
1170 static char buf[BUFSIZ];
1177 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1178 struct keybinding *keybinding = &default_keybindings[i];
1180 if (keybinding->request != request)
1183 if (!string_format_from(buf, &pos, "%s%s", sep,
1184 get_key_name(keybinding->alias)))
1185 return "Too many keybindings!";
1192 struct run_request {
1195 const char *argv[SIZEOF_ARG];
1198 static struct run_request *run_request;
1199 static size_t run_requests;
1202 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1204 struct run_request *req;
1206 if (argc >= ARRAY_SIZE(req->argv) - 1)
1209 req = realloc(run_request, (run_requests + 1) * sizeof(*run_request));
1214 req = &run_request[run_requests];
1215 req->keymap = keymap;
1217 req->argv[0] = NULL;
1219 if (!format_argv(req->argv, argv, FORMAT_NONE))
1222 return REQ_NONE + ++run_requests;
1225 static struct run_request *
1226 get_run_request(enum request request)
1228 if (request <= REQ_NONE)
1230 return &run_request[request - REQ_NONE - 1];
1234 add_builtin_run_requests(void)
1236 const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1237 const char *gc[] = { "git", "gc", NULL };
1244 { KEYMAP_MAIN, 'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1245 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1249 for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1252 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1253 if (req != REQ_NONE)
1254 add_keybinding(reqs[i].keymap, req, reqs[i].key);
1259 * User config file handling.
1262 static struct int_map color_map[] = {
1263 #define COLOR_MAP(name) { #name, STRING_SIZE(#name), COLOR_##name }
1275 #define set_color(color, name) \
1276 set_from_int_map(color_map, ARRAY_SIZE(color_map), color, name, strlen(name))
1278 static struct int_map attr_map[] = {
1279 #define ATTR_MAP(name) { #name, STRING_SIZE(#name), A_##name }
1286 ATTR_MAP(UNDERLINE),
1289 #define set_attribute(attr, name) \
1290 set_from_int_map(attr_map, ARRAY_SIZE(attr_map), attr, name, strlen(name))
1292 static int config_lineno;
1293 static bool config_errors;
1294 static const char *config_msg;
1296 /* Wants: object fgcolor bgcolor [attr] */
1298 option_color_command(int argc, const char *argv[])
1300 struct line_info *info;
1302 if (argc != 3 && argc != 4) {
1303 config_msg = "Wrong number of arguments given to color command";
1307 info = get_line_info(argv[0]);
1309 if (!string_enum_compare(argv[0], "main-delim", strlen("main-delim"))) {
1310 info = get_line_info("delimiter");
1312 } else if (!string_enum_compare(argv[0], "main-date", strlen("main-date"))) {
1313 info = get_line_info("date");
1316 config_msg = "Unknown color name";
1321 if (set_color(&info->fg, argv[1]) == ERR ||
1322 set_color(&info->bg, argv[2]) == ERR) {
1323 config_msg = "Unknown color";
1327 if (argc == 4 && set_attribute(&info->attr, argv[3]) == ERR) {
1328 config_msg = "Unknown attribute";
1335 static bool parse_bool(const char *s)
1337 return (!strcmp(s, "1") || !strcmp(s, "true") ||
1338 !strcmp(s, "yes")) ? TRUE : FALSE;
1342 parse_int(const char *s, int default_value, int min, int max)
1344 int value = atoi(s);
1346 return (value < min || value > max) ? default_value : value;
1349 /* Wants: name = value */
1351 option_set_command(int argc, const char *argv[])
1354 config_msg = "Wrong number of arguments given to set command";
1358 if (strcmp(argv[1], "=")) {
1359 config_msg = "No value assigned";
1363 if (!strcmp(argv[0], "show-author")) {
1364 opt_author = parse_bool(argv[2]);
1368 if (!strcmp(argv[0], "show-date")) {
1369 opt_date = parse_bool(argv[2]);
1373 if (!strcmp(argv[0], "show-rev-graph")) {
1374 opt_rev_graph = parse_bool(argv[2]);
1378 if (!strcmp(argv[0], "show-refs")) {
1379 opt_show_refs = parse_bool(argv[2]);
1383 if (!strcmp(argv[0], "show-line-numbers")) {
1384 opt_line_number = parse_bool(argv[2]);
1388 if (!strcmp(argv[0], "line-graphics")) {
1389 opt_line_graphics = parse_bool(argv[2]);
1393 if (!strcmp(argv[0], "line-number-interval")) {
1394 opt_num_interval = parse_int(argv[2], opt_num_interval, 1, 1024);
1398 if (!strcmp(argv[0], "author-width")) {
1399 opt_author_cols = parse_int(argv[2], opt_author_cols, 0, 1024);
1403 if (!strcmp(argv[0], "tab-size")) {
1404 opt_tab_size = parse_int(argv[2], opt_tab_size, 1, 1024);
1408 if (!strcmp(argv[0], "commit-encoding")) {
1409 const char *arg = argv[2];
1410 int arglen = strlen(arg);
1415 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1416 config_msg = "Unmatched quotation";
1419 arg += 1; arglen -= 2;
1421 string_ncopy(opt_encoding, arg, strlen(arg));
1426 config_msg = "Unknown variable name";
1430 /* Wants: mode request key */
1432 option_bind_command(int argc, const char *argv[])
1434 enum request request;
1439 config_msg = "Wrong number of arguments given to bind command";
1443 if (set_keymap(&keymap, argv[0]) == ERR) {
1444 config_msg = "Unknown key map";
1448 key = get_key_value(argv[1]);
1450 config_msg = "Unknown key";
1454 request = get_request(argv[2]);
1455 if (request == REQ_NONE) {
1456 const char *obsolete[] = { "cherry-pick" };
1457 size_t namelen = strlen(argv[2]);
1460 for (i = 0; i < ARRAY_SIZE(obsolete); i++) {
1461 if (namelen == strlen(obsolete[i]) &&
1462 !string_enum_compare(obsolete[i], argv[2], namelen)) {
1463 config_msg = "Obsolete request name";
1468 if (request == REQ_NONE && *argv[2]++ == '!')
1469 request = add_run_request(keymap, key, argc - 2, argv + 2);
1470 if (request == REQ_NONE) {
1471 config_msg = "Unknown request name";
1475 add_keybinding(keymap, request, key);
1481 set_option(const char *opt, char *value)
1483 const char *argv[SIZEOF_ARG];
1486 if (!argv_from_string(argv, &argc, value)) {
1487 config_msg = "Too many option arguments";
1491 if (!strcmp(opt, "color"))
1492 return option_color_command(argc, argv);
1494 if (!strcmp(opt, "set"))
1495 return option_set_command(argc, argv);
1497 if (!strcmp(opt, "bind"))
1498 return option_bind_command(argc, argv);
1500 config_msg = "Unknown option command";
1505 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1510 config_msg = "Internal error";
1512 /* Check for comment markers, since read_properties() will
1513 * only ensure opt and value are split at first " \t". */
1514 optlen = strcspn(opt, "#");
1518 if (opt[optlen] != 0) {
1519 config_msg = "No option value";
1523 /* Look for comment endings in the value. */
1524 size_t len = strcspn(value, "#");
1526 if (len < valuelen) {
1528 value[valuelen] = 0;
1531 status = set_option(opt, value);
1534 if (status == ERR) {
1535 fprintf(stderr, "Error on line %d, near '%.*s': %s\n",
1536 config_lineno, (int) optlen, opt, config_msg);
1537 config_errors = TRUE;
1540 /* Always keep going if errors are encountered. */
1545 load_option_file(const char *path)
1549 /* It's ok that the file doesn't exist. */
1550 if (!init_io_fd(&io, fopen(path, "r")))
1554 config_errors = FALSE;
1556 if (read_properties(&io, " \t", read_option) == ERR ||
1557 config_errors == TRUE)
1558 fprintf(stderr, "Errors while loading %s.\n", path);
1564 const char *home = getenv("HOME");
1565 const char *tigrc_user = getenv("TIGRC_USER");
1566 const char *tigrc_system = getenv("TIGRC_SYSTEM");
1567 char buf[SIZEOF_STR];
1569 add_builtin_run_requests();
1571 if (!tigrc_system) {
1572 if (!string_format(buf, "%s/tigrc", SYSCONFDIR))
1576 load_option_file(tigrc_system);
1579 if (!home || !string_format(buf, "%s/.tigrc", home))
1583 load_option_file(tigrc_user);
1596 /* The display array of active views and the index of the current view. */
1597 static struct view *display[2];
1598 static unsigned int current_view;
1600 /* Reading from the prompt? */
1601 static bool input_mode = FALSE;
1603 #define foreach_displayed_view(view, i) \
1604 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1606 #define displayed_views() (display[1] != NULL ? 2 : 1)
1608 /* Current head and commit ID */
1609 static char ref_blob[SIZEOF_REF] = "";
1610 static char ref_commit[SIZEOF_REF] = "HEAD";
1611 static char ref_head[SIZEOF_REF] = "HEAD";
1614 const char *name; /* View name */
1615 const char *cmd_env; /* Command line set via environment */
1616 const char *id; /* Points to either of ref_{head,commit,blob} */
1618 struct view_ops *ops; /* View operations */
1620 enum keymap keymap; /* What keymap does this view have */
1621 bool git_dir; /* Whether the view requires a git directory. */
1623 char ref[SIZEOF_REF]; /* Hovered commit reference */
1624 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
1626 int height, width; /* The width and height of the main window */
1627 WINDOW *win; /* The main window */
1628 WINDOW *title; /* The title window living below the main window */
1631 unsigned long offset; /* Offset of the window top */
1632 unsigned long lineno; /* Current line number */
1635 char grep[SIZEOF_STR]; /* Search string */
1636 regex_t *regex; /* Pre-compiled regex */
1638 /* If non-NULL, points to the view that opened this view. If this view
1639 * is closed tig will switch back to the parent view. */
1640 struct view *parent;
1643 size_t lines; /* Total number of lines */
1644 struct line *line; /* Line index */
1645 size_t line_alloc; /* Total number of allocated lines */
1646 size_t line_size; /* Total number of used lines */
1647 unsigned int digits; /* Number of digits in the lines member. */
1650 struct line *curline; /* Line currently being drawn. */
1651 enum line_type curtype; /* Attribute currently used for drawing. */
1652 unsigned long col; /* Column when drawing. */
1661 /* What type of content being displayed. Used in the title bar. */
1663 /* Default command arguments. */
1665 /* Open and reads in all view content. */
1666 bool (*open)(struct view *view);
1667 /* Read one line; updates view->line. */
1668 bool (*read)(struct view *view, char *data);
1669 /* Draw one line; @lineno must be < view->height. */
1670 bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
1671 /* Depending on view handle a special requests. */
1672 enum request (*request)(struct view *view, enum request request, struct line *line);
1673 /* Search for regex in a line. */
1674 bool (*grep)(struct view *view, struct line *line);
1676 void (*select)(struct view *view, struct line *line);
1679 static struct view_ops blame_ops;
1680 static struct view_ops blob_ops;
1681 static struct view_ops diff_ops;
1682 static struct view_ops help_ops;
1683 static struct view_ops log_ops;
1684 static struct view_ops main_ops;
1685 static struct view_ops pager_ops;
1686 static struct view_ops stage_ops;
1687 static struct view_ops status_ops;
1688 static struct view_ops tree_ops;
1690 #define VIEW_STR(name, env, ref, ops, map, git) \
1691 { name, #env, ref, ops, map, git }
1693 #define VIEW_(id, name, ops, git, ref) \
1694 VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
1697 static struct view views[] = {
1698 VIEW_(MAIN, "main", &main_ops, TRUE, ref_head),
1699 VIEW_(DIFF, "diff", &diff_ops, TRUE, ref_commit),
1700 VIEW_(LOG, "log", &log_ops, TRUE, ref_head),
1701 VIEW_(TREE, "tree", &tree_ops, TRUE, ref_commit),
1702 VIEW_(BLOB, "blob", &blob_ops, TRUE, ref_blob),
1703 VIEW_(BLAME, "blame", &blame_ops, TRUE, ref_commit),
1704 VIEW_(HELP, "help", &help_ops, FALSE, ""),
1705 VIEW_(PAGER, "pager", &pager_ops, FALSE, "stdin"),
1706 VIEW_(STATUS, "status", &status_ops, TRUE, ""),
1707 VIEW_(STAGE, "stage", &stage_ops, TRUE, ""),
1710 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
1711 #define VIEW_REQ(view) ((view) - views + REQ_OFFSET + 1)
1713 #define foreach_view(view, i) \
1714 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1716 #define view_is_displayed(view) \
1717 (view == display[0] || view == display[1])
1724 static int line_graphics[] = {
1725 /* LINE_GRAPHIC_VLINE: */ '|'
1729 set_view_attr(struct view *view, enum line_type type)
1731 if (!view->curline->selected && view->curtype != type) {
1732 wattrset(view->win, get_line_attr(type));
1733 wchgat(view->win, -1, 0, type, NULL);
1734 view->curtype = type;
1739 draw_chars(struct view *view, enum line_type type, const char *string,
1740 int max_len, bool use_tilde)
1744 int trimmed = FALSE;
1750 len = utf8_length(string, &col, max_len, &trimmed, use_tilde);
1752 col = len = strlen(string);
1753 if (len > max_len) {
1757 col = len = max_len;
1762 set_view_attr(view, type);
1763 waddnstr(view->win, string, len);
1764 if (trimmed && use_tilde) {
1765 set_view_attr(view, LINE_DELIMITER);
1766 waddch(view->win, '~');
1774 draw_space(struct view *view, enum line_type type, int max, int spaces)
1776 static char space[] = " ";
1779 spaces = MIN(max, spaces);
1781 while (spaces > 0) {
1782 int len = MIN(spaces, sizeof(space) - 1);
1784 col += draw_chars(view, type, space, spaces, FALSE);
1792 draw_lineno(struct view *view, unsigned int lineno)
1795 int digits3 = view->digits < 3 ? 3 : view->digits;
1796 int max_number = MIN(digits3, STRING_SIZE(number));
1797 int max = view->width - view->col;
1800 if (max < max_number)
1803 lineno += view->offset + 1;
1804 if (lineno == 1 || (lineno % opt_num_interval) == 0) {
1805 static char fmt[] = "%1ld";
1807 if (view->digits <= 9)
1808 fmt[1] = '0' + digits3;
1810 if (!string_format(number, fmt, lineno))
1812 col = draw_chars(view, LINE_LINE_NUMBER, number, max_number, TRUE);
1814 col = draw_space(view, LINE_LINE_NUMBER, max_number, max_number);
1818 set_view_attr(view, LINE_DEFAULT);
1819 waddch(view->win, line_graphics[LINE_GRAPHIC_VLINE]);
1824 col += draw_space(view, LINE_DEFAULT, max - col, 1);
1827 return view->width - view->col <= 0;
1831 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
1833 view->col += draw_chars(view, type, string, view->width - view->col, trim);
1834 return view->width - view->col <= 0;
1838 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
1840 int max = view->width - view->col;
1846 set_view_attr(view, type);
1847 /* Using waddch() instead of waddnstr() ensures that
1848 * they'll be rendered correctly for the cursor line. */
1849 for (i = 0; i < size; i++)
1850 waddch(view->win, graphic[i]);
1854 waddch(view->win, ' ');
1858 return view->width - view->col <= 0;
1862 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
1864 int max = MIN(view->width - view->col, len);
1868 col = draw_chars(view, type, text, max - 1, trim);
1870 col = draw_space(view, type, max - 1, max - 1);
1872 view->col += col + draw_space(view, LINE_DEFAULT, max - col, max - col);
1873 return view->width - view->col <= 0;
1877 draw_date(struct view *view, struct tm *time)
1879 char buf[DATE_COLS];
1884 timelen = strftime(buf, sizeof(buf), DATE_FORMAT, time);
1885 date = timelen ? buf : NULL;
1887 return draw_field(view, LINE_DATE, date, DATE_COLS, FALSE);
1891 draw_view_line(struct view *view, unsigned int lineno)
1894 bool selected = (view->offset + lineno == view->lineno);
1897 assert(view_is_displayed(view));
1899 if (view->offset + lineno >= view->lines)
1902 line = &view->line[view->offset + lineno];
1904 wmove(view->win, lineno, 0);
1906 view->curline = line;
1907 view->curtype = LINE_NONE;
1908 line->selected = FALSE;
1911 set_view_attr(view, LINE_CURSOR);
1912 line->selected = TRUE;
1913 view->ops->select(view, line);
1914 } else if (line->selected) {
1915 wclrtoeol(view->win);
1918 scrollok(view->win, FALSE);
1919 draw_ok = view->ops->draw(view, line, lineno);
1920 scrollok(view->win, TRUE);
1926 redraw_view_dirty(struct view *view)
1931 for (lineno = 0; lineno < view->height; lineno++) {
1932 struct line *line = &view->line[view->offset + lineno];
1938 if (!draw_view_line(view, lineno))
1944 redrawwin(view->win);
1946 wnoutrefresh(view->win);
1948 wrefresh(view->win);
1952 redraw_view_from(struct view *view, int lineno)
1954 assert(0 <= lineno && lineno < view->height);
1956 for (; lineno < view->height; lineno++) {
1957 if (!draw_view_line(view, lineno))
1961 redrawwin(view->win);
1963 wnoutrefresh(view->win);
1965 wrefresh(view->win);
1969 redraw_view(struct view *view)
1972 redraw_view_from(view, 0);
1977 update_view_title(struct view *view)
1979 char buf[SIZEOF_STR];
1980 char state[SIZEOF_STR];
1981 size_t bufpos = 0, statelen = 0;
1983 assert(view_is_displayed(view));
1985 if (view != VIEW(REQ_VIEW_STATUS) && (view->lines || view->pipe)) {
1986 unsigned int view_lines = view->offset + view->height;
1987 unsigned int lines = view->lines
1988 ? MIN(view_lines, view->lines) * 100 / view->lines
1991 string_format_from(state, &statelen, "- %s %d of %d (%d%%)",
1998 time_t secs = time(NULL) - view->start_time;
2000 /* Three git seconds are a long time ... */
2002 string_format_from(state, &statelen, " %lds", secs);
2006 string_format_from(buf, &bufpos, "[%s]", view->name);
2007 if (*view->ref && bufpos < view->width) {
2008 size_t refsize = strlen(view->ref);
2009 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2011 if (minsize < view->width)
2012 refsize = view->width - minsize + 7;
2013 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2016 if (statelen && bufpos < view->width) {
2017 string_format_from(buf, &bufpos, " %s", state);
2020 if (view == display[current_view])
2021 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2023 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2025 mvwaddnstr(view->title, 0, 0, buf, bufpos);
2026 wclrtoeol(view->title);
2027 wmove(view->title, 0, view->width - 1);
2030 wnoutrefresh(view->title);
2032 wrefresh(view->title);
2036 resize_display(void)
2039 struct view *base = display[0];
2040 struct view *view = display[1] ? display[1] : display[0];
2042 /* Setup window dimensions */
2044 getmaxyx(stdscr, base->height, base->width);
2046 /* Make room for the status window. */
2050 /* Horizontal split. */
2051 view->width = base->width;
2052 view->height = SCALE_SPLIT_VIEW(base->height);
2053 base->height -= view->height;
2055 /* Make room for the title bar. */
2059 /* Make room for the title bar. */
2064 foreach_displayed_view (view, i) {
2066 view->win = newwin(view->height, 0, offset, 0);
2068 die("Failed to create %s view", view->name);
2070 scrollok(view->win, TRUE);
2072 view->title = newwin(1, 0, offset + view->height, 0);
2074 die("Failed to create title window");
2077 wresize(view->win, view->height, view->width);
2078 mvwin(view->win, offset, 0);
2079 mvwin(view->title, offset + view->height, 0);
2082 offset += view->height + 1;
2087 redraw_display(void)
2092 foreach_displayed_view (view, i) {
2094 update_view_title(view);
2099 update_display_cursor(struct view *view)
2101 /* Move the cursor to the right-most column of the cursor line.
2103 * XXX: This could turn out to be a bit expensive, but it ensures that
2104 * the cursor does not jump around. */
2106 wmove(view->win, view->lineno - view->offset, view->width - 1);
2107 wrefresh(view->win);
2115 /* Scrolling backend */
2117 do_scroll_view(struct view *view, int lines)
2119 bool redraw_current_line = FALSE;
2121 /* The rendering expects the new offset. */
2122 view->offset += lines;
2124 assert(0 <= view->offset && view->offset < view->lines);
2127 /* Move current line into the view. */
2128 if (view->lineno < view->offset) {
2129 view->lineno = view->offset;
2130 redraw_current_line = TRUE;
2131 } else if (view->lineno >= view->offset + view->height) {
2132 view->lineno = view->offset + view->height - 1;
2133 redraw_current_line = TRUE;
2136 assert(view->offset <= view->lineno && view->lineno < view->lines);
2138 /* Redraw the whole screen if scrolling is pointless. */
2139 if (view->height < ABS(lines)) {
2143 int line = lines > 0 ? view->height - lines : 0;
2144 int end = line + ABS(lines);
2146 wscrl(view->win, lines);
2148 for (; line < end; line++) {
2149 if (!draw_view_line(view, line))
2153 if (redraw_current_line)
2154 draw_view_line(view, view->lineno - view->offset);
2157 redrawwin(view->win);
2158 wrefresh(view->win);
2162 /* Scroll frontend */
2164 scroll_view(struct view *view, enum request request)
2168 assert(view_is_displayed(view));
2171 case REQ_SCROLL_PAGE_DOWN:
2172 lines = view->height;
2173 case REQ_SCROLL_LINE_DOWN:
2174 if (view->offset + lines > view->lines)
2175 lines = view->lines - view->offset;
2177 if (lines == 0 || view->offset + view->height >= view->lines) {
2178 report("Cannot scroll beyond the last line");
2183 case REQ_SCROLL_PAGE_UP:
2184 lines = view->height;
2185 case REQ_SCROLL_LINE_UP:
2186 if (lines > view->offset)
2187 lines = view->offset;
2190 report("Cannot scroll beyond the first line");
2198 die("request %d not handled in switch", request);
2201 do_scroll_view(view, lines);
2206 move_view(struct view *view, enum request request)
2208 int scroll_steps = 0;
2212 case REQ_MOVE_FIRST_LINE:
2213 steps = -view->lineno;
2216 case REQ_MOVE_LAST_LINE:
2217 steps = view->lines - view->lineno - 1;
2220 case REQ_MOVE_PAGE_UP:
2221 steps = view->height > view->lineno
2222 ? -view->lineno : -view->height;
2225 case REQ_MOVE_PAGE_DOWN:
2226 steps = view->lineno + view->height >= view->lines
2227 ? view->lines - view->lineno - 1 : view->height;
2239 die("request %d not handled in switch", request);
2242 if (steps <= 0 && view->lineno == 0) {
2243 report("Cannot move beyond the first line");
2246 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2247 report("Cannot move beyond the last line");
2251 /* Move the current line */
2252 view->lineno += steps;
2253 assert(0 <= view->lineno && view->lineno < view->lines);
2255 /* Check whether the view needs to be scrolled */
2256 if (view->lineno < view->offset ||
2257 view->lineno >= view->offset + view->height) {
2258 scroll_steps = steps;
2259 if (steps < 0 && -steps > view->offset) {
2260 scroll_steps = -view->offset;
2262 } else if (steps > 0) {
2263 if (view->lineno == view->lines - 1 &&
2264 view->lines > view->height) {
2265 scroll_steps = view->lines - view->offset - 1;
2266 if (scroll_steps >= view->height)
2267 scroll_steps -= view->height - 1;
2272 if (!view_is_displayed(view)) {
2273 view->offset += scroll_steps;
2274 assert(0 <= view->offset && view->offset < view->lines);
2275 view->ops->select(view, &view->line[view->lineno]);
2279 /* Repaint the old "current" line if we be scrolling */
2280 if (ABS(steps) < view->height)
2281 draw_view_line(view, view->lineno - steps - view->offset);
2284 do_scroll_view(view, scroll_steps);
2288 /* Draw the current line */
2289 draw_view_line(view, view->lineno - view->offset);
2291 redrawwin(view->win);
2292 wrefresh(view->win);
2301 static void search_view(struct view *view, enum request request);
2304 find_next_line(struct view *view, unsigned long lineno, struct line *line)
2306 assert(view_is_displayed(view));
2308 if (!view->ops->grep(view, line))
2311 if (lineno - view->offset >= view->height) {
2312 view->offset = lineno;
2313 view->lineno = lineno;
2317 unsigned long old_lineno = view->lineno - view->offset;
2319 view->lineno = lineno;
2320 draw_view_line(view, old_lineno);
2322 draw_view_line(view, view->lineno - view->offset);
2323 redrawwin(view->win);
2324 wrefresh(view->win);
2327 report("Line %ld matches '%s'", lineno + 1, view->grep);
2332 find_next(struct view *view, enum request request)
2334 unsigned long lineno = view->lineno;
2339 report("No previous search");
2341 search_view(view, request);
2351 case REQ_SEARCH_BACK:
2360 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2361 lineno += direction;
2363 /* Note, lineno is unsigned long so will wrap around in which case it
2364 * will become bigger than view->lines. */
2365 for (; lineno < view->lines; lineno += direction) {
2366 struct line *line = &view->line[lineno];
2368 if (find_next_line(view, lineno, line))
2372 report("No match found for '%s'", view->grep);
2376 search_view(struct view *view, enum request request)
2381 regfree(view->regex);
2384 view->regex = calloc(1, sizeof(*view->regex));
2389 regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2390 if (regex_err != 0) {
2391 char buf[SIZEOF_STR] = "unknown error";
2393 regerror(regex_err, view->regex, buf, sizeof(buf));
2394 report("Search failed: %s", buf);
2398 string_copy(view->grep, opt_search);
2400 find_next(view, request);
2404 * Incremental updating
2408 reset_view(struct view *view)
2412 for (i = 0; i < view->lines; i++)
2413 free(view->line[i].data);
2420 view->line_size = 0;
2421 view->line_alloc = 0;
2426 free_argv(const char *argv[])
2430 for (argc = 0; argv[argc]; argc++)
2431 free((void *) argv[argc]);
2435 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
2437 char buf[SIZEOF_STR];
2439 bool noreplace = flags == FORMAT_NONE;
2441 free_argv(dst_argv);
2443 for (argc = 0; src_argv[argc]; argc++) {
2444 const char *arg = src_argv[argc];
2448 char *next = strstr(arg, "%(");
2449 int len = next - arg;
2452 if (!next || noreplace) {
2453 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
2458 } else if (!prefixcmp(next, "%(directory)")) {
2461 } else if (!prefixcmp(next, "%(file)")) {
2464 } else if (!prefixcmp(next, "%(ref)")) {
2465 value = *opt_ref ? opt_ref : "HEAD";
2467 } else if (!prefixcmp(next, "%(head)")) {
2470 } else if (!prefixcmp(next, "%(commit)")) {
2473 } else if (!prefixcmp(next, "%(blob)")) {
2477 report("Unknown replacement: `%s`", next);
2481 if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
2484 arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
2487 dst_argv[argc] = strdup(buf);
2488 if (!dst_argv[argc])
2492 dst_argv[argc] = NULL;
2494 return src_argv[argc] == NULL;
2498 format_command(char dst[], const char *src_argv[], enum format_flags flags)
2500 const char *dst_argv[SIZEOF_ARG * 2] = { NULL };
2504 if (!format_argv(dst_argv, src_argv, flags)) {
2505 free_argv(dst_argv);
2509 for (argc = 0; dst_argv[argc] && bufsize < SIZEOF_STR; argc++) {
2511 dst[bufsize++] = ' ';
2512 bufsize = sq_quote(dst, bufsize, dst_argv[argc]);
2515 if (bufsize < SIZEOF_STR)
2517 free_argv(dst_argv);
2519 return src_argv[argc] == NULL && bufsize < SIZEOF_STR;
2523 end_update(struct view *view, bool force)
2527 while (!view->ops->read(view, NULL))
2530 set_nonblocking_input(FALSE);
2531 done_io(view->pipe);
2536 setup_update(struct view *view, const char *vid)
2538 set_nonblocking_input(TRUE);
2540 string_copy_rev(view->vid, vid);
2541 view->pipe = &view->io;
2542 view->start_time = time(NULL);
2546 prepare_update(struct view *view, const char *argv[], const char *dir,
2547 enum format_flags flags)
2550 end_update(view, TRUE);
2551 return init_io_rd(&view->io, argv, dir, flags);
2555 prepare_update_file(struct view *view, const char *name)
2558 end_update(view, TRUE);
2559 return init_io_fd(&view->io, fopen(name, "r"));
2563 begin_update(struct view *view, bool refresh)
2566 if (!start_io(&view->io))
2570 if (view == VIEW(REQ_VIEW_TREE) && strcmp(view->vid, view->id))
2573 if (!run_io_rd(&view->io, view->ops->argv, FORMAT_ALL))
2576 /* Put the current ref_* value to the view title ref
2577 * member. This is needed by the blob view. Most other
2578 * views sets it automatically after loading because the
2579 * first line is a commit line. */
2580 string_copy_rev(view->ref, view->id);
2583 setup_update(view, view->id);
2588 #define ITEM_CHUNK_SIZE 256
2590 realloc_items(void *mem, size_t *size, size_t new_size, size_t item_size)
2592 size_t num_chunks = *size / ITEM_CHUNK_SIZE;
2593 size_t num_chunks_new = (new_size + ITEM_CHUNK_SIZE - 1) / ITEM_CHUNK_SIZE;
2595 if (mem == NULL || num_chunks != num_chunks_new) {
2596 *size = num_chunks_new * ITEM_CHUNK_SIZE;
2597 mem = realloc(mem, *size * item_size);
2603 static struct line *
2604 realloc_lines(struct view *view, size_t line_size)
2606 size_t alloc = view->line_alloc;
2607 struct line *tmp = realloc_items(view->line, &alloc, line_size,
2608 sizeof(*view->line));
2614 view->line_alloc = alloc;
2615 view->line_size = line_size;
2620 update_view(struct view *view)
2622 char out_buffer[BUFSIZ * 2];
2624 /* The number of lines to read. If too low it will cause too much
2625 * redrawing (and possible flickering), if too high responsiveness
2627 unsigned long lines = view->height;
2628 int redraw_from = -1;
2633 /* Only redraw if lines are visible. */
2634 if (view->offset + view->height >= view->lines)
2635 redraw_from = view->lines - view->offset;
2637 /* FIXME: This is probably not perfect for backgrounded views. */
2638 if (!realloc_lines(view, view->lines + lines))
2641 while ((line = io_gets(view->pipe))) {
2642 size_t linelen = strlen(line);
2645 line[linelen - 1] = 0;
2647 if (opt_iconv != ICONV_NONE) {
2648 ICONV_CONST char *inbuf = line;
2649 size_t inlen = linelen;
2651 char *outbuf = out_buffer;
2652 size_t outlen = sizeof(out_buffer);
2656 ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2657 if (ret != (size_t) -1) {
2659 linelen = strlen(out_buffer);
2663 if (!view->ops->read(view, line))
2673 lines = view->lines;
2674 for (digits = 0; lines; digits++)
2677 /* Keep the displayed view in sync with line number scaling. */
2678 if (digits != view->digits) {
2679 view->digits = digits;
2684 if (io_error(view->pipe)) {
2685 report("Failed to read: %s", io_strerror(view->pipe));
2686 end_update(view, TRUE);
2688 } else if (io_eof(view->pipe)) {
2690 end_update(view, FALSE);
2693 if (!view_is_displayed(view))
2696 if (view == VIEW(REQ_VIEW_TREE)) {
2697 /* Clear the view and redraw everything since the tree sorting
2698 * might have rearranged things. */
2701 } else if (redraw_from >= 0) {
2702 /* If this is an incremental update, redraw the previous line
2703 * since for commits some members could have changed when
2704 * loading the main view. */
2705 if (redraw_from > 0)
2708 /* Since revision graph visualization requires knowledge
2709 * about the parent commit, it causes a further one-off
2710 * needed to be redrawn for incremental updates. */
2711 if (redraw_from > 0 && opt_rev_graph)
2714 /* Incrementally draw avoids flickering. */
2715 redraw_view_from(view, redraw_from);
2718 if (view == VIEW(REQ_VIEW_BLAME))
2719 redraw_view_dirty(view);
2721 /* Update the title _after_ the redraw so that if the redraw picks up a
2722 * commit reference in view->ref it'll be available here. */
2723 update_view_title(view);
2727 report("Allocation failure");
2728 end_update(view, TRUE);
2732 static struct line *
2733 add_line_data(struct view *view, void *data, enum line_type type)
2735 struct line *line = &view->line[view->lines++];
2737 memset(line, 0, sizeof(*line));
2744 static struct line *
2745 add_line_text(struct view *view, const char *text, enum line_type type)
2747 char *data = text ? strdup(text) : NULL;
2749 return data ? add_line_data(view, data, type) : NULL;
2758 OPEN_DEFAULT = 0, /* Use default view switching. */
2759 OPEN_SPLIT = 1, /* Split current view. */
2760 OPEN_BACKGROUNDED = 2, /* Backgrounded. */
2761 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
2762 OPEN_NOMAXIMIZE = 8, /* Do not maximize the current view. */
2763 OPEN_REFRESH = 16, /* Refresh view using previous command. */
2764 OPEN_PREPARED = 32, /* Open already prepared command. */
2768 open_view(struct view *prev, enum request request, enum open_flags flags)
2770 bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
2771 bool split = !!(flags & OPEN_SPLIT);
2772 bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
2773 bool nomaximize = !!(flags & (OPEN_NOMAXIMIZE | OPEN_REFRESH));
2774 struct view *view = VIEW(request);
2775 int nviews = displayed_views();
2776 struct view *base_view = display[0];
2778 if (view == prev && nviews == 1 && !reload) {
2779 report("Already in %s view", view->name);
2783 if (view->git_dir && !opt_git_dir[0]) {
2784 report("The %s view is disabled in pager view", view->name);
2792 } else if (!nomaximize) {
2793 /* Maximize the current view. */
2794 memset(display, 0, sizeof(display));
2796 display[current_view] = view;
2799 /* Resize the view when switching between split- and full-screen,
2800 * or when switching between two different full-screen views. */
2801 if (nviews != displayed_views() ||
2802 (nviews == 1 && base_view != display[0]))
2806 end_update(view, TRUE);
2808 if (view->ops->open) {
2809 if (!view->ops->open(view)) {
2810 report("Failed to load %s view", view->name);
2814 } else if ((reload || strcmp(view->vid, view->id)) &&
2815 !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
2816 report("Failed to load %s view", view->name);
2820 if (split && prev->lineno - prev->offset >= prev->height) {
2821 /* Take the title line into account. */
2822 int lines = prev->lineno - prev->offset - prev->height + 1;
2824 /* Scroll the view that was split if the current line is
2825 * outside the new limited view. */
2826 do_scroll_view(prev, lines);
2829 if (prev && view != prev) {
2830 if (split && !backgrounded) {
2831 /* "Blur" the previous view. */
2832 update_view_title(prev);
2835 view->parent = prev;
2838 if (view->pipe && view->lines == 0) {
2839 /* Clear the old view and let the incremental updating refill
2843 } else if (view_is_displayed(view)) {
2848 /* If the view is backgrounded the above calls to report()
2849 * won't redraw the view title. */
2851 update_view_title(view);
2855 open_external_viewer(const char *argv[], const char *dir)
2857 def_prog_mode(); /* save current tty modes */
2858 endwin(); /* restore original tty modes */
2859 run_io_fg(argv, dir);
2860 fprintf(stderr, "Press Enter to continue");
2867 open_mergetool(const char *file)
2869 const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
2871 open_external_viewer(mergetool_argv, NULL);
2875 open_editor(bool from_root, const char *file)
2877 const char *editor_argv[] = { "vi", file, NULL };
2880 editor = getenv("GIT_EDITOR");
2881 if (!editor && *opt_editor)
2882 editor = opt_editor;
2884 editor = getenv("VISUAL");
2886 editor = getenv("EDITOR");
2890 editor_argv[0] = editor;
2891 open_external_viewer(editor_argv, from_root ? opt_cdup : NULL);
2895 open_run_request(enum request request)
2897 struct run_request *req = get_run_request(request);
2898 const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
2901 report("Unknown run request");
2905 if (format_argv(argv, req->argv, FORMAT_ALL))
2906 open_external_viewer(argv, NULL);
2911 * User request switch noodle
2915 view_driver(struct view *view, enum request request)
2919 if (request == REQ_NONE) {
2924 if (request > REQ_NONE) {
2925 open_run_request(request);
2926 /* FIXME: When all views can refresh always do this. */
2927 if (view == VIEW(REQ_VIEW_STATUS) ||
2928 view == VIEW(REQ_VIEW_MAIN) ||
2929 view == VIEW(REQ_VIEW_LOG) ||
2930 view == VIEW(REQ_VIEW_STAGE))
2931 request = REQ_REFRESH;
2936 if (view && view->lines) {
2937 request = view->ops->request(view, request, &view->line[view->lineno]);
2938 if (request == REQ_NONE)
2945 case REQ_MOVE_PAGE_UP:
2946 case REQ_MOVE_PAGE_DOWN:
2947 case REQ_MOVE_FIRST_LINE:
2948 case REQ_MOVE_LAST_LINE:
2949 move_view(view, request);
2952 case REQ_SCROLL_LINE_DOWN:
2953 case REQ_SCROLL_LINE_UP:
2954 case REQ_SCROLL_PAGE_DOWN:
2955 case REQ_SCROLL_PAGE_UP:
2956 scroll_view(view, request);
2959 case REQ_VIEW_BLAME:
2961 report("No file chosen, press %s to open tree view",
2962 get_key(REQ_VIEW_TREE));
2965 open_view(view, request, OPEN_DEFAULT);
2970 report("No file chosen, press %s to open tree view",
2971 get_key(REQ_VIEW_TREE));
2974 open_view(view, request, OPEN_DEFAULT);
2977 case REQ_VIEW_PAGER:
2978 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
2979 report("No pager content, press %s to run command from prompt",
2980 get_key(REQ_PROMPT));
2983 open_view(view, request, OPEN_DEFAULT);
2986 case REQ_VIEW_STAGE:
2987 if (!VIEW(REQ_VIEW_STAGE)->lines) {
2988 report("No stage content, press %s to open the status view and choose file",
2989 get_key(REQ_VIEW_STATUS));
2992 open_view(view, request, OPEN_DEFAULT);
2995 case REQ_VIEW_STATUS:
2996 if (opt_is_inside_work_tree == FALSE) {
2997 report("The status view requires a working tree");
3000 open_view(view, request, OPEN_DEFAULT);
3008 open_view(view, request, OPEN_DEFAULT);
3013 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3015 if ((view == VIEW(REQ_VIEW_DIFF) &&
3016 view->parent == VIEW(REQ_VIEW_MAIN)) ||
3017 (view == VIEW(REQ_VIEW_DIFF) &&
3018 view->parent == VIEW(REQ_VIEW_BLAME)) ||
3019 (view == VIEW(REQ_VIEW_STAGE) &&
3020 view->parent == VIEW(REQ_VIEW_STATUS)) ||
3021 (view == VIEW(REQ_VIEW_BLOB) &&
3022 view->parent == VIEW(REQ_VIEW_TREE))) {
3025 view = view->parent;
3026 line = view->lineno;
3027 move_view(view, request);
3028 if (view_is_displayed(view))
3029 update_view_title(view);
3030 if (line != view->lineno)
3031 view->ops->request(view, REQ_ENTER,
3032 &view->line[view->lineno]);
3035 move_view(view, request);
3041 int nviews = displayed_views();
3042 int next_view = (current_view + 1) % nviews;
3044 if (next_view == current_view) {
3045 report("Only one view is displayed");
3049 current_view = next_view;
3050 /* Blur out the title of the previous view. */
3051 update_view_title(view);
3056 report("Refreshing is not yet supported for the %s view", view->name);
3060 if (displayed_views() == 2)
3061 open_view(view, VIEW_REQ(view), OPEN_DEFAULT);
3064 case REQ_TOGGLE_LINENO:
3065 opt_line_number = !opt_line_number;
3069 case REQ_TOGGLE_DATE:
3070 opt_date = !opt_date;
3074 case REQ_TOGGLE_AUTHOR:
3075 opt_author = !opt_author;
3079 case REQ_TOGGLE_REV_GRAPH:
3080 opt_rev_graph = !opt_rev_graph;
3084 case REQ_TOGGLE_REFS:
3085 opt_show_refs = !opt_show_refs;
3090 case REQ_SEARCH_BACK:
3091 search_view(view, request);
3096 find_next(view, request);
3099 case REQ_STOP_LOADING:
3100 for (i = 0; i < ARRAY_SIZE(views); i++) {
3103 report("Stopped loading the %s view", view->name),
3104 end_update(view, TRUE);
3108 case REQ_SHOW_VERSION:
3109 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3112 case REQ_SCREEN_RESIZE:
3115 case REQ_SCREEN_REDRAW:
3120 report("Nothing to edit");
3124 report("Nothing to enter");
3127 case REQ_VIEW_CLOSE:
3128 /* XXX: Mark closed views by letting view->parent point to the
3129 * view itself. Parents to closed view should never be
3132 view->parent->parent != view->parent) {
3133 memset(display, 0, sizeof(display));
3135 display[current_view] = view->parent;
3136 view->parent = view;
3147 report("Unknown key, press 'h' for help");
3160 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3162 char *text = line->data;
3164 if (opt_line_number && draw_lineno(view, lineno))
3167 draw_text(view, line->type, text, TRUE);
3172 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3174 const char *describe_argv[] = { "git", "describe", commit_id, NULL };
3175 char refbuf[SIZEOF_STR];
3178 if (run_io_buf(describe_argv, refbuf, sizeof(refbuf)))
3179 ref = chomp_string(refbuf);
3184 /* This is the only fatal call, since it can "corrupt" the buffer. */
3185 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
3192 add_pager_refs(struct view *view, struct line *line)
3194 char buf[SIZEOF_STR];
3195 char *commit_id = (char *)line->data + STRING_SIZE("commit ");
3197 size_t bufpos = 0, refpos = 0;
3198 const char *sep = "Refs: ";
3199 bool is_tag = FALSE;
3201 assert(line->type == LINE_COMMIT);
3203 refs = get_refs(commit_id);
3205 if (view == VIEW(REQ_VIEW_DIFF))
3206 goto try_add_describe_ref;
3211 struct ref *ref = refs[refpos];
3212 const char *fmt = ref->tag ? "%s[%s]" :
3213 ref->remote ? "%s<%s>" : "%s%s";
3215 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
3220 } while (refs[refpos++]->next);
3222 if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
3223 try_add_describe_ref:
3224 /* Add <tag>-g<commit_id> "fake" reference. */
3225 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
3232 if (!realloc_lines(view, view->line_size + 1))
3235 add_line_text(view, buf, LINE_PP_REFS);
3239 pager_read(struct view *view, char *data)
3246 line = add_line_text(view, data, get_line_type(data));
3250 if (line->type == LINE_COMMIT &&
3251 (view == VIEW(REQ_VIEW_DIFF) ||
3252 view == VIEW(REQ_VIEW_LOG)))
3253 add_pager_refs(view, line);
3259 pager_request(struct view *view, enum request request, struct line *line)
3263 if (request != REQ_ENTER)
3266 if (line->type == LINE_COMMIT &&
3267 (view == VIEW(REQ_VIEW_LOG) ||
3268 view == VIEW(REQ_VIEW_PAGER))) {
3269 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3273 /* Always scroll the view even if it was split. That way
3274 * you can use Enter to scroll through the log view and
3275 * split open each commit diff. */
3276 scroll_view(view, REQ_SCROLL_LINE_DOWN);
3278 /* FIXME: A minor workaround. Scrolling the view will call report("")
3279 * but if we are scrolling a non-current view this won't properly
3280 * update the view title. */
3282 update_view_title(view);
3288 pager_grep(struct view *view, struct line *line)
3291 char *text = line->data;
3296 if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
3303 pager_select(struct view *view, struct line *line)
3305 if (line->type == LINE_COMMIT) {
3306 char *text = (char *)line->data + STRING_SIZE("commit ");
3308 if (view != VIEW(REQ_VIEW_PAGER))
3309 string_copy_rev(view->ref, text);
3310 string_copy_rev(ref_commit, text);
3314 static struct view_ops pager_ops = {
3325 static const char *log_argv[SIZEOF_ARG] = {
3326 "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3330 log_request(struct view *view, enum request request, struct line *line)
3335 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
3338 return pager_request(view, request, line);
3342 static struct view_ops log_ops = {
3353 static const char *diff_argv[SIZEOF_ARG] = {
3354 "git", "show", "--pretty=fuller", "--no-color", "--root",
3355 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
3358 static struct view_ops diff_ops = {
3374 help_open(struct view *view)
3377 int lines = ARRAY_SIZE(req_info) + 2;
3380 if (view->lines > 0)
3383 for (i = 0; i < ARRAY_SIZE(req_info); i++)
3384 if (!req_info[i].request)
3387 lines += run_requests + 1;
3389 view->line = calloc(lines, sizeof(*view->line));
3393 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
3395 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
3398 if (req_info[i].request == REQ_NONE)
3401 if (!req_info[i].request) {
3402 add_line_text(view, "", LINE_DEFAULT);
3403 add_line_text(view, req_info[i].help, LINE_DEFAULT);
3407 key = get_key(req_info[i].request);
3409 key = "(no key defined)";
3411 if (!string_format(buf, " %-25s %s", key, req_info[i].help))
3414 add_line_text(view, buf, LINE_DEFAULT);
3418 add_line_text(view, "", LINE_DEFAULT);
3419 add_line_text(view, "External commands:", LINE_DEFAULT);
3422 for (i = 0; i < run_requests; i++) {
3423 struct run_request *req = get_run_request(REQ_NONE + i + 1);
3425 char cmd[SIZEOF_STR];
3432 key = get_key_name(req->key);
3434 key = "(no key defined)";
3436 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
3437 if (!string_format_from(cmd, &bufpos, "%s%s",
3438 argc ? " " : "", req->argv[argc]))
3441 if (!string_format(buf, " %-10s %-14s `%s`",
3442 keymap_table[req->keymap].name, key, cmd))
3445 add_line_text(view, buf, LINE_DEFAULT);
3451 static struct view_ops help_ops = {
3467 struct tree_stack_entry {
3468 struct tree_stack_entry *prev; /* Entry below this in the stack */
3469 unsigned long lineno; /* Line number to restore */
3470 char *name; /* Position of name in opt_path */
3473 /* The top of the path stack. */
3474 static struct tree_stack_entry *tree_stack = NULL;
3475 unsigned long tree_lineno = 0;
3478 pop_tree_stack_entry(void)
3480 struct tree_stack_entry *entry = tree_stack;
3482 tree_lineno = entry->lineno;
3484 tree_stack = entry->prev;
3489 push_tree_stack_entry(const char *name, unsigned long lineno)
3491 struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
3492 size_t pathlen = strlen(opt_path);
3497 entry->prev = tree_stack;
3498 entry->name = opt_path + pathlen;
3501 if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
3502 pop_tree_stack_entry();
3506 /* Move the current line to the first tree entry. */
3508 entry->lineno = lineno;
3511 /* Parse output from git-ls-tree(1):
3513 * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile
3514 * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README
3515 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
3516 * 100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38 web.conf
3519 #define SIZEOF_TREE_ATTR \
3520 STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t")
3522 #define TREE_UP_FORMAT "040000 tree %s\t.."
3525 tree_compare_entry(enum line_type type1, const char *name1,
3526 enum line_type type2, const char *name2)
3528 if (type1 != type2) {
3529 if (type1 == LINE_TREE_DIR)
3534 return strcmp(name1, name2);
3538 tree_path(struct line *line)
3540 const char *path = line->data;
3542 return path + SIZEOF_TREE_ATTR;
3546 tree_read(struct view *view, char *text)
3548 size_t textlen = text ? strlen(text) : 0;
3549 char buf[SIZEOF_STR];
3551 enum line_type type;
3552 bool first_read = view->lines == 0;
3556 if (textlen <= SIZEOF_TREE_ATTR)
3559 type = text[STRING_SIZE("100644 ")] == 't'
3560 ? LINE_TREE_DIR : LINE_TREE_FILE;
3563 /* Add path info line */
3564 if (!string_format(buf, "Directory path /%s", opt_path) ||
3565 !realloc_lines(view, view->line_size + 1) ||
3566 !add_line_text(view, buf, LINE_DEFAULT))
3569 /* Insert "link" to parent directory. */
3571 if (!string_format(buf, TREE_UP_FORMAT, view->ref) ||
3572 !realloc_lines(view, view->line_size + 1) ||
3573 !add_line_text(view, buf, LINE_TREE_DIR))
3578 /* Strip the path part ... */
3580 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
3581 size_t striplen = strlen(opt_path);
3582 char *path = text + SIZEOF_TREE_ATTR;
3584 if (pathlen > striplen)
3585 memmove(path, path + striplen,
3586 pathlen - striplen + 1);
3589 /* Skip "Directory ..." and ".." line. */
3590 for (pos = 1 + !!*opt_path; pos < view->lines; pos++) {
3591 struct line *line = &view->line[pos];
3592 const char *path1 = tree_path(line);
3593 char *path2 = text + SIZEOF_TREE_ATTR;
3594 int cmp = tree_compare_entry(line->type, path1, type, path2);
3599 text = strdup(text);
3603 if (view->lines > pos)
3604 memmove(&view->line[pos + 1], &view->line[pos],
3605 (view->lines - pos) * sizeof(*line));
3607 line = &view->line[pos];
3614 if (!add_line_text(view, text, type))
3617 if (tree_lineno > view->lineno) {
3618 view->lineno = tree_lineno;
3626 tree_request(struct view *view, enum request request, struct line *line)
3628 enum open_flags flags;
3631 case REQ_VIEW_BLAME:
3632 if (line->type != LINE_TREE_FILE) {
3633 report("Blame only supported for files");
3637 string_copy(opt_ref, view->vid);
3641 if (line->type != LINE_TREE_FILE) {
3642 report("Edit only supported for files");
3643 } else if (!is_head_commit(view->vid)) {
3644 report("Edit only supported for files in the current work tree");
3646 open_editor(TRUE, opt_file);
3650 case REQ_TREE_PARENT:
3652 /* quit view if at top of tree */
3653 return REQ_VIEW_CLOSE;
3656 line = &view->line[1];
3666 /* Cleanup the stack if the tree view is at a different tree. */
3667 while (!*opt_path && tree_stack)
3668 pop_tree_stack_entry();
3670 switch (line->type) {
3672 /* Depending on whether it is a subdir or parent (updir?) link
3673 * mangle the path buffer. */
3674 if (line == &view->line[1] && *opt_path) {
3675 pop_tree_stack_entry();
3678 const char *basename = tree_path(line);
3680 push_tree_stack_entry(basename, view->lineno);
3683 /* Trees and subtrees share the same ID, so they are not not
3684 * unique like blobs. */
3685 flags = OPEN_RELOAD;
3686 request = REQ_VIEW_TREE;
3689 case LINE_TREE_FILE:
3690 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
3691 request = REQ_VIEW_BLOB;
3698 open_view(view, request, flags);
3699 if (request == REQ_VIEW_TREE) {
3700 view->lineno = tree_lineno;
3707 tree_select(struct view *view, struct line *line)
3709 char *text = (char *)line->data + STRING_SIZE("100644 blob ");
3711 if (line->type == LINE_TREE_FILE) {
3712 string_copy_rev(ref_blob, text);
3713 string_format(opt_file, "%s%s", opt_path, tree_path(line));
3715 } else if (line->type != LINE_TREE_DIR) {
3719 string_copy_rev(view->ref, text);
3722 static const char *tree_argv[SIZEOF_ARG] = {
3723 "git", "ls-tree", "%(commit)", "%(directory)", NULL
3726 static struct view_ops tree_ops = {
3738 blob_read(struct view *view, char *line)
3742 return add_line_text(view, line, LINE_DEFAULT) != NULL;
3745 static const char *blob_argv[SIZEOF_ARG] = {
3746 "git", "cat-file", "blob", "%(blob)", NULL
3749 static struct view_ops blob_ops = {
3763 * Loading the blame view is a two phase job:
3765 * 1. File content is read either using opt_file from the
3766 * filesystem or using git-cat-file.
3767 * 2. Then blame information is incrementally added by
3768 * reading output from git-blame.
3771 static const char *blame_head_argv[] = {
3772 "git", "blame", "--incremental", "--", "%(file)", NULL
3775 static const char *blame_ref_argv[] = {
3776 "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
3779 static const char *blame_cat_file_argv[] = {
3780 "git", "cat-file", "blob", "%(ref):%(file)", NULL
3783 struct blame_commit {
3784 char id[SIZEOF_REV]; /* SHA1 ID. */
3785 char title[128]; /* First line of the commit message. */
3786 char author[75]; /* Author of the commit. */
3787 struct tm time; /* Date from the author ident. */
3788 char filename[128]; /* Name of file. */
3792 struct blame_commit *commit;
3797 blame_open(struct view *view)
3799 if (*opt_ref || !init_io_fd(&view->io, fopen(opt_file, "r"))) {
3800 if (!run_io_rd(&view->io, blame_cat_file_argv, FORMAT_ALL))
3804 setup_update(view, opt_file);
3805 string_format(view->ref, "%s ...", opt_file);
3810 static struct blame_commit *
3811 get_blame_commit(struct view *view, const char *id)
3815 for (i = 0; i < view->lines; i++) {
3816 struct blame *blame = view->line[i].data;
3821 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
3822 return blame->commit;
3826 struct blame_commit *commit = calloc(1, sizeof(*commit));
3829 string_ncopy(commit->id, id, SIZEOF_REV);
3835 parse_number(const char **posref, size_t *number, size_t min, size_t max)
3837 const char *pos = *posref;
3840 pos = strchr(pos + 1, ' ');
3841 if (!pos || !isdigit(pos[1]))
3843 *number = atoi(pos + 1);
3844 if (*number < min || *number > max)
3851 static struct blame_commit *
3852 parse_blame_commit(struct view *view, const char *text, int *blamed)
3854 struct blame_commit *commit;
3855 struct blame *blame;
3856 const char *pos = text + SIZEOF_REV - 1;
3860 if (strlen(text) <= SIZEOF_REV || *pos != ' ')
3863 if (!parse_number(&pos, &lineno, 1, view->lines) ||
3864 !parse_number(&pos, &group, 1, view->lines - lineno + 1))
3867 commit = get_blame_commit(view, text);
3873 struct line *line = &view->line[lineno + group - 1];
3876 blame->commit = commit;
3884 blame_read_file(struct view *view, const char *line, bool *read_file)
3887 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
3890 if (view->lines == 0 && !view->parent)
3891 die("No blame exist for %s", view->vid);
3893 if (view->lines == 0 || !run_io_rd(&io, argv, FORMAT_ALL)) {
3894 report("Failed to load blame data");
3898 done_io(view->pipe);
3904 size_t linelen = strlen(line);
3905 struct blame *blame = malloc(sizeof(*blame) + linelen);
3907 blame->commit = NULL;
3908 strncpy(blame->text, line, linelen);
3909 blame->text[linelen] = 0;
3910 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
3915 match_blame_header(const char *name, char **line)
3917 size_t namelen = strlen(name);
3918 bool matched = !strncmp(name, *line, namelen);
3927 blame_read(struct view *view, char *line)
3929 static struct blame_commit *commit = NULL;
3930 static int blamed = 0;
3931 static time_t author_time;
3932 static bool read_file = TRUE;
3935 return blame_read_file(view, line, &read_file);
3942 string_format(view->ref, "%s", view->vid);
3943 if (view_is_displayed(view)) {
3944 update_view_title(view);
3945 redraw_view_from(view, 0);
3951 commit = parse_blame_commit(view, line, &blamed);
3952 string_format(view->ref, "%s %2d%%", view->vid,
3953 blamed * 100 / view->lines);
3955 } else if (match_blame_header("author ", &line)) {
3956 string_ncopy(commit->author, line, strlen(line));
3958 } else if (match_blame_header("author-time ", &line)) {
3959 author_time = (time_t) atol(line);
3961 } else if (match_blame_header("author-tz ", &line)) {
3964 tz = ('0' - line[1]) * 60 * 60 * 10;
3965 tz += ('0' - line[2]) * 60 * 60;
3966 tz += ('0' - line[3]) * 60;
3967 tz += ('0' - line[4]) * 60;
3973 gmtime_r(&author_time, &commit->time);
3975 } else if (match_blame_header("summary ", &line)) {
3976 string_ncopy(commit->title, line, strlen(line));
3978 } else if (match_blame_header("filename ", &line)) {
3979 string_ncopy(commit->filename, line, strlen(line));
3987 blame_draw(struct view *view, struct line *line, unsigned int lineno)
3989 struct blame *blame = line->data;
3990 struct tm *time = NULL;
3991 const char *id = NULL, *author = NULL;
3993 if (blame->commit && *blame->commit->filename) {
3994 id = blame->commit->id;
3995 author = blame->commit->author;
3996 time = &blame->commit->time;
3999 if (opt_date && draw_date(view, time))
4003 draw_field(view, LINE_MAIN_AUTHOR, author, opt_author_cols, TRUE))
4006 if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
4009 if (draw_lineno(view, lineno))
4012 draw_text(view, LINE_DEFAULT, blame->text, TRUE);
4017 blame_request(struct view *view, enum request request, struct line *line)
4019 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4020 struct blame *blame = line->data;
4023 case REQ_VIEW_BLAME:
4024 if (!blame->commit || !strcmp(blame->commit->id, NULL_ID)) {
4025 report("Commit ID unknown");
4028 string_copy(opt_ref, blame->commit->id);
4029 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4033 if (!blame->commit) {
4034 report("No commit loaded yet");
4038 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
4039 !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
4042 if (!strcmp(blame->commit->id, NULL_ID)) {
4043 struct view *diff = VIEW(REQ_VIEW_DIFF);
4044 const char *diff_index_argv[] = {
4045 "git", "diff-index", "--root", "--cached",
4046 "--patch-with-stat", "-C", "-M",
4047 "HEAD", "--", view->vid, NULL
4050 if (!prepare_update(diff, diff_index_argv, NULL, FORMAT_DASH)) {
4051 report("Failed to allocate diff command");
4054 flags |= OPEN_PREPARED;
4057 open_view(view, REQ_VIEW_DIFF, flags);
4068 blame_grep(struct view *view, struct line *line)
4070 struct blame *blame = line->data;
4071 struct blame_commit *commit = blame->commit;
4074 #define MATCH(text, on) \
4075 (on && *text && regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4078 char buf[DATE_COLS + 1];
4080 if (MATCH(commit->title, 1) ||
4081 MATCH(commit->author, opt_author) ||
4082 MATCH(commit->id, opt_date))
4085 if (strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time) &&
4090 return MATCH(blame->text, 1);
4096 blame_select(struct view *view, struct line *line)
4098 struct blame *blame = line->data;
4099 struct blame_commit *commit = blame->commit;
4104 if (!strcmp(commit->id, NULL_ID))
4105 string_ncopy(ref_commit, "HEAD", 4);
4107 string_copy_rev(ref_commit, commit->id);
4110 static struct view_ops blame_ops = {
4129 char rev[SIZEOF_REV];
4130 char name[SIZEOF_STR];
4134 char rev[SIZEOF_REV];
4135 char name[SIZEOF_STR];
4139 static char status_onbranch[SIZEOF_STR];
4140 static struct status stage_status;
4141 static enum line_type stage_line_type;
4142 static size_t stage_chunks;
4143 static int *stage_chunk;
4145 /* This should work even for the "On branch" line. */
4147 status_has_none(struct view *view, struct line *line)
4149 return line < view->line + view->lines && !line[1].data;
4152 /* Get fields from the diff line:
4153 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
4156 status_get_diff(struct status *file, const char *buf, size_t bufsize)
4158 const char *old_mode = buf + 1;
4159 const char *new_mode = buf + 8;
4160 const char *old_rev = buf + 15;
4161 const char *new_rev = buf + 56;
4162 const char *status = buf + 97;
4165 old_mode[-1] != ':' ||
4166 new_mode[-1] != ' ' ||
4167 old_rev[-1] != ' ' ||
4168 new_rev[-1] != ' ' ||
4172 file->status = *status;
4174 string_copy_rev(file->old.rev, old_rev);
4175 string_copy_rev(file->new.rev, new_rev);
4177 file->old.mode = strtoul(old_mode, NULL, 8);
4178 file->new.mode = strtoul(new_mode, NULL, 8);
4180 file->old.name[0] = file->new.name[0] = 0;
4186 status_run(struct view *view, const char cmd[], char status, enum line_type type)
4188 struct status *file = NULL;
4189 struct status *unmerged = NULL;
4190 char buf[SIZEOF_STR * 4];
4194 pipe = popen(cmd, "r");
4198 add_line_data(view, NULL, type);
4200 while (!feof(pipe) && !ferror(pipe)) {
4204 readsize = fread(buf + bufsize, 1, sizeof(buf) - bufsize, pipe);
4207 bufsize += readsize;
4209 /* Process while we have NUL chars. */
4210 while ((sep = memchr(buf, 0, bufsize))) {
4211 size_t sepsize = sep - buf + 1;
4214 if (!realloc_lines(view, view->line_size + 1))
4217 file = calloc(1, sizeof(*file));
4221 add_line_data(view, file, type);
4224 /* Parse diff info part. */
4226 file->status = status;
4228 string_copy(file->old.rev, NULL_ID);
4230 } else if (!file->status) {
4231 if (!status_get_diff(file, buf, sepsize))
4235 memmove(buf, sep + 1, bufsize);
4237 sep = memchr(buf, 0, bufsize);
4240 sepsize = sep - buf + 1;
4242 /* Collapse all 'M'odified entries that
4243 * follow a associated 'U'nmerged entry.
4245 if (file->status == 'U') {
4248 } else if (unmerged) {
4249 int collapse = !strcmp(buf, unmerged->new.name);
4260 /* Grab the old name for rename/copy. */
4261 if (!*file->old.name &&
4262 (file->status == 'R' || file->status == 'C')) {
4263 sepsize = sep - buf + 1;
4264 string_ncopy(file->old.name, buf, sepsize);
4266 memmove(buf, sep + 1, bufsize);
4268 sep = memchr(buf, 0, bufsize);
4271 sepsize = sep - buf + 1;
4274 /* git-ls-files just delivers a NUL separated
4275 * list of file names similar to the second half
4276 * of the git-diff-* output. */
4277 string_ncopy(file->new.name, buf, sepsize);
4278 if (!*file->old.name)
4279 string_copy(file->old.name, file->new.name);
4281 memmove(buf, sep + 1, bufsize);
4292 if (!view->line[view->lines - 1].data)
4293 add_line_data(view, NULL, LINE_STAT_NONE);
4299 /* Don't show unmerged entries in the staged section. */
4300 #define STATUS_DIFF_INDEX_CMD "git diff-index -z --diff-filter=ACDMRTXB --cached -M HEAD"
4301 #define STATUS_DIFF_FILES_CMD "git diff-files -z"
4302 #define STATUS_LIST_OTHER_CMD \
4303 "git ls-files -z --others --exclude-standard"
4304 #define STATUS_LIST_NO_HEAD_CMD \
4305 "git ls-files -z --cached --exclude-standard"
4307 static const char *update_index_argv[] = {
4308 "git", "update-index", "-q", "--unmerged", "--refresh", NULL
4311 /* First parse staged info using git-diff-index(1), then parse unstaged
4312 * info using git-diff-files(1), and finally untracked files using
4313 * git-ls-files(1). */
4315 status_open(struct view *view)
4317 unsigned long prev_lineno = view->lineno;
4321 if (!realloc_lines(view, view->line_size + 7))
4324 add_line_data(view, NULL, LINE_STAT_HEAD);
4325 if (is_initial_commit())
4326 string_copy(status_onbranch, "Initial commit");
4327 else if (!*opt_head)
4328 string_copy(status_onbranch, "Not currently on any branch");
4329 else if (!string_format(status_onbranch, "On branch %s", opt_head))
4332 system("git update-index -q --refresh >/dev/null 2>/dev/null");
4334 if (is_initial_commit()) {
4335 if (!status_run(view, STATUS_LIST_NO_HEAD_CMD, 'A', LINE_STAT_STAGED))
4337 } else if (!status_run(view, STATUS_DIFF_INDEX_CMD, 0, LINE_STAT_STAGED)) {
4341 if (!status_run(view, STATUS_DIFF_FILES_CMD, 0, LINE_STAT_UNSTAGED) ||
4342 !status_run(view, STATUS_LIST_OTHER_CMD, '?', LINE_STAT_UNTRACKED))
4345 /* If all went well restore the previous line number to stay in
4346 * the context or select a line with something that can be
4348 if (prev_lineno >= view->lines)
4349 prev_lineno = view->lines - 1;
4350 while (prev_lineno < view->lines && !view->line[prev_lineno].data)
4352 while (prev_lineno > 0 && !view->line[prev_lineno].data)
4355 /* If the above fails, always skip the "On branch" line. */
4356 if (prev_lineno < view->lines)
4357 view->lineno = prev_lineno;
4361 if (view->lineno < view->offset)
4362 view->offset = view->lineno;
4363 else if (view->offset + view->height <= view->lineno)
4364 view->offset = view->lineno - view->height + 1;
4370 status_draw(struct view *view, struct line *line, unsigned int lineno)
4372 struct status *status = line->data;
4373 enum line_type type;
4377 switch (line->type) {
4378 case LINE_STAT_STAGED:
4379 type = LINE_STAT_SECTION;
4380 text = "Changes to be committed:";
4383 case LINE_STAT_UNSTAGED:
4384 type = LINE_STAT_SECTION;
4385 text = "Changed but not updated:";
4388 case LINE_STAT_UNTRACKED:
4389 type = LINE_STAT_SECTION;
4390 text = "Untracked files:";
4393 case LINE_STAT_NONE:
4394 type = LINE_DEFAULT;
4395 text = " (no files)";
4398 case LINE_STAT_HEAD:
4399 type = LINE_STAT_HEAD;
4400 text = status_onbranch;
4407 static char buf[] = { '?', ' ', ' ', ' ', 0 };
4409 buf[0] = status->status;
4410 if (draw_text(view, line->type, buf, TRUE))
4412 type = LINE_DEFAULT;
4413 text = status->new.name;
4416 draw_text(view, type, text, TRUE);
4421 status_enter(struct view *view, struct line *line)
4423 struct status *status = line->data;
4424 const char *oldpath = status ? status->old.name : NULL;
4425 /* Diffs for unmerged entries are empty when passing the new
4426 * path, so leave it empty. */
4427 const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
4429 enum open_flags split;
4430 struct view *stage = VIEW(REQ_VIEW_STAGE);
4432 if (line->type == LINE_STAT_NONE ||
4433 (!status && line[1].type == LINE_STAT_NONE)) {
4434 report("No file to diff");
4438 switch (line->type) {
4439 case LINE_STAT_STAGED:
4440 if (is_initial_commit()) {
4441 const char *no_head_diff_argv[] = {
4442 "git", "diff", "--no-color", "--patch-with-stat",
4443 "--", "/dev/null", newpath, NULL
4446 if (!prepare_update(stage, no_head_diff_argv, opt_cdup, FORMAT_DASH))
4449 const char *index_show_argv[] = {
4450 "git", "diff-index", "--root", "--patch-with-stat",
4451 "-C", "-M", "--cached", "HEAD", "--",
4452 oldpath, newpath, NULL
4455 if (!prepare_update(stage, index_show_argv, opt_cdup, FORMAT_DASH))
4460 info = "Staged changes to %s";
4462 info = "Staged changes";
4465 case LINE_STAT_UNSTAGED:
4467 const char *files_show_argv[] = {
4468 "git", "diff-files", "--root", "--patch-with-stat",
4469 "-C", "-M", "--", oldpath, newpath, NULL
4472 if (!prepare_update(stage, files_show_argv, opt_cdup, FORMAT_DASH))
4475 info = "Unstaged changes to %s";
4477 info = "Unstaged changes";
4480 case LINE_STAT_UNTRACKED:
4482 report("No file to show");
4486 if (!suffixcmp(status->new.name, -1, "/")) {
4487 report("Cannot display a directory");
4491 if (!prepare_update_file(stage, newpath))
4493 info = "Untracked file %s";
4496 case LINE_STAT_HEAD:
4500 die("line type %d not handled in switch", line->type);
4503 split = view_is_displayed(view) ? OPEN_SPLIT : 0;
4504 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH | split);
4505 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
4507 stage_status = *status;
4509 memset(&stage_status, 0, sizeof(stage_status));
4512 stage_line_type = line->type;
4514 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
4521 status_exists(struct status *status, enum line_type type)
4523 struct view *view = VIEW(REQ_VIEW_STATUS);
4526 for (line = view->line; line < view->line + view->lines; line++) {
4527 struct status *pos = line->data;
4529 if (line->type == type && pos &&
4530 !strcmp(status->new.name, pos->new.name))
4539 status_update_prepare(enum line_type type)
4541 char cmd[SIZEOF_STR];
4545 type != LINE_STAT_UNTRACKED &&
4546 !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
4550 case LINE_STAT_STAGED:
4551 string_add(cmd, cmdsize, "git update-index -z --index-info");
4554 case LINE_STAT_UNSTAGED:
4555 case LINE_STAT_UNTRACKED:
4556 string_add(cmd, cmdsize, "git update-index -z --add --remove --stdin");
4560 die("line type %d not handled in switch", type);
4563 return popen(cmd, "w");
4567 status_update_write(FILE *pipe, struct status *status, enum line_type type)
4569 char buf[SIZEOF_STR];
4574 case LINE_STAT_STAGED:
4575 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
4578 status->old.name, 0))
4582 case LINE_STAT_UNSTAGED:
4583 case LINE_STAT_UNTRACKED:
4584 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
4589 die("line type %d not handled in switch", type);
4592 while (!ferror(pipe) && written < bufsize) {
4593 written += fwrite(buf + written, 1, bufsize - written, pipe);
4596 return written == bufsize;
4600 status_update_file(struct status *status, enum line_type type)
4602 FILE *pipe = status_update_prepare(type);
4608 result = status_update_write(pipe, status, type);
4614 status_update_files(struct view *view, struct line *line)
4616 FILE *pipe = status_update_prepare(line->type);
4618 struct line *pos = view->line + view->lines;
4625 for (pos = line; pos < view->line + view->lines && pos->data; pos++)
4628 for (file = 0, done = 0; result && file < files; line++, file++) {
4629 int almost_done = file * 100 / files;
4631 if (almost_done > done) {
4633 string_format(view->ref, "updating file %u of %u (%d%% done)",
4635 update_view_title(view);
4637 result = status_update_write(pipe, line->data, line->type);
4645 status_update(struct view *view)
4647 struct line *line = &view->line[view->lineno];
4649 assert(view->lines);
4652 /* This should work even for the "On branch" line. */
4653 if (line < view->line + view->lines && !line[1].data) {
4654 report("Nothing to update");
4658 if (!status_update_files(view, line + 1)) {
4659 report("Failed to update file status");
4663 } else if (!status_update_file(line->data, line->type)) {
4664 report("Failed to update file status");
4672 status_revert(struct status *status, enum line_type type, bool has_none)
4674 if (!status || type != LINE_STAT_UNSTAGED) {
4675 if (type == LINE_STAT_STAGED) {
4676 report("Cannot revert changes to staged files");
4677 } else if (type == LINE_STAT_UNTRACKED) {
4678 report("Cannot revert changes to untracked files");
4679 } else if (has_none) {
4680 report("Nothing to revert");
4682 report("Cannot revert changes to multiple files");
4687 const char *checkout_argv[] = {
4688 "git", "checkout", "--", status->old.name, NULL
4691 if (!prompt_yesno("Are you sure you want to overwrite any changes?"))
4693 return run_io_fg(checkout_argv, opt_cdup);
4698 status_request(struct view *view, enum request request, struct line *line)
4700 struct status *status = line->data;
4703 case REQ_STATUS_UPDATE:
4704 if (!status_update(view))
4708 case REQ_STATUS_REVERT:
4709 if (!status_revert(status, line->type, status_has_none(view, line)))
4713 case REQ_STATUS_MERGE:
4714 if (!status || status->status != 'U') {
4715 report("Merging only possible for files with unmerged status ('U').");
4718 open_mergetool(status->new.name);
4724 if (status->status == 'D') {
4725 report("File has been deleted.");
4729 open_editor(status->status != '?', status->new.name);
4732 case REQ_VIEW_BLAME:
4734 string_copy(opt_file, status->new.name);
4740 /* After returning the status view has been split to
4741 * show the stage view. No further reloading is
4743 status_enter(view, line);
4747 /* Simply reload the view. */
4754 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
4760 status_select(struct view *view, struct line *line)
4762 struct status *status = line->data;
4763 char file[SIZEOF_STR] = "all files";
4767 if (status && !string_format(file, "'%s'", status->new.name))
4770 if (!status && line[1].type == LINE_STAT_NONE)
4773 switch (line->type) {
4774 case LINE_STAT_STAGED:
4775 text = "Press %s to unstage %s for commit";
4778 case LINE_STAT_UNSTAGED:
4779 text = "Press %s to stage %s for commit";
4782 case LINE_STAT_UNTRACKED:
4783 text = "Press %s to stage %s for addition";
4786 case LINE_STAT_HEAD:
4787 case LINE_STAT_NONE:
4788 text = "Nothing to update";
4792 die("line type %d not handled in switch", line->type);
4795 if (status && status->status == 'U') {
4796 text = "Press %s to resolve conflict in %s";
4797 key = get_key(REQ_STATUS_MERGE);
4800 key = get_key(REQ_STATUS_UPDATE);
4803 string_format(view->ref, text, key, file);
4807 status_grep(struct view *view, struct line *line)
4809 struct status *status = line->data;
4810 enum { S_STATUS, S_NAME, S_END } state;
4817 for (state = S_STATUS; state < S_END; state++) {
4821 case S_NAME: text = status->new.name; break;
4823 buf[0] = status->status;
4831 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4838 static struct view_ops status_ops = {
4851 stage_diff_write(struct io *io, struct line *line, struct line *end)
4853 while (line < end) {
4854 if (!io_write(io, line->data, strlen(line->data)) ||
4855 !io_write(io, "\n", 1))
4858 if (line->type == LINE_DIFF_CHUNK ||
4859 line->type == LINE_DIFF_HEADER)
4866 static struct line *
4867 stage_diff_find(struct view *view, struct line *line, enum line_type type)
4869 for (; view->line < line; line--)
4870 if (line->type == type)
4877 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
4879 const char *apply_argv[SIZEOF_ARG] = {
4880 "git", "apply", "--whitespace=nowarn", NULL
4882 struct line *diff_hdr;
4886 diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
4891 apply_argv[argc++] = "--cached";
4892 if (revert || stage_line_type == LINE_STAT_STAGED)
4893 apply_argv[argc++] = "-R";
4894 apply_argv[argc++] = "-";
4895 apply_argv[argc++] = NULL;
4896 if (!run_io(&io, apply_argv, opt_cdup, IO_WR))
4899 if (!stage_diff_write(&io, diff_hdr, chunk) ||
4900 !stage_diff_write(&io, chunk, view->line + view->lines))
4904 run_io_bg(update_index_argv);
4906 return chunk ? TRUE : FALSE;
4910 stage_update(struct view *view, struct line *line)
4912 struct line *chunk = NULL;
4914 if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
4915 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
4918 if (!stage_apply_chunk(view, chunk, FALSE)) {
4919 report("Failed to apply chunk");
4923 } else if (!stage_status.status) {
4924 view = VIEW(REQ_VIEW_STATUS);
4926 for (line = view->line; line < view->line + view->lines; line++)
4927 if (line->type == stage_line_type)
4930 if (!status_update_files(view, line + 1)) {
4931 report("Failed to update files");
4935 } else if (!status_update_file(&stage_status, stage_line_type)) {
4936 report("Failed to update file");
4944 stage_revert(struct view *view, struct line *line)
4946 struct line *chunk = NULL;
4948 if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
4949 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
4952 if (!prompt_yesno("Are you sure you want to revert changes?"))
4955 if (!stage_apply_chunk(view, chunk, TRUE)) {
4956 report("Failed to revert chunk");
4962 return status_revert(stage_status.status ? &stage_status : NULL,
4963 stage_line_type, FALSE);
4969 stage_next(struct view *view, struct line *line)
4973 if (!stage_chunks) {
4974 static size_t alloc = 0;
4977 for (line = view->line; line < view->line + view->lines; line++) {
4978 if (line->type != LINE_DIFF_CHUNK)
4981 tmp = realloc_items(stage_chunk, &alloc,
4982 stage_chunks, sizeof(*tmp));
4984 report("Allocation failure");
4989 stage_chunk[stage_chunks++] = line - view->line;
4993 for (i = 0; i < stage_chunks; i++) {
4994 if (stage_chunk[i] > view->lineno) {
4995 do_scroll_view(view, stage_chunk[i] - view->lineno);
4996 report("Chunk %d of %d", i + 1, stage_chunks);
5001 report("No next chunk found");
5005 stage_request(struct view *view, enum request request, struct line *line)
5008 case REQ_STATUS_UPDATE:
5009 if (!stage_update(view, line))
5013 case REQ_STATUS_REVERT:
5014 if (!stage_revert(view, line))
5018 case REQ_STAGE_NEXT:
5019 if (stage_line_type == LINE_STAT_UNTRACKED) {
5020 report("File is untracked; press %s to add",
5021 get_key(REQ_STATUS_UPDATE));
5024 stage_next(view, line);
5028 if (!stage_status.new.name[0])
5030 if (stage_status.status == 'D') {
5031 report("File has been deleted.");
5035 open_editor(stage_status.status != '?', stage_status.new.name);
5039 /* Reload everything ... */
5042 case REQ_VIEW_BLAME:
5043 if (stage_status.new.name[0]) {
5044 string_copy(opt_file, stage_status.new.name);
5050 return pager_request(view, request, line);
5056 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD | OPEN_NOMAXIMIZE);
5058 /* Check whether the staged entry still exists, and close the
5059 * stage view if it doesn't. */
5060 if (!status_exists(&stage_status, stage_line_type))
5061 return REQ_VIEW_CLOSE;
5063 if (stage_line_type == LINE_STAT_UNTRACKED) {
5064 if (!suffixcmp(stage_status.new.name, -1, "/")) {
5065 report("Cannot display a directory");
5069 if (!prepare_update_file(view, stage_status.new.name)) {
5070 report("Failed to open file: %s", strerror(errno));
5074 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
5079 static struct view_ops stage_ops = {
5096 char id[SIZEOF_REV]; /* SHA1 ID. */
5097 char title[128]; /* First line of the commit message. */
5098 char author[75]; /* Author of the commit. */
5099 struct tm time; /* Date from the author ident. */
5100 struct ref **refs; /* Repository references. */
5101 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
5102 size_t graph_size; /* The width of the graph array. */
5103 bool has_parents; /* Rewritten --parents seen. */
5106 /* Size of rev graph with no "padding" columns */
5107 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
5110 struct rev_graph *prev, *next, *parents;
5111 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
5113 struct commit *commit;
5115 unsigned int boundary:1;
5118 /* Parents of the commit being visualized. */
5119 static struct rev_graph graph_parents[4];
5121 /* The current stack of revisions on the graph. */
5122 static struct rev_graph graph_stacks[4] = {
5123 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
5124 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
5125 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
5126 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
5130 graph_parent_is_merge(struct rev_graph *graph)
5132 return graph->parents->size > 1;
5136 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
5138 struct commit *commit = graph->commit;
5140 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
5141 commit->graph[commit->graph_size++] = symbol;
5145 clear_rev_graph(struct rev_graph *graph)
5147 graph->boundary = 0;
5148 graph->size = graph->pos = 0;
5149 graph->commit = NULL;
5150 memset(graph->parents, 0, sizeof(*graph->parents));
5154 done_rev_graph(struct rev_graph *graph)
5156 if (graph_parent_is_merge(graph) &&
5157 graph->pos < graph->size - 1 &&
5158 graph->next->size == graph->size + graph->parents->size - 1) {
5159 size_t i = graph->pos + graph->parents->size - 1;
5161 graph->commit->graph_size = i * 2;
5162 while (i < graph->next->size - 1) {
5163 append_to_rev_graph(graph, ' ');
5164 append_to_rev_graph(graph, '\\');
5169 clear_rev_graph(graph);
5173 push_rev_graph(struct rev_graph *graph, const char *parent)
5177 /* "Collapse" duplicate parents lines.
5179 * FIXME: This needs to also update update the drawn graph but
5180 * for now it just serves as a method for pruning graph lines. */
5181 for (i = 0; i < graph->size; i++)
5182 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
5185 if (graph->size < SIZEOF_REVITEMS) {
5186 string_copy_rev(graph->rev[graph->size++], parent);
5191 get_rev_graph_symbol(struct rev_graph *graph)
5195 if (graph->boundary)
5196 symbol = REVGRAPH_BOUND;
5197 else if (graph->parents->size == 0)
5198 symbol = REVGRAPH_INIT;
5199 else if (graph_parent_is_merge(graph))
5200 symbol = REVGRAPH_MERGE;
5201 else if (graph->pos >= graph->size)
5202 symbol = REVGRAPH_BRANCH;
5204 symbol = REVGRAPH_COMMIT;
5210 draw_rev_graph(struct rev_graph *graph)
5213 chtype separator, line;
5215 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
5216 static struct rev_filler fillers[] = {
5222 chtype symbol = get_rev_graph_symbol(graph);
5223 struct rev_filler *filler;
5226 if (opt_line_graphics)
5227 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
5229 filler = &fillers[DEFAULT];
5231 for (i = 0; i < graph->pos; i++) {
5232 append_to_rev_graph(graph, filler->line);
5233 if (graph_parent_is_merge(graph->prev) &&
5234 graph->prev->pos == i)
5235 filler = &fillers[RSHARP];
5237 append_to_rev_graph(graph, filler->separator);
5240 /* Place the symbol for this revision. */
5241 append_to_rev_graph(graph, symbol);
5243 if (graph->prev->size > graph->size)
5244 filler = &fillers[RDIAG];
5246 filler = &fillers[DEFAULT];
5250 for (; i < graph->size; i++) {
5251 append_to_rev_graph(graph, filler->separator);
5252 append_to_rev_graph(graph, filler->line);
5253 if (graph_parent_is_merge(graph->prev) &&
5254 i < graph->prev->pos + graph->parents->size)
5255 filler = &fillers[RSHARP];
5256 if (graph->prev->size > graph->size)
5257 filler = &fillers[LDIAG];
5260 if (graph->prev->size > graph->size) {
5261 append_to_rev_graph(graph, filler->separator);
5262 if (filler->line != ' ')
5263 append_to_rev_graph(graph, filler->line);
5267 /* Prepare the next rev graph */
5269 prepare_rev_graph(struct rev_graph *graph)
5273 /* First, traverse all lines of revisions up to the active one. */
5274 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
5275 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
5278 push_rev_graph(graph->next, graph->rev[graph->pos]);
5281 /* Interleave the new revision parent(s). */
5282 for (i = 0; !graph->boundary && i < graph->parents->size; i++)
5283 push_rev_graph(graph->next, graph->parents->rev[i]);
5285 /* Lastly, put any remaining revisions. */
5286 for (i = graph->pos + 1; i < graph->size; i++)
5287 push_rev_graph(graph->next, graph->rev[i]);
5291 update_rev_graph(struct rev_graph *graph)
5293 /* If this is the finalizing update ... */
5295 prepare_rev_graph(graph);
5297 /* Graph visualization needs a one rev look-ahead,
5298 * so the first update doesn't visualize anything. */
5299 if (!graph->prev->commit)
5302 draw_rev_graph(graph->prev);
5303 done_rev_graph(graph->prev->prev);
5311 static const char *main_argv[SIZEOF_ARG] = {
5312 "git", "log", "--no-color", "--pretty=raw", "--parents",
5313 "--topo-order", "%(head)", NULL
5317 main_draw(struct view *view, struct line *line, unsigned int lineno)
5319 struct commit *commit = line->data;
5321 if (!*commit->author)
5324 if (opt_date && draw_date(view, &commit->time))
5328 draw_field(view, LINE_MAIN_AUTHOR, commit->author, opt_author_cols, TRUE))
5331 if (opt_rev_graph && commit->graph_size &&
5332 draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
5335 if (opt_show_refs && commit->refs) {
5339 enum line_type type;
5341 if (commit->refs[i]->head)
5342 type = LINE_MAIN_HEAD;
5343 else if (commit->refs[i]->ltag)
5344 type = LINE_MAIN_LOCAL_TAG;
5345 else if (commit->refs[i]->tag)
5346 type = LINE_MAIN_TAG;
5347 else if (commit->refs[i]->tracked)
5348 type = LINE_MAIN_TRACKED;
5349 else if (commit->refs[i]->remote)
5350 type = LINE_MAIN_REMOTE;
5352 type = LINE_MAIN_REF;
5354 if (draw_text(view, type, "[", TRUE) ||
5355 draw_text(view, type, commit->refs[i]->name, TRUE) ||
5356 draw_text(view, type, "]", TRUE))
5359 if (draw_text(view, LINE_DEFAULT, " ", TRUE))
5361 } while (commit->refs[i++]->next);
5364 draw_text(view, LINE_DEFAULT, commit->title, TRUE);
5368 /* Reads git log --pretty=raw output and parses it into the commit struct. */
5370 main_read(struct view *view, char *line)
5372 static struct rev_graph *graph = graph_stacks;
5373 enum line_type type;
5374 struct commit *commit;
5379 if (!view->lines && !view->parent)
5380 die("No revisions match the given arguments.");
5381 if (view->lines > 0) {
5382 commit = view->line[view->lines - 1].data;
5383 if (!*commit->author) {
5386 graph->commit = NULL;
5389 update_rev_graph(graph);
5391 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
5392 clear_rev_graph(&graph_stacks[i]);
5396 type = get_line_type(line);
5397 if (type == LINE_COMMIT) {
5398 commit = calloc(1, sizeof(struct commit));
5402 line += STRING_SIZE("commit ");
5404 graph->boundary = 1;
5408 string_copy_rev(commit->id, line);
5409 commit->refs = get_refs(commit->id);
5410 graph->commit = commit;
5411 add_line_data(view, commit, LINE_MAIN_COMMIT);
5413 while ((line = strchr(line, ' '))) {
5415 push_rev_graph(graph->parents, line);
5416 commit->has_parents = TRUE;
5423 commit = view->line[view->lines - 1].data;
5427 if (commit->has_parents)
5429 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
5434 /* Parse author lines where the name may be empty:
5435 * author <email@address.tld> 1138474660 +0100
5437 char *ident = line + STRING_SIZE("author ");
5438 char *nameend = strchr(ident, '<');
5439 char *emailend = strchr(ident, '>');
5441 if (!nameend || !emailend)
5444 update_rev_graph(graph);
5445 graph = graph->next;
5447 *nameend = *emailend = 0;
5448 ident = chomp_string(ident);
5450 ident = chomp_string(nameend + 1);
5455 string_ncopy(commit->author, ident, strlen(ident));
5457 /* Parse epoch and timezone */
5458 if (emailend[1] == ' ') {
5459 char *secs = emailend + 2;
5460 char *zone = strchr(secs, ' ');
5461 time_t time = (time_t) atol(secs);
5463 if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
5467 tz = ('0' - zone[1]) * 60 * 60 * 10;
5468 tz += ('0' - zone[2]) * 60 * 60;
5469 tz += ('0' - zone[3]) * 60;
5470 tz += ('0' - zone[4]) * 60;
5478 gmtime_r(&time, &commit->time);
5483 /* Fill in the commit title if it has not already been set. */
5484 if (commit->title[0])
5487 /* Require titles to start with a non-space character at the
5488 * offset used by git log. */
5489 if (strncmp(line, " ", 4))
5492 /* Well, if the title starts with a whitespace character,
5493 * try to be forgiving. Otherwise we end up with no title. */
5494 while (isspace(*line))
5498 /* FIXME: More graceful handling of titles; append "..." to
5499 * shortened titles, etc. */
5501 string_ncopy(commit->title, line, strlen(line));
5508 main_request(struct view *view, enum request request, struct line *line)
5510 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
5514 open_view(view, REQ_VIEW_DIFF, flags);
5518 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
5528 grep_refs(struct ref **refs, regex_t *regex)
5536 if (regexec(regex, refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
5538 } while (refs[i++]->next);
5544 main_grep(struct view *view, struct line *line)
5546 struct commit *commit = line->data;
5547 enum { S_TITLE, S_AUTHOR, S_DATE, S_REFS, S_END } state;
5548 char buf[DATE_COLS + 1];
5551 for (state = S_TITLE; state < S_END; state++) {
5555 case S_TITLE: text = commit->title; break;
5559 text = commit->author;
5564 if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
5571 if (grep_refs(commit->refs, view->regex) == TRUE)
5578 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
5586 main_select(struct view *view, struct line *line)
5588 struct commit *commit = line->data;
5590 string_copy_rev(view->ref, commit->id);
5591 string_copy_rev(ref_commit, view->ref);
5594 static struct view_ops main_ops = {
5607 * Unicode / UTF-8 handling
5609 * NOTE: Much of the following code for dealing with unicode is derived from
5610 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
5611 * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
5614 /* I've (over)annotated a lot of code snippets because I am not entirely
5615 * confident that the approach taken by this small UTF-8 interface is correct.
5619 unicode_width(unsigned long c)
5622 (c <= 0x115f /* Hangul Jamo */
5625 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
5627 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
5628 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
5629 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
5630 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
5631 || (c >= 0xffe0 && c <= 0xffe6)
5632 || (c >= 0x20000 && c <= 0x2fffd)
5633 || (c >= 0x30000 && c <= 0x3fffd)))
5637 return opt_tab_size;
5642 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
5643 * Illegal bytes are set one. */
5644 static const unsigned char utf8_bytes[256] = {
5645 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,
5646 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,
5647 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,
5648 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,
5649 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,
5650 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,
5651 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,
5652 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,
5655 /* Decode UTF-8 multi-byte representation into a unicode character. */
5656 static inline unsigned long
5657 utf8_to_unicode(const char *string, size_t length)
5659 unsigned long unicode;
5663 unicode = string[0];
5666 unicode = (string[0] & 0x1f) << 6;
5667 unicode += (string[1] & 0x3f);
5670 unicode = (string[0] & 0x0f) << 12;
5671 unicode += ((string[1] & 0x3f) << 6);
5672 unicode += (string[2] & 0x3f);
5675 unicode = (string[0] & 0x0f) << 18;
5676 unicode += ((string[1] & 0x3f) << 12);
5677 unicode += ((string[2] & 0x3f) << 6);
5678 unicode += (string[3] & 0x3f);
5681 unicode = (string[0] & 0x0f) << 24;
5682 unicode += ((string[1] & 0x3f) << 18);
5683 unicode += ((string[2] & 0x3f) << 12);
5684 unicode += ((string[3] & 0x3f) << 6);
5685 unicode += (string[4] & 0x3f);
5688 unicode = (string[0] & 0x01) << 30;
5689 unicode += ((string[1] & 0x3f) << 24);
5690 unicode += ((string[2] & 0x3f) << 18);
5691 unicode += ((string[3] & 0x3f) << 12);
5692 unicode += ((string[4] & 0x3f) << 6);
5693 unicode += (string[5] & 0x3f);
5696 die("Invalid unicode length");
5699 /* Invalid characters could return the special 0xfffd value but NUL
5700 * should be just as good. */
5701 return unicode > 0xffff ? 0 : unicode;
5704 /* Calculates how much of string can be shown within the given maximum width
5705 * and sets trimmed parameter to non-zero value if all of string could not be
5706 * shown. If the reserve flag is TRUE, it will reserve at least one
5707 * trailing character, which can be useful when drawing a delimiter.
5709 * Returns the number of bytes to output from string to satisfy max_width. */
5711 utf8_length(const char *string, int *width, size_t max_width, int *trimmed, bool reserve)
5713 const char *start = string;
5714 const char *end = strchr(string, '\0');
5715 unsigned char last_bytes = 0;
5716 size_t last_ucwidth = 0;
5721 while (string < end) {
5722 int c = *(unsigned char *) string;
5723 unsigned char bytes = utf8_bytes[c];
5725 unsigned long unicode;
5727 if (string + bytes > end)
5730 /* Change representation to figure out whether
5731 * it is a single- or double-width character. */
5733 unicode = utf8_to_unicode(string, bytes);
5734 /* FIXME: Graceful handling of invalid unicode character. */
5738 ucwidth = unicode_width(unicode);
5740 if (*width > max_width) {
5743 if (reserve && *width == max_width) {
5744 string -= last_bytes;
5745 *width -= last_ucwidth;
5752 last_ucwidth = ucwidth;
5755 return string - start;
5763 /* Whether or not the curses interface has been initialized. */
5764 static bool cursed = FALSE;
5766 /* The status window is used for polling keystrokes. */
5767 static WINDOW *status_win;
5769 static bool status_empty = TRUE;
5771 /* Update status and title window. */
5773 report(const char *msg, ...)
5775 struct view *view = display[current_view];
5781 char buf[SIZEOF_STR];
5784 va_start(args, msg);
5785 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
5786 buf[sizeof(buf) - 1] = 0;
5787 buf[sizeof(buf) - 2] = '.';
5788 buf[sizeof(buf) - 3] = '.';
5789 buf[sizeof(buf) - 4] = '.';
5795 if (!status_empty || *msg) {
5798 va_start(args, msg);
5800 wmove(status_win, 0, 0);
5802 vwprintw(status_win, msg, args);
5803 status_empty = FALSE;
5805 status_empty = TRUE;
5807 wclrtoeol(status_win);
5808 wrefresh(status_win);
5813 update_view_title(view);
5814 update_display_cursor(view);
5817 /* Controls when nodelay should be in effect when polling user input. */
5819 set_nonblocking_input(bool loading)
5821 static unsigned int loading_views;
5823 if ((loading == FALSE && loading_views-- == 1) ||
5824 (loading == TRUE && loading_views++ == 0))
5825 nodelay(status_win, loading);
5833 /* Initialize the curses library */
5834 if (isatty(STDIN_FILENO)) {
5835 cursed = !!initscr();
5838 /* Leave stdin and stdout alone when acting as a pager. */
5839 opt_tty = fopen("/dev/tty", "r+");
5841 die("Failed to open /dev/tty");
5842 cursed = !!newterm(NULL, opt_tty, opt_tty);
5846 die("Failed to initialize curses");
5848 nonl(); /* Tell curses not to do NL->CR/NL on output */
5849 cbreak(); /* Take input chars one at a time, no wait for \n */
5850 noecho(); /* Don't echo input */
5851 leaveok(stdscr, TRUE);
5856 getmaxyx(stdscr, y, x);
5857 status_win = newwin(1, 0, y - 1, 0);
5859 die("Failed to create status window");
5861 /* Enable keyboard mapping */
5862 keypad(status_win, TRUE);
5863 wbkgdset(status_win, get_line_attr(LINE_STATUS));
5865 TABSIZE = opt_tab_size;
5866 if (opt_line_graphics) {
5867 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
5872 prompt_yesno(const char *prompt)
5874 enum { WAIT, STOP, CANCEL } status = WAIT;
5875 bool answer = FALSE;
5877 while (status == WAIT) {
5883 foreach_view (view, i)
5888 mvwprintw(status_win, 0, 0, "%s [Yy]/[Nn]", prompt);
5889 wclrtoeol(status_win);
5891 /* Refresh, accept single keystroke of input */
5892 key = wgetch(status_win);
5916 /* Clear the status window */
5917 status_empty = FALSE;
5924 read_prompt(const char *prompt)
5926 enum { READING, STOP, CANCEL } status = READING;
5927 static char buf[SIZEOF_STR];
5930 while (status == READING) {
5936 foreach_view (view, i)
5941 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
5942 wclrtoeol(status_win);
5944 /* Refresh, accept single keystroke of input */
5945 key = wgetch(status_win);
5950 status = pos ? STOP : CANCEL;
5968 if (pos >= sizeof(buf)) {
5969 report("Input string too long");
5974 buf[pos++] = (char) key;
5978 /* Clear the status window */
5979 status_empty = FALSE;
5982 if (status == CANCEL)
5991 * Repository properties
5995 git_properties(const char **argv, const char *separators,
5996 int (*read_property)(char *, size_t, char *, size_t))
6000 if (init_io_rd(&io, argv, NULL, FORMAT_NONE))
6001 return read_properties(&io, separators, read_property);
6005 static struct ref *refs = NULL;
6006 static size_t refs_alloc = 0;
6007 static size_t refs_size = 0;
6009 /* Id <-> ref store */
6010 static struct ref ***id_refs = NULL;
6011 static size_t id_refs_alloc = 0;
6012 static size_t id_refs_size = 0;
6015 compare_refs(const void *ref1_, const void *ref2_)
6017 const struct ref *ref1 = *(const struct ref **)ref1_;
6018 const struct ref *ref2 = *(const struct ref **)ref2_;
6020 if (ref1->tag != ref2->tag)
6021 return ref2->tag - ref1->tag;
6022 if (ref1->ltag != ref2->ltag)
6023 return ref2->ltag - ref2->ltag;
6024 if (ref1->head != ref2->head)
6025 return ref2->head - ref1->head;
6026 if (ref1->tracked != ref2->tracked)
6027 return ref2->tracked - ref1->tracked;
6028 if (ref1->remote != ref2->remote)
6029 return ref2->remote - ref1->remote;
6030 return strcmp(ref1->name, ref2->name);
6033 static struct ref **
6034 get_refs(const char *id)
6036 struct ref ***tmp_id_refs;
6037 struct ref **ref_list = NULL;
6038 size_t ref_list_alloc = 0;
6039 size_t ref_list_size = 0;
6042 for (i = 0; i < id_refs_size; i++)
6043 if (!strcmp(id, id_refs[i][0]->id))
6046 tmp_id_refs = realloc_items(id_refs, &id_refs_alloc, id_refs_size + 1,
6051 id_refs = tmp_id_refs;
6053 for (i = 0; i < refs_size; i++) {
6056 if (strcmp(id, refs[i].id))
6059 tmp = realloc_items(ref_list, &ref_list_alloc,
6060 ref_list_size + 1, sizeof(*ref_list));
6068 ref_list[ref_list_size] = &refs[i];
6069 /* XXX: The properties of the commit chains ensures that we can
6070 * safely modify the shared ref. The repo references will
6071 * always be similar for the same id. */
6072 ref_list[ref_list_size]->next = 1;
6078 qsort(ref_list, ref_list_size, sizeof(*ref_list), compare_refs);
6079 ref_list[ref_list_size - 1]->next = 0;
6080 id_refs[id_refs_size++] = ref_list;
6087 read_ref(char *id, size_t idlen, char *name, size_t namelen)
6092 bool remote = FALSE;
6093 bool tracked = FALSE;
6094 bool check_replace = FALSE;
6097 if (!prefixcmp(name, "refs/tags/")) {
6098 if (!suffixcmp(name, namelen, "^{}")) {
6101 if (refs_size > 0 && refs[refs_size - 1].ltag == TRUE)
6102 check_replace = TRUE;
6108 namelen -= STRING_SIZE("refs/tags/");
6109 name += STRING_SIZE("refs/tags/");
6111 } else if (!prefixcmp(name, "refs/remotes/")) {
6113 namelen -= STRING_SIZE("refs/remotes/");
6114 name += STRING_SIZE("refs/remotes/");
6115 tracked = !strcmp(opt_remote, name);
6117 } else if (!prefixcmp(name, "refs/heads/")) {
6118 namelen -= STRING_SIZE("refs/heads/");
6119 name += STRING_SIZE("refs/heads/");
6120 head = !strncmp(opt_head, name, namelen);
6122 } else if (!strcmp(name, "HEAD")) {
6123 string_ncopy(opt_head_rev, id, idlen);
6127 if (check_replace && !strcmp(name, refs[refs_size - 1].name)) {
6128 /* it's an annotated tag, replace the previous sha1 with the
6129 * resolved commit id; relies on the fact git-ls-remote lists
6130 * the commit id of an annotated tag right before the commit id
6132 refs[refs_size - 1].ltag = ltag;
6133 string_copy_rev(refs[refs_size - 1].id, id);
6137 refs = realloc_items(refs, &refs_alloc, refs_size + 1, sizeof(*refs));
6141 ref = &refs[refs_size++];
6142 ref->name = malloc(namelen + 1);
6146 strncpy(ref->name, name, namelen);
6147 ref->name[namelen] = 0;
6151 ref->remote = remote;
6152 ref->tracked = tracked;
6153 string_copy_rev(ref->id, id);
6161 static const char *ls_remote_argv[SIZEOF_ARG] = {
6162 "git", "ls-remote", ".", NULL
6164 static bool init = FALSE;
6167 argv_from_env(ls_remote_argv, "TIG_LS_REMOTE");
6174 while (refs_size > 0)
6175 free(refs[--refs_size].name);
6176 while (id_refs_size > 0)
6177 free(id_refs[--id_refs_size]);
6179 return git_properties(ls_remote_argv, "\t", read_ref);
6183 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
6185 if (!strcmp(name, "i18n.commitencoding"))
6186 string_ncopy(opt_encoding, value, valuelen);
6188 if (!strcmp(name, "core.editor"))
6189 string_ncopy(opt_editor, value, valuelen);
6191 /* branch.<head>.remote */
6193 !strncmp(name, "branch.", 7) &&
6194 !strncmp(name + 7, opt_head, strlen(opt_head)) &&
6195 !strcmp(name + 7 + strlen(opt_head), ".remote"))
6196 string_ncopy(opt_remote, value, valuelen);
6198 if (*opt_head && *opt_remote &&
6199 !strncmp(name, "branch.", 7) &&
6200 !strncmp(name + 7, opt_head, strlen(opt_head)) &&
6201 !strcmp(name + 7 + strlen(opt_head), ".merge")) {
6202 size_t from = strlen(opt_remote);
6204 if (!prefixcmp(value, "refs/heads/")) {
6205 value += STRING_SIZE("refs/heads/");
6206 valuelen -= STRING_SIZE("refs/heads/");
6209 if (!string_format_from(opt_remote, &from, "/%s", value))
6217 load_git_config(void)
6219 const char *config_list_argv[] = { "git", GIT_CONFIG, "--list", NULL };
6221 return git_properties(config_list_argv, "=", read_repo_config_option);
6225 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
6227 if (!opt_git_dir[0]) {
6228 string_ncopy(opt_git_dir, name, namelen);
6230 } else if (opt_is_inside_work_tree == -1) {
6231 /* This can be 3 different values depending on the
6232 * version of git being used. If git-rev-parse does not
6233 * understand --is-inside-work-tree it will simply echo
6234 * the option else either "true" or "false" is printed.
6235 * Default to true for the unknown case. */
6236 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
6238 string_ncopy(opt_cdup, name, namelen);
6245 load_repo_info(void)
6247 const char *head_argv[] = {
6248 "git", "symbolic-ref", "HEAD", NULL
6250 const char *rev_parse_argv[] = {
6251 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
6255 if (run_io_buf(head_argv, opt_head, sizeof(opt_head))) {
6256 chomp_string(opt_head);
6257 if (!prefixcmp(opt_head, "refs/heads/")) {
6258 char *offset = opt_head + STRING_SIZE("refs/heads/");
6260 memmove(opt_head, offset, strlen(offset) + 1);
6264 return git_properties(rev_parse_argv, "=", read_repo_info);
6268 read_properties(struct io *io, const char *separators,
6269 int (*read_property)(char *, size_t, char *, size_t))
6277 while (state == OK && (name = io_gets(io))) {
6282 name = chomp_string(name);
6283 namelen = strcspn(name, separators);
6285 if (name[namelen]) {
6287 value = chomp_string(name + namelen + 1);
6288 valuelen = strlen(value);
6295 state = read_property(name, namelen, value, valuelen);
6298 if (state != ERR && io_error(io))
6310 static void __NORETURN
6313 /* XXX: Restore tty modes and let the OS cleanup the rest! */
6319 static void __NORETURN
6320 die(const char *err, ...)
6326 va_start(args, err);
6327 fputs("tig: ", stderr);
6328 vfprintf(stderr, err, args);
6329 fputs("\n", stderr);
6336 warn(const char *msg, ...)
6340 va_start(args, msg);
6341 fputs("tig warning: ", stderr);
6342 vfprintf(stderr, msg, args);
6343 fputs("\n", stderr);
6348 main(int argc, const char *argv[])
6350 const char **run_argv = NULL;
6352 enum request request;
6355 signal(SIGINT, quit);
6357 if (setlocale(LC_ALL, "")) {
6358 char *codeset = nl_langinfo(CODESET);
6360 string_ncopy(opt_codeset, codeset, strlen(codeset));
6363 if (load_repo_info() == ERR)
6364 die("Failed to load repo info.");
6366 if (load_options() == ERR)
6367 die("Failed to load user config.");
6369 if (load_git_config() == ERR)
6370 die("Failed to load repo config.");
6372 request = parse_options(argc, argv, &run_argv);
6373 if (request == REQ_NONE)
6376 /* Require a git repository unless when running in pager mode. */
6377 if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
6378 die("Not a git repository");
6380 if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
6383 if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
6384 opt_iconv = iconv_open(opt_codeset, opt_encoding);
6385 if (opt_iconv == ICONV_NONE)
6386 die("Failed to initialize character set conversion");
6389 if (load_refs() == ERR)
6390 die("Failed to load refs.");
6392 foreach_view (view, i)
6393 argv_from_env(view->ops->argv, view->cmd_env);
6397 if (request == REQ_VIEW_PAGER || run_argv) {
6398 if (request == REQ_VIEW_PAGER)
6399 init_io_fd(&VIEW(request)->io, stdin);
6400 else if (!prepare_update(VIEW(request), run_argv, NULL, FORMAT_NONE))
6401 die("Failed to format arguments");
6402 open_view(NULL, request, OPEN_PREPARED);
6406 while (view_driver(display[current_view], request)) {
6410 foreach_view (view, i)
6412 view = display[current_view];
6414 /* Refresh, accept single keystroke of input */
6415 key = wgetch(status_win);
6417 /* wgetch() with nodelay() enabled returns ERR when there's no
6424 request = get_keybinding(view->keymap, key);
6426 /* Some low-level request handling. This keeps access to
6427 * status_win restricted. */
6431 char *cmd = read_prompt(":");
6434 struct view *next = VIEW(REQ_VIEW_PAGER);
6435 const char *argv[SIZEOF_ARG] = { "git" };
6438 /* When running random commands, initially show the
6439 * command in the title. However, it maybe later be
6440 * overwritten if a commit line is selected. */
6441 string_ncopy(next->ref, cmd, strlen(cmd));
6443 if (!argv_from_string(argv, &argc, cmd)) {
6444 report("Too many arguments");
6445 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
6446 report("Failed to format command");
6448 open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
6456 case REQ_SEARCH_BACK:
6458 const char *prompt = request == REQ_SEARCH ? "/" : "?";
6459 char *search = read_prompt(prompt);
6462 string_ncopy(opt_search, search, strlen(search));
6467 case REQ_SCREEN_RESIZE:
6471 getmaxyx(stdscr, height, width);
6473 /* Resize the status view and let the view driver take
6474 * care of resizing the displayed views. */
6475 wresize(status_win, 1, width);
6476 mvwin(status_win, height - 1, 0);
6477 wrefresh(status_win);