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->time))
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];
4686 switch (get_line_type(line)) {
4688 string_copy_rev(id, line + STRING_SIZE("commit "));
4692 for (i = 0; i < view->lines; i++) {
4693 struct branch *branch = view->line[i].data;
4695 if (strcmp(branch->ref->id, id))
4698 parse_author_line(line + STRING_SIZE("author "),
4699 &branch->author, &branch->time);
4700 view->line[i].dirty = TRUE;
4711 branch_open_visitor(void *data, struct ref *ref)
4713 struct view *view = data;
4714 struct branch *branch;
4716 if (ref->tag || ref->ltag || ref->remote)
4719 branch = calloc(1, sizeof(*branch));
4724 return !!add_line_data(view, branch, LINE_DEFAULT);
4728 branch_open(struct view *view)
4730 const char *branch_log[] = {
4731 "git", "log", "--no-color", "--pretty=raw",
4732 "--simplify-by-decoration", "--all", NULL
4735 if (!run_io_rd(&view->io, branch_log, FORMAT_NONE)) {
4736 report("Failed to load branch data");
4740 setup_update(view, view->id);
4741 foreach_ref(branch_open_visitor, view);
4747 branch_grep(struct view *view, struct line *line)
4749 struct branch *branch = line->data;
4750 const char *text[] = {
4756 return grep_text(view, text);
4760 branch_select(struct view *view, struct line *line)
4762 struct branch *branch = line->data;
4764 string_copy_rev(view->ref, branch->ref->id);
4765 string_copy_rev(ref_commit, branch->ref->id);
4766 string_copy_rev(ref_head, branch->ref->id);
4769 static struct view_ops branch_ops = {
4788 char rev[SIZEOF_REV];
4789 char name[SIZEOF_STR];
4793 char rev[SIZEOF_REV];
4794 char name[SIZEOF_STR];
4798 static char status_onbranch[SIZEOF_STR];
4799 static struct status stage_status;
4800 static enum line_type stage_line_type;
4801 static size_t stage_chunks;
4802 static int *stage_chunk;
4804 DEFINE_ALLOCATOR(realloc_ints, int, 32)
4806 /* This should work even for the "On branch" line. */
4808 status_has_none(struct view *view, struct line *line)
4810 return line < view->line + view->lines && !line[1].data;
4813 /* Get fields from the diff line:
4814 * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
4817 status_get_diff(struct status *file, const char *buf, size_t bufsize)
4819 const char *old_mode = buf + 1;
4820 const char *new_mode = buf + 8;
4821 const char *old_rev = buf + 15;
4822 const char *new_rev = buf + 56;
4823 const char *status = buf + 97;
4826 old_mode[-1] != ':' ||
4827 new_mode[-1] != ' ' ||
4828 old_rev[-1] != ' ' ||
4829 new_rev[-1] != ' ' ||
4833 file->status = *status;
4835 string_copy_rev(file->old.rev, old_rev);
4836 string_copy_rev(file->new.rev, new_rev);
4838 file->old.mode = strtoul(old_mode, NULL, 8);
4839 file->new.mode = strtoul(new_mode, NULL, 8);
4841 file->old.name[0] = file->new.name[0] = 0;
4847 status_run(struct view *view, const char *argv[], char status, enum line_type type)
4849 struct status *unmerged = NULL;
4853 if (!run_io(&io, argv, NULL, IO_RD))
4856 add_line_data(view, NULL, type);
4858 while ((buf = io_get(&io, 0, TRUE))) {
4859 struct status *file = unmerged;
4862 file = calloc(1, sizeof(*file));
4863 if (!file || !add_line_data(view, file, type))
4867 /* Parse diff info part. */
4869 file->status = status;
4871 string_copy(file->old.rev, NULL_ID);
4873 } else if (!file->status || file == unmerged) {
4874 if (!status_get_diff(file, buf, strlen(buf)))
4877 buf = io_get(&io, 0, TRUE);
4881 /* Collapse all modified entries that follow an
4882 * associated unmerged entry. */
4883 if (unmerged == file) {
4884 unmerged->status = 'U';
4886 } else if (file->status == 'U') {
4891 /* Grab the old name for rename/copy. */
4892 if (!*file->old.name &&
4893 (file->status == 'R' || file->status == 'C')) {
4894 string_ncopy(file->old.name, buf, strlen(buf));
4896 buf = io_get(&io, 0, TRUE);
4901 /* git-ls-files just delivers a NUL separated list of
4902 * file names similar to the second half of the
4903 * git-diff-* output. */
4904 string_ncopy(file->new.name, buf, strlen(buf));
4905 if (!*file->old.name)
4906 string_copy(file->old.name, file->new.name);
4910 if (io_error(&io)) {
4916 if (!view->line[view->lines - 1].data)
4917 add_line_data(view, NULL, LINE_STAT_NONE);
4923 /* Don't show unmerged entries in the staged section. */
4924 static const char *status_diff_index_argv[] = {
4925 "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
4926 "--cached", "-M", "HEAD", NULL
4929 static const char *status_diff_files_argv[] = {
4930 "git", "diff-files", "-z", NULL
4933 static const char *status_list_other_argv[] = {
4934 "git", "ls-files", "-z", "--others", "--exclude-standard", NULL
4937 static const char *status_list_no_head_argv[] = {
4938 "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
4941 static const char *update_index_argv[] = {
4942 "git", "update-index", "-q", "--unmerged", "--refresh", NULL
4945 /* Restore the previous line number to stay in the context or select a
4946 * line with something that can be updated. */
4948 status_restore(struct view *view)
4950 if (view->p_lineno >= view->lines)
4951 view->p_lineno = view->lines - 1;
4952 while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
4954 while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
4957 /* If the above fails, always skip the "On branch" line. */
4958 if (view->p_lineno < view->lines)
4959 view->lineno = view->p_lineno;
4963 if (view->lineno < view->offset)
4964 view->offset = view->lineno;
4965 else if (view->offset + view->height <= view->lineno)
4966 view->offset = view->lineno - view->height + 1;
4968 view->p_restore = FALSE;
4972 status_update_onbranch(void)
4974 static const char *paths[][2] = {
4975 { "rebase-apply/rebasing", "Rebasing" },
4976 { "rebase-apply/applying", "Applying mailbox" },
4977 { "rebase-apply/", "Rebasing mailbox" },
4978 { "rebase-merge/interactive", "Interactive rebase" },
4979 { "rebase-merge/", "Rebase merge" },
4980 { "MERGE_HEAD", "Merging" },
4981 { "BISECT_LOG", "Bisecting" },
4982 { "HEAD", "On branch" },
4984 char buf[SIZEOF_STR];
4988 if (is_initial_commit()) {
4989 string_copy(status_onbranch, "Initial commit");
4993 for (i = 0; i < ARRAY_SIZE(paths); i++) {
4994 char *head = opt_head;
4996 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
4997 lstat(buf, &stat) < 0)
5003 if (string_format(buf, "%s/rebase-merge/head-name", opt_git_dir) &&
5004 io_open(&io, buf) &&
5005 io_read_buf(&io, buf, sizeof(buf))) {
5007 if (!prefixcmp(head, "refs/heads/"))
5008 head += STRING_SIZE("refs/heads/");
5012 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5013 string_copy(status_onbranch, opt_head);
5017 string_copy(status_onbranch, "Not currently on any branch");
5020 /* First parse staged info using git-diff-index(1), then parse unstaged
5021 * info using git-diff-files(1), and finally untracked files using
5022 * git-ls-files(1). */
5024 status_open(struct view *view)
5028 add_line_data(view, NULL, LINE_STAT_HEAD);
5029 status_update_onbranch();
5031 run_io_bg(update_index_argv);
5033 if (is_initial_commit()) {
5034 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5036 } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5040 if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5041 !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5044 /* Restore the exact position or use the specialized restore
5046 if (!view->p_restore)
5047 status_restore(view);
5052 status_draw(struct view *view, struct line *line, unsigned int lineno)
5054 struct status *status = line->data;
5055 enum line_type type;
5059 switch (line->type) {
5060 case LINE_STAT_STAGED:
5061 type = LINE_STAT_SECTION;
5062 text = "Changes to be committed:";
5065 case LINE_STAT_UNSTAGED:
5066 type = LINE_STAT_SECTION;
5067 text = "Changed but not updated:";
5070 case LINE_STAT_UNTRACKED:
5071 type = LINE_STAT_SECTION;
5072 text = "Untracked files:";
5075 case LINE_STAT_NONE:
5076 type = LINE_DEFAULT;
5077 text = " (no files)";
5080 case LINE_STAT_HEAD:
5081 type = LINE_STAT_HEAD;
5082 text = status_onbranch;
5089 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5091 buf[0] = status->status;
5092 if (draw_text(view, line->type, buf, TRUE))
5094 type = LINE_DEFAULT;
5095 text = status->new.name;
5098 draw_text(view, type, text, TRUE);
5103 status_load_error(struct view *view, struct view *stage, const char *path)
5105 if (displayed_views() == 2 || display[current_view] != view)
5106 maximize_view(view);
5107 report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5112 status_enter(struct view *view, struct line *line)
5114 struct status *status = line->data;
5115 const char *oldpath = status ? status->old.name : NULL;
5116 /* Diffs for unmerged entries are empty when passing the new
5117 * path, so leave it empty. */
5118 const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5120 enum open_flags split;
5121 struct view *stage = VIEW(REQ_VIEW_STAGE);
5123 if (line->type == LINE_STAT_NONE ||
5124 (!status && line[1].type == LINE_STAT_NONE)) {
5125 report("No file to diff");
5129 switch (line->type) {
5130 case LINE_STAT_STAGED:
5131 if (is_initial_commit()) {
5132 const char *no_head_diff_argv[] = {
5133 "git", "diff", "--no-color", "--patch-with-stat",
5134 "--", "/dev/null", newpath, NULL
5137 if (!prepare_update(stage, no_head_diff_argv, opt_cdup, FORMAT_DASH))
5138 return status_load_error(view, stage, newpath);
5140 const char *index_show_argv[] = {
5141 "git", "diff-index", "--root", "--patch-with-stat",
5142 "-C", "-M", "--cached", "HEAD", "--",
5143 oldpath, newpath, NULL
5146 if (!prepare_update(stage, index_show_argv, opt_cdup, FORMAT_DASH))
5147 return status_load_error(view, stage, newpath);
5151 info = "Staged changes to %s";
5153 info = "Staged changes";
5156 case LINE_STAT_UNSTAGED:
5158 const char *files_show_argv[] = {
5159 "git", "diff-files", "--root", "--patch-with-stat",
5160 "-C", "-M", "--", oldpath, newpath, NULL
5163 if (!prepare_update(stage, files_show_argv, opt_cdup, FORMAT_DASH))
5164 return status_load_error(view, stage, newpath);
5166 info = "Unstaged changes to %s";
5168 info = "Unstaged changes";
5171 case LINE_STAT_UNTRACKED:
5173 report("No file to show");
5177 if (!suffixcmp(status->new.name, -1, "/")) {
5178 report("Cannot display a directory");
5182 if (!prepare_update_file(stage, newpath))
5183 return status_load_error(view, stage, newpath);
5184 info = "Untracked file %s";
5187 case LINE_STAT_HEAD:
5191 die("line type %d not handled in switch", line->type);
5194 split = view_is_displayed(view) ? OPEN_SPLIT : 0;
5195 open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5196 if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5198 stage_status = *status;
5200 memset(&stage_status, 0, sizeof(stage_status));
5203 stage_line_type = line->type;
5205 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5212 status_exists(struct status *status, enum line_type type)
5214 struct view *view = VIEW(REQ_VIEW_STATUS);
5215 unsigned long lineno;
5217 for (lineno = 0; lineno < view->lines; lineno++) {
5218 struct line *line = &view->line[lineno];
5219 struct status *pos = line->data;
5221 if (line->type != type)
5223 if (!pos && (!status || !status->status) && line[1].data) {
5224 select_view_line(view, lineno);
5227 if (pos && !strcmp(status->new.name, pos->new.name)) {
5228 select_view_line(view, lineno);
5238 status_update_prepare(struct io *io, enum line_type type)
5240 const char *staged_argv[] = {
5241 "git", "update-index", "-z", "--index-info", NULL
5243 const char *others_argv[] = {
5244 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5248 case LINE_STAT_STAGED:
5249 return run_io(io, staged_argv, opt_cdup, IO_WR);
5251 case LINE_STAT_UNSTAGED:
5252 return run_io(io, others_argv, opt_cdup, IO_WR);
5254 case LINE_STAT_UNTRACKED:
5255 return run_io(io, others_argv, NULL, IO_WR);
5258 die("line type %d not handled in switch", type);
5264 status_update_write(struct io *io, struct status *status, enum line_type type)
5266 char buf[SIZEOF_STR];
5270 case LINE_STAT_STAGED:
5271 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
5274 status->old.name, 0))
5278 case LINE_STAT_UNSTAGED:
5279 case LINE_STAT_UNTRACKED:
5280 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
5285 die("line type %d not handled in switch", type);
5288 return io_write(io, buf, bufsize);
5292 status_update_file(struct status *status, enum line_type type)
5297 if (!status_update_prepare(&io, type))
5300 result = status_update_write(&io, status, type);
5301 return done_io(&io) && result;
5305 status_update_files(struct view *view, struct line *line)
5307 char buf[sizeof(view->ref)];
5310 struct line *pos = view->line + view->lines;
5313 int cursor_y, cursor_x;
5315 if (!status_update_prepare(&io, line->type))
5318 for (pos = line; pos < view->line + view->lines && pos->data; pos++)
5321 string_copy(buf, view->ref);
5322 getsyx(cursor_y, cursor_x);
5323 for (file = 0, done = 5; result && file < files; line++, file++) {
5324 int almost_done = file * 100 / files;
5326 if (almost_done > done) {
5328 string_format(view->ref, "updating file %u of %u (%d%% done)",
5330 update_view_title(view);
5331 setsyx(cursor_y, cursor_x);
5334 result = status_update_write(&io, line->data, line->type);
5336 string_copy(view->ref, buf);
5338 return done_io(&io) && result;
5342 status_update(struct view *view)
5344 struct line *line = &view->line[view->lineno];
5346 assert(view->lines);
5349 /* This should work even for the "On branch" line. */
5350 if (line < view->line + view->lines && !line[1].data) {
5351 report("Nothing to update");
5355 if (!status_update_files(view, line + 1)) {
5356 report("Failed to update file status");
5360 } else if (!status_update_file(line->data, line->type)) {
5361 report("Failed to update file status");
5369 status_revert(struct status *status, enum line_type type, bool has_none)
5371 if (!status || type != LINE_STAT_UNSTAGED) {
5372 if (type == LINE_STAT_STAGED) {
5373 report("Cannot revert changes to staged files");
5374 } else if (type == LINE_STAT_UNTRACKED) {
5375 report("Cannot revert changes to untracked files");
5376 } else if (has_none) {
5377 report("Nothing to revert");
5379 report("Cannot revert changes to multiple files");
5384 char mode[10] = "100644";
5385 const char *reset_argv[] = {
5386 "git", "update-index", "--cacheinfo", mode,
5387 status->old.rev, status->old.name, NULL
5389 const char *checkout_argv[] = {
5390 "git", "checkout", "--", status->old.name, NULL
5393 if (!prompt_yesno("Are you sure you want to overwrite any changes?"))
5395 string_format(mode, "%o", status->old.mode);
5396 return (status->status != 'U' || run_io_fg(reset_argv, opt_cdup)) &&
5397 run_io_fg(checkout_argv, opt_cdup);
5402 status_request(struct view *view, enum request request, struct line *line)
5404 struct status *status = line->data;
5407 case REQ_STATUS_UPDATE:
5408 if (!status_update(view))
5412 case REQ_STATUS_REVERT:
5413 if (!status_revert(status, line->type, status_has_none(view, line)))
5417 case REQ_STATUS_MERGE:
5418 if (!status || status->status != 'U') {
5419 report("Merging only possible for files with unmerged status ('U').");
5422 open_mergetool(status->new.name);
5428 if (status->status == 'D') {
5429 report("File has been deleted.");
5433 open_editor(status->status != '?', status->new.name);
5436 case REQ_VIEW_BLAME:
5438 string_copy(opt_file, status->new.name);
5444 /* After returning the status view has been split to
5445 * show the stage view. No further reloading is
5447 return status_enter(view, line);
5450 /* Simply reload the view. */
5457 open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
5463 status_select(struct view *view, struct line *line)
5465 struct status *status = line->data;
5466 char file[SIZEOF_STR] = "all files";
5470 if (status && !string_format(file, "'%s'", status->new.name))
5473 if (!status && line[1].type == LINE_STAT_NONE)
5476 switch (line->type) {
5477 case LINE_STAT_STAGED:
5478 text = "Press %s to unstage %s for commit";
5481 case LINE_STAT_UNSTAGED:
5482 text = "Press %s to stage %s for commit";
5485 case LINE_STAT_UNTRACKED:
5486 text = "Press %s to stage %s for addition";
5489 case LINE_STAT_HEAD:
5490 case LINE_STAT_NONE:
5491 text = "Nothing to update";
5495 die("line type %d not handled in switch", line->type);
5498 if (status && status->status == 'U') {
5499 text = "Press %s to resolve conflict in %s";
5500 key = get_key(REQ_STATUS_MERGE);
5503 key = get_key(REQ_STATUS_UPDATE);
5506 string_format(view->ref, text, key, file);
5510 status_grep(struct view *view, struct line *line)
5512 struct status *status = line->data;
5515 const char buf[2] = { status->status, 0 };
5516 const char *text[] = { status->new.name, buf, NULL };
5518 return grep_text(view, text);
5524 static struct view_ops status_ops = {
5537 stage_diff_write(struct io *io, struct line *line, struct line *end)
5539 while (line < end) {
5540 if (!io_write(io, line->data, strlen(line->data)) ||
5541 !io_write(io, "\n", 1))
5544 if (line->type == LINE_DIFF_CHUNK ||
5545 line->type == LINE_DIFF_HEADER)
5552 static struct line *
5553 stage_diff_find(struct view *view, struct line *line, enum line_type type)
5555 for (; view->line < line; line--)
5556 if (line->type == type)
5563 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
5565 const char *apply_argv[SIZEOF_ARG] = {
5566 "git", "apply", "--whitespace=nowarn", NULL
5568 struct line *diff_hdr;
5572 diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
5577 apply_argv[argc++] = "--cached";
5578 if (revert || stage_line_type == LINE_STAT_STAGED)
5579 apply_argv[argc++] = "-R";
5580 apply_argv[argc++] = "-";
5581 apply_argv[argc++] = NULL;
5582 if (!run_io(&io, apply_argv, opt_cdup, IO_WR))
5585 if (!stage_diff_write(&io, diff_hdr, chunk) ||
5586 !stage_diff_write(&io, chunk, view->line + view->lines))
5590 run_io_bg(update_index_argv);
5592 return chunk ? TRUE : FALSE;
5596 stage_update(struct view *view, struct line *line)
5598 struct line *chunk = NULL;
5600 if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
5601 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5604 if (!stage_apply_chunk(view, chunk, FALSE)) {
5605 report("Failed to apply chunk");
5609 } else if (!stage_status.status) {
5610 view = VIEW(REQ_VIEW_STATUS);
5612 for (line = view->line; line < view->line + view->lines; line++)
5613 if (line->type == stage_line_type)
5616 if (!status_update_files(view, line + 1)) {
5617 report("Failed to update files");
5621 } else if (!status_update_file(&stage_status, stage_line_type)) {
5622 report("Failed to update file");
5630 stage_revert(struct view *view, struct line *line)
5632 struct line *chunk = NULL;
5634 if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
5635 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
5638 if (!prompt_yesno("Are you sure you want to revert changes?"))
5641 if (!stage_apply_chunk(view, chunk, TRUE)) {
5642 report("Failed to revert chunk");
5648 return status_revert(stage_status.status ? &stage_status : NULL,
5649 stage_line_type, FALSE);
5655 stage_next(struct view *view, struct line *line)
5659 if (!stage_chunks) {
5660 for (line = view->line; line < view->line + view->lines; line++) {
5661 if (line->type != LINE_DIFF_CHUNK)
5664 if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
5665 report("Allocation failure");
5669 stage_chunk[stage_chunks++] = line - view->line;
5673 for (i = 0; i < stage_chunks; i++) {
5674 if (stage_chunk[i] > view->lineno) {
5675 do_scroll_view(view, stage_chunk[i] - view->lineno);
5676 report("Chunk %d of %d", i + 1, stage_chunks);
5681 report("No next chunk found");
5685 stage_request(struct view *view, enum request request, struct line *line)
5688 case REQ_STATUS_UPDATE:
5689 if (!stage_update(view, line))
5693 case REQ_STATUS_REVERT:
5694 if (!stage_revert(view, line))
5698 case REQ_STAGE_NEXT:
5699 if (stage_line_type == LINE_STAT_UNTRACKED) {
5700 report("File is untracked; press %s to add",
5701 get_key(REQ_STATUS_UPDATE));
5704 stage_next(view, line);
5708 if (!stage_status.new.name[0])
5710 if (stage_status.status == 'D') {
5711 report("File has been deleted.");
5715 open_editor(stage_status.status != '?', stage_status.new.name);
5719 /* Reload everything ... */
5722 case REQ_VIEW_BLAME:
5723 if (stage_status.new.name[0]) {
5724 string_copy(opt_file, stage_status.new.name);
5730 return pager_request(view, request, line);
5736 VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
5737 open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
5739 /* Check whether the staged entry still exists, and close the
5740 * stage view if it doesn't. */
5741 if (!status_exists(&stage_status, stage_line_type)) {
5742 status_restore(VIEW(REQ_VIEW_STATUS));
5743 return REQ_VIEW_CLOSE;
5746 if (stage_line_type == LINE_STAT_UNTRACKED) {
5747 if (!suffixcmp(stage_status.new.name, -1, "/")) {
5748 report("Cannot display a directory");
5752 if (!prepare_update_file(view, stage_status.new.name)) {
5753 report("Failed to open file: %s", strerror(errno));
5757 open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
5762 static struct view_ops stage_ops = {
5779 char id[SIZEOF_REV]; /* SHA1 ID. */
5780 char title[128]; /* First line of the commit message. */
5781 const char *author; /* Author of the commit. */
5782 time_t time; /* Date from the author ident. */
5783 struct ref **refs; /* Repository references. */
5784 chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
5785 size_t graph_size; /* The width of the graph array. */
5786 bool has_parents; /* Rewritten --parents seen. */
5789 /* Size of rev graph with no "padding" columns */
5790 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
5793 struct rev_graph *prev, *next, *parents;
5794 char rev[SIZEOF_REVITEMS][SIZEOF_REV];
5796 struct commit *commit;
5798 unsigned int boundary:1;
5801 /* Parents of the commit being visualized. */
5802 static struct rev_graph graph_parents[4];
5804 /* The current stack of revisions on the graph. */
5805 static struct rev_graph graph_stacks[4] = {
5806 { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
5807 { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
5808 { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
5809 { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
5813 graph_parent_is_merge(struct rev_graph *graph)
5815 return graph->parents->size > 1;
5819 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
5821 struct commit *commit = graph->commit;
5823 if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
5824 commit->graph[commit->graph_size++] = symbol;
5828 clear_rev_graph(struct rev_graph *graph)
5830 graph->boundary = 0;
5831 graph->size = graph->pos = 0;
5832 graph->commit = NULL;
5833 memset(graph->parents, 0, sizeof(*graph->parents));
5837 done_rev_graph(struct rev_graph *graph)
5839 if (graph_parent_is_merge(graph) &&
5840 graph->pos < graph->size - 1 &&
5841 graph->next->size == graph->size + graph->parents->size - 1) {
5842 size_t i = graph->pos + graph->parents->size - 1;
5844 graph->commit->graph_size = i * 2;
5845 while (i < graph->next->size - 1) {
5846 append_to_rev_graph(graph, ' ');
5847 append_to_rev_graph(graph, '\\');
5852 clear_rev_graph(graph);
5856 push_rev_graph(struct rev_graph *graph, const char *parent)
5860 /* "Collapse" duplicate parents lines.
5862 * FIXME: This needs to also update update the drawn graph but
5863 * for now it just serves as a method for pruning graph lines. */
5864 for (i = 0; i < graph->size; i++)
5865 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
5868 if (graph->size < SIZEOF_REVITEMS) {
5869 string_copy_rev(graph->rev[graph->size++], parent);
5874 get_rev_graph_symbol(struct rev_graph *graph)
5878 if (graph->boundary)
5879 symbol = REVGRAPH_BOUND;
5880 else if (graph->parents->size == 0)
5881 symbol = REVGRAPH_INIT;
5882 else if (graph_parent_is_merge(graph))
5883 symbol = REVGRAPH_MERGE;
5884 else if (graph->pos >= graph->size)
5885 symbol = REVGRAPH_BRANCH;
5887 symbol = REVGRAPH_COMMIT;
5893 draw_rev_graph(struct rev_graph *graph)
5896 chtype separator, line;
5898 enum { DEFAULT, RSHARP, RDIAG, LDIAG };
5899 static struct rev_filler fillers[] = {
5905 chtype symbol = get_rev_graph_symbol(graph);
5906 struct rev_filler *filler;
5909 if (opt_line_graphics)
5910 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
5912 filler = &fillers[DEFAULT];
5914 for (i = 0; i < graph->pos; i++) {
5915 append_to_rev_graph(graph, filler->line);
5916 if (graph_parent_is_merge(graph->prev) &&
5917 graph->prev->pos == i)
5918 filler = &fillers[RSHARP];
5920 append_to_rev_graph(graph, filler->separator);
5923 /* Place the symbol for this revision. */
5924 append_to_rev_graph(graph, symbol);
5926 if (graph->prev->size > graph->size)
5927 filler = &fillers[RDIAG];
5929 filler = &fillers[DEFAULT];
5933 for (; i < graph->size; i++) {
5934 append_to_rev_graph(graph, filler->separator);
5935 append_to_rev_graph(graph, filler->line);
5936 if (graph_parent_is_merge(graph->prev) &&
5937 i < graph->prev->pos + graph->parents->size)
5938 filler = &fillers[RSHARP];
5939 if (graph->prev->size > graph->size)
5940 filler = &fillers[LDIAG];
5943 if (graph->prev->size > graph->size) {
5944 append_to_rev_graph(graph, filler->separator);
5945 if (filler->line != ' ')
5946 append_to_rev_graph(graph, filler->line);
5950 /* Prepare the next rev graph */
5952 prepare_rev_graph(struct rev_graph *graph)
5956 /* First, traverse all lines of revisions up to the active one. */
5957 for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
5958 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
5961 push_rev_graph(graph->next, graph->rev[graph->pos]);
5964 /* Interleave the new revision parent(s). */
5965 for (i = 0; !graph->boundary && i < graph->parents->size; i++)
5966 push_rev_graph(graph->next, graph->parents->rev[i]);
5968 /* Lastly, put any remaining revisions. */
5969 for (i = graph->pos + 1; i < graph->size; i++)
5970 push_rev_graph(graph->next, graph->rev[i]);
5974 update_rev_graph(struct view *view, struct rev_graph *graph)
5976 /* If this is the finalizing update ... */
5978 prepare_rev_graph(graph);
5980 /* Graph visualization needs a one rev look-ahead,
5981 * so the first update doesn't visualize anything. */
5982 if (!graph->prev->commit)
5985 if (view->lines > 2)
5986 view->line[view->lines - 3].dirty = 1;
5987 if (view->lines > 1)
5988 view->line[view->lines - 2].dirty = 1;
5989 draw_rev_graph(graph->prev);
5990 done_rev_graph(graph->prev->prev);
5998 static const char *main_argv[SIZEOF_ARG] = {
5999 "git", "log", "--no-color", "--pretty=raw", "--parents",
6000 "--topo-order", "%(head)", NULL
6004 main_draw(struct view *view, struct line *line, unsigned int lineno)
6006 struct commit *commit = line->data;
6008 if (!commit->author)
6011 if (opt_date && draw_date(view, &commit->time))
6014 if (opt_author && draw_author(view, commit->author))
6017 if (opt_rev_graph && commit->graph_size &&
6018 draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6021 if (opt_show_refs && commit->refs) {
6025 enum line_type type;
6027 if (commit->refs[i]->head)
6028 type = LINE_MAIN_HEAD;
6029 else if (commit->refs[i]->ltag)
6030 type = LINE_MAIN_LOCAL_TAG;
6031 else if (commit->refs[i]->tag)
6032 type = LINE_MAIN_TAG;
6033 else if (commit->refs[i]->tracked)
6034 type = LINE_MAIN_TRACKED;
6035 else if (commit->refs[i]->remote)
6036 type = LINE_MAIN_REMOTE;
6038 type = LINE_MAIN_REF;
6040 if (draw_text(view, type, "[", TRUE) ||
6041 draw_text(view, type, commit->refs[i]->name, TRUE) ||
6042 draw_text(view, type, "]", TRUE))
6045 if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6047 } while (commit->refs[i++]->next);
6050 draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6054 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6056 main_read(struct view *view, char *line)
6058 static struct rev_graph *graph = graph_stacks;
6059 enum line_type type;
6060 struct commit *commit;
6065 if (!view->lines && !view->parent)
6066 die("No revisions match the given arguments.");
6067 if (view->lines > 0) {
6068 commit = view->line[view->lines - 1].data;
6069 view->line[view->lines - 1].dirty = 1;
6070 if (!commit->author) {
6073 graph->commit = NULL;
6076 update_rev_graph(view, graph);
6078 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6079 clear_rev_graph(&graph_stacks[i]);
6083 type = get_line_type(line);
6084 if (type == LINE_COMMIT) {
6085 commit = calloc(1, sizeof(struct commit));
6089 line += STRING_SIZE("commit ");
6091 graph->boundary = 1;
6095 string_copy_rev(commit->id, line);
6096 commit->refs = get_refs(commit->id);
6097 graph->commit = commit;
6098 add_line_data(view, commit, LINE_MAIN_COMMIT);
6100 while ((line = strchr(line, ' '))) {
6102 push_rev_graph(graph->parents, line);
6103 commit->has_parents = TRUE;
6110 commit = view->line[view->lines - 1].data;
6114 if (commit->has_parents)
6116 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6120 parse_author_line(line + STRING_SIZE("author "),
6121 &commit->author, &commit->time);
6122 update_rev_graph(view, graph);
6123 graph = graph->next;
6127 /* Fill in the commit title if it has not already been set. */
6128 if (commit->title[0])
6131 /* Require titles to start with a non-space character at the
6132 * offset used by git log. */
6133 if (strncmp(line, " ", 4))
6136 /* Well, if the title starts with a whitespace character,
6137 * try to be forgiving. Otherwise we end up with no title. */
6138 while (isspace(*line))
6142 /* FIXME: More graceful handling of titles; append "..." to
6143 * shortened titles, etc. */
6145 string_expand(commit->title, sizeof(commit->title), line, 1);
6146 view->line[view->lines - 1].dirty = 1;
6153 main_request(struct view *view, enum request request, struct line *line)
6155 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
6159 open_view(view, REQ_VIEW_DIFF, flags);
6163 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6173 grep_refs(struct ref **refs, regex_t *regex)
6178 if (!opt_show_refs || !refs)
6181 if (regexec(regex, refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6183 } while (refs[i++]->next);
6189 main_grep(struct view *view, struct line *line)
6191 struct commit *commit = line->data;
6192 const char *text[] = {
6194 opt_author ? commit->author : "",
6195 opt_date ? mkdate(&commit->time) : "",
6199 return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6203 main_select(struct view *view, struct line *line)
6205 struct commit *commit = line->data;
6207 string_copy_rev(view->ref, commit->id);
6208 string_copy_rev(ref_commit, view->ref);
6211 static struct view_ops main_ops = {
6224 * Unicode / UTF-8 handling
6226 * NOTE: Much of the following code for dealing with Unicode is derived from
6227 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
6228 * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
6232 unicode_width(unsigned long c)
6235 (c <= 0x115f /* Hangul Jamo */
6238 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
6240 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
6241 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
6242 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
6243 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
6244 || (c >= 0xffe0 && c <= 0xffe6)
6245 || (c >= 0x20000 && c <= 0x2fffd)
6246 || (c >= 0x30000 && c <= 0x3fffd)))
6250 return opt_tab_size;
6255 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
6256 * Illegal bytes are set one. */
6257 static const unsigned char utf8_bytes[256] = {
6258 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,
6259 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,
6260 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,
6261 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,
6262 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,
6263 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,
6264 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,
6265 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,
6268 /* Decode UTF-8 multi-byte representation into a Unicode character. */
6269 static inline unsigned long
6270 utf8_to_unicode(const char *string, size_t length)
6272 unsigned long unicode;
6276 unicode = string[0];
6279 unicode = (string[0] & 0x1f) << 6;
6280 unicode += (string[1] & 0x3f);
6283 unicode = (string[0] & 0x0f) << 12;
6284 unicode += ((string[1] & 0x3f) << 6);
6285 unicode += (string[2] & 0x3f);
6288 unicode = (string[0] & 0x0f) << 18;
6289 unicode += ((string[1] & 0x3f) << 12);
6290 unicode += ((string[2] & 0x3f) << 6);
6291 unicode += (string[3] & 0x3f);
6294 unicode = (string[0] & 0x0f) << 24;
6295 unicode += ((string[1] & 0x3f) << 18);
6296 unicode += ((string[2] & 0x3f) << 12);
6297 unicode += ((string[3] & 0x3f) << 6);
6298 unicode += (string[4] & 0x3f);
6301 unicode = (string[0] & 0x01) << 30;
6302 unicode += ((string[1] & 0x3f) << 24);
6303 unicode += ((string[2] & 0x3f) << 18);
6304 unicode += ((string[3] & 0x3f) << 12);
6305 unicode += ((string[4] & 0x3f) << 6);
6306 unicode += (string[5] & 0x3f);
6309 die("Invalid Unicode length");
6312 /* Invalid characters could return the special 0xfffd value but NUL
6313 * should be just as good. */
6314 return unicode > 0xffff ? 0 : unicode;
6317 /* Calculates how much of string can be shown within the given maximum width
6318 * and sets trimmed parameter to non-zero value if all of string could not be
6319 * shown. If the reserve flag is TRUE, it will reserve at least one
6320 * trailing character, which can be useful when drawing a delimiter.
6322 * Returns the number of bytes to output from string to satisfy max_width. */
6324 utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve)
6326 const char *string = *start;
6327 const char *end = strchr(string, '\0');
6328 unsigned char last_bytes = 0;
6329 size_t last_ucwidth = 0;
6334 while (string < end) {
6335 int c = *(unsigned char *) string;
6336 unsigned char bytes = utf8_bytes[c];
6338 unsigned long unicode;
6340 if (string + bytes > end)
6343 /* Change representation to figure out whether
6344 * it is a single- or double-width character. */
6346 unicode = utf8_to_unicode(string, bytes);
6347 /* FIXME: Graceful handling of invalid Unicode character. */
6351 ucwidth = unicode_width(unicode);
6353 skip -= ucwidth <= skip ? ucwidth : skip;
6357 if (*width > max_width) {
6360 if (reserve && *width == max_width) {
6361 string -= last_bytes;
6362 *width -= last_ucwidth;
6368 last_bytes = ucwidth ? bytes : 0;
6369 last_ucwidth = ucwidth;
6372 return string - *start;
6380 /* Whether or not the curses interface has been initialized. */
6381 static bool cursed = FALSE;
6383 /* Terminal hacks and workarounds. */
6384 static bool use_scroll_redrawwin;
6385 static bool use_scroll_status_wclear;
6387 /* The status window is used for polling keystrokes. */
6388 static WINDOW *status_win;
6390 /* Reading from the prompt? */
6391 static bool input_mode = FALSE;
6393 static bool status_empty = FALSE;
6395 /* Update status and title window. */
6397 report(const char *msg, ...)
6399 struct view *view = display[current_view];
6405 char buf[SIZEOF_STR];
6408 va_start(args, msg);
6409 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6410 buf[sizeof(buf) - 1] = 0;
6411 buf[sizeof(buf) - 2] = '.';
6412 buf[sizeof(buf) - 3] = '.';
6413 buf[sizeof(buf) - 4] = '.';
6419 if (!status_empty || *msg) {
6422 va_start(args, msg);
6424 wmove(status_win, 0, 0);
6425 if (view->has_scrolled && use_scroll_status_wclear)
6428 vwprintw(status_win, msg, args);
6429 status_empty = FALSE;
6431 status_empty = TRUE;
6433 wclrtoeol(status_win);
6434 wnoutrefresh(status_win);
6439 update_view_title(view);
6442 /* Controls when nodelay should be in effect when polling user input. */
6444 set_nonblocking_input(bool loading)
6446 static unsigned int loading_views;
6448 if ((loading == FALSE && loading_views-- == 1) ||
6449 (loading == TRUE && loading_views++ == 0))
6450 nodelay(status_win, loading);
6459 /* Initialize the curses library */
6460 if (isatty(STDIN_FILENO)) {
6461 cursed = !!initscr();
6464 /* Leave stdin and stdout alone when acting as a pager. */
6465 opt_tty = fopen("/dev/tty", "r+");
6467 die("Failed to open /dev/tty");
6468 cursed = !!newterm(NULL, opt_tty, opt_tty);
6472 die("Failed to initialize curses");
6474 nonl(); /* Disable conversion and detect newlines from input. */
6475 cbreak(); /* Take input chars one at a time, no wait for \n */
6476 noecho(); /* Don't echo input */
6477 leaveok(stdscr, FALSE);
6482 getmaxyx(stdscr, y, x);
6483 status_win = newwin(1, 0, y - 1, 0);
6485 die("Failed to create status window");
6487 /* Enable keyboard mapping */
6488 keypad(status_win, TRUE);
6489 wbkgdset(status_win, get_line_attr(LINE_STATUS));
6491 TABSIZE = opt_tab_size;
6492 if (opt_line_graphics) {
6493 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
6496 term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
6497 if (term && !strcmp(term, "gnome-terminal")) {
6498 /* In the gnome-terminal-emulator, the message from
6499 * scrolling up one line when impossible followed by
6500 * scrolling down one line causes corruption of the
6501 * status line. This is fixed by calling wclear. */
6502 use_scroll_status_wclear = TRUE;
6503 use_scroll_redrawwin = FALSE;
6505 } else if (term && !strcmp(term, "xrvt-xpm")) {
6506 /* No problems with full optimizations in xrvt-(unicode)
6508 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
6511 /* When scrolling in (u)xterm the last line in the
6512 * scrolling direction will update slowly. */
6513 use_scroll_redrawwin = TRUE;
6514 use_scroll_status_wclear = FALSE;
6519 get_input(int prompt_position)
6522 int i, key, cursor_y, cursor_x;
6524 if (prompt_position)
6528 foreach_view (view, i) {
6530 if (view_is_displayed(view) && view->has_scrolled &&
6531 use_scroll_redrawwin)
6532 redrawwin(view->win);
6533 view->has_scrolled = FALSE;
6536 /* Update the cursor position. */
6537 if (prompt_position) {
6538 getbegyx(status_win, cursor_y, cursor_x);
6539 cursor_x = prompt_position;
6541 view = display[current_view];
6542 getbegyx(view->win, cursor_y, cursor_x);
6543 cursor_x = view->width - 1;
6544 cursor_y += view->lineno - view->offset;
6546 setsyx(cursor_y, cursor_x);
6548 /* Refresh, accept single keystroke of input */
6550 key = wgetch(status_win);
6552 /* wgetch() with nodelay() enabled returns ERR when
6553 * there's no input. */
6556 } else if (key == KEY_RESIZE) {
6559 getmaxyx(stdscr, height, width);
6561 wresize(status_win, 1, width);
6562 mvwin(status_win, height - 1, 0);
6563 wnoutrefresh(status_win);
6565 redraw_display(TRUE);
6575 prompt_input(const char *prompt, input_handler handler, void *data)
6577 enum input_status status = INPUT_OK;
6578 static char buf[SIZEOF_STR];
6583 while (status == INPUT_OK || status == INPUT_SKIP) {
6586 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
6587 wclrtoeol(status_win);
6589 key = get_input(pos + 1);
6594 status = pos ? INPUT_STOP : INPUT_CANCEL;
6601 status = INPUT_CANCEL;
6605 status = INPUT_CANCEL;
6609 if (pos >= sizeof(buf)) {
6610 report("Input string too long");
6614 status = handler(data, buf, key);
6615 if (status == INPUT_OK)
6616 buf[pos++] = (char) key;
6620 /* Clear the status window */
6621 status_empty = FALSE;
6624 if (status == INPUT_CANCEL)
6632 static enum input_status
6633 prompt_yesno_handler(void *data, char *buf, int c)
6635 if (c == 'y' || c == 'Y')
6637 if (c == 'n' || c == 'N')
6638 return INPUT_CANCEL;
6643 prompt_yesno(const char *prompt)
6645 char prompt2[SIZEOF_STR];
6647 if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
6650 return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
6653 static enum input_status
6654 read_prompt_handler(void *data, char *buf, int c)
6656 return isprint(c) ? INPUT_OK : INPUT_SKIP;
6660 read_prompt(const char *prompt)
6662 return prompt_input(prompt, read_prompt_handler, NULL);
6666 * Repository properties
6669 static struct ref *refs = NULL;
6670 static size_t refs_size = 0;
6672 /* Id <-> ref store */
6673 static struct ref ***id_refs = NULL;
6674 static size_t id_refs_size = 0;
6676 DEFINE_ALLOCATOR(realloc_refs, struct ref, 256)
6677 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
6678 DEFINE_ALLOCATOR(realloc_refs_lists, struct ref **, 8)
6681 compare_refs(const void *ref1_, const void *ref2_)
6683 const struct ref *ref1 = *(const struct ref **)ref1_;
6684 const struct ref *ref2 = *(const struct ref **)ref2_;
6686 if (ref1->tag != ref2->tag)
6687 return ref2->tag - ref1->tag;
6688 if (ref1->ltag != ref2->ltag)
6689 return ref2->ltag - ref2->ltag;
6690 if (ref1->head != ref2->head)
6691 return ref2->head - ref1->head;
6692 if (ref1->tracked != ref2->tracked)
6693 return ref2->tracked - ref1->tracked;
6694 if (ref1->remote != ref2->remote)
6695 return ref2->remote - ref1->remote;
6696 return strcmp(ref1->name, ref2->name);
6700 foreach_ref(bool (*visitor)(void *data, struct ref *ref), void *data)
6704 for (i = 0; i < refs_size; i++)
6705 if (!visitor(data, &refs[i]))
6709 static struct ref **
6710 get_refs(const char *id)
6712 struct ref **ref_list = NULL;
6713 size_t ref_list_size = 0;
6716 for (i = 0; i < id_refs_size; i++)
6717 if (!strcmp(id, id_refs[i][0]->id))
6720 if (!realloc_refs_lists(&id_refs, id_refs_size, 1))
6723 for (i = 0; i < refs_size; i++) {
6724 if (strcmp(id, refs[i].id))
6727 if (!realloc_refs_list(&ref_list, ref_list_size, 1))
6730 ref_list[ref_list_size] = &refs[i];
6731 /* XXX: The properties of the commit chains ensures that we can
6732 * safely modify the shared ref. The repo references will
6733 * always be similar for the same id. */
6734 ref_list[ref_list_size]->next = 1;
6739 qsort(ref_list, ref_list_size, sizeof(*ref_list), compare_refs);
6740 ref_list[ref_list_size - 1]->next = 0;
6741 id_refs[id_refs_size++] = ref_list;
6748 read_ref(char *id, size_t idlen, char *name, size_t namelen)
6753 bool remote = FALSE;
6754 bool tracked = FALSE;
6755 bool check_replace = FALSE;
6758 if (!prefixcmp(name, "refs/tags/")) {
6759 if (!suffixcmp(name, namelen, "^{}")) {
6762 if (refs_size > 0 && refs[refs_size - 1].ltag == TRUE)
6763 check_replace = TRUE;
6769 namelen -= STRING_SIZE("refs/tags/");
6770 name += STRING_SIZE("refs/tags/");
6772 } else if (!prefixcmp(name, "refs/remotes/")) {
6774 namelen -= STRING_SIZE("refs/remotes/");
6775 name += STRING_SIZE("refs/remotes/");
6776 tracked = !strcmp(opt_remote, name);
6778 } else if (!prefixcmp(name, "refs/heads/")) {
6779 namelen -= STRING_SIZE("refs/heads/");
6780 name += STRING_SIZE("refs/heads/");
6781 head = !strncmp(opt_head, name, namelen);
6783 } else if (!strcmp(name, "HEAD")) {
6784 string_ncopy(opt_head_rev, id, idlen);
6788 if (check_replace && !strcmp(name, refs[refs_size - 1].name)) {
6789 /* it's an annotated tag, replace the previous SHA1 with the
6790 * resolved commit id; relies on the fact git-ls-remote lists
6791 * the commit id of an annotated tag right before the commit id
6793 refs[refs_size - 1].ltag = ltag;
6794 string_copy_rev(refs[refs_size - 1].id, id);
6799 if (!realloc_refs(&refs, refs_size, 1))
6802 ref = &refs[refs_size++];
6803 ref->name = malloc(namelen + 1);
6807 strncpy(ref->name, name, namelen);
6808 ref->name[namelen] = 0;
6812 ref->remote = remote;
6813 ref->tracked = tracked;
6814 string_copy_rev(ref->id, id);
6822 const char *head_argv[] = {
6823 "git", "symbolic-ref", "HEAD", NULL
6825 static const char *ls_remote_argv[SIZEOF_ARG] = {
6826 "git", "ls-remote", opt_git_dir, NULL
6828 static bool init = FALSE;
6831 argv_from_env(ls_remote_argv, "TIG_LS_REMOTE");
6838 if (run_io_buf(head_argv, opt_head, sizeof(opt_head)) &&
6839 !prefixcmp(opt_head, "refs/heads/")) {
6840 char *offset = opt_head + STRING_SIZE("refs/heads/");
6842 memmove(opt_head, offset, strlen(offset) + 1);
6845 while (refs_size > 0)
6846 free(refs[--refs_size].name);
6847 while (id_refs_size > 0)
6848 free(id_refs[--id_refs_size]);
6850 return run_io_load(ls_remote_argv, "\t", read_ref);
6854 set_remote_branch(const char *name, const char *value, size_t valuelen)
6856 if (!strcmp(name, ".remote")) {
6857 string_ncopy(opt_remote, value, valuelen);
6859 } else if (*opt_remote && !strcmp(name, ".merge")) {
6860 size_t from = strlen(opt_remote);
6862 if (!prefixcmp(value, "refs/heads/"))
6863 value += STRING_SIZE("refs/heads/");
6865 if (!string_format_from(opt_remote, &from, "/%s", value))
6871 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
6873 const char *argv[SIZEOF_ARG] = { name, "=" };
6874 int argc = 1 + (cmd == option_set_command);
6877 if (!argv_from_string(argv, &argc, value))
6878 config_msg = "Too many option arguments";
6880 error = cmd(argc, argv);
6883 warn("Option 'tig.%s': %s", name, config_msg);
6887 set_environment_variable(const char *name, const char *value)
6889 size_t len = strlen(name) + 1 + strlen(value) + 1;
6890 char *env = malloc(len);
6893 string_nformat(env, len, NULL, "%s=%s", name, value) &&
6901 set_work_tree(const char *value)
6903 char cwd[SIZEOF_STR];
6905 if (!getcwd(cwd, sizeof(cwd)))
6906 die("Failed to get cwd path: %s", strerror(errno));
6907 if (chdir(opt_git_dir) < 0)
6908 die("Failed to chdir(%s): %s", strerror(errno));
6909 if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
6910 die("Failed to get git path: %s", strerror(errno));
6912 die("Failed to chdir(%s): %s", cwd, strerror(errno));
6913 if (chdir(value) < 0)
6914 die("Failed to chdir(%s): %s", value, strerror(errno));
6915 if (!getcwd(cwd, sizeof(cwd)))
6916 die("Failed to get cwd path: %s", strerror(errno));
6917 if (!set_environment_variable("GIT_WORK_TREE", cwd))
6918 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
6919 if (!set_environment_variable("GIT_DIR", opt_git_dir))
6920 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
6921 opt_is_inside_work_tree = TRUE;
6925 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
6927 if (!strcmp(name, "i18n.commitencoding"))
6928 string_ncopy(opt_encoding, value, valuelen);
6930 else if (!strcmp(name, "core.editor"))
6931 string_ncopy(opt_editor, value, valuelen);
6933 else if (!strcmp(name, "core.worktree"))
6934 set_work_tree(value);
6936 else if (!prefixcmp(name, "tig.color."))
6937 set_repo_config_option(name + 10, value, option_color_command);
6939 else if (!prefixcmp(name, "tig.bind."))
6940 set_repo_config_option(name + 9, value, option_bind_command);
6942 else if (!prefixcmp(name, "tig."))
6943 set_repo_config_option(name + 4, value, option_set_command);
6945 else if (*opt_head && !prefixcmp(name, "branch.") &&
6946 !strncmp(name + 7, opt_head, strlen(opt_head)))
6947 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
6953 load_git_config(void)
6955 const char *config_list_argv[] = { "git", GIT_CONFIG, "--list", NULL };
6957 return run_io_load(config_list_argv, "=", read_repo_config_option);
6961 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
6963 if (!opt_git_dir[0]) {
6964 string_ncopy(opt_git_dir, name, namelen);
6966 } else if (opt_is_inside_work_tree == -1) {
6967 /* This can be 3 different values depending on the
6968 * version of git being used. If git-rev-parse does not
6969 * understand --is-inside-work-tree it will simply echo
6970 * the option else either "true" or "false" is printed.
6971 * Default to true for the unknown case. */
6972 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
6974 } else if (*name == '.') {
6975 string_ncopy(opt_cdup, name, namelen);
6978 string_ncopy(opt_prefix, name, namelen);
6985 load_repo_info(void)
6987 const char *rev_parse_argv[] = {
6988 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
6989 "--show-cdup", "--show-prefix", NULL
6992 return run_io_load(rev_parse_argv, "=", read_repo_info);
7000 static const char usage[] =
7001 "tig " TIG_VERSION " (" __DATE__ ")\n"
7003 "Usage: tig [options] [revs] [--] [paths]\n"
7004 " or: tig show [options] [revs] [--] [paths]\n"
7005 " or: tig blame [rev] path\n"
7007 " or: tig < [git command output]\n"
7010 " -v, --version Show version and exit\n"
7011 " -h, --help Show help message and exit";
7013 static void __NORETURN
7016 /* XXX: Restore tty modes and let the OS cleanup the rest! */
7022 static void __NORETURN
7023 die(const char *err, ...)
7029 va_start(args, err);
7030 fputs("tig: ", stderr);
7031 vfprintf(stderr, err, args);
7032 fputs("\n", stderr);
7039 warn(const char *msg, ...)
7043 va_start(args, msg);
7044 fputs("tig warning: ", stderr);
7045 vfprintf(stderr, msg, args);
7046 fputs("\n", stderr);
7051 parse_options(int argc, const char *argv[])
7053 enum request request = REQ_VIEW_MAIN;
7054 const char *subcommand;
7055 bool seen_dashdash = FALSE;
7056 /* XXX: This is vulnerable to the user overriding options
7057 * required for the main view parser. */
7058 const char *custom_argv[SIZEOF_ARG] = {
7059 "git", "log", "--no-color", "--pretty=raw", "--parents",
7060 "--topo-order", NULL
7064 if (!isatty(STDIN_FILENO)) {
7065 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
7066 return REQ_VIEW_PAGER;
7072 subcommand = argv[1];
7073 if (!strcmp(subcommand, "status")) {
7075 warn("ignoring arguments after `%s'", subcommand);
7076 return REQ_VIEW_STATUS;
7078 } else if (!strcmp(subcommand, "blame")) {
7079 if (argc <= 2 || argc > 4)
7080 die("invalid number of options to blame\n\n%s", usage);
7084 string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7088 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7089 return REQ_VIEW_BLAME;
7091 } else if (!strcmp(subcommand, "show")) {
7092 request = REQ_VIEW_DIFF;
7099 custom_argv[1] = subcommand;
7103 for (i = 1 + !!subcommand; i < argc; i++) {
7104 const char *opt = argv[i];
7106 if (seen_dashdash || !strcmp(opt, "--")) {
7107 seen_dashdash = TRUE;
7109 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7110 printf("tig version %s\n", TIG_VERSION);
7113 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7114 printf("%s\n", usage);
7118 custom_argv[j++] = opt;
7119 if (j >= ARRAY_SIZE(custom_argv))
7120 die("command too long");
7123 if (!prepare_update(VIEW(request), custom_argv, NULL, FORMAT_NONE))
7124 die("Failed to format arguments");
7130 main(int argc, const char *argv[])
7132 enum request request = parse_options(argc, argv);
7136 signal(SIGINT, quit);
7137 signal(SIGPIPE, SIG_IGN);
7139 if (setlocale(LC_ALL, "")) {
7140 char *codeset = nl_langinfo(CODESET);
7142 string_ncopy(opt_codeset, codeset, strlen(codeset));
7145 if (load_repo_info() == ERR)
7146 die("Failed to load repo info.");
7148 if (load_options() == ERR)
7149 die("Failed to load user config.");
7151 if (load_git_config() == ERR)
7152 die("Failed to load repo config.");
7154 /* Require a git repository unless when running in pager mode. */
7155 if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7156 die("Not a git repository");
7158 if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
7161 if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
7162 opt_iconv = iconv_open(opt_codeset, opt_encoding);
7163 if (opt_iconv == ICONV_NONE)
7164 die("Failed to initialize character set conversion");
7167 if (load_refs() == ERR)
7168 die("Failed to load refs.");
7170 foreach_view (view, i)
7171 argv_from_env(view->ops->argv, view->cmd_env);
7175 if (request != REQ_NONE)
7176 open_view(NULL, request, OPEN_PREPARED);
7177 request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
7179 while (view_driver(display[current_view], request)) {
7180 int key = get_input(0);
7182 view = display[current_view];
7183 request = get_keybinding(view->keymap, key);
7185 /* Some low-level request handling. This keeps access to
7186 * status_win restricted. */
7190 char *cmd = read_prompt(":");
7192 if (cmd && isdigit(*cmd)) {
7193 int lineno = view->lineno + 1;
7195 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7196 select_view_line(view, lineno - 1);
7199 report("Unable to parse '%s' as a line number", cmd);
7203 struct view *next = VIEW(REQ_VIEW_PAGER);
7204 const char *argv[SIZEOF_ARG] = { "git" };
7207 /* When running random commands, initially show the
7208 * command in the title. However, it maybe later be
7209 * overwritten if a commit line is selected. */
7210 string_ncopy(next->ref, cmd, strlen(cmd));
7212 if (!argv_from_string(argv, &argc, cmd)) {
7213 report("Too many arguments");
7214 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
7215 report("Failed to format command");
7217 open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7225 case REQ_SEARCH_BACK:
7227 const char *prompt = request == REQ_SEARCH ? "/" : "?";
7228 char *search = read_prompt(prompt);
7231 string_ncopy(opt_search, search, strlen(search));
7232 else if (*opt_search)
7233 request = request == REQ_SEARCH ?