1 /* Copyright (c) 2006-2008 Jonas Fonseca <fonseca@diku.dk>
3 * This program is free software; you can redistribute it and/or
4 * modify it under the terms of the GNU General Public License as
5 * published by the Free Software Foundation; either version 2 of
6 * the License, or (at your option) any later version.
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
19 #define TIG_VERSION "unknown-version"
34 #include <sys/types.h>
45 /* ncurses(3): Must be defined to have extended wide-character functions. */
46 #define _XOPEN_SOURCE_EXTENDED
48 #ifdef HAVE_NCURSESW_NCURSES_H
49 #include <ncursesw/ncurses.h>
51 #ifdef HAVE_NCURSES_NCURSES_H
52 #include <ncurses/ncurses.h>
59 #define __NORETURN __attribute__((__noreturn__))
64 static void __NORETURN die(const char *err, ...);
65 static void warn(const char *msg, ...);
66 static void report(const char *msg, ...);
67 static int read_properties(FILE *pipe, const char *separators, int (*read)(char *, size_t, char *, size_t));
68 static void set_nonblocking_input(bool loading);
69 static size_t utf8_length(const char *string, int *width, size_t max_width, int *trimmed, bool reserve);
70 static bool prompt_yesno(const char *prompt);
71 static int load_refs(void);
73 #define ABS(x) ((x) >= 0 ? (x) : -(x))
74 #define MIN(x, y) ((x) < (y) ? (x) : (y))
76 #define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
77 #define STRING_SIZE(x) (sizeof(x) - 1)
79 #define SIZEOF_STR 1024 /* Default string size. */
80 #define SIZEOF_REF 256 /* Size of symbolic or SHA1 ID. */
81 #define SIZEOF_REV 41 /* Holds a SHA-1 and an ending NUL. */
82 #define SIZEOF_ARG 32 /* Default argument array size. */
86 #define REVGRAPH_INIT 'I'
87 #define REVGRAPH_MERGE 'M'
88 #define REVGRAPH_BRANCH '+'
89 #define REVGRAPH_COMMIT '*'
90 #define REVGRAPH_BOUND '^'
92 #define SIZEOF_REVGRAPH 19 /* Size of revision ancestry graphics. */
94 /* This color name can be used to refer to the default term colors. */
95 #define COLOR_DEFAULT (-1)
97 #define ICONV_NONE ((iconv_t) -1)
99 #define ICONV_CONST /* nothing */
102 /* The format and size of the date column in the main view. */
103 #define DATE_FORMAT "%Y-%m-%d %H:%M"
104 #define DATE_COLS STRING_SIZE("2006-04-29 14:21 ")
106 #define AUTHOR_COLS 20
109 /* The default interval between line numbers. */
110 #define NUMBER_INTERVAL 5
114 #define SCALE_SPLIT_VIEW(height) ((height) * 2 / 3)
116 #define NULL_ID "0000000000000000000000000000000000000000"
119 #define GIT_CONFIG "config"
122 #define TIG_LS_REMOTE \
123 "git ls-remote . 2>/dev/null"
125 /* Some ascii-shorthands fitted into the ncurses namespace. */
127 #define KEY_RETURN '\r'
132 char *name; /* Ref name; tag or head names are shortened. */
133 char id[SIZEOF_REV]; /* Commit SHA1 ID */
134 unsigned int head:1; /* Is it the current HEAD? */
135 unsigned int tag:1; /* Is it a tag? */
136 unsigned int ltag:1; /* If so, is the tag local? */
137 unsigned int remote:1; /* Is it a remote ref? */
138 unsigned int tracked:1; /* Is it the remote for the current HEAD? */
139 unsigned int next:1; /* For ref lists: are there more refs? */
142 static struct ref **get_refs(const char *id);
145 FORMAT_ALL, /* Perform replacement in all arguments. */
146 FORMAT_DASH, /* Perform replacement up until "--". */
147 FORMAT_NONE /* No replacement should be performed. */
150 static bool format_command(char dst[], const char *src[], enum format_flags flags);
151 static bool format_argv(const char *dst[], const char *src[], enum format_flags flags);
160 set_from_int_map(struct int_map *map, size_t map_size,
161 int *value, const char *name, int namelen)
166 for (i = 0; i < map_size; i++)
167 if (namelen == map[i].namelen &&
168 !strncasecmp(name, map[i].name, namelen)) {
169 *value = map[i].value;
182 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
184 if (srclen > dstlen - 1)
187 strncpy(dst, src, srclen);
191 /* Shorthands for safely copying into a fixed buffer. */
193 #define string_copy(dst, src) \
194 string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
196 #define string_ncopy(dst, src, srclen) \
197 string_ncopy_do(dst, sizeof(dst), src, srclen)
199 #define string_copy_rev(dst, src) \
200 string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
202 #define string_add(dst, from, src) \
203 string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
206 chomp_string(char *name)
210 while (isspace(*name))
213 namelen = strlen(name) - 1;
214 while (namelen > 0 && isspace(name[namelen]))
221 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
224 size_t pos = bufpos ? *bufpos : 0;
227 pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
233 return pos >= bufsize ? FALSE : TRUE;
236 #define string_format(buf, fmt, args...) \
237 string_nformat(buf, sizeof(buf), NULL, fmt, args)
239 #define string_format_from(buf, from, fmt, args...) \
240 string_nformat(buf, sizeof(buf), from, fmt, args)
243 string_enum_compare(const char *str1, const char *str2, int len)
247 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
249 /* Diff-Header == DIFF_HEADER */
250 for (i = 0; i < len; i++) {
251 if (toupper(str1[i]) == toupper(str2[i]))
254 if (string_enum_sep(str1[i]) &&
255 string_enum_sep(str2[i]))
258 return str1[i] - str2[i];
264 #define prefixcmp(str1, str2) \
265 strncmp(str1, str2, STRING_SIZE(str2))
268 suffixcmp(const char *str, int slen, const char *suffix)
270 size_t len = slen >= 0 ? slen : strlen(str);
271 size_t suffixlen = strlen(suffix);
273 return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
278 * NOTE: The following is a slightly modified copy of the git project's shell
279 * quoting routines found in the quote.c file.
281 * Help to copy the thing properly quoted for the shell safety. any single
282 * quote is replaced with '\'', any exclamation point is replaced with '\!',
283 * and the whole thing is enclosed in a
286 * original sq_quote result
287 * name ==> name ==> 'name'
288 * a b ==> a b ==> 'a b'
289 * a'b ==> a'\''b ==> 'a'\''b'
290 * a!b ==> a'\!'b ==> 'a'\!'b'
294 sq_quote(char buf[SIZEOF_STR], size_t bufsize, const char *src)
298 #define BUFPUT(x) do { if (bufsize < SIZEOF_STR) buf[bufsize++] = (x); } while (0)
301 while ((c = *src++)) {
302 if (c == '\'' || c == '!') {
313 if (bufsize < SIZEOF_STR)
320 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
324 while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
325 bool advance = cmd[valuelen] != 0;
328 argv[(*argc)++] = chomp_string(cmd);
329 cmd += valuelen + advance;
332 if (*argc < SIZEOF_ARG)
334 return *argc < SIZEOF_ARG;
338 argv_from_env(const char **argv, const char *name)
340 char *env = argv ? getenv(name) : NULL;
345 if (env && !argv_from_string(argv, &argc, env))
346 die("Too many arguments in the `%s` environment variable", name);
351 * Executing external commands.
355 IO_FD, /* File descriptor based IO. */
356 IO_BG, /* Execute command in the background. */
357 IO_FG, /* Execute command with same std{in,out,err}. */
358 IO_RD, /* Read only fork+exec IO. */
359 IO_WR, /* Write only fork+exec IO. */
363 enum io_type type; /* The requested type of pipe. */
364 const char *dir; /* Directory from which to execute. */
365 FILE *pipe; /* Pipe for reading or writing. */
366 int error; /* Error status. */
367 char sh[SIZEOF_STR]; /* Shell command buffer. */
368 char *buf; /* Read/write buffer. */
369 size_t bufalloc; /* Allocated buffer size. */
373 reset_io(struct io *io)
382 init_io(struct io *io, const char *dir, enum io_type type)
390 init_io_rd(struct io *io, const char *argv[], const char *dir,
391 enum format_flags flags)
393 init_io(io, dir, IO_RD);
394 return format_command(io->sh, argv, flags);
398 init_io_fd(struct io *io, FILE *pipe)
400 init_io(io, NULL, IO_FD);
402 return io->pipe != NULL;
406 done_io(struct io *io)
409 if (io->type == IO_FD)
411 else if (io->type == IO_RD || io->type == IO_WR)
418 start_io(struct io *io)
420 char buf[SIZEOF_STR * 2];
423 if (io->type == IO_FD)
426 if (io->dir && *io->dir &&
427 !string_format_from(buf, &bufpos, "cd %s;", io->dir))
430 if (!string_format_from(buf, &bufpos, "%s", io->sh))
433 if (io->type == IO_FG || io->type == IO_BG)
434 return system(buf) == 0;
436 io->pipe = popen(io->sh, io->type == IO_RD ? "r" : "w");
437 return io->pipe != NULL;
441 run_io(struct io *io, const char **argv, const char *dir, enum io_type type)
443 init_io(io, dir, type);
444 if (!format_command(io->sh, argv, FORMAT_NONE))
450 run_io_do(struct io *io)
452 return start_io(io) && done_io(io);
456 run_io_bg(const char **argv)
460 init_io(&io, NULL, IO_BG);
461 if (!format_command(io.sh, argv, FORMAT_NONE))
463 return run_io_do(&io);
467 run_io_fg(const char **argv, const char *dir)
471 init_io(&io, dir, IO_FG);
472 if (!format_command(io.sh, argv, FORMAT_NONE))
474 return run_io_do(&io);
478 run_io_rd(struct io *io, const char **argv, enum format_flags flags)
480 return init_io_rd(io, argv, NULL, flags) && start_io(io);
484 io_eof(struct io *io)
486 return feof(io->pipe);
490 io_error(struct io *io)
496 io_strerror(struct io *io)
498 return strerror(io->error);
502 io_gets(struct io *io)
505 io->buf = malloc(BUFSIZ);
508 io->bufalloc = BUFSIZ;
511 if (!fgets(io->buf, io->bufalloc, io->pipe)) {
512 if (ferror(io->pipe))
521 io_write(struct io *io, const void *buf, size_t bufsize)
525 while (!io_error(io) && written < bufsize) {
526 written += fwrite(buf + written, 1, bufsize - written, io->pipe);
527 if (ferror(io->pipe))
531 return written == bufsize;
535 run_io_buf(const char **argv, char buf[], size_t bufsize)
540 if (!run_io_rd(&io, argv, FORMAT_NONE))
544 io.bufalloc = bufsize;
545 error = !io_gets(&io) && io_error(&io);
548 return done_io(&io) || error;
557 /* XXX: Keep the view request first and in sync with views[]. */ \
558 REQ_GROUP("View switching") \
559 REQ_(VIEW_MAIN, "Show main view"), \
560 REQ_(VIEW_DIFF, "Show diff view"), \
561 REQ_(VIEW_LOG, "Show log view"), \
562 REQ_(VIEW_TREE, "Show tree view"), \
563 REQ_(VIEW_BLOB, "Show blob view"), \
564 REQ_(VIEW_BLAME, "Show blame view"), \
565 REQ_(VIEW_HELP, "Show help page"), \
566 REQ_(VIEW_PAGER, "Show pager view"), \
567 REQ_(VIEW_STATUS, "Show status view"), \
568 REQ_(VIEW_STAGE, "Show stage view"), \
570 REQ_GROUP("View manipulation") \
571 REQ_(ENTER, "Enter current line and scroll"), \
572 REQ_(NEXT, "Move to next"), \
573 REQ_(PREVIOUS, "Move to previous"), \
574 REQ_(VIEW_NEXT, "Move focus to next view"), \
575 REQ_(REFRESH, "Reload and refresh"), \
576 REQ_(MAXIMIZE, "Maximize the current view"), \
577 REQ_(VIEW_CLOSE, "Close the current view"), \
578 REQ_(QUIT, "Close all views and quit"), \
580 REQ_GROUP("View specific requests") \
581 REQ_(STATUS_UPDATE, "Update file status"), \
582 REQ_(STATUS_REVERT, "Revert file changes"), \
583 REQ_(STATUS_MERGE, "Merge file using external tool"), \
584 REQ_(STAGE_NEXT, "Find next chunk to stage"), \
585 REQ_(TREE_PARENT, "Switch to parent directory in tree view"), \
587 REQ_GROUP("Cursor navigation") \
588 REQ_(MOVE_UP, "Move cursor one line up"), \
589 REQ_(MOVE_DOWN, "Move cursor one line down"), \
590 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
591 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
592 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
593 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
595 REQ_GROUP("Scrolling") \
596 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
597 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
598 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
599 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
601 REQ_GROUP("Searching") \
602 REQ_(SEARCH, "Search the view"), \
603 REQ_(SEARCH_BACK, "Search backwards in the view"), \
604 REQ_(FIND_NEXT, "Find next search match"), \
605 REQ_(FIND_PREV, "Find previous search match"), \
607 REQ_GROUP("Option manipulation") \
608 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
609 REQ_(TOGGLE_DATE, "Toggle date display"), \
610 REQ_(TOGGLE_AUTHOR, "Toggle author display"), \
611 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
612 REQ_(TOGGLE_REFS, "Toggle reference display (tags/branches)"), \
615 REQ_(PROMPT, "Bring up the prompt"), \
616 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
617 REQ_(SCREEN_RESIZE, "Resize the screen"), \
618 REQ_(SHOW_VERSION, "Show version information"), \
619 REQ_(STOP_LOADING, "Stop all loading views"), \
620 REQ_(EDIT, "Open in editor"), \
621 REQ_(NONE, "Do nothing")
624 /* User action requests. */
626 #define REQ_GROUP(help)
627 #define REQ_(req, help) REQ_##req
629 /* Offset all requests to avoid conflicts with ncurses getch values. */
630 REQ_OFFSET = KEY_MAX + 1,
637 struct request_info {
638 enum request request;
644 static struct request_info req_info[] = {
645 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
646 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
653 get_request(const char *name)
655 int namelen = strlen(name);
658 for (i = 0; i < ARRAY_SIZE(req_info); i++)
659 if (req_info[i].namelen == namelen &&
660 !string_enum_compare(req_info[i].name, name, namelen))
661 return req_info[i].request;
671 static const char usage[] =
672 "tig " TIG_VERSION " (" __DATE__ ")\n"
674 "Usage: tig [options] [revs] [--] [paths]\n"
675 " or: tig show [options] [revs] [--] [paths]\n"
676 " or: tig blame [rev] path\n"
678 " or: tig < [git command output]\n"
681 " -v, --version Show version and exit\n"
682 " -h, --help Show help message and exit";
684 /* Option and state variables. */
685 static bool opt_date = TRUE;
686 static bool opt_author = TRUE;
687 static bool opt_line_number = FALSE;
688 static bool opt_line_graphics = TRUE;
689 static bool opt_rev_graph = FALSE;
690 static bool opt_show_refs = TRUE;
691 static int opt_num_interval = NUMBER_INTERVAL;
692 static int opt_tab_size = TAB_SIZE;
693 static int opt_author_cols = AUTHOR_COLS-1;
694 static char opt_path[SIZEOF_STR] = "";
695 static char opt_file[SIZEOF_STR] = "";
696 static char opt_ref[SIZEOF_REF] = "";
697 static char opt_head[SIZEOF_REF] = "";
698 static char opt_head_rev[SIZEOF_REV] = "";
699 static char opt_remote[SIZEOF_REF] = "";
700 static char opt_encoding[20] = "UTF-8";
701 static bool opt_utf8 = TRUE;
702 static char opt_codeset[20] = "UTF-8";
703 static iconv_t opt_iconv = ICONV_NONE;
704 static char opt_search[SIZEOF_STR] = "";
705 static char opt_cdup[SIZEOF_STR] = "";
706 static char opt_git_dir[SIZEOF_STR] = "";
707 static signed char opt_is_inside_work_tree = -1; /* set to TRUE or FALSE */
708 static char opt_editor[SIZEOF_STR] = "";
709 static FILE *opt_tty = NULL;
711 #define is_initial_commit() (!*opt_head_rev)
712 #define is_head_commit(rev) (!strcmp((rev), "HEAD") || !strcmp(opt_head_rev, (rev)))
715 parse_options(int argc, const char *argv[], const char ***run_argv)
717 enum request request = REQ_VIEW_MAIN;
718 const char *subcommand;
719 bool seen_dashdash = FALSE;
720 /* XXX: This is vulnerable to the user overriding options
721 * required for the main view parser. */
722 const char *custom_argv[SIZEOF_ARG] = {
723 "git", "log", "--no-color", "--pretty=raw", "--parents",
728 if (!isatty(STDIN_FILENO))
729 return REQ_VIEW_PAGER;
732 return REQ_VIEW_MAIN;
734 subcommand = argv[1];
735 if (!strcmp(subcommand, "status") || !strcmp(subcommand, "-S")) {
736 if (!strcmp(subcommand, "-S"))
737 warn("`-S' has been deprecated; use `tig status' instead");
739 warn("ignoring arguments after `%s'", subcommand);
740 return REQ_VIEW_STATUS;
742 } else if (!strcmp(subcommand, "blame")) {
743 if (argc <= 2 || argc > 4)
744 die("invalid number of options to blame\n\n%s", usage);
748 string_ncopy(opt_ref, argv[i], strlen(argv[i]));
752 string_ncopy(opt_file, argv[i], strlen(argv[i]));
753 return REQ_VIEW_BLAME;
755 } else if (!strcmp(subcommand, "show")) {
756 request = REQ_VIEW_DIFF;
758 } else if (!strcmp(subcommand, "log") || !strcmp(subcommand, "diff")) {
759 request = subcommand[0] == 'l' ? REQ_VIEW_LOG : REQ_VIEW_DIFF;
760 warn("`tig %s' has been deprecated", subcommand);
767 custom_argv[1] = subcommand;
771 for (i = 1 + !!subcommand; i < argc; i++) {
772 const char *opt = argv[i];
774 if (seen_dashdash || !strcmp(opt, "--")) {
775 seen_dashdash = TRUE;
777 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
778 printf("tig version %s\n", TIG_VERSION);
781 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
782 printf("%s\n", usage);
786 custom_argv[j++] = opt;
787 if (j >= ARRAY_SIZE(custom_argv))
788 die("command too long");
791 custom_argv[j] = NULL;
792 *run_argv = custom_argv;
799 * Line-oriented content detection.
803 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
804 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
805 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
806 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
807 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
808 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
809 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
810 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
811 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
812 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
813 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
814 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
815 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
816 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
817 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
818 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
819 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
820 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
821 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
822 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
823 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
824 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
825 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
826 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
827 LINE(AUTHOR, "author ", COLOR_CYAN, COLOR_DEFAULT, 0), \
828 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
829 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
830 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
831 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
832 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
833 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
834 LINE(DELIMITER, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
835 LINE(DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
836 LINE(LINE_NUMBER, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
837 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
838 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
839 LINE(MAIN_AUTHOR, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
840 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
841 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
842 LINE(MAIN_LOCAL_TAG,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
843 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
844 LINE(MAIN_TRACKED, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
845 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
846 LINE(MAIN_HEAD, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
847 LINE(MAIN_REVGRAPH,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
848 LINE(TREE_DIR, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
849 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
850 LINE(STAT_HEAD, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
851 LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
852 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
853 LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
854 LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
855 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
856 LINE(BLAME_ID, "", COLOR_MAGENTA, COLOR_DEFAULT, 0)
859 #define LINE(type, line, fg, bg, attr) \
867 const char *name; /* Option name. */
868 int namelen; /* Size of option name. */
869 const char *line; /* The start of line to match. */
870 int linelen; /* Size of string to match. */
871 int fg, bg, attr; /* Color and text attributes for the lines. */
874 static struct line_info line_info[] = {
875 #define LINE(type, line, fg, bg, attr) \
876 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
881 static enum line_type
882 get_line_type(const char *line)
884 int linelen = strlen(line);
887 for (type = 0; type < ARRAY_SIZE(line_info); type++)
888 /* Case insensitive search matches Signed-off-by lines better. */
889 if (linelen >= line_info[type].linelen &&
890 !strncasecmp(line_info[type].line, line, line_info[type].linelen))
897 get_line_attr(enum line_type type)
899 assert(type < ARRAY_SIZE(line_info));
900 return COLOR_PAIR(type) | line_info[type].attr;
903 static struct line_info *
904 get_line_info(const char *name)
906 size_t namelen = strlen(name);
909 for (type = 0; type < ARRAY_SIZE(line_info); type++)
910 if (namelen == line_info[type].namelen &&
911 !string_enum_compare(line_info[type].name, name, namelen))
912 return &line_info[type];
920 int default_bg = line_info[LINE_DEFAULT].bg;
921 int default_fg = line_info[LINE_DEFAULT].fg;
926 if (assume_default_colors(default_fg, default_bg) == ERR) {
927 default_bg = COLOR_BLACK;
928 default_fg = COLOR_WHITE;
931 for (type = 0; type < ARRAY_SIZE(line_info); type++) {
932 struct line_info *info = &line_info[type];
933 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
934 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
936 init_pair(type, fg, bg);
944 unsigned int selected:1;
945 unsigned int dirty:1;
947 void *data; /* User data */
957 enum request request;
960 static struct keybinding default_keybindings[] = {
962 { 'm', REQ_VIEW_MAIN },
963 { 'd', REQ_VIEW_DIFF },
964 { 'l', REQ_VIEW_LOG },
965 { 't', REQ_VIEW_TREE },
966 { 'f', REQ_VIEW_BLOB },
967 { 'B', REQ_VIEW_BLAME },
968 { 'p', REQ_VIEW_PAGER },
969 { 'h', REQ_VIEW_HELP },
970 { 'S', REQ_VIEW_STATUS },
971 { 'c', REQ_VIEW_STAGE },
973 /* View manipulation */
974 { 'q', REQ_VIEW_CLOSE },
975 { KEY_TAB, REQ_VIEW_NEXT },
976 { KEY_RETURN, REQ_ENTER },
977 { KEY_UP, REQ_PREVIOUS },
978 { KEY_DOWN, REQ_NEXT },
979 { 'R', REQ_REFRESH },
980 { KEY_F(5), REQ_REFRESH },
981 { 'O', REQ_MAXIMIZE },
983 /* Cursor navigation */
984 { 'k', REQ_MOVE_UP },
985 { 'j', REQ_MOVE_DOWN },
986 { KEY_HOME, REQ_MOVE_FIRST_LINE },
987 { KEY_END, REQ_MOVE_LAST_LINE },
988 { KEY_NPAGE, REQ_MOVE_PAGE_DOWN },
989 { ' ', REQ_MOVE_PAGE_DOWN },
990 { KEY_PPAGE, REQ_MOVE_PAGE_UP },
991 { 'b', REQ_MOVE_PAGE_UP },
992 { '-', REQ_MOVE_PAGE_UP },
995 { KEY_IC, REQ_SCROLL_LINE_UP },
996 { KEY_DC, REQ_SCROLL_LINE_DOWN },
997 { 'w', REQ_SCROLL_PAGE_UP },
998 { 's', REQ_SCROLL_PAGE_DOWN },
1001 { '/', REQ_SEARCH },
1002 { '?', REQ_SEARCH_BACK },
1003 { 'n', REQ_FIND_NEXT },
1004 { 'N', REQ_FIND_PREV },
1008 { 'z', REQ_STOP_LOADING },
1009 { 'v', REQ_SHOW_VERSION },
1010 { 'r', REQ_SCREEN_REDRAW },
1011 { '.', REQ_TOGGLE_LINENO },
1012 { 'D', REQ_TOGGLE_DATE },
1013 { 'A', REQ_TOGGLE_AUTHOR },
1014 { 'g', REQ_TOGGLE_REV_GRAPH },
1015 { 'F', REQ_TOGGLE_REFS },
1016 { ':', REQ_PROMPT },
1017 { 'u', REQ_STATUS_UPDATE },
1018 { '!', REQ_STATUS_REVERT },
1019 { 'M', REQ_STATUS_MERGE },
1020 { '@', REQ_STAGE_NEXT },
1021 { ',', REQ_TREE_PARENT },
1024 /* Using the ncurses SIGWINCH handler. */
1025 { KEY_RESIZE, REQ_SCREEN_RESIZE },
1028 #define KEYMAP_INFO \
1042 #define KEYMAP_(name) KEYMAP_##name
1047 static struct int_map keymap_table[] = {
1048 #define KEYMAP_(name) { #name, STRING_SIZE(#name), KEYMAP_##name }
1053 #define set_keymap(map, name) \
1054 set_from_int_map(keymap_table, ARRAY_SIZE(keymap_table), map, name, strlen(name))
1056 struct keybinding_table {
1057 struct keybinding *data;
1061 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1064 add_keybinding(enum keymap keymap, enum request request, int key)
1066 struct keybinding_table *table = &keybindings[keymap];
1068 table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1070 die("Failed to allocate keybinding");
1071 table->data[table->size].alias = key;
1072 table->data[table->size++].request = request;
1075 /* Looks for a key binding first in the given map, then in the generic map, and
1076 * lastly in the default keybindings. */
1078 get_keybinding(enum keymap keymap, int key)
1082 for (i = 0; i < keybindings[keymap].size; i++)
1083 if (keybindings[keymap].data[i].alias == key)
1084 return keybindings[keymap].data[i].request;
1086 for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1087 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1088 return keybindings[KEYMAP_GENERIC].data[i].request;
1090 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1091 if (default_keybindings[i].alias == key)
1092 return default_keybindings[i].request;
1094 return (enum request) key;
1103 static struct key key_table[] = {
1104 { "Enter", KEY_RETURN },
1106 { "Backspace", KEY_BACKSPACE },
1108 { "Escape", KEY_ESC },
1109 { "Left", KEY_LEFT },
1110 { "Right", KEY_RIGHT },
1112 { "Down", KEY_DOWN },
1113 { "Insert", KEY_IC },
1114 { "Delete", KEY_DC },
1116 { "Home", KEY_HOME },
1118 { "PageUp", KEY_PPAGE },
1119 { "PageDown", KEY_NPAGE },
1129 { "F10", KEY_F(10) },
1130 { "F11", KEY_F(11) },
1131 { "F12", KEY_F(12) },
1135 get_key_value(const char *name)
1139 for (i = 0; i < ARRAY_SIZE(key_table); i++)
1140 if (!strcasecmp(key_table[i].name, name))
1141 return key_table[i].value;
1143 if (strlen(name) == 1 && isprint(*name))
1150 get_key_name(int key_value)
1152 static char key_char[] = "'X'";
1153 const char *seq = NULL;
1156 for (key = 0; key < ARRAY_SIZE(key_table); key++)
1157 if (key_table[key].value == key_value)
1158 seq = key_table[key].name;
1162 isprint(key_value)) {
1163 key_char[1] = (char) key_value;
1167 return seq ? seq : "(no key)";
1171 get_key(enum request request)
1173 static char buf[BUFSIZ];
1180 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1181 struct keybinding *keybinding = &default_keybindings[i];
1183 if (keybinding->request != request)
1186 if (!string_format_from(buf, &pos, "%s%s", sep,
1187 get_key_name(keybinding->alias)))
1188 return "Too many keybindings!";
1195 struct run_request {
1198 const char *argv[SIZEOF_ARG];
1201 static struct run_request *run_request;
1202 static size_t run_requests;
1205 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1207 struct run_request *req;
1209 if (argc >= ARRAY_SIZE(req->argv) - 1)
1212 req = realloc(run_request, (run_requests + 1) * sizeof(*run_request));
1217 req = &run_request[run_requests];
1218 req->keymap = keymap;
1220 req->argv[0] = NULL;
1222 if (!format_argv(req->argv, argv, FORMAT_NONE))
1225 return REQ_NONE + ++run_requests;
1228 static struct run_request *
1229 get_run_request(enum request request)
1231 if (request <= REQ_NONE)
1233 return &run_request[request - REQ_NONE - 1];
1237 add_builtin_run_requests(void)
1239 const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1240 const char *gc[] = { "git", "gc", NULL };
1247 { KEYMAP_MAIN, 'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1248 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1252 for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1255 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1256 if (req != REQ_NONE)
1257 add_keybinding(reqs[i].keymap, req, reqs[i].key);
1262 * User config file handling.
1265 static struct int_map color_map[] = {
1266 #define COLOR_MAP(name) { #name, STRING_SIZE(#name), COLOR_##name }
1278 #define set_color(color, name) \
1279 set_from_int_map(color_map, ARRAY_SIZE(color_map), color, name, strlen(name))
1281 static struct int_map attr_map[] = {
1282 #define ATTR_MAP(name) { #name, STRING_SIZE(#name), A_##name }
1289 ATTR_MAP(UNDERLINE),
1292 #define set_attribute(attr, name) \
1293 set_from_int_map(attr_map, ARRAY_SIZE(attr_map), attr, name, strlen(name))
1295 static int config_lineno;
1296 static bool config_errors;
1297 static const char *config_msg;
1299 /* Wants: object fgcolor bgcolor [attr] */
1301 option_color_command(int argc, const char *argv[])
1303 struct line_info *info;
1305 if (argc != 3 && argc != 4) {
1306 config_msg = "Wrong number of arguments given to color command";
1310 info = get_line_info(argv[0]);
1312 if (!string_enum_compare(argv[0], "main-delim", strlen("main-delim"))) {
1313 info = get_line_info("delimiter");
1315 } else if (!string_enum_compare(argv[0], "main-date", strlen("main-date"))) {
1316 info = get_line_info("date");
1319 config_msg = "Unknown color name";
1324 if (set_color(&info->fg, argv[1]) == ERR ||
1325 set_color(&info->bg, argv[2]) == ERR) {
1326 config_msg = "Unknown color";
1330 if (argc == 4 && set_attribute(&info->attr, argv[3]) == ERR) {
1331 config_msg = "Unknown attribute";
1338 static bool parse_bool(const char *s)
1340 return (!strcmp(s, "1") || !strcmp(s, "true") ||
1341 !strcmp(s, "yes")) ? TRUE : FALSE;
1345 parse_int(const char *s, int default_value, int min, int max)
1347 int value = atoi(s);
1349 return (value < min || value > max) ? default_value : value;
1352 /* Wants: name = value */
1354 option_set_command(int argc, const char *argv[])
1357 config_msg = "Wrong number of arguments given to set command";
1361 if (strcmp(argv[1], "=")) {
1362 config_msg = "No value assigned";
1366 if (!strcmp(argv[0], "show-author")) {
1367 opt_author = parse_bool(argv[2]);
1371 if (!strcmp(argv[0], "show-date")) {
1372 opt_date = parse_bool(argv[2]);
1376 if (!strcmp(argv[0], "show-rev-graph")) {
1377 opt_rev_graph = parse_bool(argv[2]);
1381 if (!strcmp(argv[0], "show-refs")) {
1382 opt_show_refs = parse_bool(argv[2]);
1386 if (!strcmp(argv[0], "show-line-numbers")) {
1387 opt_line_number = parse_bool(argv[2]);
1391 if (!strcmp(argv[0], "line-graphics")) {
1392 opt_line_graphics = parse_bool(argv[2]);
1396 if (!strcmp(argv[0], "line-number-interval")) {
1397 opt_num_interval = parse_int(argv[2], opt_num_interval, 1, 1024);
1401 if (!strcmp(argv[0], "author-width")) {
1402 opt_author_cols = parse_int(argv[2], opt_author_cols, 0, 1024);
1406 if (!strcmp(argv[0], "tab-size")) {
1407 opt_tab_size = parse_int(argv[2], opt_tab_size, 1, 1024);
1411 if (!strcmp(argv[0], "commit-encoding")) {
1412 const char *arg = argv[2];
1413 int arglen = strlen(arg);
1418 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1419 config_msg = "Unmatched quotation";
1422 arg += 1; arglen -= 2;
1424 string_ncopy(opt_encoding, arg, strlen(arg));
1429 config_msg = "Unknown variable name";
1433 /* Wants: mode request key */
1435 option_bind_command(int argc, const char *argv[])
1437 enum request request;
1442 config_msg = "Wrong number of arguments given to bind command";
1446 if (set_keymap(&keymap, argv[0]) == ERR) {
1447 config_msg = "Unknown key map";
1451 key = get_key_value(argv[1]);
1453 config_msg = "Unknown key";
1457 request = get_request(argv[2]);
1458 if (request == REQ_NONE) {
1459 const char *obsolete[] = { "cherry-pick" };
1460 size_t namelen = strlen(argv[2]);
1463 for (i = 0; i < ARRAY_SIZE(obsolete); i++) {
1464 if (namelen == strlen(obsolete[i]) &&
1465 !string_enum_compare(obsolete[i], argv[2], namelen)) {
1466 config_msg = "Obsolete request name";
1471 if (request == REQ_NONE && *argv[2]++ == '!')
1472 request = add_run_request(keymap, key, argc - 2, argv + 2);
1473 if (request == REQ_NONE) {
1474 config_msg = "Unknown request name";
1478 add_keybinding(keymap, request, key);
1484 set_option(const char *opt, char *value)
1486 const char *argv[SIZEOF_ARG];
1489 if (!argv_from_string(argv, &argc, value)) {
1490 config_msg = "Too many option arguments";
1494 if (!strcmp(opt, "color"))
1495 return option_color_command(argc, argv);
1497 if (!strcmp(opt, "set"))
1498 return option_set_command(argc, argv);
1500 if (!strcmp(opt, "bind"))
1501 return option_bind_command(argc, argv);
1503 config_msg = "Unknown option command";
1508 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1513 config_msg = "Internal error";
1515 /* Check for comment markers, since read_properties() will
1516 * only ensure opt and value are split at first " \t". */
1517 optlen = strcspn(opt, "#");
1521 if (opt[optlen] != 0) {
1522 config_msg = "No option value";
1526 /* Look for comment endings in the value. */
1527 size_t len = strcspn(value, "#");
1529 if (len < valuelen) {
1531 value[valuelen] = 0;
1534 status = set_option(opt, value);
1537 if (status == ERR) {
1538 fprintf(stderr, "Error on line %d, near '%.*s': %s\n",
1539 config_lineno, (int) optlen, opt, config_msg);
1540 config_errors = TRUE;
1543 /* Always keep going if errors are encountered. */
1548 load_option_file(const char *path)
1552 /* It's ok that the file doesn't exist. */
1553 file = fopen(path, "r");
1558 config_errors = FALSE;
1560 if (read_properties(file, " \t", read_option) == ERR ||
1561 config_errors == TRUE)
1562 fprintf(stderr, "Errors while loading %s.\n", path);
1568 const char *home = getenv("HOME");
1569 const char *tigrc_user = getenv("TIGRC_USER");
1570 const char *tigrc_system = getenv("TIGRC_SYSTEM");
1571 char buf[SIZEOF_STR];
1573 add_builtin_run_requests();
1575 if (!tigrc_system) {
1576 if (!string_format(buf, "%s/tigrc", SYSCONFDIR))
1580 load_option_file(tigrc_system);
1583 if (!home || !string_format(buf, "%s/.tigrc", home))
1587 load_option_file(tigrc_user);
1600 /* The display array of active views and the index of the current view. */
1601 static struct view *display[2];
1602 static unsigned int current_view;
1604 /* Reading from the prompt? */
1605 static bool input_mode = FALSE;
1607 #define foreach_displayed_view(view, i) \
1608 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1610 #define displayed_views() (display[1] != NULL ? 2 : 1)
1612 /* Current head and commit ID */
1613 static char ref_blob[SIZEOF_REF] = "";
1614 static char ref_commit[SIZEOF_REF] = "HEAD";
1615 static char ref_head[SIZEOF_REF] = "HEAD";
1618 const char *name; /* View name */
1619 const char *cmd_env; /* Command line set via environment */
1620 const char *id; /* Points to either of ref_{head,commit,blob} */
1622 struct view_ops *ops; /* View operations */
1624 enum keymap keymap; /* What keymap does this view have */
1625 bool git_dir; /* Whether the view requires a git directory. */
1627 char ref[SIZEOF_REF]; /* Hovered commit reference */
1628 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
1630 int height, width; /* The width and height of the main window */
1631 WINDOW *win; /* The main window */
1632 WINDOW *title; /* The title window living below the main window */
1635 unsigned long offset; /* Offset of the window top */
1636 unsigned long lineno; /* Current line number */
1639 char grep[SIZEOF_STR]; /* Search string */
1640 regex_t *regex; /* Pre-compiled regex */
1642 /* If non-NULL, points to the view that opened this view. If this view
1643 * is closed tig will switch back to the parent view. */
1644 struct view *parent;
1647 size_t lines; /* Total number of lines */
1648 struct line *line; /* Line index */
1649 size_t line_alloc; /* Total number of allocated lines */
1650 size_t line_size; /* Total number of used lines */
1651 unsigned int digits; /* Number of digits in the lines member. */
1654 struct line *curline; /* Line currently being drawn. */
1655 enum line_type curtype; /* Attribute currently used for drawing. */
1656 unsigned long col; /* Column when drawing. */
1665 /* What type of content being displayed. Used in the title bar. */
1667 /* Default command arguments. */
1669 /* Open and reads in all view content. */
1670 bool (*open)(struct view *view);
1671 /* Read one line; updates view->line. */
1672 bool (*read)(struct view *view, char *data);
1673 /* Draw one line; @lineno must be < view->height. */
1674 bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
1675 /* Depending on view handle a special requests. */
1676 enum request (*request)(struct view *view, enum request request, struct line *line);
1677 /* Search for regex in a line. */
1678 bool (*grep)(struct view *view, struct line *line);
1680 void (*select)(struct view *view, struct line *line);
1683 static struct view_ops blame_ops;
1684 static struct view_ops blob_ops;
1685 static struct view_ops diff_ops;
1686 static struct view_ops help_ops;
1687 static struct view_ops log_ops;
1688 static struct view_ops main_ops;
1689 static struct view_ops pager_ops;
1690 static struct view_ops stage_ops;
1691 static struct view_ops status_ops;
1692 static struct view_ops tree_ops;
1694 #define VIEW_STR(name, env, ref, ops, map, git) \
1695 { name, #env, ref, ops, map, git }
1697 #define VIEW_(id, name, ops, git, ref) \
1698 VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
1701 static struct view views[] = {
1702 VIEW_(MAIN, "main", &main_ops, TRUE, ref_head),
1703 VIEW_(DIFF, "diff", &diff_ops, TRUE, ref_commit),
1704 VIEW_(LOG, "log", &log_ops, TRUE, ref_head),
1705 VIEW_(TREE, "tree", &tree_ops, TRUE, ref_commit),
1706 VIEW_(BLOB, "blob", &blob_ops, TRUE, ref_blob),
1707 VIEW_(BLAME, "blame", &blame_ops, TRUE, ref_commit),
1708 VIEW_(HELP, "help", &help_ops, FALSE, ""),
1709 VIEW_(PAGER, "pager", &pager_ops, FALSE, "stdin"),
1710 VIEW_(STATUS, "status", &status_ops, TRUE, ""),
1711 VIEW_(STAGE, "stage", &stage_ops, TRUE, ""),
1714 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
1715 #define VIEW_REQ(view) ((view) - views + REQ_OFFSET + 1)
1717 #define foreach_view(view, i) \
1718 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1720 #define view_is_displayed(view) \
1721 (view == display[0] || view == display[1])
1728 static int line_graphics[] = {
1729 /* LINE_GRAPHIC_VLINE: */ '|'
1733 set_view_attr(struct view *view, enum line_type type)
1735 if (!view->curline->selected && view->curtype != type) {
1736 wattrset(view->win, get_line_attr(type));
1737 wchgat(view->win, -1, 0, type, NULL);
1738 view->curtype = type;
1743 draw_chars(struct view *view, enum line_type type, const char *string,
1744 int max_len, bool use_tilde)
1748 int trimmed = FALSE;
1754 len = utf8_length(string, &col, max_len, &trimmed, use_tilde);
1756 col = len = strlen(string);
1757 if (len > max_len) {
1761 col = len = max_len;
1766 set_view_attr(view, type);
1767 waddnstr(view->win, string, len);
1768 if (trimmed && use_tilde) {
1769 set_view_attr(view, LINE_DELIMITER);
1770 waddch(view->win, '~');
1778 draw_space(struct view *view, enum line_type type, int max, int spaces)
1780 static char space[] = " ";
1783 spaces = MIN(max, spaces);
1785 while (spaces > 0) {
1786 int len = MIN(spaces, sizeof(space) - 1);
1788 col += draw_chars(view, type, space, spaces, FALSE);
1796 draw_lineno(struct view *view, unsigned int lineno)
1799 int digits3 = view->digits < 3 ? 3 : view->digits;
1800 int max_number = MIN(digits3, STRING_SIZE(number));
1801 int max = view->width - view->col;
1804 if (max < max_number)
1807 lineno += view->offset + 1;
1808 if (lineno == 1 || (lineno % opt_num_interval) == 0) {
1809 static char fmt[] = "%1ld";
1811 if (view->digits <= 9)
1812 fmt[1] = '0' + digits3;
1814 if (!string_format(number, fmt, lineno))
1816 col = draw_chars(view, LINE_LINE_NUMBER, number, max_number, TRUE);
1818 col = draw_space(view, LINE_LINE_NUMBER, max_number, max_number);
1822 set_view_attr(view, LINE_DEFAULT);
1823 waddch(view->win, line_graphics[LINE_GRAPHIC_VLINE]);
1828 col += draw_space(view, LINE_DEFAULT, max - col, 1);
1831 return view->width - view->col <= 0;
1835 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
1837 view->col += draw_chars(view, type, string, view->width - view->col, trim);
1838 return view->width - view->col <= 0;
1842 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
1844 int max = view->width - view->col;
1850 set_view_attr(view, type);
1851 /* Using waddch() instead of waddnstr() ensures that
1852 * they'll be rendered correctly for the cursor line. */
1853 for (i = 0; i < size; i++)
1854 waddch(view->win, graphic[i]);
1858 waddch(view->win, ' ');
1862 return view->width - view->col <= 0;
1866 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
1868 int max = MIN(view->width - view->col, len);
1872 col = draw_chars(view, type, text, max - 1, trim);
1874 col = draw_space(view, type, max - 1, max - 1);
1876 view->col += col + draw_space(view, LINE_DEFAULT, max - col, max - col);
1877 return view->width - view->col <= 0;
1881 draw_date(struct view *view, struct tm *time)
1883 char buf[DATE_COLS];
1888 timelen = strftime(buf, sizeof(buf), DATE_FORMAT, time);
1889 date = timelen ? buf : NULL;
1891 return draw_field(view, LINE_DATE, date, DATE_COLS, FALSE);
1895 draw_view_line(struct view *view, unsigned int lineno)
1898 bool selected = (view->offset + lineno == view->lineno);
1901 assert(view_is_displayed(view));
1903 if (view->offset + lineno >= view->lines)
1906 line = &view->line[view->offset + lineno];
1908 wmove(view->win, lineno, 0);
1910 view->curline = line;
1911 view->curtype = LINE_NONE;
1912 line->selected = FALSE;
1915 set_view_attr(view, LINE_CURSOR);
1916 line->selected = TRUE;
1917 view->ops->select(view, line);
1918 } else if (line->selected) {
1919 wclrtoeol(view->win);
1922 scrollok(view->win, FALSE);
1923 draw_ok = view->ops->draw(view, line, lineno);
1924 scrollok(view->win, TRUE);
1930 redraw_view_dirty(struct view *view)
1935 for (lineno = 0; lineno < view->height; lineno++) {
1936 struct line *line = &view->line[view->offset + lineno];
1942 if (!draw_view_line(view, lineno))
1948 redrawwin(view->win);
1950 wnoutrefresh(view->win);
1952 wrefresh(view->win);
1956 redraw_view_from(struct view *view, int lineno)
1958 assert(0 <= lineno && lineno < view->height);
1960 for (; lineno < view->height; lineno++) {
1961 if (!draw_view_line(view, lineno))
1965 redrawwin(view->win);
1967 wnoutrefresh(view->win);
1969 wrefresh(view->win);
1973 redraw_view(struct view *view)
1976 redraw_view_from(view, 0);
1981 update_view_title(struct view *view)
1983 char buf[SIZEOF_STR];
1984 char state[SIZEOF_STR];
1985 size_t bufpos = 0, statelen = 0;
1987 assert(view_is_displayed(view));
1989 if (view != VIEW(REQ_VIEW_STATUS) && (view->lines || view->pipe)) {
1990 unsigned int view_lines = view->offset + view->height;
1991 unsigned int lines = view->lines
1992 ? MIN(view_lines, view->lines) * 100 / view->lines
1995 string_format_from(state, &statelen, "- %s %d of %d (%d%%)",
2002 time_t secs = time(NULL) - view->start_time;
2004 /* Three git seconds are a long time ... */
2006 string_format_from(state, &statelen, " %lds", secs);
2010 string_format_from(buf, &bufpos, "[%s]", view->name);
2011 if (*view->ref && bufpos < view->width) {
2012 size_t refsize = strlen(view->ref);
2013 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2015 if (minsize < view->width)
2016 refsize = view->width - minsize + 7;
2017 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2020 if (statelen && bufpos < view->width) {
2021 string_format_from(buf, &bufpos, " %s", state);
2024 if (view == display[current_view])
2025 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2027 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2029 mvwaddnstr(view->title, 0, 0, buf, bufpos);
2030 wclrtoeol(view->title);
2031 wmove(view->title, 0, view->width - 1);
2034 wnoutrefresh(view->title);
2036 wrefresh(view->title);
2040 resize_display(void)
2043 struct view *base = display[0];
2044 struct view *view = display[1] ? display[1] : display[0];
2046 /* Setup window dimensions */
2048 getmaxyx(stdscr, base->height, base->width);
2050 /* Make room for the status window. */
2054 /* Horizontal split. */
2055 view->width = base->width;
2056 view->height = SCALE_SPLIT_VIEW(base->height);
2057 base->height -= view->height;
2059 /* Make room for the title bar. */
2063 /* Make room for the title bar. */
2068 foreach_displayed_view (view, i) {
2070 view->win = newwin(view->height, 0, offset, 0);
2072 die("Failed to create %s view", view->name);
2074 scrollok(view->win, TRUE);
2076 view->title = newwin(1, 0, offset + view->height, 0);
2078 die("Failed to create title window");
2081 wresize(view->win, view->height, view->width);
2082 mvwin(view->win, offset, 0);
2083 mvwin(view->title, offset + view->height, 0);
2086 offset += view->height + 1;
2091 redraw_display(void)
2096 foreach_displayed_view (view, i) {
2098 update_view_title(view);
2103 update_display_cursor(struct view *view)
2105 /* Move the cursor to the right-most column of the cursor line.
2107 * XXX: This could turn out to be a bit expensive, but it ensures that
2108 * the cursor does not jump around. */
2110 wmove(view->win, view->lineno - view->offset, view->width - 1);
2111 wrefresh(view->win);
2119 /* Scrolling backend */
2121 do_scroll_view(struct view *view, int lines)
2123 bool redraw_current_line = FALSE;
2125 /* The rendering expects the new offset. */
2126 view->offset += lines;
2128 assert(0 <= view->offset && view->offset < view->lines);
2131 /* Move current line into the view. */
2132 if (view->lineno < view->offset) {
2133 view->lineno = view->offset;
2134 redraw_current_line = TRUE;
2135 } else if (view->lineno >= view->offset + view->height) {
2136 view->lineno = view->offset + view->height - 1;
2137 redraw_current_line = TRUE;
2140 assert(view->offset <= view->lineno && view->lineno < view->lines);
2142 /* Redraw the whole screen if scrolling is pointless. */
2143 if (view->height < ABS(lines)) {
2147 int line = lines > 0 ? view->height - lines : 0;
2148 int end = line + ABS(lines);
2150 wscrl(view->win, lines);
2152 for (; line < end; line++) {
2153 if (!draw_view_line(view, line))
2157 if (redraw_current_line)
2158 draw_view_line(view, view->lineno - view->offset);
2161 redrawwin(view->win);
2162 wrefresh(view->win);
2166 /* Scroll frontend */
2168 scroll_view(struct view *view, enum request request)
2172 assert(view_is_displayed(view));
2175 case REQ_SCROLL_PAGE_DOWN:
2176 lines = view->height;
2177 case REQ_SCROLL_LINE_DOWN:
2178 if (view->offset + lines > view->lines)
2179 lines = view->lines - view->offset;
2181 if (lines == 0 || view->offset + view->height >= view->lines) {
2182 report("Cannot scroll beyond the last line");
2187 case REQ_SCROLL_PAGE_UP:
2188 lines = view->height;
2189 case REQ_SCROLL_LINE_UP:
2190 if (lines > view->offset)
2191 lines = view->offset;
2194 report("Cannot scroll beyond the first line");
2202 die("request %d not handled in switch", request);
2205 do_scroll_view(view, lines);
2210 move_view(struct view *view, enum request request)
2212 int scroll_steps = 0;
2216 case REQ_MOVE_FIRST_LINE:
2217 steps = -view->lineno;
2220 case REQ_MOVE_LAST_LINE:
2221 steps = view->lines - view->lineno - 1;
2224 case REQ_MOVE_PAGE_UP:
2225 steps = view->height > view->lineno
2226 ? -view->lineno : -view->height;
2229 case REQ_MOVE_PAGE_DOWN:
2230 steps = view->lineno + view->height >= view->lines
2231 ? view->lines - view->lineno - 1 : view->height;
2243 die("request %d not handled in switch", request);
2246 if (steps <= 0 && view->lineno == 0) {
2247 report("Cannot move beyond the first line");
2250 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2251 report("Cannot move beyond the last line");
2255 /* Move the current line */
2256 view->lineno += steps;
2257 assert(0 <= view->lineno && view->lineno < view->lines);
2259 /* Check whether the view needs to be scrolled */
2260 if (view->lineno < view->offset ||
2261 view->lineno >= view->offset + view->height) {
2262 scroll_steps = steps;
2263 if (steps < 0 && -steps > view->offset) {
2264 scroll_steps = -view->offset;
2266 } else if (steps > 0) {
2267 if (view->lineno == view->lines - 1 &&
2268 view->lines > view->height) {
2269 scroll_steps = view->lines - view->offset - 1;
2270 if (scroll_steps >= view->height)
2271 scroll_steps -= view->height - 1;
2276 if (!view_is_displayed(view)) {
2277 view->offset += scroll_steps;
2278 assert(0 <= view->offset && view->offset < view->lines);
2279 view->ops->select(view, &view->line[view->lineno]);
2283 /* Repaint the old "current" line if we be scrolling */
2284 if (ABS(steps) < view->height)
2285 draw_view_line(view, view->lineno - steps - view->offset);
2288 do_scroll_view(view, scroll_steps);
2292 /* Draw the current line */
2293 draw_view_line(view, view->lineno - view->offset);
2295 redrawwin(view->win);
2296 wrefresh(view->win);
2305 static void search_view(struct view *view, enum request request);
2308 find_next_line(struct view *view, unsigned long lineno, struct line *line)
2310 assert(view_is_displayed(view));
2312 if (!view->ops->grep(view, line))
2315 if (lineno - view->offset >= view->height) {
2316 view->offset = lineno;
2317 view->lineno = lineno;
2321 unsigned long old_lineno = view->lineno - view->offset;
2323 view->lineno = lineno;
2324 draw_view_line(view, old_lineno);
2326 draw_view_line(view, view->lineno - view->offset);
2327 redrawwin(view->win);
2328 wrefresh(view->win);
2331 report("Line %ld matches '%s'", lineno + 1, view->grep);
2336 find_next(struct view *view, enum request request)
2338 unsigned long lineno = view->lineno;
2343 report("No previous search");
2345 search_view(view, request);
2355 case REQ_SEARCH_BACK:
2364 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2365 lineno += direction;
2367 /* Note, lineno is unsigned long so will wrap around in which case it
2368 * will become bigger than view->lines. */
2369 for (; lineno < view->lines; lineno += direction) {
2370 struct line *line = &view->line[lineno];
2372 if (find_next_line(view, lineno, line))
2376 report("No match found for '%s'", view->grep);
2380 search_view(struct view *view, enum request request)
2385 regfree(view->regex);
2388 view->regex = calloc(1, sizeof(*view->regex));
2393 regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2394 if (regex_err != 0) {
2395 char buf[SIZEOF_STR] = "unknown error";
2397 regerror(regex_err, view->regex, buf, sizeof(buf));
2398 report("Search failed: %s", buf);
2402 string_copy(view->grep, opt_search);
2404 find_next(view, request);
2408 * Incremental updating
2412 reset_view(struct view *view)
2416 for (i = 0; i < view->lines; i++)
2417 free(view->line[i].data);
2424 view->line_size = 0;
2425 view->line_alloc = 0;
2430 free_argv(const char *argv[])
2434 for (argc = 0; argv[argc]; argc++)
2435 free((void *) argv[argc]);
2439 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
2441 char buf[SIZEOF_STR];
2443 bool noreplace = flags == FORMAT_NONE;
2445 free_argv(dst_argv);
2447 for (argc = 0; src_argv[argc]; argc++) {
2448 const char *arg = src_argv[argc];
2452 char *next = strstr(arg, "%(");
2453 int len = next - arg;
2456 if (!next || noreplace) {
2457 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
2462 } else if (!prefixcmp(next, "%(directory)")) {
2465 } else if (!prefixcmp(next, "%(file)")) {
2468 } else if (!prefixcmp(next, "%(ref)")) {
2469 value = *opt_ref ? opt_ref : "HEAD";
2471 } else if (!prefixcmp(next, "%(head)")) {
2474 } else if (!prefixcmp(next, "%(commit)")) {
2477 } else if (!prefixcmp(next, "%(blob)")) {
2481 report("Unknown replacement: `%s`", next);
2485 if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
2488 arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
2491 dst_argv[argc] = strdup(buf);
2492 if (!dst_argv[argc])
2496 dst_argv[argc] = NULL;
2498 return src_argv[argc] == NULL;
2502 format_command(char dst[], const char *src_argv[], enum format_flags flags)
2504 const char *dst_argv[SIZEOF_ARG * 2] = { NULL };
2508 if (!format_argv(dst_argv, src_argv, flags)) {
2509 free_argv(dst_argv);
2513 for (argc = 0; dst_argv[argc] && bufsize < SIZEOF_STR; argc++) {
2515 dst[bufsize++] = ' ';
2516 bufsize = sq_quote(dst, bufsize, dst_argv[argc]);
2519 if (bufsize < SIZEOF_STR)
2521 free_argv(dst_argv);
2523 return src_argv[argc] == NULL && bufsize < SIZEOF_STR;
2527 end_update(struct view *view, bool force)
2531 while (!view->ops->read(view, NULL))
2534 set_nonblocking_input(FALSE);
2535 done_io(view->pipe);
2540 setup_update(struct view *view, const char *vid)
2542 set_nonblocking_input(TRUE);
2544 string_copy_rev(view->vid, vid);
2545 view->pipe = &view->io;
2546 view->start_time = time(NULL);
2550 prepare_update(struct view *view, const char *argv[], const char *dir,
2551 enum format_flags flags)
2554 end_update(view, TRUE);
2555 return init_io_rd(&view->io, argv, dir, flags);
2559 prepare_update_file(struct view *view, const char *name)
2562 end_update(view, TRUE);
2563 return init_io_fd(&view->io, fopen(name, "r"));
2567 begin_update(struct view *view, bool refresh)
2570 if (!start_io(&view->io))
2574 if (view == VIEW(REQ_VIEW_TREE) && strcmp(view->vid, view->id))
2577 if (!run_io_rd(&view->io, view->ops->argv, FORMAT_ALL))
2580 /* Put the current ref_* value to the view title ref
2581 * member. This is needed by the blob view. Most other
2582 * views sets it automatically after loading because the
2583 * first line is a commit line. */
2584 string_copy_rev(view->ref, view->id);
2587 setup_update(view, view->id);
2592 #define ITEM_CHUNK_SIZE 256
2594 realloc_items(void *mem, size_t *size, size_t new_size, size_t item_size)
2596 size_t num_chunks = *size / ITEM_CHUNK_SIZE;
2597 size_t num_chunks_new = (new_size + ITEM_CHUNK_SIZE - 1) / ITEM_CHUNK_SIZE;
2599 if (mem == NULL || num_chunks != num_chunks_new) {
2600 *size = num_chunks_new * ITEM_CHUNK_SIZE;
2601 mem = realloc(mem, *size * item_size);
2607 static struct line *
2608 realloc_lines(struct view *view, size_t line_size)
2610 size_t alloc = view->line_alloc;
2611 struct line *tmp = realloc_items(view->line, &alloc, line_size,
2612 sizeof(*view->line));
2618 view->line_alloc = alloc;
2619 view->line_size = line_size;
2624 update_view(struct view *view)
2626 char out_buffer[BUFSIZ * 2];
2628 /* The number of lines to read. If too low it will cause too much
2629 * redrawing (and possible flickering), if too high responsiveness
2631 unsigned long lines = view->height;
2632 int redraw_from = -1;
2637 /* Only redraw if lines are visible. */
2638 if (view->offset + view->height >= view->lines)
2639 redraw_from = view->lines - view->offset;
2641 /* FIXME: This is probably not perfect for backgrounded views. */
2642 if (!realloc_lines(view, view->lines + lines))
2645 while ((line = io_gets(view->pipe))) {
2646 size_t linelen = strlen(line);
2649 line[linelen - 1] = 0;
2651 if (opt_iconv != ICONV_NONE) {
2652 ICONV_CONST char *inbuf = line;
2653 size_t inlen = linelen;
2655 char *outbuf = out_buffer;
2656 size_t outlen = sizeof(out_buffer);
2660 ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2661 if (ret != (size_t) -1) {
2663 linelen = strlen(out_buffer);
2667 if (!view->ops->read(view, line))
2677 lines = view->lines;
2678 for (digits = 0; lines; digits++)
2681 /* Keep the displayed view in sync with line number scaling. */
2682 if (digits != view->digits) {
2683 view->digits = digits;
2688 if (io_error(view->pipe)) {
2689 report("Failed to read: %s", io_strerror(view->pipe));
2690 end_update(view, TRUE);
2692 } else if (io_eof(view->pipe)) {
2694 end_update(view, FALSE);
2697 if (!view_is_displayed(view))
2700 if (view == VIEW(REQ_VIEW_TREE)) {
2701 /* Clear the view and redraw everything since the tree sorting
2702 * might have rearranged things. */
2705 } else if (redraw_from >= 0) {
2706 /* If this is an incremental update, redraw the previous line
2707 * since for commits some members could have changed when
2708 * loading the main view. */
2709 if (redraw_from > 0)
2712 /* Since revision graph visualization requires knowledge
2713 * about the parent commit, it causes a further one-off
2714 * needed to be redrawn for incremental updates. */
2715 if (redraw_from > 0 && opt_rev_graph)
2718 /* Incrementally draw avoids flickering. */
2719 redraw_view_from(view, redraw_from);
2722 if (view == VIEW(REQ_VIEW_BLAME))
2723 redraw_view_dirty(view);
2725 /* Update the title _after_ the redraw so that if the redraw picks up a
2726 * commit reference in view->ref it'll be available here. */
2727 update_view_title(view);
2731 report("Allocation failure");
2732 end_update(view, TRUE);
2736 static struct line *
2737 add_line_data(struct view *view, void *data, enum line_type type)
2739 struct line *line = &view->line[view->lines++];
2741 memset(line, 0, sizeof(*line));
2748 static struct line *
2749 add_line_text(struct view *view, const char *text, enum line_type type)
2751 char *data = text ? strdup(text) : NULL;
2753 return data ? add_line_data(view, data, type) : NULL;
2762 OPEN_DEFAULT = 0, /* Use default view switching. */
2763 OPEN_SPLIT = 1, /* Split current view. */
2764 OPEN_BACKGROUNDED = 2, /* Backgrounded. */
2765 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
2766 OPEN_NOMAXIMIZE = 8, /* Do not maximize the current view. */
2767 OPEN_REFRESH = 16, /* Refresh view using previous command. */
2768 OPEN_PREPARED = 32, /* Open already prepared command. */
2772 open_view(struct view *prev, enum request request, enum open_flags flags)
2774 bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
2775 bool split = !!(flags & OPEN_SPLIT);
2776 bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
2777 bool nomaximize = !!(flags & (OPEN_NOMAXIMIZE | OPEN_REFRESH));
2778 struct view *view = VIEW(request);
2779 int nviews = displayed_views();
2780 struct view *base_view = display[0];
2782 if (view == prev && nviews == 1 && !reload) {
2783 report("Already in %s view", view->name);
2787 if (view->git_dir && !opt_git_dir[0]) {
2788 report("The %s view is disabled in pager view", view->name);
2796 } else if (!nomaximize) {
2797 /* Maximize the current view. */
2798 memset(display, 0, sizeof(display));
2800 display[current_view] = view;
2803 /* Resize the view when switching between split- and full-screen,
2804 * or when switching between two different full-screen views. */
2805 if (nviews != displayed_views() ||
2806 (nviews == 1 && base_view != display[0]))
2810 end_update(view, TRUE);
2812 if (view->ops->open) {
2813 if (!view->ops->open(view)) {
2814 report("Failed to load %s view", view->name);
2818 } else if ((reload || strcmp(view->vid, view->id)) &&
2819 !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
2820 report("Failed to load %s view", view->name);
2824 if (split && prev->lineno - prev->offset >= prev->height) {
2825 /* Take the title line into account. */
2826 int lines = prev->lineno - prev->offset - prev->height + 1;
2828 /* Scroll the view that was split if the current line is
2829 * outside the new limited view. */
2830 do_scroll_view(prev, lines);
2833 if (prev && view != prev) {
2834 if (split && !backgrounded) {
2835 /* "Blur" the previous view. */
2836 update_view_title(prev);
2839 view->parent = prev;
2842 if (view->pipe && view->lines == 0) {
2843 /* Clear the old view and let the incremental updating refill
2847 } else if (view_is_displayed(view)) {
2852 /* If the view is backgrounded the above calls to report()
2853 * won't redraw the view title. */
2855 update_view_title(view);
2859 open_external_viewer(const char *argv[], const char *dir)
2861 def_prog_mode(); /* save current tty modes */
2862 endwin(); /* restore original tty modes */
2863 run_io_fg(argv, dir);
2864 fprintf(stderr, "Press Enter to continue");
2871 open_mergetool(const char *file)
2873 const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
2875 open_external_viewer(mergetool_argv, NULL);
2879 open_editor(bool from_root, const char *file)
2881 const char *editor_argv[] = { "vi", file, NULL };
2884 editor = getenv("GIT_EDITOR");
2885 if (!editor && *opt_editor)
2886 editor = opt_editor;
2888 editor = getenv("VISUAL");
2890 editor = getenv("EDITOR");
2894 editor_argv[0] = editor;
2895 open_external_viewer(editor_argv, from_root ? opt_cdup : NULL);
2899 open_run_request(enum request request)
2901 struct run_request *req = get_run_request(request);
2902 const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
2905 report("Unknown run request");
2909 if (format_argv(argv, req->argv, FORMAT_ALL))
2910 open_external_viewer(argv, NULL);
2915 * User request switch noodle
2919 view_driver(struct view *view, enum request request)
2923 if (request == REQ_NONE) {
2928 if (request > REQ_NONE) {
2929 open_run_request(request);
2930 /* FIXME: When all views can refresh always do this. */
2931 if (view == VIEW(REQ_VIEW_STATUS) ||
2932 view == VIEW(REQ_VIEW_MAIN) ||
2933 view == VIEW(REQ_VIEW_LOG) ||
2934 view == VIEW(REQ_VIEW_STAGE))
2935 request = REQ_REFRESH;
2940 if (view && view->lines) {
2941 request = view->ops->request(view, request, &view->line[view->lineno]);
2942 if (request == REQ_NONE)
2949 case REQ_MOVE_PAGE_UP:
2950 case REQ_MOVE_PAGE_DOWN:
2951 case REQ_MOVE_FIRST_LINE:
2952 case REQ_MOVE_LAST_LINE:
2953 move_view(view, request);
2956 case REQ_SCROLL_LINE_DOWN:
2957 case REQ_SCROLL_LINE_UP:
2958 case REQ_SCROLL_PAGE_DOWN:
2959 case REQ_SCROLL_PAGE_UP:
2960 scroll_view(view, request);
2963 case REQ_VIEW_BLAME:
2965 report("No file chosen, press %s to open tree view",
2966 get_key(REQ_VIEW_TREE));
2969 open_view(view, request, OPEN_DEFAULT);
2974 report("No file chosen, press %s to open tree view",
2975 get_key(REQ_VIEW_TREE));
2978 open_view(view, request, OPEN_DEFAULT);
2981 case REQ_VIEW_PAGER:
2982 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
2983 report("No pager content, press %s to run command from prompt",
2984 get_key(REQ_PROMPT));
2987 open_view(view, request, OPEN_DEFAULT);
2990 case REQ_VIEW_STAGE:
2991 if (!VIEW(REQ_VIEW_STAGE)->lines) {
2992 report("No stage content, press %s to open the status view and choose file",
2993 get_key(REQ_VIEW_STATUS));
2996 open_view(view, request, OPEN_DEFAULT);
2999 case REQ_VIEW_STATUS:
3000 if (opt_is_inside_work_tree == FALSE) {
3001 report("The status view requires a working tree");
3004 open_view(view, request, OPEN_DEFAULT);
3012 open_view(view, request, OPEN_DEFAULT);
3017 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3019 if ((view == VIEW(REQ_VIEW_DIFF) &&
3020 view->parent == VIEW(REQ_VIEW_MAIN)) ||
3021 (view == VIEW(REQ_VIEW_DIFF) &&
3022 view->parent == VIEW(REQ_VIEW_BLAME)) ||
3023 (view == VIEW(REQ_VIEW_STAGE) &&
3024 view->parent == VIEW(REQ_VIEW_STATUS)) ||
3025 (view == VIEW(REQ_VIEW_BLOB) &&
3026 view->parent == VIEW(REQ_VIEW_TREE))) {
3029 view = view->parent;
3030 line = view->lineno;
3031 move_view(view, request);
3032 if (view_is_displayed(view))
3033 update_view_title(view);
3034 if (line != view->lineno)
3035 view->ops->request(view, REQ_ENTER,
3036 &view->line[view->lineno]);
3039 move_view(view, request);
3045 int nviews = displayed_views();
3046 int next_view = (current_view + 1) % nviews;
3048 if (next_view == current_view) {
3049 report("Only one view is displayed");
3053 current_view = next_view;
3054 /* Blur out the title of the previous view. */
3055 update_view_title(view);
3060 report("Refreshing is not yet supported for the %s view", view->name);
3064 if (displayed_views() == 2)
3065 open_view(view, VIEW_REQ(view), OPEN_DEFAULT);
3068 case REQ_TOGGLE_LINENO:
3069 opt_line_number = !opt_line_number;
3073 case REQ_TOGGLE_DATE:
3074 opt_date = !opt_date;
3078 case REQ_TOGGLE_AUTHOR:
3079 opt_author = !opt_author;
3083 case REQ_TOGGLE_REV_GRAPH:
3084 opt_rev_graph = !opt_rev_graph;
3088 case REQ_TOGGLE_REFS:
3089 opt_show_refs = !opt_show_refs;
3094 case REQ_SEARCH_BACK:
3095 search_view(view, request);
3100 find_next(view, request);
3103 case REQ_STOP_LOADING:
3104 for (i = 0; i < ARRAY_SIZE(views); i++) {
3107 report("Stopped loading the %s view", view->name),
3108 end_update(view, TRUE);
3112 case REQ_SHOW_VERSION:
3113 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3116 case REQ_SCREEN_RESIZE:
3119 case REQ_SCREEN_REDRAW:
3124 report("Nothing to edit");
3128 report("Nothing to enter");
3131 case REQ_VIEW_CLOSE:
3132 /* XXX: Mark closed views by letting view->parent point to the
3133 * view itself. Parents to closed view should never be
3136 view->parent->parent != view->parent) {
3137 memset(display, 0, sizeof(display));
3139 display[current_view] = view->parent;
3140 view->parent = view;
3151 report("Unknown key, press 'h' for help");
3164 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3166 char *text = line->data;
3168 if (opt_line_number && draw_lineno(view, lineno))
3171 draw_text(view, line->type, text, TRUE);
3176 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3178 const char *describe_argv[] = { "git", "describe", commit_id, NULL };
3179 char refbuf[SIZEOF_STR];
3182 if (run_io_buf(describe_argv, refbuf, sizeof(refbuf)))
3183 ref = chomp_string(refbuf);
3188 /* This is the only fatal call, since it can "corrupt" the buffer. */
3189 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
3196 add_pager_refs(struct view *view, struct line *line)
3198 char buf[SIZEOF_STR];
3199 char *commit_id = (char *)line->data + STRING_SIZE("commit ");
3201 size_t bufpos = 0, refpos = 0;
3202 const char *sep = "Refs: ";
3203 bool is_tag = FALSE;
3205 assert(line->type == LINE_COMMIT);
3207 refs = get_refs(commit_id);
3209 if (view == VIEW(REQ_VIEW_DIFF))
3210 goto try_add_describe_ref;
3215 struct ref *ref = refs[refpos];
3216 const char *fmt = ref->tag ? "%s[%s]" :
3217 ref->remote ? "%s<%s>" : "%s%s";
3219 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
3224 } while (refs[refpos++]->next);
3226 if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
3227 try_add_describe_ref:
3228 /* Add <tag>-g<commit_id> "fake" reference. */
3229 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
3236 if (!realloc_lines(view, view->line_size + 1))
3239 add_line_text(view, buf, LINE_PP_REFS);
3243 pager_read(struct view *view, char *data)
3250 line = add_line_text(view, data, get_line_type(data));
3254 if (line->type == LINE_COMMIT &&
3255 (view == VIEW(REQ_VIEW_DIFF) ||
3256 view == VIEW(REQ_VIEW_LOG)))
3257 add_pager_refs(view, line);
3263 pager_request(struct view *view, enum request request, struct line *line)
3267 if (request != REQ_ENTER)
3270 if (line->type == LINE_COMMIT &&
3271 (view == VIEW(REQ_VIEW_LOG) ||
3272 view == VIEW(REQ_VIEW_PAGER))) {
3273 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3277 /* Always scroll the view even if it was split. That way
3278 * you can use Enter to scroll through the log view and
3279 * split open each commit diff. */
3280 scroll_view(view, REQ_SCROLL_LINE_DOWN);
3282 /* FIXME: A minor workaround. Scrolling the view will call report("")
3283 * but if we are scrolling a non-current view this won't properly
3284 * update the view title. */
3286 update_view_title(view);
3292 pager_grep(struct view *view, struct line *line)
3295 char *text = line->data;
3300 if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
3307 pager_select(struct view *view, struct line *line)
3309 if (line->type == LINE_COMMIT) {
3310 char *text = (char *)line->data + STRING_SIZE("commit ");
3312 if (view != VIEW(REQ_VIEW_PAGER))
3313 string_copy_rev(view->ref, text);
3314 string_copy_rev(ref_commit, text);
3318 static struct view_ops pager_ops = {
3329 static const char *log_argv[SIZEOF_ARG] = {
3330 "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3334 log_request(struct view *view, enum request request, struct line *line)
3339 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
3342 return pager_request(view, request, line);
3346 static struct view_ops log_ops = {
3357 static const char *diff_argv[SIZEOF_ARG] = {
3358 "git", "show", "--pretty=fuller", "--no-color", "--root",
3359 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
3362 static struct view_ops diff_ops = {
3378 help_open(struct view *view)
3381 int lines = ARRAY_SIZE(req_info) + 2;
3384 if (view->lines > 0)
3387 for (i = 0; i < ARRAY_SIZE(req_info); i++)
3388 if (!req_info[i].request)
3391 lines += run_requests + 1;
3393 view->line = calloc(lines, sizeof(*view->line));
3397 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
3399 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
3402 if (req_info[i].request == REQ_NONE)
3405 if (!req_info[i].request) {
3406 add_line_text(view, "", LINE_DEFAULT);
3407 add_line_text(view, req_info[i].help, LINE_DEFAULT);
3411 key = get_key(req_info[i].request);
3413 key = "(no key defined)";
3415 if (!string_format(buf, " %-25s %s", key, req_info[i].help))
3418 add_line_text(view, buf, LINE_DEFAULT);
3422 add_line_text(view, "", LINE_DEFAULT);
3423 add_line_text(view, "External commands:", LINE_DEFAULT);
3426 for (i = 0; i < run_requests; i++) {
3427 struct run_request *req = get_run_request(REQ_NONE + i + 1);
3429 char cmd[SIZEOF_STR];
3436 key = get_key_name(req->key);
3438 key = "(no key defined)";
3440 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
3441 if (!string_format_from(cmd, &bufpos, "%s%s",
3442 argc ? " " : "", req->argv[argc]))
3445 if (!string_format(buf, " %-10s %-14s `%s`",
3446 keymap_table[req->keymap].name, key, cmd))
3449 add_line_text(view, buf, LINE_DEFAULT);
3455 static struct view_ops help_ops = {
3471 struct tree_stack_entry {
3472 struct tree_stack_entry *prev; /* Entry below this in the stack */
3473 unsigned long lineno; /* Line number to restore */
3474 char *name; /* Position of name in opt_path */
3477 /* The top of the path stack. */
3478 static struct tree_stack_entry *tree_stack = NULL;
3479 unsigned long tree_lineno = 0;
3482 pop_tree_stack_entry(void)
3484 struct tree_stack_entry *entry = tree_stack;
3486 tree_lineno = entry->lineno;
3488 tree_stack = entry->prev;
3493 push_tree_stack_entry(const char *name, unsigned long lineno)
3495 struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
3496 size_t pathlen = strlen(opt_path);
3501 entry->prev = tree_stack;
3502 entry->name = opt_path + pathlen;
3505 if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
3506 pop_tree_stack_entry();
3510 /* Move the current line to the first tree entry. */
3512 entry->lineno = lineno;
3515 /* Parse output from git-ls-tree(1):
3517 * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile
3518 * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README
3519 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
3520 * 100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38 web.conf
3523 #define SIZEOF_TREE_ATTR \
3524 STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t")
3526 #define TREE_UP_FORMAT "040000 tree %s\t.."
3529 tree_compare_entry(enum line_type type1, const char *name1,
3530 enum line_type type2, const char *name2)
3532 if (type1 != type2) {
3533 if (type1 == LINE_TREE_DIR)
3538 return strcmp(name1, name2);
3542 tree_path(struct line *line)
3544 const char *path = line->data;
3546 return path + SIZEOF_TREE_ATTR;
3550 tree_read(struct view *view, char *text)
3552 size_t textlen = text ? strlen(text) : 0;
3553 char buf[SIZEOF_STR];
3555 enum line_type type;
3556 bool first_read = view->lines == 0;
3560 if (textlen <= SIZEOF_TREE_ATTR)
3563 type = text[STRING_SIZE("100644 ")] == 't'
3564 ? LINE_TREE_DIR : LINE_TREE_FILE;
3567 /* Add path info line */
3568 if (!string_format(buf, "Directory path /%s", opt_path) ||
3569 !realloc_lines(view, view->line_size + 1) ||
3570 !add_line_text(view, buf, LINE_DEFAULT))
3573 /* Insert "link" to parent directory. */
3575 if (!string_format(buf, TREE_UP_FORMAT, view->ref) ||
3576 !realloc_lines(view, view->line_size + 1) ||
3577 !add_line_text(view, buf, LINE_TREE_DIR))
3582 /* Strip the path part ... */
3584 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
3585 size_t striplen = strlen(opt_path);
3586 char *path = text + SIZEOF_TREE_ATTR;
3588 if (pathlen > striplen)
3589 memmove(path, path + striplen,
3590 pathlen - striplen + 1);
3593 /* Skip "Directory ..." and ".." line. */
3594 for (pos = 1 + !!*opt_path; pos < view->lines; pos++) {
3595 struct line *line = &view->line[pos];
3596 const char *path1 = tree_path(line);
3597 char *path2 = text + SIZEOF_TREE_ATTR;
3598 int cmp = tree_compare_entry(line->type, path1, type, path2);
3603 text = strdup(text);
3607 if (view->lines > pos)
3608 memmove(&view->line[pos + 1], &view->line[pos],
3609 (view->lines - pos) * sizeof(*line));
3611 line = &view->line[pos];
3618 if (!add_line_text(view, text, type))
3621 if (tree_lineno > view->lineno) {
3622 view->lineno = tree_lineno;
3630 tree_request(struct view *view, enum request request, struct line *line)
3632 enum open_flags flags;
3635 case REQ_VIEW_BLAME:
3636 if (line->type != LINE_TREE_FILE) {
3637 report("Blame only supported for files");
3641 string_copy(opt_ref, view->vid);
3645 if (line->type != LINE_TREE_FILE) {
3646 report("Edit only supported for files");
3647 } else if (!is_head_commit(view->vid)) {
3648 report("Edit only supported for files in the current work tree");
3650 open_editor(TRUE, opt_file);
3654 case REQ_TREE_PARENT:
3656 /* quit view if at top of tree */
3657 return REQ_VIEW_CLOSE;
3660 line = &view->line[1];
3670 /* Cleanup the stack if the tree view is at a different tree. */
3671 while (!*opt_path && tree_stack)
3672 pop_tree_stack_entry();
3674 switch (line->type) {
3676 /* Depending on whether it is a subdir or parent (updir?) link
3677 * mangle the path buffer. */
3678 if (line == &view->line[1] && *opt_path) {
3679 pop_tree_stack_entry();
3682 const char *basename = tree_path(line);
3684 push_tree_stack_entry(basename, view->lineno);
3687 /* Trees and subtrees share the same ID, so they are not not
3688 * unique like blobs. */
3689 flags = OPEN_RELOAD;
3690 request = REQ_VIEW_TREE;
3693 case LINE_TREE_FILE:
3694 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
3695 request = REQ_VIEW_BLOB;
3702 open_view(view, request, flags);
3703 if (request == REQ_VIEW_TREE) {
3704 view->lineno = tree_lineno;
3711 tree_select(struct view *view, struct line *line)
3713 char *text = (char *)line->data + STRING_SIZE("100644 blob ");
3715 if (line->type == LINE_TREE_FILE) {
3716 string_copy_rev(ref_blob, text);
3717 string_format(opt_file, "%s%s", opt_path, tree_path(line));
3719 } else if (line->type != LINE_TREE_DIR) {
3723 string_copy_rev(view->ref, text);
3726 static const char *tree_argv[SIZEOF_ARG] = {
3727 "git", "ls-tree", "%(commit)", "%(directory)", NULL
3730 static struct view_ops tree_ops = {
3742 blob_read(struct view *view, char *line)
3746 return add_line_text(view, line, LINE_DEFAULT) != NULL;
3749 static const char *blob_argv[SIZEOF_ARG] = {
3750 "git", "cat-file", "blob", "%(blob)", NULL
3753 static struct view_ops blob_ops = {
3767 * Loading the blame view is a two phase job:
3769 * 1. File content is read either using opt_file from the
3770 * filesystem or using git-cat-file.
3771 * 2. Then blame information is incrementally added by
3772 * reading output from git-blame.
3775 static const char *blame_head_argv[] = {
3776 "git", "blame", "--incremental", "--", "%(file)", NULL
3779 static const char *blame_ref_argv[] = {
3780 "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
3783 static const char *blame_cat_file_argv[] = {
3784 "git", "cat-file", "blob", "%(ref):%(file)", NULL
3787 struct blame_commit {
3788 char id[SIZEOF_REV]; /* SHA1 ID. */
3789 char title[128]; /* First line of the commit message. */
3790 char author[75]; /* Author of the commit. */
3791 struct tm time; /* Date from the author ident. */
3792 char filename[128]; /* Name of file. */
3796 struct blame_commit *commit;
3801 blame_open(struct view *view)
3803 if (*opt_ref || !init_io_fd(&view->io, fopen(opt_file, "r"))) {
3804 if (!run_io_rd(&view->io, blame_cat_file_argv, FORMAT_ALL))
3808 setup_update(view, opt_file);
3809 string_format(view->ref, "%s ...", opt_file);
3814 static struct blame_commit *
3815 get_blame_commit(struct view *view, const char *id)
3819 for (i = 0; i < view->lines; i++) {
3820 struct blame *blame = view->line[i].data;
3825 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
3826 return blame->commit;
3830 struct blame_commit *commit = calloc(1, sizeof(*commit));
3833 string_ncopy(commit->id, id, SIZEOF_REV);
3839 parse_number(const char **posref, size_t *number, size_t min, size_t max)
3841 const char *pos = *posref;
3844 pos = strchr(pos + 1, ' ');
3845 if (!pos || !isdigit(pos[1]))
3847 *number = atoi(pos + 1);
3848 if (*number < min || *number > max)
3855 static struct blame_commit *
3856 parse_blame_commit(struct view *view, const char *text, int *blamed)
3858 struct blame_commit *commit;
3859 struct blame *blame;
3860 const char *pos = text + SIZEOF_REV - 1;
3864 if (strlen(text) <= SIZEOF_REV || *pos != ' ')
3867 if (!parse_number(&pos, &lineno, 1, view->lines) ||
3868 !parse_number(&pos, &group, 1, view->lines - lineno + 1))
3871 commit = get_blame_commit(view, text);
3877 struct line *line = &view->line[lineno + group - 1];
3880 blame->commit = commit;
3888 blame_read_file(struct view *view, const char *line, bool *read_file)
3891 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
3894 if (view->lines == 0 && !view->parent)
3895 die("No blame exist for %s", view->vid);
3897 if (view->lines == 0 || !run_io_rd(&io, argv, FORMAT_ALL)) {
3898 report("Failed to load blame data");
3902 done_io(view->pipe);
3908 size_t linelen = strlen(line);
3909 struct blame *blame = malloc(sizeof(*blame) + linelen);
3911 blame->commit = NULL;
3912 strncpy(blame->text, line, linelen);
3913 blame->text[linelen] = 0;
3914 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
3919 match_blame_header(const char *name, char **line)
3921 size_t namelen = strlen(name);
3922 bool matched = !strncmp(name, *line, namelen);
3931 blame_read(struct view *view, char *line)
3933 static struct blame_commit *commit = NULL;
3934 static int blamed = 0;
3935 static time_t author_time;
3936 static bool read_file = TRUE;
3939 return blame_read_file(view, line, &read_file);
3946 string_format(view->ref, "%s", view->vid);
3947 if (view_is_displayed(view)) {
3948 update_view_title(view);
3949 redraw_view_from(view, 0);
3955 commit = parse_blame_commit(view, line, &blamed);
3956 string_format(view->ref, "%s %2d%%", view->vid,
3957 blamed * 100 / view->lines);
3959 } else if (match_blame_header("author ", &line)) {
3960 string_ncopy(commit->author, line, strlen(line));
3962 } else if (match_blame_header("author-time ", &line)) {
3963 author_time = (time_t) atol(line);
3965 } else if (match_blame_header("author-tz ", &line)) {
3968 tz = ('0' - line[1]) * 60 * 60 * 10;
3969 tz += ('0' - line[2]) * 60 * 60;
3970 tz += ('0' - line[3]) * 60;
3971 tz += ('0' - line[4]) * 60;
3977 gmtime_r(&author_time, &commit->time);
3979 } else if (match_blame_header("summary ", &line)) {
3980 string_ncopy(commit->title, line, strlen(line));
3982 } else if (match_blame_header("filename ", &line)) {
3983 string_ncopy(commit->filename, line, strlen(line));
3991 blame_draw(struct view *view, struct line *line, unsigned int lineno)
3993 struct blame *blame = line->data;
3994 struct tm *time = NULL;
3995 const char *id = NULL, *author = NULL;
3997 if (blame->commit && *blame->commit->filename) {
3998 id = blame->commit->id;
3999 author = blame->commit->author;
4000 time = &blame->commit->time;
4003 if (opt_date && draw_date(view, time))
4007 draw_field(view, LINE_MAIN_AUTHOR, author, opt_author_cols, TRUE))
4010 if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
4013 if (draw_lineno(view, lineno))
4016 draw_text(view, LINE_DEFAULT, blame->text, TRUE);
4021 blame_request(struct view *view, enum request request, struct line *line)
4023 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4024 struct blame *blame = line->data;
4027 case REQ_VIEW_BLAME:
4028 if (!blame->commit || !strcmp(blame->commit->id, NULL_ID)) {
4029 report("Commit ID unknown");
4032 string_copy(opt_ref, blame->commit->id);
4033 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4037 if (!blame->commit) {
4038 report("No commit loaded yet");
4042 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
4043 !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
4046 if (!strcmp(blame->commit->id, NULL_ID)) {
4047 struct view *diff = VIEW(REQ_VIEW_DIFF);
4048 const char *diff_index_argv[] = {
4049 "git", "diff-index", "--root", "--cached",
4050 "--patch-with-stat", "-C", "-M",
4051 "HEAD", "--", view->vid, NULL
4054 if (!prepare_update(diff, diff_index_argv, NULL, FORMAT_DASH)) {
4055 report("Failed to allocate diff command");
4058 flags |= OPEN_PREPARED;
4061 open_view(view, REQ_VIEW_DIFF, flags);
4072 blame_grep(struct view *view, struct line *line)
4074 struct blame *blame = line->data;
4075 struct blame_commit *commit = blame->commit;
4078 #define MATCH(text, on) \
4079 (on && *text && regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4082 char buf[DATE_COLS + 1];
4084 if (MATCH(commit->title, 1) ||
4085 MATCH(commit->author, opt_author) ||
4086 MATCH(commit->id, opt_date))
4089 if (strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time) &&
4094 return MATCH(blame->text, 1);
4100 blame_select(struct view *view, struct line *line)
4102 struct blame *blame = line->data;
4103 struct blame_commit *commit = blame->commit;
4108 if (!strcmp(commit->id, NULL_ID))
4109 string_ncopy(ref_commit, "HEAD", 4);
4111 string_copy_rev(ref_commit, commit->id);
4114 static struct view_ops blame_ops = {
4133 char rev[SIZEOF_REV];
4134 char name[SIZEOF_STR];
4138 char rev[SIZEOF_REV];
4139 char name[SIZEOF_STR];
4143 static char status_onbranch[SIZEOF_STR];
4144 static struct status stage_status;
4145 static enum line_type stage_line_type;
4146 static size_t stage_chunks;
4147 static int *stage_chunk;
4149 /* This should work even for the "On branch" line. */
4151 status_has_none(struct view *view, struct line *line)
4153 return line < view->line + view->lines && !line[1].data;
4156 /* Get fields from the diff line:
4157 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
4160 status_get_diff(struct status *file, const char *buf, size_t bufsize)
4162 const char *old_mode = buf + 1;
4163 const char *new_mode = buf + 8;
4164 const char *old_rev = buf + 15;
4165 const char *new_rev = buf + 56;
4166 const char *status = buf + 97;
4169 old_mode[-1] != ':' ||
4170 new_mode[-1] != ' ' ||
4171 old_rev[-1] != ' ' ||
4172 new_rev[-1] != ' ' ||
4176 file->status = *status;
4178 string_copy_rev(file->old.rev, old_rev);
4179 string_copy_rev(file->new.rev, new_rev);
4181 file->old.mode = strtoul(old_mode, NULL, 8);
4182 file->new.mode = strtoul(new_mode, NULL, 8);
4184 file->old.name[0] = file->new.name[0] = 0;
4190 status_run(struct view *view, const char cmd[], char status, enum line_type type)
4192 struct status *file = NULL;
4193 struct status *unmerged = NULL;
4194 char buf[SIZEOF_STR * 4];
4198 pipe = popen(cmd, "r");
4202 add_line_data(view, NULL, type);
4204 while (!feof(pipe) && !ferror(pipe)) {
4208 readsize = fread(buf + bufsize, 1, sizeof(buf) - bufsize, pipe);
4211 bufsize += readsize;
4213 /* Process while we have NUL chars. */
4214 while ((sep = memchr(buf, 0, bufsize))) {
4215 size_t sepsize = sep - buf + 1;
4218 if (!realloc_lines(view, view->line_size + 1))
4221 file = calloc(1, sizeof(*file));
4225 add_line_data(view, file, type);
4228 /* Parse diff info part. */
4230 file->status = status;
4232 string_copy(file->old.rev, NULL_ID);
4234 } else if (!file->status) {
4235 if (!status_get_diff(file, buf, sepsize))
4239 memmove(buf, sep + 1, bufsize);
4241 sep = memchr(buf, 0, bufsize);
4244 sepsize = sep - buf + 1;
4246 /* Collapse all 'M'odified entries that
4247 * follow a associated 'U'nmerged entry.
4249 if (file->status == 'U') {
4252 } else if (unmerged) {
4253 int collapse = !strcmp(buf, unmerged->new.name);
4264 /* Grab the old name for rename/copy. */
4265 if (!*file->old.name &&
4266 (file->status == 'R' || file->status == 'C')) {
4267 sepsize = sep - buf + 1;
4268 string_ncopy(file->old.name, buf, sepsize);
4270 memmove(buf, sep + 1, bufsize);
4272 sep = memchr(buf, 0, bufsize);
4275 sepsize = sep - buf + 1;
4278 /* git-ls-files just delivers a NUL separated
4279 * list of file names similar to the second half
4280 * of the git-diff-* output. */
4281 string_ncopy(file->new.name, buf, sepsize);
4282 if (!*file->old.name)
4283 string_copy(file->old.name, file->new.name);
4285 memmove(buf, sep + 1, bufsize);
4296 if (!view->line[view->lines - 1].data)
4297 add_line_data(view, NULL, LINE_STAT_NONE);
4303 /* Don't show unmerged entries in the staged section. */
4304 #define STATUS_DIFF_INDEX_CMD "git diff-index -z --diff-filter=ACDMRTXB --cached -M HEAD"
4305 #define STATUS_DIFF_FILES_CMD "git diff-files -z"
4306 #define STATUS_LIST_OTHER_CMD \
4307 "git ls-files -z --others --exclude-standard"
4308 #define STATUS_LIST_NO_HEAD_CMD \
4309 "git ls-files -z --cached --exclude-standard"
4311 static const char *update_index_argv[] = {
4312 "git", "update-index", "-q", "--unmerged", "--refresh", NULL
4315 /* First parse staged info using git-diff-index(1), then parse unstaged
4316 * info using git-diff-files(1), and finally untracked files using
4317 * git-ls-files(1). */
4319 status_open(struct view *view)
4321 unsigned long prev_lineno = view->lineno;
4325 if (!realloc_lines(view, view->line_size + 7))
4328 add_line_data(view, NULL, LINE_STAT_HEAD);
4329 if (is_initial_commit())
4330 string_copy(status_onbranch, "Initial commit");
4331 else if (!*opt_head)
4332 string_copy(status_onbranch, "Not currently on any branch");
4333 else if (!string_format(status_onbranch, "On branch %s", opt_head))
4336 system("git update-index -q --refresh >/dev/null 2>/dev/null");
4338 if (is_initial_commit()) {
4339 if (!status_run(view, STATUS_LIST_NO_HEAD_CMD, 'A', LINE_STAT_STAGED))
4341 } else if (!status_run(view, STATUS_DIFF_INDEX_CMD, 0, LINE_STAT_STAGED)) {
4345 if (!status_run(view, STATUS_DIFF_FILES_CMD, 0, LINE_STAT_UNSTAGED) ||
4346 !status_run(view, STATUS_LIST_OTHER_CMD, '?', LINE_STAT_UNTRACKED))
4349 /* If all went well restore the previous line number to stay in
4350 * the context or select a line with something that can be
4352 if (prev_lineno >= view->lines)
4353 prev_lineno = view->lines - 1;
4354 while (prev_lineno < view->lines && !view->line[prev_lineno].data)
4356 while (prev_lineno > 0 && !view->line[prev_lineno].data)
4359 /* If the above fails, always skip the "On branch" line. */
4360 if (prev_lineno < view->lines)
4361 view->lineno = prev_lineno;
4365 if (view->lineno < view->offset)
4366 view->offset = view->lineno;
4367 else if (view->offset + view->height <= view->lineno)
4368 view->offset = view->lineno - view->height + 1;
4374 status_draw(struct view *view, struct line *line, unsigned int lineno)
4376 struct status *status = line->data;
4377 enum line_type type;
4381 switch (line->type) {
4382 case LINE_STAT_STAGED:
4383 type = LINE_STAT_SECTION;
4384 text = "Changes to be committed:";
4387 case LINE_STAT_UNSTAGED:
4388 type = LINE_STAT_SECTION;
4389 text = "Changed but not updated:";
4392 case LINE_STAT_UNTRACKED:
4393 type = LINE_STAT_SECTION;
4394 text = "Untracked files:";
4397 case LINE_STAT_NONE:
4398 type = LINE_DEFAULT;
4399 text = " (no files)";
4402 case LINE_STAT_HEAD:
4403 type = LINE_STAT_HEAD;
4404 text = status_onbranch;
4411 static char buf[] = { '?', ' ', ' ', ' ', 0 };
4413 buf[0] = status->status;
4414 if (draw_text(view, line->type, buf, TRUE))
4416 type = LINE_DEFAULT;
4417 text = status->new.name;
4420 draw_text(view, type, text, TRUE);
4425 status_enter(struct view *view, struct line *line)
4427 struct status *status = line->data;
4428 const char *oldpath = status ? status->old.name : NULL;
4429 /* Diffs for unmerged entries are empty when passing the new
4430 * path, so leave it empty. */
4431 const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
4433 enum open_flags split;
4434 struct view *stage = VIEW(REQ_VIEW_STAGE);
4436 if (line->type == LINE_STAT_NONE ||
4437 (!status && line[1].type == LINE_STAT_NONE)) {
4438 report("No file to diff");
4442 switch (line->type) {
4443 case LINE_STAT_STAGED:
4444 if (is_initial_commit()) {
4445 const char *no_head_diff_argv[] = {
4446 "git", "diff", "--no-color", "--patch-with-stat",
4447 "--", "/dev/null", newpath, NULL
4450 if (!prepare_update(stage, no_head_diff_argv, opt_cdup, FORMAT_DASH))
4453 const char *index_show_argv[] = {
4454 "git", "diff-index", "--root", "--patch-with-stat",
4455 "-C", "-M", "--cached", "HEAD", "--",
4456 oldpath, newpath, NULL
4459 if (!prepare_update(stage, index_show_argv, opt_cdup, FORMAT_DASH))
4464 info = "Staged changes to %s";
4466 info = "Staged changes";
4469 case LINE_STAT_UNSTAGED:
4471 const char *files_show_argv[] = {
4472 "git", "diff-files", "--root", "--patch-with-stat",
4473 "-C", "-M", "--", oldpath, newpath, NULL
4476 if (!prepare_update(stage, files_show_argv, opt_cdup, FORMAT_DASH))
4479 info = "Unstaged changes to %s";
4481 info = "Unstaged changes";
4484 case LINE_STAT_UNTRACKED:
4486 report("No file to show");
4490 if (!suffixcmp(status->new.name, -1, "/")) {
4491 report("Cannot display a directory");
4495 if (!prepare_update_file(stage, newpath))
4497 info = "Untracked file %s";
4500 case LINE_STAT_HEAD:
4504 die("line type %d not handled in switch", line->type);
4507 split = view_is_displayed(view) ? OPEN_SPLIT : 0;
4508 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH | split);
4509 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
4511 stage_status = *status;
4513 memset(&stage_status, 0, sizeof(stage_status));
4516 stage_line_type = line->type;
4518 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
4525 status_exists(struct status *status, enum line_type type)
4527 struct view *view = VIEW(REQ_VIEW_STATUS);
4530 for (line = view->line; line < view->line + view->lines; line++) {
4531 struct status *pos = line->data;
4533 if (line->type == type && pos &&
4534 !strcmp(status->new.name, pos->new.name))
4543 status_update_prepare(enum line_type type)
4545 char cmd[SIZEOF_STR];
4549 type != LINE_STAT_UNTRACKED &&
4550 !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
4554 case LINE_STAT_STAGED:
4555 string_add(cmd, cmdsize, "git update-index -z --index-info");
4558 case LINE_STAT_UNSTAGED:
4559 case LINE_STAT_UNTRACKED:
4560 string_add(cmd, cmdsize, "git update-index -z --add --remove --stdin");
4564 die("line type %d not handled in switch", type);
4567 return popen(cmd, "w");
4571 status_update_write(FILE *pipe, struct status *status, enum line_type type)
4573 char buf[SIZEOF_STR];
4578 case LINE_STAT_STAGED:
4579 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
4582 status->old.name, 0))
4586 case LINE_STAT_UNSTAGED:
4587 case LINE_STAT_UNTRACKED:
4588 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
4593 die("line type %d not handled in switch", type);
4596 while (!ferror(pipe) && written < bufsize) {
4597 written += fwrite(buf + written, 1, bufsize - written, pipe);
4600 return written == bufsize;
4604 status_update_file(struct status *status, enum line_type type)
4606 FILE *pipe = status_update_prepare(type);
4612 result = status_update_write(pipe, status, type);
4618 status_update_files(struct view *view, struct line *line)
4620 FILE *pipe = status_update_prepare(line->type);
4622 struct line *pos = view->line + view->lines;
4629 for (pos = line; pos < view->line + view->lines && pos->data; pos++)
4632 for (file = 0, done = 0; result && file < files; line++, file++) {
4633 int almost_done = file * 100 / files;
4635 if (almost_done > done) {
4637 string_format(view->ref, "updating file %u of %u (%d%% done)",
4639 update_view_title(view);
4641 result = status_update_write(pipe, line->data, line->type);
4649 status_update(struct view *view)
4651 struct line *line = &view->line[view->lineno];
4653 assert(view->lines);
4656 /* This should work even for the "On branch" line. */
4657 if (line < view->line + view->lines && !line[1].data) {
4658 report("Nothing to update");
4662 if (!status_update_files(view, line + 1)) {
4663 report("Failed to update file status");
4667 } else if (!status_update_file(line->data, line->type)) {
4668 report("Failed to update file status");
4676 status_revert(struct status *status, enum line_type type, bool has_none)
4678 if (!status || type != LINE_STAT_UNSTAGED) {
4679 if (type == LINE_STAT_STAGED) {
4680 report("Cannot revert changes to staged files");
4681 } else if (type == LINE_STAT_UNTRACKED) {
4682 report("Cannot revert changes to untracked files");
4683 } else if (has_none) {
4684 report("Nothing to revert");
4686 report("Cannot revert changes to multiple files");
4691 const char *checkout_argv[] = {
4692 "git", "checkout", "--", status->old.name, NULL
4695 if (!prompt_yesno("Are you sure you want to overwrite any changes?"))
4697 return run_io_fg(checkout_argv, opt_cdup);
4702 status_request(struct view *view, enum request request, struct line *line)
4704 struct status *status = line->data;
4707 case REQ_STATUS_UPDATE:
4708 if (!status_update(view))
4712 case REQ_STATUS_REVERT:
4713 if (!status_revert(status, line->type, status_has_none(view, line)))
4717 case REQ_STATUS_MERGE:
4718 if (!status || status->status != 'U') {
4719 report("Merging only possible for files with unmerged status ('U').");
4722 open_mergetool(status->new.name);
4728 if (status->status == 'D') {
4729 report("File has been deleted.");
4733 open_editor(status->status != '?', status->new.name);
4736 case REQ_VIEW_BLAME:
4738 string_copy(opt_file, status->new.name);
4744 /* After returning the status view has been split to
4745 * show the stage view. No further reloading is
4747 status_enter(view, line);
4751 /* Simply reload the view. */
4758 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
4764 status_select(struct view *view, struct line *line)
4766 struct status *status = line->data;
4767 char file[SIZEOF_STR] = "all files";
4771 if (status && !string_format(file, "'%s'", status->new.name))
4774 if (!status && line[1].type == LINE_STAT_NONE)
4777 switch (line->type) {
4778 case LINE_STAT_STAGED:
4779 text = "Press %s to unstage %s for commit";
4782 case LINE_STAT_UNSTAGED:
4783 text = "Press %s to stage %s for commit";
4786 case LINE_STAT_UNTRACKED:
4787 text = "Press %s to stage %s for addition";
4790 case LINE_STAT_HEAD:
4791 case LINE_STAT_NONE:
4792 text = "Nothing to update";
4796 die("line type %d not handled in switch", line->type);
4799 if (status && status->status == 'U') {
4800 text = "Press %s to resolve conflict in %s";
4801 key = get_key(REQ_STATUS_MERGE);
4804 key = get_key(REQ_STATUS_UPDATE);
4807 string_format(view->ref, text, key, file);
4811 status_grep(struct view *view, struct line *line)
4813 struct status *status = line->data;
4814 enum { S_STATUS, S_NAME, S_END } state;
4821 for (state = S_STATUS; state < S_END; state++) {
4825 case S_NAME: text = status->new.name; break;
4827 buf[0] = status->status;
4835 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4842 static struct view_ops status_ops = {
4855 stage_diff_write(struct io *io, struct line *line, struct line *end)
4857 while (line < end) {
4858 if (!io_write(io, line->data, strlen(line->data)) ||
4859 !io_write(io, "\n", 1))
4862 if (line->type == LINE_DIFF_CHUNK ||
4863 line->type == LINE_DIFF_HEADER)
4870 static struct line *
4871 stage_diff_find(struct view *view, struct line *line, enum line_type type)
4873 for (; view->line < line; line--)
4874 if (line->type == type)
4881 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
4883 const char *apply_argv[SIZEOF_ARG] = {
4884 "git", "apply", "--whitespace=nowarn", NULL
4886 struct line *diff_hdr;
4890 diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
4895 apply_argv[argc++] = "--cached";
4896 if (revert || stage_line_type == LINE_STAT_STAGED)
4897 apply_argv[argc++] = "-R";
4898 apply_argv[argc++] = "-";
4899 apply_argv[argc++] = NULL;
4900 if (!run_io(&io, apply_argv, opt_cdup, IO_WR))
4903 if (!stage_diff_write(&io, diff_hdr, chunk) ||
4904 !stage_diff_write(&io, chunk, view->line + view->lines))
4908 run_io_bg(update_index_argv);
4910 return chunk ? TRUE : FALSE;
4914 stage_update(struct view *view, struct line *line)
4916 struct line *chunk = NULL;
4918 if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
4919 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
4922 if (!stage_apply_chunk(view, chunk, FALSE)) {
4923 report("Failed to apply chunk");
4927 } else if (!stage_status.status) {
4928 view = VIEW(REQ_VIEW_STATUS);
4930 for (line = view->line; line < view->line + view->lines; line++)
4931 if (line->type == stage_line_type)
4934 if (!status_update_files(view, line + 1)) {
4935 report("Failed to update files");
4939 } else if (!status_update_file(&stage_status, stage_line_type)) {
4940 report("Failed to update file");
4948 stage_revert(struct view *view, struct line *line)
4950 struct line *chunk = NULL;
4952 if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
4953 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
4956 if (!prompt_yesno("Are you sure you want to revert changes?"))
4959 if (!stage_apply_chunk(view, chunk, TRUE)) {
4960 report("Failed to revert chunk");
4966 return status_revert(stage_status.status ? &stage_status : NULL,
4967 stage_line_type, FALSE);
4973 stage_next(struct view *view, struct line *line)
4977 if (!stage_chunks) {
4978 static size_t alloc = 0;
4981 for (line = view->line; line < view->line + view->lines; line++) {
4982 if (line->type != LINE_DIFF_CHUNK)
4985 tmp = realloc_items(stage_chunk, &alloc,
4986 stage_chunks, sizeof(*tmp));
4988 report("Allocation failure");
4993 stage_chunk[stage_chunks++] = line - view->line;
4997 for (i = 0; i < stage_chunks; i++) {
4998 if (stage_chunk[i] > view->lineno) {
4999 do_scroll_view(view, stage_chunk[i] - view->lineno);
5000 report("Chunk %d of %d", i + 1, stage_chunks);
5005 report("No next chunk found");
5009 stage_request(struct view *view, enum request request, struct line *line)
5012 case REQ_STATUS_UPDATE:
5013 if (!stage_update(view, line))
5017 case REQ_STATUS_REVERT:
5018 if (!stage_revert(view, line))
5022 case REQ_STAGE_NEXT:
5023 if (stage_line_type == LINE_STAT_UNTRACKED) {
5024 report("File is untracked; press %s to add",
5025 get_key(REQ_STATUS_UPDATE));
5028 stage_next(view, line);
5032 if (!stage_status.new.name[0])
5034 if (stage_status.status == 'D') {
5035 report("File has been deleted.");
5039 open_editor(stage_status.status != '?', stage_status.new.name);
5043 /* Reload everything ... */
5046 case REQ_VIEW_BLAME:
5047 if (stage_status.new.name[0]) {
5048 string_copy(opt_file, stage_status.new.name);
5054 return pager_request(view, request, line);
5060 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD | OPEN_NOMAXIMIZE);
5062 /* Check whether the staged entry still exists, and close the
5063 * stage view if it doesn't. */
5064 if (!status_exists(&stage_status, stage_line_type))
5065 return REQ_VIEW_CLOSE;
5067 if (stage_line_type == LINE_STAT_UNTRACKED) {
5068 if (!suffixcmp(stage_status.new.name, -1, "/")) {
5069 report("Cannot display a directory");
5073 if (!prepare_update_file(view, stage_status.new.name)) {
5074 report("Failed to open file: %s", strerror(errno));
5078 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
5083 static struct view_ops stage_ops = {
5100 char id[SIZEOF_REV]; /* SHA1 ID. */
5101 char title[128]; /* First line of the commit message. */
5102 char author[75]; /* Author of the commit. */
5103 struct tm time; /* Date from the author ident. */
5104 struct ref **refs; /* Repository references. */
5105 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
5106 size_t graph_size; /* The width of the graph array. */
5107 bool has_parents; /* Rewritten --parents seen. */
5110 /* Size of rev graph with no "padding" columns */
5111 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
5114 struct rev_graph *prev, *next, *parents;
5115 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
5117 struct commit *commit;
5119 unsigned int boundary:1;
5122 /* Parents of the commit being visualized. */
5123 static struct rev_graph graph_parents[4];
5125 /* The current stack of revisions on the graph. */
5126 static struct rev_graph graph_stacks[4] = {
5127 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
5128 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
5129 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
5130 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
5134 graph_parent_is_merge(struct rev_graph *graph)
5136 return graph->parents->size > 1;
5140 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
5142 struct commit *commit = graph->commit;
5144 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
5145 commit->graph[commit->graph_size++] = symbol;
5149 clear_rev_graph(struct rev_graph *graph)
5151 graph->boundary = 0;
5152 graph->size = graph->pos = 0;
5153 graph->commit = NULL;
5154 memset(graph->parents, 0, sizeof(*graph->parents));
5158 done_rev_graph(struct rev_graph *graph)
5160 if (graph_parent_is_merge(graph) &&
5161 graph->pos < graph->size - 1 &&
5162 graph->next->size == graph->size + graph->parents->size - 1) {
5163 size_t i = graph->pos + graph->parents->size - 1;
5165 graph->commit->graph_size = i * 2;
5166 while (i < graph->next->size - 1) {
5167 append_to_rev_graph(graph, ' ');
5168 append_to_rev_graph(graph, '\\');
5173 clear_rev_graph(graph);
5177 push_rev_graph(struct rev_graph *graph, const char *parent)
5181 /* "Collapse" duplicate parents lines.
5183 * FIXME: This needs to also update update the drawn graph but
5184 * for now it just serves as a method for pruning graph lines. */
5185 for (i = 0; i < graph->size; i++)
5186 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
5189 if (graph->size < SIZEOF_REVITEMS) {
5190 string_copy_rev(graph->rev[graph->size++], parent);
5195 get_rev_graph_symbol(struct rev_graph *graph)
5199 if (graph->boundary)
5200 symbol = REVGRAPH_BOUND;
5201 else if (graph->parents->size == 0)
5202 symbol = REVGRAPH_INIT;
5203 else if (graph_parent_is_merge(graph))
5204 symbol = REVGRAPH_MERGE;
5205 else if (graph->pos >= graph->size)
5206 symbol = REVGRAPH_BRANCH;
5208 symbol = REVGRAPH_COMMIT;
5214 draw_rev_graph(struct rev_graph *graph)
5217 chtype separator, line;
5219 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
5220 static struct rev_filler fillers[] = {
5226 chtype symbol = get_rev_graph_symbol(graph);
5227 struct rev_filler *filler;
5230 if (opt_line_graphics)
5231 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
5233 filler = &fillers[DEFAULT];
5235 for (i = 0; i < graph->pos; i++) {
5236 append_to_rev_graph(graph, filler->line);
5237 if (graph_parent_is_merge(graph->prev) &&
5238 graph->prev->pos == i)
5239 filler = &fillers[RSHARP];
5241 append_to_rev_graph(graph, filler->separator);
5244 /* Place the symbol for this revision. */
5245 append_to_rev_graph(graph, symbol);
5247 if (graph->prev->size > graph->size)
5248 filler = &fillers[RDIAG];
5250 filler = &fillers[DEFAULT];
5254 for (; i < graph->size; i++) {
5255 append_to_rev_graph(graph, filler->separator);
5256 append_to_rev_graph(graph, filler->line);
5257 if (graph_parent_is_merge(graph->prev) &&
5258 i < graph->prev->pos + graph->parents->size)
5259 filler = &fillers[RSHARP];
5260 if (graph->prev->size > graph->size)
5261 filler = &fillers[LDIAG];
5264 if (graph->prev->size > graph->size) {
5265 append_to_rev_graph(graph, filler->separator);
5266 if (filler->line != ' ')
5267 append_to_rev_graph(graph, filler->line);
5271 /* Prepare the next rev graph */
5273 prepare_rev_graph(struct rev_graph *graph)
5277 /* First, traverse all lines of revisions up to the active one. */
5278 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
5279 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
5282 push_rev_graph(graph->next, graph->rev[graph->pos]);
5285 /* Interleave the new revision parent(s). */
5286 for (i = 0; !graph->boundary && i < graph->parents->size; i++)
5287 push_rev_graph(graph->next, graph->parents->rev[i]);
5289 /* Lastly, put any remaining revisions. */
5290 for (i = graph->pos + 1; i < graph->size; i++)
5291 push_rev_graph(graph->next, graph->rev[i]);
5295 update_rev_graph(struct rev_graph *graph)
5297 /* If this is the finalizing update ... */
5299 prepare_rev_graph(graph);
5301 /* Graph visualization needs a one rev look-ahead,
5302 * so the first update doesn't visualize anything. */
5303 if (!graph->prev->commit)
5306 draw_rev_graph(graph->prev);
5307 done_rev_graph(graph->prev->prev);
5315 static const char *main_argv[SIZEOF_ARG] = {
5316 "git", "log", "--no-color", "--pretty=raw", "--parents",
5317 "--topo-order", "%(head)", NULL
5321 main_draw(struct view *view, struct line *line, unsigned int lineno)
5323 struct commit *commit = line->data;
5325 if (!*commit->author)
5328 if (opt_date && draw_date(view, &commit->time))
5332 draw_field(view, LINE_MAIN_AUTHOR, commit->author, opt_author_cols, TRUE))
5335 if (opt_rev_graph && commit->graph_size &&
5336 draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
5339 if (opt_show_refs && commit->refs) {
5343 enum line_type type;
5345 if (commit->refs[i]->head)
5346 type = LINE_MAIN_HEAD;
5347 else if (commit->refs[i]->ltag)
5348 type = LINE_MAIN_LOCAL_TAG;
5349 else if (commit->refs[i]->tag)
5350 type = LINE_MAIN_TAG;
5351 else if (commit->refs[i]->tracked)
5352 type = LINE_MAIN_TRACKED;
5353 else if (commit->refs[i]->remote)
5354 type = LINE_MAIN_REMOTE;
5356 type = LINE_MAIN_REF;
5358 if (draw_text(view, type, "[", TRUE) ||
5359 draw_text(view, type, commit->refs[i]->name, TRUE) ||
5360 draw_text(view, type, "]", TRUE))
5363 if (draw_text(view, LINE_DEFAULT, " ", TRUE))
5365 } while (commit->refs[i++]->next);
5368 draw_text(view, LINE_DEFAULT, commit->title, TRUE);
5372 /* Reads git log --pretty=raw output and parses it into the commit struct. */
5374 main_read(struct view *view, char *line)
5376 static struct rev_graph *graph = graph_stacks;
5377 enum line_type type;
5378 struct commit *commit;
5383 if (!view->lines && !view->parent)
5384 die("No revisions match the given arguments.");
5385 if (view->lines > 0) {
5386 commit = view->line[view->lines - 1].data;
5387 if (!*commit->author) {
5390 graph->commit = NULL;
5393 update_rev_graph(graph);
5395 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
5396 clear_rev_graph(&graph_stacks[i]);
5400 type = get_line_type(line);
5401 if (type == LINE_COMMIT) {
5402 commit = calloc(1, sizeof(struct commit));
5406 line += STRING_SIZE("commit ");
5408 graph->boundary = 1;
5412 string_copy_rev(commit->id, line);
5413 commit->refs = get_refs(commit->id);
5414 graph->commit = commit;
5415 add_line_data(view, commit, LINE_MAIN_COMMIT);
5417 while ((line = strchr(line, ' '))) {
5419 push_rev_graph(graph->parents, line);
5420 commit->has_parents = TRUE;
5427 commit = view->line[view->lines - 1].data;
5431 if (commit->has_parents)
5433 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
5438 /* Parse author lines where the name may be empty:
5439 * author <email@address.tld> 1138474660 +0100
5441 char *ident = line + STRING_SIZE("author ");
5442 char *nameend = strchr(ident, '<');
5443 char *emailend = strchr(ident, '>');
5445 if (!nameend || !emailend)
5448 update_rev_graph(graph);
5449 graph = graph->next;
5451 *nameend = *emailend = 0;
5452 ident = chomp_string(ident);
5454 ident = chomp_string(nameend + 1);
5459 string_ncopy(commit->author, ident, strlen(ident));
5461 /* Parse epoch and timezone */
5462 if (emailend[1] == ' ') {
5463 char *secs = emailend + 2;
5464 char *zone = strchr(secs, ' ');
5465 time_t time = (time_t) atol(secs);
5467 if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
5471 tz = ('0' - zone[1]) * 60 * 60 * 10;
5472 tz += ('0' - zone[2]) * 60 * 60;
5473 tz += ('0' - zone[3]) * 60;
5474 tz += ('0' - zone[4]) * 60;
5482 gmtime_r(&time, &commit->time);
5487 /* Fill in the commit title if it has not already been set. */
5488 if (commit->title[0])
5491 /* Require titles to start with a non-space character at the
5492 * offset used by git log. */
5493 if (strncmp(line, " ", 4))
5496 /* Well, if the title starts with a whitespace character,
5497 * try to be forgiving. Otherwise we end up with no title. */
5498 while (isspace(*line))
5502 /* FIXME: More graceful handling of titles; append "..." to
5503 * shortened titles, etc. */
5505 string_ncopy(commit->title, line, strlen(line));
5512 main_request(struct view *view, enum request request, struct line *line)
5514 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
5518 open_view(view, REQ_VIEW_DIFF, flags);
5522 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
5532 grep_refs(struct ref **refs, regex_t *regex)
5540 if (regexec(regex, refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
5542 } while (refs[i++]->next);
5548 main_grep(struct view *view, struct line *line)
5550 struct commit *commit = line->data;
5551 enum { S_TITLE, S_AUTHOR, S_DATE, S_REFS, S_END } state;
5552 char buf[DATE_COLS + 1];
5555 for (state = S_TITLE; state < S_END; state++) {
5559 case S_TITLE: text = commit->title; break;
5563 text = commit->author;
5568 if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
5575 if (grep_refs(commit->refs, view->regex) == TRUE)
5582 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
5590 main_select(struct view *view, struct line *line)
5592 struct commit *commit = line->data;
5594 string_copy_rev(view->ref, commit->id);
5595 string_copy_rev(ref_commit, view->ref);
5598 static struct view_ops main_ops = {
5611 * Unicode / UTF-8 handling
5613 * NOTE: Much of the following code for dealing with unicode is derived from
5614 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
5615 * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
5618 /* I've (over)annotated a lot of code snippets because I am not entirely
5619 * confident that the approach taken by this small UTF-8 interface is correct.
5623 unicode_width(unsigned long c)
5626 (c <= 0x115f /* Hangul Jamo */
5629 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
5631 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
5632 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
5633 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
5634 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
5635 || (c >= 0xffe0 && c <= 0xffe6)
5636 || (c >= 0x20000 && c <= 0x2fffd)
5637 || (c >= 0x30000 && c <= 0x3fffd)))
5641 return opt_tab_size;
5646 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
5647 * Illegal bytes are set one. */
5648 static const unsigned char utf8_bytes[256] = {
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 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,
5652 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,
5653 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
5654 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
5655 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,
5656 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,
5659 /* Decode UTF-8 multi-byte representation into a unicode character. */
5660 static inline unsigned long
5661 utf8_to_unicode(const char *string, size_t length)
5663 unsigned long unicode;
5667 unicode = string[0];
5670 unicode = (string[0] & 0x1f) << 6;
5671 unicode += (string[1] & 0x3f);
5674 unicode = (string[0] & 0x0f) << 12;
5675 unicode += ((string[1] & 0x3f) << 6);
5676 unicode += (string[2] & 0x3f);
5679 unicode = (string[0] & 0x0f) << 18;
5680 unicode += ((string[1] & 0x3f) << 12);
5681 unicode += ((string[2] & 0x3f) << 6);
5682 unicode += (string[3] & 0x3f);
5685 unicode = (string[0] & 0x0f) << 24;
5686 unicode += ((string[1] & 0x3f) << 18);
5687 unicode += ((string[2] & 0x3f) << 12);
5688 unicode += ((string[3] & 0x3f) << 6);
5689 unicode += (string[4] & 0x3f);
5692 unicode = (string[0] & 0x01) << 30;
5693 unicode += ((string[1] & 0x3f) << 24);
5694 unicode += ((string[2] & 0x3f) << 18);
5695 unicode += ((string[3] & 0x3f) << 12);
5696 unicode += ((string[4] & 0x3f) << 6);
5697 unicode += (string[5] & 0x3f);
5700 die("Invalid unicode length");
5703 /* Invalid characters could return the special 0xfffd value but NUL
5704 * should be just as good. */
5705 return unicode > 0xffff ? 0 : unicode;
5708 /* Calculates how much of string can be shown within the given maximum width
5709 * and sets trimmed parameter to non-zero value if all of string could not be
5710 * shown. If the reserve flag is TRUE, it will reserve at least one
5711 * trailing character, which can be useful when drawing a delimiter.
5713 * Returns the number of bytes to output from string to satisfy max_width. */
5715 utf8_length(const char *string, int *width, size_t max_width, int *trimmed, bool reserve)
5717 const char *start = string;
5718 const char *end = strchr(string, '\0');
5719 unsigned char last_bytes = 0;
5720 size_t last_ucwidth = 0;
5725 while (string < end) {
5726 int c = *(unsigned char *) string;
5727 unsigned char bytes = utf8_bytes[c];
5729 unsigned long unicode;
5731 if (string + bytes > end)
5734 /* Change representation to figure out whether
5735 * it is a single- or double-width character. */
5737 unicode = utf8_to_unicode(string, bytes);
5738 /* FIXME: Graceful handling of invalid unicode character. */
5742 ucwidth = unicode_width(unicode);
5744 if (*width > max_width) {
5747 if (reserve && *width == max_width) {
5748 string -= last_bytes;
5749 *width -= last_ucwidth;
5756 last_ucwidth = ucwidth;
5759 return string - start;
5767 /* Whether or not the curses interface has been initialized. */
5768 static bool cursed = FALSE;
5770 /* The status window is used for polling keystrokes. */
5771 static WINDOW *status_win;
5773 static bool status_empty = TRUE;
5775 /* Update status and title window. */
5777 report(const char *msg, ...)
5779 struct view *view = display[current_view];
5785 char buf[SIZEOF_STR];
5788 va_start(args, msg);
5789 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
5790 buf[sizeof(buf) - 1] = 0;
5791 buf[sizeof(buf) - 2] = '.';
5792 buf[sizeof(buf) - 3] = '.';
5793 buf[sizeof(buf) - 4] = '.';
5799 if (!status_empty || *msg) {
5802 va_start(args, msg);
5804 wmove(status_win, 0, 0);
5806 vwprintw(status_win, msg, args);
5807 status_empty = FALSE;
5809 status_empty = TRUE;
5811 wclrtoeol(status_win);
5812 wrefresh(status_win);
5817 update_view_title(view);
5818 update_display_cursor(view);
5821 /* Controls when nodelay should be in effect when polling user input. */
5823 set_nonblocking_input(bool loading)
5825 static unsigned int loading_views;
5827 if ((loading == FALSE && loading_views-- == 1) ||
5828 (loading == TRUE && loading_views++ == 0))
5829 nodelay(status_win, loading);
5837 /* Initialize the curses library */
5838 if (isatty(STDIN_FILENO)) {
5839 cursed = !!initscr();
5842 /* Leave stdin and stdout alone when acting as a pager. */
5843 opt_tty = fopen("/dev/tty", "r+");
5845 die("Failed to open /dev/tty");
5846 cursed = !!newterm(NULL, opt_tty, opt_tty);
5850 die("Failed to initialize curses");
5852 nonl(); /* Tell curses not to do NL->CR/NL on output */
5853 cbreak(); /* Take input chars one at a time, no wait for \n */
5854 noecho(); /* Don't echo input */
5855 leaveok(stdscr, TRUE);
5860 getmaxyx(stdscr, y, x);
5861 status_win = newwin(1, 0, y - 1, 0);
5863 die("Failed to create status window");
5865 /* Enable keyboard mapping */
5866 keypad(status_win, TRUE);
5867 wbkgdset(status_win, get_line_attr(LINE_STATUS));
5869 TABSIZE = opt_tab_size;
5870 if (opt_line_graphics) {
5871 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
5876 prompt_yesno(const char *prompt)
5878 enum { WAIT, STOP, CANCEL } status = WAIT;
5879 bool answer = FALSE;
5881 while (status == WAIT) {
5887 foreach_view (view, i)
5892 mvwprintw(status_win, 0, 0, "%s [Yy]/[Nn]", prompt);
5893 wclrtoeol(status_win);
5895 /* Refresh, accept single keystroke of input */
5896 key = wgetch(status_win);
5920 /* Clear the status window */
5921 status_empty = FALSE;
5928 read_prompt(const char *prompt)
5930 enum { READING, STOP, CANCEL } status = READING;
5931 static char buf[SIZEOF_STR];
5934 while (status == READING) {
5940 foreach_view (view, i)
5945 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
5946 wclrtoeol(status_win);
5948 /* Refresh, accept single keystroke of input */
5949 key = wgetch(status_win);
5954 status = pos ? STOP : CANCEL;
5972 if (pos >= sizeof(buf)) {
5973 report("Input string too long");
5978 buf[pos++] = (char) key;
5982 /* Clear the status window */
5983 status_empty = FALSE;
5986 if (status == CANCEL)
5995 * Repository references
5998 static struct ref *refs = NULL;
5999 static size_t refs_alloc = 0;
6000 static size_t refs_size = 0;
6002 /* Id <-> ref store */
6003 static struct ref ***id_refs = NULL;
6004 static size_t id_refs_alloc = 0;
6005 static size_t id_refs_size = 0;
6008 compare_refs(const void *ref1_, const void *ref2_)
6010 const struct ref *ref1 = *(const struct ref **)ref1_;
6011 const struct ref *ref2 = *(const struct ref **)ref2_;
6013 if (ref1->tag != ref2->tag)
6014 return ref2->tag - ref1->tag;
6015 if (ref1->ltag != ref2->ltag)
6016 return ref2->ltag - ref2->ltag;
6017 if (ref1->head != ref2->head)
6018 return ref2->head - ref1->head;
6019 if (ref1->tracked != ref2->tracked)
6020 return ref2->tracked - ref1->tracked;
6021 if (ref1->remote != ref2->remote)
6022 return ref2->remote - ref1->remote;
6023 return strcmp(ref1->name, ref2->name);
6026 static struct ref **
6027 get_refs(const char *id)
6029 struct ref ***tmp_id_refs;
6030 struct ref **ref_list = NULL;
6031 size_t ref_list_alloc = 0;
6032 size_t ref_list_size = 0;
6035 for (i = 0; i < id_refs_size; i++)
6036 if (!strcmp(id, id_refs[i][0]->id))
6039 tmp_id_refs = realloc_items(id_refs, &id_refs_alloc, id_refs_size + 1,
6044 id_refs = tmp_id_refs;
6046 for (i = 0; i < refs_size; i++) {
6049 if (strcmp(id, refs[i].id))
6052 tmp = realloc_items(ref_list, &ref_list_alloc,
6053 ref_list_size + 1, sizeof(*ref_list));
6061 ref_list[ref_list_size] = &refs[i];
6062 /* XXX: The properties of the commit chains ensures that we can
6063 * safely modify the shared ref. The repo references will
6064 * always be similar for the same id. */
6065 ref_list[ref_list_size]->next = 1;
6071 qsort(ref_list, ref_list_size, sizeof(*ref_list), compare_refs);
6072 ref_list[ref_list_size - 1]->next = 0;
6073 id_refs[id_refs_size++] = ref_list;
6080 read_ref(char *id, size_t idlen, char *name, size_t namelen)
6085 bool remote = FALSE;
6086 bool tracked = FALSE;
6087 bool check_replace = FALSE;
6090 if (!prefixcmp(name, "refs/tags/")) {
6091 if (!suffixcmp(name, namelen, "^{}")) {
6094 if (refs_size > 0 && refs[refs_size - 1].ltag == TRUE)
6095 check_replace = TRUE;
6101 namelen -= STRING_SIZE("refs/tags/");
6102 name += STRING_SIZE("refs/tags/");
6104 } else if (!prefixcmp(name, "refs/remotes/")) {
6106 namelen -= STRING_SIZE("refs/remotes/");
6107 name += STRING_SIZE("refs/remotes/");
6108 tracked = !strcmp(opt_remote, name);
6110 } else if (!prefixcmp(name, "refs/heads/")) {
6111 namelen -= STRING_SIZE("refs/heads/");
6112 name += STRING_SIZE("refs/heads/");
6113 head = !strncmp(opt_head, name, namelen);
6115 } else if (!strcmp(name, "HEAD")) {
6116 string_ncopy(opt_head_rev, id, idlen);
6120 if (check_replace && !strcmp(name, refs[refs_size - 1].name)) {
6121 /* it's an annotated tag, replace the previous sha1 with the
6122 * resolved commit id; relies on the fact git-ls-remote lists
6123 * the commit id of an annotated tag right before the commit id
6125 refs[refs_size - 1].ltag = ltag;
6126 string_copy_rev(refs[refs_size - 1].id, id);
6130 refs = realloc_items(refs, &refs_alloc, refs_size + 1, sizeof(*refs));
6134 ref = &refs[refs_size++];
6135 ref->name = malloc(namelen + 1);
6139 strncpy(ref->name, name, namelen);
6140 ref->name[namelen] = 0;
6144 ref->remote = remote;
6145 ref->tracked = tracked;
6146 string_copy_rev(ref->id, id);
6154 const char *cmd_env = getenv("TIG_LS_REMOTE");
6155 const char *cmd = cmd_env && *cmd_env ? cmd_env : TIG_LS_REMOTE;
6160 while (refs_size > 0)
6161 free(refs[--refs_size].name);
6162 while (id_refs_size > 0)
6163 free(id_refs[--id_refs_size]);
6165 return read_properties(popen(cmd, "r"), "\t", read_ref);
6169 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
6171 if (!strcmp(name, "i18n.commitencoding"))
6172 string_ncopy(opt_encoding, value, valuelen);
6174 if (!strcmp(name, "core.editor"))
6175 string_ncopy(opt_editor, value, valuelen);
6177 /* branch.<head>.remote */
6179 !strncmp(name, "branch.", 7) &&
6180 !strncmp(name + 7, opt_head, strlen(opt_head)) &&
6181 !strcmp(name + 7 + strlen(opt_head), ".remote"))
6182 string_ncopy(opt_remote, value, valuelen);
6184 if (*opt_head && *opt_remote &&
6185 !strncmp(name, "branch.", 7) &&
6186 !strncmp(name + 7, opt_head, strlen(opt_head)) &&
6187 !strcmp(name + 7 + strlen(opt_head), ".merge")) {
6188 size_t from = strlen(opt_remote);
6190 if (!prefixcmp(value, "refs/heads/")) {
6191 value += STRING_SIZE("refs/heads/");
6192 valuelen -= STRING_SIZE("refs/heads/");
6195 if (!string_format_from(opt_remote, &from, "/%s", value))
6203 load_git_config(void)
6205 return read_properties(popen("git " GIT_CONFIG " --list", "r"),
6206 "=", read_repo_config_option);
6210 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
6212 if (!opt_git_dir[0]) {
6213 string_ncopy(opt_git_dir, name, namelen);
6215 } else if (opt_is_inside_work_tree == -1) {
6216 /* This can be 3 different values depending on the
6217 * version of git being used. If git-rev-parse does not
6218 * understand --is-inside-work-tree it will simply echo
6219 * the option else either "true" or "false" is printed.
6220 * Default to true for the unknown case. */
6221 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
6223 } else if (opt_cdup[0] == ' ') {
6224 string_ncopy(opt_cdup, name, namelen);
6226 if (!prefixcmp(name, "refs/heads/")) {
6227 namelen -= STRING_SIZE("refs/heads/");
6228 name += STRING_SIZE("refs/heads/");
6229 string_ncopy(opt_head, name, namelen);
6237 load_repo_info(void)
6240 FILE *pipe = popen("(git rev-parse --git-dir --is-inside-work-tree "
6241 " --show-cdup; git symbolic-ref HEAD) 2>/dev/null", "r");
6243 /* XXX: The line outputted by "--show-cdup" can be empty so
6244 * initialize it to something invalid to make it possible to
6245 * detect whether it has been set or not. */
6248 result = read_properties(pipe, "=", read_repo_info);
6249 if (opt_cdup[0] == ' ')
6256 read_properties(FILE *pipe, const char *separators,
6257 int (*read_property)(char *, size_t, char *, size_t))
6259 char buffer[BUFSIZ];
6266 while (state == OK && (name = fgets(buffer, sizeof(buffer), pipe))) {
6271 name = chomp_string(name);
6272 namelen = strcspn(name, separators);
6274 if (name[namelen]) {
6276 value = chomp_string(name + namelen + 1);
6277 valuelen = strlen(value);
6284 state = read_property(name, namelen, value, valuelen);
6287 if (state != ERR && ferror(pipe))
6300 static void __NORETURN
6303 /* XXX: Restore tty modes and let the OS cleanup the rest! */
6309 static void __NORETURN
6310 die(const char *err, ...)
6316 va_start(args, err);
6317 fputs("tig: ", stderr);
6318 vfprintf(stderr, err, args);
6319 fputs("\n", stderr);
6326 warn(const char *msg, ...)
6330 va_start(args, msg);
6331 fputs("tig warning: ", stderr);
6332 vfprintf(stderr, msg, args);
6333 fputs("\n", stderr);
6338 main(int argc, const char *argv[])
6340 const char **run_argv = NULL;
6342 enum request request;
6345 signal(SIGINT, quit);
6347 if (setlocale(LC_ALL, "")) {
6348 char *codeset = nl_langinfo(CODESET);
6350 string_ncopy(opt_codeset, codeset, strlen(codeset));
6353 if (load_repo_info() == ERR)
6354 die("Failed to load repo info.");
6356 if (load_options() == ERR)
6357 die("Failed to load user config.");
6359 if (load_git_config() == ERR)
6360 die("Failed to load repo config.");
6362 request = parse_options(argc, argv, &run_argv);
6363 if (request == REQ_NONE)
6366 /* Require a git repository unless when running in pager mode. */
6367 if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
6368 die("Not a git repository");
6370 if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
6373 if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
6374 opt_iconv = iconv_open(opt_codeset, opt_encoding);
6375 if (opt_iconv == ICONV_NONE)
6376 die("Failed to initialize character set conversion");
6379 if (load_refs() == ERR)
6380 die("Failed to load refs.");
6382 foreach_view (view, i)
6383 argv_from_env(view->ops->argv, view->cmd_env);
6387 if (request == REQ_VIEW_PAGER || run_argv) {
6388 if (request == REQ_VIEW_PAGER)
6389 init_io_fd(&VIEW(request)->io, stdin);
6390 else if (!prepare_update(VIEW(request), run_argv, NULL, FORMAT_NONE))
6391 die("Failed to format arguments");
6392 open_view(NULL, request, OPEN_PREPARED);
6396 while (view_driver(display[current_view], request)) {
6400 foreach_view (view, i)
6402 view = display[current_view];
6404 /* Refresh, accept single keystroke of input */
6405 key = wgetch(status_win);
6407 /* wgetch() with nodelay() enabled returns ERR when there's no
6414 request = get_keybinding(view->keymap, key);
6416 /* Some low-level request handling. This keeps access to
6417 * status_win restricted. */
6421 char *cmd = read_prompt(":");
6424 struct view *next = VIEW(REQ_VIEW_PAGER);
6425 const char *argv[SIZEOF_ARG] = { "git" };
6428 /* When running random commands, initially show the
6429 * command in the title. However, it maybe later be
6430 * overwritten if a commit line is selected. */
6431 string_ncopy(next->ref, cmd, strlen(cmd));
6433 if (!argv_from_string(argv, &argc, cmd)) {
6434 report("Too many arguments");
6435 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
6436 report("Failed to format command");
6438 open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
6446 case REQ_SEARCH_BACK:
6448 const char *prompt = request == REQ_SEARCH ? "/" : "?";
6449 char *search = read_prompt(prompt);
6452 string_ncopy(opt_search, search, strlen(search));
6457 case REQ_SCREEN_RESIZE:
6461 getmaxyx(stdscr, height, width);
6463 /* Resize the status view and let the view driver take
6464 * care of resizing the displayed views. */
6465 wresize(status_win, 1, width);
6466 mvwin(status_win, height - 1, 0);
6467 wrefresh(status_win);