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 struct ref *ref = commit->refs[i];
6026 enum line_type type;
6029 type = LINE_MAIN_HEAD;
6031 type = LINE_MAIN_LOCAL_TAG;
6033 type = LINE_MAIN_TAG;
6034 else if (ref->tracked)
6035 type = LINE_MAIN_TRACKED;
6036 else if (ref->remote)
6037 type = LINE_MAIN_REMOTE;
6039 type = LINE_MAIN_REF;
6041 if (draw_text(view, type, "[", TRUE) ||
6042 draw_text(view, type, ref->name, TRUE) ||
6043 draw_text(view, type, "]", TRUE))
6046 if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6048 } while (commit->refs[i++]->next);
6051 draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6055 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6057 main_read(struct view *view, char *line)
6059 static struct rev_graph *graph = graph_stacks;
6060 enum line_type type;
6061 struct commit *commit;
6066 if (!view->lines && !view->parent)
6067 die("No revisions match the given arguments.");
6068 if (view->lines > 0) {
6069 commit = view->line[view->lines - 1].data;
6070 view->line[view->lines - 1].dirty = 1;
6071 if (!commit->author) {
6074 graph->commit = NULL;
6077 update_rev_graph(view, graph);
6079 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6080 clear_rev_graph(&graph_stacks[i]);
6084 type = get_line_type(line);
6085 if (type == LINE_COMMIT) {
6086 commit = calloc(1, sizeof(struct commit));
6090 line += STRING_SIZE("commit ");
6092 graph->boundary = 1;
6096 string_copy_rev(commit->id, line);
6097 commit->refs = get_refs(commit->id);
6098 graph->commit = commit;
6099 add_line_data(view, commit, LINE_MAIN_COMMIT);
6101 while ((line = strchr(line, ' '))) {
6103 push_rev_graph(graph->parents, line);
6104 commit->has_parents = TRUE;
6111 commit = view->line[view->lines - 1].data;
6115 if (commit->has_parents)
6117 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6121 parse_author_line(line + STRING_SIZE("author "),
6122 &commit->author, &commit->time);
6123 update_rev_graph(view, graph);
6124 graph = graph->next;
6128 /* Fill in the commit title if it has not already been set. */
6129 if (commit->title[0])
6132 /* Require titles to start with a non-space character at the
6133 * offset used by git log. */
6134 if (strncmp(line, " ", 4))
6137 /* Well, if the title starts with a whitespace character,
6138 * try to be forgiving. Otherwise we end up with no title. */
6139 while (isspace(*line))
6143 /* FIXME: More graceful handling of titles; append "..." to
6144 * shortened titles, etc. */
6146 string_expand(commit->title, sizeof(commit->title), line, 1);
6147 view->line[view->lines - 1].dirty = 1;
6154 main_request(struct view *view, enum request request, struct line *line)
6156 enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
6160 open_view(view, REQ_VIEW_DIFF, flags);
6164 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6174 grep_refs(struct ref **refs, regex_t *regex)
6179 if (!opt_show_refs || !refs)
6182 if (regexec(regex, refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6184 } while (refs[i++]->next);
6190 main_grep(struct view *view, struct line *line)
6192 struct commit *commit = line->data;
6193 const char *text[] = {
6195 opt_author ? commit->author : "",
6196 opt_date ? mkdate(&commit->time) : "",
6200 return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6204 main_select(struct view *view, struct line *line)
6206 struct commit *commit = line->data;
6208 string_copy_rev(view->ref, commit->id);
6209 string_copy_rev(ref_commit, view->ref);
6212 static struct view_ops main_ops = {
6225 * Unicode / UTF-8 handling
6227 * NOTE: Much of the following code for dealing with Unicode is derived from
6228 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
6229 * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
6233 unicode_width(unsigned long c)
6236 (c <= 0x115f /* Hangul Jamo */
6239 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
6241 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
6242 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
6243 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
6244 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
6245 || (c >= 0xffe0 && c <= 0xffe6)
6246 || (c >= 0x20000 && c <= 0x2fffd)
6247 || (c >= 0x30000 && c <= 0x3fffd)))
6251 return opt_tab_size;
6256 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
6257 * Illegal bytes are set one. */
6258 static const unsigned char utf8_bytes[256] = {
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 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,
6265 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,
6266 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,
6269 /* Decode UTF-8 multi-byte representation into a Unicode character. */
6270 static inline unsigned long
6271 utf8_to_unicode(const char *string, size_t length)
6273 unsigned long unicode;
6277 unicode = string[0];
6280 unicode = (string[0] & 0x1f) << 6;
6281 unicode += (string[1] & 0x3f);
6284 unicode = (string[0] & 0x0f) << 12;
6285 unicode += ((string[1] & 0x3f) << 6);
6286 unicode += (string[2] & 0x3f);
6289 unicode = (string[0] & 0x0f) << 18;
6290 unicode += ((string[1] & 0x3f) << 12);
6291 unicode += ((string[2] & 0x3f) << 6);
6292 unicode += (string[3] & 0x3f);
6295 unicode = (string[0] & 0x0f) << 24;
6296 unicode += ((string[1] & 0x3f) << 18);
6297 unicode += ((string[2] & 0x3f) << 12);
6298 unicode += ((string[3] & 0x3f) << 6);
6299 unicode += (string[4] & 0x3f);
6302 unicode = (string[0] & 0x01) << 30;
6303 unicode += ((string[1] & 0x3f) << 24);
6304 unicode += ((string[2] & 0x3f) << 18);
6305 unicode += ((string[3] & 0x3f) << 12);
6306 unicode += ((string[4] & 0x3f) << 6);
6307 unicode += (string[5] & 0x3f);
6310 die("Invalid Unicode length");
6313 /* Invalid characters could return the special 0xfffd value but NUL
6314 * should be just as good. */
6315 return unicode > 0xffff ? 0 : unicode;
6318 /* Calculates how much of string can be shown within the given maximum width
6319 * and sets trimmed parameter to non-zero value if all of string could not be
6320 * shown. If the reserve flag is TRUE, it will reserve at least one
6321 * trailing character, which can be useful when drawing a delimiter.
6323 * Returns the number of bytes to output from string to satisfy max_width. */
6325 utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve)
6327 const char *string = *start;
6328 const char *end = strchr(string, '\0');
6329 unsigned char last_bytes = 0;
6330 size_t last_ucwidth = 0;
6335 while (string < end) {
6336 int c = *(unsigned char *) string;
6337 unsigned char bytes = utf8_bytes[c];
6339 unsigned long unicode;
6341 if (string + bytes > end)
6344 /* Change representation to figure out whether
6345 * it is a single- or double-width character. */
6347 unicode = utf8_to_unicode(string, bytes);
6348 /* FIXME: Graceful handling of invalid Unicode character. */
6352 ucwidth = unicode_width(unicode);
6354 skip -= ucwidth <= skip ? ucwidth : skip;
6358 if (*width > max_width) {
6361 if (reserve && *width == max_width) {
6362 string -= last_bytes;
6363 *width -= last_ucwidth;
6369 last_bytes = ucwidth ? bytes : 0;
6370 last_ucwidth = ucwidth;
6373 return string - *start;
6381 /* Whether or not the curses interface has been initialized. */
6382 static bool cursed = FALSE;
6384 /* Terminal hacks and workarounds. */
6385 static bool use_scroll_redrawwin;
6386 static bool use_scroll_status_wclear;
6388 /* The status window is used for polling keystrokes. */
6389 static WINDOW *status_win;
6391 /* Reading from the prompt? */
6392 static bool input_mode = FALSE;
6394 static bool status_empty = FALSE;
6396 /* Update status and title window. */
6398 report(const char *msg, ...)
6400 struct view *view = display[current_view];
6406 char buf[SIZEOF_STR];
6409 va_start(args, msg);
6410 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6411 buf[sizeof(buf) - 1] = 0;
6412 buf[sizeof(buf) - 2] = '.';
6413 buf[sizeof(buf) - 3] = '.';
6414 buf[sizeof(buf) - 4] = '.';
6420 if (!status_empty || *msg) {
6423 va_start(args, msg);
6425 wmove(status_win, 0, 0);
6426 if (view->has_scrolled && use_scroll_status_wclear)
6429 vwprintw(status_win, msg, args);
6430 status_empty = FALSE;
6432 status_empty = TRUE;
6434 wclrtoeol(status_win);
6435 wnoutrefresh(status_win);
6440 update_view_title(view);
6443 /* Controls when nodelay should be in effect when polling user input. */
6445 set_nonblocking_input(bool loading)
6447 static unsigned int loading_views;
6449 if ((loading == FALSE && loading_views-- == 1) ||
6450 (loading == TRUE && loading_views++ == 0))
6451 nodelay(status_win, loading);
6460 /* Initialize the curses library */
6461 if (isatty(STDIN_FILENO)) {
6462 cursed = !!initscr();
6465 /* Leave stdin and stdout alone when acting as a pager. */
6466 opt_tty = fopen("/dev/tty", "r+");
6468 die("Failed to open /dev/tty");
6469 cursed = !!newterm(NULL, opt_tty, opt_tty);
6473 die("Failed to initialize curses");
6475 nonl(); /* Disable conversion and detect newlines from input. */
6476 cbreak(); /* Take input chars one at a time, no wait for \n */
6477 noecho(); /* Don't echo input */
6478 leaveok(stdscr, FALSE);
6483 getmaxyx(stdscr, y, x);
6484 status_win = newwin(1, 0, y - 1, 0);
6486 die("Failed to create status window");
6488 /* Enable keyboard mapping */
6489 keypad(status_win, TRUE);
6490 wbkgdset(status_win, get_line_attr(LINE_STATUS));
6492 TABSIZE = opt_tab_size;
6493 if (opt_line_graphics) {
6494 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
6497 term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
6498 if (term && !strcmp(term, "gnome-terminal")) {
6499 /* In the gnome-terminal-emulator, the message from
6500 * scrolling up one line when impossible followed by
6501 * scrolling down one line causes corruption of the
6502 * status line. This is fixed by calling wclear. */
6503 use_scroll_status_wclear = TRUE;
6504 use_scroll_redrawwin = FALSE;
6506 } else if (term && !strcmp(term, "xrvt-xpm")) {
6507 /* No problems with full optimizations in xrvt-(unicode)
6509 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
6512 /* When scrolling in (u)xterm the last line in the
6513 * scrolling direction will update slowly. */
6514 use_scroll_redrawwin = TRUE;
6515 use_scroll_status_wclear = FALSE;
6520 get_input(int prompt_position)
6523 int i, key, cursor_y, cursor_x;
6525 if (prompt_position)
6529 foreach_view (view, i) {
6531 if (view_is_displayed(view) && view->has_scrolled &&
6532 use_scroll_redrawwin)
6533 redrawwin(view->win);
6534 view->has_scrolled = FALSE;
6537 /* Update the cursor position. */
6538 if (prompt_position) {
6539 getbegyx(status_win, cursor_y, cursor_x);
6540 cursor_x = prompt_position;
6542 view = display[current_view];
6543 getbegyx(view->win, cursor_y, cursor_x);
6544 cursor_x = view->width - 1;
6545 cursor_y += view->lineno - view->offset;
6547 setsyx(cursor_y, cursor_x);
6549 /* Refresh, accept single keystroke of input */
6551 key = wgetch(status_win);
6553 /* wgetch() with nodelay() enabled returns ERR when
6554 * there's no input. */
6557 } else if (key == KEY_RESIZE) {
6560 getmaxyx(stdscr, height, width);
6562 wresize(status_win, 1, width);
6563 mvwin(status_win, height - 1, 0);
6564 wnoutrefresh(status_win);
6566 redraw_display(TRUE);
6576 prompt_input(const char *prompt, input_handler handler, void *data)
6578 enum input_status status = INPUT_OK;
6579 static char buf[SIZEOF_STR];
6584 while (status == INPUT_OK || status == INPUT_SKIP) {
6587 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
6588 wclrtoeol(status_win);
6590 key = get_input(pos + 1);
6595 status = pos ? INPUT_STOP : INPUT_CANCEL;
6602 status = INPUT_CANCEL;
6606 status = INPUT_CANCEL;
6610 if (pos >= sizeof(buf)) {
6611 report("Input string too long");
6615 status = handler(data, buf, key);
6616 if (status == INPUT_OK)
6617 buf[pos++] = (char) key;
6621 /* Clear the status window */
6622 status_empty = FALSE;
6625 if (status == INPUT_CANCEL)
6633 static enum input_status
6634 prompt_yesno_handler(void *data, char *buf, int c)
6636 if (c == 'y' || c == 'Y')
6638 if (c == 'n' || c == 'N')
6639 return INPUT_CANCEL;
6644 prompt_yesno(const char *prompt)
6646 char prompt2[SIZEOF_STR];
6648 if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
6651 return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
6654 static enum input_status
6655 read_prompt_handler(void *data, char *buf, int c)
6657 return isprint(c) ? INPUT_OK : INPUT_SKIP;
6661 read_prompt(const char *prompt)
6663 return prompt_input(prompt, read_prompt_handler, NULL);
6667 * Repository properties
6670 static struct ref *refs = NULL;
6671 static size_t refs_size = 0;
6673 /* Id <-> ref store */
6674 static struct ref ***id_refs = NULL;
6675 static size_t id_refs_size = 0;
6677 DEFINE_ALLOCATOR(realloc_refs, struct ref, 256)
6678 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
6679 DEFINE_ALLOCATOR(realloc_refs_lists, struct ref **, 8)
6682 compare_refs(const void *ref1_, const void *ref2_)
6684 const struct ref *ref1 = *(const struct ref **)ref1_;
6685 const struct ref *ref2 = *(const struct ref **)ref2_;
6687 if (ref1->tag != ref2->tag)
6688 return ref2->tag - ref1->tag;
6689 if (ref1->ltag != ref2->ltag)
6690 return ref2->ltag - ref2->ltag;
6691 if (ref1->head != ref2->head)
6692 return ref2->head - ref1->head;
6693 if (ref1->tracked != ref2->tracked)
6694 return ref2->tracked - ref1->tracked;
6695 if (ref1->remote != ref2->remote)
6696 return ref2->remote - ref1->remote;
6697 return strcmp(ref1->name, ref2->name);
6701 foreach_ref(bool (*visitor)(void *data, struct ref *ref), void *data)
6705 for (i = 0; i < refs_size; i++)
6706 if (!visitor(data, &refs[i]))
6710 static struct ref **
6711 get_refs(const char *id)
6713 struct ref **ref_list = NULL;
6714 size_t ref_list_size = 0;
6717 for (i = 0; i < id_refs_size; i++)
6718 if (!strcmp(id, id_refs[i][0]->id))
6721 if (!realloc_refs_lists(&id_refs, id_refs_size, 1))
6724 for (i = 0; i < refs_size; i++) {
6725 if (strcmp(id, refs[i].id))
6728 if (!realloc_refs_list(&ref_list, ref_list_size, 1))
6731 ref_list[ref_list_size] = &refs[i];
6732 /* XXX: The properties of the commit chains ensures that we can
6733 * safely modify the shared ref. The repo references will
6734 * always be similar for the same id. */
6735 ref_list[ref_list_size]->next = 1;
6740 qsort(ref_list, ref_list_size, sizeof(*ref_list), compare_refs);
6741 ref_list[ref_list_size - 1]->next = 0;
6742 id_refs[id_refs_size++] = ref_list;
6749 read_ref(char *id, size_t idlen, char *name, size_t namelen)
6754 bool remote = FALSE;
6755 bool tracked = FALSE;
6756 bool check_replace = FALSE;
6759 if (!prefixcmp(name, "refs/tags/")) {
6760 if (!suffixcmp(name, namelen, "^{}")) {
6763 if (refs_size > 0 && refs[refs_size - 1].ltag == TRUE)
6764 check_replace = TRUE;
6770 namelen -= STRING_SIZE("refs/tags/");
6771 name += STRING_SIZE("refs/tags/");
6773 } else if (!prefixcmp(name, "refs/remotes/")) {
6775 namelen -= STRING_SIZE("refs/remotes/");
6776 name += STRING_SIZE("refs/remotes/");
6777 tracked = !strcmp(opt_remote, name);
6779 } else if (!prefixcmp(name, "refs/heads/")) {
6780 namelen -= STRING_SIZE("refs/heads/");
6781 name += STRING_SIZE("refs/heads/");
6782 head = !strncmp(opt_head, name, namelen);
6784 } else if (!strcmp(name, "HEAD")) {
6785 string_ncopy(opt_head_rev, id, idlen);
6789 if (check_replace && !strcmp(name, refs[refs_size - 1].name)) {
6790 /* it's an annotated tag, replace the previous SHA1 with the
6791 * resolved commit id; relies on the fact git-ls-remote lists
6792 * the commit id of an annotated tag right before the commit id
6794 refs[refs_size - 1].ltag = ltag;
6795 string_copy_rev(refs[refs_size - 1].id, id);
6800 if (!realloc_refs(&refs, refs_size, 1))
6803 ref = &refs[refs_size++];
6804 ref->name = malloc(namelen + 1);
6808 strncpy(ref->name, name, namelen);
6809 ref->name[namelen] = 0;
6813 ref->remote = remote;
6814 ref->tracked = tracked;
6815 string_copy_rev(ref->id, id);
6823 const char *head_argv[] = {
6824 "git", "symbolic-ref", "HEAD", NULL
6826 static const char *ls_remote_argv[SIZEOF_ARG] = {
6827 "git", "ls-remote", opt_git_dir, NULL
6829 static bool init = FALSE;
6832 argv_from_env(ls_remote_argv, "TIG_LS_REMOTE");
6839 if (run_io_buf(head_argv, opt_head, sizeof(opt_head)) &&
6840 !prefixcmp(opt_head, "refs/heads/")) {
6841 char *offset = opt_head + STRING_SIZE("refs/heads/");
6843 memmove(opt_head, offset, strlen(offset) + 1);
6846 while (refs_size > 0)
6847 free(refs[--refs_size].name);
6848 while (id_refs_size > 0)
6849 free(id_refs[--id_refs_size]);
6851 return run_io_load(ls_remote_argv, "\t", read_ref);
6855 set_remote_branch(const char *name, const char *value, size_t valuelen)
6857 if (!strcmp(name, ".remote")) {
6858 string_ncopy(opt_remote, value, valuelen);
6860 } else if (*opt_remote && !strcmp(name, ".merge")) {
6861 size_t from = strlen(opt_remote);
6863 if (!prefixcmp(value, "refs/heads/"))
6864 value += STRING_SIZE("refs/heads/");
6866 if (!string_format_from(opt_remote, &from, "/%s", value))
6872 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
6874 const char *argv[SIZEOF_ARG] = { name, "=" };
6875 int argc = 1 + (cmd == option_set_command);
6878 if (!argv_from_string(argv, &argc, value))
6879 config_msg = "Too many option arguments";
6881 error = cmd(argc, argv);
6884 warn("Option 'tig.%s': %s", name, config_msg);
6888 set_environment_variable(const char *name, const char *value)
6890 size_t len = strlen(name) + 1 + strlen(value) + 1;
6891 char *env = malloc(len);
6894 string_nformat(env, len, NULL, "%s=%s", name, value) &&
6902 set_work_tree(const char *value)
6904 char cwd[SIZEOF_STR];
6906 if (!getcwd(cwd, sizeof(cwd)))
6907 die("Failed to get cwd path: %s", strerror(errno));
6908 if (chdir(opt_git_dir) < 0)
6909 die("Failed to chdir(%s): %s", strerror(errno));
6910 if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
6911 die("Failed to get git path: %s", strerror(errno));
6913 die("Failed to chdir(%s): %s", cwd, strerror(errno));
6914 if (chdir(value) < 0)
6915 die("Failed to chdir(%s): %s", value, strerror(errno));
6916 if (!getcwd(cwd, sizeof(cwd)))
6917 die("Failed to get cwd path: %s", strerror(errno));
6918 if (!set_environment_variable("GIT_WORK_TREE", cwd))
6919 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
6920 if (!set_environment_variable("GIT_DIR", opt_git_dir))
6921 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
6922 opt_is_inside_work_tree = TRUE;
6926 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
6928 if (!strcmp(name, "i18n.commitencoding"))
6929 string_ncopy(opt_encoding, value, valuelen);
6931 else if (!strcmp(name, "core.editor"))
6932 string_ncopy(opt_editor, value, valuelen);
6934 else if (!strcmp(name, "core.worktree"))
6935 set_work_tree(value);
6937 else if (!prefixcmp(name, "tig.color."))
6938 set_repo_config_option(name + 10, value, option_color_command);
6940 else if (!prefixcmp(name, "tig.bind."))
6941 set_repo_config_option(name + 9, value, option_bind_command);
6943 else if (!prefixcmp(name, "tig."))
6944 set_repo_config_option(name + 4, value, option_set_command);
6946 else if (*opt_head && !prefixcmp(name, "branch.") &&
6947 !strncmp(name + 7, opt_head, strlen(opt_head)))
6948 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
6954 load_git_config(void)
6956 const char *config_list_argv[] = { "git", GIT_CONFIG, "--list", NULL };
6958 return run_io_load(config_list_argv, "=", read_repo_config_option);
6962 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
6964 if (!opt_git_dir[0]) {
6965 string_ncopy(opt_git_dir, name, namelen);
6967 } else if (opt_is_inside_work_tree == -1) {
6968 /* This can be 3 different values depending on the
6969 * version of git being used. If git-rev-parse does not
6970 * understand --is-inside-work-tree it will simply echo
6971 * the option else either "true" or "false" is printed.
6972 * Default to true for the unknown case. */
6973 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
6975 } else if (*name == '.') {
6976 string_ncopy(opt_cdup, name, namelen);
6979 string_ncopy(opt_prefix, name, namelen);
6986 load_repo_info(void)
6988 const char *rev_parse_argv[] = {
6989 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
6990 "--show-cdup", "--show-prefix", NULL
6993 return run_io_load(rev_parse_argv, "=", read_repo_info);
7001 static const char usage[] =
7002 "tig " TIG_VERSION " (" __DATE__ ")\n"
7004 "Usage: tig [options] [revs] [--] [paths]\n"
7005 " or: tig show [options] [revs] [--] [paths]\n"
7006 " or: tig blame [rev] path\n"
7008 " or: tig < [git command output]\n"
7011 " -v, --version Show version and exit\n"
7012 " -h, --help Show help message and exit";
7014 static void __NORETURN
7017 /* XXX: Restore tty modes and let the OS cleanup the rest! */
7023 static void __NORETURN
7024 die(const char *err, ...)
7030 va_start(args, err);
7031 fputs("tig: ", stderr);
7032 vfprintf(stderr, err, args);
7033 fputs("\n", stderr);
7040 warn(const char *msg, ...)
7044 va_start(args, msg);
7045 fputs("tig warning: ", stderr);
7046 vfprintf(stderr, msg, args);
7047 fputs("\n", stderr);
7052 parse_options(int argc, const char *argv[])
7054 enum request request = REQ_VIEW_MAIN;
7055 const char *subcommand;
7056 bool seen_dashdash = FALSE;
7057 /* XXX: This is vulnerable to the user overriding options
7058 * required for the main view parser. */
7059 const char *custom_argv[SIZEOF_ARG] = {
7060 "git", "log", "--no-color", "--pretty=raw", "--parents",
7061 "--topo-order", NULL
7065 if (!isatty(STDIN_FILENO)) {
7066 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
7067 return REQ_VIEW_PAGER;
7073 subcommand = argv[1];
7074 if (!strcmp(subcommand, "status")) {
7076 warn("ignoring arguments after `%s'", subcommand);
7077 return REQ_VIEW_STATUS;
7079 } else if (!strcmp(subcommand, "blame")) {
7080 if (argc <= 2 || argc > 4)
7081 die("invalid number of options to blame\n\n%s", usage);
7085 string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7089 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7090 return REQ_VIEW_BLAME;
7092 } else if (!strcmp(subcommand, "show")) {
7093 request = REQ_VIEW_DIFF;
7100 custom_argv[1] = subcommand;
7104 for (i = 1 + !!subcommand; i < argc; i++) {
7105 const char *opt = argv[i];
7107 if (seen_dashdash || !strcmp(opt, "--")) {
7108 seen_dashdash = TRUE;
7110 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7111 printf("tig version %s\n", TIG_VERSION);
7114 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7115 printf("%s\n", usage);
7119 custom_argv[j++] = opt;
7120 if (j >= ARRAY_SIZE(custom_argv))
7121 die("command too long");
7124 if (!prepare_update(VIEW(request), custom_argv, NULL, FORMAT_NONE))
7125 die("Failed to format arguments");
7131 main(int argc, const char *argv[])
7133 enum request request = parse_options(argc, argv);
7137 signal(SIGINT, quit);
7138 signal(SIGPIPE, SIG_IGN);
7140 if (setlocale(LC_ALL, "")) {
7141 char *codeset = nl_langinfo(CODESET);
7143 string_ncopy(opt_codeset, codeset, strlen(codeset));
7146 if (load_repo_info() == ERR)
7147 die("Failed to load repo info.");
7149 if (load_options() == ERR)
7150 die("Failed to load user config.");
7152 if (load_git_config() == ERR)
7153 die("Failed to load repo config.");
7155 /* Require a git repository unless when running in pager mode. */
7156 if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7157 die("Not a git repository");
7159 if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
7162 if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
7163 opt_iconv = iconv_open(opt_codeset, opt_encoding);
7164 if (opt_iconv == ICONV_NONE)
7165 die("Failed to initialize character set conversion");
7168 if (load_refs() == ERR)
7169 die("Failed to load refs.");
7171 foreach_view (view, i)
7172 argv_from_env(view->ops->argv, view->cmd_env);
7176 if (request != REQ_NONE)
7177 open_view(NULL, request, OPEN_PREPARED);
7178 request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
7180 while (view_driver(display[current_view], request)) {
7181 int key = get_input(0);
7183 view = display[current_view];
7184 request = get_keybinding(view->keymap, key);
7186 /* Some low-level request handling. This keeps access to
7187 * status_win restricted. */
7191 char *cmd = read_prompt(":");
7193 if (cmd && isdigit(*cmd)) {
7194 int lineno = view->lineno + 1;
7196 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7197 select_view_line(view, lineno - 1);
7200 report("Unable to parse '%s' as a line number", cmd);
7204 struct view *next = VIEW(REQ_VIEW_PAGER);
7205 const char *argv[SIZEOF_ARG] = { "git" };
7208 /* When running random commands, initially show the
7209 * command in the title. However, it maybe later be
7210 * overwritten if a commit line is selected. */
7211 string_ncopy(next->ref, cmd, strlen(cmd));
7213 if (!argv_from_string(argv, &argc, cmd)) {
7214 report("Too many arguments");
7215 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
7216 report("Failed to format command");
7218 open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7226 case REQ_SEARCH_BACK:
7228 const char *prompt = request == REQ_SEARCH ? "/" : "?";
7229 char *search = read_prompt(prompt);
7232 string_ncopy(opt_search, search, strlen(search));
7233 else if (*opt_search)
7234 request = request == REQ_SEARCH ?