Code

IO API: replace init_io_fd with io_open which calls fopen(3)
[tig.git] / tig.c
1 /* Copyright (c) 2006-2008 Jonas Fonseca <fonseca@diku.dk>
2  *
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.
7  *
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.
12  */
14 #ifdef HAVE_CONFIG_H
15 #include "config.h"
16 #endif
18 #ifndef TIG_VERSION
19 #define TIG_VERSION "unknown-version"
20 #endif
22 #ifndef DEBUG
23 #define NDEBUG
24 #endif
26 #include <assert.h>
27 #include <errno.h>
28 #include <ctype.h>
29 #include <signal.h>
30 #include <stdarg.h>
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <sys/types.h>
35 #include <sys/stat.h>
36 #include <unistd.h>
37 #include <time.h>
38 #include <fcntl.h>
40 #include <regex.h>
42 #include <locale.h>
43 #include <langinfo.h>
44 #include <iconv.h>
46 /* ncurses(3): Must be defined to have extended wide-character functions. */
47 #define _XOPEN_SOURCE_EXTENDED
49 #ifdef HAVE_NCURSESW_NCURSES_H
50 #include <ncursesw/ncurses.h>
51 #else
52 #ifdef HAVE_NCURSES_NCURSES_H
53 #include <ncurses/ncurses.h>
54 #else
55 #include <ncurses.h>
56 #endif
57 #endif
59 #if __GNUC__ >= 3
60 #define __NORETURN __attribute__((__noreturn__))
61 #else
62 #define __NORETURN
63 #endif
65 static void __NORETURN die(const char *err, ...);
66 static void warn(const char *msg, ...);
67 static void report(const char *msg, ...);
68 static void set_nonblocking_input(bool loading);
69 static size_t utf8_length(const char *string, int *width, size_t max_width, int *trimmed, bool reserve);
70 static bool prompt_yesno(const char *prompt);
71 static int load_refs(void);
73 #define ABS(x)          ((x) >= 0  ? (x) : -(x))
74 #define MIN(x, y)       ((x) < (y) ? (x) :  (y))
76 #define ARRAY_SIZE(x)   (sizeof(x) / sizeof(x[0]))
77 #define STRING_SIZE(x)  (sizeof(x) - 1)
79 #define SIZEOF_STR      1024    /* Default string size. */
80 #define SIZEOF_REF      256     /* Size of symbolic or SHA1 ID. */
81 #define SIZEOF_REV      41      /* Holds a SHA-1 and an ending NUL. */
82 #define SIZEOF_ARG      32      /* Default argument array size. */
84 /* Revision graph */
86 #define REVGRAPH_INIT   'I'
87 #define REVGRAPH_MERGE  'M'
88 #define REVGRAPH_BRANCH '+'
89 #define REVGRAPH_COMMIT '*'
90 #define REVGRAPH_BOUND  '^'
92 #define SIZEOF_REVGRAPH 19      /* Size of revision ancestry graphics. */
94 /* This color name can be used to refer to the default term colors. */
95 #define COLOR_DEFAULT   (-1)
97 #define ICONV_NONE      ((iconv_t) -1)
98 #ifndef ICONV_CONST
99 #define ICONV_CONST     /* nothing */
100 #endif
102 /* The format and size of the date column in the main view. */
103 #define DATE_FORMAT     "%Y-%m-%d %H:%M"
104 #define DATE_COLS       STRING_SIZE("2006-04-29 14:21 ")
106 #define AUTHOR_COLS     20
107 #define ID_COLS         8
109 /* The default interval between line numbers. */
110 #define NUMBER_INTERVAL 5
112 #define TAB_SIZE        8
114 #define SCALE_SPLIT_VIEW(height)        ((height) * 2 / 3)
116 #define NULL_ID         "0000000000000000000000000000000000000000"
118 #ifndef GIT_CONFIG
119 #define GIT_CONFIG "config"
120 #endif
122 /* Some ascii-shorthands fitted into the ncurses namespace. */
123 #define KEY_TAB         '\t'
124 #define KEY_RETURN      '\r'
125 #define KEY_ESC         27
128 struct ref {
129         char *name;             /* Ref name; tag or head names are shortened. */
130         char id[SIZEOF_REV];    /* Commit SHA1 ID */
131         unsigned int head:1;    /* Is it the current HEAD? */
132         unsigned int tag:1;     /* Is it a tag? */
133         unsigned int ltag:1;    /* If so, is the tag local? */
134         unsigned int remote:1;  /* Is it a remote ref? */
135         unsigned int tracked:1; /* Is it the remote for the current HEAD? */
136         unsigned int next:1;    /* For ref lists: are there more refs? */
137 };
139 static struct ref **get_refs(const char *id);
141 enum format_flags {
142         FORMAT_ALL,             /* Perform replacement in all arguments. */
143         FORMAT_DASH,            /* Perform replacement up until "--". */
144         FORMAT_NONE             /* No replacement should be performed. */
145 };
147 static bool format_command(char dst[], const char *src[], enum format_flags flags);
148 static bool format_argv(const char *dst[], const char *src[], enum format_flags flags);
150 struct int_map {
151         const char *name;
152         int namelen;
153         int value;
154 };
156 static int
157 set_from_int_map(struct int_map *map, size_t map_size,
158                  int *value, const char *name, int namelen)
161         int i;
163         for (i = 0; i < map_size; i++)
164                 if (namelen == map[i].namelen &&
165                     !strncasecmp(name, map[i].name, namelen)) {
166                         *value = map[i].value;
167                         return OK;
168                 }
170         return ERR;
174 /*
175  * String helpers
176  */
178 static inline void
179 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
181         if (srclen > dstlen - 1)
182                 srclen = dstlen - 1;
184         strncpy(dst, src, srclen);
185         dst[srclen] = 0;
188 /* Shorthands for safely copying into a fixed buffer. */
190 #define string_copy(dst, src) \
191         string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
193 #define string_ncopy(dst, src, srclen) \
194         string_ncopy_do(dst, sizeof(dst), src, srclen)
196 #define string_copy_rev(dst, src) \
197         string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
199 #define string_add(dst, from, src) \
200         string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
202 static char *
203 chomp_string(char *name)
205         int namelen;
207         while (isspace(*name))
208                 name++;
210         namelen = strlen(name) - 1;
211         while (namelen > 0 && isspace(name[namelen]))
212                 name[namelen--] = 0;
214         return name;
217 static bool
218 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
220         va_list args;
221         size_t pos = bufpos ? *bufpos : 0;
223         va_start(args, fmt);
224         pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
225         va_end(args);
227         if (bufpos)
228                 *bufpos = pos;
230         return pos >= bufsize ? FALSE : TRUE;
233 #define string_format(buf, fmt, args...) \
234         string_nformat(buf, sizeof(buf), NULL, fmt, args)
236 #define string_format_from(buf, from, fmt, args...) \
237         string_nformat(buf, sizeof(buf), from, fmt, args)
239 static int
240 string_enum_compare(const char *str1, const char *str2, int len)
242         size_t i;
244 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
246         /* Diff-Header == DIFF_HEADER */
247         for (i = 0; i < len; i++) {
248                 if (toupper(str1[i]) == toupper(str2[i]))
249                         continue;
251                 if (string_enum_sep(str1[i]) &&
252                     string_enum_sep(str2[i]))
253                         continue;
255                 return str1[i] - str2[i];
256         }
258         return 0;
261 #define prefixcmp(str1, str2) \
262         strncmp(str1, str2, STRING_SIZE(str2))
264 static inline int
265 suffixcmp(const char *str, int slen, const char *suffix)
267         size_t len = slen >= 0 ? slen : strlen(str);
268         size_t suffixlen = strlen(suffix);
270         return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
273 /* Shell quoting
274  *
275  * NOTE: The following is a slightly modified copy of the git project's shell
276  * quoting routines found in the quote.c file.
277  *
278  * Help to copy the thing properly quoted for the shell safety.  any single
279  * quote is replaced with '\'', any exclamation point is replaced with '\!',
280  * and the whole thing is enclosed in a
281  *
282  * E.g.
283  *  original     sq_quote     result
284  *  name     ==> name      ==> 'name'
285  *  a b      ==> a b       ==> 'a b'
286  *  a'b      ==> a'\''b    ==> 'a'\''b'
287  *  a!b      ==> a'\!'b    ==> 'a'\!'b'
288  */
290 static size_t
291 sq_quote(char buf[SIZEOF_STR], size_t bufsize, const char *src)
293         char c;
295 #define BUFPUT(x) do { if (bufsize < SIZEOF_STR) buf[bufsize++] = (x); } while (0)
297         BUFPUT('\'');
298         while ((c = *src++)) {
299                 if (c == '\'' || c == '!') {
300                         BUFPUT('\'');
301                         BUFPUT('\\');
302                         BUFPUT(c);
303                         BUFPUT('\'');
304                 } else {
305                         BUFPUT(c);
306                 }
307         }
308         BUFPUT('\'');
310         if (bufsize < SIZEOF_STR)
311                 buf[bufsize] = 0;
313         return bufsize;
316 static bool
317 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
319         int valuelen;
321         while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
322                 bool advance = cmd[valuelen] != 0;
324                 cmd[valuelen] = 0;
325                 argv[(*argc)++] = chomp_string(cmd);
326                 cmd += valuelen + advance;
327         }
329         if (*argc < SIZEOF_ARG)
330                 argv[*argc] = NULL;
331         return *argc < SIZEOF_ARG;
334 static void
335 argv_from_env(const char **argv, const char *name)
337         char *env = argv ? getenv(name) : NULL;
338         int argc = 0;
340         if (env && *env)
341                 env = strdup(env);
342         if (env && !argv_from_string(argv, &argc, env))
343                 die("Too many arguments in the `%s` environment variable", name);
347 /*
348  * Executing external commands.
349  */
351 enum io_type {
352         IO_FD,                  /* File descriptor based IO. */
353         IO_BG,                  /* Execute command in the background. */
354         IO_FG,                  /* Execute command with same std{in,out,err}. */
355         IO_RD,                  /* Read only fork+exec IO. */
356         IO_WR,                  /* Write only fork+exec IO. */
357 };
359 struct io {
360         enum io_type type;      /* The requested type of pipe. */
361         const char *dir;        /* Directory from which to execute. */
362         FILE *pipe;             /* Pipe for reading or writing. */
363         int error;              /* Error status. */
364         char sh[SIZEOF_STR];    /* Shell command buffer. */
365         char *buf;              /* Read/write buffer. */
366         size_t bufalloc;        /* Allocated buffer size. */
367 };
369 static void
370 reset_io(struct io *io)
372         io->pipe = NULL;
373         io->buf = NULL;
374         io->bufalloc = 0;
375         io->error = 0;
378 static void
379 init_io(struct io *io, const char *dir, enum io_type type)
381         reset_io(io);
382         io->type = type;
383         io->dir = dir;
386 static bool
387 init_io_rd(struct io *io, const char *argv[], const char *dir,
388                 enum format_flags flags)
390         init_io(io, dir, IO_RD);
391         return format_command(io->sh, argv, flags);
394 static bool
395 io_open(struct io *io, const char *name)
397         init_io(io, NULL, IO_FD);
398         io->pipe = *name ? fopen(name, "r") : stdin;
399         return io->pipe != NULL;
402 static bool
403 done_io(struct io *io)
405         free(io->buf);
406         if (io->type == IO_FD)
407                 fclose(io->pipe);
408         else if (io->type == IO_RD || io->type == IO_WR)
409                 pclose(io->pipe);
410         reset_io(io);
411         return TRUE;
414 static bool
415 start_io(struct io *io)
417         char buf[SIZEOF_STR * 2];
418         size_t bufpos = 0;
420         if (io->type == IO_FD)
421                 return TRUE;
423         if (io->dir && *io->dir &&
424             !string_format_from(buf, &bufpos, "cd %s;", io->dir))
425                 return FALSE;
427         if (!string_format_from(buf, &bufpos, "%s", io->sh))
428                 return FALSE;
430         if (io->type == IO_FG || io->type == IO_BG)
431                 return system(buf) == 0;
433         io->pipe = popen(io->sh, io->type == IO_RD ? "r" : "w");
434         return io->pipe != NULL;
437 static bool
438 run_io(struct io *io, const char **argv, const char *dir, enum io_type type)
440         init_io(io, dir, type);
441         if (!format_command(io->sh, argv, FORMAT_NONE))
442                 return FALSE;
443         return start_io(io);
446 static int
447 run_io_do(struct io *io)
449         return start_io(io) && done_io(io);
452 static int
453 run_io_bg(const char **argv)
455         struct io io = {};
457         init_io(&io, NULL, IO_BG);
458         if (!format_command(io.sh, argv, FORMAT_NONE))
459                 return FALSE;
460         return run_io_do(&io);
463 static bool
464 run_io_fg(const char **argv, const char *dir)
466         struct io io = {};
468         init_io(&io, dir, IO_FG);
469         if (!format_command(io.sh, argv, FORMAT_NONE))
470                 return FALSE;
471         return run_io_do(&io);
474 static bool
475 run_io_rd(struct io *io, const char **argv, enum format_flags flags)
477         return init_io_rd(io, argv, NULL, flags) && start_io(io);
480 static bool
481 io_eof(struct io *io)
483         return feof(io->pipe);
486 static int
487 io_error(struct io *io)
489         return io->error;
492 static bool
493 io_strerror(struct io *io)
495         return strerror(io->error);
498 static size_t
499 io_read(struct io *io, void *buf, size_t bufsize)
501         size_t readsize = fread(buf, 1, bufsize, io->pipe);
503         if (ferror(io->pipe))
504                 io->error = errno;
506         return readsize;
509 static char *
510 io_gets(struct io *io)
512         if (!io->buf) {
513                 io->buf = malloc(BUFSIZ);
514                 if (!io->buf)
515                         return NULL;
516                 io->bufalloc = BUFSIZ;
517         }
519         if (!fgets(io->buf, io->bufalloc, io->pipe)) {
520                 if (ferror(io->pipe))
521                         io->error = errno;
522                 return NULL;
523         }
525         return io->buf;
528 static bool
529 io_write(struct io *io, const void *buf, size_t bufsize)
531         size_t written = 0;
533         while (!io_error(io) && written < bufsize) {
534                 written += fwrite(buf + written, 1, bufsize - written, io->pipe);
535                 if (ferror(io->pipe))
536                         io->error = errno;
537         }
539         return written == bufsize;
542 static bool
543 run_io_buf(const char **argv, char buf[], size_t bufsize)
545         struct io io = {};
546         bool error;
548         if (!run_io_rd(&io, argv, FORMAT_NONE))
549                 return FALSE;
551         io.buf = buf;
552         io.bufalloc = bufsize;
553         error = !io_gets(&io) && io_error(&io);
554         io.buf = NULL;
556         return done_io(&io) || error;
559 static int read_properties(struct io *io, const char *separators, int (*read)(char *, size_t, char *, size_t));
561 /*
562  * User requests
563  */
565 #define REQ_INFO \
566         /* XXX: Keep the view request first and in sync with views[]. */ \
567         REQ_GROUP("View switching") \
568         REQ_(VIEW_MAIN,         "Show main view"), \
569         REQ_(VIEW_DIFF,         "Show diff view"), \
570         REQ_(VIEW_LOG,          "Show log view"), \
571         REQ_(VIEW_TREE,         "Show tree view"), \
572         REQ_(VIEW_BLOB,         "Show blob view"), \
573         REQ_(VIEW_BLAME,        "Show blame view"), \
574         REQ_(VIEW_HELP,         "Show help page"), \
575         REQ_(VIEW_PAGER,        "Show pager view"), \
576         REQ_(VIEW_STATUS,       "Show status view"), \
577         REQ_(VIEW_STAGE,        "Show stage view"), \
578         \
579         REQ_GROUP("View manipulation") \
580         REQ_(ENTER,             "Enter current line and scroll"), \
581         REQ_(NEXT,              "Move to next"), \
582         REQ_(PREVIOUS,          "Move to previous"), \
583         REQ_(VIEW_NEXT,         "Move focus to next view"), \
584         REQ_(REFRESH,           "Reload and refresh"), \
585         REQ_(MAXIMIZE,          "Maximize the current view"), \
586         REQ_(VIEW_CLOSE,        "Close the current view"), \
587         REQ_(QUIT,              "Close all views and quit"), \
588         \
589         REQ_GROUP("View specific requests") \
590         REQ_(STATUS_UPDATE,     "Update file status"), \
591         REQ_(STATUS_REVERT,     "Revert file changes"), \
592         REQ_(STATUS_MERGE,      "Merge file using external tool"), \
593         REQ_(STAGE_NEXT,        "Find next chunk to stage"), \
594         REQ_(TREE_PARENT,       "Switch to parent directory in tree view"), \
595         \
596         REQ_GROUP("Cursor navigation") \
597         REQ_(MOVE_UP,           "Move cursor one line up"), \
598         REQ_(MOVE_DOWN,         "Move cursor one line down"), \
599         REQ_(MOVE_PAGE_DOWN,    "Move cursor one page down"), \
600         REQ_(MOVE_PAGE_UP,      "Move cursor one page up"), \
601         REQ_(MOVE_FIRST_LINE,   "Move cursor to first line"), \
602         REQ_(MOVE_LAST_LINE,    "Move cursor to last line"), \
603         \
604         REQ_GROUP("Scrolling") \
605         REQ_(SCROLL_LINE_UP,    "Scroll one line up"), \
606         REQ_(SCROLL_LINE_DOWN,  "Scroll one line down"), \
607         REQ_(SCROLL_PAGE_UP,    "Scroll one page up"), \
608         REQ_(SCROLL_PAGE_DOWN,  "Scroll one page down"), \
609         \
610         REQ_GROUP("Searching") \
611         REQ_(SEARCH,            "Search the view"), \
612         REQ_(SEARCH_BACK,       "Search backwards in the view"), \
613         REQ_(FIND_NEXT,         "Find next search match"), \
614         REQ_(FIND_PREV,         "Find previous search match"), \
615         \
616         REQ_GROUP("Option manipulation") \
617         REQ_(TOGGLE_LINENO,     "Toggle line numbers"), \
618         REQ_(TOGGLE_DATE,       "Toggle date display"), \
619         REQ_(TOGGLE_AUTHOR,     "Toggle author display"), \
620         REQ_(TOGGLE_REV_GRAPH,  "Toggle revision graph visualization"), \
621         REQ_(TOGGLE_REFS,       "Toggle reference display (tags/branches)"), \
622         \
623         REQ_GROUP("Misc") \
624         REQ_(PROMPT,            "Bring up the prompt"), \
625         REQ_(SCREEN_REDRAW,     "Redraw the screen"), \
626         REQ_(SCREEN_RESIZE,     "Resize the screen"), \
627         REQ_(SHOW_VERSION,      "Show version information"), \
628         REQ_(STOP_LOADING,      "Stop all loading views"), \
629         REQ_(EDIT,              "Open in editor"), \
630         REQ_(NONE,              "Do nothing")
633 /* User action requests. */
634 enum request {
635 #define REQ_GROUP(help)
636 #define REQ_(req, help) REQ_##req
638         /* Offset all requests to avoid conflicts with ncurses getch values. */
639         REQ_OFFSET = KEY_MAX + 1,
640         REQ_INFO
642 #undef  REQ_GROUP
643 #undef  REQ_
644 };
646 struct request_info {
647         enum request request;
648         const char *name;
649         int namelen;
650         const char *help;
651 };
653 static struct request_info req_info[] = {
654 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
655 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
656         REQ_INFO
657 #undef  REQ_GROUP
658 #undef  REQ_
659 };
661 static enum request
662 get_request(const char *name)
664         int namelen = strlen(name);
665         int i;
667         for (i = 0; i < ARRAY_SIZE(req_info); i++)
668                 if (req_info[i].namelen == namelen &&
669                     !string_enum_compare(req_info[i].name, name, namelen))
670                         return req_info[i].request;
672         return REQ_NONE;
676 /*
677  * Options
678  */
680 static const char usage[] =
681 "tig " TIG_VERSION " (" __DATE__ ")\n"
682 "\n"
683 "Usage: tig        [options] [revs] [--] [paths]\n"
684 "   or: tig show   [options] [revs] [--] [paths]\n"
685 "   or: tig blame  [rev] path\n"
686 "   or: tig status\n"
687 "   or: tig <      [git command output]\n"
688 "\n"
689 "Options:\n"
690 "  -v, --version   Show version and exit\n"
691 "  -h, --help      Show help message and exit";
693 /* Option and state variables. */
694 static bool opt_date                    = TRUE;
695 static bool opt_author                  = TRUE;
696 static bool opt_line_number             = FALSE;
697 static bool opt_line_graphics           = TRUE;
698 static bool opt_rev_graph               = FALSE;
699 static bool opt_show_refs               = TRUE;
700 static int opt_num_interval             = NUMBER_INTERVAL;
701 static int opt_tab_size                 = TAB_SIZE;
702 static int opt_author_cols              = AUTHOR_COLS-1;
703 static char opt_path[SIZEOF_STR]        = "";
704 static char opt_file[SIZEOF_STR]        = "";
705 static char opt_ref[SIZEOF_REF]         = "";
706 static char opt_head[SIZEOF_REF]        = "";
707 static char opt_head_rev[SIZEOF_REV]    = "";
708 static char opt_remote[SIZEOF_REF]      = "";
709 static char opt_encoding[20]            = "UTF-8";
710 static bool opt_utf8                    = TRUE;
711 static char opt_codeset[20]             = "UTF-8";
712 static iconv_t opt_iconv                = ICONV_NONE;
713 static char opt_search[SIZEOF_STR]      = "";
714 static char opt_cdup[SIZEOF_STR]        = "";
715 static char opt_git_dir[SIZEOF_STR]     = "";
716 static signed char opt_is_inside_work_tree      = -1; /* set to TRUE or FALSE */
717 static char opt_editor[SIZEOF_STR]      = "";
718 static FILE *opt_tty                    = NULL;
720 #define is_initial_commit()     (!*opt_head_rev)
721 #define is_head_commit(rev)     (!strcmp((rev), "HEAD") || !strcmp(opt_head_rev, (rev)))
723 static enum request
724 parse_options(int argc, const char *argv[], const char ***run_argv)
726         enum request request = REQ_VIEW_MAIN;
727         const char *subcommand;
728         bool seen_dashdash = FALSE;
729         /* XXX: This is vulnerable to the user overriding options
730          * required for the main view parser. */
731         const char *custom_argv[SIZEOF_ARG] = {
732                 "git", "log", "--no-color", "--pretty=raw", "--parents",
733                         "--topo-order", NULL
734         };
735         int i, j = 6;
737         if (!isatty(STDIN_FILENO))
738                 return REQ_VIEW_PAGER;
740         if (argc <= 1)
741                 return REQ_VIEW_MAIN;
743         subcommand = argv[1];
744         if (!strcmp(subcommand, "status") || !strcmp(subcommand, "-S")) {
745                 if (!strcmp(subcommand, "-S"))
746                         warn("`-S' has been deprecated; use `tig status' instead");
747                 if (argc > 2)
748                         warn("ignoring arguments after `%s'", subcommand);
749                 return REQ_VIEW_STATUS;
751         } else if (!strcmp(subcommand, "blame")) {
752                 if (argc <= 2 || argc > 4)
753                         die("invalid number of options to blame\n\n%s", usage);
755                 i = 2;
756                 if (argc == 4) {
757                         string_ncopy(opt_ref, argv[i], strlen(argv[i]));
758                         i++;
759                 }
761                 string_ncopy(opt_file, argv[i], strlen(argv[i]));
762                 return REQ_VIEW_BLAME;
764         } else if (!strcmp(subcommand, "show")) {
765                 request = REQ_VIEW_DIFF;
767         } else if (!strcmp(subcommand, "log") || !strcmp(subcommand, "diff")) {
768                 request = subcommand[0] == 'l' ? REQ_VIEW_LOG : REQ_VIEW_DIFF;
769                 warn("`tig %s' has been deprecated", subcommand);
771         } else {
772                 subcommand = NULL;
773         }
775         if (subcommand) {
776                 custom_argv[1] = subcommand;
777                 j = 2;
778         }
780         for (i = 1 + !!subcommand; i < argc; i++) {
781                 const char *opt = argv[i];
783                 if (seen_dashdash || !strcmp(opt, "--")) {
784                         seen_dashdash = TRUE;
786                 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
787                         printf("tig version %s\n", TIG_VERSION);
788                         return REQ_NONE;
790                 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
791                         printf("%s\n", usage);
792                         return REQ_NONE;
793                 }
795                 custom_argv[j++] = opt;
796                 if (j >= ARRAY_SIZE(custom_argv))
797                         die("command too long");
798         }
800         custom_argv[j] = NULL;
801         *run_argv = custom_argv;
803         return request;
807 /*
808  * Line-oriented content detection.
809  */
811 #define LINE_INFO \
812 LINE(DIFF_HEADER,  "diff --git ",       COLOR_YELLOW,   COLOR_DEFAULT,  0), \
813 LINE(DIFF_CHUNK,   "@@",                COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
814 LINE(DIFF_ADD,     "+",                 COLOR_GREEN,    COLOR_DEFAULT,  0), \
815 LINE(DIFF_DEL,     "-",                 COLOR_RED,      COLOR_DEFAULT,  0), \
816 LINE(DIFF_INDEX,        "index ",         COLOR_BLUE,   COLOR_DEFAULT,  0), \
817 LINE(DIFF_OLDMODE,      "old file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
818 LINE(DIFF_NEWMODE,      "new file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
819 LINE(DIFF_COPY_FROM,    "copy from",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
820 LINE(DIFF_COPY_TO,      "copy to",        COLOR_YELLOW, COLOR_DEFAULT,  0), \
821 LINE(DIFF_RENAME_FROM,  "rename from",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
822 LINE(DIFF_RENAME_TO,    "rename to",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
823 LINE(DIFF_SIMILARITY,   "similarity ",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
824 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
825 LINE(DIFF_TREE,         "diff-tree ",     COLOR_BLUE,   COLOR_DEFAULT,  0), \
826 LINE(PP_AUTHOR,    "Author: ",          COLOR_CYAN,     COLOR_DEFAULT,  0), \
827 LINE(PP_COMMIT,    "Commit: ",          COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
828 LINE(PP_MERGE,     "Merge: ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
829 LINE(PP_DATE,      "Date:   ",          COLOR_YELLOW,   COLOR_DEFAULT,  0), \
830 LINE(PP_ADATE,     "AuthorDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
831 LINE(PP_CDATE,     "CommitDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
832 LINE(PP_REFS,      "Refs: ",            COLOR_RED,      COLOR_DEFAULT,  0), \
833 LINE(COMMIT,       "commit ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
834 LINE(PARENT,       "parent ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
835 LINE(TREE,         "tree ",             COLOR_BLUE,     COLOR_DEFAULT,  0), \
836 LINE(AUTHOR,       "author ",           COLOR_CYAN,     COLOR_DEFAULT,  0), \
837 LINE(COMMITTER,    "committer ",        COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
838 LINE(SIGNOFF,      "    Signed-off-by", COLOR_YELLOW,   COLOR_DEFAULT,  0), \
839 LINE(ACKED,        "    Acked-by",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
840 LINE(DEFAULT,      "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
841 LINE(CURSOR,       "",                  COLOR_WHITE,    COLOR_GREEN,    A_BOLD), \
842 LINE(STATUS,       "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
843 LINE(DELIMITER,    "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
844 LINE(DATE,         "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
845 LINE(LINE_NUMBER,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
846 LINE(TITLE_BLUR,   "",                  COLOR_WHITE,    COLOR_BLUE,     0), \
847 LINE(TITLE_FOCUS,  "",                  COLOR_WHITE,    COLOR_BLUE,     A_BOLD), \
848 LINE(MAIN_AUTHOR,  "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
849 LINE(MAIN_COMMIT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
850 LINE(MAIN_TAG,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
851 LINE(MAIN_LOCAL_TAG,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
852 LINE(MAIN_REMOTE,  "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
853 LINE(MAIN_TRACKED, "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_BOLD), \
854 LINE(MAIN_REF,     "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
855 LINE(MAIN_HEAD,    "",                  COLOR_CYAN,     COLOR_DEFAULT,  A_BOLD), \
856 LINE(MAIN_REVGRAPH,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
857 LINE(TREE_DIR,     "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
858 LINE(TREE_FILE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
859 LINE(STAT_HEAD,    "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
860 LINE(STAT_SECTION, "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
861 LINE(STAT_NONE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
862 LINE(STAT_STAGED,  "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
863 LINE(STAT_UNSTAGED,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
864 LINE(STAT_UNTRACKED,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
865 LINE(BLAME_ID,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0)
867 enum line_type {
868 #define LINE(type, line, fg, bg, attr) \
869         LINE_##type
870         LINE_INFO,
871         LINE_NONE
872 #undef  LINE
873 };
875 struct line_info {
876         const char *name;       /* Option name. */
877         int namelen;            /* Size of option name. */
878         const char *line;       /* The start of line to match. */
879         int linelen;            /* Size of string to match. */
880         int fg, bg, attr;       /* Color and text attributes for the lines. */
881 };
883 static struct line_info line_info[] = {
884 #define LINE(type, line, fg, bg, attr) \
885         { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
886         LINE_INFO
887 #undef  LINE
888 };
890 static enum line_type
891 get_line_type(const char *line)
893         int linelen = strlen(line);
894         enum line_type type;
896         for (type = 0; type < ARRAY_SIZE(line_info); type++)
897                 /* Case insensitive search matches Signed-off-by lines better. */
898                 if (linelen >= line_info[type].linelen &&
899                     !strncasecmp(line_info[type].line, line, line_info[type].linelen))
900                         return type;
902         return LINE_DEFAULT;
905 static inline int
906 get_line_attr(enum line_type type)
908         assert(type < ARRAY_SIZE(line_info));
909         return COLOR_PAIR(type) | line_info[type].attr;
912 static struct line_info *
913 get_line_info(const char *name)
915         size_t namelen = strlen(name);
916         enum line_type type;
918         for (type = 0; type < ARRAY_SIZE(line_info); type++)
919                 if (namelen == line_info[type].namelen &&
920                     !string_enum_compare(line_info[type].name, name, namelen))
921                         return &line_info[type];
923         return NULL;
926 static void
927 init_colors(void)
929         int default_bg = line_info[LINE_DEFAULT].bg;
930         int default_fg = line_info[LINE_DEFAULT].fg;
931         enum line_type type;
933         start_color();
935         if (assume_default_colors(default_fg, default_bg) == ERR) {
936                 default_bg = COLOR_BLACK;
937                 default_fg = COLOR_WHITE;
938         }
940         for (type = 0; type < ARRAY_SIZE(line_info); type++) {
941                 struct line_info *info = &line_info[type];
942                 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
943                 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
945                 init_pair(type, fg, bg);
946         }
949 struct line {
950         enum line_type type;
952         /* State flags */
953         unsigned int selected:1;
954         unsigned int dirty:1;
956         void *data;             /* User data */
957 };
960 /*
961  * Keys
962  */
964 struct keybinding {
965         int alias;
966         enum request request;
967 };
969 static struct keybinding default_keybindings[] = {
970         /* View switching */
971         { 'm',          REQ_VIEW_MAIN },
972         { 'd',          REQ_VIEW_DIFF },
973         { 'l',          REQ_VIEW_LOG },
974         { 't',          REQ_VIEW_TREE },
975         { 'f',          REQ_VIEW_BLOB },
976         { 'B',          REQ_VIEW_BLAME },
977         { 'p',          REQ_VIEW_PAGER },
978         { 'h',          REQ_VIEW_HELP },
979         { 'S',          REQ_VIEW_STATUS },
980         { 'c',          REQ_VIEW_STAGE },
982         /* View manipulation */
983         { 'q',          REQ_VIEW_CLOSE },
984         { KEY_TAB,      REQ_VIEW_NEXT },
985         { KEY_RETURN,   REQ_ENTER },
986         { KEY_UP,       REQ_PREVIOUS },
987         { KEY_DOWN,     REQ_NEXT },
988         { 'R',          REQ_REFRESH },
989         { KEY_F(5),     REQ_REFRESH },
990         { 'O',          REQ_MAXIMIZE },
992         /* Cursor navigation */
993         { 'k',          REQ_MOVE_UP },
994         { 'j',          REQ_MOVE_DOWN },
995         { KEY_HOME,     REQ_MOVE_FIRST_LINE },
996         { KEY_END,      REQ_MOVE_LAST_LINE },
997         { KEY_NPAGE,    REQ_MOVE_PAGE_DOWN },
998         { ' ',          REQ_MOVE_PAGE_DOWN },
999         { KEY_PPAGE,    REQ_MOVE_PAGE_UP },
1000         { 'b',          REQ_MOVE_PAGE_UP },
1001         { '-',          REQ_MOVE_PAGE_UP },
1003         /* Scrolling */
1004         { KEY_IC,       REQ_SCROLL_LINE_UP },
1005         { KEY_DC,       REQ_SCROLL_LINE_DOWN },
1006         { 'w',          REQ_SCROLL_PAGE_UP },
1007         { 's',          REQ_SCROLL_PAGE_DOWN },
1009         /* Searching */
1010         { '/',          REQ_SEARCH },
1011         { '?',          REQ_SEARCH_BACK },
1012         { 'n',          REQ_FIND_NEXT },
1013         { 'N',          REQ_FIND_PREV },
1015         /* Misc */
1016         { 'Q',          REQ_QUIT },
1017         { 'z',          REQ_STOP_LOADING },
1018         { 'v',          REQ_SHOW_VERSION },
1019         { 'r',          REQ_SCREEN_REDRAW },
1020         { '.',          REQ_TOGGLE_LINENO },
1021         { 'D',          REQ_TOGGLE_DATE },
1022         { 'A',          REQ_TOGGLE_AUTHOR },
1023         { 'g',          REQ_TOGGLE_REV_GRAPH },
1024         { 'F',          REQ_TOGGLE_REFS },
1025         { ':',          REQ_PROMPT },
1026         { 'u',          REQ_STATUS_UPDATE },
1027         { '!',          REQ_STATUS_REVERT },
1028         { 'M',          REQ_STATUS_MERGE },
1029         { '@',          REQ_STAGE_NEXT },
1030         { ',',          REQ_TREE_PARENT },
1031         { 'e',          REQ_EDIT },
1033         /* Using the ncurses SIGWINCH handler. */
1034         { KEY_RESIZE,   REQ_SCREEN_RESIZE },
1035 };
1037 #define KEYMAP_INFO \
1038         KEYMAP_(GENERIC), \
1039         KEYMAP_(MAIN), \
1040         KEYMAP_(DIFF), \
1041         KEYMAP_(LOG), \
1042         KEYMAP_(TREE), \
1043         KEYMAP_(BLOB), \
1044         KEYMAP_(BLAME), \
1045         KEYMAP_(PAGER), \
1046         KEYMAP_(HELP), \
1047         KEYMAP_(STATUS), \
1048         KEYMAP_(STAGE)
1050 enum keymap {
1051 #define KEYMAP_(name) KEYMAP_##name
1052         KEYMAP_INFO
1053 #undef  KEYMAP_
1054 };
1056 static struct int_map keymap_table[] = {
1057 #define KEYMAP_(name) { #name, STRING_SIZE(#name), KEYMAP_##name }
1058         KEYMAP_INFO
1059 #undef  KEYMAP_
1060 };
1062 #define set_keymap(map, name) \
1063         set_from_int_map(keymap_table, ARRAY_SIZE(keymap_table), map, name, strlen(name))
1065 struct keybinding_table {
1066         struct keybinding *data;
1067         size_t size;
1068 };
1070 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1072 static void
1073 add_keybinding(enum keymap keymap, enum request request, int key)
1075         struct keybinding_table *table = &keybindings[keymap];
1077         table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1078         if (!table->data)
1079                 die("Failed to allocate keybinding");
1080         table->data[table->size].alias = key;
1081         table->data[table->size++].request = request;
1084 /* Looks for a key binding first in the given map, then in the generic map, and
1085  * lastly in the default keybindings. */
1086 static enum request
1087 get_keybinding(enum keymap keymap, int key)
1089         size_t i;
1091         for (i = 0; i < keybindings[keymap].size; i++)
1092                 if (keybindings[keymap].data[i].alias == key)
1093                         return keybindings[keymap].data[i].request;
1095         for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1096                 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1097                         return keybindings[KEYMAP_GENERIC].data[i].request;
1099         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1100                 if (default_keybindings[i].alias == key)
1101                         return default_keybindings[i].request;
1103         return (enum request) key;
1107 struct key {
1108         const char *name;
1109         int value;
1110 };
1112 static struct key key_table[] = {
1113         { "Enter",      KEY_RETURN },
1114         { "Space",      ' ' },
1115         { "Backspace",  KEY_BACKSPACE },
1116         { "Tab",        KEY_TAB },
1117         { "Escape",     KEY_ESC },
1118         { "Left",       KEY_LEFT },
1119         { "Right",      KEY_RIGHT },
1120         { "Up",         KEY_UP },
1121         { "Down",       KEY_DOWN },
1122         { "Insert",     KEY_IC },
1123         { "Delete",     KEY_DC },
1124         { "Hash",       '#' },
1125         { "Home",       KEY_HOME },
1126         { "End",        KEY_END },
1127         { "PageUp",     KEY_PPAGE },
1128         { "PageDown",   KEY_NPAGE },
1129         { "F1",         KEY_F(1) },
1130         { "F2",         KEY_F(2) },
1131         { "F3",         KEY_F(3) },
1132         { "F4",         KEY_F(4) },
1133         { "F5",         KEY_F(5) },
1134         { "F6",         KEY_F(6) },
1135         { "F7",         KEY_F(7) },
1136         { "F8",         KEY_F(8) },
1137         { "F9",         KEY_F(9) },
1138         { "F10",        KEY_F(10) },
1139         { "F11",        KEY_F(11) },
1140         { "F12",        KEY_F(12) },
1141 };
1143 static int
1144 get_key_value(const char *name)
1146         int i;
1148         for (i = 0; i < ARRAY_SIZE(key_table); i++)
1149                 if (!strcasecmp(key_table[i].name, name))
1150                         return key_table[i].value;
1152         if (strlen(name) == 1 && isprint(*name))
1153                 return (int) *name;
1155         return ERR;
1158 static const char *
1159 get_key_name(int key_value)
1161         static char key_char[] = "'X'";
1162         const char *seq = NULL;
1163         int key;
1165         for (key = 0; key < ARRAY_SIZE(key_table); key++)
1166                 if (key_table[key].value == key_value)
1167                         seq = key_table[key].name;
1169         if (seq == NULL &&
1170             key_value < 127 &&
1171             isprint(key_value)) {
1172                 key_char[1] = (char) key_value;
1173                 seq = key_char;
1174         }
1176         return seq ? seq : "(no key)";
1179 static const char *
1180 get_key(enum request request)
1182         static char buf[BUFSIZ];
1183         size_t pos = 0;
1184         char *sep = "";
1185         int i;
1187         buf[pos] = 0;
1189         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1190                 struct keybinding *keybinding = &default_keybindings[i];
1192                 if (keybinding->request != request)
1193                         continue;
1195                 if (!string_format_from(buf, &pos, "%s%s", sep,
1196                                         get_key_name(keybinding->alias)))
1197                         return "Too many keybindings!";
1198                 sep = ", ";
1199         }
1201         return buf;
1204 struct run_request {
1205         enum keymap keymap;
1206         int key;
1207         const char *argv[SIZEOF_ARG];
1208 };
1210 static struct run_request *run_request;
1211 static size_t run_requests;
1213 static enum request
1214 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1216         struct run_request *req;
1218         if (argc >= ARRAY_SIZE(req->argv) - 1)
1219                 return REQ_NONE;
1221         req = realloc(run_request, (run_requests + 1) * sizeof(*run_request));
1222         if (!req)
1223                 return REQ_NONE;
1225         run_request = req;
1226         req = &run_request[run_requests];
1227         req->keymap = keymap;
1228         req->key = key;
1229         req->argv[0] = NULL;
1231         if (!format_argv(req->argv, argv, FORMAT_NONE))
1232                 return REQ_NONE;
1234         return REQ_NONE + ++run_requests;
1237 static struct run_request *
1238 get_run_request(enum request request)
1240         if (request <= REQ_NONE)
1241                 return NULL;
1242         return &run_request[request - REQ_NONE - 1];
1245 static void
1246 add_builtin_run_requests(void)
1248         const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1249         const char *gc[] = { "git", "gc", NULL };
1250         struct {
1251                 enum keymap keymap;
1252                 int key;
1253                 int argc;
1254                 const char **argv;
1255         } reqs[] = {
1256                 { KEYMAP_MAIN,    'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1257                 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1258         };
1259         int i;
1261         for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1262                 enum request req;
1264                 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1265                 if (req != REQ_NONE)
1266                         add_keybinding(reqs[i].keymap, req, reqs[i].key);
1267         }
1270 /*
1271  * User config file handling.
1272  */
1274 static struct int_map color_map[] = {
1275 #define COLOR_MAP(name) { #name, STRING_SIZE(#name), COLOR_##name }
1276         COLOR_MAP(DEFAULT),
1277         COLOR_MAP(BLACK),
1278         COLOR_MAP(BLUE),
1279         COLOR_MAP(CYAN),
1280         COLOR_MAP(GREEN),
1281         COLOR_MAP(MAGENTA),
1282         COLOR_MAP(RED),
1283         COLOR_MAP(WHITE),
1284         COLOR_MAP(YELLOW),
1285 };
1287 #define set_color(color, name) \
1288         set_from_int_map(color_map, ARRAY_SIZE(color_map), color, name, strlen(name))
1290 static struct int_map attr_map[] = {
1291 #define ATTR_MAP(name) { #name, STRING_SIZE(#name), A_##name }
1292         ATTR_MAP(NORMAL),
1293         ATTR_MAP(BLINK),
1294         ATTR_MAP(BOLD),
1295         ATTR_MAP(DIM),
1296         ATTR_MAP(REVERSE),
1297         ATTR_MAP(STANDOUT),
1298         ATTR_MAP(UNDERLINE),
1299 };
1301 #define set_attribute(attr, name) \
1302         set_from_int_map(attr_map, ARRAY_SIZE(attr_map), attr, name, strlen(name))
1304 static int   config_lineno;
1305 static bool  config_errors;
1306 static const char *config_msg;
1308 /* Wants: object fgcolor bgcolor [attr] */
1309 static int
1310 option_color_command(int argc, const char *argv[])
1312         struct line_info *info;
1314         if (argc != 3 && argc != 4) {
1315                 config_msg = "Wrong number of arguments given to color command";
1316                 return ERR;
1317         }
1319         info = get_line_info(argv[0]);
1320         if (!info) {
1321                 if (!string_enum_compare(argv[0], "main-delim", strlen("main-delim"))) {
1322                         info = get_line_info("delimiter");
1324                 } else if (!string_enum_compare(argv[0], "main-date", strlen("main-date"))) {
1325                         info = get_line_info("date");
1327                 } else {
1328                         config_msg = "Unknown color name";
1329                         return ERR;
1330                 }
1331         }
1333         if (set_color(&info->fg, argv[1]) == ERR ||
1334             set_color(&info->bg, argv[2]) == ERR) {
1335                 config_msg = "Unknown color";
1336                 return ERR;
1337         }
1339         if (argc == 4 && set_attribute(&info->attr, argv[3]) == ERR) {
1340                 config_msg = "Unknown attribute";
1341                 return ERR;
1342         }
1344         return OK;
1347 static bool parse_bool(const char *s)
1349         return (!strcmp(s, "1") || !strcmp(s, "true") ||
1350                 !strcmp(s, "yes")) ? TRUE : FALSE;
1353 static int
1354 parse_int(const char *s, int default_value, int min, int max)
1356         int value = atoi(s);
1358         return (value < min || value > max) ? default_value : value;
1361 /* Wants: name = value */
1362 static int
1363 option_set_command(int argc, const char *argv[])
1365         if (argc != 3) {
1366                 config_msg = "Wrong number of arguments given to set command";
1367                 return ERR;
1368         }
1370         if (strcmp(argv[1], "=")) {
1371                 config_msg = "No value assigned";
1372                 return ERR;
1373         }
1375         if (!strcmp(argv[0], "show-author")) {
1376                 opt_author = parse_bool(argv[2]);
1377                 return OK;
1378         }
1380         if (!strcmp(argv[0], "show-date")) {
1381                 opt_date = parse_bool(argv[2]);
1382                 return OK;
1383         }
1385         if (!strcmp(argv[0], "show-rev-graph")) {
1386                 opt_rev_graph = parse_bool(argv[2]);
1387                 return OK;
1388         }
1390         if (!strcmp(argv[0], "show-refs")) {
1391                 opt_show_refs = parse_bool(argv[2]);
1392                 return OK;
1393         }
1395         if (!strcmp(argv[0], "show-line-numbers")) {
1396                 opt_line_number = parse_bool(argv[2]);
1397                 return OK;
1398         }
1400         if (!strcmp(argv[0], "line-graphics")) {
1401                 opt_line_graphics = parse_bool(argv[2]);
1402                 return OK;
1403         }
1405         if (!strcmp(argv[0], "line-number-interval")) {
1406                 opt_num_interval = parse_int(argv[2], opt_num_interval, 1, 1024);
1407                 return OK;
1408         }
1410         if (!strcmp(argv[0], "author-width")) {
1411                 opt_author_cols = parse_int(argv[2], opt_author_cols, 0, 1024);
1412                 return OK;
1413         }
1415         if (!strcmp(argv[0], "tab-size")) {
1416                 opt_tab_size = parse_int(argv[2], opt_tab_size, 1, 1024);
1417                 return OK;
1418         }
1420         if (!strcmp(argv[0], "commit-encoding")) {
1421                 const char *arg = argv[2];
1422                 int arglen = strlen(arg);
1424                 switch (arg[0]) {
1425                 case '"':
1426                 case '\'':
1427                         if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1428                                 config_msg = "Unmatched quotation";
1429                                 return ERR;
1430                         }
1431                         arg += 1; arglen -= 2;
1432                 default:
1433                         string_ncopy(opt_encoding, arg, strlen(arg));
1434                         return OK;
1435                 }
1436         }
1438         config_msg = "Unknown variable name";
1439         return ERR;
1442 /* Wants: mode request key */
1443 static int
1444 option_bind_command(int argc, const char *argv[])
1446         enum request request;
1447         int keymap;
1448         int key;
1450         if (argc < 3) {
1451                 config_msg = "Wrong number of arguments given to bind command";
1452                 return ERR;
1453         }
1455         if (set_keymap(&keymap, argv[0]) == ERR) {
1456                 config_msg = "Unknown key map";
1457                 return ERR;
1458         }
1460         key = get_key_value(argv[1]);
1461         if (key == ERR) {
1462                 config_msg = "Unknown key";
1463                 return ERR;
1464         }
1466         request = get_request(argv[2]);
1467         if (request == REQ_NONE) {
1468                 const char *obsolete[] = { "cherry-pick" };
1469                 size_t namelen = strlen(argv[2]);
1470                 int i;
1472                 for (i = 0; i < ARRAY_SIZE(obsolete); i++) {
1473                         if (namelen == strlen(obsolete[i]) &&
1474                             !string_enum_compare(obsolete[i], argv[2], namelen)) {
1475                                 config_msg = "Obsolete request name";
1476                                 return ERR;
1477                         }
1478                 }
1479         }
1480         if (request == REQ_NONE && *argv[2]++ == '!')
1481                 request = add_run_request(keymap, key, argc - 2, argv + 2);
1482         if (request == REQ_NONE) {
1483                 config_msg = "Unknown request name";
1484                 return ERR;
1485         }
1487         add_keybinding(keymap, request, key);
1489         return OK;
1492 static int
1493 set_option(const char *opt, char *value)
1495         const char *argv[SIZEOF_ARG];
1496         int argc = 0;
1498         if (!argv_from_string(argv, &argc, value)) {
1499                 config_msg = "Too many option arguments";
1500                 return ERR;
1501         }
1503         if (!strcmp(opt, "color"))
1504                 return option_color_command(argc, argv);
1506         if (!strcmp(opt, "set"))
1507                 return option_set_command(argc, argv);
1509         if (!strcmp(opt, "bind"))
1510                 return option_bind_command(argc, argv);
1512         config_msg = "Unknown option command";
1513         return ERR;
1516 static int
1517 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1519         int status = OK;
1521         config_lineno++;
1522         config_msg = "Internal error";
1524         /* Check for comment markers, since read_properties() will
1525          * only ensure opt and value are split at first " \t". */
1526         optlen = strcspn(opt, "#");
1527         if (optlen == 0)
1528                 return OK;
1530         if (opt[optlen] != 0) {
1531                 config_msg = "No option value";
1532                 status = ERR;
1534         }  else {
1535                 /* Look for comment endings in the value. */
1536                 size_t len = strcspn(value, "#");
1538                 if (len < valuelen) {
1539                         valuelen = len;
1540                         value[valuelen] = 0;
1541                 }
1543                 status = set_option(opt, value);
1544         }
1546         if (status == ERR) {
1547                 fprintf(stderr, "Error on line %d, near '%.*s': %s\n",
1548                         config_lineno, (int) optlen, opt, config_msg);
1549                 config_errors = TRUE;
1550         }
1552         /* Always keep going if errors are encountered. */
1553         return OK;
1556 static void
1557 load_option_file(const char *path)
1559         struct io io = {};
1561         /* It's ok that the file doesn't exist. */
1562         if (!io_open(&io, path))
1563                 return;
1565         config_lineno = 0;
1566         config_errors = FALSE;
1568         if (read_properties(&io, " \t", read_option) == ERR ||
1569             config_errors == TRUE)
1570                 fprintf(stderr, "Errors while loading %s.\n", path);
1573 static int
1574 load_options(void)
1576         const char *home = getenv("HOME");
1577         const char *tigrc_user = getenv("TIGRC_USER");
1578         const char *tigrc_system = getenv("TIGRC_SYSTEM");
1579         char buf[SIZEOF_STR];
1581         add_builtin_run_requests();
1583         if (!tigrc_system) {
1584                 if (!string_format(buf, "%s/tigrc", SYSCONFDIR))
1585                         return ERR;
1586                 tigrc_system = buf;
1587         }
1588         load_option_file(tigrc_system);
1590         if (!tigrc_user) {
1591                 if (!home || !string_format(buf, "%s/.tigrc", home))
1592                         return ERR;
1593                 tigrc_user = buf;
1594         }
1595         load_option_file(tigrc_user);
1597         return OK;
1601 /*
1602  * The viewer
1603  */
1605 struct view;
1606 struct view_ops;
1608 /* The display array of active views and the index of the current view. */
1609 static struct view *display[2];
1610 static unsigned int current_view;
1612 /* Reading from the prompt? */
1613 static bool input_mode = FALSE;
1615 #define foreach_displayed_view(view, i) \
1616         for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1618 #define displayed_views()       (display[1] != NULL ? 2 : 1)
1620 /* Current head and commit ID */
1621 static char ref_blob[SIZEOF_REF]        = "";
1622 static char ref_commit[SIZEOF_REF]      = "HEAD";
1623 static char ref_head[SIZEOF_REF]        = "HEAD";
1625 struct view {
1626         const char *name;       /* View name */
1627         const char *cmd_env;    /* Command line set via environment */
1628         const char *id;         /* Points to either of ref_{head,commit,blob} */
1630         struct view_ops *ops;   /* View operations */
1632         enum keymap keymap;     /* What keymap does this view have */
1633         bool git_dir;           /* Whether the view requires a git directory. */
1635         char ref[SIZEOF_REF];   /* Hovered commit reference */
1636         char vid[SIZEOF_REF];   /* View ID. Set to id member when updating. */
1638         int height, width;      /* The width and height of the main window */
1639         WINDOW *win;            /* The main window */
1640         WINDOW *title;          /* The title window living below the main window */
1642         /* Navigation */
1643         unsigned long offset;   /* Offset of the window top */
1644         unsigned long lineno;   /* Current line number */
1646         /* Searching */
1647         char grep[SIZEOF_STR];  /* Search string */
1648         regex_t *regex;         /* Pre-compiled regex */
1650         /* If non-NULL, points to the view that opened this view. If this view
1651          * is closed tig will switch back to the parent view. */
1652         struct view *parent;
1654         /* Buffering */
1655         size_t lines;           /* Total number of lines */
1656         struct line *line;      /* Line index */
1657         size_t line_alloc;      /* Total number of allocated lines */
1658         size_t line_size;       /* Total number of used lines */
1659         unsigned int digits;    /* Number of digits in the lines member. */
1661         /* Drawing */
1662         struct line *curline;   /* Line currently being drawn. */
1663         enum line_type curtype; /* Attribute currently used for drawing. */
1664         unsigned long col;      /* Column when drawing. */
1666         /* Loading */
1667         struct io io;
1668         struct io *pipe;
1669         time_t start_time;
1670 };
1672 struct view_ops {
1673         /* What type of content being displayed. Used in the title bar. */
1674         const char *type;
1675         /* Default command arguments. */
1676         const char **argv;
1677         /* Open and reads in all view content. */
1678         bool (*open)(struct view *view);
1679         /* Read one line; updates view->line. */
1680         bool (*read)(struct view *view, char *data);
1681         /* Draw one line; @lineno must be < view->height. */
1682         bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
1683         /* Depending on view handle a special requests. */
1684         enum request (*request)(struct view *view, enum request request, struct line *line);
1685         /* Search for regex in a line. */
1686         bool (*grep)(struct view *view, struct line *line);
1687         /* Select line */
1688         void (*select)(struct view *view, struct line *line);
1689 };
1691 static struct view_ops blame_ops;
1692 static struct view_ops blob_ops;
1693 static struct view_ops diff_ops;
1694 static struct view_ops help_ops;
1695 static struct view_ops log_ops;
1696 static struct view_ops main_ops;
1697 static struct view_ops pager_ops;
1698 static struct view_ops stage_ops;
1699 static struct view_ops status_ops;
1700 static struct view_ops tree_ops;
1702 #define VIEW_STR(name, env, ref, ops, map, git) \
1703         { name, #env, ref, ops, map, git }
1705 #define VIEW_(id, name, ops, git, ref) \
1706         VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
1709 static struct view views[] = {
1710         VIEW_(MAIN,   "main",   &main_ops,   TRUE,  ref_head),
1711         VIEW_(DIFF,   "diff",   &diff_ops,   TRUE,  ref_commit),
1712         VIEW_(LOG,    "log",    &log_ops,    TRUE,  ref_head),
1713         VIEW_(TREE,   "tree",   &tree_ops,   TRUE,  ref_commit),
1714         VIEW_(BLOB,   "blob",   &blob_ops,   TRUE,  ref_blob),
1715         VIEW_(BLAME,  "blame",  &blame_ops,  TRUE,  ref_commit),
1716         VIEW_(HELP,   "help",   &help_ops,   FALSE, ""),
1717         VIEW_(PAGER,  "pager",  &pager_ops,  FALSE, "stdin"),
1718         VIEW_(STATUS, "status", &status_ops, TRUE,  ""),
1719         VIEW_(STAGE,  "stage",  &stage_ops,  TRUE,  ""),
1720 };
1722 #define VIEW(req)       (&views[(req) - REQ_OFFSET - 1])
1723 #define VIEW_REQ(view)  ((view) - views + REQ_OFFSET + 1)
1725 #define foreach_view(view, i) \
1726         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1728 #define view_is_displayed(view) \
1729         (view == display[0] || view == display[1])
1732 enum line_graphic {
1733         LINE_GRAPHIC_VLINE
1734 };
1736 static int line_graphics[] = {
1737         /* LINE_GRAPHIC_VLINE: */ '|'
1738 };
1740 static inline void
1741 set_view_attr(struct view *view, enum line_type type)
1743         if (!view->curline->selected && view->curtype != type) {
1744                 wattrset(view->win, get_line_attr(type));
1745                 wchgat(view->win, -1, 0, type, NULL);
1746                 view->curtype = type;
1747         }
1750 static int
1751 draw_chars(struct view *view, enum line_type type, const char *string,
1752            int max_len, bool use_tilde)
1754         int len = 0;
1755         int col = 0;
1756         int trimmed = FALSE;
1758         if (max_len <= 0)
1759                 return 0;
1761         if (opt_utf8) {
1762                 len = utf8_length(string, &col, max_len, &trimmed, use_tilde);
1763         } else {
1764                 col = len = strlen(string);
1765                 if (len > max_len) {
1766                         if (use_tilde) {
1767                                 max_len -= 1;
1768                         }
1769                         col = len = max_len;
1770                         trimmed = TRUE;
1771                 }
1772         }
1774         set_view_attr(view, type);
1775         waddnstr(view->win, string, len);
1776         if (trimmed && use_tilde) {
1777                 set_view_attr(view, LINE_DELIMITER);
1778                 waddch(view->win, '~');
1779                 col++;
1780         }
1782         return col;
1785 static int
1786 draw_space(struct view *view, enum line_type type, int max, int spaces)
1788         static char space[] = "                    ";
1789         int col = 0;
1791         spaces = MIN(max, spaces);
1793         while (spaces > 0) {
1794                 int len = MIN(spaces, sizeof(space) - 1);
1796                 col += draw_chars(view, type, space, spaces, FALSE);
1797                 spaces -= len;
1798         }
1800         return col;
1803 static bool
1804 draw_lineno(struct view *view, unsigned int lineno)
1806         char number[10];
1807         int digits3 = view->digits < 3 ? 3 : view->digits;
1808         int max_number = MIN(digits3, STRING_SIZE(number));
1809         int max = view->width - view->col;
1810         int col;
1812         if (max < max_number)
1813                 max_number = max;
1815         lineno += view->offset + 1;
1816         if (lineno == 1 || (lineno % opt_num_interval) == 0) {
1817                 static char fmt[] = "%1ld";
1819                 if (view->digits <= 9)
1820                         fmt[1] = '0' + digits3;
1822                 if (!string_format(number, fmt, lineno))
1823                         number[0] = 0;
1824                 col = draw_chars(view, LINE_LINE_NUMBER, number, max_number, TRUE);
1825         } else {
1826                 col = draw_space(view, LINE_LINE_NUMBER, max_number, max_number);
1827         }
1829         if (col < max) {
1830                 set_view_attr(view, LINE_DEFAULT);
1831                 waddch(view->win, line_graphics[LINE_GRAPHIC_VLINE]);
1832                 col++;
1833         }
1835         if (col < max)
1836                 col += draw_space(view, LINE_DEFAULT, max - col, 1);
1837         view->col += col;
1839         return view->width - view->col <= 0;
1842 static bool
1843 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
1845         view->col += draw_chars(view, type, string, view->width - view->col, trim);
1846         return view->width - view->col <= 0;
1849 static bool
1850 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
1852         int max = view->width - view->col;
1853         int i;
1855         if (max < size)
1856                 size = max;
1858         set_view_attr(view, type);
1859         /* Using waddch() instead of waddnstr() ensures that
1860          * they'll be rendered correctly for the cursor line. */
1861         for (i = 0; i < size; i++)
1862                 waddch(view->win, graphic[i]);
1864         view->col += size;
1865         if (size < max) {
1866                 waddch(view->win, ' ');
1867                 view->col++;
1868         }
1870         return view->width - view->col <= 0;
1873 static bool
1874 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
1876         int max = MIN(view->width - view->col, len);
1877         int col;
1879         if (text)
1880                 col = draw_chars(view, type, text, max - 1, trim);
1881         else
1882                 col = draw_space(view, type, max - 1, max - 1);
1884         view->col += col + draw_space(view, LINE_DEFAULT, max - col, max - col);
1885         return view->width - view->col <= 0;
1888 static bool
1889 draw_date(struct view *view, struct tm *time)
1891         char buf[DATE_COLS];
1892         char *date;
1893         int timelen = 0;
1895         if (time)
1896                 timelen = strftime(buf, sizeof(buf), DATE_FORMAT, time);
1897         date = timelen ? buf : NULL;
1899         return draw_field(view, LINE_DATE, date, DATE_COLS, FALSE);
1902 static bool
1903 draw_view_line(struct view *view, unsigned int lineno)
1905         struct line *line;
1906         bool selected = (view->offset + lineno == view->lineno);
1907         bool draw_ok;
1909         assert(view_is_displayed(view));
1911         if (view->offset + lineno >= view->lines)
1912                 return FALSE;
1914         line = &view->line[view->offset + lineno];
1916         wmove(view->win, lineno, 0);
1917         view->col = 0;
1918         view->curline = line;
1919         view->curtype = LINE_NONE;
1920         line->selected = FALSE;
1922         if (selected) {
1923                 set_view_attr(view, LINE_CURSOR);
1924                 line->selected = TRUE;
1925                 view->ops->select(view, line);
1926         } else if (line->selected) {
1927                 wclrtoeol(view->win);
1928         }
1930         scrollok(view->win, FALSE);
1931         draw_ok = view->ops->draw(view, line, lineno);
1932         scrollok(view->win, TRUE);
1934         return draw_ok;
1937 static void
1938 redraw_view_dirty(struct view *view)
1940         bool dirty = FALSE;
1941         int lineno;
1943         for (lineno = 0; lineno < view->height; lineno++) {
1944                 struct line *line = &view->line[view->offset + lineno];
1946                 if (!line->dirty)
1947                         continue;
1948                 line->dirty = 0;
1949                 dirty = TRUE;
1950                 if (!draw_view_line(view, lineno))
1951                         break;
1952         }
1954         if (!dirty)
1955                 return;
1956         redrawwin(view->win);
1957         if (input_mode)
1958                 wnoutrefresh(view->win);
1959         else
1960                 wrefresh(view->win);
1963 static void
1964 redraw_view_from(struct view *view, int lineno)
1966         assert(0 <= lineno && lineno < view->height);
1968         for (; lineno < view->height; lineno++) {
1969                 if (!draw_view_line(view, lineno))
1970                         break;
1971         }
1973         redrawwin(view->win);
1974         if (input_mode)
1975                 wnoutrefresh(view->win);
1976         else
1977                 wrefresh(view->win);
1980 static void
1981 redraw_view(struct view *view)
1983         wclear(view->win);
1984         redraw_view_from(view, 0);
1988 static void
1989 update_view_title(struct view *view)
1991         char buf[SIZEOF_STR];
1992         char state[SIZEOF_STR];
1993         size_t bufpos = 0, statelen = 0;
1995         assert(view_is_displayed(view));
1997         if (view != VIEW(REQ_VIEW_STATUS) && (view->lines || view->pipe)) {
1998                 unsigned int view_lines = view->offset + view->height;
1999                 unsigned int lines = view->lines
2000                                    ? MIN(view_lines, view->lines) * 100 / view->lines
2001                                    : 0;
2003                 string_format_from(state, &statelen, "- %s %d of %d (%d%%)",
2004                                    view->ops->type,
2005                                    view->lineno + 1,
2006                                    view->lines,
2007                                    lines);
2009                 if (view->pipe) {
2010                         time_t secs = time(NULL) - view->start_time;
2012                         /* Three git seconds are a long time ... */
2013                         if (secs > 2)
2014                                 string_format_from(state, &statelen, " %lds", secs);
2015                 }
2016         }
2018         string_format_from(buf, &bufpos, "[%s]", view->name);
2019         if (*view->ref && bufpos < view->width) {
2020                 size_t refsize = strlen(view->ref);
2021                 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2023                 if (minsize < view->width)
2024                         refsize = view->width - minsize + 7;
2025                 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2026         }
2028         if (statelen && bufpos < view->width) {
2029                 string_format_from(buf, &bufpos, " %s", state);
2030         }
2032         if (view == display[current_view])
2033                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2034         else
2035                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2037         mvwaddnstr(view->title, 0, 0, buf, bufpos);
2038         wclrtoeol(view->title);
2039         wmove(view->title, 0, view->width - 1);
2041         if (input_mode)
2042                 wnoutrefresh(view->title);
2043         else
2044                 wrefresh(view->title);
2047 static void
2048 resize_display(void)
2050         int offset, i;
2051         struct view *base = display[0];
2052         struct view *view = display[1] ? display[1] : display[0];
2054         /* Setup window dimensions */
2056         getmaxyx(stdscr, base->height, base->width);
2058         /* Make room for the status window. */
2059         base->height -= 1;
2061         if (view != base) {
2062                 /* Horizontal split. */
2063                 view->width   = base->width;
2064                 view->height  = SCALE_SPLIT_VIEW(base->height);
2065                 base->height -= view->height;
2067                 /* Make room for the title bar. */
2068                 view->height -= 1;
2069         }
2071         /* Make room for the title bar. */
2072         base->height -= 1;
2074         offset = 0;
2076         foreach_displayed_view (view, i) {
2077                 if (!view->win) {
2078                         view->win = newwin(view->height, 0, offset, 0);
2079                         if (!view->win)
2080                                 die("Failed to create %s view", view->name);
2082                         scrollok(view->win, TRUE);
2084                         view->title = newwin(1, 0, offset + view->height, 0);
2085                         if (!view->title)
2086                                 die("Failed to create title window");
2088                 } else {
2089                         wresize(view->win, view->height, view->width);
2090                         mvwin(view->win,   offset, 0);
2091                         mvwin(view->title, offset + view->height, 0);
2092                 }
2094                 offset += view->height + 1;
2095         }
2098 static void
2099 redraw_display(void)
2101         struct view *view;
2102         int i;
2104         foreach_displayed_view (view, i) {
2105                 redraw_view(view);
2106                 update_view_title(view);
2107         }
2110 static void
2111 update_display_cursor(struct view *view)
2113         /* Move the cursor to the right-most column of the cursor line.
2114          *
2115          * XXX: This could turn out to be a bit expensive, but it ensures that
2116          * the cursor does not jump around. */
2117         if (view->lines) {
2118                 wmove(view->win, view->lineno - view->offset, view->width - 1);
2119                 wrefresh(view->win);
2120         }
2123 /*
2124  * Navigation
2125  */
2127 /* Scrolling backend */
2128 static void
2129 do_scroll_view(struct view *view, int lines)
2131         bool redraw_current_line = FALSE;
2133         /* The rendering expects the new offset. */
2134         view->offset += lines;
2136         assert(0 <= view->offset && view->offset < view->lines);
2137         assert(lines);
2139         /* Move current line into the view. */
2140         if (view->lineno < view->offset) {
2141                 view->lineno = view->offset;
2142                 redraw_current_line = TRUE;
2143         } else if (view->lineno >= view->offset + view->height) {
2144                 view->lineno = view->offset + view->height - 1;
2145                 redraw_current_line = TRUE;
2146         }
2148         assert(view->offset <= view->lineno && view->lineno < view->lines);
2150         /* Redraw the whole screen if scrolling is pointless. */
2151         if (view->height < ABS(lines)) {
2152                 redraw_view(view);
2154         } else {
2155                 int line = lines > 0 ? view->height - lines : 0;
2156                 int end = line + ABS(lines);
2158                 wscrl(view->win, lines);
2160                 for (; line < end; line++) {
2161                         if (!draw_view_line(view, line))
2162                                 break;
2163                 }
2165                 if (redraw_current_line)
2166                         draw_view_line(view, view->lineno - view->offset);
2167         }
2169         redrawwin(view->win);
2170         wrefresh(view->win);
2171         report("");
2174 /* Scroll frontend */
2175 static void
2176 scroll_view(struct view *view, enum request request)
2178         int lines = 1;
2180         assert(view_is_displayed(view));
2182         switch (request) {
2183         case REQ_SCROLL_PAGE_DOWN:
2184                 lines = view->height;
2185         case REQ_SCROLL_LINE_DOWN:
2186                 if (view->offset + lines > view->lines)
2187                         lines = view->lines - view->offset;
2189                 if (lines == 0 || view->offset + view->height >= view->lines) {
2190                         report("Cannot scroll beyond the last line");
2191                         return;
2192                 }
2193                 break;
2195         case REQ_SCROLL_PAGE_UP:
2196                 lines = view->height;
2197         case REQ_SCROLL_LINE_UP:
2198                 if (lines > view->offset)
2199                         lines = view->offset;
2201                 if (lines == 0) {
2202                         report("Cannot scroll beyond the first line");
2203                         return;
2204                 }
2206                 lines = -lines;
2207                 break;
2209         default:
2210                 die("request %d not handled in switch", request);
2211         }
2213         do_scroll_view(view, lines);
2216 /* Cursor moving */
2217 static void
2218 move_view(struct view *view, enum request request)
2220         int scroll_steps = 0;
2221         int steps;
2223         switch (request) {
2224         case REQ_MOVE_FIRST_LINE:
2225                 steps = -view->lineno;
2226                 break;
2228         case REQ_MOVE_LAST_LINE:
2229                 steps = view->lines - view->lineno - 1;
2230                 break;
2232         case REQ_MOVE_PAGE_UP:
2233                 steps = view->height > view->lineno
2234                       ? -view->lineno : -view->height;
2235                 break;
2237         case REQ_MOVE_PAGE_DOWN:
2238                 steps = view->lineno + view->height >= view->lines
2239                       ? view->lines - view->lineno - 1 : view->height;
2240                 break;
2242         case REQ_MOVE_UP:
2243                 steps = -1;
2244                 break;
2246         case REQ_MOVE_DOWN:
2247                 steps = 1;
2248                 break;
2250         default:
2251                 die("request %d not handled in switch", request);
2252         }
2254         if (steps <= 0 && view->lineno == 0) {
2255                 report("Cannot move beyond the first line");
2256                 return;
2258         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2259                 report("Cannot move beyond the last line");
2260                 return;
2261         }
2263         /* Move the current line */
2264         view->lineno += steps;
2265         assert(0 <= view->lineno && view->lineno < view->lines);
2267         /* Check whether the view needs to be scrolled */
2268         if (view->lineno < view->offset ||
2269             view->lineno >= view->offset + view->height) {
2270                 scroll_steps = steps;
2271                 if (steps < 0 && -steps > view->offset) {
2272                         scroll_steps = -view->offset;
2274                 } else if (steps > 0) {
2275                         if (view->lineno == view->lines - 1 &&
2276                             view->lines > view->height) {
2277                                 scroll_steps = view->lines - view->offset - 1;
2278                                 if (scroll_steps >= view->height)
2279                                         scroll_steps -= view->height - 1;
2280                         }
2281                 }
2282         }
2284         if (!view_is_displayed(view)) {
2285                 view->offset += scroll_steps;
2286                 assert(0 <= view->offset && view->offset < view->lines);
2287                 view->ops->select(view, &view->line[view->lineno]);
2288                 return;
2289         }
2291         /* Repaint the old "current" line if we be scrolling */
2292         if (ABS(steps) < view->height)
2293                 draw_view_line(view, view->lineno - steps - view->offset);
2295         if (scroll_steps) {
2296                 do_scroll_view(view, scroll_steps);
2297                 return;
2298         }
2300         /* Draw the current line */
2301         draw_view_line(view, view->lineno - view->offset);
2303         redrawwin(view->win);
2304         wrefresh(view->win);
2305         report("");
2309 /*
2310  * Searching
2311  */
2313 static void search_view(struct view *view, enum request request);
2315 static bool
2316 find_next_line(struct view *view, unsigned long lineno, struct line *line)
2318         assert(view_is_displayed(view));
2320         if (!view->ops->grep(view, line))
2321                 return FALSE;
2323         if (lineno - view->offset >= view->height) {
2324                 view->offset = lineno;
2325                 view->lineno = lineno;
2326                 redraw_view(view);
2328         } else {
2329                 unsigned long old_lineno = view->lineno - view->offset;
2331                 view->lineno = lineno;
2332                 draw_view_line(view, old_lineno);
2334                 draw_view_line(view, view->lineno - view->offset);
2335                 redrawwin(view->win);
2336                 wrefresh(view->win);
2337         }
2339         report("Line %ld matches '%s'", lineno + 1, view->grep);
2340         return TRUE;
2343 static void
2344 find_next(struct view *view, enum request request)
2346         unsigned long lineno = view->lineno;
2347         int direction;
2349         if (!*view->grep) {
2350                 if (!*opt_search)
2351                         report("No previous search");
2352                 else
2353                         search_view(view, request);
2354                 return;
2355         }
2357         switch (request) {
2358         case REQ_SEARCH:
2359         case REQ_FIND_NEXT:
2360                 direction = 1;
2361                 break;
2363         case REQ_SEARCH_BACK:
2364         case REQ_FIND_PREV:
2365                 direction = -1;
2366                 break;
2368         default:
2369                 return;
2370         }
2372         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2373                 lineno += direction;
2375         /* Note, lineno is unsigned long so will wrap around in which case it
2376          * will become bigger than view->lines. */
2377         for (; lineno < view->lines; lineno += direction) {
2378                 struct line *line = &view->line[lineno];
2380                 if (find_next_line(view, lineno, line))
2381                         return;
2382         }
2384         report("No match found for '%s'", view->grep);
2387 static void
2388 search_view(struct view *view, enum request request)
2390         int regex_err;
2392         if (view->regex) {
2393                 regfree(view->regex);
2394                 *view->grep = 0;
2395         } else {
2396                 view->regex = calloc(1, sizeof(*view->regex));
2397                 if (!view->regex)
2398                         return;
2399         }
2401         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2402         if (regex_err != 0) {
2403                 char buf[SIZEOF_STR] = "unknown error";
2405                 regerror(regex_err, view->regex, buf, sizeof(buf));
2406                 report("Search failed: %s", buf);
2407                 return;
2408         }
2410         string_copy(view->grep, opt_search);
2412         find_next(view, request);
2415 /*
2416  * Incremental updating
2417  */
2419 static void
2420 reset_view(struct view *view)
2422         int i;
2424         for (i = 0; i < view->lines; i++)
2425                 free(view->line[i].data);
2426         free(view->line);
2428         view->line = NULL;
2429         view->offset = 0;
2430         view->lines  = 0;
2431         view->lineno = 0;
2432         view->line_size = 0;
2433         view->line_alloc = 0;
2434         view->vid[0] = 0;
2437 static void
2438 free_argv(const char *argv[])
2440         int argc;
2442         for (argc = 0; argv[argc]; argc++)
2443                 free((void *) argv[argc]);
2446 static bool
2447 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
2449         char buf[SIZEOF_STR];
2450         int argc;
2451         bool noreplace = flags == FORMAT_NONE;
2453         free_argv(dst_argv);
2455         for (argc = 0; src_argv[argc]; argc++) {
2456                 const char *arg = src_argv[argc];
2457                 size_t bufpos = 0;
2459                 while (arg) {
2460                         char *next = strstr(arg, "%(");
2461                         int len = next - arg;
2462                         const char *value;
2464                         if (!next || noreplace) {
2465                                 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
2466                                         noreplace = TRUE;
2467                                 len = strlen(arg);
2468                                 value = "";
2470                         } else if (!prefixcmp(next, "%(directory)")) {
2471                                 value = opt_path;
2473                         } else if (!prefixcmp(next, "%(file)")) {
2474                                 value = opt_file;
2476                         } else if (!prefixcmp(next, "%(ref)")) {
2477                                 value = *opt_ref ? opt_ref : "HEAD";
2479                         } else if (!prefixcmp(next, "%(head)")) {
2480                                 value = ref_head;
2482                         } else if (!prefixcmp(next, "%(commit)")) {
2483                                 value = ref_commit;
2485                         } else if (!prefixcmp(next, "%(blob)")) {
2486                                 value = ref_blob;
2488                         } else {
2489                                 report("Unknown replacement: `%s`", next);
2490                                 return FALSE;
2491                         }
2493                         if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
2494                                 return FALSE;
2496                         arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
2497                 }
2499                 dst_argv[argc] = strdup(buf);
2500                 if (!dst_argv[argc])
2501                         break;
2502         }
2504         dst_argv[argc] = NULL;
2506         return src_argv[argc] == NULL;
2509 static bool
2510 format_command(char dst[], const char *src_argv[], enum format_flags flags)
2512         const char *dst_argv[SIZEOF_ARG * 2] = { NULL };
2513         int bufsize = 0;
2514         int argc;
2516         if (!format_argv(dst_argv, src_argv, flags)) {
2517                 free_argv(dst_argv);
2518                 return FALSE;
2519         }
2521         for (argc = 0; dst_argv[argc] && bufsize < SIZEOF_STR; argc++) {
2522                 if (bufsize > 0)
2523                         dst[bufsize++] = ' ';
2524                 bufsize = sq_quote(dst, bufsize, dst_argv[argc]);
2525         }
2527         if (bufsize < SIZEOF_STR)
2528                 dst[bufsize] = 0;
2529         free_argv(dst_argv);
2531         return src_argv[argc] == NULL && bufsize < SIZEOF_STR;
2534 static void
2535 end_update(struct view *view, bool force)
2537         if (!view->pipe)
2538                 return;
2539         while (!view->ops->read(view, NULL))
2540                 if (!force)
2541                         return;
2542         set_nonblocking_input(FALSE);
2543         done_io(view->pipe);
2544         view->pipe = NULL;
2547 static void
2548 setup_update(struct view *view, const char *vid)
2550         set_nonblocking_input(TRUE);
2551         reset_view(view);
2552         string_copy_rev(view->vid, vid);
2553         view->pipe = &view->io;
2554         view->start_time = time(NULL);
2557 static bool
2558 prepare_update(struct view *view, const char *argv[], const char *dir,
2559                enum format_flags flags)
2561         if (view->pipe)
2562                 end_update(view, TRUE);
2563         return init_io_rd(&view->io, argv, dir, flags);
2566 static bool
2567 prepare_update_file(struct view *view, const char *name)
2569         if (view->pipe)
2570                 end_update(view, TRUE);
2571         return io_open(&view->io, name);
2574 static bool
2575 begin_update(struct view *view, bool refresh)
2577         if (refresh) {
2578                 if (!start_io(&view->io))
2579                         return FALSE;
2581         } else {
2582                 if (view == VIEW(REQ_VIEW_TREE) && strcmp(view->vid, view->id))
2583                         opt_path[0] = 0;
2585                 if (!run_io_rd(&view->io, view->ops->argv, FORMAT_ALL))
2586                         return FALSE;
2588                 /* Put the current ref_* value to the view title ref
2589                  * member. This is needed by the blob view. Most other
2590                  * views sets it automatically after loading because the
2591                  * first line is a commit line. */
2592                 string_copy_rev(view->ref, view->id);
2593         }
2595         setup_update(view, view->id);
2597         return TRUE;
2600 #define ITEM_CHUNK_SIZE 256
2601 static void *
2602 realloc_items(void *mem, size_t *size, size_t new_size, size_t item_size)
2604         size_t num_chunks = *size / ITEM_CHUNK_SIZE;
2605         size_t num_chunks_new = (new_size + ITEM_CHUNK_SIZE - 1) / ITEM_CHUNK_SIZE;
2607         if (mem == NULL || num_chunks != num_chunks_new) {
2608                 *size = num_chunks_new * ITEM_CHUNK_SIZE;
2609                 mem = realloc(mem, *size * item_size);
2610         }
2612         return mem;
2615 static struct line *
2616 realloc_lines(struct view *view, size_t line_size)
2618         size_t alloc = view->line_alloc;
2619         struct line *tmp = realloc_items(view->line, &alloc, line_size,
2620                                          sizeof(*view->line));
2622         if (!tmp)
2623                 return NULL;
2625         view->line = tmp;
2626         view->line_alloc = alloc;
2627         view->line_size = line_size;
2628         return view->line;
2631 static bool
2632 update_view(struct view *view)
2634         char out_buffer[BUFSIZ * 2];
2635         char *line;
2636         /* The number of lines to read. If too low it will cause too much
2637          * redrawing (and possible flickering), if too high responsiveness
2638          * will suffer. */
2639         unsigned long lines = view->height;
2640         int redraw_from = -1;
2642         if (!view->pipe)
2643                 return TRUE;
2645         /* Only redraw if lines are visible. */
2646         if (view->offset + view->height >= view->lines)
2647                 redraw_from = view->lines - view->offset;
2649         /* FIXME: This is probably not perfect for backgrounded views. */
2650         if (!realloc_lines(view, view->lines + lines))
2651                 goto alloc_error;
2653         while ((line = io_gets(view->pipe))) {
2654                 size_t linelen = strlen(line);
2656                 if (linelen)
2657                         line[linelen - 1] = 0;
2659                 if (opt_iconv != ICONV_NONE) {
2660                         ICONV_CONST char *inbuf = line;
2661                         size_t inlen = linelen;
2663                         char *outbuf = out_buffer;
2664                         size_t outlen = sizeof(out_buffer);
2666                         size_t ret;
2668                         ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2669                         if (ret != (size_t) -1) {
2670                                 line = out_buffer;
2671                                 linelen = strlen(out_buffer);
2672                         }
2673                 }
2675                 if (!view->ops->read(view, line))
2676                         goto alloc_error;
2678                 if (lines-- == 1)
2679                         break;
2680         }
2682         {
2683                 int digits;
2685                 lines = view->lines;
2686                 for (digits = 0; lines; digits++)
2687                         lines /= 10;
2689                 /* Keep the displayed view in sync with line number scaling. */
2690                 if (digits != view->digits) {
2691                         view->digits = digits;
2692                         redraw_from = 0;
2693                 }
2694         }
2696         if (io_error(view->pipe)) {
2697                 report("Failed to read: %s", io_strerror(view->pipe));
2698                 end_update(view, TRUE);
2700         } else if (io_eof(view->pipe)) {
2701                 report("");
2702                 end_update(view, FALSE);
2703         }
2705         if (!view_is_displayed(view))
2706                 return TRUE;
2708         if (view == VIEW(REQ_VIEW_TREE)) {
2709                 /* Clear the view and redraw everything since the tree sorting
2710                  * might have rearranged things. */
2711                 redraw_view(view);
2713         } else if (redraw_from >= 0) {
2714                 /* If this is an incremental update, redraw the previous line
2715                  * since for commits some members could have changed when
2716                  * loading the main view. */
2717                 if (redraw_from > 0)
2718                         redraw_from--;
2720                 /* Since revision graph visualization requires knowledge
2721                  * about the parent commit, it causes a further one-off
2722                  * needed to be redrawn for incremental updates. */
2723                 if (redraw_from > 0 && opt_rev_graph)
2724                         redraw_from--;
2726                 /* Incrementally draw avoids flickering. */
2727                 redraw_view_from(view, redraw_from);
2728         }
2730         if (view == VIEW(REQ_VIEW_BLAME))
2731                 redraw_view_dirty(view);
2733         /* Update the title _after_ the redraw so that if the redraw picks up a
2734          * commit reference in view->ref it'll be available here. */
2735         update_view_title(view);
2736         return TRUE;
2738 alloc_error:
2739         report("Allocation failure");
2740         end_update(view, TRUE);
2741         return FALSE;
2744 static struct line *
2745 add_line_data(struct view *view, void *data, enum line_type type)
2747         struct line *line = &view->line[view->lines++];
2749         memset(line, 0, sizeof(*line));
2750         line->type = type;
2751         line->data = data;
2753         return line;
2756 static struct line *
2757 add_line_text(struct view *view, const char *text, enum line_type type)
2759         char *data = text ? strdup(text) : NULL;
2761         return data ? add_line_data(view, data, type) : NULL;
2765 /*
2766  * View opening
2767  */
2769 enum open_flags {
2770         OPEN_DEFAULT = 0,       /* Use default view switching. */
2771         OPEN_SPLIT = 1,         /* Split current view. */
2772         OPEN_BACKGROUNDED = 2,  /* Backgrounded. */
2773         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
2774         OPEN_NOMAXIMIZE = 8,    /* Do not maximize the current view. */
2775         OPEN_REFRESH = 16,      /* Refresh view using previous command. */
2776         OPEN_PREPARED = 32,     /* Open already prepared command. */
2777 };
2779 static void
2780 open_view(struct view *prev, enum request request, enum open_flags flags)
2782         bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
2783         bool split = !!(flags & OPEN_SPLIT);
2784         bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
2785         bool nomaximize = !!(flags & (OPEN_NOMAXIMIZE | OPEN_REFRESH));
2786         struct view *view = VIEW(request);
2787         int nviews = displayed_views();
2788         struct view *base_view = display[0];
2790         if (view == prev && nviews == 1 && !reload) {
2791                 report("Already in %s view", view->name);
2792                 return;
2793         }
2795         if (view->git_dir && !opt_git_dir[0]) {
2796                 report("The %s view is disabled in pager view", view->name);
2797                 return;
2798         }
2800         if (split) {
2801                 display[1] = view;
2802                 if (!backgrounded)
2803                         current_view = 1;
2804         } else if (!nomaximize) {
2805                 /* Maximize the current view. */
2806                 memset(display, 0, sizeof(display));
2807                 current_view = 0;
2808                 display[current_view] = view;
2809         }
2811         /* Resize the view when switching between split- and full-screen,
2812          * or when switching between two different full-screen views. */
2813         if (nviews != displayed_views() ||
2814             (nviews == 1 && base_view != display[0]))
2815                 resize_display();
2817         if (view->pipe)
2818                 end_update(view, TRUE);
2820         if (view->ops->open) {
2821                 if (!view->ops->open(view)) {
2822                         report("Failed to load %s view", view->name);
2823                         return;
2824                 }
2826         } else if ((reload || strcmp(view->vid, view->id)) &&
2827                    !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
2828                 report("Failed to load %s view", view->name);
2829                 return;
2830         }
2832         if (split && prev->lineno - prev->offset >= prev->height) {
2833                 /* Take the title line into account. */
2834                 int lines = prev->lineno - prev->offset - prev->height + 1;
2836                 /* Scroll the view that was split if the current line is
2837                  * outside the new limited view. */
2838                 do_scroll_view(prev, lines);
2839         }
2841         if (prev && view != prev) {
2842                 if (split && !backgrounded) {
2843                         /* "Blur" the previous view. */
2844                         update_view_title(prev);
2845                 }
2847                 view->parent = prev;
2848         }
2850         if (view->pipe && view->lines == 0) {
2851                 /* Clear the old view and let the incremental updating refill
2852                  * the screen. */
2853                 werase(view->win);
2854                 report("");
2855         } else if (view_is_displayed(view)) {
2856                 redraw_view(view);
2857                 report("");
2858         }
2860         /* If the view is backgrounded the above calls to report()
2861          * won't redraw the view title. */
2862         if (backgrounded)
2863                 update_view_title(view);
2866 static void
2867 open_external_viewer(const char *argv[], const char *dir)
2869         def_prog_mode();           /* save current tty modes */
2870         endwin();                  /* restore original tty modes */
2871         run_io_fg(argv, dir);
2872         fprintf(stderr, "Press Enter to continue");
2873         getc(opt_tty);
2874         reset_prog_mode();
2875         redraw_display();
2878 static void
2879 open_mergetool(const char *file)
2881         const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
2883         open_external_viewer(mergetool_argv, NULL);
2886 static void
2887 open_editor(bool from_root, const char *file)
2889         const char *editor_argv[] = { "vi", file, NULL };
2890         const char *editor;
2892         editor = getenv("GIT_EDITOR");
2893         if (!editor && *opt_editor)
2894                 editor = opt_editor;
2895         if (!editor)
2896                 editor = getenv("VISUAL");
2897         if (!editor)
2898                 editor = getenv("EDITOR");
2899         if (!editor)
2900                 editor = "vi";
2902         editor_argv[0] = editor;
2903         open_external_viewer(editor_argv, from_root ? opt_cdup : NULL);
2906 static void
2907 open_run_request(enum request request)
2909         struct run_request *req = get_run_request(request);
2910         const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
2912         if (!req) {
2913                 report("Unknown run request");
2914                 return;
2915         }
2917         if (format_argv(argv, req->argv, FORMAT_ALL))
2918                 open_external_viewer(argv, NULL);
2919         free_argv(argv);
2922 /*
2923  * User request switch noodle
2924  */
2926 static int
2927 view_driver(struct view *view, enum request request)
2929         int i;
2931         if (request == REQ_NONE) {
2932                 doupdate();
2933                 return TRUE;
2934         }
2936         if (request > REQ_NONE) {
2937                 open_run_request(request);
2938                 /* FIXME: When all views can refresh always do this. */
2939                 if (view == VIEW(REQ_VIEW_STATUS) ||
2940                     view == VIEW(REQ_VIEW_MAIN) ||
2941                     view == VIEW(REQ_VIEW_LOG) ||
2942                     view == VIEW(REQ_VIEW_STAGE))
2943                         request = REQ_REFRESH;
2944                 else
2945                         return TRUE;
2946         }
2948         if (view && view->lines) {
2949                 request = view->ops->request(view, request, &view->line[view->lineno]);
2950                 if (request == REQ_NONE)
2951                         return TRUE;
2952         }
2954         switch (request) {
2955         case REQ_MOVE_UP:
2956         case REQ_MOVE_DOWN:
2957         case REQ_MOVE_PAGE_UP:
2958         case REQ_MOVE_PAGE_DOWN:
2959         case REQ_MOVE_FIRST_LINE:
2960         case REQ_MOVE_LAST_LINE:
2961                 move_view(view, request);
2962                 break;
2964         case REQ_SCROLL_LINE_DOWN:
2965         case REQ_SCROLL_LINE_UP:
2966         case REQ_SCROLL_PAGE_DOWN:
2967         case REQ_SCROLL_PAGE_UP:
2968                 scroll_view(view, request);
2969                 break;
2971         case REQ_VIEW_BLAME:
2972                 if (!opt_file[0]) {
2973                         report("No file chosen, press %s to open tree view",
2974                                get_key(REQ_VIEW_TREE));
2975                         break;
2976                 }
2977                 open_view(view, request, OPEN_DEFAULT);
2978                 break;
2980         case REQ_VIEW_BLOB:
2981                 if (!ref_blob[0]) {
2982                         report("No file chosen, press %s to open tree view",
2983                                get_key(REQ_VIEW_TREE));
2984                         break;
2985                 }
2986                 open_view(view, request, OPEN_DEFAULT);
2987                 break;
2989         case REQ_VIEW_PAGER:
2990                 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
2991                         report("No pager content, press %s to run command from prompt",
2992                                get_key(REQ_PROMPT));
2993                         break;
2994                 }
2995                 open_view(view, request, OPEN_DEFAULT);
2996                 break;
2998         case REQ_VIEW_STAGE:
2999                 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3000                         report("No stage content, press %s to open the status view and choose file",
3001                                get_key(REQ_VIEW_STATUS));
3002                         break;
3003                 }
3004                 open_view(view, request, OPEN_DEFAULT);
3005                 break;
3007         case REQ_VIEW_STATUS:
3008                 if (opt_is_inside_work_tree == FALSE) {
3009                         report("The status view requires a working tree");
3010                         break;
3011                 }
3012                 open_view(view, request, OPEN_DEFAULT);
3013                 break;
3015         case REQ_VIEW_MAIN:
3016         case REQ_VIEW_DIFF:
3017         case REQ_VIEW_LOG:
3018         case REQ_VIEW_TREE:
3019         case REQ_VIEW_HELP:
3020                 open_view(view, request, OPEN_DEFAULT);
3021                 break;
3023         case REQ_NEXT:
3024         case REQ_PREVIOUS:
3025                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3027                 if ((view == VIEW(REQ_VIEW_DIFF) &&
3028                      view->parent == VIEW(REQ_VIEW_MAIN)) ||
3029                    (view == VIEW(REQ_VIEW_DIFF) &&
3030                      view->parent == VIEW(REQ_VIEW_BLAME)) ||
3031                    (view == VIEW(REQ_VIEW_STAGE) &&
3032                      view->parent == VIEW(REQ_VIEW_STATUS)) ||
3033                    (view == VIEW(REQ_VIEW_BLOB) &&
3034                      view->parent == VIEW(REQ_VIEW_TREE))) {
3035                         int line;
3037                         view = view->parent;
3038                         line = view->lineno;
3039                         move_view(view, request);
3040                         if (view_is_displayed(view))
3041                                 update_view_title(view);
3042                         if (line != view->lineno)
3043                                 view->ops->request(view, REQ_ENTER,
3044                                                    &view->line[view->lineno]);
3046                 } else {
3047                         move_view(view, request);
3048                 }
3049                 break;
3051         case REQ_VIEW_NEXT:
3052         {
3053                 int nviews = displayed_views();
3054                 int next_view = (current_view + 1) % nviews;
3056                 if (next_view == current_view) {
3057                         report("Only one view is displayed");
3058                         break;
3059                 }
3061                 current_view = next_view;
3062                 /* Blur out the title of the previous view. */
3063                 update_view_title(view);
3064                 report("");
3065                 break;
3066         }
3067         case REQ_REFRESH:
3068                 report("Refreshing is not yet supported for the %s view", view->name);
3069                 break;
3071         case REQ_MAXIMIZE:
3072                 if (displayed_views() == 2)
3073                         open_view(view, VIEW_REQ(view), OPEN_DEFAULT);
3074                 break;
3076         case REQ_TOGGLE_LINENO:
3077                 opt_line_number = !opt_line_number;
3078                 redraw_display();
3079                 break;
3081         case REQ_TOGGLE_DATE:
3082                 opt_date = !opt_date;
3083                 redraw_display();
3084                 break;
3086         case REQ_TOGGLE_AUTHOR:
3087                 opt_author = !opt_author;
3088                 redraw_display();
3089                 break;
3091         case REQ_TOGGLE_REV_GRAPH:
3092                 opt_rev_graph = !opt_rev_graph;
3093                 redraw_display();
3094                 break;
3096         case REQ_TOGGLE_REFS:
3097                 opt_show_refs = !opt_show_refs;
3098                 redraw_display();
3099                 break;
3101         case REQ_SEARCH:
3102         case REQ_SEARCH_BACK:
3103                 search_view(view, request);
3104                 break;
3106         case REQ_FIND_NEXT:
3107         case REQ_FIND_PREV:
3108                 find_next(view, request);
3109                 break;
3111         case REQ_STOP_LOADING:
3112                 for (i = 0; i < ARRAY_SIZE(views); i++) {
3113                         view = &views[i];
3114                         if (view->pipe)
3115                                 report("Stopped loading the %s view", view->name),
3116                         end_update(view, TRUE);
3117                 }
3118                 break;
3120         case REQ_SHOW_VERSION:
3121                 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3122                 return TRUE;
3124         case REQ_SCREEN_RESIZE:
3125                 resize_display();
3126                 /* Fall-through */
3127         case REQ_SCREEN_REDRAW:
3128                 redraw_display();
3129                 break;
3131         case REQ_EDIT:
3132                 report("Nothing to edit");
3133                 break;
3135         case REQ_ENTER:
3136                 report("Nothing to enter");
3137                 break;
3139         case REQ_VIEW_CLOSE:
3140                 /* XXX: Mark closed views by letting view->parent point to the
3141                  * view itself. Parents to closed view should never be
3142                  * followed. */
3143                 if (view->parent &&
3144                     view->parent->parent != view->parent) {
3145                         memset(display, 0, sizeof(display));
3146                         current_view = 0;
3147                         display[current_view] = view->parent;
3148                         view->parent = view;
3149                         resize_display();
3150                         redraw_display();
3151                         report("");
3152                         break;
3153                 }
3154                 /* Fall-through */
3155         case REQ_QUIT:
3156                 return FALSE;
3158         default:
3159                 report("Unknown key, press 'h' for help");
3160                 return TRUE;
3161         }
3163         return TRUE;
3167 /*
3168  * Pager backend
3169  */
3171 static bool
3172 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3174         char *text = line->data;
3176         if (opt_line_number && draw_lineno(view, lineno))
3177                 return TRUE;
3179         draw_text(view, line->type, text, TRUE);
3180         return TRUE;
3183 static bool
3184 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3186         const char *describe_argv[] = { "git", "describe", commit_id, NULL };
3187         char refbuf[SIZEOF_STR];
3188         char *ref = NULL;
3190         if (run_io_buf(describe_argv, refbuf, sizeof(refbuf)))
3191                 ref = chomp_string(refbuf);
3193         if (!ref || !*ref)
3194                 return TRUE;
3196         /* This is the only fatal call, since it can "corrupt" the buffer. */
3197         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
3198                 return FALSE;
3200         return TRUE;
3203 static void
3204 add_pager_refs(struct view *view, struct line *line)
3206         char buf[SIZEOF_STR];
3207         char *commit_id = (char *)line->data + STRING_SIZE("commit ");
3208         struct ref **refs;
3209         size_t bufpos = 0, refpos = 0;
3210         const char *sep = "Refs: ";
3211         bool is_tag = FALSE;
3213         assert(line->type == LINE_COMMIT);
3215         refs = get_refs(commit_id);
3216         if (!refs) {
3217                 if (view == VIEW(REQ_VIEW_DIFF))
3218                         goto try_add_describe_ref;
3219                 return;
3220         }
3222         do {
3223                 struct ref *ref = refs[refpos];
3224                 const char *fmt = ref->tag    ? "%s[%s]" :
3225                                   ref->remote ? "%s<%s>" : "%s%s";
3227                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
3228                         return;
3229                 sep = ", ";
3230                 if (ref->tag)
3231                         is_tag = TRUE;
3232         } while (refs[refpos++]->next);
3234         if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
3235 try_add_describe_ref:
3236                 /* Add <tag>-g<commit_id> "fake" reference. */
3237                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
3238                         return;
3239         }
3241         if (bufpos == 0)
3242                 return;
3244         if (!realloc_lines(view, view->line_size + 1))
3245                 return;
3247         add_line_text(view, buf, LINE_PP_REFS);
3250 static bool
3251 pager_read(struct view *view, char *data)
3253         struct line *line;
3255         if (!data)
3256                 return TRUE;
3258         line = add_line_text(view, data, get_line_type(data));
3259         if (!line)
3260                 return FALSE;
3262         if (line->type == LINE_COMMIT &&
3263             (view == VIEW(REQ_VIEW_DIFF) ||
3264              view == VIEW(REQ_VIEW_LOG)))
3265                 add_pager_refs(view, line);
3267         return TRUE;
3270 static enum request
3271 pager_request(struct view *view, enum request request, struct line *line)
3273         int split = 0;
3275         if (request != REQ_ENTER)
3276                 return request;
3278         if (line->type == LINE_COMMIT &&
3279            (view == VIEW(REQ_VIEW_LOG) ||
3280             view == VIEW(REQ_VIEW_PAGER))) {
3281                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3282                 split = 1;
3283         }
3285         /* Always scroll the view even if it was split. That way
3286          * you can use Enter to scroll through the log view and
3287          * split open each commit diff. */
3288         scroll_view(view, REQ_SCROLL_LINE_DOWN);
3290         /* FIXME: A minor workaround. Scrolling the view will call report("")
3291          * but if we are scrolling a non-current view this won't properly
3292          * update the view title. */
3293         if (split)
3294                 update_view_title(view);
3296         return REQ_NONE;
3299 static bool
3300 pager_grep(struct view *view, struct line *line)
3302         regmatch_t pmatch;
3303         char *text = line->data;
3305         if (!*text)
3306                 return FALSE;
3308         if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
3309                 return FALSE;
3311         return TRUE;
3314 static void
3315 pager_select(struct view *view, struct line *line)
3317         if (line->type == LINE_COMMIT) {
3318                 char *text = (char *)line->data + STRING_SIZE("commit ");
3320                 if (view != VIEW(REQ_VIEW_PAGER))
3321                         string_copy_rev(view->ref, text);
3322                 string_copy_rev(ref_commit, text);
3323         }
3326 static struct view_ops pager_ops = {
3327         "line",
3328         NULL,
3329         NULL,
3330         pager_read,
3331         pager_draw,
3332         pager_request,
3333         pager_grep,
3334         pager_select,
3335 };
3337 static const char *log_argv[SIZEOF_ARG] = {
3338         "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3339 };
3341 static enum request
3342 log_request(struct view *view, enum request request, struct line *line)
3344         switch (request) {
3345         case REQ_REFRESH:
3346                 load_refs();
3347                 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
3348                 return REQ_NONE;
3349         default:
3350                 return pager_request(view, request, line);
3351         }
3354 static struct view_ops log_ops = {
3355         "line",
3356         log_argv,
3357         NULL,
3358         pager_read,
3359         pager_draw,
3360         log_request,
3361         pager_grep,
3362         pager_select,
3363 };
3365 static const char *diff_argv[SIZEOF_ARG] = {
3366         "git", "show", "--pretty=fuller", "--no-color", "--root",
3367                 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
3368 };
3370 static struct view_ops diff_ops = {
3371         "line",
3372         diff_argv,
3373         NULL,
3374         pager_read,
3375         pager_draw,
3376         pager_request,
3377         pager_grep,
3378         pager_select,
3379 };
3381 /*
3382  * Help backend
3383  */
3385 static bool
3386 help_open(struct view *view)
3388         char buf[BUFSIZ];
3389         int lines = ARRAY_SIZE(req_info) + 2;
3390         int i;
3392         if (view->lines > 0)
3393                 return TRUE;
3395         for (i = 0; i < ARRAY_SIZE(req_info); i++)
3396                 if (!req_info[i].request)
3397                         lines++;
3399         lines += run_requests + 1;
3401         view->line = calloc(lines, sizeof(*view->line));
3402         if (!view->line)
3403                 return FALSE;
3405         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
3407         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
3408                 const char *key;
3410                 if (req_info[i].request == REQ_NONE)
3411                         continue;
3413                 if (!req_info[i].request) {
3414                         add_line_text(view, "", LINE_DEFAULT);
3415                         add_line_text(view, req_info[i].help, LINE_DEFAULT);
3416                         continue;
3417                 }
3419                 key = get_key(req_info[i].request);
3420                 if (!*key)
3421                         key = "(no key defined)";
3423                 if (!string_format(buf, "    %-25s %s", key, req_info[i].help))
3424                         continue;
3426                 add_line_text(view, buf, LINE_DEFAULT);
3427         }
3429         if (run_requests) {
3430                 add_line_text(view, "", LINE_DEFAULT);
3431                 add_line_text(view, "External commands:", LINE_DEFAULT);
3432         }
3434         for (i = 0; i < run_requests; i++) {
3435                 struct run_request *req = get_run_request(REQ_NONE + i + 1);
3436                 const char *key;
3437                 char cmd[SIZEOF_STR];
3438                 size_t bufpos;
3439                 int argc;
3441                 if (!req)
3442                         continue;
3444                 key = get_key_name(req->key);
3445                 if (!*key)
3446                         key = "(no key defined)";
3448                 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
3449                         if (!string_format_from(cmd, &bufpos, "%s%s",
3450                                                 argc ? " " : "", req->argv[argc]))
3451                                 return REQ_NONE;
3453                 if (!string_format(buf, "    %-10s %-14s `%s`",
3454                                    keymap_table[req->keymap].name, key, cmd))
3455                         continue;
3457                 add_line_text(view, buf, LINE_DEFAULT);
3458         }
3460         return TRUE;
3463 static struct view_ops help_ops = {
3464         "line",
3465         NULL,
3466         help_open,
3467         NULL,
3468         pager_draw,
3469         pager_request,
3470         pager_grep,
3471         pager_select,
3472 };
3475 /*
3476  * Tree backend
3477  */
3479 struct tree_stack_entry {
3480         struct tree_stack_entry *prev;  /* Entry below this in the stack */
3481         unsigned long lineno;           /* Line number to restore */
3482         char *name;                     /* Position of name in opt_path */
3483 };
3485 /* The top of the path stack. */
3486 static struct tree_stack_entry *tree_stack = NULL;
3487 unsigned long tree_lineno = 0;
3489 static void
3490 pop_tree_stack_entry(void)
3492         struct tree_stack_entry *entry = tree_stack;
3494         tree_lineno = entry->lineno;
3495         entry->name[0] = 0;
3496         tree_stack = entry->prev;
3497         free(entry);
3500 static void
3501 push_tree_stack_entry(const char *name, unsigned long lineno)
3503         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
3504         size_t pathlen = strlen(opt_path);
3506         if (!entry)
3507                 return;
3509         entry->prev = tree_stack;
3510         entry->name = opt_path + pathlen;
3511         tree_stack = entry;
3513         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
3514                 pop_tree_stack_entry();
3515                 return;
3516         }
3518         /* Move the current line to the first tree entry. */
3519         tree_lineno = 1;
3520         entry->lineno = lineno;
3523 /* Parse output from git-ls-tree(1):
3524  *
3525  * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile
3526  * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README
3527  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
3528  * 100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38 web.conf
3529  */
3531 #define SIZEOF_TREE_ATTR \
3532         STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t")
3534 #define TREE_UP_FORMAT "040000 tree %s\t.."
3536 static int
3537 tree_compare_entry(enum line_type type1, const char *name1,
3538                    enum line_type type2, const char *name2)
3540         if (type1 != type2) {
3541                 if (type1 == LINE_TREE_DIR)
3542                         return -1;
3543                 return 1;
3544         }
3546         return strcmp(name1, name2);
3549 static const char *
3550 tree_path(struct line *line)
3552         const char *path = line->data;
3554         return path + SIZEOF_TREE_ATTR;
3557 static bool
3558 tree_read(struct view *view, char *text)
3560         size_t textlen = text ? strlen(text) : 0;
3561         char buf[SIZEOF_STR];
3562         unsigned long pos;
3563         enum line_type type;
3564         bool first_read = view->lines == 0;
3566         if (!text)
3567                 return TRUE;
3568         if (textlen <= SIZEOF_TREE_ATTR)
3569                 return FALSE;
3571         type = text[STRING_SIZE("100644 ")] == 't'
3572              ? LINE_TREE_DIR : LINE_TREE_FILE;
3574         if (first_read) {
3575                 /* Add path info line */
3576                 if (!string_format(buf, "Directory path /%s", opt_path) ||
3577                     !realloc_lines(view, view->line_size + 1) ||
3578                     !add_line_text(view, buf, LINE_DEFAULT))
3579                         return FALSE;
3581                 /* Insert "link" to parent directory. */
3582                 if (*opt_path) {
3583                         if (!string_format(buf, TREE_UP_FORMAT, view->ref) ||
3584                             !realloc_lines(view, view->line_size + 1) ||
3585                             !add_line_text(view, buf, LINE_TREE_DIR))
3586                                 return FALSE;
3587                 }
3588         }
3590         /* Strip the path part ... */
3591         if (*opt_path) {
3592                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
3593                 size_t striplen = strlen(opt_path);
3594                 char *path = text + SIZEOF_TREE_ATTR;
3596                 if (pathlen > striplen)
3597                         memmove(path, path + striplen,
3598                                 pathlen - striplen + 1);
3599         }
3601         /* Skip "Directory ..." and ".." line. */
3602         for (pos = 1 + !!*opt_path; pos < view->lines; pos++) {
3603                 struct line *line = &view->line[pos];
3604                 const char *path1 = tree_path(line);
3605                 char *path2 = text + SIZEOF_TREE_ATTR;
3606                 int cmp = tree_compare_entry(line->type, path1, type, path2);
3608                 if (cmp <= 0)
3609                         continue;
3611                 text = strdup(text);
3612                 if (!text)
3613                         return FALSE;
3615                 if (view->lines > pos)
3616                         memmove(&view->line[pos + 1], &view->line[pos],
3617                                 (view->lines - pos) * sizeof(*line));
3619                 line = &view->line[pos];
3620                 line->data = text;
3621                 line->type = type;
3622                 view->lines++;
3623                 return TRUE;
3624         }
3626         if (!add_line_text(view, text, type))
3627                 return FALSE;
3629         if (tree_lineno > view->lineno) {
3630                 view->lineno = tree_lineno;
3631                 tree_lineno = 0;
3632         }
3634         return TRUE;
3637 static enum request
3638 tree_request(struct view *view, enum request request, struct line *line)
3640         enum open_flags flags;
3642         switch (request) {
3643         case REQ_VIEW_BLAME:
3644                 if (line->type != LINE_TREE_FILE) {
3645                         report("Blame only supported for files");
3646                         return REQ_NONE;
3647                 }
3649                 string_copy(opt_ref, view->vid);
3650                 return request;
3652         case REQ_EDIT:
3653                 if (line->type != LINE_TREE_FILE) {
3654                         report("Edit only supported for files");
3655                 } else if (!is_head_commit(view->vid)) {
3656                         report("Edit only supported for files in the current work tree");
3657                 } else {
3658                         open_editor(TRUE, opt_file);
3659                 }
3660                 return REQ_NONE;
3662         case REQ_TREE_PARENT:
3663                 if (!*opt_path) {
3664                         /* quit view if at top of tree */
3665                         return REQ_VIEW_CLOSE;
3666                 }
3667                 /* fake 'cd  ..' */
3668                 line = &view->line[1];
3669                 break;
3671         case REQ_ENTER:
3672                 break;
3674         default:
3675                 return request;
3676         }
3678         /* Cleanup the stack if the tree view is at a different tree. */
3679         while (!*opt_path && tree_stack)
3680                 pop_tree_stack_entry();
3682         switch (line->type) {
3683         case LINE_TREE_DIR:
3684                 /* Depending on whether it is a subdir or parent (updir?) link
3685                  * mangle the path buffer. */
3686                 if (line == &view->line[1] && *opt_path) {
3687                         pop_tree_stack_entry();
3689                 } else {
3690                         const char *basename = tree_path(line);
3692                         push_tree_stack_entry(basename, view->lineno);
3693                 }
3695                 /* Trees and subtrees share the same ID, so they are not not
3696                  * unique like blobs. */
3697                 flags = OPEN_RELOAD;
3698                 request = REQ_VIEW_TREE;
3699                 break;
3701         case LINE_TREE_FILE:
3702                 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
3703                 request = REQ_VIEW_BLOB;
3704                 break;
3706         default:
3707                 return TRUE;
3708         }
3710         open_view(view, request, flags);
3711         if (request == REQ_VIEW_TREE) {
3712                 view->lineno = tree_lineno;
3713         }
3715         return REQ_NONE;
3718 static void
3719 tree_select(struct view *view, struct line *line)
3721         char *text = (char *)line->data + STRING_SIZE("100644 blob ");
3723         if (line->type == LINE_TREE_FILE) {
3724                 string_copy_rev(ref_blob, text);
3725                 string_format(opt_file, "%s%s", opt_path, tree_path(line));
3727         } else if (line->type != LINE_TREE_DIR) {
3728                 return;
3729         }
3731         string_copy_rev(view->ref, text);
3734 static const char *tree_argv[SIZEOF_ARG] = {
3735         "git", "ls-tree", "%(commit)", "%(directory)", NULL
3736 };
3738 static struct view_ops tree_ops = {
3739         "file",
3740         tree_argv,
3741         NULL,
3742         tree_read,
3743         pager_draw,
3744         tree_request,
3745         pager_grep,
3746         tree_select,
3747 };
3749 static bool
3750 blob_read(struct view *view, char *line)
3752         if (!line)
3753                 return TRUE;
3754         return add_line_text(view, line, LINE_DEFAULT) != NULL;
3757 static const char *blob_argv[SIZEOF_ARG] = {
3758         "git", "cat-file", "blob", "%(blob)", NULL
3759 };
3761 static struct view_ops blob_ops = {
3762         "line",
3763         blob_argv,
3764         NULL,
3765         blob_read,
3766         pager_draw,
3767         pager_request,
3768         pager_grep,
3769         pager_select,
3770 };
3772 /*
3773  * Blame backend
3774  *
3775  * Loading the blame view is a two phase job:
3776  *
3777  *  1. File content is read either using opt_file from the
3778  *     filesystem or using git-cat-file.
3779  *  2. Then blame information is incrementally added by
3780  *     reading output from git-blame.
3781  */
3783 static const char *blame_head_argv[] = {
3784         "git", "blame", "--incremental", "--", "%(file)", NULL
3785 };
3787 static const char *blame_ref_argv[] = {
3788         "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
3789 };
3791 static const char *blame_cat_file_argv[] = {
3792         "git", "cat-file", "blob", "%(ref):%(file)", NULL
3793 };
3795 struct blame_commit {
3796         char id[SIZEOF_REV];            /* SHA1 ID. */
3797         char title[128];                /* First line of the commit message. */
3798         char author[75];                /* Author of the commit. */
3799         struct tm time;                 /* Date from the author ident. */
3800         char filename[128];             /* Name of file. */
3801 };
3803 struct blame {
3804         struct blame_commit *commit;
3805         char text[1];
3806 };
3808 static bool
3809 blame_open(struct view *view)
3811         if (*opt_ref || !io_open(&view->io, opt_file)) {
3812                 if (!run_io_rd(&view->io, blame_cat_file_argv, FORMAT_ALL))
3813                         return FALSE;
3814         }
3816         setup_update(view, opt_file);
3817         string_format(view->ref, "%s ...", opt_file);
3819         return TRUE;
3822 static struct blame_commit *
3823 get_blame_commit(struct view *view, const char *id)
3825         size_t i;
3827         for (i = 0; i < view->lines; i++) {
3828                 struct blame *blame = view->line[i].data;
3830                 if (!blame->commit)
3831                         continue;
3833                 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
3834                         return blame->commit;
3835         }
3837         {
3838                 struct blame_commit *commit = calloc(1, sizeof(*commit));
3840                 if (commit)
3841                         string_ncopy(commit->id, id, SIZEOF_REV);
3842                 return commit;
3843         }
3846 static bool
3847 parse_number(const char **posref, size_t *number, size_t min, size_t max)
3849         const char *pos = *posref;
3851         *posref = NULL;
3852         pos = strchr(pos + 1, ' ');
3853         if (!pos || !isdigit(pos[1]))
3854                 return FALSE;
3855         *number = atoi(pos + 1);
3856         if (*number < min || *number > max)
3857                 return FALSE;
3859         *posref = pos;
3860         return TRUE;
3863 static struct blame_commit *
3864 parse_blame_commit(struct view *view, const char *text, int *blamed)
3866         struct blame_commit *commit;
3867         struct blame *blame;
3868         const char *pos = text + SIZEOF_REV - 1;
3869         size_t lineno;
3870         size_t group;
3872         if (strlen(text) <= SIZEOF_REV || *pos != ' ')
3873                 return NULL;
3875         if (!parse_number(&pos, &lineno, 1, view->lines) ||
3876             !parse_number(&pos, &group, 1, view->lines - lineno + 1))
3877                 return NULL;
3879         commit = get_blame_commit(view, text);
3880         if (!commit)
3881                 return NULL;
3883         *blamed += group;
3884         while (group--) {
3885                 struct line *line = &view->line[lineno + group - 1];
3887                 blame = line->data;
3888                 blame->commit = commit;
3889                 line->dirty = 1;
3890         }
3892         return commit;
3895 static bool
3896 blame_read_file(struct view *view, const char *line, bool *read_file)
3898         if (!line) {
3899                 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
3900                 struct io io = {};
3902                 if (view->lines == 0 && !view->parent)
3903                         die("No blame exist for %s", view->vid);
3905                 if (view->lines == 0 || !run_io_rd(&io, argv, FORMAT_ALL)) {
3906                         report("Failed to load blame data");
3907                         return TRUE;
3908                 }
3910                 done_io(view->pipe);
3911                 view->io = io;
3912                 *read_file = FALSE;
3913                 return FALSE;
3915         } else {
3916                 size_t linelen = strlen(line);
3917                 struct blame *blame = malloc(sizeof(*blame) + linelen);
3919                 blame->commit = NULL;
3920                 strncpy(blame->text, line, linelen);
3921                 blame->text[linelen] = 0;
3922                 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
3923         }
3926 static bool
3927 match_blame_header(const char *name, char **line)
3929         size_t namelen = strlen(name);
3930         bool matched = !strncmp(name, *line, namelen);
3932         if (matched)
3933                 *line += namelen;
3935         return matched;
3938 static bool
3939 blame_read(struct view *view, char *line)
3941         static struct blame_commit *commit = NULL;
3942         static int blamed = 0;
3943         static time_t author_time;
3944         static bool read_file = TRUE;
3946         if (read_file)
3947                 return blame_read_file(view, line, &read_file);
3949         if (!line) {
3950                 /* Reset all! */
3951                 commit = NULL;
3952                 blamed = 0;
3953                 read_file = TRUE;
3954                 string_format(view->ref, "%s", view->vid);
3955                 if (view_is_displayed(view)) {
3956                         update_view_title(view);
3957                         redraw_view_from(view, 0);
3958                 }
3959                 return TRUE;
3960         }
3962         if (!commit) {
3963                 commit = parse_blame_commit(view, line, &blamed);
3964                 string_format(view->ref, "%s %2d%%", view->vid,
3965                               blamed * 100 / view->lines);
3967         } else if (match_blame_header("author ", &line)) {
3968                 string_ncopy(commit->author, line, strlen(line));
3970         } else if (match_blame_header("author-time ", &line)) {
3971                 author_time = (time_t) atol(line);
3973         } else if (match_blame_header("author-tz ", &line)) {
3974                 long tz;
3976                 tz  = ('0' - line[1]) * 60 * 60 * 10;
3977                 tz += ('0' - line[2]) * 60 * 60;
3978                 tz += ('0' - line[3]) * 60;
3979                 tz += ('0' - line[4]) * 60;
3981                 if (line[0] == '-')
3982                         tz = -tz;
3984                 author_time -= tz;
3985                 gmtime_r(&author_time, &commit->time);
3987         } else if (match_blame_header("summary ", &line)) {
3988                 string_ncopy(commit->title, line, strlen(line));
3990         } else if (match_blame_header("filename ", &line)) {
3991                 string_ncopy(commit->filename, line, strlen(line));
3992                 commit = NULL;
3993         }
3995         return TRUE;
3998 static bool
3999 blame_draw(struct view *view, struct line *line, unsigned int lineno)
4001         struct blame *blame = line->data;
4002         struct tm *time = NULL;
4003         const char *id = NULL, *author = NULL;
4005         if (blame->commit && *blame->commit->filename) {
4006                 id = blame->commit->id;
4007                 author = blame->commit->author;
4008                 time = &blame->commit->time;
4009         }
4011         if (opt_date && draw_date(view, time))
4012                 return TRUE;
4014         if (opt_author &&
4015             draw_field(view, LINE_MAIN_AUTHOR, author, opt_author_cols, TRUE))
4016                 return TRUE;
4018         if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
4019                 return TRUE;
4021         if (draw_lineno(view, lineno))
4022                 return TRUE;
4024         draw_text(view, LINE_DEFAULT, blame->text, TRUE);
4025         return TRUE;
4028 static enum request
4029 blame_request(struct view *view, enum request request, struct line *line)
4031         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4032         struct blame *blame = line->data;
4034         switch (request) {
4035         case REQ_VIEW_BLAME:
4036                 if (!blame->commit || !strcmp(blame->commit->id, NULL_ID)) {
4037                         report("Commit ID unknown");
4038                         break;
4039                 }
4040                 string_copy(opt_ref, blame->commit->id);
4041                 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4042                 return request;
4044         case REQ_ENTER:
4045                 if (!blame->commit) {
4046                         report("No commit loaded yet");
4047                         break;
4048                 }
4050                 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
4051                     !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
4052                         break;
4054                 if (!strcmp(blame->commit->id, NULL_ID)) {
4055                         struct view *diff = VIEW(REQ_VIEW_DIFF);
4056                         const char *diff_index_argv[] = {
4057                                 "git", "diff-index", "--root", "--cached",
4058                                         "--patch-with-stat", "-C", "-M",
4059                                         "HEAD", "--", view->vid, NULL
4060                         };
4062                         if (!prepare_update(diff, diff_index_argv, NULL, FORMAT_DASH)) {
4063                                 report("Failed to allocate diff command");
4064                                 break;
4065                         }
4066                         flags |= OPEN_PREPARED;
4067                 }
4069                 open_view(view, REQ_VIEW_DIFF, flags);
4070                 break;
4072         default:
4073                 return request;
4074         }
4076         return REQ_NONE;
4079 static bool
4080 blame_grep(struct view *view, struct line *line)
4082         struct blame *blame = line->data;
4083         struct blame_commit *commit = blame->commit;
4084         regmatch_t pmatch;
4086 #define MATCH(text, on)                                                 \
4087         (on && *text && regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4089         if (commit) {
4090                 char buf[DATE_COLS + 1];
4092                 if (MATCH(commit->title, 1) ||
4093                     MATCH(commit->author, opt_author) ||
4094                     MATCH(commit->id, opt_date))
4095                         return TRUE;
4097                 if (strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time) &&
4098                     MATCH(buf, 1))
4099                         return TRUE;
4100         }
4102         return MATCH(blame->text, 1);
4104 #undef MATCH
4107 static void
4108 blame_select(struct view *view, struct line *line)
4110         struct blame *blame = line->data;
4111         struct blame_commit *commit = blame->commit;
4113         if (!commit)
4114                 return;
4116         if (!strcmp(commit->id, NULL_ID))
4117                 string_ncopy(ref_commit, "HEAD", 4);
4118         else
4119                 string_copy_rev(ref_commit, commit->id);
4122 static struct view_ops blame_ops = {
4123         "line",
4124         NULL,
4125         blame_open,
4126         blame_read,
4127         blame_draw,
4128         blame_request,
4129         blame_grep,
4130         blame_select,
4131 };
4133 /*
4134  * Status backend
4135  */
4137 struct status {
4138         char status;
4139         struct {
4140                 mode_t mode;
4141                 char rev[SIZEOF_REV];
4142                 char name[SIZEOF_STR];
4143         } old;
4144         struct {
4145                 mode_t mode;
4146                 char rev[SIZEOF_REV];
4147                 char name[SIZEOF_STR];
4148         } new;
4149 };
4151 static char status_onbranch[SIZEOF_STR];
4152 static struct status stage_status;
4153 static enum line_type stage_line_type;
4154 static size_t stage_chunks;
4155 static int *stage_chunk;
4157 /* This should work even for the "On branch" line. */
4158 static inline bool
4159 status_has_none(struct view *view, struct line *line)
4161         return line < view->line + view->lines && !line[1].data;
4164 /* Get fields from the diff line:
4165  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
4166  */
4167 static inline bool
4168 status_get_diff(struct status *file, const char *buf, size_t bufsize)
4170         const char *old_mode = buf +  1;
4171         const char *new_mode = buf +  8;
4172         const char *old_rev  = buf + 15;
4173         const char *new_rev  = buf + 56;
4174         const char *status   = buf + 97;
4176         if (bufsize < 99 ||
4177             old_mode[-1] != ':' ||
4178             new_mode[-1] != ' ' ||
4179             old_rev[-1]  != ' ' ||
4180             new_rev[-1]  != ' ' ||
4181             status[-1]   != ' ')
4182                 return FALSE;
4184         file->status = *status;
4186         string_copy_rev(file->old.rev, old_rev);
4187         string_copy_rev(file->new.rev, new_rev);
4189         file->old.mode = strtoul(old_mode, NULL, 8);
4190         file->new.mode = strtoul(new_mode, NULL, 8);
4192         file->old.name[0] = file->new.name[0] = 0;
4194         return TRUE;
4197 static bool
4198 status_run(struct view *view, const char *argv[], char status, enum line_type type)
4200         struct status *file = NULL;
4201         struct status *unmerged = NULL;
4202         char buf[SIZEOF_STR * 4];
4203         size_t bufsize = 0;
4204         struct io io = {};
4206         if (!run_io(&io, argv, NULL, IO_RD))
4207                 return FALSE;
4209         add_line_data(view, NULL, type);
4211         while (!io_eof(&io)) {
4212                 char *sep;
4213                 size_t readsize;
4215                 readsize = io_read(&io, buf + bufsize, sizeof(buf) - bufsize);
4216                 if (io_error(&io))
4217                         break;
4218                 bufsize += readsize;
4220                 /* Process while we have NUL chars. */
4221                 while ((sep = memchr(buf, 0, bufsize))) {
4222                         size_t sepsize = sep - buf + 1;
4224                         if (!file) {
4225                                 if (!realloc_lines(view, view->line_size + 1))
4226                                         goto error_out;
4228                                 file = calloc(1, sizeof(*file));
4229                                 if (!file)
4230                                         goto error_out;
4232                                 add_line_data(view, file, type);
4233                         }
4235                         /* Parse diff info part. */
4236                         if (status) {
4237                                 file->status = status;
4238                                 if (status == 'A')
4239                                         string_copy(file->old.rev, NULL_ID);
4241                         } else if (!file->status) {
4242                                 if (!status_get_diff(file, buf, sepsize))
4243                                         goto error_out;
4245                                 bufsize -= sepsize;
4246                                 memmove(buf, sep + 1, bufsize);
4248                                 sep = memchr(buf, 0, bufsize);
4249                                 if (!sep)
4250                                         break;
4251                                 sepsize = sep - buf + 1;
4253                                 /* Collapse all 'M'odified entries that
4254                                  * follow a associated 'U'nmerged entry.
4255                                  */
4256                                 if (file->status == 'U') {
4257                                         unmerged = file;
4259                                 } else if (unmerged) {
4260                                         int collapse = !strcmp(buf, unmerged->new.name);
4262                                         unmerged = NULL;
4263                                         if (collapse) {
4264                                                 free(file);
4265                                                 view->lines--;
4266                                                 continue;
4267                                         }
4268                                 }
4269                         }
4271                         /* Grab the old name for rename/copy. */
4272                         if (!*file->old.name &&
4273                             (file->status == 'R' || file->status == 'C')) {
4274                                 sepsize = sep - buf + 1;
4275                                 string_ncopy(file->old.name, buf, sepsize);
4276                                 bufsize -= sepsize;
4277                                 memmove(buf, sep + 1, bufsize);
4279                                 sep = memchr(buf, 0, bufsize);
4280                                 if (!sep)
4281                                         break;
4282                                 sepsize = sep - buf + 1;
4283                         }
4285                         /* git-ls-files just delivers a NUL separated
4286                          * list of file names similar to the second half
4287                          * of the git-diff-* output. */
4288                         string_ncopy(file->new.name, buf, sepsize);
4289                         if (!*file->old.name)
4290                                 string_copy(file->old.name, file->new.name);
4291                         bufsize -= sepsize;
4292                         memmove(buf, sep + 1, bufsize);
4293                         file = NULL;
4294                 }
4295         }
4297         if (io_error(&io)) {
4298 error_out:
4299                 done_io(&io);
4300                 return FALSE;
4301         }
4303         if (!view->line[view->lines - 1].data)
4304                 add_line_data(view, NULL, LINE_STAT_NONE);
4306         done_io(&io);
4307         return TRUE;
4310 /* Don't show unmerged entries in the staged section. */
4311 static const char *status_diff_index_argv[] = {
4312         "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
4313                              "--cached", "-M", "HEAD", NULL
4314 };
4316 static const char *status_diff_files_argv[] = {
4317         "git", "diff-files", "-z", NULL
4318 };
4320 static const char *status_list_other_argv[] = {
4321         "git", "ls-files", "-z", "--others", "--exclude-standard", NULL
4322 };
4324 static const char *status_list_no_head_argv[] = {
4325         "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
4326 };
4328 static const char *update_index_argv[] = {
4329         "git", "update-index", "-q", "--unmerged", "--refresh", NULL
4330 };
4332 /* First parse staged info using git-diff-index(1), then parse unstaged
4333  * info using git-diff-files(1), and finally untracked files using
4334  * git-ls-files(1). */
4335 static bool
4336 status_open(struct view *view)
4338         unsigned long prev_lineno = view->lineno;
4340         reset_view(view);
4342         if (!realloc_lines(view, view->line_size + 7))
4343                 return FALSE;
4345         add_line_data(view, NULL, LINE_STAT_HEAD);
4346         if (is_initial_commit())
4347                 string_copy(status_onbranch, "Initial commit");
4348         else if (!*opt_head)
4349                 string_copy(status_onbranch, "Not currently on any branch");
4350         else if (!string_format(status_onbranch, "On branch %s", opt_head))
4351                 return FALSE;
4353         run_io_bg(update_index_argv);
4355         if (is_initial_commit()) {
4356                 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
4357                         return FALSE;
4358         } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
4359                 return FALSE;
4360         }
4362         if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
4363             !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
4364                 return FALSE;
4366         /* If all went well restore the previous line number to stay in
4367          * the context or select a line with something that can be
4368          * updated. */
4369         if (prev_lineno >= view->lines)
4370                 prev_lineno = view->lines - 1;
4371         while (prev_lineno < view->lines && !view->line[prev_lineno].data)
4372                 prev_lineno++;
4373         while (prev_lineno > 0 && !view->line[prev_lineno].data)
4374                 prev_lineno--;
4376         /* If the above fails, always skip the "On branch" line. */
4377         if (prev_lineno < view->lines)
4378                 view->lineno = prev_lineno;
4379         else
4380                 view->lineno = 1;
4382         if (view->lineno < view->offset)
4383                 view->offset = view->lineno;
4384         else if (view->offset + view->height <= view->lineno)
4385                 view->offset = view->lineno - view->height + 1;
4387         return TRUE;
4390 static bool
4391 status_draw(struct view *view, struct line *line, unsigned int lineno)
4393         struct status *status = line->data;
4394         enum line_type type;
4395         const char *text;
4397         if (!status) {
4398                 switch (line->type) {
4399                 case LINE_STAT_STAGED:
4400                         type = LINE_STAT_SECTION;
4401                         text = "Changes to be committed:";
4402                         break;
4404                 case LINE_STAT_UNSTAGED:
4405                         type = LINE_STAT_SECTION;
4406                         text = "Changed but not updated:";
4407                         break;
4409                 case LINE_STAT_UNTRACKED:
4410                         type = LINE_STAT_SECTION;
4411                         text = "Untracked files:";
4412                         break;
4414                 case LINE_STAT_NONE:
4415                         type = LINE_DEFAULT;
4416                         text = "    (no files)";
4417                         break;
4419                 case LINE_STAT_HEAD:
4420                         type = LINE_STAT_HEAD;
4421                         text = status_onbranch;
4422                         break;
4424                 default:
4425                         return FALSE;
4426                 }
4427         } else {
4428                 static char buf[] = { '?', ' ', ' ', ' ', 0 };
4430                 buf[0] = status->status;
4431                 if (draw_text(view, line->type, buf, TRUE))
4432                         return TRUE;
4433                 type = LINE_DEFAULT;
4434                 text = status->new.name;
4435         }
4437         draw_text(view, type, text, TRUE);
4438         return TRUE;
4441 static enum request
4442 status_enter(struct view *view, struct line *line)
4444         struct status *status = line->data;
4445         const char *oldpath = status ? status->old.name : NULL;
4446         /* Diffs for unmerged entries are empty when passing the new
4447          * path, so leave it empty. */
4448         const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
4449         const char *info;
4450         enum open_flags split;
4451         struct view *stage = VIEW(REQ_VIEW_STAGE);
4453         if (line->type == LINE_STAT_NONE ||
4454             (!status && line[1].type == LINE_STAT_NONE)) {
4455                 report("No file to diff");
4456                 return REQ_NONE;
4457         }
4459         switch (line->type) {
4460         case LINE_STAT_STAGED:
4461                 if (is_initial_commit()) {
4462                         const char *no_head_diff_argv[] = {
4463                                 "git", "diff", "--no-color", "--patch-with-stat",
4464                                         "--", "/dev/null", newpath, NULL
4465                         };
4467                         if (!prepare_update(stage, no_head_diff_argv, opt_cdup, FORMAT_DASH))
4468                                 return REQ_QUIT;
4469                 } else {
4470                         const char *index_show_argv[] = {
4471                                 "git", "diff-index", "--root", "--patch-with-stat",
4472                                         "-C", "-M", "--cached", "HEAD", "--",
4473                                         oldpath, newpath, NULL
4474                         };
4476                         if (!prepare_update(stage, index_show_argv, opt_cdup, FORMAT_DASH))
4477                                 return REQ_QUIT;
4478                 }
4480                 if (status)
4481                         info = "Staged changes to %s";
4482                 else
4483                         info = "Staged changes";
4484                 break;
4486         case LINE_STAT_UNSTAGED:
4487         {
4488                 const char *files_show_argv[] = {
4489                         "git", "diff-files", "--root", "--patch-with-stat",
4490                                 "-C", "-M", "--", oldpath, newpath, NULL
4491                 };
4493                 if (!prepare_update(stage, files_show_argv, opt_cdup, FORMAT_DASH))
4494                         return REQ_QUIT;
4495                 if (status)
4496                         info = "Unstaged changes to %s";
4497                 else
4498                         info = "Unstaged changes";
4499                 break;
4500         }
4501         case LINE_STAT_UNTRACKED:
4502                 if (!newpath) {
4503                         report("No file to show");
4504                         return REQ_NONE;
4505                 }
4507                 if (!suffixcmp(status->new.name, -1, "/")) {
4508                         report("Cannot display a directory");
4509                         return REQ_NONE;
4510                 }
4512                 if (!prepare_update_file(stage, newpath))
4513                         return REQ_QUIT;
4514                 info = "Untracked file %s";
4515                 break;
4517         case LINE_STAT_HEAD:
4518                 return REQ_NONE;
4520         default:
4521                 die("line type %d not handled in switch", line->type);
4522         }
4524         split = view_is_displayed(view) ? OPEN_SPLIT : 0;
4525         open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH | split);
4526         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
4527                 if (status) {
4528                         stage_status = *status;
4529                 } else {
4530                         memset(&stage_status, 0, sizeof(stage_status));
4531                 }
4533                 stage_line_type = line->type;
4534                 stage_chunks = 0;
4535                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
4536         }
4538         return REQ_NONE;
4541 static bool
4542 status_exists(struct status *status, enum line_type type)
4544         struct view *view = VIEW(REQ_VIEW_STATUS);
4545         struct line *line;
4547         for (line = view->line; line < view->line + view->lines; line++) {
4548                 struct status *pos = line->data;
4550                 if (line->type == type && pos &&
4551                     !strcmp(status->new.name, pos->new.name))
4552                         return TRUE;
4553         }
4555         return FALSE;
4559 static bool
4560 status_update_prepare(struct io *io, enum line_type type)
4562         const char *staged_argv[] = {
4563                 "git", "update-index", "-z", "--index-info", NULL
4564         };
4565         const char *others_argv[] = {
4566                 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
4567         };
4569         switch (type) {
4570         case LINE_STAT_STAGED:
4571                 return run_io(io, staged_argv, opt_cdup, IO_WR);
4573         case LINE_STAT_UNSTAGED:
4574                 return run_io(io, others_argv, opt_cdup, IO_WR);
4576         case LINE_STAT_UNTRACKED:
4577                 return run_io(io, others_argv, NULL, IO_WR);
4579         default:
4580                 die("line type %d not handled in switch", type);
4581                 return FALSE;
4582         }
4585 static bool
4586 status_update_write(struct io *io, struct status *status, enum line_type type)
4588         char buf[SIZEOF_STR];
4589         size_t bufsize = 0;
4591         switch (type) {
4592         case LINE_STAT_STAGED:
4593                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
4594                                         status->old.mode,
4595                                         status->old.rev,
4596                                         status->old.name, 0))
4597                         return FALSE;
4598                 break;
4600         case LINE_STAT_UNSTAGED:
4601         case LINE_STAT_UNTRACKED:
4602                 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
4603                         return FALSE;
4604                 break;
4606         default:
4607                 die("line type %d not handled in switch", type);
4608         }
4610         return io_write(io, buf, bufsize);
4613 static bool
4614 status_update_file(struct status *status, enum line_type type)
4616         struct io io = {};
4617         bool result;
4619         if (!status_update_prepare(&io, type))
4620                 return FALSE;
4622         result = status_update_write(&io, status, type);
4623         done_io(&io);
4624         return result;
4627 static bool
4628 status_update_files(struct view *view, struct line *line)
4630         struct io io = {};
4631         bool result = TRUE;
4632         struct line *pos = view->line + view->lines;
4633         int files = 0;
4634         int file, done;
4636         if (!status_update_prepare(&io, line->type))
4637                 return FALSE;
4639         for (pos = line; pos < view->line + view->lines && pos->data; pos++)
4640                 files++;
4642         for (file = 0, done = 0; result && file < files; line++, file++) {
4643                 int almost_done = file * 100 / files;
4645                 if (almost_done > done) {
4646                         done = almost_done;
4647                         string_format(view->ref, "updating file %u of %u (%d%% done)",
4648                                       file, files, done);
4649                         update_view_title(view);
4650                 }
4651                 result = status_update_write(&io, line->data, line->type);
4652         }
4654         done_io(&io);
4655         return result;
4658 static bool
4659 status_update(struct view *view)
4661         struct line *line = &view->line[view->lineno];
4663         assert(view->lines);
4665         if (!line->data) {
4666                 /* This should work even for the "On branch" line. */
4667                 if (line < view->line + view->lines && !line[1].data) {
4668                         report("Nothing to update");
4669                         return FALSE;
4670                 }
4672                 if (!status_update_files(view, line + 1)) {
4673                         report("Failed to update file status");
4674                         return FALSE;
4675                 }
4677         } else if (!status_update_file(line->data, line->type)) {
4678                 report("Failed to update file status");
4679                 return FALSE;
4680         }
4682         return TRUE;
4685 static bool
4686 status_revert(struct status *status, enum line_type type, bool has_none)
4688         if (!status || type != LINE_STAT_UNSTAGED) {
4689                 if (type == LINE_STAT_STAGED) {
4690                         report("Cannot revert changes to staged files");
4691                 } else if (type == LINE_STAT_UNTRACKED) {
4692                         report("Cannot revert changes to untracked files");
4693                 } else if (has_none) {
4694                         report("Nothing to revert");
4695                 } else {
4696                         report("Cannot revert changes to multiple files");
4697                 }
4698                 return FALSE;
4700         } else {
4701                 const char *checkout_argv[] = {
4702                         "git", "checkout", "--", status->old.name, NULL
4703                 };
4705                 if (!prompt_yesno("Are you sure you want to overwrite any changes?"))
4706                         return FALSE;
4707                 return run_io_fg(checkout_argv, opt_cdup);
4708         }
4711 static enum request
4712 status_request(struct view *view, enum request request, struct line *line)
4714         struct status *status = line->data;
4716         switch (request) {
4717         case REQ_STATUS_UPDATE:
4718                 if (!status_update(view))
4719                         return REQ_NONE;
4720                 break;
4722         case REQ_STATUS_REVERT:
4723                 if (!status_revert(status, line->type, status_has_none(view, line)))
4724                         return REQ_NONE;
4725                 break;
4727         case REQ_STATUS_MERGE:
4728                 if (!status || status->status != 'U') {
4729                         report("Merging only possible for files with unmerged status ('U').");
4730                         return REQ_NONE;
4731                 }
4732                 open_mergetool(status->new.name);
4733                 break;
4735         case REQ_EDIT:
4736                 if (!status)
4737                         return request;
4738                 if (status->status == 'D') {
4739                         report("File has been deleted.");
4740                         return REQ_NONE;
4741                 }
4743                 open_editor(status->status != '?', status->new.name);
4744                 break;
4746         case REQ_VIEW_BLAME:
4747                 if (status) {
4748                         string_copy(opt_file, status->new.name);
4749                         opt_ref[0] = 0;
4750                 }
4751                 return request;
4753         case REQ_ENTER:
4754                 /* After returning the status view has been split to
4755                  * show the stage view. No further reloading is
4756                  * necessary. */
4757                 status_enter(view, line);
4758                 return REQ_NONE;
4760         case REQ_REFRESH:
4761                 /* Simply reload the view. */
4762                 break;
4764         default:
4765                 return request;
4766         }
4768         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
4770         return REQ_NONE;
4773 static void
4774 status_select(struct view *view, struct line *line)
4776         struct status *status = line->data;
4777         char file[SIZEOF_STR] = "all files";
4778         const char *text;
4779         const char *key;
4781         if (status && !string_format(file, "'%s'", status->new.name))
4782                 return;
4784         if (!status && line[1].type == LINE_STAT_NONE)
4785                 line++;
4787         switch (line->type) {
4788         case LINE_STAT_STAGED:
4789                 text = "Press %s to unstage %s for commit";
4790                 break;
4792         case LINE_STAT_UNSTAGED:
4793                 text = "Press %s to stage %s for commit";
4794                 break;
4796         case LINE_STAT_UNTRACKED:
4797                 text = "Press %s to stage %s for addition";
4798                 break;
4800         case LINE_STAT_HEAD:
4801         case LINE_STAT_NONE:
4802                 text = "Nothing to update";
4803                 break;
4805         default:
4806                 die("line type %d not handled in switch", line->type);
4807         }
4809         if (status && status->status == 'U') {
4810                 text = "Press %s to resolve conflict in %s";
4811                 key = get_key(REQ_STATUS_MERGE);
4813         } else {
4814                 key = get_key(REQ_STATUS_UPDATE);
4815         }
4817         string_format(view->ref, text, key, file);
4820 static bool
4821 status_grep(struct view *view, struct line *line)
4823         struct status *status = line->data;
4824         enum { S_STATUS, S_NAME, S_END } state;
4825         char buf[2] = "?";
4826         regmatch_t pmatch;
4828         if (!status)
4829                 return FALSE;
4831         for (state = S_STATUS; state < S_END; state++) {
4832                 const char *text;
4834                 switch (state) {
4835                 case S_NAME:    text = status->new.name;        break;
4836                 case S_STATUS:
4837                         buf[0] = status->status;
4838                         text = buf;
4839                         break;
4841                 default:
4842                         return FALSE;
4843                 }
4845                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4846                         return TRUE;
4847         }
4849         return FALSE;
4852 static struct view_ops status_ops = {
4853         "file",
4854         NULL,
4855         status_open,
4856         NULL,
4857         status_draw,
4858         status_request,
4859         status_grep,
4860         status_select,
4861 };
4864 static bool
4865 stage_diff_write(struct io *io, struct line *line, struct line *end)
4867         while (line < end) {
4868                 if (!io_write(io, line->data, strlen(line->data)) ||
4869                     !io_write(io, "\n", 1))
4870                         return FALSE;
4871                 line++;
4872                 if (line->type == LINE_DIFF_CHUNK ||
4873                     line->type == LINE_DIFF_HEADER)
4874                         break;
4875         }
4877         return TRUE;
4880 static struct line *
4881 stage_diff_find(struct view *view, struct line *line, enum line_type type)
4883         for (; view->line < line; line--)
4884                 if (line->type == type)
4885                         return line;
4887         return NULL;
4890 static bool
4891 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
4893         const char *apply_argv[SIZEOF_ARG] = {
4894                 "git", "apply", "--whitespace=nowarn", NULL
4895         };
4896         struct line *diff_hdr;
4897         struct io io = {};
4898         int argc = 3;
4900         diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
4901         if (!diff_hdr)
4902                 return FALSE;
4904         if (!revert)
4905                 apply_argv[argc++] = "--cached";
4906         if (revert || stage_line_type == LINE_STAT_STAGED)
4907                 apply_argv[argc++] = "-R";
4908         apply_argv[argc++] = "-";
4909         apply_argv[argc++] = NULL;
4910         if (!run_io(&io, apply_argv, opt_cdup, IO_WR))
4911                 return FALSE;
4913         if (!stage_diff_write(&io, diff_hdr, chunk) ||
4914             !stage_diff_write(&io, chunk, view->line + view->lines))
4915                 chunk = NULL;
4917         done_io(&io);
4918         run_io_bg(update_index_argv);
4920         return chunk ? TRUE : FALSE;
4923 static bool
4924 stage_update(struct view *view, struct line *line)
4926         struct line *chunk = NULL;
4928         if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
4929                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
4931         if (chunk) {
4932                 if (!stage_apply_chunk(view, chunk, FALSE)) {
4933                         report("Failed to apply chunk");
4934                         return FALSE;
4935                 }
4937         } else if (!stage_status.status) {
4938                 view = VIEW(REQ_VIEW_STATUS);
4940                 for (line = view->line; line < view->line + view->lines; line++)
4941                         if (line->type == stage_line_type)
4942                                 break;
4944                 if (!status_update_files(view, line + 1)) {
4945                         report("Failed to update files");
4946                         return FALSE;
4947                 }
4949         } else if (!status_update_file(&stage_status, stage_line_type)) {
4950                 report("Failed to update file");
4951                 return FALSE;
4952         }
4954         return TRUE;
4957 static bool
4958 stage_revert(struct view *view, struct line *line)
4960         struct line *chunk = NULL;
4962         if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
4963                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
4965         if (chunk) {
4966                 if (!prompt_yesno("Are you sure you want to revert changes?"))
4967                         return FALSE;
4969                 if (!stage_apply_chunk(view, chunk, TRUE)) {
4970                         report("Failed to revert chunk");
4971                         return FALSE;
4972                 }
4973                 return TRUE;
4975         } else {
4976                 return status_revert(stage_status.status ? &stage_status : NULL,
4977                                      stage_line_type, FALSE);
4978         }
4982 static void
4983 stage_next(struct view *view, struct line *line)
4985         int i;
4987         if (!stage_chunks) {
4988                 static size_t alloc = 0;
4989                 int *tmp;
4991                 for (line = view->line; line < view->line + view->lines; line++) {
4992                         if (line->type != LINE_DIFF_CHUNK)
4993                                 continue;
4995                         tmp = realloc_items(stage_chunk, &alloc,
4996                                             stage_chunks, sizeof(*tmp));
4997                         if (!tmp) {
4998                                 report("Allocation failure");
4999                                 return;
5000                         }
5002                         stage_chunk = tmp;
5003                         stage_chunk[stage_chunks++] = line - view->line;
5004                 }
5005         }
5007         for (i = 0; i < stage_chunks; i++) {
5008                 if (stage_chunk[i] > view->lineno) {
5009                         do_scroll_view(view, stage_chunk[i] - view->lineno);
5010                         report("Chunk %d of %d", i + 1, stage_chunks);
5011                         return;
5012                 }
5013         }
5015         report("No next chunk found");
5018 static enum request
5019 stage_request(struct view *view, enum request request, struct line *line)
5021         switch (request) {
5022         case REQ_STATUS_UPDATE:
5023                 if (!stage_update(view, line))
5024                         return REQ_NONE;
5025                 break;
5027         case REQ_STATUS_REVERT:
5028                 if (!stage_revert(view, line))
5029                         return REQ_NONE;
5030                 break;
5032         case REQ_STAGE_NEXT:
5033                 if (stage_line_type == LINE_STAT_UNTRACKED) {
5034                         report("File is untracked; press %s to add",
5035                                get_key(REQ_STATUS_UPDATE));
5036                         return REQ_NONE;
5037                 }
5038                 stage_next(view, line);
5039                 return REQ_NONE;
5041         case REQ_EDIT:
5042                 if (!stage_status.new.name[0])
5043                         return request;
5044                 if (stage_status.status == 'D') {
5045                         report("File has been deleted.");
5046                         return REQ_NONE;
5047                 }
5049                 open_editor(stage_status.status != '?', stage_status.new.name);
5050                 break;
5052         case REQ_REFRESH:
5053                 /* Reload everything ... */
5054                 break;
5056         case REQ_VIEW_BLAME:
5057                 if (stage_status.new.name[0]) {
5058                         string_copy(opt_file, stage_status.new.name);
5059                         opt_ref[0] = 0;
5060                 }
5061                 return request;
5063         case REQ_ENTER:
5064                 return pager_request(view, request, line);
5066         default:
5067                 return request;
5068         }
5070         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD | OPEN_NOMAXIMIZE);
5072         /* Check whether the staged entry still exists, and close the
5073          * stage view if it doesn't. */
5074         if (!status_exists(&stage_status, stage_line_type))
5075                 return REQ_VIEW_CLOSE;
5077         if (stage_line_type == LINE_STAT_UNTRACKED) {
5078                 if (!suffixcmp(stage_status.new.name, -1, "/")) {
5079                         report("Cannot display a directory");
5080                         return REQ_NONE;
5081                 }
5083                 if (!prepare_update_file(view, stage_status.new.name)) {
5084                         report("Failed to open file: %s", strerror(errno));
5085                         return REQ_NONE;
5086                 }
5087         }
5088         open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
5090         return REQ_NONE;
5093 static struct view_ops stage_ops = {
5094         "line",
5095         NULL,
5096         NULL,
5097         pager_read,
5098         pager_draw,
5099         stage_request,
5100         pager_grep,
5101         pager_select,
5102 };
5105 /*
5106  * Revision graph
5107  */
5109 struct commit {
5110         char id[SIZEOF_REV];            /* SHA1 ID. */
5111         char title[128];                /* First line of the commit message. */
5112         char author[75];                /* Author of the commit. */
5113         struct tm time;                 /* Date from the author ident. */
5114         struct ref **refs;              /* Repository references. */
5115         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
5116         size_t graph_size;              /* The width of the graph array. */
5117         bool has_parents;               /* Rewritten --parents seen. */
5118 };
5120 /* Size of rev graph with no  "padding" columns */
5121 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
5123 struct rev_graph {
5124         struct rev_graph *prev, *next, *parents;
5125         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
5126         size_t size;
5127         struct commit *commit;
5128         size_t pos;
5129         unsigned int boundary:1;
5130 };
5132 /* Parents of the commit being visualized. */
5133 static struct rev_graph graph_parents[4];
5135 /* The current stack of revisions on the graph. */
5136 static struct rev_graph graph_stacks[4] = {
5137         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
5138         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
5139         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
5140         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
5141 };
5143 static inline bool
5144 graph_parent_is_merge(struct rev_graph *graph)
5146         return graph->parents->size > 1;
5149 static inline void
5150 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
5152         struct commit *commit = graph->commit;
5154         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
5155                 commit->graph[commit->graph_size++] = symbol;
5158 static void
5159 clear_rev_graph(struct rev_graph *graph)
5161         graph->boundary = 0;
5162         graph->size = graph->pos = 0;
5163         graph->commit = NULL;
5164         memset(graph->parents, 0, sizeof(*graph->parents));
5167 static void
5168 done_rev_graph(struct rev_graph *graph)
5170         if (graph_parent_is_merge(graph) &&
5171             graph->pos < graph->size - 1 &&
5172             graph->next->size == graph->size + graph->parents->size - 1) {
5173                 size_t i = graph->pos + graph->parents->size - 1;
5175                 graph->commit->graph_size = i * 2;
5176                 while (i < graph->next->size - 1) {
5177                         append_to_rev_graph(graph, ' ');
5178                         append_to_rev_graph(graph, '\\');
5179                         i++;
5180                 }
5181         }
5183         clear_rev_graph(graph);
5186 static void
5187 push_rev_graph(struct rev_graph *graph, const char *parent)
5189         int i;
5191         /* "Collapse" duplicate parents lines.
5192          *
5193          * FIXME: This needs to also update update the drawn graph but
5194          * for now it just serves as a method for pruning graph lines. */
5195         for (i = 0; i < graph->size; i++)
5196                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
5197                         return;
5199         if (graph->size < SIZEOF_REVITEMS) {
5200                 string_copy_rev(graph->rev[graph->size++], parent);
5201         }
5204 static chtype
5205 get_rev_graph_symbol(struct rev_graph *graph)
5207         chtype symbol;
5209         if (graph->boundary)
5210                 symbol = REVGRAPH_BOUND;
5211         else if (graph->parents->size == 0)
5212                 symbol = REVGRAPH_INIT;
5213         else if (graph_parent_is_merge(graph))
5214                 symbol = REVGRAPH_MERGE;
5215         else if (graph->pos >= graph->size)
5216                 symbol = REVGRAPH_BRANCH;
5217         else
5218                 symbol = REVGRAPH_COMMIT;
5220         return symbol;
5223 static void
5224 draw_rev_graph(struct rev_graph *graph)
5226         struct rev_filler {
5227                 chtype separator, line;
5228         };
5229         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
5230         static struct rev_filler fillers[] = {
5231                 { ' ',  '|' },
5232                 { '`',  '.' },
5233                 { '\'', ' ' },
5234                 { '/',  ' ' },
5235         };
5236         chtype symbol = get_rev_graph_symbol(graph);
5237         struct rev_filler *filler;
5238         size_t i;
5240         if (opt_line_graphics)
5241                 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
5243         filler = &fillers[DEFAULT];
5245         for (i = 0; i < graph->pos; i++) {
5246                 append_to_rev_graph(graph, filler->line);
5247                 if (graph_parent_is_merge(graph->prev) &&
5248                     graph->prev->pos == i)
5249                         filler = &fillers[RSHARP];
5251                 append_to_rev_graph(graph, filler->separator);
5252         }
5254         /* Place the symbol for this revision. */
5255         append_to_rev_graph(graph, symbol);
5257         if (graph->prev->size > graph->size)
5258                 filler = &fillers[RDIAG];
5259         else
5260                 filler = &fillers[DEFAULT];
5262         i++;
5264         for (; i < graph->size; i++) {
5265                 append_to_rev_graph(graph, filler->separator);
5266                 append_to_rev_graph(graph, filler->line);
5267                 if (graph_parent_is_merge(graph->prev) &&
5268                     i < graph->prev->pos + graph->parents->size)
5269                         filler = &fillers[RSHARP];
5270                 if (graph->prev->size > graph->size)
5271                         filler = &fillers[LDIAG];
5272         }
5274         if (graph->prev->size > graph->size) {
5275                 append_to_rev_graph(graph, filler->separator);
5276                 if (filler->line != ' ')
5277                         append_to_rev_graph(graph, filler->line);
5278         }
5281 /* Prepare the next rev graph */
5282 static void
5283 prepare_rev_graph(struct rev_graph *graph)
5285         size_t i;
5287         /* First, traverse all lines of revisions up to the active one. */
5288         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
5289                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
5290                         break;
5292                 push_rev_graph(graph->next, graph->rev[graph->pos]);
5293         }
5295         /* Interleave the new revision parent(s). */
5296         for (i = 0; !graph->boundary && i < graph->parents->size; i++)
5297                 push_rev_graph(graph->next, graph->parents->rev[i]);
5299         /* Lastly, put any remaining revisions. */
5300         for (i = graph->pos + 1; i < graph->size; i++)
5301                 push_rev_graph(graph->next, graph->rev[i]);
5304 static void
5305 update_rev_graph(struct rev_graph *graph)
5307         /* If this is the finalizing update ... */
5308         if (graph->commit)
5309                 prepare_rev_graph(graph);
5311         /* Graph visualization needs a one rev look-ahead,
5312          * so the first update doesn't visualize anything. */
5313         if (!graph->prev->commit)
5314                 return;
5316         draw_rev_graph(graph->prev);
5317         done_rev_graph(graph->prev->prev);
5321 /*
5322  * Main view backend
5323  */
5325 static const char *main_argv[SIZEOF_ARG] = {
5326         "git", "log", "--no-color", "--pretty=raw", "--parents",
5327                       "--topo-order", "%(head)", NULL
5328 };
5330 static bool
5331 main_draw(struct view *view, struct line *line, unsigned int lineno)
5333         struct commit *commit = line->data;
5335         if (!*commit->author)
5336                 return FALSE;
5338         if (opt_date && draw_date(view, &commit->time))
5339                 return TRUE;
5341         if (opt_author &&
5342             draw_field(view, LINE_MAIN_AUTHOR, commit->author, opt_author_cols, TRUE))
5343                 return TRUE;
5345         if (opt_rev_graph && commit->graph_size &&
5346             draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
5347                 return TRUE;
5349         if (opt_show_refs && commit->refs) {
5350                 size_t i = 0;
5352                 do {
5353                         enum line_type type;
5355                         if (commit->refs[i]->head)
5356                                 type = LINE_MAIN_HEAD;
5357                         else if (commit->refs[i]->ltag)
5358                                 type = LINE_MAIN_LOCAL_TAG;
5359                         else if (commit->refs[i]->tag)
5360                                 type = LINE_MAIN_TAG;
5361                         else if (commit->refs[i]->tracked)
5362                                 type = LINE_MAIN_TRACKED;
5363                         else if (commit->refs[i]->remote)
5364                                 type = LINE_MAIN_REMOTE;
5365                         else
5366                                 type = LINE_MAIN_REF;
5368                         if (draw_text(view, type, "[", TRUE) ||
5369                             draw_text(view, type, commit->refs[i]->name, TRUE) ||
5370                             draw_text(view, type, "]", TRUE))
5371                                 return TRUE;
5373                         if (draw_text(view, LINE_DEFAULT, " ", TRUE))
5374                                 return TRUE;
5375                 } while (commit->refs[i++]->next);
5376         }
5378         draw_text(view, LINE_DEFAULT, commit->title, TRUE);
5379         return TRUE;
5382 /* Reads git log --pretty=raw output and parses it into the commit struct. */
5383 static bool
5384 main_read(struct view *view, char *line)
5386         static struct rev_graph *graph = graph_stacks;
5387         enum line_type type;
5388         struct commit *commit;
5390         if (!line) {
5391                 int i;
5393                 if (!view->lines && !view->parent)
5394                         die("No revisions match the given arguments.");
5395                 if (view->lines > 0) {
5396                         commit = view->line[view->lines - 1].data;
5397                         if (!*commit->author) {
5398                                 view->lines--;
5399                                 free(commit);
5400                                 graph->commit = NULL;
5401                         }
5402                 }
5403                 update_rev_graph(graph);
5405                 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
5406                         clear_rev_graph(&graph_stacks[i]);
5407                 return TRUE;
5408         }
5410         type = get_line_type(line);
5411         if (type == LINE_COMMIT) {
5412                 commit = calloc(1, sizeof(struct commit));
5413                 if (!commit)
5414                         return FALSE;
5416                 line += STRING_SIZE("commit ");
5417                 if (*line == '-') {
5418                         graph->boundary = 1;
5419                         line++;
5420                 }
5422                 string_copy_rev(commit->id, line);
5423                 commit->refs = get_refs(commit->id);
5424                 graph->commit = commit;
5425                 add_line_data(view, commit, LINE_MAIN_COMMIT);
5427                 while ((line = strchr(line, ' '))) {
5428                         line++;
5429                         push_rev_graph(graph->parents, line);
5430                         commit->has_parents = TRUE;
5431                 }
5432                 return TRUE;
5433         }
5435         if (!view->lines)
5436                 return TRUE;
5437         commit = view->line[view->lines - 1].data;
5439         switch (type) {
5440         case LINE_PARENT:
5441                 if (commit->has_parents)
5442                         break;
5443                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
5444                 break;
5446         case LINE_AUTHOR:
5447         {
5448                 /* Parse author lines where the name may be empty:
5449                  *      author  <email@address.tld> 1138474660 +0100
5450                  */
5451                 char *ident = line + STRING_SIZE("author ");
5452                 char *nameend = strchr(ident, '<');
5453                 char *emailend = strchr(ident, '>');
5455                 if (!nameend || !emailend)
5456                         break;
5458                 update_rev_graph(graph);
5459                 graph = graph->next;
5461                 *nameend = *emailend = 0;
5462                 ident = chomp_string(ident);
5463                 if (!*ident) {
5464                         ident = chomp_string(nameend + 1);
5465                         if (!*ident)
5466                                 ident = "Unknown";
5467                 }
5469                 string_ncopy(commit->author, ident, strlen(ident));
5471                 /* Parse epoch and timezone */
5472                 if (emailend[1] == ' ') {
5473                         char *secs = emailend + 2;
5474                         char *zone = strchr(secs, ' ');
5475                         time_t time = (time_t) atol(secs);
5477                         if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
5478                                 long tz;
5480                                 zone++;
5481                                 tz  = ('0' - zone[1]) * 60 * 60 * 10;
5482                                 tz += ('0' - zone[2]) * 60 * 60;
5483                                 tz += ('0' - zone[3]) * 60;
5484                                 tz += ('0' - zone[4]) * 60;
5486                                 if (zone[0] == '-')
5487                                         tz = -tz;
5489                                 time -= tz;
5490                         }
5492                         gmtime_r(&time, &commit->time);
5493                 }
5494                 break;
5495         }
5496         default:
5497                 /* Fill in the commit title if it has not already been set. */
5498                 if (commit->title[0])
5499                         break;
5501                 /* Require titles to start with a non-space character at the
5502                  * offset used by git log. */
5503                 if (strncmp(line, "    ", 4))
5504                         break;
5505                 line += 4;
5506                 /* Well, if the title starts with a whitespace character,
5507                  * try to be forgiving.  Otherwise we end up with no title. */
5508                 while (isspace(*line))
5509                         line++;
5510                 if (*line == '\0')
5511                         break;
5512                 /* FIXME: More graceful handling of titles; append "..." to
5513                  * shortened titles, etc. */
5515                 string_ncopy(commit->title, line, strlen(line));
5516         }
5518         return TRUE;
5521 static enum request
5522 main_request(struct view *view, enum request request, struct line *line)
5524         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
5526         switch (request) {
5527         case REQ_ENTER:
5528                 open_view(view, REQ_VIEW_DIFF, flags);
5529                 break;
5530         case REQ_REFRESH:
5531                 load_refs();
5532                 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
5533                 break;
5534         default:
5535                 return request;
5536         }
5538         return REQ_NONE;
5541 static bool
5542 grep_refs(struct ref **refs, regex_t *regex)
5544         regmatch_t pmatch;
5545         size_t i = 0;
5547         if (!refs)
5548                 return FALSE;
5549         do {
5550                 if (regexec(regex, refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
5551                         return TRUE;
5552         } while (refs[i++]->next);
5554         return FALSE;
5557 static bool
5558 main_grep(struct view *view, struct line *line)
5560         struct commit *commit = line->data;
5561         enum { S_TITLE, S_AUTHOR, S_DATE, S_REFS, S_END } state;
5562         char buf[DATE_COLS + 1];
5563         regmatch_t pmatch;
5565         for (state = S_TITLE; state < S_END; state++) {
5566                 char *text;
5568                 switch (state) {
5569                 case S_TITLE:   text = commit->title;   break;
5570                 case S_AUTHOR:
5571                         if (!opt_author)
5572                                 continue;
5573                         text = commit->author;
5574                         break;
5575                 case S_DATE:
5576                         if (!opt_date)
5577                                 continue;
5578                         if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
5579                                 continue;
5580                         text = buf;
5581                         break;
5582                 case S_REFS:
5583                         if (!opt_show_refs)
5584                                 continue;
5585                         if (grep_refs(commit->refs, view->regex) == TRUE)
5586                                 return TRUE;
5587                         continue;
5588                 default:
5589                         return FALSE;
5590                 }
5592                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
5593                         return TRUE;
5594         }
5596         return FALSE;
5599 static void
5600 main_select(struct view *view, struct line *line)
5602         struct commit *commit = line->data;
5604         string_copy_rev(view->ref, commit->id);
5605         string_copy_rev(ref_commit, view->ref);
5608 static struct view_ops main_ops = {
5609         "commit",
5610         main_argv,
5611         NULL,
5612         main_read,
5613         main_draw,
5614         main_request,
5615         main_grep,
5616         main_select,
5617 };
5620 /*
5621  * Unicode / UTF-8 handling
5622  *
5623  * NOTE: Much of the following code for dealing with unicode is derived from
5624  * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
5625  * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
5626  */
5628 /* I've (over)annotated a lot of code snippets because I am not entirely
5629  * confident that the approach taken by this small UTF-8 interface is correct.
5630  * --jonas */
5632 static inline int
5633 unicode_width(unsigned long c)
5635         if (c >= 0x1100 &&
5636            (c <= 0x115f                         /* Hangul Jamo */
5637             || c == 0x2329
5638             || c == 0x232a
5639             || (c >= 0x2e80  && c <= 0xa4cf && c != 0x303f)
5640                                                 /* CJK ... Yi */
5641             || (c >= 0xac00  && c <= 0xd7a3)    /* Hangul Syllables */
5642             || (c >= 0xf900  && c <= 0xfaff)    /* CJK Compatibility Ideographs */
5643             || (c >= 0xfe30  && c <= 0xfe6f)    /* CJK Compatibility Forms */
5644             || (c >= 0xff00  && c <= 0xff60)    /* Fullwidth Forms */
5645             || (c >= 0xffe0  && c <= 0xffe6)
5646             || (c >= 0x20000 && c <= 0x2fffd)
5647             || (c >= 0x30000 && c <= 0x3fffd)))
5648                 return 2;
5650         if (c == '\t')
5651                 return opt_tab_size;
5653         return 1;
5656 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
5657  * Illegal bytes are set one. */
5658 static const unsigned char utf8_bytes[256] = {
5659         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,
5660         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,
5661         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,
5662         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,
5663         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,
5664         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,
5665         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,
5666         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,
5667 };
5669 /* Decode UTF-8 multi-byte representation into a unicode character. */
5670 static inline unsigned long
5671 utf8_to_unicode(const char *string, size_t length)
5673         unsigned long unicode;
5675         switch (length) {
5676         case 1:
5677                 unicode  =   string[0];
5678                 break;
5679         case 2:
5680                 unicode  =  (string[0] & 0x1f) << 6;
5681                 unicode +=  (string[1] & 0x3f);
5682                 break;
5683         case 3:
5684                 unicode  =  (string[0] & 0x0f) << 12;
5685                 unicode += ((string[1] & 0x3f) << 6);
5686                 unicode +=  (string[2] & 0x3f);
5687                 break;
5688         case 4:
5689                 unicode  =  (string[0] & 0x0f) << 18;
5690                 unicode += ((string[1] & 0x3f) << 12);
5691                 unicode += ((string[2] & 0x3f) << 6);
5692                 unicode +=  (string[3] & 0x3f);
5693                 break;
5694         case 5:
5695                 unicode  =  (string[0] & 0x0f) << 24;
5696                 unicode += ((string[1] & 0x3f) << 18);
5697                 unicode += ((string[2] & 0x3f) << 12);
5698                 unicode += ((string[3] & 0x3f) << 6);
5699                 unicode +=  (string[4] & 0x3f);
5700                 break;
5701         case 6:
5702                 unicode  =  (string[0] & 0x01) << 30;
5703                 unicode += ((string[1] & 0x3f) << 24);
5704                 unicode += ((string[2] & 0x3f) << 18);
5705                 unicode += ((string[3] & 0x3f) << 12);
5706                 unicode += ((string[4] & 0x3f) << 6);
5707                 unicode +=  (string[5] & 0x3f);
5708                 break;
5709         default:
5710                 die("Invalid unicode length");
5711         }
5713         /* Invalid characters could return the special 0xfffd value but NUL
5714          * should be just as good. */
5715         return unicode > 0xffff ? 0 : unicode;
5718 /* Calculates how much of string can be shown within the given maximum width
5719  * and sets trimmed parameter to non-zero value if all of string could not be
5720  * shown. If the reserve flag is TRUE, it will reserve at least one
5721  * trailing character, which can be useful when drawing a delimiter.
5722  *
5723  * Returns the number of bytes to output from string to satisfy max_width. */
5724 static size_t
5725 utf8_length(const char *string, int *width, size_t max_width, int *trimmed, bool reserve)
5727         const char *start = string;
5728         const char *end = strchr(string, '\0');
5729         unsigned char last_bytes = 0;
5730         size_t last_ucwidth = 0;
5732         *width = 0;
5733         *trimmed = 0;
5735         while (string < end) {
5736                 int c = *(unsigned char *) string;
5737                 unsigned char bytes = utf8_bytes[c];
5738                 size_t ucwidth;
5739                 unsigned long unicode;
5741                 if (string + bytes > end)
5742                         break;
5744                 /* Change representation to figure out whether
5745                  * it is a single- or double-width character. */
5747                 unicode = utf8_to_unicode(string, bytes);
5748                 /* FIXME: Graceful handling of invalid unicode character. */
5749                 if (!unicode)
5750                         break;
5752                 ucwidth = unicode_width(unicode);
5753                 *width  += ucwidth;
5754                 if (*width > max_width) {
5755                         *trimmed = 1;
5756                         *width -= ucwidth;
5757                         if (reserve && *width == max_width) {
5758                                 string -= last_bytes;
5759                                 *width -= last_ucwidth;
5760                         }
5761                         break;
5762                 }
5764                 string  += bytes;
5765                 last_bytes = bytes;
5766                 last_ucwidth = ucwidth;
5767         }
5769         return string - start;
5773 /*
5774  * Status management
5775  */
5777 /* Whether or not the curses interface has been initialized. */
5778 static bool cursed = FALSE;
5780 /* The status window is used for polling keystrokes. */
5781 static WINDOW *status_win;
5783 static bool status_empty = TRUE;
5785 /* Update status and title window. */
5786 static void
5787 report(const char *msg, ...)
5789         struct view *view = display[current_view];
5791         if (input_mode)
5792                 return;
5794         if (!view) {
5795                 char buf[SIZEOF_STR];
5796                 va_list args;
5798                 va_start(args, msg);
5799                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
5800                         buf[sizeof(buf) - 1] = 0;
5801                         buf[sizeof(buf) - 2] = '.';
5802                         buf[sizeof(buf) - 3] = '.';
5803                         buf[sizeof(buf) - 4] = '.';
5804                 }
5805                 va_end(args);
5806                 die("%s", buf);
5807         }
5809         if (!status_empty || *msg) {
5810                 va_list args;
5812                 va_start(args, msg);
5814                 wmove(status_win, 0, 0);
5815                 if (*msg) {
5816                         vwprintw(status_win, msg, args);
5817                         status_empty = FALSE;
5818                 } else {
5819                         status_empty = TRUE;
5820                 }
5821                 wclrtoeol(status_win);
5822                 wrefresh(status_win);
5824                 va_end(args);
5825         }
5827         update_view_title(view);
5828         update_display_cursor(view);
5831 /* Controls when nodelay should be in effect when polling user input. */
5832 static void
5833 set_nonblocking_input(bool loading)
5835         static unsigned int loading_views;
5837         if ((loading == FALSE && loading_views-- == 1) ||
5838             (loading == TRUE  && loading_views++ == 0))
5839                 nodelay(status_win, loading);
5842 static void
5843 init_display(void)
5845         int x, y;
5847         /* Initialize the curses library */
5848         if (isatty(STDIN_FILENO)) {
5849                 cursed = !!initscr();
5850                 opt_tty = stdin;
5851         } else {
5852                 /* Leave stdin and stdout alone when acting as a pager. */
5853                 opt_tty = fopen("/dev/tty", "r+");
5854                 if (!opt_tty)
5855                         die("Failed to open /dev/tty");
5856                 cursed = !!newterm(NULL, opt_tty, opt_tty);
5857         }
5859         if (!cursed)
5860                 die("Failed to initialize curses");
5862         nonl();         /* Tell curses not to do NL->CR/NL on output */
5863         cbreak();       /* Take input chars one at a time, no wait for \n */
5864         noecho();       /* Don't echo input */
5865         leaveok(stdscr, TRUE);
5867         if (has_colors())
5868                 init_colors();
5870         getmaxyx(stdscr, y, x);
5871         status_win = newwin(1, 0, y - 1, 0);
5872         if (!status_win)
5873                 die("Failed to create status window");
5875         /* Enable keyboard mapping */
5876         keypad(status_win, TRUE);
5877         wbkgdset(status_win, get_line_attr(LINE_STATUS));
5879         TABSIZE = opt_tab_size;
5880         if (opt_line_graphics) {
5881                 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
5882         }
5885 static bool
5886 prompt_yesno(const char *prompt)
5888         enum { WAIT, STOP, CANCEL  } status = WAIT;
5889         bool answer = FALSE;
5891         while (status == WAIT) {
5892                 struct view *view;
5893                 int i, key;
5895                 input_mode = TRUE;
5897                 foreach_view (view, i)
5898                         update_view(view);
5900                 input_mode = FALSE;
5902                 mvwprintw(status_win, 0, 0, "%s [Yy]/[Nn]", prompt);
5903                 wclrtoeol(status_win);
5905                 /* Refresh, accept single keystroke of input */
5906                 key = wgetch(status_win);
5907                 switch (key) {
5908                 case ERR:
5909                         break;
5911                 case 'y':
5912                 case 'Y':
5913                         answer = TRUE;
5914                         status = STOP;
5915                         break;
5917                 case KEY_ESC:
5918                 case KEY_RETURN:
5919                 case KEY_ENTER:
5920                 case KEY_BACKSPACE:
5921                 case 'n':
5922                 case 'N':
5923                 case '\n':
5924                 default:
5925                         answer = FALSE;
5926                         status = CANCEL;
5927                 }
5928         }
5930         /* Clear the status window */
5931         status_empty = FALSE;
5932         report("");
5934         return answer;
5937 static char *
5938 read_prompt(const char *prompt)
5940         enum { READING, STOP, CANCEL } status = READING;
5941         static char buf[SIZEOF_STR];
5942         int pos = 0;
5944         while (status == READING) {
5945                 struct view *view;
5946                 int i, key;
5948                 input_mode = TRUE;
5950                 foreach_view (view, i)
5951                         update_view(view);
5953                 input_mode = FALSE;
5955                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
5956                 wclrtoeol(status_win);
5958                 /* Refresh, accept single keystroke of input */
5959                 key = wgetch(status_win);
5960                 switch (key) {
5961                 case KEY_RETURN:
5962                 case KEY_ENTER:
5963                 case '\n':
5964                         status = pos ? STOP : CANCEL;
5965                         break;
5967                 case KEY_BACKSPACE:
5968                         if (pos > 0)
5969                                 pos--;
5970                         else
5971                                 status = CANCEL;
5972                         break;
5974                 case KEY_ESC:
5975                         status = CANCEL;
5976                         break;
5978                 case ERR:
5979                         break;
5981                 default:
5982                         if (pos >= sizeof(buf)) {
5983                                 report("Input string too long");
5984                                 return NULL;
5985                         }
5987                         if (isprint(key))
5988                                 buf[pos++] = (char) key;
5989                 }
5990         }
5992         /* Clear the status window */
5993         status_empty = FALSE;
5994         report("");
5996         if (status == CANCEL)
5997                 return NULL;
5999         buf[pos++] = 0;
6001         return buf;
6004 /*
6005  * Repository properties
6006  */
6008 static int
6009 git_properties(const char **argv, const char *separators,
6010                int (*read_property)(char *, size_t, char *, size_t))
6012         struct io io = {};
6014         if (init_io_rd(&io, argv, NULL, FORMAT_NONE))
6015                 return read_properties(&io, separators, read_property);
6016         return ERR;
6019 static struct ref *refs = NULL;
6020 static size_t refs_alloc = 0;
6021 static size_t refs_size = 0;
6023 /* Id <-> ref store */
6024 static struct ref ***id_refs = NULL;
6025 static size_t id_refs_alloc = 0;
6026 static size_t id_refs_size = 0;
6028 static int
6029 compare_refs(const void *ref1_, const void *ref2_)
6031         const struct ref *ref1 = *(const struct ref **)ref1_;
6032         const struct ref *ref2 = *(const struct ref **)ref2_;
6034         if (ref1->tag != ref2->tag)
6035                 return ref2->tag - ref1->tag;
6036         if (ref1->ltag != ref2->ltag)
6037                 return ref2->ltag - ref2->ltag;
6038         if (ref1->head != ref2->head)
6039                 return ref2->head - ref1->head;
6040         if (ref1->tracked != ref2->tracked)
6041                 return ref2->tracked - ref1->tracked;
6042         if (ref1->remote != ref2->remote)
6043                 return ref2->remote - ref1->remote;
6044         return strcmp(ref1->name, ref2->name);
6047 static struct ref **
6048 get_refs(const char *id)
6050         struct ref ***tmp_id_refs;
6051         struct ref **ref_list = NULL;
6052         size_t ref_list_alloc = 0;
6053         size_t ref_list_size = 0;
6054         size_t i;
6056         for (i = 0; i < id_refs_size; i++)
6057                 if (!strcmp(id, id_refs[i][0]->id))
6058                         return id_refs[i];
6060         tmp_id_refs = realloc_items(id_refs, &id_refs_alloc, id_refs_size + 1,
6061                                     sizeof(*id_refs));
6062         if (!tmp_id_refs)
6063                 return NULL;
6065         id_refs = tmp_id_refs;
6067         for (i = 0; i < refs_size; i++) {
6068                 struct ref **tmp;
6070                 if (strcmp(id, refs[i].id))
6071                         continue;
6073                 tmp = realloc_items(ref_list, &ref_list_alloc,
6074                                     ref_list_size + 1, sizeof(*ref_list));
6075                 if (!tmp) {
6076                         if (ref_list)
6077                                 free(ref_list);
6078                         return NULL;
6079                 }
6081                 ref_list = tmp;
6082                 ref_list[ref_list_size] = &refs[i];
6083                 /* XXX: The properties of the commit chains ensures that we can
6084                  * safely modify the shared ref. The repo references will
6085                  * always be similar for the same id. */
6086                 ref_list[ref_list_size]->next = 1;
6088                 ref_list_size++;
6089         }
6091         if (ref_list) {
6092                 qsort(ref_list, ref_list_size, sizeof(*ref_list), compare_refs);
6093                 ref_list[ref_list_size - 1]->next = 0;
6094                 id_refs[id_refs_size++] = ref_list;
6095         }
6097         return ref_list;
6100 static int
6101 read_ref(char *id, size_t idlen, char *name, size_t namelen)
6103         struct ref *ref;
6104         bool tag = FALSE;
6105         bool ltag = FALSE;
6106         bool remote = FALSE;
6107         bool tracked = FALSE;
6108         bool check_replace = FALSE;
6109         bool head = FALSE;
6111         if (!prefixcmp(name, "refs/tags/")) {
6112                 if (!suffixcmp(name, namelen, "^{}")) {
6113                         namelen -= 3;
6114                         name[namelen] = 0;
6115                         if (refs_size > 0 && refs[refs_size - 1].ltag == TRUE)
6116                                 check_replace = TRUE;
6117                 } else {
6118                         ltag = TRUE;
6119                 }
6121                 tag = TRUE;
6122                 namelen -= STRING_SIZE("refs/tags/");
6123                 name    += STRING_SIZE("refs/tags/");
6125         } else if (!prefixcmp(name, "refs/remotes/")) {
6126                 remote = TRUE;
6127                 namelen -= STRING_SIZE("refs/remotes/");
6128                 name    += STRING_SIZE("refs/remotes/");
6129                 tracked  = !strcmp(opt_remote, name);
6131         } else if (!prefixcmp(name, "refs/heads/")) {
6132                 namelen -= STRING_SIZE("refs/heads/");
6133                 name    += STRING_SIZE("refs/heads/");
6134                 head     = !strncmp(opt_head, name, namelen);
6136         } else if (!strcmp(name, "HEAD")) {
6137                 string_ncopy(opt_head_rev, id, idlen);
6138                 return OK;
6139         }
6141         if (check_replace && !strcmp(name, refs[refs_size - 1].name)) {
6142                 /* it's an annotated tag, replace the previous sha1 with the
6143                  * resolved commit id; relies on the fact git-ls-remote lists
6144                  * the commit id of an annotated tag right before the commit id
6145                  * it points to. */
6146                 refs[refs_size - 1].ltag = ltag;
6147                 string_copy_rev(refs[refs_size - 1].id, id);
6149                 return OK;
6150         }
6151         refs = realloc_items(refs, &refs_alloc, refs_size + 1, sizeof(*refs));
6152         if (!refs)
6153                 return ERR;
6155         ref = &refs[refs_size++];
6156         ref->name = malloc(namelen + 1);
6157         if (!ref->name)
6158                 return ERR;
6160         strncpy(ref->name, name, namelen);
6161         ref->name[namelen] = 0;
6162         ref->head = head;
6163         ref->tag = tag;
6164         ref->ltag = ltag;
6165         ref->remote = remote;
6166         ref->tracked = tracked;
6167         string_copy_rev(ref->id, id);
6169         return OK;
6172 static int
6173 load_refs(void)
6175         static const char *ls_remote_argv[SIZEOF_ARG] = {
6176                 "git", "ls-remote", ".", NULL
6177         };
6178         static bool init = FALSE;
6180         if (!init) {
6181                 argv_from_env(ls_remote_argv, "TIG_LS_REMOTE");
6182                 init = TRUE;
6183         }
6185         if (!*opt_git_dir)
6186                 return OK;
6188         while (refs_size > 0)
6189                 free(refs[--refs_size].name);
6190         while (id_refs_size > 0)
6191                 free(id_refs[--id_refs_size]);
6193         return git_properties(ls_remote_argv, "\t", read_ref);
6196 static int
6197 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
6199         if (!strcmp(name, "i18n.commitencoding"))
6200                 string_ncopy(opt_encoding, value, valuelen);
6202         if (!strcmp(name, "core.editor"))
6203                 string_ncopy(opt_editor, value, valuelen);
6205         /* branch.<head>.remote */
6206         if (*opt_head &&
6207             !strncmp(name, "branch.", 7) &&
6208             !strncmp(name + 7, opt_head, strlen(opt_head)) &&
6209             !strcmp(name + 7 + strlen(opt_head), ".remote"))
6210                 string_ncopy(opt_remote, value, valuelen);
6212         if (*opt_head && *opt_remote &&
6213             !strncmp(name, "branch.", 7) &&
6214             !strncmp(name + 7, opt_head, strlen(opt_head)) &&
6215             !strcmp(name + 7 + strlen(opt_head), ".merge")) {
6216                 size_t from = strlen(opt_remote);
6218                 if (!prefixcmp(value, "refs/heads/")) {
6219                         value += STRING_SIZE("refs/heads/");
6220                         valuelen -= STRING_SIZE("refs/heads/");
6221                 }
6223                 if (!string_format_from(opt_remote, &from, "/%s", value))
6224                         opt_remote[0] = 0;
6225         }
6227         return OK;
6230 static int
6231 load_git_config(void)
6233         const char *config_list_argv[] = { "git", GIT_CONFIG, "--list", NULL };
6235         return git_properties(config_list_argv, "=", read_repo_config_option);
6238 static int
6239 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
6241         if (!opt_git_dir[0]) {
6242                 string_ncopy(opt_git_dir, name, namelen);
6244         } else if (opt_is_inside_work_tree == -1) {
6245                 /* This can be 3 different values depending on the
6246                  * version of git being used. If git-rev-parse does not
6247                  * understand --is-inside-work-tree it will simply echo
6248                  * the option else either "true" or "false" is printed.
6249                  * Default to true for the unknown case. */
6250                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
6251         } else {
6252                 string_ncopy(opt_cdup, name, namelen);
6253         }
6255         return OK;
6258 static int
6259 load_repo_info(void)
6261         const char *head_argv[] = {
6262                 "git", "symbolic-ref", "HEAD", NULL
6263         };
6264         const char *rev_parse_argv[] = {
6265                 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
6266                         "--show-cdup", NULL
6267         };
6269         if (run_io_buf(head_argv, opt_head, sizeof(opt_head))) {
6270                 chomp_string(opt_head);
6271                 if (!prefixcmp(opt_head, "refs/heads/")) {
6272                         char *offset = opt_head + STRING_SIZE("refs/heads/");
6274                         memmove(opt_head, offset, strlen(offset) + 1);
6275                 }
6276         }
6278         return git_properties(rev_parse_argv, "=", read_repo_info);
6281 static int
6282 read_properties(struct io *io, const char *separators,
6283                 int (*read_property)(char *, size_t, char *, size_t))
6285         char *name;
6286         int state = OK;
6288         if (!start_io(io))
6289                 return ERR;
6291         while (state == OK && (name = io_gets(io))) {
6292                 char *value;
6293                 size_t namelen;
6294                 size_t valuelen;
6296                 name = chomp_string(name);
6297                 namelen = strcspn(name, separators);
6299                 if (name[namelen]) {
6300                         name[namelen] = 0;
6301                         value = chomp_string(name + namelen + 1);
6302                         valuelen = strlen(value);
6304                 } else {
6305                         value = "";
6306                         valuelen = 0;
6307                 }
6309                 state = read_property(name, namelen, value, valuelen);
6310         }
6312         if (state != ERR && io_error(io))
6313                 state = ERR;
6314         done_io(io);
6316         return state;
6320 /*
6321  * Main
6322  */
6324 static void __NORETURN
6325 quit(int sig)
6327         /* XXX: Restore tty modes and let the OS cleanup the rest! */
6328         if (cursed)
6329                 endwin();
6330         exit(0);
6333 static void __NORETURN
6334 die(const char *err, ...)
6336         va_list args;
6338         endwin();
6340         va_start(args, err);
6341         fputs("tig: ", stderr);
6342         vfprintf(stderr, err, args);
6343         fputs("\n", stderr);
6344         va_end(args);
6346         exit(1);
6349 static void
6350 warn(const char *msg, ...)
6352         va_list args;
6354         va_start(args, msg);
6355         fputs("tig warning: ", stderr);
6356         vfprintf(stderr, msg, args);
6357         fputs("\n", stderr);
6358         va_end(args);
6361 int
6362 main(int argc, const char *argv[])
6364         const char **run_argv = NULL;
6365         struct view *view;
6366         enum request request;
6367         size_t i;
6369         signal(SIGINT, quit);
6371         if (setlocale(LC_ALL, "")) {
6372                 char *codeset = nl_langinfo(CODESET);
6374                 string_ncopy(opt_codeset, codeset, strlen(codeset));
6375         }
6377         if (load_repo_info() == ERR)
6378                 die("Failed to load repo info.");
6380         if (load_options() == ERR)
6381                 die("Failed to load user config.");
6383         if (load_git_config() == ERR)
6384                 die("Failed to load repo config.");
6386         request = parse_options(argc, argv, &run_argv);
6387         if (request == REQ_NONE)
6388                 return 0;
6390         /* Require a git repository unless when running in pager mode. */
6391         if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
6392                 die("Not a git repository");
6394         if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
6395                 opt_utf8 = FALSE;
6397         if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
6398                 opt_iconv = iconv_open(opt_codeset, opt_encoding);
6399                 if (opt_iconv == ICONV_NONE)
6400                         die("Failed to initialize character set conversion");
6401         }
6403         if (load_refs() == ERR)
6404                 die("Failed to load refs.");
6406         foreach_view (view, i)
6407                 argv_from_env(view->ops->argv, view->cmd_env);
6409         init_display();
6411         if (request == REQ_VIEW_PAGER || run_argv) {
6412                 if (request == REQ_VIEW_PAGER)
6413                         io_open(&VIEW(request)->io, "");
6414                 else if (!prepare_update(VIEW(request), run_argv, NULL, FORMAT_NONE))
6415                         die("Failed to format arguments");
6416                 open_view(NULL, request, OPEN_PREPARED);
6417                 request = REQ_NONE;
6418         }
6420         while (view_driver(display[current_view], request)) {
6421                 int key;
6422                 int i;
6424                 foreach_view (view, i)
6425                         update_view(view);
6426                 view = display[current_view];
6428                 /* Refresh, accept single keystroke of input */
6429                 key = wgetch(status_win);
6431                 /* wgetch() with nodelay() enabled returns ERR when there's no
6432                  * input. */
6433                 if (key == ERR) {
6434                         request = REQ_NONE;
6435                         continue;
6436                 }
6438                 request = get_keybinding(view->keymap, key);
6440                 /* Some low-level request handling. This keeps access to
6441                  * status_win restricted. */
6442                 switch (request) {
6443                 case REQ_PROMPT:
6444                 {
6445                         char *cmd = read_prompt(":");
6447                         if (cmd) {
6448                                 struct view *next = VIEW(REQ_VIEW_PAGER);
6449                                 const char *argv[SIZEOF_ARG] = { "git" };
6450                                 int argc = 1;
6452                                 /* When running random commands, initially show the
6453                                  * command in the title. However, it maybe later be
6454                                  * overwritten if a commit line is selected. */
6455                                 string_ncopy(next->ref, cmd, strlen(cmd));
6457                                 if (!argv_from_string(argv, &argc, cmd)) {
6458                                         report("Too many arguments");
6459                                 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
6460                                         report("Failed to format command");
6461                                 } else {
6462                                         open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
6463                                 }
6464                         }
6466                         request = REQ_NONE;
6467                         break;
6468                 }
6469                 case REQ_SEARCH:
6470                 case REQ_SEARCH_BACK:
6471                 {
6472                         const char *prompt = request == REQ_SEARCH ? "/" : "?";
6473                         char *search = read_prompt(prompt);
6475                         if (search)
6476                                 string_ncopy(opt_search, search, strlen(search));
6477                         else
6478                                 request = REQ_NONE;
6479                         break;
6480                 }
6481                 case REQ_SCREEN_RESIZE:
6482                 {
6483                         int height, width;
6485                         getmaxyx(stdscr, height, width);
6487                         /* Resize the status view and let the view driver take
6488                          * care of resizing the displayed views. */
6489                         wresize(status_win, 1, width);
6490                         mvwin(status_win, height - 1, 0);
6491                         wrefresh(status_win);
6492                         break;
6493                 }
6494                 default:
6495                         break;
6496                 }
6497         }
6499         quit(0);
6501         return 0;