1 /* Copyright (c) 2006-2009 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>
37 #include <sys/select.h>
48 /* ncurses(3): Must be defined to have extended wide-character functions. */
49 #define _XOPEN_SOURCE_EXTENDED
51 #ifdef HAVE_NCURSESW_NCURSES_H
52 #include <ncursesw/ncurses.h>
54 #ifdef HAVE_NCURSES_NCURSES_H
55 #include <ncurses/ncurses.h>
62 #define __NORETURN __attribute__((__noreturn__))
67 static void __NORETURN die(const char *err, ...);
68 static void warn(const char *msg, ...);
69 static void report(const char *msg, ...);
70 static void set_nonblocking_input(bool loading);
71 static int load_refs(void);
72 static size_t utf8_length(const char **string, size_t col, int *width, size_t max_width, int *trimmed, bool reserve);
74 #define ABS(x) ((x) >= 0 ? (x) : -(x))
75 #define MIN(x, y) ((x) < (y) ? (x) : (y))
77 #define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
78 #define STRING_SIZE(x) (sizeof(x) - 1)
80 #define SIZEOF_STR 1024 /* Default string size. */
81 #define SIZEOF_REF 256 /* Size of symbolic or SHA1 ID. */
82 #define SIZEOF_REV 41 /* Holds a SHA-1 and an ending NUL. */
83 #define SIZEOF_ARG 32 /* Default argument array size. */
87 #define REVGRAPH_INIT 'I'
88 #define REVGRAPH_MERGE 'M'
89 #define REVGRAPH_BRANCH '+'
90 #define REVGRAPH_COMMIT '*'
91 #define REVGRAPH_BOUND '^'
93 #define SIZEOF_REVGRAPH 19 /* Size of revision ancestry graphics. */
95 /* This color name can be used to refer to the default term colors. */
96 #define COLOR_DEFAULT (-1)
98 #define ICONV_NONE ((iconv_t) -1)
100 #define ICONV_CONST /* nothing */
103 /* The format and size of the date column in the main view. */
104 #define DATE_FORMAT "%Y-%m-%d %H:%M"
105 #define DATE_COLS STRING_SIZE("2006-04-29 14:21 ")
107 #define AUTHOR_COLS 20
110 /* The default interval between line numbers. */
111 #define NUMBER_INTERVAL 5
115 #define SCALE_SPLIT_VIEW(height) ((height) * 2 / 3)
117 #define NULL_ID "0000000000000000000000000000000000000000"
119 #define S_ISGITLINK(mode) (((mode) & S_IFMT) == 0160000)
122 #define GIT_CONFIG "config"
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);
143 static void foreach_ref(bool (*visitor)(void *data, struct ref *ref), void *data);
146 FORMAT_ALL, /* Perform replacement in all arguments. */
147 FORMAT_DASH, /* Perform replacement up until "--". */
148 FORMAT_NONE /* No replacement should be performed. */
151 static bool format_argv(const char *dst[], const char *src[], enum format_flags flags);
160 typedef enum input_status (*input_handler)(void *data, char *buf, int c);
162 static char *prompt_input(const char *prompt, input_handler handler, void *data);
163 static bool prompt_yesno(const char *prompt);
166 * Allocation helpers ... Entering macro hell to never be seen again.
169 #define DEFINE_ALLOCATOR(name, type, chunk_size) \
171 name(type **mem, size_t size, size_t increase) \
173 size_t num_chunks = (size + chunk_size - 1) / chunk_size; \
174 size_t num_chunks_new = (size + increase + chunk_size - 1) / chunk_size;\
177 if (mem == NULL || num_chunks != num_chunks_new) { \
178 tmp = realloc(tmp, num_chunks_new * chunk_size * sizeof(type)); \
191 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
193 if (srclen > dstlen - 1)
196 strncpy(dst, src, srclen);
200 /* Shorthands for safely copying into a fixed buffer. */
202 #define string_copy(dst, src) \
203 string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
205 #define string_ncopy(dst, src, srclen) \
206 string_ncopy_do(dst, sizeof(dst), src, srclen)
208 #define string_copy_rev(dst, src) \
209 string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
211 #define string_add(dst, from, src) \
212 string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
215 string_expand(char *dst, size_t dstlen, const char *src, int tabsize)
219 for (size = pos = 0; size < dstlen - 1 && src[pos]; pos++) {
220 if (src[pos] == '\t') {
221 size_t expanded = tabsize - (size % tabsize);
223 if (expanded + size >= dstlen - 1)
224 expanded = dstlen - size - 1;
225 memcpy(dst + size, " ", expanded);
228 dst[size++] = src[pos];
236 chomp_string(char *name)
240 while (isspace(*name))
243 namelen = strlen(name) - 1;
244 while (namelen > 0 && isspace(name[namelen]))
251 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
254 size_t pos = bufpos ? *bufpos : 0;
257 pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
263 return pos >= bufsize ? FALSE : TRUE;
266 #define string_format(buf, fmt, args...) \
267 string_nformat(buf, sizeof(buf), NULL, fmt, args)
269 #define string_format_from(buf, from, fmt, args...) \
270 string_nformat(buf, sizeof(buf), from, fmt, args)
273 string_enum_compare(const char *str1, const char *str2, int len)
277 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
279 /* Diff-Header == DIFF_HEADER */
280 for (i = 0; i < len; i++) {
281 if (toupper(str1[i]) == toupper(str2[i]))
284 if (string_enum_sep(str1[i]) &&
285 string_enum_sep(str2[i]))
288 return str1[i] - str2[i];
300 #define ENUM_MAP(name, value) { name, STRING_SIZE(name), value }
303 map_enum_do(const struct enum_map *map, size_t map_size, int *value, const char *name)
305 size_t namelen = strlen(name);
308 for (i = 0; i < map_size; i++)
309 if (namelen == map[i].namelen &&
310 !string_enum_compare(name, map[i].name, namelen)) {
311 *value = map[i].value;
318 #define map_enum(attr, map, name) \
319 map_enum_do(map, ARRAY_SIZE(map), attr, name)
321 #define prefixcmp(str1, str2) \
322 strncmp(str1, str2, STRING_SIZE(str2))
325 suffixcmp(const char *str, int slen, const char *suffix)
327 size_t len = slen >= 0 ? slen : strlen(str);
328 size_t suffixlen = strlen(suffix);
330 return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
335 mkdate(const time_t *time)
337 static char buf[DATE_COLS + 1];
341 return strftime(buf, sizeof(buf), DATE_FORMAT, &tm) ? buf : NULL;
346 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
350 while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
351 bool advance = cmd[valuelen] != 0;
354 argv[(*argc)++] = chomp_string(cmd);
355 cmd = chomp_string(cmd + valuelen + advance);
358 if (*argc < SIZEOF_ARG)
360 return *argc < SIZEOF_ARG;
364 argv_from_env(const char **argv, const char *name)
366 char *env = argv ? getenv(name) : NULL;
371 if (env && !argv_from_string(argv, &argc, env))
372 die("Too many arguments in the `%s` environment variable", name);
377 * Executing external commands.
381 IO_FD, /* File descriptor based IO. */
382 IO_BG, /* Execute command in the background. */
383 IO_FG, /* Execute command with same std{in,out,err}. */
384 IO_RD, /* Read only fork+exec IO. */
385 IO_WR, /* Write only fork+exec IO. */
386 IO_AP, /* Append fork+exec output to file. */
390 enum io_type type; /* The requested type of pipe. */
391 const char *dir; /* Directory from which to execute. */
392 pid_t pid; /* Pipe for reading or writing. */
393 int pipe; /* Pipe end for reading or writing. */
394 int error; /* Error status. */
395 const char *argv[SIZEOF_ARG]; /* Shell command arguments. */
396 char *buf; /* Read buffer. */
397 size_t bufalloc; /* Allocated buffer size. */
398 size_t bufsize; /* Buffer content size. */
399 char *bufpos; /* Current buffer position. */
400 unsigned int eof:1; /* Has end of file been reached. */
404 reset_io(struct io *io)
408 io->buf = io->bufpos = NULL;
409 io->bufalloc = io->bufsize = 0;
415 init_io(struct io *io, const char *dir, enum io_type type)
423 init_io_rd(struct io *io, const char *argv[], const char *dir,
424 enum format_flags flags)
426 init_io(io, dir, IO_RD);
427 return format_argv(io->argv, argv, flags);
431 io_open(struct io *io, const char *name)
433 init_io(io, NULL, IO_FD);
434 io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
437 return io->pipe != -1;
441 kill_io(struct io *io)
443 return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
447 done_io(struct io *io)
458 pid_t waiting = waitpid(pid, &status, 0);
463 report("waitpid failed (%s)", strerror(errno));
467 return waiting == pid &&
468 !WIFSIGNALED(status) &&
470 !WEXITSTATUS(status);
477 start_io(struct io *io)
479 int pipefds[2] = { -1, -1 };
481 if (io->type == IO_FD)
484 if ((io->type == IO_RD || io->type == IO_WR) &&
487 else if (io->type == IO_AP)
488 pipefds[1] = io->pipe;
490 if ((io->pid = fork())) {
491 if (pipefds[!(io->type == IO_WR)] != -1)
492 close(pipefds[!(io->type == IO_WR)]);
494 io->pipe = pipefds[!!(io->type == IO_WR)];
499 if (io->type != IO_FG) {
500 int devnull = open("/dev/null", O_RDWR);
501 int readfd = io->type == IO_WR ? pipefds[0] : devnull;
502 int writefd = (io->type == IO_RD || io->type == IO_AP)
503 ? pipefds[1] : devnull;
505 dup2(readfd, STDIN_FILENO);
506 dup2(writefd, STDOUT_FILENO);
507 dup2(devnull, STDERR_FILENO);
510 if (pipefds[0] != -1)
512 if (pipefds[1] != -1)
516 if (io->dir && *io->dir && chdir(io->dir) == -1)
517 die("Failed to change directory: %s", strerror(errno));
519 execvp(io->argv[0], (char *const*) io->argv);
520 die("Failed to execute program: %s", strerror(errno));
523 if (pipefds[!!(io->type == IO_WR)] != -1)
524 close(pipefds[!!(io->type == IO_WR)]);
529 run_io(struct io *io, const char **argv, const char *dir, enum io_type type)
531 init_io(io, dir, type);
532 if (!format_argv(io->argv, argv, FORMAT_NONE))
538 run_io_do(struct io *io)
540 return start_io(io) && done_io(io);
544 run_io_bg(const char **argv)
548 init_io(&io, NULL, IO_BG);
549 if (!format_argv(io.argv, argv, FORMAT_NONE))
551 return run_io_do(&io);
555 run_io_fg(const char **argv, const char *dir)
559 init_io(&io, dir, IO_FG);
560 if (!format_argv(io.argv, argv, FORMAT_NONE))
562 return run_io_do(&io);
566 run_io_append(const char **argv, enum format_flags flags, int fd)
570 init_io(&io, NULL, IO_AP);
572 if (format_argv(io.argv, argv, flags))
573 return run_io_do(&io);
579 run_io_rd(struct io *io, const char **argv, enum format_flags flags)
581 return init_io_rd(io, argv, NULL, flags) && start_io(io);
585 io_eof(struct io *io)
591 io_error(struct io *io)
597 io_strerror(struct io *io)
599 return strerror(io->error);
603 io_can_read(struct io *io)
605 struct timeval tv = { 0, 500 };
609 FD_SET(io->pipe, &fds);
611 return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
615 io_read(struct io *io, void *buf, size_t bufsize)
618 ssize_t readsize = read(io->pipe, buf, bufsize);
620 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
622 else if (readsize == -1)
624 else if (readsize == 0)
630 DEFINE_ALLOCATOR(realloc_io_buf, char, BUFSIZ)
633 io_get(struct io *io, int c, bool can_read)
639 if (io->bufsize > 0) {
640 eol = memchr(io->bufpos, c, io->bufsize);
642 char *line = io->bufpos;
645 io->bufpos = eol + 1;
646 io->bufsize -= io->bufpos - line;
653 io->bufpos[io->bufsize] = 0;
663 if (io->bufsize > 0 && io->bufpos > io->buf)
664 memmove(io->buf, io->bufpos, io->bufsize);
666 if (io->bufalloc == io->bufsize) {
667 if (!realloc_io_buf(&io->buf, io->bufalloc, BUFSIZ))
669 io->bufalloc += BUFSIZ;
672 io->bufpos = io->buf;
673 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
676 io->bufsize += readsize;
681 io_write(struct io *io, const void *buf, size_t bufsize)
685 while (!io_error(io) && written < bufsize) {
688 size = write(io->pipe, buf + written, bufsize - written);
689 if (size < 0 && (errno == EAGAIN || errno == EINTR))
697 return written == bufsize;
701 io_read_buf(struct io *io, char buf[], size_t bufsize)
703 char *result = io_get(io, '\n', TRUE);
706 result = chomp_string(result);
707 string_ncopy_do(buf, bufsize, result, strlen(result));
710 return done_io(io) && result;
714 run_io_buf(const char **argv, char buf[], size_t bufsize)
718 return run_io_rd(&io, argv, FORMAT_NONE) && io_read_buf(&io, buf, bufsize);
722 io_load(struct io *io, const char *separators,
723 int (*read_property)(char *, size_t, char *, size_t))
731 while (state == OK && (name = io_get(io, '\n', TRUE))) {
736 name = chomp_string(name);
737 namelen = strcspn(name, separators);
741 value = chomp_string(name + namelen + 1);
742 valuelen = strlen(value);
749 state = read_property(name, namelen, value, valuelen);
752 if (state != ERR && io_error(io))
760 run_io_load(const char **argv, const char *separators,
761 int (*read_property)(char *, size_t, char *, size_t))
765 return init_io_rd(&io, argv, NULL, FORMAT_NONE)
766 ? io_load(&io, separators, read_property) : ERR;
775 /* XXX: Keep the view request first and in sync with views[]. */ \
776 REQ_GROUP("View switching") \
777 REQ_(VIEW_MAIN, "Show main view"), \
778 REQ_(VIEW_DIFF, "Show diff view"), \
779 REQ_(VIEW_LOG, "Show log view"), \
780 REQ_(VIEW_TREE, "Show tree view"), \
781 REQ_(VIEW_BLOB, "Show blob view"), \
782 REQ_(VIEW_BLAME, "Show blame view"), \
783 REQ_(VIEW_BRANCH, "Show branch view"), \
784 REQ_(VIEW_HELP, "Show help page"), \
785 REQ_(VIEW_PAGER, "Show pager view"), \
786 REQ_(VIEW_STATUS, "Show status view"), \
787 REQ_(VIEW_STAGE, "Show stage view"), \
789 REQ_GROUP("View manipulation") \
790 REQ_(ENTER, "Enter current line and scroll"), \
791 REQ_(NEXT, "Move to next"), \
792 REQ_(PREVIOUS, "Move to previous"), \
793 REQ_(PARENT, "Move to parent"), \
794 REQ_(VIEW_NEXT, "Move focus to next view"), \
795 REQ_(REFRESH, "Reload and refresh"), \
796 REQ_(MAXIMIZE, "Maximize the current view"), \
797 REQ_(VIEW_CLOSE, "Close the current view"), \
798 REQ_(QUIT, "Close all views and quit"), \
800 REQ_GROUP("View specific requests") \
801 REQ_(STATUS_UPDATE, "Update file status"), \
802 REQ_(STATUS_REVERT, "Revert file changes"), \
803 REQ_(STATUS_MERGE, "Merge file using external tool"), \
804 REQ_(STAGE_NEXT, "Find next chunk to stage"), \
806 REQ_GROUP("Cursor navigation") \
807 REQ_(MOVE_UP, "Move cursor one line up"), \
808 REQ_(MOVE_DOWN, "Move cursor one line down"), \
809 REQ_(MOVE_PAGE_DOWN, "Move cursor one page down"), \
810 REQ_(MOVE_PAGE_UP, "Move cursor one page up"), \
811 REQ_(MOVE_FIRST_LINE, "Move cursor to first line"), \
812 REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
814 REQ_GROUP("Scrolling") \
815 REQ_(SCROLL_LEFT, "Scroll two columns left"), \
816 REQ_(SCROLL_RIGHT, "Scroll two columns right"), \
817 REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
818 REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
819 REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
820 REQ_(SCROLL_PAGE_DOWN, "Scroll one page down"), \
822 REQ_GROUP("Searching") \
823 REQ_(SEARCH, "Search the view"), \
824 REQ_(SEARCH_BACK, "Search backwards in the view"), \
825 REQ_(FIND_NEXT, "Find next search match"), \
826 REQ_(FIND_PREV, "Find previous search match"), \
828 REQ_GROUP("Option manipulation") \
829 REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
830 REQ_(TOGGLE_DATE, "Toggle date display"), \
831 REQ_(TOGGLE_AUTHOR, "Toggle author display"), \
832 REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
833 REQ_(TOGGLE_REFS, "Toggle reference display (tags/branches)"), \
836 REQ_(PROMPT, "Bring up the prompt"), \
837 REQ_(SCREEN_REDRAW, "Redraw the screen"), \
838 REQ_(SHOW_VERSION, "Show version information"), \
839 REQ_(STOP_LOADING, "Stop all loading views"), \
840 REQ_(EDIT, "Open in editor"), \
841 REQ_(NONE, "Do nothing")
844 /* User action requests. */
846 #define REQ_GROUP(help)
847 #define REQ_(req, help) REQ_##req
849 /* Offset all requests to avoid conflicts with ncurses getch values. */
850 REQ_OFFSET = KEY_MAX + 1,
857 struct request_info {
858 enum request request;
864 static const struct request_info req_info[] = {
865 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
866 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
873 get_request(const char *name)
875 int namelen = strlen(name);
878 for (i = 0; i < ARRAY_SIZE(req_info); i++)
879 if (req_info[i].namelen == namelen &&
880 !string_enum_compare(req_info[i].name, name, namelen))
881 return req_info[i].request;
891 /* Option and state variables. */
892 static bool opt_date = TRUE;
893 static bool opt_author = TRUE;
894 static bool opt_line_number = FALSE;
895 static bool opt_line_graphics = TRUE;
896 static bool opt_rev_graph = FALSE;
897 static bool opt_show_refs = TRUE;
898 static int opt_num_interval = NUMBER_INTERVAL;
899 static double opt_hscroll = 0.50;
900 static int opt_tab_size = TAB_SIZE;
901 static int opt_author_cols = AUTHOR_COLS-1;
902 static char opt_path[SIZEOF_STR] = "";
903 static char opt_file[SIZEOF_STR] = "";
904 static char opt_ref[SIZEOF_REF] = "";
905 static char opt_head[SIZEOF_REF] = "";
906 static char opt_head_rev[SIZEOF_REV] = "";
907 static char opt_remote[SIZEOF_REF] = "";
908 static char opt_encoding[20] = "UTF-8";
909 static bool opt_utf8 = TRUE;
910 static char opt_codeset[20] = "UTF-8";
911 static iconv_t opt_iconv = ICONV_NONE;
912 static char opt_search[SIZEOF_STR] = "";
913 static char opt_cdup[SIZEOF_STR] = "";
914 static char opt_prefix[SIZEOF_STR] = "";
915 static char opt_git_dir[SIZEOF_STR] = "";
916 static signed char opt_is_inside_work_tree = -1; /* set to TRUE or FALSE */
917 static char opt_editor[SIZEOF_STR] = "";
918 static FILE *opt_tty = NULL;
920 #define is_initial_commit() (!*opt_head_rev)
921 #define is_head_commit(rev) (!strcmp((rev), "HEAD") || !strcmp(opt_head_rev, (rev)))
925 * Line-oriented content detection.
929 LINE(DIFF_HEADER, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
930 LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
931 LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
932 LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
933 LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
934 LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
935 LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
936 LINE(DIFF_COPY_FROM, "copy from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
937 LINE(DIFF_COPY_TO, "copy to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
938 LINE(DIFF_RENAME_FROM, "rename from", COLOR_YELLOW, COLOR_DEFAULT, 0), \
939 LINE(DIFF_RENAME_TO, "rename to", COLOR_YELLOW, COLOR_DEFAULT, 0), \
940 LINE(DIFF_SIMILARITY, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
941 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
942 LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
943 LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
944 LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
945 LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
946 LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
947 LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
948 LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
949 LINE(PP_REFS, "Refs: ", COLOR_RED, COLOR_DEFAULT, 0), \
950 LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
951 LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
952 LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
953 LINE(AUTHOR, "author ", COLOR_GREEN, COLOR_DEFAULT, 0), \
954 LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
955 LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
956 LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
957 LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
958 LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
959 LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
960 LINE(DELIMITER, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
961 LINE(DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
962 LINE(MODE, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
963 LINE(LINE_NUMBER, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
964 LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
965 LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
966 LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
967 LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
968 LINE(MAIN_LOCAL_TAG,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
969 LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
970 LINE(MAIN_TRACKED, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \
971 LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
972 LINE(MAIN_HEAD, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
973 LINE(MAIN_REVGRAPH,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
974 LINE(TREE_HEAD, "", COLOR_DEFAULT, COLOR_DEFAULT, A_BOLD), \
975 LINE(TREE_DIR, "", COLOR_YELLOW, COLOR_DEFAULT, A_NORMAL), \
976 LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
977 LINE(STAT_HEAD, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
978 LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
979 LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
980 LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
981 LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
982 LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
983 LINE(BLAME_ID, "", COLOR_MAGENTA, COLOR_DEFAULT, 0)
986 #define LINE(type, line, fg, bg, attr) \
994 const char *name; /* Option name. */
995 int namelen; /* Size of option name. */
996 const char *line; /* The start of line to match. */
997 int linelen; /* Size of string to match. */
998 int fg, bg, attr; /* Color and text attributes for the lines. */
1001 static struct line_info line_info[] = {
1002 #define LINE(type, line, fg, bg, attr) \
1003 { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1008 static enum line_type
1009 get_line_type(const char *line)
1011 int linelen = strlen(line);
1012 enum line_type type;
1014 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1015 /* Case insensitive search matches Signed-off-by lines better. */
1016 if (linelen >= line_info[type].linelen &&
1017 !strncasecmp(line_info[type].line, line, line_info[type].linelen))
1020 return LINE_DEFAULT;
1024 get_line_attr(enum line_type type)
1026 assert(type < ARRAY_SIZE(line_info));
1027 return COLOR_PAIR(type) | line_info[type].attr;
1030 static struct line_info *
1031 get_line_info(const char *name)
1033 size_t namelen = strlen(name);
1034 enum line_type type;
1036 for (type = 0; type < ARRAY_SIZE(line_info); type++)
1037 if (namelen == line_info[type].namelen &&
1038 !string_enum_compare(line_info[type].name, name, namelen))
1039 return &line_info[type];
1047 int default_bg = line_info[LINE_DEFAULT].bg;
1048 int default_fg = line_info[LINE_DEFAULT].fg;
1049 enum line_type type;
1053 if (assume_default_colors(default_fg, default_bg) == ERR) {
1054 default_bg = COLOR_BLACK;
1055 default_fg = COLOR_WHITE;
1058 for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1059 struct line_info *info = &line_info[type];
1060 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1061 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1063 init_pair(type, fg, bg);
1068 enum line_type type;
1071 unsigned int selected:1;
1072 unsigned int dirty:1;
1073 unsigned int cleareol:1;
1075 void *data; /* User data */
1085 enum request request;
1088 static const struct keybinding default_keybindings[] = {
1089 /* View switching */
1090 { 'm', REQ_VIEW_MAIN },
1091 { 'd', REQ_VIEW_DIFF },
1092 { 'l', REQ_VIEW_LOG },
1093 { 't', REQ_VIEW_TREE },
1094 { 'f', REQ_VIEW_BLOB },
1095 { 'B', REQ_VIEW_BLAME },
1096 { 'H', REQ_VIEW_BRANCH },
1097 { 'p', REQ_VIEW_PAGER },
1098 { 'h', REQ_VIEW_HELP },
1099 { 'S', REQ_VIEW_STATUS },
1100 { 'c', REQ_VIEW_STAGE },
1102 /* View manipulation */
1103 { 'q', REQ_VIEW_CLOSE },
1104 { KEY_TAB, REQ_VIEW_NEXT },
1105 { KEY_RETURN, REQ_ENTER },
1106 { KEY_UP, REQ_PREVIOUS },
1107 { KEY_DOWN, REQ_NEXT },
1108 { 'R', REQ_REFRESH },
1109 { KEY_F(5), REQ_REFRESH },
1110 { 'O', REQ_MAXIMIZE },
1112 /* Cursor navigation */
1113 { 'k', REQ_MOVE_UP },
1114 { 'j', REQ_MOVE_DOWN },
1115 { KEY_HOME, REQ_MOVE_FIRST_LINE },
1116 { KEY_END, REQ_MOVE_LAST_LINE },
1117 { KEY_NPAGE, REQ_MOVE_PAGE_DOWN },
1118 { ' ', REQ_MOVE_PAGE_DOWN },
1119 { KEY_PPAGE, REQ_MOVE_PAGE_UP },
1120 { 'b', REQ_MOVE_PAGE_UP },
1121 { '-', REQ_MOVE_PAGE_UP },
1124 { KEY_LEFT, REQ_SCROLL_LEFT },
1125 { KEY_RIGHT, REQ_SCROLL_RIGHT },
1126 { KEY_IC, REQ_SCROLL_LINE_UP },
1127 { KEY_DC, REQ_SCROLL_LINE_DOWN },
1128 { 'w', REQ_SCROLL_PAGE_UP },
1129 { 's', REQ_SCROLL_PAGE_DOWN },
1132 { '/', REQ_SEARCH },
1133 { '?', REQ_SEARCH_BACK },
1134 { 'n', REQ_FIND_NEXT },
1135 { 'N', REQ_FIND_PREV },
1139 { 'z', REQ_STOP_LOADING },
1140 { 'v', REQ_SHOW_VERSION },
1141 { 'r', REQ_SCREEN_REDRAW },
1142 { '.', REQ_TOGGLE_LINENO },
1143 { 'D', REQ_TOGGLE_DATE },
1144 { 'A', REQ_TOGGLE_AUTHOR },
1145 { 'g', REQ_TOGGLE_REV_GRAPH },
1146 { 'F', REQ_TOGGLE_REFS },
1147 { ':', REQ_PROMPT },
1148 { 'u', REQ_STATUS_UPDATE },
1149 { '!', REQ_STATUS_REVERT },
1150 { 'M', REQ_STATUS_MERGE },
1151 { '@', REQ_STAGE_NEXT },
1152 { ',', REQ_PARENT },
1156 #define KEYMAP_INFO \
1171 #define KEYMAP_(name) KEYMAP_##name
1176 static const struct enum_map keymap_table[] = {
1177 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1182 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1184 struct keybinding_table {
1185 struct keybinding *data;
1189 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1192 add_keybinding(enum keymap keymap, enum request request, int key)
1194 struct keybinding_table *table = &keybindings[keymap];
1196 table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1198 die("Failed to allocate keybinding");
1199 table->data[table->size].alias = key;
1200 table->data[table->size++].request = request;
1203 /* Looks for a key binding first in the given map, then in the generic map, and
1204 * lastly in the default keybindings. */
1206 get_keybinding(enum keymap keymap, int key)
1210 for (i = 0; i < keybindings[keymap].size; i++)
1211 if (keybindings[keymap].data[i].alias == key)
1212 return keybindings[keymap].data[i].request;
1214 for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1215 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1216 return keybindings[KEYMAP_GENERIC].data[i].request;
1218 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1219 if (default_keybindings[i].alias == key)
1220 return default_keybindings[i].request;
1222 return (enum request) key;
1231 static const struct key key_table[] = {
1232 { "Enter", KEY_RETURN },
1234 { "Backspace", KEY_BACKSPACE },
1236 { "Escape", KEY_ESC },
1237 { "Left", KEY_LEFT },
1238 { "Right", KEY_RIGHT },
1240 { "Down", KEY_DOWN },
1241 { "Insert", KEY_IC },
1242 { "Delete", KEY_DC },
1244 { "Home", KEY_HOME },
1246 { "PageUp", KEY_PPAGE },
1247 { "PageDown", KEY_NPAGE },
1257 { "F10", KEY_F(10) },
1258 { "F11", KEY_F(11) },
1259 { "F12", KEY_F(12) },
1263 get_key_value(const char *name)
1267 for (i = 0; i < ARRAY_SIZE(key_table); i++)
1268 if (!strcasecmp(key_table[i].name, name))
1269 return key_table[i].value;
1271 if (strlen(name) == 1 && isprint(*name))
1278 get_key_name(int key_value)
1280 static char key_char[] = "'X'";
1281 const char *seq = NULL;
1284 for (key = 0; key < ARRAY_SIZE(key_table); key++)
1285 if (key_table[key].value == key_value)
1286 seq = key_table[key].name;
1290 isprint(key_value)) {
1291 key_char[1] = (char) key_value;
1295 return seq ? seq : "(no key)";
1299 get_key(enum request request)
1301 static char buf[BUFSIZ];
1308 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1309 const struct keybinding *keybinding = &default_keybindings[i];
1311 if (keybinding->request != request)
1314 if (!string_format_from(buf, &pos, "%s%s", sep,
1315 get_key_name(keybinding->alias)))
1316 return "Too many keybindings!";
1323 struct run_request {
1326 const char *argv[SIZEOF_ARG];
1329 static struct run_request *run_request;
1330 static size_t run_requests;
1332 DEFINE_ALLOCATOR(realloc_run_requests, struct run_request, 8)
1335 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1337 struct run_request *req;
1339 if (argc >= ARRAY_SIZE(req->argv) - 1)
1342 if (!realloc_run_requests(&run_request, run_requests, 1))
1345 req = &run_request[run_requests];
1346 req->keymap = keymap;
1348 req->argv[0] = NULL;
1350 if (!format_argv(req->argv, argv, FORMAT_NONE))
1353 return REQ_NONE + ++run_requests;
1356 static struct run_request *
1357 get_run_request(enum request request)
1359 if (request <= REQ_NONE)
1361 return &run_request[request - REQ_NONE - 1];
1365 add_builtin_run_requests(void)
1367 const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1368 const char *gc[] = { "git", "gc", NULL };
1375 { KEYMAP_MAIN, 'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1376 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1380 for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1383 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1384 if (req != REQ_NONE)
1385 add_keybinding(reqs[i].keymap, req, reqs[i].key);
1390 * User config file handling.
1393 static int config_lineno;
1394 static bool config_errors;
1395 static const char *config_msg;
1397 static const struct enum_map color_map[] = {
1398 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1410 static const struct enum_map attr_map[] = {
1411 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1418 ATTR_MAP(UNDERLINE),
1421 #define set_attribute(attr, name) map_enum(attr, attr_map, name)
1423 static int parse_step(double *opt, const char *arg)
1426 if (!strchr(arg, '%'))
1429 /* "Shift down" so 100% and 1 does not conflict. */
1430 *opt = (*opt - 1) / 100;
1433 config_msg = "Step value larger than 100%";
1438 config_msg = "Invalid step value";
1445 parse_int(int *opt, const char *arg, int min, int max)
1447 int value = atoi(arg);
1449 if (min <= value && value <= max) {
1454 config_msg = "Integer value out of bound";
1459 set_color(int *color, const char *name)
1461 if (map_enum(color, color_map, name))
1463 if (!prefixcmp(name, "color"))
1464 return parse_int(color, name + 5, 0, 255) == OK;
1468 /* Wants: object fgcolor bgcolor [attribute] */
1470 option_color_command(int argc, const char *argv[])
1472 struct line_info *info;
1474 if (argc != 3 && argc != 4) {
1475 config_msg = "Wrong number of arguments given to color command";
1479 info = get_line_info(argv[0]);
1481 static const struct enum_map obsolete[] = {
1482 ENUM_MAP("main-delim", LINE_DELIMITER),
1483 ENUM_MAP("main-date", LINE_DATE),
1484 ENUM_MAP("main-author", LINE_AUTHOR),
1488 if (!map_enum(&index, obsolete, argv[0])) {
1489 config_msg = "Unknown color name";
1492 info = &line_info[index];
1495 if (!set_color(&info->fg, argv[1]) ||
1496 !set_color(&info->bg, argv[2])) {
1497 config_msg = "Unknown color";
1501 if (argc == 4 && !set_attribute(&info->attr, argv[3])) {
1502 config_msg = "Unknown attribute";
1509 static int parse_bool(bool *opt, const char *arg)
1511 *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1517 parse_string(char *opt, const char *arg, size_t optsize)
1519 int arglen = strlen(arg);
1524 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1525 config_msg = "Unmatched quotation";
1528 arg += 1; arglen -= 2;
1530 string_ncopy_do(opt, optsize, arg, arglen);
1535 /* Wants: name = value */
1537 option_set_command(int argc, const char *argv[])
1540 config_msg = "Wrong number of arguments given to set command";
1544 if (strcmp(argv[1], "=")) {
1545 config_msg = "No value assigned";
1549 if (!strcmp(argv[0], "show-author"))
1550 return parse_bool(&opt_author, argv[2]);
1552 if (!strcmp(argv[0], "show-date"))
1553 return parse_bool(&opt_date, argv[2]);
1555 if (!strcmp(argv[0], "show-rev-graph"))
1556 return parse_bool(&opt_rev_graph, argv[2]);
1558 if (!strcmp(argv[0], "show-refs"))
1559 return parse_bool(&opt_show_refs, argv[2]);
1561 if (!strcmp(argv[0], "show-line-numbers"))
1562 return parse_bool(&opt_line_number, argv[2]);
1564 if (!strcmp(argv[0], "line-graphics"))
1565 return parse_bool(&opt_line_graphics, argv[2]);
1567 if (!strcmp(argv[0], "line-number-interval"))
1568 return parse_int(&opt_num_interval, argv[2], 1, 1024);
1570 if (!strcmp(argv[0], "author-width"))
1571 return parse_int(&opt_author_cols, argv[2], 0, 1024);
1573 if (!strcmp(argv[0], "horizontal-scroll"))
1574 return parse_step(&opt_hscroll, argv[2]);
1576 if (!strcmp(argv[0], "tab-size"))
1577 return parse_int(&opt_tab_size, argv[2], 1, 1024);
1579 if (!strcmp(argv[0], "commit-encoding"))
1580 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
1582 config_msg = "Unknown variable name";
1586 /* Wants: mode request key */
1588 option_bind_command(int argc, const char *argv[])
1590 enum request request;
1595 config_msg = "Wrong number of arguments given to bind command";
1599 if (set_keymap(&keymap, argv[0]) == ERR) {
1600 config_msg = "Unknown key map";
1604 key = get_key_value(argv[1]);
1606 config_msg = "Unknown key";
1610 request = get_request(argv[2]);
1611 if (request == REQ_NONE) {
1612 static const struct enum_map obsolete[] = {
1613 ENUM_MAP("cherry-pick", REQ_NONE),
1614 ENUM_MAP("screen-resize", REQ_NONE),
1615 ENUM_MAP("tree-parent", REQ_PARENT),
1619 if (map_enum(&alias, obsolete, argv[2])) {
1620 if (alias != REQ_NONE)
1621 add_keybinding(keymap, alias, key);
1622 config_msg = "Obsolete request name";
1626 if (request == REQ_NONE && *argv[2]++ == '!')
1627 request = add_run_request(keymap, key, argc - 2, argv + 2);
1628 if (request == REQ_NONE) {
1629 config_msg = "Unknown request name";
1633 add_keybinding(keymap, request, key);
1639 set_option(const char *opt, char *value)
1641 const char *argv[SIZEOF_ARG];
1644 if (!argv_from_string(argv, &argc, value)) {
1645 config_msg = "Too many option arguments";
1649 if (!strcmp(opt, "color"))
1650 return option_color_command(argc, argv);
1652 if (!strcmp(opt, "set"))
1653 return option_set_command(argc, argv);
1655 if (!strcmp(opt, "bind"))
1656 return option_bind_command(argc, argv);
1658 config_msg = "Unknown option command";
1663 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1668 config_msg = "Internal error";
1670 /* Check for comment markers, since read_properties() will
1671 * only ensure opt and value are split at first " \t". */
1672 optlen = strcspn(opt, "#");
1676 if (opt[optlen] != 0) {
1677 config_msg = "No option value";
1681 /* Look for comment endings in the value. */
1682 size_t len = strcspn(value, "#");
1684 if (len < valuelen) {
1686 value[valuelen] = 0;
1689 status = set_option(opt, value);
1692 if (status == ERR) {
1693 warn("Error on line %d, near '%.*s': %s",
1694 config_lineno, (int) optlen, opt, config_msg);
1695 config_errors = TRUE;
1698 /* Always keep going if errors are encountered. */
1703 load_option_file(const char *path)
1707 /* It's OK that the file doesn't exist. */
1708 if (!io_open(&io, path))
1712 config_errors = FALSE;
1714 if (io_load(&io, " \t", read_option) == ERR ||
1715 config_errors == TRUE)
1716 warn("Errors while loading %s.", path);
1722 const char *home = getenv("HOME");
1723 const char *tigrc_user = getenv("TIGRC_USER");
1724 const char *tigrc_system = getenv("TIGRC_SYSTEM");
1725 char buf[SIZEOF_STR];
1727 add_builtin_run_requests();
1730 tigrc_system = SYSCONFDIR "/tigrc";
1731 load_option_file(tigrc_system);
1734 if (!home || !string_format(buf, "%s/.tigrc", home))
1738 load_option_file(tigrc_user);
1751 /* The display array of active views and the index of the current view. */
1752 static struct view *display[2];
1753 static unsigned int current_view;
1755 #define foreach_displayed_view(view, i) \
1756 for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1758 #define displayed_views() (display[1] != NULL ? 2 : 1)
1760 /* Current head and commit ID */
1761 static char ref_blob[SIZEOF_REF] = "";
1762 static char ref_commit[SIZEOF_REF] = "HEAD";
1763 static char ref_head[SIZEOF_REF] = "HEAD";
1766 const char *name; /* View name */
1767 const char *cmd_env; /* Command line set via environment */
1768 const char *id; /* Points to either of ref_{head,commit,blob} */
1770 struct view_ops *ops; /* View operations */
1772 enum keymap keymap; /* What keymap does this view have */
1773 bool git_dir; /* Whether the view requires a git directory. */
1775 char ref[SIZEOF_REF]; /* Hovered commit reference */
1776 char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
1778 int height, width; /* The width and height of the main window */
1779 WINDOW *win; /* The main window */
1780 WINDOW *title; /* The title window living below the main window */
1783 unsigned long offset; /* Offset of the window top */
1784 unsigned long yoffset; /* Offset from the window side. */
1785 unsigned long lineno; /* Current line number */
1786 unsigned long p_offset; /* Previous offset of the window top */
1787 unsigned long p_yoffset;/* Previous offset from the window side */
1788 unsigned long p_lineno; /* Previous current line number */
1789 bool p_restore; /* Should the previous position be restored. */
1792 char grep[SIZEOF_STR]; /* Search string */
1793 regex_t *regex; /* Pre-compiled regexp */
1795 /* If non-NULL, points to the view that opened this view. If this view
1796 * is closed tig will switch back to the parent view. */
1797 struct view *parent;
1800 size_t lines; /* Total number of lines */
1801 struct line *line; /* Line index */
1802 unsigned int digits; /* Number of digits in the lines member. */
1805 struct line *curline; /* Line currently being drawn. */
1806 enum line_type curtype; /* Attribute currently used for drawing. */
1807 unsigned long col; /* Column when drawing. */
1808 bool has_scrolled; /* View was scrolled. */
1818 /* What type of content being displayed. Used in the title bar. */
1820 /* Default command arguments. */
1822 /* Open and reads in all view content. */
1823 bool (*open)(struct view *view);
1824 /* Read one line; updates view->line. */
1825 bool (*read)(struct view *view, char *data);
1826 /* Draw one line; @lineno must be < view->height. */
1827 bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
1828 /* Depending on view handle a special requests. */
1829 enum request (*request)(struct view *view, enum request request, struct line *line);
1830 /* Search for regexp in a line. */
1831 bool (*grep)(struct view *view, struct line *line);
1833 void (*select)(struct view *view, struct line *line);
1836 static struct view_ops blame_ops;
1837 static struct view_ops blob_ops;
1838 static struct view_ops diff_ops;
1839 static struct view_ops help_ops;
1840 static struct view_ops log_ops;
1841 static struct view_ops main_ops;
1842 static struct view_ops pager_ops;
1843 static struct view_ops stage_ops;
1844 static struct view_ops status_ops;
1845 static struct view_ops tree_ops;
1846 static struct view_ops branch_ops;
1848 #define VIEW_STR(name, env, ref, ops, map, git) \
1849 { name, #env, ref, ops, map, git }
1851 #define VIEW_(id, name, ops, git, ref) \
1852 VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
1855 static struct view views[] = {
1856 VIEW_(MAIN, "main", &main_ops, TRUE, ref_head),
1857 VIEW_(DIFF, "diff", &diff_ops, TRUE, ref_commit),
1858 VIEW_(LOG, "log", &log_ops, TRUE, ref_head),
1859 VIEW_(TREE, "tree", &tree_ops, TRUE, ref_commit),
1860 VIEW_(BLOB, "blob", &blob_ops, TRUE, ref_blob),
1861 VIEW_(BLAME, "blame", &blame_ops, TRUE, ref_commit),
1862 VIEW_(BRANCH, "branch", &branch_ops, TRUE, ref_head),
1863 VIEW_(HELP, "help", &help_ops, FALSE, ""),
1864 VIEW_(PAGER, "pager", &pager_ops, FALSE, "stdin"),
1865 VIEW_(STATUS, "status", &status_ops, TRUE, ""),
1866 VIEW_(STAGE, "stage", &stage_ops, TRUE, ""),
1869 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
1870 #define VIEW_REQ(view) ((view) - views + REQ_OFFSET + 1)
1872 #define foreach_view(view, i) \
1873 for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1875 #define view_is_displayed(view) \
1876 (view == display[0] || view == display[1])
1883 static chtype line_graphics[] = {
1884 /* LINE_GRAPHIC_VLINE: */ '|'
1888 set_view_attr(struct view *view, enum line_type type)
1890 if (!view->curline->selected && view->curtype != type) {
1891 wattrset(view->win, get_line_attr(type));
1892 wchgat(view->win, -1, 0, type, NULL);
1893 view->curtype = type;
1898 draw_chars(struct view *view, enum line_type type, const char *string,
1899 int max_len, bool use_tilde)
1903 int trimmed = FALSE;
1904 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
1910 len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde);
1912 col = len = strlen(string);
1913 if (len > max_len) {
1917 col = len = max_len;
1922 set_view_attr(view, type);
1924 waddnstr(view->win, string, len);
1925 if (trimmed && use_tilde) {
1926 set_view_attr(view, LINE_DELIMITER);
1927 waddch(view->win, '~');
1935 draw_space(struct view *view, enum line_type type, int max, int spaces)
1937 static char space[] = " ";
1940 spaces = MIN(max, spaces);
1942 while (spaces > 0) {
1943 int len = MIN(spaces, sizeof(space) - 1);
1945 col += draw_chars(view, type, space, len, FALSE);
1953 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
1955 view->col += draw_chars(view, type, string, view->width + view->yoffset - view->col, trim);
1956 return view->width + view->yoffset <= view->col;
1960 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
1962 size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
1963 int max = view->width + view->yoffset - view->col;
1969 set_view_attr(view, type);
1970 /* Using waddch() instead of waddnstr() ensures that
1971 * they'll be rendered correctly for the cursor line. */
1972 for (i = skip; i < size; i++)
1973 waddch(view->win, graphic[i]);
1976 if (size < max && skip <= size)
1977 waddch(view->win, ' ');
1980 return view->width + view->yoffset <= view->col;
1984 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
1986 int max = MIN(view->width + view->yoffset - view->col, len);
1990 col = draw_chars(view, type, text, max - 1, trim);
1992 col = draw_space(view, type, max - 1, max - 1);
1995 view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
1996 return view->width + view->yoffset <= view->col;
2000 draw_date(struct view *view, time_t *time)
2002 const char *date = mkdate(time);
2004 return draw_field(view, LINE_DATE, date, DATE_COLS, FALSE);
2008 draw_author(struct view *view, const char *author)
2010 bool trim = opt_author_cols == 0 || opt_author_cols > 5 || !author;
2013 static char initials[10];
2016 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@')
2018 memset(initials, 0, sizeof(initials));
2019 for (pos = 0; *author && pos < opt_author_cols - 1; author++, pos++) {
2020 while (is_initial_sep(*author))
2022 strncpy(&initials[pos], author, sizeof(initials) - 1 - pos);
2023 while (*author && !is_initial_sep(author[1]))
2030 return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2034 draw_mode(struct view *view, mode_t mode)
2040 else if (S_ISLNK(mode))
2042 else if (S_ISGITLINK(mode))
2044 else if (S_ISREG(mode) && mode & S_IXUSR)
2046 else if (S_ISREG(mode))
2051 return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2055 draw_lineno(struct view *view, unsigned int lineno)
2058 int digits3 = view->digits < 3 ? 3 : view->digits;
2059 int max = MIN(view->width + view->yoffset - view->col, digits3);
2062 lineno += view->offset + 1;
2063 if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2064 static char fmt[] = "%1ld";
2066 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2067 if (string_format(number, fmt, lineno))
2071 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2073 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2074 return draw_graphic(view, LINE_DEFAULT, &line_graphics[LINE_GRAPHIC_VLINE], 1);
2078 draw_view_line(struct view *view, unsigned int lineno)
2081 bool selected = (view->offset + lineno == view->lineno);
2083 assert(view_is_displayed(view));
2085 if (view->offset + lineno >= view->lines)
2088 line = &view->line[view->offset + lineno];
2090 wmove(view->win, lineno, 0);
2092 wclrtoeol(view->win);
2094 view->curline = line;
2095 view->curtype = LINE_NONE;
2096 line->selected = FALSE;
2097 line->dirty = line->cleareol = 0;
2100 set_view_attr(view, LINE_CURSOR);
2101 line->selected = TRUE;
2102 view->ops->select(view, line);
2105 return view->ops->draw(view, line, lineno);
2109 redraw_view_dirty(struct view *view)
2114 for (lineno = 0; lineno < view->height; lineno++) {
2115 if (view->offset + lineno >= view->lines)
2117 if (!view->line[view->offset + lineno].dirty)
2120 if (!draw_view_line(view, lineno))
2126 wnoutrefresh(view->win);
2130 redraw_view_from(struct view *view, int lineno)
2132 assert(0 <= lineno && lineno < view->height);
2134 for (; lineno < view->height; lineno++) {
2135 if (!draw_view_line(view, lineno))
2139 wnoutrefresh(view->win);
2143 redraw_view(struct view *view)
2146 redraw_view_from(view, 0);
2151 update_view_title(struct view *view)
2153 char buf[SIZEOF_STR];
2154 char state[SIZEOF_STR];
2155 size_t bufpos = 0, statelen = 0;
2157 assert(view_is_displayed(view));
2159 if (view != VIEW(REQ_VIEW_STATUS) && view->lines) {
2160 unsigned int view_lines = view->offset + view->height;
2161 unsigned int lines = view->lines
2162 ? MIN(view_lines, view->lines) * 100 / view->lines
2165 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2174 time_t secs = time(NULL) - view->start_time;
2176 /* Three git seconds are a long time ... */
2178 string_format_from(state, &statelen, " loading %lds", secs);
2181 string_format_from(buf, &bufpos, "[%s]", view->name);
2182 if (*view->ref && bufpos < view->width) {
2183 size_t refsize = strlen(view->ref);
2184 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2186 if (minsize < view->width)
2187 refsize = view->width - minsize + 7;
2188 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2191 if (statelen && bufpos < view->width) {
2192 string_format_from(buf, &bufpos, "%s", state);
2195 if (view == display[current_view])
2196 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2198 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2200 mvwaddnstr(view->title, 0, 0, buf, bufpos);
2201 wclrtoeol(view->title);
2202 wnoutrefresh(view->title);
2206 resize_display(void)
2209 struct view *base = display[0];
2210 struct view *view = display[1] ? display[1] : display[0];
2212 /* Setup window dimensions */
2214 getmaxyx(stdscr, base->height, base->width);
2216 /* Make room for the status window. */
2220 /* Horizontal split. */
2221 view->width = base->width;
2222 view->height = SCALE_SPLIT_VIEW(base->height);
2223 base->height -= view->height;
2225 /* Make room for the title bar. */
2229 /* Make room for the title bar. */
2234 foreach_displayed_view (view, i) {
2236 view->win = newwin(view->height, 0, offset, 0);
2238 die("Failed to create %s view", view->name);
2240 scrollok(view->win, FALSE);
2242 view->title = newwin(1, 0, offset + view->height, 0);
2244 die("Failed to create title window");
2247 wresize(view->win, view->height, view->width);
2248 mvwin(view->win, offset, 0);
2249 mvwin(view->title, offset + view->height, 0);
2252 offset += view->height + 1;
2257 redraw_display(bool clear)
2262 foreach_displayed_view (view, i) {
2266 update_view_title(view);
2271 toggle_view_option(bool *option, const char *help)
2274 redraw_display(FALSE);
2275 report("%sabling %s", *option ? "En" : "Dis", help);
2279 maximize_view(struct view *view)
2281 memset(display, 0, sizeof(display));
2283 display[current_view] = view;
2285 redraw_display(FALSE);
2295 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2297 if (lineno >= view->lines)
2298 lineno = view->lines > 0 ? view->lines - 1 : 0;
2300 if (offset > lineno || offset + view->height <= lineno) {
2301 unsigned long half = view->height / 2;
2304 offset = lineno - half;
2309 if (offset != view->offset || lineno != view->lineno) {
2310 view->offset = offset;
2311 view->lineno = lineno;
2319 apply_step(double step, int value)
2323 value *= step + 0.01;
2324 return value ? value : 1;
2327 /* Scrolling backend */
2329 do_scroll_view(struct view *view, int lines)
2331 bool redraw_current_line = FALSE;
2333 /* The rendering expects the new offset. */
2334 view->offset += lines;
2336 assert(0 <= view->offset && view->offset < view->lines);
2339 /* Move current line into the view. */
2340 if (view->lineno < view->offset) {
2341 view->lineno = view->offset;
2342 redraw_current_line = TRUE;
2343 } else if (view->lineno >= view->offset + view->height) {
2344 view->lineno = view->offset + view->height - 1;
2345 redraw_current_line = TRUE;
2348 assert(view->offset <= view->lineno && view->lineno < view->lines);
2350 /* Redraw the whole screen if scrolling is pointless. */
2351 if (view->height < ABS(lines)) {
2355 int line = lines > 0 ? view->height - lines : 0;
2356 int end = line + ABS(lines);
2358 scrollok(view->win, TRUE);
2359 wscrl(view->win, lines);
2360 scrollok(view->win, FALSE);
2362 while (line < end && draw_view_line(view, line))
2365 if (redraw_current_line)
2366 draw_view_line(view, view->lineno - view->offset);
2367 wnoutrefresh(view->win);
2370 view->has_scrolled = TRUE;
2374 /* Scroll frontend */
2376 scroll_view(struct view *view, enum request request)
2380 assert(view_is_displayed(view));
2383 case REQ_SCROLL_LEFT:
2384 if (view->yoffset == 0) {
2385 report("Cannot scroll beyond the first column");
2388 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2391 view->yoffset -= apply_step(opt_hscroll, view->width);
2392 redraw_view_from(view, 0);
2395 case REQ_SCROLL_RIGHT:
2396 view->yoffset += apply_step(opt_hscroll, view->width);
2400 case REQ_SCROLL_PAGE_DOWN:
2401 lines = view->height;
2402 case REQ_SCROLL_LINE_DOWN:
2403 if (view->offset + lines > view->lines)
2404 lines = view->lines - view->offset;
2406 if (lines == 0 || view->offset + view->height >= view->lines) {
2407 report("Cannot scroll beyond the last line");
2412 case REQ_SCROLL_PAGE_UP:
2413 lines = view->height;
2414 case REQ_SCROLL_LINE_UP:
2415 if (lines > view->offset)
2416 lines = view->offset;
2419 report("Cannot scroll beyond the first line");
2427 die("request %d not handled in switch", request);
2430 do_scroll_view(view, lines);
2435 move_view(struct view *view, enum request request)
2437 int scroll_steps = 0;
2441 case REQ_MOVE_FIRST_LINE:
2442 steps = -view->lineno;
2445 case REQ_MOVE_LAST_LINE:
2446 steps = view->lines - view->lineno - 1;
2449 case REQ_MOVE_PAGE_UP:
2450 steps = view->height > view->lineno
2451 ? -view->lineno : -view->height;
2454 case REQ_MOVE_PAGE_DOWN:
2455 steps = view->lineno + view->height >= view->lines
2456 ? view->lines - view->lineno - 1 : view->height;
2468 die("request %d not handled in switch", request);
2471 if (steps <= 0 && view->lineno == 0) {
2472 report("Cannot move beyond the first line");
2475 } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2476 report("Cannot move beyond the last line");
2480 /* Move the current line */
2481 view->lineno += steps;
2482 assert(0 <= view->lineno && view->lineno < view->lines);
2484 /* Check whether the view needs to be scrolled */
2485 if (view->lineno < view->offset ||
2486 view->lineno >= view->offset + view->height) {
2487 scroll_steps = steps;
2488 if (steps < 0 && -steps > view->offset) {
2489 scroll_steps = -view->offset;
2491 } else if (steps > 0) {
2492 if (view->lineno == view->lines - 1 &&
2493 view->lines > view->height) {
2494 scroll_steps = view->lines - view->offset - 1;
2495 if (scroll_steps >= view->height)
2496 scroll_steps -= view->height - 1;
2501 if (!view_is_displayed(view)) {
2502 view->offset += scroll_steps;
2503 assert(0 <= view->offset && view->offset < view->lines);
2504 view->ops->select(view, &view->line[view->lineno]);
2508 /* Repaint the old "current" line if we be scrolling */
2509 if (ABS(steps) < view->height)
2510 draw_view_line(view, view->lineno - steps - view->offset);
2513 do_scroll_view(view, scroll_steps);
2517 /* Draw the current line */
2518 draw_view_line(view, view->lineno - view->offset);
2520 wnoutrefresh(view->win);
2529 static void search_view(struct view *view, enum request request);
2532 grep_text(struct view *view, const char *text[])
2537 for (i = 0; text[i]; i++)
2539 regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
2545 select_view_line(struct view *view, unsigned long lineno)
2547 unsigned long old_lineno = view->lineno;
2548 unsigned long old_offset = view->offset;
2550 if (goto_view_line(view, view->offset, lineno)) {
2551 if (view_is_displayed(view)) {
2552 if (old_offset != view->offset) {
2555 draw_view_line(view, old_lineno - view->offset);
2556 draw_view_line(view, view->lineno - view->offset);
2557 wnoutrefresh(view->win);
2560 view->ops->select(view, &view->line[view->lineno]);
2566 find_next(struct view *view, enum request request)
2568 unsigned long lineno = view->lineno;
2573 report("No previous search");
2575 search_view(view, request);
2585 case REQ_SEARCH_BACK:
2594 if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2595 lineno += direction;
2597 /* Note, lineno is unsigned long so will wrap around in which case it
2598 * will become bigger than view->lines. */
2599 for (; lineno < view->lines; lineno += direction) {
2600 if (view->ops->grep(view, &view->line[lineno])) {
2601 select_view_line(view, lineno);
2602 report("Line %ld matches '%s'", lineno + 1, view->grep);
2607 report("No match found for '%s'", view->grep);
2611 search_view(struct view *view, enum request request)
2616 regfree(view->regex);
2619 view->regex = calloc(1, sizeof(*view->regex));
2624 regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2625 if (regex_err != 0) {
2626 char buf[SIZEOF_STR] = "unknown error";
2628 regerror(regex_err, view->regex, buf, sizeof(buf));
2629 report("Search failed: %s", buf);
2633 string_copy(view->grep, opt_search);
2635 find_next(view, request);
2639 * Incremental updating
2643 reset_view(struct view *view)
2647 for (i = 0; i < view->lines; i++)
2648 free(view->line[i].data);
2651 view->p_offset = view->offset;
2652 view->p_yoffset = view->yoffset;
2653 view->p_lineno = view->lineno;
2661 view->update_secs = 0;
2665 free_argv(const char *argv[])
2669 for (argc = 0; argv[argc]; argc++)
2670 free((void *) argv[argc]);
2674 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
2676 char buf[SIZEOF_STR];
2678 bool noreplace = flags == FORMAT_NONE;
2680 free_argv(dst_argv);
2682 for (argc = 0; src_argv[argc]; argc++) {
2683 const char *arg = src_argv[argc];
2687 char *next = strstr(arg, "%(");
2688 int len = next - arg;
2691 if (!next || noreplace) {
2692 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
2697 } else if (!prefixcmp(next, "%(directory)")) {
2700 } else if (!prefixcmp(next, "%(file)")) {
2703 } else if (!prefixcmp(next, "%(ref)")) {
2704 value = *opt_ref ? opt_ref : "HEAD";
2706 } else if (!prefixcmp(next, "%(head)")) {
2709 } else if (!prefixcmp(next, "%(commit)")) {
2712 } else if (!prefixcmp(next, "%(blob)")) {
2716 report("Unknown replacement: `%s`", next);
2720 if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
2723 arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
2726 dst_argv[argc] = strdup(buf);
2727 if (!dst_argv[argc])
2731 dst_argv[argc] = NULL;
2733 return src_argv[argc] == NULL;
2737 restore_view_position(struct view *view)
2739 if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
2742 /* Changing the view position cancels the restoring. */
2743 /* FIXME: Changing back to the first line is not detected. */
2744 if (view->offset != 0 || view->lineno != 0) {
2745 view->p_restore = FALSE;
2749 if (goto_view_line(view, view->p_offset, view->p_lineno) &&
2750 view_is_displayed(view))
2753 view->yoffset = view->p_yoffset;
2754 view->p_restore = FALSE;
2760 end_update(struct view *view, bool force)
2764 while (!view->ops->read(view, NULL))
2767 set_nonblocking_input(FALSE);
2769 kill_io(view->pipe);
2770 done_io(view->pipe);
2775 setup_update(struct view *view, const char *vid)
2777 set_nonblocking_input(TRUE);
2779 string_copy_rev(view->vid, vid);
2780 view->pipe = &view->io;
2781 view->start_time = time(NULL);
2785 prepare_update(struct view *view, const char *argv[], const char *dir,
2786 enum format_flags flags)
2789 end_update(view, TRUE);
2790 return init_io_rd(&view->io, argv, dir, flags);
2794 prepare_update_file(struct view *view, const char *name)
2797 end_update(view, TRUE);
2798 return io_open(&view->io, name);
2802 begin_update(struct view *view, bool refresh)
2805 end_update(view, TRUE);
2808 if (!start_io(&view->io))
2812 if (view == VIEW(REQ_VIEW_TREE) && strcmp(view->vid, view->id))
2815 if (!run_io_rd(&view->io, view->ops->argv, FORMAT_ALL))
2818 /* Put the current ref_* value to the view title ref
2819 * member. This is needed by the blob view. Most other
2820 * views sets it automatically after loading because the
2821 * first line is a commit line. */
2822 string_copy_rev(view->ref, view->id);
2825 setup_update(view, view->id);
2831 update_view(struct view *view)
2833 char out_buffer[BUFSIZ * 2];
2835 /* Clear the view and redraw everything since the tree sorting
2836 * might have rearranged things. */
2837 bool redraw = view->lines == 0;
2838 bool can_read = TRUE;
2843 if (!io_can_read(view->pipe)) {
2844 if (view->lines == 0 && view_is_displayed(view)) {
2845 time_t secs = time(NULL) - view->start_time;
2847 if (secs > 1 && secs > view->update_secs) {
2848 if (view->update_secs == 0)
2850 update_view_title(view);
2851 view->update_secs = secs;
2857 for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
2858 if (opt_iconv != ICONV_NONE) {
2859 ICONV_CONST char *inbuf = line;
2860 size_t inlen = strlen(line) + 1;
2862 char *outbuf = out_buffer;
2863 size_t outlen = sizeof(out_buffer);
2867 ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2868 if (ret != (size_t) -1)
2872 if (!view->ops->read(view, line)) {
2873 report("Allocation failure");
2874 end_update(view, TRUE);
2880 unsigned long lines = view->lines;
2883 for (digits = 0; lines; digits++)
2886 /* Keep the displayed view in sync with line number scaling. */
2887 if (digits != view->digits) {
2888 view->digits = digits;
2889 if (opt_line_number || view == VIEW(REQ_VIEW_BLAME))
2894 if (io_error(view->pipe)) {
2895 report("Failed to read: %s", io_strerror(view->pipe));
2896 end_update(view, TRUE);
2898 } else if (io_eof(view->pipe)) {
2900 end_update(view, FALSE);
2903 if (restore_view_position(view))
2906 if (!view_is_displayed(view))
2910 redraw_view_from(view, 0);
2912 redraw_view_dirty(view);
2914 /* Update the title _after_ the redraw so that if the redraw picks up a
2915 * commit reference in view->ref it'll be available here. */
2916 update_view_title(view);
2920 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
2922 static struct line *
2923 add_line_data(struct view *view, void *data, enum line_type type)
2927 if (!realloc_lines(&view->line, view->lines, 1))
2930 line = &view->line[view->lines++];
2931 memset(line, 0, sizeof(*line));
2939 static struct line *
2940 add_line_text(struct view *view, const char *text, enum line_type type)
2942 char *data = text ? strdup(text) : NULL;
2944 return data ? add_line_data(view, data, type) : NULL;
2947 static struct line *
2948 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
2950 char buf[SIZEOF_STR];
2953 va_start(args, fmt);
2954 if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
2958 return buf[0] ? add_line_text(view, buf, type) : NULL;
2966 OPEN_DEFAULT = 0, /* Use default view switching. */
2967 OPEN_SPLIT = 1, /* Split current view. */
2968 OPEN_RELOAD = 4, /* Reload view even if it is the current. */
2969 OPEN_REFRESH = 16, /* Refresh view using previous command. */
2970 OPEN_PREPARED = 32, /* Open already prepared command. */
2974 open_view(struct view *prev, enum request request, enum open_flags flags)
2976 bool split = !!(flags & OPEN_SPLIT);
2977 bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
2978 bool nomaximize = !!(flags & OPEN_REFRESH);
2979 struct view *view = VIEW(request);
2980 int nviews = displayed_views();
2981 struct view *base_view = display[0];
2983 if (view == prev && nviews == 1 && !reload) {
2984 report("Already in %s view", view->name);
2988 if (view->git_dir && !opt_git_dir[0]) {
2989 report("The %s view is disabled in pager view", view->name);
2996 } else if (!nomaximize) {
2997 /* Maximize the current view. */
2998 memset(display, 0, sizeof(display));
3000 display[current_view] = view;
3003 /* Resize the view when switching between split- and full-screen,
3004 * or when switching between two different full-screen views. */
3005 if (nviews != displayed_views() ||
3006 (nviews == 1 && base_view != display[0]))
3009 if (view->ops->open) {
3011 end_update(view, TRUE);
3012 if (!view->ops->open(view)) {
3013 report("Failed to load %s view", view->name);
3016 restore_view_position(view);
3018 } else if ((reload || strcmp(view->vid, view->id)) &&
3019 !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3020 report("Failed to load %s view", view->name);
3024 if (split && prev->lineno - prev->offset >= prev->height) {
3025 /* Take the title line into account. */
3026 int lines = prev->lineno - prev->offset - prev->height + 1;
3028 /* Scroll the view that was split if the current line is
3029 * outside the new limited view. */
3030 do_scroll_view(prev, lines);
3033 if (prev && view != prev) {
3035 /* "Blur" the previous view. */
3036 update_view_title(prev);
3039 view->parent = prev;
3042 if (view->pipe && view->lines == 0) {
3043 /* Clear the old view and let the incremental updating refill
3046 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3048 } else if (view_is_displayed(view)) {
3055 open_external_viewer(const char *argv[], const char *dir)
3057 def_prog_mode(); /* save current tty modes */
3058 endwin(); /* restore original tty modes */
3059 run_io_fg(argv, dir);
3060 fprintf(stderr, "Press Enter to continue");
3063 redraw_display(TRUE);
3067 open_mergetool(const char *file)
3069 const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3071 open_external_viewer(mergetool_argv, opt_cdup);
3075 open_editor(bool from_root, const char *file)
3077 const char *editor_argv[] = { "vi", file, NULL };
3080 editor = getenv("GIT_EDITOR");
3081 if (!editor && *opt_editor)
3082 editor = opt_editor;
3084 editor = getenv("VISUAL");
3086 editor = getenv("EDITOR");
3090 editor_argv[0] = editor;
3091 open_external_viewer(editor_argv, from_root ? opt_cdup : NULL);
3095 open_run_request(enum request request)
3097 struct run_request *req = get_run_request(request);
3098 const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3101 report("Unknown run request");
3105 if (format_argv(argv, req->argv, FORMAT_ALL))
3106 open_external_viewer(argv, NULL);
3111 * User request switch noodle
3115 view_driver(struct view *view, enum request request)
3119 if (request == REQ_NONE)
3122 if (request > REQ_NONE) {
3123 open_run_request(request);
3124 /* FIXME: When all views can refresh always do this. */
3125 if (view == VIEW(REQ_VIEW_STATUS) ||
3126 view == VIEW(REQ_VIEW_MAIN) ||
3127 view == VIEW(REQ_VIEW_LOG) ||
3128 view == VIEW(REQ_VIEW_BRANCH) ||
3129 view == VIEW(REQ_VIEW_STAGE))
3130 request = REQ_REFRESH;
3135 if (view && view->lines) {
3136 request = view->ops->request(view, request, &view->line[view->lineno]);
3137 if (request == REQ_NONE)
3144 case REQ_MOVE_PAGE_UP:
3145 case REQ_MOVE_PAGE_DOWN:
3146 case REQ_MOVE_FIRST_LINE:
3147 case REQ_MOVE_LAST_LINE:
3148 move_view(view, request);
3151 case REQ_SCROLL_LEFT:
3152 case REQ_SCROLL_RIGHT:
3153 case REQ_SCROLL_LINE_DOWN:
3154 case REQ_SCROLL_LINE_UP:
3155 case REQ_SCROLL_PAGE_DOWN:
3156 case REQ_SCROLL_PAGE_UP:
3157 scroll_view(view, request);
3160 case REQ_VIEW_BLAME:
3162 report("No file chosen, press %s to open tree view",
3163 get_key(REQ_VIEW_TREE));
3166 open_view(view, request, OPEN_DEFAULT);
3171 report("No file chosen, press %s to open tree view",
3172 get_key(REQ_VIEW_TREE));
3175 open_view(view, request, OPEN_DEFAULT);
3178 case REQ_VIEW_PAGER:
3179 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3180 report("No pager content, press %s to run command from prompt",
3181 get_key(REQ_PROMPT));
3184 open_view(view, request, OPEN_DEFAULT);
3187 case REQ_VIEW_STAGE:
3188 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3189 report("No stage content, press %s to open the status view and choose file",
3190 get_key(REQ_VIEW_STATUS));
3193 open_view(view, request, OPEN_DEFAULT);
3196 case REQ_VIEW_STATUS:
3197 if (opt_is_inside_work_tree == FALSE) {
3198 report("The status view requires a working tree");
3201 open_view(view, request, OPEN_DEFAULT);
3209 case REQ_VIEW_BRANCH:
3210 open_view(view, request, OPEN_DEFAULT);
3215 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3217 if ((view == VIEW(REQ_VIEW_DIFF) &&
3218 view->parent == VIEW(REQ_VIEW_MAIN)) ||
3219 (view == VIEW(REQ_VIEW_DIFF) &&
3220 view->parent == VIEW(REQ_VIEW_BLAME)) ||
3221 (view == VIEW(REQ_VIEW_STAGE) &&
3222 view->parent == VIEW(REQ_VIEW_STATUS)) ||
3223 (view == VIEW(REQ_VIEW_BLOB) &&
3224 view->parent == VIEW(REQ_VIEW_TREE))) {
3227 view = view->parent;
3228 line = view->lineno;
3229 move_view(view, request);
3230 if (view_is_displayed(view))
3231 update_view_title(view);
3232 if (line != view->lineno)
3233 view->ops->request(view, REQ_ENTER,
3234 &view->line[view->lineno]);
3237 move_view(view, request);
3243 int nviews = displayed_views();
3244 int next_view = (current_view + 1) % nviews;
3246 if (next_view == current_view) {
3247 report("Only one view is displayed");
3251 current_view = next_view;
3252 /* Blur out the title of the previous view. */
3253 update_view_title(view);
3258 report("Refreshing is not yet supported for the %s view", view->name);
3262 if (displayed_views() == 2)
3263 maximize_view(view);
3266 case REQ_TOGGLE_LINENO:
3267 toggle_view_option(&opt_line_number, "line numbers");
3270 case REQ_TOGGLE_DATE:
3271 toggle_view_option(&opt_date, "date display");
3274 case REQ_TOGGLE_AUTHOR:
3275 toggle_view_option(&opt_author, "author display");
3278 case REQ_TOGGLE_REV_GRAPH:
3279 toggle_view_option(&opt_rev_graph, "revision graph display");
3282 case REQ_TOGGLE_REFS:
3283 toggle_view_option(&opt_show_refs, "reference display");
3287 case REQ_SEARCH_BACK:
3288 search_view(view, request);
3293 find_next(view, request);
3296 case REQ_STOP_LOADING:
3297 for (i = 0; i < ARRAY_SIZE(views); i++) {
3300 report("Stopped loading the %s view", view->name),
3301 end_update(view, TRUE);
3305 case REQ_SHOW_VERSION:
3306 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3309 case REQ_SCREEN_REDRAW:
3310 redraw_display(TRUE);
3314 report("Nothing to edit");
3318 report("Nothing to enter");
3321 case REQ_VIEW_CLOSE:
3322 /* XXX: Mark closed views by letting view->parent point to the
3323 * view itself. Parents to closed view should never be
3326 view->parent->parent != view->parent) {
3327 maximize_view(view->parent);
3328 view->parent = view;
3336 report("Unknown key, press 'h' for help");
3345 * View backend utilities
3348 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3350 /* Small author cache to reduce memory consumption. It uses binary
3351 * search to lookup or find place to position new entries. No entries
3352 * are ever freed. */
3354 get_author(const char *name)
3356 static const char **authors;
3357 static size_t authors_size;
3358 int from = 0, to = authors_size - 1;
3360 while (from <= to) {
3361 size_t pos = (to + from) / 2;
3362 int cmp = strcmp(name, authors[pos]);
3365 return authors[pos];
3373 if (!realloc_authors(&authors, authors_size, 1))
3375 name = strdup(name);
3379 memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3380 authors[from] = name;
3387 parse_timezone(time_t *time, const char *zone)
3391 tz = ('0' - zone[1]) * 60 * 60 * 10;
3392 tz += ('0' - zone[2]) * 60 * 60;
3393 tz += ('0' - zone[3]) * 60;
3394 tz += ('0' - zone[4]);
3402 /* Parse author lines where the name may be empty:
3403 * author <email@address.tld> 1138474660 +0100
3406 parse_author_line(char *ident, const char **author, time_t *time)
3408 char *nameend = strchr(ident, '<');
3409 char *emailend = strchr(ident, '>');
3411 if (nameend && emailend)
3412 *nameend = *emailend = 0;
3413 ident = chomp_string(ident);
3416 ident = chomp_string(nameend + 1);
3421 *author = get_author(ident);
3423 /* Parse epoch and timezone */
3424 if (emailend && emailend[1] == ' ') {
3425 char *secs = emailend + 2;
3426 char *zone = strchr(secs, ' ');
3428 *time = (time_t) atol(secs);
3430 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
3431 parse_timezone(time, zone + 1);
3435 static enum input_status
3436 select_commit_parent_handler(void *data, char *buf, int c)
3438 size_t parents = *(size_t *) data;
3445 parent = atoi(buf) * 10;
3448 if (parent > parents)
3454 select_commit_parent(const char *id, char rev[SIZEOF_REV], const char *path)
3456 char buf[SIZEOF_STR * 4];
3457 const char *revlist_argv[] = {
3458 "git", "rev-list", "-1", "--parents", id, "--", path, NULL
3462 if (!run_io_buf(revlist_argv, buf, sizeof(buf)) ||
3463 (parents = (strlen(buf) / 40) - 1) < 0) {
3464 report("Failed to get parent information");
3467 } else if (parents == 0) {
3469 report("Path '%s' does not exist in the parent", path);
3471 report("The selected commit has no parents");
3476 char prompt[SIZEOF_STR];
3479 if (!string_format(prompt, "Which parent? [1..%d] ", parents))
3481 result = prompt_input(prompt, select_commit_parent_handler, &parents);
3484 parents = atoi(result);
3487 string_copy_rev(rev, &buf[41 * parents]);
3496 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3498 char text[SIZEOF_STR];
3500 if (opt_line_number && draw_lineno(view, lineno))
3503 string_expand(text, sizeof(text), line->data, opt_tab_size);
3504 draw_text(view, line->type, text, TRUE);
3509 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3511 const char *describe_argv[] = { "git", "describe", commit_id, NULL };
3512 char ref[SIZEOF_STR];
3514 if (!run_io_buf(describe_argv, ref, sizeof(ref)) || !*ref)
3517 /* This is the only fatal call, since it can "corrupt" the buffer. */
3518 if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
3525 add_pager_refs(struct view *view, struct line *line)
3527 char buf[SIZEOF_STR];
3528 char *commit_id = (char *)line->data + STRING_SIZE("commit ");
3530 size_t bufpos = 0, refpos = 0;
3531 const char *sep = "Refs: ";
3532 bool is_tag = FALSE;
3534 assert(line->type == LINE_COMMIT);
3536 refs = get_refs(commit_id);
3538 if (view == VIEW(REQ_VIEW_DIFF))
3539 goto try_add_describe_ref;
3544 struct ref *ref = refs[refpos];
3545 const char *fmt = ref->tag ? "%s[%s]" :
3546 ref->remote ? "%s<%s>" : "%s%s";
3548 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
3553 } while (refs[refpos++]->next);
3555 if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
3556 try_add_describe_ref:
3557 /* Add <tag>-g<commit_id> "fake" reference. */
3558 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
3565 add_line_text(view, buf, LINE_PP_REFS);
3569 pager_read(struct view *view, char *data)
3576 line = add_line_text(view, data, get_line_type(data));
3580 if (line->type == LINE_COMMIT &&
3581 (view == VIEW(REQ_VIEW_DIFF) ||
3582 view == VIEW(REQ_VIEW_LOG)))
3583 add_pager_refs(view, line);
3589 pager_request(struct view *view, enum request request, struct line *line)
3593 if (request != REQ_ENTER)
3596 if (line->type == LINE_COMMIT &&
3597 (view == VIEW(REQ_VIEW_LOG) ||
3598 view == VIEW(REQ_VIEW_PAGER))) {
3599 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3603 /* Always scroll the view even if it was split. That way
3604 * you can use Enter to scroll through the log view and
3605 * split open each commit diff. */
3606 scroll_view(view, REQ_SCROLL_LINE_DOWN);
3608 /* FIXME: A minor workaround. Scrolling the view will call report("")
3609 * but if we are scrolling a non-current view this won't properly
3610 * update the view title. */
3612 update_view_title(view);
3618 pager_grep(struct view *view, struct line *line)
3620 const char *text[] = { line->data, NULL };
3622 return grep_text(view, text);
3626 pager_select(struct view *view, struct line *line)
3628 if (line->type == LINE_COMMIT) {
3629 char *text = (char *)line->data + STRING_SIZE("commit ");
3631 if (view != VIEW(REQ_VIEW_PAGER))
3632 string_copy_rev(view->ref, text);
3633 string_copy_rev(ref_commit, text);
3637 static struct view_ops pager_ops = {
3648 static const char *log_argv[SIZEOF_ARG] = {
3649 "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3653 log_request(struct view *view, enum request request, struct line *line)
3658 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
3661 return pager_request(view, request, line);
3665 static struct view_ops log_ops = {
3676 static const char *diff_argv[SIZEOF_ARG] = {
3677 "git", "show", "--pretty=fuller", "--no-color", "--root",
3678 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
3681 static struct view_ops diff_ops = {
3697 help_open(struct view *view)
3699 char buf[SIZEOF_STR];
3703 if (view->lines > 0)
3706 add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
3708 for (i = 0; i < ARRAY_SIZE(req_info); i++) {
3711 if (req_info[i].request == REQ_NONE)
3714 if (!req_info[i].request) {
3715 add_line_text(view, "", LINE_DEFAULT);
3716 add_line_text(view, req_info[i].help, LINE_DEFAULT);
3720 key = get_key(req_info[i].request);
3722 key = "(no key defined)";
3724 for (bufpos = 0; bufpos <= req_info[i].namelen; bufpos++) {
3725 buf[bufpos] = tolower(req_info[i].name[bufpos]);
3726 if (buf[bufpos] == '_')
3730 add_line_format(view, LINE_DEFAULT, " %-25s %-20s %s",
3731 key, buf, req_info[i].help);
3735 add_line_text(view, "", LINE_DEFAULT);
3736 add_line_text(view, "External commands:", LINE_DEFAULT);
3739 for (i = 0; i < run_requests; i++) {
3740 struct run_request *req = get_run_request(REQ_NONE + i + 1);
3747 key = get_key_name(req->key);
3749 key = "(no key defined)";
3751 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
3752 if (!string_format_from(buf, &bufpos, "%s%s",
3753 argc ? " " : "", req->argv[argc]))
3756 add_line_format(view, LINE_DEFAULT, " %-10s %-14s `%s`",
3757 keymap_table[req->keymap].name, key, buf);
3763 static struct view_ops help_ops = {
3779 struct tree_stack_entry {
3780 struct tree_stack_entry *prev; /* Entry below this in the stack */
3781 unsigned long lineno; /* Line number to restore */
3782 char *name; /* Position of name in opt_path */
3785 /* The top of the path stack. */
3786 static struct tree_stack_entry *tree_stack = NULL;
3787 unsigned long tree_lineno = 0;
3790 pop_tree_stack_entry(void)
3792 struct tree_stack_entry *entry = tree_stack;
3794 tree_lineno = entry->lineno;
3796 tree_stack = entry->prev;
3801 push_tree_stack_entry(const char *name, unsigned long lineno)
3803 struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
3804 size_t pathlen = strlen(opt_path);
3809 entry->prev = tree_stack;
3810 entry->name = opt_path + pathlen;
3813 if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
3814 pop_tree_stack_entry();
3818 /* Move the current line to the first tree entry. */
3820 entry->lineno = lineno;
3823 /* Parse output from git-ls-tree(1):
3825 * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
3828 #define SIZEOF_TREE_ATTR \
3829 STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
3831 #define SIZEOF_TREE_MODE \
3832 STRING_SIZE("100644 ")
3834 #define TREE_ID_OFFSET \
3835 STRING_SIZE("100644 blob ")
3838 char id[SIZEOF_REV];
3840 time_t time; /* Date from the author ident. */
3841 const char *author; /* Author of the commit. */
3846 tree_path(struct line *line)
3848 return ((struct tree_entry *) line->data)->name;
3853 tree_compare_entry(struct line *line1, struct line *line2)
3855 if (line1->type != line2->type)
3856 return line1->type == LINE_TREE_DIR ? -1 : 1;
3857 return strcmp(tree_path(line1), tree_path(line2));
3860 static struct line *
3861 tree_entry(struct view *view, enum line_type type, const char *path,
3862 const char *mode, const char *id)
3864 struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
3865 struct line *line = entry ? add_line_data(view, entry, type) : NULL;
3867 if (!entry || !line) {
3872 strncpy(entry->name, path, strlen(path));
3874 entry->mode = strtoul(mode, NULL, 8);
3876 string_copy_rev(entry->id, id);
3882 tree_read_date(struct view *view, char *text, bool *read_date)
3884 static const char *author_name;
3885 static time_t author_time;
3887 if (!text && *read_date) {
3892 char *path = *opt_path ? opt_path : ".";
3893 /* Find next entry to process */
3894 const char *log_file[] = {
3895 "git", "log", "--no-color", "--pretty=raw",
3896 "--cc", "--raw", view->id, "--", path, NULL
3901 tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
3902 report("Tree is empty");
3906 if (!run_io_rd(&io, log_file, FORMAT_NONE)) {
3907 report("Failed to load tree data");
3911 done_io(view->pipe);
3916 } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
3917 parse_author_line(text + STRING_SIZE("author "),
3918 &author_name, &author_time);
3920 } else if (*text == ':') {
3922 size_t annotated = 1;
3925 pos = strchr(text, '\t');
3929 if (*opt_prefix && !strncmp(text, opt_prefix, strlen(opt_prefix)))
3930 text += strlen(opt_prefix);
3931 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
3932 text += strlen(opt_path);
3933 pos = strchr(text, '/');
3937 for (i = 1; i < view->lines; i++) {
3938 struct line *line = &view->line[i];
3939 struct tree_entry *entry = line->data;
3941 annotated += !!entry->author;
3942 if (entry->author || strcmp(entry->name, text))
3945 entry->author = author_name;
3946 entry->time = author_time;
3951 if (annotated == view->lines)
3952 kill_io(view->pipe);
3958 tree_read(struct view *view, char *text)
3960 static bool read_date = FALSE;
3961 struct tree_entry *data;
3962 struct line *entry, *line;
3963 enum line_type type;
3964 size_t textlen = text ? strlen(text) : 0;
3965 char *path = text + SIZEOF_TREE_ATTR;
3967 if (read_date || !text)
3968 return tree_read_date(view, text, &read_date);
3970 if (textlen <= SIZEOF_TREE_ATTR)
3972 if (view->lines == 0 &&
3973 !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
3976 /* Strip the path part ... */
3978 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
3979 size_t striplen = strlen(opt_path);
3981 if (pathlen > striplen)
3982 memmove(path, path + striplen,
3983 pathlen - striplen + 1);
3985 /* Insert "link" to parent directory. */
3986 if (view->lines == 1 &&
3987 !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
3991 type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
3992 entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
3997 /* Skip "Directory ..." and ".." line. */
3998 for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
3999 if (tree_compare_entry(line, entry) <= 0)
4002 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4006 for (; line <= entry; line++)
4007 line->dirty = line->cleareol = 1;
4011 if (tree_lineno > view->lineno) {
4012 view->lineno = tree_lineno;
4020 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4022 struct tree_entry *entry = line->data;
4024 if (line->type == LINE_TREE_HEAD) {
4025 if (draw_text(view, line->type, "Directory path /", TRUE))
4028 if (draw_mode(view, entry->mode))
4031 if (opt_author && draw_author(view, entry->author))
4034 if (opt_date && draw_date(view, entry->author ? &entry->time : NULL))
4037 if (draw_text(view, line->type, entry->name, TRUE))
4045 char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4046 int fd = mkstemp(file);
4049 report("Failed to create temporary file");
4050 else if (!run_io_append(blob_ops.argv, FORMAT_ALL, fd))
4051 report("Failed to save blob data to file");
4053 open_editor(FALSE, file);
4059 tree_request(struct view *view, enum request request, struct line *line)
4061 enum open_flags flags;
4064 case REQ_VIEW_BLAME:
4065 if (line->type != LINE_TREE_FILE) {
4066 report("Blame only supported for files");
4070 string_copy(opt_ref, view->vid);
4074 if (line->type != LINE_TREE_FILE) {
4075 report("Edit only supported for files");
4076 } else if (!is_head_commit(view->vid)) {
4079 open_editor(TRUE, opt_file);
4085 /* quit view if at top of tree */
4086 return REQ_VIEW_CLOSE;
4089 line = &view->line[1];
4099 /* Cleanup the stack if the tree view is at a different tree. */
4100 while (!*opt_path && tree_stack)
4101 pop_tree_stack_entry();
4103 switch (line->type) {
4105 /* Depending on whether it is a subdirectory or parent link
4106 * mangle the path buffer. */
4107 if (line == &view->line[1] && *opt_path) {
4108 pop_tree_stack_entry();
4111 const char *basename = tree_path(line);
4113 push_tree_stack_entry(basename, view->lineno);
4116 /* Trees and subtrees share the same ID, so they are not not
4117 * unique like blobs. */
4118 flags = OPEN_RELOAD;
4119 request = REQ_VIEW_TREE;
4122 case LINE_TREE_FILE:
4123 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4124 request = REQ_VIEW_BLOB;
4131 open_view(view, request, flags);
4132 if (request == REQ_VIEW_TREE)
4133 view->lineno = tree_lineno;
4139 tree_grep(struct view *view, struct line *line)
4141 struct tree_entry *entry = line->data;
4142 const char *text[] = {
4144 opt_author ? entry->author : "",
4145 opt_date ? mkdate(&entry->time) : "",
4149 return grep_text(view, text);
4153 tree_select(struct view *view, struct line *line)
4155 struct tree_entry *entry = line->data;
4157 if (line->type == LINE_TREE_FILE) {
4158 string_copy_rev(ref_blob, entry->id);
4159 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4161 } else if (line->type != LINE_TREE_DIR) {
4165 string_copy_rev(view->ref, entry->id);
4168 static const char *tree_argv[SIZEOF_ARG] = {
4169 "git", "ls-tree", "%(commit)", "%(directory)", NULL
4172 static struct view_ops tree_ops = {
4184 blob_read(struct view *view, char *line)
4188 return add_line_text(view, line, LINE_DEFAULT) != NULL;
4192 blob_request(struct view *view, enum request request, struct line *line)
4199 return pager_request(view, request, line);
4203 static const char *blob_argv[SIZEOF_ARG] = {
4204 "git", "cat-file", "blob", "%(blob)", NULL
4207 static struct view_ops blob_ops = {
4221 * Loading the blame view is a two phase job:
4223 * 1. File content is read either using opt_file from the
4224 * filesystem or using git-cat-file.
4225 * 2. Then blame information is incrementally added by
4226 * reading output from git-blame.
4229 static const char *blame_head_argv[] = {
4230 "git", "blame", "--incremental", "--", "%(file)", NULL
4233 static const char *blame_ref_argv[] = {
4234 "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4237 static const char *blame_cat_file_argv[] = {
4238 "git", "cat-file", "blob", "%(ref):%(file)", NULL
4241 struct blame_commit {
4242 char id[SIZEOF_REV]; /* SHA1 ID. */
4243 char title[128]; /* First line of the commit message. */
4244 const char *author; /* Author of the commit. */
4245 time_t time; /* Date from the author ident. */
4246 char filename[128]; /* Name of file. */
4247 bool has_previous; /* Was a "previous" line detected. */
4251 struct blame_commit *commit;
4252 unsigned long lineno;
4257 blame_open(struct view *view)
4259 if (*opt_ref || !io_open(&view->io, opt_file)) {
4260 if (!run_io_rd(&view->io, blame_cat_file_argv, FORMAT_ALL))
4264 setup_update(view, opt_file);
4265 string_format(view->ref, "%s ...", opt_file);
4270 static struct blame_commit *
4271 get_blame_commit(struct view *view, const char *id)
4275 for (i = 0; i < view->lines; i++) {
4276 struct blame *blame = view->line[i].data;
4281 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4282 return blame->commit;
4286 struct blame_commit *commit = calloc(1, sizeof(*commit));
4289 string_ncopy(commit->id, id, SIZEOF_REV);
4295 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4297 const char *pos = *posref;
4300 pos = strchr(pos + 1, ' ');
4301 if (!pos || !isdigit(pos[1]))
4303 *number = atoi(pos + 1);
4304 if (*number < min || *number > max)
4311 static struct blame_commit *
4312 parse_blame_commit(struct view *view, const char *text, int *blamed)
4314 struct blame_commit *commit;
4315 struct blame *blame;
4316 const char *pos = text + SIZEOF_REV - 2;
4317 size_t orig_lineno = 0;
4321 if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
4324 if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
4325 !parse_number(&pos, &lineno, 1, view->lines) ||
4326 !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4329 commit = get_blame_commit(view, text);
4335 struct line *line = &view->line[lineno + group - 1];
4338 blame->commit = commit;
4339 blame->lineno = orig_lineno + group - 1;
4347 blame_read_file(struct view *view, const char *line, bool *read_file)
4350 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
4353 if (view->lines == 0 && !view->parent)
4354 die("No blame exist for %s", view->vid);
4356 if (view->lines == 0 || !run_io_rd(&io, argv, FORMAT_ALL)) {
4357 report("Failed to load blame data");
4361 done_io(view->pipe);
4367 size_t linelen = strlen(line);
4368 struct blame *blame = malloc(sizeof(*blame) + linelen);
4373 blame->commit = NULL;
4374 strncpy(blame->text, line, linelen);
4375 blame->text[linelen] = 0;
4376 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
4381 match_blame_header(const char *name, char **line)
4383 size_t namelen = strlen(name);
4384 bool matched = !strncmp(name, *line, namelen);
4393 blame_read(struct view *view, char *line)
4395 static struct blame_commit *commit = NULL;
4396 static int blamed = 0;
4397 static bool read_file = TRUE;
4400 return blame_read_file(view, line, &read_file);
4407 string_format(view->ref, "%s", view->vid);
4408 if (view_is_displayed(view)) {
4409 update_view_title(view);
4410 redraw_view_from(view, 0);
4416 commit = parse_blame_commit(view, line, &blamed);
4417 string_format(view->ref, "%s %2d%%", view->vid,
4418 view->lines ? blamed * 100 / view->lines : 0);
4420 } else if (match_blame_header("author ", &line)) {
4421 commit->author = get_author(line);
4423 } else if (match_blame_header("author-time ", &line)) {
4424 commit->time = (time_t) atol(line);
4426 } else if (match_blame_header("author-tz ", &line)) {
4427 parse_timezone(&commit->time, line);
4429 } else if (match_blame_header("summary ", &line)) {
4430 string_ncopy(commit->title, line, strlen(line));
4432 } else if (match_blame_header("previous ", &line)) {
4433 commit->has_previous = TRUE;
4435 } else if (match_blame_header("filename ", &line)) {
4436 string_ncopy(commit->filename, line, strlen(line));
4444 blame_draw(struct view *view, struct line *line, unsigned int lineno)
4446 struct blame *blame = line->data;
4447 time_t *time = NULL;
4448 const char *id = NULL, *author = NULL;
4449 char text[SIZEOF_STR];
4451 if (blame->commit && *blame->commit->filename) {
4452 id = blame->commit->id;
4453 author = blame->commit->author;
4454 time = &blame->commit->time;
4457 if (opt_date && draw_date(view, time))
4460 if (opt_author && draw_author(view, author))
4463 if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
4466 if (draw_lineno(view, lineno))
4469 string_expand(text, sizeof(text), blame->text, opt_tab_size);
4470 draw_text(view, LINE_DEFAULT, text, TRUE);
4475 check_blame_commit(struct blame *blame, bool check_null_id)
4478 report("Commit data not loaded yet");
4479 else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
4480 report("No commit exist for the selected line");
4487 setup_blame_parent_line(struct view *view, struct blame *blame)
4489 const char *diff_tree_argv[] = {
4490 "git", "diff-tree", "-U0", blame->commit->id,
4491 "--", blame->commit->filename, NULL
4494 int parent_lineno = -1;
4495 int blamed_lineno = -1;
4498 if (!run_io(&io, diff_tree_argv, NULL, IO_RD))
4501 while ((line = io_get(&io, '\n', TRUE))) {
4503 char *pos = strchr(line, '+');
4505 parent_lineno = atoi(line + 4);
4507 blamed_lineno = atoi(pos + 1);
4509 } else if (*line == '+' && parent_lineno != -1) {
4510 if (blame->lineno == blamed_lineno - 1 &&
4511 !strcmp(blame->text, line + 1)) {
4512 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
4523 blame_request(struct view *view, enum request request, struct line *line)
4525 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4526 struct blame *blame = line->data;
4529 case REQ_VIEW_BLAME:
4530 if (check_blame_commit(blame, TRUE)) {
4531 string_copy(opt_ref, blame->commit->id);
4532 string_copy(opt_file, blame->commit->filename);
4534 view->lineno = blame->lineno;
4535 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4540 if (check_blame_commit(blame, TRUE) &&
4541 select_commit_parent(blame->commit->id, opt_ref,
4542 blame->commit->filename)) {
4543 string_copy(opt_file, blame->commit->filename);
4544 setup_blame_parent_line(view, blame);
4545 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4550 if (!check_blame_commit(blame, FALSE))
4553 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
4554 !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
4557 if (!strcmp(blame->commit->id, NULL_ID)) {
4558 struct view *diff = VIEW(REQ_VIEW_DIFF);
4559 const char *diff_index_argv[] = {
4560 "git", "diff-index", "--root", "--patch-with-stat",
4561 "-C", "-M", "HEAD", "--", view->vid, NULL
4564 if (!blame->commit->has_previous) {
4565 diff_index_argv[1] = "diff";
4566 diff_index_argv[2] = "--no-color";
4567 diff_index_argv[6] = "--";
4568 diff_index_argv[7] = "/dev/null";
4571 if (!prepare_update(diff, diff_index_argv, NULL, FORMAT_DASH)) {
4572 report("Failed to allocate diff command");
4575 flags |= OPEN_PREPARED;
4578 open_view(view, REQ_VIEW_DIFF, flags);
4579 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
4580 string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
4591 blame_grep(struct view *view, struct line *line)
4593 struct blame *blame = line->data;
4594 struct blame_commit *commit = blame->commit;
4595 const char *text[] = {
4597 commit ? commit->title : "",
4598 commit ? commit->id : "",
4599 commit && opt_author ? commit->author : "",
4600 commit && opt_date ? mkdate(&commit->time) : "",
4604 return grep_text(view, text);
4608 blame_select(struct view *view, struct line *line)
4610 struct blame *blame = line->data;
4611 struct blame_commit *commit = blame->commit;
4616 if (!strcmp(commit->id, NULL_ID))
4617 string_ncopy(ref_commit, "HEAD", 4);
4619 string_copy_rev(ref_commit, commit->id);
4622 static struct view_ops blame_ops = {
4638 const char *author; /* Author of the last commit. */
4639 time_t time; /* Date of the last activity. */
4640 struct ref *ref; /* Name and commit ID information. */
4644 branch_draw(struct view *view, struct line *line, unsigned int lineno)
4646 struct branch *branch = line->data;
4647 enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
4649 if (opt_date && draw_date(view, branch->author ? &branch->time : NULL))
4652 if (opt_author && draw_author(view, branch->author))
4655 draw_text(view, type, branch->ref->name, TRUE);
4660 branch_request(struct view *view, enum request request, struct line *line)
4665 open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
4669 open_view(view, REQ_VIEW_MAIN, OPEN_SPLIT);
4678 branch_read(struct view *view, char *line)
4680 static char id[SIZEOF_REV];
4681 struct branch *reference;
4687 switch (get_line_type(line)) {
4689 string_copy_rev(id, line + STRING_SIZE("commit "));
4693 for (i = 0, reference = NULL; i < view->lines; i++) {
4694 struct branch *branch = view->line[i].data;
4696 if (strcmp(branch->ref->id, id))
4699 view->line[i].dirty = TRUE;
4701 branch->author = reference->author;
4702 branch->time = reference->time;
4706 parse_author_line(line + STRING_SIZE("author "),
4707 &branch->author, &branch->time);
4719 branch_open_visitor(void *data, struct ref *ref)
4721 struct view *view = data;
4722 struct branch *branch;
4724 if (ref->tag || ref->ltag || ref->remote)
4727 branch = calloc(1, sizeof(*branch));
4732 return !!add_line_data(view, branch, LINE_DEFAULT);
4736 branch_open(struct view *view)
4738 const char *branch_log[] = {
4739 "git", "log", "--no-color", "--pretty=raw",
4740 "--simplify-by-decoration", "--all", NULL
4743 if (!run_io_rd(&view->io, branch_log, FORMAT_NONE)) {
4744 report("Failed to load branch data");
4748 setup_update(view, view->id);
4749 foreach_ref(branch_open_visitor, view);
4755 branch_grep(struct view *view, struct line *line)
4757 struct branch *branch = line->data;
4758 const char *text[] = {
4764 return grep_text(view, text);
4768 branch_select(struct view *view, struct line *line)
4770 struct branch *branch = line->data;
4772 string_copy_rev(view->ref, branch->ref->id);
4773 string_copy_rev(ref_commit, branch->ref->id);
4774 string_copy_rev(ref_head, branch->ref->id);
4777 static struct view_ops branch_ops = {
4796 char rev[SIZEOF_REV];
4797 char name[SIZEOF_STR];
4801 char rev[SIZEOF_REV];
4802 char name[SIZEOF_STR];
4806 static char status_onbranch[SIZEOF_STR];
4807 static struct status stage_status;
4808 static enum line_type stage_line_type;
4809 static size_t stage_chunks;
4810 static int *stage_chunk;
4812 DEFINE_ALLOCATOR(realloc_ints, int, 32)
4814 /* This should work even for the "On branch" line. */
4816 status_has_none(struct view *view, struct line *line)
4818 return line < view->line + view->lines && !line[1].data;
4821 /* Get fields from the diff line:
4822 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
4825 status_get_diff(struct status *file, const char *buf, size_t bufsize)
4827 const char *old_mode = buf + 1;
4828 const char *new_mode = buf + 8;
4829 const char *old_rev = buf + 15;
4830 const char *new_rev = buf + 56;
4831 const char *status = buf + 97;
4834 old_mode[-1] != ':' ||
4835 new_mode[-1] != ' ' ||
4836 old_rev[-1] != ' ' ||
4837 new_rev[-1] != ' ' ||
4841 file->status = *status;
4843 string_copy_rev(file->old.rev, old_rev);
4844 string_copy_rev(file->new.rev, new_rev);
4846 file->old.mode = strtoul(old_mode, NULL, 8);
4847 file->new.mode = strtoul(new_mode, NULL, 8);
4849 file->old.name[0] = file->new.name[0] = 0;
4855 status_run(struct view *view, const char *argv[], char status, enum line_type type)
4857 struct status *unmerged = NULL;
4861 if (!run_io(&io, argv, NULL, IO_RD))
4864 add_line_data(view, NULL, type);
4866 while ((buf = io_get(&io, 0, TRUE))) {
4867 struct status *file = unmerged;
4870 file = calloc(1, sizeof(*file));
4871 if (!file || !add_line_data(view, file, type))
4875 /* Parse diff info part. */
4877 file->status = status;
4879 string_copy(file->old.rev, NULL_ID);
4881 } else if (!file->status || file == unmerged) {
4882 if (!status_get_diff(file, buf, strlen(buf)))
4885 buf = io_get(&io, 0, TRUE);
4889 /* Collapse all modified entries that follow an
4890 * associated unmerged entry. */
4891 if (unmerged == file) {
4892 unmerged->status = 'U';
4894 } else if (file->status == 'U') {
4899 /* Grab the old name for rename/copy. */
4900 if (!*file->old.name &&
4901 (file->status == 'R' || file->status == 'C')) {
4902 string_ncopy(file->old.name, buf, strlen(buf));
4904 buf = io_get(&io, 0, TRUE);
4909 /* git-ls-files just delivers a NUL separated list of
4910 * file names similar to the second half of the
4911 * git-diff-* output. */
4912 string_ncopy(file->new.name, buf, strlen(buf));
4913 if (!*file->old.name)
4914 string_copy(file->old.name, file->new.name);
4918 if (io_error(&io)) {
4924 if (!view->line[view->lines - 1].data)
4925 add_line_data(view, NULL, LINE_STAT_NONE);
4931 /* Don't show unmerged entries in the staged section. */
4932 static const char *status_diff_index_argv[] = {
4933 "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
4934 "--cached", "-M", "HEAD", NULL
4937 static const char *status_diff_files_argv[] = {
4938 "git", "diff-files", "-z", NULL
4941 static const char *status_list_other_argv[] = {
4942 "git", "ls-files", "-z", "--others", "--exclude-standard", NULL
4945 static const char *status_list_no_head_argv[] = {
4946 "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
4949 static const char *update_index_argv[] = {
4950 "git", "update-index", "-q", "--unmerged", "--refresh", NULL
4953 /* Restore the previous line number to stay in the context or select a
4954 * line with something that can be updated. */
4956 status_restore(struct view *view)
4958 if (view->p_lineno >= view->lines)
4959 view->p_lineno = view->lines - 1;
4960 while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
4962 while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
4965 /* If the above fails, always skip the "On branch" line. */
4966 if (view->p_lineno < view->lines)
4967 view->lineno = view->p_lineno;
4971 if (view->lineno < view->offset)
4972 view->offset = view->lineno;
4973 else if (view->offset + view->height <= view->lineno)
4974 view->offset = view->lineno - view->height + 1;
4976 view->p_restore = FALSE;
4980 status_update_onbranch(void)
4982 static const char *paths[][2] = {
4983 { "rebase-apply/rebasing", "Rebasing" },
4984 { "rebase-apply/applying", "Applying mailbox" },
4985 { "rebase-apply/", "Rebasing mailbox" },
4986 { "rebase-merge/interactive", "Interactive rebase" },
4987 { "rebase-merge/", "Rebase merge" },
4988 { "MERGE_HEAD", "Merging" },
4989 { "BISECT_LOG", "Bisecting" },
4990 { "HEAD", "On branch" },
4992 char buf[SIZEOF_STR];
4996 if (is_initial_commit()) {
4997 string_copy(status_onbranch, "Initial commit");
5001 for (i = 0; i < ARRAY_SIZE(paths); i++) {
5002 char *head = opt_head;
5004 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5005 lstat(buf, &stat) < 0)
5011 if (string_format(buf, "%s/rebase-merge/head-name", opt_git_dir) &&
5012 io_open(&io, buf) &&
5013 io_read_buf(&io, buf, sizeof(buf))) {
5015 if (!prefixcmp(head, "refs/heads/"))
5016 head += STRING_SIZE("refs/heads/");
5020 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5021 string_copy(status_onbranch, opt_head);
5025 string_copy(status_onbranch, "Not currently on any branch");
5028 /* First parse staged info using git-diff-index(1), then parse unstaged
5029 * info using git-diff-files(1), and finally untracked files using
5030 * git-ls-files(1). */
5032 status_open(struct view *view)
5036 add_line_data(view, NULL, LINE_STAT_HEAD);
5037 status_update_onbranch();
5039 run_io_bg(update_index_argv);
5041 if (is_initial_commit()) {
5042 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5044 } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5048 if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5049 !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5052 /* Restore the exact position or use the specialized restore
5054 if (!view->p_restore)
5055 status_restore(view);
5060 status_draw(struct view *view, struct line *line, unsigned int lineno)
5062 struct status *status = line->data;
5063 enum line_type type;
5067 switch (line->type) {
5068 case LINE_STAT_STAGED:
5069 type = LINE_STAT_SECTION;
5070 text = "Changes to be committed:";
5073 case LINE_STAT_UNSTAGED:
5074 type = LINE_STAT_SECTION;
5075 text = "Changed but not updated:";
5078 case LINE_STAT_UNTRACKED:
5079 type = LINE_STAT_SECTION;
5080 text = "Untracked files:";
5083 case LINE_STAT_NONE:
5084 type = LINE_DEFAULT;
5085 text = " (no files)";
5088 case LINE_STAT_HEAD:
5089 type = LINE_STAT_HEAD;
5090 text = status_onbranch;
5097 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5099 buf[0] = status->status;
5100 if (draw_text(view, line->type, buf, TRUE))
5102 type = LINE_DEFAULT;
5103 text = status->new.name;
5106 draw_text(view, type, text, TRUE);
5111 status_load_error(struct view *view, struct view *stage, const char *path)
5113 if (displayed_views() == 2 || display[current_view] != view)
5114 maximize_view(view);
5115 report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5120 status_enter(struct view *view, struct line *line)
5122 struct status *status = line->data;
5123 const char *oldpath = status ? status->old.name : NULL;
5124 /* Diffs for unmerged entries are empty when passing the new
5125 * path, so leave it empty. */
5126 const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5128 enum open_flags split;
5129 struct view *stage = VIEW(REQ_VIEW_STAGE);
5131 if (line->type == LINE_STAT_NONE ||
5132 (!status && line[1].type == LINE_STAT_NONE)) {
5133 report("No file to diff");
5137 switch (line->type) {
5138 case LINE_STAT_STAGED:
5139 if (is_initial_commit()) {
5140 const char *no_head_diff_argv[] = {
5141 "git", "diff", "--no-color", "--patch-with-stat",
5142 "--", "/dev/null", newpath, NULL
5145 if (!prepare_update(stage, no_head_diff_argv, opt_cdup, FORMAT_DASH))
5146 return status_load_error(view, stage, newpath);
5148 const char *index_show_argv[] = {
5149 "git", "diff-index", "--root", "--patch-with-stat",
5150 "-C", "-M", "--cached", "HEAD", "--",
5151 oldpath, newpath, NULL
5154 if (!prepare_update(stage, index_show_argv, opt_cdup, FORMAT_DASH))
5155 return status_load_error(view, stage, newpath);
5159 info = "Staged changes to %s";
5161 info = "Staged changes";
5164 case LINE_STAT_UNSTAGED:
5166 const char *files_show_argv[] = {
5167 "git", "diff-files", "--root", "--patch-with-stat",
5168 "-C", "-M", "--", oldpath, newpath, NULL
5171 if (!prepare_update(stage, files_show_argv, opt_cdup, FORMAT_DASH))
5172 return status_load_error(view, stage, newpath);
5174 info = "Unstaged changes to %s";
5176 info = "Unstaged changes";
5179 case LINE_STAT_UNTRACKED:
5181 report("No file to show");
5185 if (!suffixcmp(status->new.name, -1, "/")) {
5186 report("Cannot display a directory");
5190 if (!prepare_update_file(stage, newpath))
5191 return status_load_error(view, stage, newpath);
5192 info = "Untracked file %s";
5195 case LINE_STAT_HEAD:
5199 die("line type %d not handled in switch", line->type);
5202 split = view_is_displayed(view) ? OPEN_SPLIT : 0;
5203 open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5204 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5206 stage_status = *status;
5208 memset(&stage_status, 0, sizeof(stage_status));
5211 stage_line_type = line->type;
5213 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5220 status_exists(struct status *status, enum line_type type)
5222 struct view *view = VIEW(REQ_VIEW_STATUS);
5223 unsigned long lineno;
5225 for (lineno = 0; lineno < view->lines; lineno++) {
5226 struct line *line = &view->line[lineno];
5227 struct status *pos = line->data;
5229 if (line->type != type)
5231 if (!pos && (!status || !status->status) && line[1].data) {
5232 select_view_line(view, lineno);
5235 if (pos && !strcmp(status->new.name, pos->new.name)) {
5236 select_view_line(view, lineno);
5246 status_update_prepare(struct io *io, enum line_type type)
5248 const char *staged_argv[] = {
5249 "git", "update-index", "-z", "--index-info", NULL
5251 const char *others_argv[] = {
5252 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5256 case LINE_STAT_STAGED:
5257 return run_io(io, staged_argv, opt_cdup, IO_WR);
5259 case LINE_STAT_UNSTAGED:
5260 return run_io(io, others_argv, opt_cdup, IO_WR);
5262 case LINE_STAT_UNTRACKED:
5263 return run_io(io, others_argv, NULL, IO_WR);
5266 die("line type %d not handled in switch", type);
5272 status_update_write(struct io *io, struct status *status, enum line_type type)
5274 char buf[SIZEOF_STR];
5278 case LINE_STAT_STAGED:
5279 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
5282 status->old.name, 0))
5286 case LINE_STAT_UNSTAGED:
5287 case LINE_STAT_UNTRACKED:
5288 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
5293 die("line type %d not handled in switch", type);
5296 return io_write(io, buf, bufsize);
5300 status_update_file(struct status *status, enum line_type type)
5305 if (!status_update_prepare(&io, type))
5308 result = status_update_write(&io, status, type);
5309 return done_io(&io) && result;
5313 status_update_files(struct view *view, struct line *line)
5315 char buf[sizeof(view->ref)];
5318 struct line *pos = view->line + view->lines;
5321 int cursor_y, cursor_x;
5323 if (!status_update_prepare(&io, line->type))
5326 for (pos = line; pos < view->line + view->lines && pos->data; pos++)
5329 string_copy(buf, view->ref);
5330 getsyx(cursor_y, cursor_x);
5331 for (file = 0, done = 5; result && file < files; line++, file++) {
5332 int almost_done = file * 100 / files;
5334 if (almost_done > done) {
5336 string_format(view->ref, "updating file %u of %u (%d%% done)",
5338 update_view_title(view);
5339 setsyx(cursor_y, cursor_x);
5342 result = status_update_write(&io, line->data, line->type);
5344 string_copy(view->ref, buf);
5346 return done_io(&io) && result;
5350 status_update(struct view *view)
5352 struct line *line = &view->line[view->lineno];
5354 assert(view->lines);
5357 /* This should work even for the "On branch" line. */
5358 if (line < view->line + view->lines && !line[1].data) {
5359 report("Nothing to update");
5363 if (!status_update_files(view, line + 1)) {
5364 report("Failed to update file status");
5368 } else if (!status_update_file(line->data, line->type)) {
5369 report("Failed to update file status");
5377 status_revert(struct status *status, enum line_type type, bool has_none)
5379 if (!status || type != LINE_STAT_UNSTAGED) {
5380 if (type == LINE_STAT_STAGED) {
5381 report("Cannot revert changes to staged files");
5382 } else if (type == LINE_STAT_UNTRACKED) {
5383 report("Cannot revert changes to untracked files");
5384 } else if (has_none) {
5385 report("Nothing to revert");
5387 report("Cannot revert changes to multiple files");
5392 char mode[10] = "100644";
5393 const char *reset_argv[] = {
5394 "git", "update-index", "--cacheinfo", mode,
5395 status->old.rev, status->old.name, NULL
5397 const char *checkout_argv[] = {
5398 "git", "checkout", "--", status->old.name, NULL
5401 if (!prompt_yesno("Are you sure you want to overwrite any changes?"))
5403 string_format(mode, "%o", status->old.mode);
5404 return (status->status != 'U' || run_io_fg(reset_argv, opt_cdup)) &&
5405 run_io_fg(checkout_argv, opt_cdup);
5410 status_request(struct view *view, enum request request, struct line *line)
5412 struct status *status = line->data;
5415 case REQ_STATUS_UPDATE:
5416 if (!status_update(view))
5420 case REQ_STATUS_REVERT:
5421 if (!status_revert(status, line->type, status_has_none(view, line)))
5425 case REQ_STATUS_MERGE:
5426 if (!status || status->status != 'U') {
5427 report("Merging only possible for files with unmerged status ('U').");
5430 open_mergetool(status->new.name);
5436 if (status->status == 'D') {
5437 report("File has been deleted.");
5441 open_editor(status->status != '?', status->new.name);
5444 case REQ_VIEW_BLAME:
5446 string_copy(opt_file, status->new.name);
5452 /* After returning the status view has been split to
5453 * show the stage view. No further reloading is
5455 return status_enter(view, line);
5458 /* Simply reload the view. */
5465 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
5471 status_select(struct view *view, struct line *line)
5473 struct status *status = line->data;
5474 char file[SIZEOF_STR] = "all files";
5478 if (status && !string_format(file, "'%s'", status->new.name))
5481 if (!status && line[1].type == LINE_STAT_NONE)
5484 switch (line->type) {
5485 case LINE_STAT_STAGED:
5486 text = "Press %s to unstage %s for commit";
5489 case LINE_STAT_UNSTAGED:
5490 text = "Press %s to stage %s for commit";
5493 case LINE_STAT_UNTRACKED:
5494 text = "Press %s to stage %s for addition";
5497 case LINE_STAT_HEAD:
5498 case LINE_STAT_NONE:
5499 text = "Nothing to update";
5503 die("line type %d not handled in switch", line->type);
5506 if (status && status->status == 'U') {
5507 text = "Press %s to resolve conflict in %s";
5508 key = get_key(REQ_STATUS_MERGE);
5511 key = get_key(REQ_STATUS_UPDATE);
5514 string_format(view->ref, text, key, file);
5518 status_grep(struct view *view, struct line *line)
5520 struct status *status = line->data;
5523 const char buf[2] = { status->status, 0 };
5524 const char *text[] = { status->new.name, buf, NULL };
5526 return grep_text(view, text);
5532 static struct view_ops status_ops = {
5545 stage_diff_write(struct io *io, struct line *line, struct line *end)
5547 while (line < end) {
5548 if (!io_write(io, line->data, strlen(line->data)) ||
5549 !io_write(io, "\n", 1))
5552 if (line->type == LINE_DIFF_CHUNK ||
5553 line->type == LINE_DIFF_HEADER)
5560 static struct line *
5561 stage_diff_find(struct view *view, struct line *line, enum line_type type)
5563 for (; view->line < line; line--)
5564 if (line->type == type)
5571 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
5573 const char *apply_argv[SIZEOF_ARG] = {
5574 "git", "apply", "--whitespace=nowarn", NULL
5576 struct line *diff_hdr;
5580 diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
5585 apply_argv[argc++] = "--cached";
5586 if (revert || stage_line_type == LINE_STAT_STAGED)
5587 apply_argv[argc++] = "-R";
5588 apply_argv[argc++] = "-";
5589 apply_argv[argc++] = NULL;
5590 if (!run_io(&io, apply_argv, opt_cdup, IO_WR))
5593 if (!stage_diff_write(&io, diff_hdr, chunk) ||
5594 !stage_diff_write(&io, chunk, view->line + view->lines))
5598 run_io_bg(update_index_argv);
5600 return chunk ? TRUE : FALSE;
5604 stage_update(struct view *view, struct line *line)
5606 struct line *chunk = NULL;
5608 if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
5609 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5612 if (!stage_apply_chunk(view, chunk, FALSE)) {
5613 report("Failed to apply chunk");
5617 } else if (!stage_status.status) {
5618 view = VIEW(REQ_VIEW_STATUS);
5620 for (line = view->line; line < view->line + view->lines; line++)
5621 if (line->type == stage_line_type)
5624 if (!status_update_files(view, line + 1)) {
5625 report("Failed to update files");
5629 } else if (!status_update_file(&stage_status, stage_line_type)) {
5630 report("Failed to update file");
5638 stage_revert(struct view *view, struct line *line)
5640 struct line *chunk = NULL;
5642 if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
5643 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5646 if (!prompt_yesno("Are you sure you want to revert changes?"))
5649 if (!stage_apply_chunk(view, chunk, TRUE)) {
5650 report("Failed to revert chunk");
5656 return status_revert(stage_status.status ? &stage_status : NULL,
5657 stage_line_type, FALSE);
5663 stage_next(struct view *view, struct line *line)
5667 if (!stage_chunks) {
5668 for (line = view->line; line < view->line + view->lines; line++) {
5669 if (line->type != LINE_DIFF_CHUNK)
5672 if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
5673 report("Allocation failure");
5677 stage_chunk[stage_chunks++] = line - view->line;
5681 for (i = 0; i < stage_chunks; i++) {
5682 if (stage_chunk[i] > view->lineno) {
5683 do_scroll_view(view, stage_chunk[i] - view->lineno);
5684 report("Chunk %d of %d", i + 1, stage_chunks);
5689 report("No next chunk found");
5693 stage_request(struct view *view, enum request request, struct line *line)
5696 case REQ_STATUS_UPDATE:
5697 if (!stage_update(view, line))
5701 case REQ_STATUS_REVERT:
5702 if (!stage_revert(view, line))
5706 case REQ_STAGE_NEXT:
5707 if (stage_line_type == LINE_STAT_UNTRACKED) {
5708 report("File is untracked; press %s to add",
5709 get_key(REQ_STATUS_UPDATE));
5712 stage_next(view, line);
5716 if (!stage_status.new.name[0])
5718 if (stage_status.status == 'D') {
5719 report("File has been deleted.");
5723 open_editor(stage_status.status != '?', stage_status.new.name);
5727 /* Reload everything ... */
5730 case REQ_VIEW_BLAME:
5731 if (stage_status.new.name[0]) {
5732 string_copy(opt_file, stage_status.new.name);
5738 return pager_request(view, request, line);
5744 VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
5745 open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
5747 /* Check whether the staged entry still exists, and close the
5748 * stage view if it doesn't. */
5749 if (!status_exists(&stage_status, stage_line_type)) {
5750 status_restore(VIEW(REQ_VIEW_STATUS));
5751 return REQ_VIEW_CLOSE;
5754 if (stage_line_type == LINE_STAT_UNTRACKED) {
5755 if (!suffixcmp(stage_status.new.name, -1, "/")) {
5756 report("Cannot display a directory");
5760 if (!prepare_update_file(view, stage_status.new.name)) {
5761 report("Failed to open file: %s", strerror(errno));
5765 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
5770 static struct view_ops stage_ops = {
5787 char id[SIZEOF_REV]; /* SHA1 ID. */
5788 char title[128]; /* First line of the commit message. */
5789 const char *author; /* Author of the commit. */
5790 time_t time; /* Date from the author ident. */
5791 struct ref **refs; /* Repository references. */
5792 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
5793 size_t graph_size; /* The width of the graph array. */
5794 bool has_parents; /* Rewritten --parents seen. */
5797 /* Size of rev graph with no "padding" columns */
5798 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
5801 struct rev_graph *prev, *next, *parents;
5802 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
5804 struct commit *commit;
5806 unsigned int boundary:1;
5809 /* Parents of the commit being visualized. */
5810 static struct rev_graph graph_parents[4];
5812 /* The current stack of revisions on the graph. */
5813 static struct rev_graph graph_stacks[4] = {
5814 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
5815 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
5816 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
5817 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
5821 graph_parent_is_merge(struct rev_graph *graph)
5823 return graph->parents->size > 1;
5827 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
5829 struct commit *commit = graph->commit;
5831 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
5832 commit->graph[commit->graph_size++] = symbol;
5836 clear_rev_graph(struct rev_graph *graph)
5838 graph->boundary = 0;
5839 graph->size = graph->pos = 0;
5840 graph->commit = NULL;
5841 memset(graph->parents, 0, sizeof(*graph->parents));
5845 done_rev_graph(struct rev_graph *graph)
5847 if (graph_parent_is_merge(graph) &&
5848 graph->pos < graph->size - 1 &&
5849 graph->next->size == graph->size + graph->parents->size - 1) {
5850 size_t i = graph->pos + graph->parents->size - 1;
5852 graph->commit->graph_size = i * 2;
5853 while (i < graph->next->size - 1) {
5854 append_to_rev_graph(graph, ' ');
5855 append_to_rev_graph(graph, '\\');
5860 clear_rev_graph(graph);
5864 push_rev_graph(struct rev_graph *graph, const char *parent)
5868 /* "Collapse" duplicate parents lines.
5870 * FIXME: This needs to also update update the drawn graph but
5871 * for now it just serves as a method for pruning graph lines. */
5872 for (i = 0; i < graph->size; i++)
5873 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
5876 if (graph->size < SIZEOF_REVITEMS) {
5877 string_copy_rev(graph->rev[graph->size++], parent);
5882 get_rev_graph_symbol(struct rev_graph *graph)
5886 if (graph->boundary)
5887 symbol = REVGRAPH_BOUND;
5888 else if (graph->parents->size == 0)
5889 symbol = REVGRAPH_INIT;
5890 else if (graph_parent_is_merge(graph))
5891 symbol = REVGRAPH_MERGE;
5892 else if (graph->pos >= graph->size)
5893 symbol = REVGRAPH_BRANCH;
5895 symbol = REVGRAPH_COMMIT;
5901 draw_rev_graph(struct rev_graph *graph)
5904 chtype separator, line;
5906 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
5907 static struct rev_filler fillers[] = {
5913 chtype symbol = get_rev_graph_symbol(graph);
5914 struct rev_filler *filler;
5917 if (opt_line_graphics)
5918 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
5920 filler = &fillers[DEFAULT];
5922 for (i = 0; i < graph->pos; i++) {
5923 append_to_rev_graph(graph, filler->line);
5924 if (graph_parent_is_merge(graph->prev) &&
5925 graph->prev->pos == i)
5926 filler = &fillers[RSHARP];
5928 append_to_rev_graph(graph, filler->separator);
5931 /* Place the symbol for this revision. */
5932 append_to_rev_graph(graph, symbol);
5934 if (graph->prev->size > graph->size)
5935 filler = &fillers[RDIAG];
5937 filler = &fillers[DEFAULT];
5941 for (; i < graph->size; i++) {
5942 append_to_rev_graph(graph, filler->separator);
5943 append_to_rev_graph(graph, filler->line);
5944 if (graph_parent_is_merge(graph->prev) &&
5945 i < graph->prev->pos + graph->parents->size)
5946 filler = &fillers[RSHARP];
5947 if (graph->prev->size > graph->size)
5948 filler = &fillers[LDIAG];
5951 if (graph->prev->size > graph->size) {
5952 append_to_rev_graph(graph, filler->separator);
5953 if (filler->line != ' ')
5954 append_to_rev_graph(graph, filler->line);
5958 /* Prepare the next rev graph */
5960 prepare_rev_graph(struct rev_graph *graph)
5964 /* First, traverse all lines of revisions up to the active one. */
5965 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
5966 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
5969 push_rev_graph(graph->next, graph->rev[graph->pos]);
5972 /* Interleave the new revision parent(s). */
5973 for (i = 0; !graph->boundary && i < graph->parents->size; i++)
5974 push_rev_graph(graph->next, graph->parents->rev[i]);
5976 /* Lastly, put any remaining revisions. */
5977 for (i = graph->pos + 1; i < graph->size; i++)
5978 push_rev_graph(graph->next, graph->rev[i]);
5982 update_rev_graph(struct view *view, struct rev_graph *graph)
5984 /* If this is the finalizing update ... */
5986 prepare_rev_graph(graph);
5988 /* Graph visualization needs a one rev look-ahead,
5989 * so the first update doesn't visualize anything. */
5990 if (!graph->prev->commit)
5993 if (view->lines > 2)
5994 view->line[view->lines - 3].dirty = 1;
5995 if (view->lines > 1)
5996 view->line[view->lines - 2].dirty = 1;
5997 draw_rev_graph(graph->prev);
5998 done_rev_graph(graph->prev->prev);
6006 static const char *main_argv[SIZEOF_ARG] = {
6007 "git", "log", "--no-color", "--pretty=raw", "--parents",
6008 "--topo-order", "%(head)", NULL
6012 main_draw(struct view *view, struct line *line, unsigned int lineno)
6014 struct commit *commit = line->data;
6016 if (!commit->author)
6019 if (opt_date && draw_date(view, &commit->time))
6022 if (opt_author && draw_author(view, commit->author))
6025 if (opt_rev_graph && commit->graph_size &&
6026 draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6029 if (opt_show_refs && commit->refs) {
6033 struct ref *ref = commit->refs[i];
6034 enum line_type type;
6037 type = LINE_MAIN_HEAD;
6039 type = LINE_MAIN_LOCAL_TAG;
6041 type = LINE_MAIN_TAG;
6042 else if (ref->tracked)
6043 type = LINE_MAIN_TRACKED;
6044 else if (ref->remote)
6045 type = LINE_MAIN_REMOTE;
6047 type = LINE_MAIN_REF;
6049 if (draw_text(view, type, "[", TRUE) ||
6050 draw_text(view, type, ref->name, TRUE) ||
6051 draw_text(view, type, "]", TRUE))
6054 if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6056 } while (commit->refs[i++]->next);
6059 draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6063 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6065 main_read(struct view *view, char *line)
6067 static struct rev_graph *graph = graph_stacks;
6068 enum line_type type;
6069 struct commit *commit;
6074 if (!view->lines && !view->parent)
6075 die("No revisions match the given arguments.");
6076 if (view->lines > 0) {
6077 commit = view->line[view->lines - 1].data;
6078 view->line[view->lines - 1].dirty = 1;
6079 if (!commit->author) {
6082 graph->commit = NULL;
6085 update_rev_graph(view, graph);
6087 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6088 clear_rev_graph(&graph_stacks[i]);
6092 type = get_line_type(line);
6093 if (type == LINE_COMMIT) {
6094 commit = calloc(1, sizeof(struct commit));
6098 line += STRING_SIZE("commit ");
6100 graph->boundary = 1;
6104 string_copy_rev(commit->id, line);
6105 commit->refs = get_refs(commit->id);
6106 graph->commit = commit;
6107 add_line_data(view, commit, LINE_MAIN_COMMIT);
6109 while ((line = strchr(line, ' '))) {
6111 push_rev_graph(graph->parents, line);
6112 commit->has_parents = TRUE;
6119 commit = view->line[view->lines - 1].data;
6123 if (commit->has_parents)
6125 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6129 parse_author_line(line + STRING_SIZE("author "),
6130 &commit->author, &commit->time);
6131 update_rev_graph(view, graph);
6132 graph = graph->next;
6136 /* Fill in the commit title if it has not already been set. */
6137 if (commit->title[0])
6140 /* Require titles to start with a non-space character at the
6141 * offset used by git log. */
6142 if (strncmp(line, " ", 4))
6145 /* Well, if the title starts with a whitespace character,
6146 * try to be forgiving. Otherwise we end up with no title. */
6147 while (isspace(*line))
6151 /* FIXME: More graceful handling of titles; append "..." to
6152 * shortened titles, etc. */
6154 string_expand(commit->title, sizeof(commit->title), line, 1);
6155 view->line[view->lines - 1].dirty = 1;
6162 main_request(struct view *view, enum request request, struct line *line)
6164 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
6168 open_view(view, REQ_VIEW_DIFF, flags);
6172 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6182 grep_refs(struct ref **refs, regex_t *regex)
6187 if (!opt_show_refs || !refs)
6190 if (regexec(regex, refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6192 } while (refs[i++]->next);
6198 main_grep(struct view *view, struct line *line)
6200 struct commit *commit = line->data;
6201 const char *text[] = {
6203 opt_author ? commit->author : "",
6204 opt_date ? mkdate(&commit->time) : "",
6208 return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6212 main_select(struct view *view, struct line *line)
6214 struct commit *commit = line->data;
6216 string_copy_rev(view->ref, commit->id);
6217 string_copy_rev(ref_commit, view->ref);
6220 static struct view_ops main_ops = {
6233 * Unicode / UTF-8 handling
6235 * NOTE: Much of the following code for dealing with Unicode is derived from
6236 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
6237 * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
6241 unicode_width(unsigned long c)
6244 (c <= 0x115f /* Hangul Jamo */
6247 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
6249 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
6250 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
6251 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
6252 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
6253 || (c >= 0xffe0 && c <= 0xffe6)
6254 || (c >= 0x20000 && c <= 0x2fffd)
6255 || (c >= 0x30000 && c <= 0x3fffd)))
6259 return opt_tab_size;
6264 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
6265 * Illegal bytes are set one. */
6266 static const unsigned char utf8_bytes[256] = {
6267 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,
6268 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,
6269 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,
6270 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,
6271 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,
6272 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,
6273 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,
6274 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,
6277 /* Decode UTF-8 multi-byte representation into a Unicode character. */
6278 static inline unsigned long
6279 utf8_to_unicode(const char *string, size_t length)
6281 unsigned long unicode;
6285 unicode = string[0];
6288 unicode = (string[0] & 0x1f) << 6;
6289 unicode += (string[1] & 0x3f);
6292 unicode = (string[0] & 0x0f) << 12;
6293 unicode += ((string[1] & 0x3f) << 6);
6294 unicode += (string[2] & 0x3f);
6297 unicode = (string[0] & 0x0f) << 18;
6298 unicode += ((string[1] & 0x3f) << 12);
6299 unicode += ((string[2] & 0x3f) << 6);
6300 unicode += (string[3] & 0x3f);
6303 unicode = (string[0] & 0x0f) << 24;
6304 unicode += ((string[1] & 0x3f) << 18);
6305 unicode += ((string[2] & 0x3f) << 12);
6306 unicode += ((string[3] & 0x3f) << 6);
6307 unicode += (string[4] & 0x3f);
6310 unicode = (string[0] & 0x01) << 30;
6311 unicode += ((string[1] & 0x3f) << 24);
6312 unicode += ((string[2] & 0x3f) << 18);
6313 unicode += ((string[3] & 0x3f) << 12);
6314 unicode += ((string[4] & 0x3f) << 6);
6315 unicode += (string[5] & 0x3f);
6318 die("Invalid Unicode length");
6321 /* Invalid characters could return the special 0xfffd value but NUL
6322 * should be just as good. */
6323 return unicode > 0xffff ? 0 : unicode;
6326 /* Calculates how much of string can be shown within the given maximum width
6327 * and sets trimmed parameter to non-zero value if all of string could not be
6328 * shown. If the reserve flag is TRUE, it will reserve at least one
6329 * trailing character, which can be useful when drawing a delimiter.
6331 * Returns the number of bytes to output from string to satisfy max_width. */
6333 utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve)
6335 const char *string = *start;
6336 const char *end = strchr(string, '\0');
6337 unsigned char last_bytes = 0;
6338 size_t last_ucwidth = 0;
6343 while (string < end) {
6344 int c = *(unsigned char *) string;
6345 unsigned char bytes = utf8_bytes[c];
6347 unsigned long unicode;
6349 if (string + bytes > end)
6352 /* Change representation to figure out whether
6353 * it is a single- or double-width character. */
6355 unicode = utf8_to_unicode(string, bytes);
6356 /* FIXME: Graceful handling of invalid Unicode character. */
6360 ucwidth = unicode_width(unicode);
6362 skip -= ucwidth <= skip ? ucwidth : skip;
6366 if (*width > max_width) {
6369 if (reserve && *width == max_width) {
6370 string -= last_bytes;
6371 *width -= last_ucwidth;
6377 last_bytes = ucwidth ? bytes : 0;
6378 last_ucwidth = ucwidth;
6381 return string - *start;
6389 /* Whether or not the curses interface has been initialized. */
6390 static bool cursed = FALSE;
6392 /* Terminal hacks and workarounds. */
6393 static bool use_scroll_redrawwin;
6394 static bool use_scroll_status_wclear;
6396 /* The status window is used for polling keystrokes. */
6397 static WINDOW *status_win;
6399 /* Reading from the prompt? */
6400 static bool input_mode = FALSE;
6402 static bool status_empty = FALSE;
6404 /* Update status and title window. */
6406 report(const char *msg, ...)
6408 struct view *view = display[current_view];
6414 char buf[SIZEOF_STR];
6417 va_start(args, msg);
6418 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6419 buf[sizeof(buf) - 1] = 0;
6420 buf[sizeof(buf) - 2] = '.';
6421 buf[sizeof(buf) - 3] = '.';
6422 buf[sizeof(buf) - 4] = '.';
6428 if (!status_empty || *msg) {
6431 va_start(args, msg);
6433 wmove(status_win, 0, 0);
6434 if (view->has_scrolled && use_scroll_status_wclear)
6437 vwprintw(status_win, msg, args);
6438 status_empty = FALSE;
6440 status_empty = TRUE;
6442 wclrtoeol(status_win);
6443 wnoutrefresh(status_win);
6448 update_view_title(view);
6451 /* Controls when nodelay should be in effect when polling user input. */
6453 set_nonblocking_input(bool loading)
6455 static unsigned int loading_views;
6457 if ((loading == FALSE && loading_views-- == 1) ||
6458 (loading == TRUE && loading_views++ == 0))
6459 nodelay(status_win, loading);
6468 /* Initialize the curses library */
6469 if (isatty(STDIN_FILENO)) {
6470 cursed = !!initscr();
6473 /* Leave stdin and stdout alone when acting as a pager. */
6474 opt_tty = fopen("/dev/tty", "r+");
6476 die("Failed to open /dev/tty");
6477 cursed = !!newterm(NULL, opt_tty, opt_tty);
6481 die("Failed to initialize curses");
6483 nonl(); /* Disable conversion and detect newlines from input. */
6484 cbreak(); /* Take input chars one at a time, no wait for \n */
6485 noecho(); /* Don't echo input */
6486 leaveok(stdscr, FALSE);
6491 getmaxyx(stdscr, y, x);
6492 status_win = newwin(1, 0, y - 1, 0);
6494 die("Failed to create status window");
6496 /* Enable keyboard mapping */
6497 keypad(status_win, TRUE);
6498 wbkgdset(status_win, get_line_attr(LINE_STATUS));
6500 TABSIZE = opt_tab_size;
6501 if (opt_line_graphics) {
6502 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
6505 term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
6506 if (term && !strcmp(term, "gnome-terminal")) {
6507 /* In the gnome-terminal-emulator, the message from
6508 * scrolling up one line when impossible followed by
6509 * scrolling down one line causes corruption of the
6510 * status line. This is fixed by calling wclear. */
6511 use_scroll_status_wclear = TRUE;
6512 use_scroll_redrawwin = FALSE;
6514 } else if (term && !strcmp(term, "xrvt-xpm")) {
6515 /* No problems with full optimizations in xrvt-(unicode)
6517 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
6520 /* When scrolling in (u)xterm the last line in the
6521 * scrolling direction will update slowly. */
6522 use_scroll_redrawwin = TRUE;
6523 use_scroll_status_wclear = FALSE;
6528 get_input(int prompt_position)
6531 int i, key, cursor_y, cursor_x;
6533 if (prompt_position)
6537 foreach_view (view, i) {
6539 if (view_is_displayed(view) && view->has_scrolled &&
6540 use_scroll_redrawwin)
6541 redrawwin(view->win);
6542 view->has_scrolled = FALSE;
6545 /* Update the cursor position. */
6546 if (prompt_position) {
6547 getbegyx(status_win, cursor_y, cursor_x);
6548 cursor_x = prompt_position;
6550 view = display[current_view];
6551 getbegyx(view->win, cursor_y, cursor_x);
6552 cursor_x = view->width - 1;
6553 cursor_y += view->lineno - view->offset;
6555 setsyx(cursor_y, cursor_x);
6557 /* Refresh, accept single keystroke of input */
6559 key = wgetch(status_win);
6561 /* wgetch() with nodelay() enabled returns ERR when
6562 * there's no input. */
6565 } else if (key == KEY_RESIZE) {
6568 getmaxyx(stdscr, height, width);
6570 wresize(status_win, 1, width);
6571 mvwin(status_win, height - 1, 0);
6572 wnoutrefresh(status_win);
6574 redraw_display(TRUE);
6584 prompt_input(const char *prompt, input_handler handler, void *data)
6586 enum input_status status = INPUT_OK;
6587 static char buf[SIZEOF_STR];
6592 while (status == INPUT_OK || status == INPUT_SKIP) {
6595 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
6596 wclrtoeol(status_win);
6598 key = get_input(pos + 1);
6603 status = pos ? INPUT_STOP : INPUT_CANCEL;
6610 status = INPUT_CANCEL;
6614 status = INPUT_CANCEL;
6618 if (pos >= sizeof(buf)) {
6619 report("Input string too long");
6623 status = handler(data, buf, key);
6624 if (status == INPUT_OK)
6625 buf[pos++] = (char) key;
6629 /* Clear the status window */
6630 status_empty = FALSE;
6633 if (status == INPUT_CANCEL)
6641 static enum input_status
6642 prompt_yesno_handler(void *data, char *buf, int c)
6644 if (c == 'y' || c == 'Y')
6646 if (c == 'n' || c == 'N')
6647 return INPUT_CANCEL;
6652 prompt_yesno(const char *prompt)
6654 char prompt2[SIZEOF_STR];
6656 if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
6659 return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
6662 static enum input_status
6663 read_prompt_handler(void *data, char *buf, int c)
6665 return isprint(c) ? INPUT_OK : INPUT_SKIP;
6669 read_prompt(const char *prompt)
6671 return prompt_input(prompt, read_prompt_handler, NULL);
6675 * Repository properties
6678 static struct ref *refs = NULL;
6679 static size_t refs_size = 0;
6681 /* Id <-> ref store */
6682 static struct ref ***id_refs = NULL;
6683 static size_t id_refs_size = 0;
6685 DEFINE_ALLOCATOR(realloc_refs, struct ref, 256)
6686 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
6687 DEFINE_ALLOCATOR(realloc_refs_lists, struct ref **, 8)
6690 compare_refs(const void *ref1_, const void *ref2_)
6692 const struct ref *ref1 = *(const struct ref **)ref1_;
6693 const struct ref *ref2 = *(const struct ref **)ref2_;
6695 if (ref1->tag != ref2->tag)
6696 return ref2->tag - ref1->tag;
6697 if (ref1->ltag != ref2->ltag)
6698 return ref2->ltag - ref2->ltag;
6699 if (ref1->head != ref2->head)
6700 return ref2->head - ref1->head;
6701 if (ref1->tracked != ref2->tracked)
6702 return ref2->tracked - ref1->tracked;
6703 if (ref1->remote != ref2->remote)
6704 return ref2->remote - ref1->remote;
6705 return strcmp(ref1->name, ref2->name);
6709 foreach_ref(bool (*visitor)(void *data, struct ref *ref), void *data)
6713 for (i = 0; i < refs_size; i++)
6714 if (!visitor(data, &refs[i]))
6718 static struct ref **
6719 get_refs(const char *id)
6721 struct ref **ref_list = NULL;
6722 size_t ref_list_size = 0;
6725 for (i = 0; i < id_refs_size; i++)
6726 if (!strcmp(id, id_refs[i][0]->id))
6729 if (!realloc_refs_lists(&id_refs, id_refs_size, 1))
6732 for (i = 0; i < refs_size; i++) {
6733 if (strcmp(id, refs[i].id))
6736 if (!realloc_refs_list(&ref_list, ref_list_size, 1))
6739 ref_list[ref_list_size] = &refs[i];
6740 /* XXX: The properties of the commit chains ensures that we can
6741 * safely modify the shared ref. The repo references will
6742 * always be similar for the same id. */
6743 ref_list[ref_list_size]->next = 1;
6748 qsort(ref_list, ref_list_size, sizeof(*ref_list), compare_refs);
6749 ref_list[ref_list_size - 1]->next = 0;
6750 id_refs[id_refs_size++] = ref_list;
6757 read_ref(char *id, size_t idlen, char *name, size_t namelen)
6762 bool remote = FALSE;
6763 bool tracked = FALSE;
6764 bool check_replace = FALSE;
6767 if (!prefixcmp(name, "refs/tags/")) {
6768 if (!suffixcmp(name, namelen, "^{}")) {
6771 if (refs_size > 0 && refs[refs_size - 1].ltag == TRUE)
6772 check_replace = TRUE;
6778 namelen -= STRING_SIZE("refs/tags/");
6779 name += STRING_SIZE("refs/tags/");
6781 } else if (!prefixcmp(name, "refs/remotes/")) {
6783 namelen -= STRING_SIZE("refs/remotes/");
6784 name += STRING_SIZE("refs/remotes/");
6785 tracked = !strcmp(opt_remote, name);
6787 } else if (!prefixcmp(name, "refs/heads/")) {
6788 namelen -= STRING_SIZE("refs/heads/");
6789 name += STRING_SIZE("refs/heads/");
6790 head = !strncmp(opt_head, name, namelen);
6792 } else if (!strcmp(name, "HEAD")) {
6793 string_ncopy(opt_head_rev, id, idlen);
6797 if (check_replace && !strcmp(name, refs[refs_size - 1].name)) {
6798 /* it's an annotated tag, replace the previous SHA1 with the
6799 * resolved commit id; relies on the fact git-ls-remote lists
6800 * the commit id of an annotated tag right before the commit id
6802 refs[refs_size - 1].ltag = ltag;
6803 string_copy_rev(refs[refs_size - 1].id, id);
6808 if (!realloc_refs(&refs, refs_size, 1))
6811 ref = &refs[refs_size++];
6812 ref->name = malloc(namelen + 1);
6816 strncpy(ref->name, name, namelen);
6817 ref->name[namelen] = 0;
6821 ref->remote = remote;
6822 ref->tracked = tracked;
6823 string_copy_rev(ref->id, id);
6831 const char *head_argv[] = {
6832 "git", "symbolic-ref", "HEAD", NULL
6834 static const char *ls_remote_argv[SIZEOF_ARG] = {
6835 "git", "ls-remote", opt_git_dir, NULL
6837 static bool init = FALSE;
6840 argv_from_env(ls_remote_argv, "TIG_LS_REMOTE");
6847 if (run_io_buf(head_argv, opt_head, sizeof(opt_head)) &&
6848 !prefixcmp(opt_head, "refs/heads/")) {
6849 char *offset = opt_head + STRING_SIZE("refs/heads/");
6851 memmove(opt_head, offset, strlen(offset) + 1);
6854 while (refs_size > 0)
6855 free(refs[--refs_size].name);
6856 while (id_refs_size > 0)
6857 free(id_refs[--id_refs_size]);
6859 return run_io_load(ls_remote_argv, "\t", read_ref);
6863 set_remote_branch(const char *name, const char *value, size_t valuelen)
6865 if (!strcmp(name, ".remote")) {
6866 string_ncopy(opt_remote, value, valuelen);
6868 } else if (*opt_remote && !strcmp(name, ".merge")) {
6869 size_t from = strlen(opt_remote);
6871 if (!prefixcmp(value, "refs/heads/"))
6872 value += STRING_SIZE("refs/heads/");
6874 if (!string_format_from(opt_remote, &from, "/%s", value))
6880 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
6882 const char *argv[SIZEOF_ARG] = { name, "=" };
6883 int argc = 1 + (cmd == option_set_command);
6886 if (!argv_from_string(argv, &argc, value))
6887 config_msg = "Too many option arguments";
6889 error = cmd(argc, argv);
6892 warn("Option 'tig.%s': %s", name, config_msg);
6896 set_environment_variable(const char *name, const char *value)
6898 size_t len = strlen(name) + 1 + strlen(value) + 1;
6899 char *env = malloc(len);
6902 string_nformat(env, len, NULL, "%s=%s", name, value) &&
6910 set_work_tree(const char *value)
6912 char cwd[SIZEOF_STR];
6914 if (!getcwd(cwd, sizeof(cwd)))
6915 die("Failed to get cwd path: %s", strerror(errno));
6916 if (chdir(opt_git_dir) < 0)
6917 die("Failed to chdir(%s): %s", strerror(errno));
6918 if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
6919 die("Failed to get git path: %s", strerror(errno));
6921 die("Failed to chdir(%s): %s", cwd, strerror(errno));
6922 if (chdir(value) < 0)
6923 die("Failed to chdir(%s): %s", value, strerror(errno));
6924 if (!getcwd(cwd, sizeof(cwd)))
6925 die("Failed to get cwd path: %s", strerror(errno));
6926 if (!set_environment_variable("GIT_WORK_TREE", cwd))
6927 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
6928 if (!set_environment_variable("GIT_DIR", opt_git_dir))
6929 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
6930 opt_is_inside_work_tree = TRUE;
6934 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
6936 if (!strcmp(name, "i18n.commitencoding"))
6937 string_ncopy(opt_encoding, value, valuelen);
6939 else if (!strcmp(name, "core.editor"))
6940 string_ncopy(opt_editor, value, valuelen);
6942 else if (!strcmp(name, "core.worktree"))
6943 set_work_tree(value);
6945 else if (!prefixcmp(name, "tig.color."))
6946 set_repo_config_option(name + 10, value, option_color_command);
6948 else if (!prefixcmp(name, "tig.bind."))
6949 set_repo_config_option(name + 9, value, option_bind_command);
6951 else if (!prefixcmp(name, "tig."))
6952 set_repo_config_option(name + 4, value, option_set_command);
6954 else if (*opt_head && !prefixcmp(name, "branch.") &&
6955 !strncmp(name + 7, opt_head, strlen(opt_head)))
6956 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
6962 load_git_config(void)
6964 const char *config_list_argv[] = { "git", GIT_CONFIG, "--list", NULL };
6966 return run_io_load(config_list_argv, "=", read_repo_config_option);
6970 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
6972 if (!opt_git_dir[0]) {
6973 string_ncopy(opt_git_dir, name, namelen);
6975 } else if (opt_is_inside_work_tree == -1) {
6976 /* This can be 3 different values depending on the
6977 * version of git being used. If git-rev-parse does not
6978 * understand --is-inside-work-tree it will simply echo
6979 * the option else either "true" or "false" is printed.
6980 * Default to true for the unknown case. */
6981 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
6983 } else if (*name == '.') {
6984 string_ncopy(opt_cdup, name, namelen);
6987 string_ncopy(opt_prefix, name, namelen);
6994 load_repo_info(void)
6996 const char *rev_parse_argv[] = {
6997 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
6998 "--show-cdup", "--show-prefix", NULL
7001 return run_io_load(rev_parse_argv, "=", read_repo_info);
7009 static const char usage[] =
7010 "tig " TIG_VERSION " (" __DATE__ ")\n"
7012 "Usage: tig [options] [revs] [--] [paths]\n"
7013 " or: tig show [options] [revs] [--] [paths]\n"
7014 " or: tig blame [rev] path\n"
7016 " or: tig < [git command output]\n"
7019 " -v, --version Show version and exit\n"
7020 " -h, --help Show help message and exit";
7022 static void __NORETURN
7025 /* XXX: Restore tty modes and let the OS cleanup the rest! */
7031 static void __NORETURN
7032 die(const char *err, ...)
7038 va_start(args, err);
7039 fputs("tig: ", stderr);
7040 vfprintf(stderr, err, args);
7041 fputs("\n", stderr);
7048 warn(const char *msg, ...)
7052 va_start(args, msg);
7053 fputs("tig warning: ", stderr);
7054 vfprintf(stderr, msg, args);
7055 fputs("\n", stderr);
7060 parse_options(int argc, const char *argv[])
7062 enum request request = REQ_VIEW_MAIN;
7063 const char *subcommand;
7064 bool seen_dashdash = FALSE;
7065 /* XXX: This is vulnerable to the user overriding options
7066 * required for the main view parser. */
7067 const char *custom_argv[SIZEOF_ARG] = {
7068 "git", "log", "--no-color", "--pretty=raw", "--parents",
7069 "--topo-order", NULL
7073 if (!isatty(STDIN_FILENO)) {
7074 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
7075 return REQ_VIEW_PAGER;
7081 subcommand = argv[1];
7082 if (!strcmp(subcommand, "status")) {
7084 warn("ignoring arguments after `%s'", subcommand);
7085 return REQ_VIEW_STATUS;
7087 } else if (!strcmp(subcommand, "blame")) {
7088 if (argc <= 2 || argc > 4)
7089 die("invalid number of options to blame\n\n%s", usage);
7093 string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7097 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7098 return REQ_VIEW_BLAME;
7100 } else if (!strcmp(subcommand, "show")) {
7101 request = REQ_VIEW_DIFF;
7108 custom_argv[1] = subcommand;
7112 for (i = 1 + !!subcommand; i < argc; i++) {
7113 const char *opt = argv[i];
7115 if (seen_dashdash || !strcmp(opt, "--")) {
7116 seen_dashdash = TRUE;
7118 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7119 printf("tig version %s\n", TIG_VERSION);
7122 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7123 printf("%s\n", usage);
7127 custom_argv[j++] = opt;
7128 if (j >= ARRAY_SIZE(custom_argv))
7129 die("command too long");
7132 if (!prepare_update(VIEW(request), custom_argv, NULL, FORMAT_NONE))
7133 die("Failed to format arguments");
7139 main(int argc, const char *argv[])
7141 enum request request = parse_options(argc, argv);
7145 signal(SIGINT, quit);
7146 signal(SIGPIPE, SIG_IGN);
7148 if (setlocale(LC_ALL, "")) {
7149 char *codeset = nl_langinfo(CODESET);
7151 string_ncopy(opt_codeset, codeset, strlen(codeset));
7154 if (load_repo_info() == ERR)
7155 die("Failed to load repo info.");
7157 if (load_options() == ERR)
7158 die("Failed to load user config.");
7160 if (load_git_config() == ERR)
7161 die("Failed to load repo config.");
7163 /* Require a git repository unless when running in pager mode. */
7164 if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7165 die("Not a git repository");
7167 if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
7170 if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
7171 opt_iconv = iconv_open(opt_codeset, opt_encoding);
7172 if (opt_iconv == ICONV_NONE)
7173 die("Failed to initialize character set conversion");
7176 if (load_refs() == ERR)
7177 die("Failed to load refs.");
7179 foreach_view (view, i)
7180 argv_from_env(view->ops->argv, view->cmd_env);
7184 if (request != REQ_NONE)
7185 open_view(NULL, request, OPEN_PREPARED);
7186 request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
7188 while (view_driver(display[current_view], request)) {
7189 int key = get_input(0);
7191 view = display[current_view];
7192 request = get_keybinding(view->keymap, key);
7194 /* Some low-level request handling. This keeps access to
7195 * status_win restricted. */
7199 char *cmd = read_prompt(":");
7201 if (cmd && isdigit(*cmd)) {
7202 int lineno = view->lineno + 1;
7204 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7205 select_view_line(view, lineno - 1);
7208 report("Unable to parse '%s' as a line number", cmd);
7212 struct view *next = VIEW(REQ_VIEW_PAGER);
7213 const char *argv[SIZEOF_ARG] = { "git" };
7216 /* When running random commands, initially show the
7217 * command in the title. However, it maybe later be
7218 * overwritten if a commit line is selected. */
7219 string_ncopy(next->ref, cmd, strlen(cmd));
7221 if (!argv_from_string(argv, &argc, cmd)) {
7222 report("Too many arguments");
7223 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
7224 report("Failed to format command");
7226 open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7234 case REQ_SEARCH_BACK:
7236 const char *prompt = request == REQ_SEARCH ? "/" : "?";
7237 char *search = read_prompt(prompt);
7240 string_ncopy(opt_search, search, strlen(search));
7241 else if (*opt_search)
7242 request = request == REQ_SEARCH ?