Code

7275a993591e8e3cf241bab26ae4014a34f234fe
[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>
39 #include <regex.h>
41 #include <locale.h>
42 #include <langinfo.h>
43 #include <iconv.h>
45 /* ncurses(3): Must be defined to have extended wide-character functions. */
46 #define _XOPEN_SOURCE_EXTENDED
48 #ifdef HAVE_NCURSESW_NCURSES_H
49 #include <ncursesw/ncurses.h>
50 #else
51 #ifdef HAVE_NCURSES_NCURSES_H
52 #include <ncurses/ncurses.h>
53 #else
54 #include <ncurses.h>
55 #endif
56 #endif
58 #if __GNUC__ >= 3
59 #define __NORETURN __attribute__((__noreturn__))
60 #else
61 #define __NORETURN
62 #endif
64 static void __NORETURN die(const char *err, ...);
65 static void warn(const char *msg, ...);
66 static void report(const char *msg, ...);
67 static int read_properties(FILE *pipe, const char *separators, int (*read)(char *, size_t, char *, size_t));
68 static void set_nonblocking_input(bool loading);
69 static size_t utf8_length(const char *string, int *width, size_t max_width, int *trimmed, bool reserve);
70 static bool prompt_yesno(const char *prompt);
71 static int load_refs(void);
73 #define ABS(x)          ((x) >= 0  ? (x) : -(x))
74 #define MIN(x, y)       ((x) < (y) ? (x) :  (y))
76 #define ARRAY_SIZE(x)   (sizeof(x) / sizeof(x[0]))
77 #define STRING_SIZE(x)  (sizeof(x) - 1)
79 #define SIZEOF_STR      1024    /* Default string size. */
80 #define SIZEOF_REF      256     /* Size of symbolic or SHA1 ID. */
81 #define SIZEOF_REV      41      /* Holds a SHA-1 and an ending NUL. */
82 #define SIZEOF_ARG      32      /* Default argument array size. */
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 #define TIG_LS_REMOTE \
123         "git ls-remote . 2>/dev/null"
125 #define TIG_MAIN_BASE \
126         "git log --no-color --pretty=raw --parents --topo-order"
128 /* Some ascii-shorthands fitted into the ncurses namespace. */
129 #define KEY_TAB         '\t'
130 #define KEY_RETURN      '\r'
131 #define KEY_ESC         27
134 struct ref {
135         char *name;             /* Ref name; tag or head names are shortened. */
136         char id[SIZEOF_REV];    /* Commit SHA1 ID */
137         unsigned int head:1;    /* Is it the current HEAD? */
138         unsigned int tag:1;     /* Is it a tag? */
139         unsigned int ltag:1;    /* If so, is the tag local? */
140         unsigned int remote:1;  /* Is it a remote ref? */
141         unsigned int tracked:1; /* Is it the remote for the current HEAD? */
142         unsigned int next:1;    /* For ref lists: are there more refs? */
143 };
145 static struct ref **get_refs(const char *id);
147 enum format_flags {
148         FORMAT_ALL,             /* Perform replacement in all arguments. */
149         FORMAT_DASH,            /* Perform replacement up until "--". */
150         FORMAT_NONE             /* No replacement should be performed. */
151 };
153 static bool format_command(char dst[], const char *src[], enum format_flags flags);
154 static bool format_argv(const char *dst[], const char *src[], enum format_flags flags);
156 struct int_map {
157         const char *name;
158         int namelen;
159         int value;
160 };
162 static int
163 set_from_int_map(struct int_map *map, size_t map_size,
164                  int *value, const char *name, int namelen)
167         int i;
169         for (i = 0; i < map_size; i++)
170                 if (namelen == map[i].namelen &&
171                     !strncasecmp(name, map[i].name, namelen)) {
172                         *value = map[i].value;
173                         return OK;
174                 }
176         return ERR;
180 /*
181  * String helpers
182  */
184 static inline void
185 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
187         if (srclen > dstlen - 1)
188                 srclen = dstlen - 1;
190         strncpy(dst, src, srclen);
191         dst[srclen] = 0;
194 /* Shorthands for safely copying into a fixed buffer. */
196 #define string_copy(dst, src) \
197         string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
199 #define string_ncopy(dst, src, srclen) \
200         string_ncopy_do(dst, sizeof(dst), src, srclen)
202 #define string_copy_rev(dst, src) \
203         string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
205 #define string_add(dst, from, src) \
206         string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
208 static char *
209 chomp_string(char *name)
211         int namelen;
213         while (isspace(*name))
214                 name++;
216         namelen = strlen(name) - 1;
217         while (namelen > 0 && isspace(name[namelen]))
218                 name[namelen--] = 0;
220         return name;
223 static bool
224 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
226         va_list args;
227         size_t pos = bufpos ? *bufpos : 0;
229         va_start(args, fmt);
230         pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
231         va_end(args);
233         if (bufpos)
234                 *bufpos = pos;
236         return pos >= bufsize ? FALSE : TRUE;
239 #define string_format(buf, fmt, args...) \
240         string_nformat(buf, sizeof(buf), NULL, fmt, args)
242 #define string_format_from(buf, from, fmt, args...) \
243         string_nformat(buf, sizeof(buf), from, fmt, args)
245 static int
246 string_enum_compare(const char *str1, const char *str2, int len)
248         size_t i;
250 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
252         /* Diff-Header == DIFF_HEADER */
253         for (i = 0; i < len; i++) {
254                 if (toupper(str1[i]) == toupper(str2[i]))
255                         continue;
257                 if (string_enum_sep(str1[i]) &&
258                     string_enum_sep(str2[i]))
259                         continue;
261                 return str1[i] - str2[i];
262         }
264         return 0;
267 #define prefixcmp(str1, str2) \
268         strncmp(str1, str2, STRING_SIZE(str2))
270 static inline int
271 suffixcmp(const char *str, int slen, const char *suffix)
273         size_t len = slen >= 0 ? slen : strlen(str);
274         size_t suffixlen = strlen(suffix);
276         return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
279 /* Shell quoting
280  *
281  * NOTE: The following is a slightly modified copy of the git project's shell
282  * quoting routines found in the quote.c file.
283  *
284  * Help to copy the thing properly quoted for the shell safety.  any single
285  * quote is replaced with '\'', any exclamation point is replaced with '\!',
286  * and the whole thing is enclosed in a
287  *
288  * E.g.
289  *  original     sq_quote     result
290  *  name     ==> name      ==> 'name'
291  *  a b      ==> a b       ==> 'a b'
292  *  a'b      ==> a'\''b    ==> 'a'\''b'
293  *  a!b      ==> a'\!'b    ==> 'a'\!'b'
294  */
296 static size_t
297 sq_quote(char buf[SIZEOF_STR], size_t bufsize, const char *src)
299         char c;
301 #define BUFPUT(x) do { if (bufsize < SIZEOF_STR) buf[bufsize++] = (x); } while (0)
303         BUFPUT('\'');
304         while ((c = *src++)) {
305                 if (c == '\'' || c == '!') {
306                         BUFPUT('\'');
307                         BUFPUT('\\');
308                         BUFPUT(c);
309                         BUFPUT('\'');
310                 } else {
311                         BUFPUT(c);
312                 }
313         }
314         BUFPUT('\'');
316         if (bufsize < SIZEOF_STR)
317                 buf[bufsize] = 0;
319         return bufsize;
322 static bool
323 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
325         int valuelen;
327         while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
328                 bool advance = cmd[valuelen] != 0;
330                 cmd[valuelen] = 0;
331                 argv[(*argc)++] = chomp_string(cmd);
332                 cmd += valuelen + advance;
333         }
335         if (*argc < SIZEOF_ARG)
336                 argv[*argc] = NULL;
337         return *argc < SIZEOF_ARG;
340 static void
341 argv_from_env(const char **argv, const char *name)
343         char *env = argv ? getenv(name) : NULL;
344         int argc = 0;
346         if (env && *env)
347                 env = strdup(env);
348         if (env && !argv_from_string(argv, &argc, env))
349                 die("Too many arguments in the `%s` environment variable", name);
353 /*
354  * Executing external commands.
355  */
357 enum io_type {
358         IO_FD,                  /* File descriptor based IO. */
359         IO_FG,                  /* Execute command with same std{in,out,err}. */
360         IO_RD,                  /* Read only fork+exec IO. */
361         IO_WR,                  /* Write only fork+exec IO. */
362 };
364 struct io {
365         enum io_type type;      /* The requested type of pipe. */
366         const char *dir;        /* Directory from which to execute. */
367         FILE *pipe;             /* Pipe for reading or writing. */
368         int error;              /* Error status. */
369         char sh[SIZEOF_STR];    /* Shell command buffer. */
370         char *buf;              /* Read/write buffer. */
371         size_t bufalloc;        /* Allocated buffer size. */
372 };
374 static void
375 reset_io(struct io *io)
377         io->pipe = NULL;
378         io->buf = NULL;
379         io->bufalloc = 0;
380         io->error = 0;
383 static void
384 init_io(struct io *io, const char *dir, enum io_type type)
386         reset_io(io);
387         io->type = type;
388         io->dir = dir;
391 static bool
392 init_io_rd(struct io *io, const char *argv[], const char *dir,
393                 enum format_flags flags)
395         init_io(io, dir, IO_RD);
396         return format_command(io->sh, argv, flags);
399 static bool
400 init_io_fd(struct io *io, FILE *pipe)
402         init_io(io, NULL, IO_FD);
403         io->pipe = pipe;
404         return io->pipe != NULL;
407 static bool
408 done_io(struct io *io)
410         free(io->buf);
411         if (io->type == IO_FD)
412                 fclose(io->pipe);
413         else if (io->type == IO_RD || io->type == IO_WR)
414                 pclose(io->pipe);
415         reset_io(io);
416         return TRUE;
419 static bool
420 start_io(struct io *io)
422         char buf[SIZEOF_STR * 2];
423         size_t bufpos = 0;
425         if (io->dir && *io->dir &&
426             !string_format_from(buf, &bufpos, "cd %s;", io->dir))
427                 return FALSE;
429         if (!string_format_from(buf, &bufpos, "%s", io->sh))
430                 return FALSE;
432         if (io->type == IO_FG)
433                 return system(buf) == 0;
435         io->pipe = popen(io->sh, io->type == IO_RD ? "r" : "w");
436         return io->pipe != NULL;
439 static bool
440 run_io(struct io *io, enum io_type type, const char *cmd)
442         init_io(io, NULL, type);
443         string_ncopy(io->sh, cmd, strlen(cmd));
444         return start_io(io);
447 static int
448 run_io_do(struct io *io)
450         return start_io(io) && done_io(io);
453 static bool
454 run_io_fg(const char **argv, const char *dir)
456         struct io io = {};
458         init_io(&io, dir, IO_FG);
459         if (!format_command(io.sh, argv, FORMAT_NONE))
460                 return FALSE;
461         return run_io_do(&io);
464 static bool
465 run_io_rd(struct io *io, const char **argv, enum format_flags flags)
467         return init_io_rd(io, argv, NULL, flags) && start_io(io);
470 static bool
471 io_eof(struct io *io)
473         return feof(io->pipe);
476 static int
477 io_error(struct io *io)
479         return io->error;
482 static bool
483 io_strerror(struct io *io)
485         return strerror(io->error);
488 static char *
489 io_gets(struct io *io)
491         if (!io->buf) {
492                 io->buf = malloc(BUFSIZ);
493                 if (!io->buf)
494                         return NULL;
495                 io->bufalloc = BUFSIZ;
496         }
498         if (!fgets(io->buf, io->bufalloc, io->pipe)) {
499                 if (ferror(io->pipe))
500                         io->error = errno;
501                 return NULL;
502         }
504         return io->buf;
507 static bool
508 run_io_buf(const char **argv, char buf[], size_t bufsize)
510         struct io io = {};
511         bool error;
513         if (!run_io_rd(&io, argv, FORMAT_NONE))
514                 return FALSE;
516         io.buf = buf;
517         io.bufalloc = bufsize;
518         error = !io_gets(&io) && io_error(&io);
519         io.buf = NULL;
521         return done_io(&io) || error;
525 /*
526  * User requests
527  */
529 #define REQ_INFO \
530         /* XXX: Keep the view request first and in sync with views[]. */ \
531         REQ_GROUP("View switching") \
532         REQ_(VIEW_MAIN,         "Show main view"), \
533         REQ_(VIEW_DIFF,         "Show diff view"), \
534         REQ_(VIEW_LOG,          "Show log view"), \
535         REQ_(VIEW_TREE,         "Show tree view"), \
536         REQ_(VIEW_BLOB,         "Show blob view"), \
537         REQ_(VIEW_BLAME,        "Show blame view"), \
538         REQ_(VIEW_HELP,         "Show help page"), \
539         REQ_(VIEW_PAGER,        "Show pager view"), \
540         REQ_(VIEW_STATUS,       "Show status view"), \
541         REQ_(VIEW_STAGE,        "Show stage view"), \
542         \
543         REQ_GROUP("View manipulation") \
544         REQ_(ENTER,             "Enter current line and scroll"), \
545         REQ_(NEXT,              "Move to next"), \
546         REQ_(PREVIOUS,          "Move to previous"), \
547         REQ_(VIEW_NEXT,         "Move focus to next view"), \
548         REQ_(REFRESH,           "Reload and refresh"), \
549         REQ_(MAXIMIZE,          "Maximize the current view"), \
550         REQ_(VIEW_CLOSE,        "Close the current view"), \
551         REQ_(QUIT,              "Close all views and quit"), \
552         \
553         REQ_GROUP("View specific requests") \
554         REQ_(STATUS_UPDATE,     "Update file status"), \
555         REQ_(STATUS_REVERT,     "Revert file changes"), \
556         REQ_(STATUS_MERGE,      "Merge file using external tool"), \
557         REQ_(STAGE_NEXT,        "Find next chunk to stage"), \
558         REQ_(TREE_PARENT,       "Switch to parent directory in tree view"), \
559         \
560         REQ_GROUP("Cursor navigation") \
561         REQ_(MOVE_UP,           "Move cursor one line up"), \
562         REQ_(MOVE_DOWN,         "Move cursor one line down"), \
563         REQ_(MOVE_PAGE_DOWN,    "Move cursor one page down"), \
564         REQ_(MOVE_PAGE_UP,      "Move cursor one page up"), \
565         REQ_(MOVE_FIRST_LINE,   "Move cursor to first line"), \
566         REQ_(MOVE_LAST_LINE,    "Move cursor to last line"), \
567         \
568         REQ_GROUP("Scrolling") \
569         REQ_(SCROLL_LINE_UP,    "Scroll one line up"), \
570         REQ_(SCROLL_LINE_DOWN,  "Scroll one line down"), \
571         REQ_(SCROLL_PAGE_UP,    "Scroll one page up"), \
572         REQ_(SCROLL_PAGE_DOWN,  "Scroll one page down"), \
573         \
574         REQ_GROUP("Searching") \
575         REQ_(SEARCH,            "Search the view"), \
576         REQ_(SEARCH_BACK,       "Search backwards in the view"), \
577         REQ_(FIND_NEXT,         "Find next search match"), \
578         REQ_(FIND_PREV,         "Find previous search match"), \
579         \
580         REQ_GROUP("Option manipulation") \
581         REQ_(TOGGLE_LINENO,     "Toggle line numbers"), \
582         REQ_(TOGGLE_DATE,       "Toggle date display"), \
583         REQ_(TOGGLE_AUTHOR,     "Toggle author display"), \
584         REQ_(TOGGLE_REV_GRAPH,  "Toggle revision graph visualization"), \
585         REQ_(TOGGLE_REFS,       "Toggle reference display (tags/branches)"), \
586         \
587         REQ_GROUP("Misc") \
588         REQ_(PROMPT,            "Bring up the prompt"), \
589         REQ_(SCREEN_REDRAW,     "Redraw the screen"), \
590         REQ_(SCREEN_RESIZE,     "Resize the screen"), \
591         REQ_(SHOW_VERSION,      "Show version information"), \
592         REQ_(STOP_LOADING,      "Stop all loading views"), \
593         REQ_(EDIT,              "Open in editor"), \
594         REQ_(NONE,              "Do nothing")
597 /* User action requests. */
598 enum request {
599 #define REQ_GROUP(help)
600 #define REQ_(req, help) REQ_##req
602         /* Offset all requests to avoid conflicts with ncurses getch values. */
603         REQ_OFFSET = KEY_MAX + 1,
604         REQ_INFO
606 #undef  REQ_GROUP
607 #undef  REQ_
608 };
610 struct request_info {
611         enum request request;
612         const char *name;
613         int namelen;
614         const char *help;
615 };
617 static struct request_info req_info[] = {
618 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
619 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
620         REQ_INFO
621 #undef  REQ_GROUP
622 #undef  REQ_
623 };
625 static enum request
626 get_request(const char *name)
628         int namelen = strlen(name);
629         int i;
631         for (i = 0; i < ARRAY_SIZE(req_info); i++)
632                 if (req_info[i].namelen == namelen &&
633                     !string_enum_compare(req_info[i].name, name, namelen))
634                         return req_info[i].request;
636         return REQ_NONE;
640 /*
641  * Options
642  */
644 static const char usage[] =
645 "tig " TIG_VERSION " (" __DATE__ ")\n"
646 "\n"
647 "Usage: tig        [options] [revs] [--] [paths]\n"
648 "   or: tig show   [options] [revs] [--] [paths]\n"
649 "   or: tig blame  [rev] path\n"
650 "   or: tig status\n"
651 "   or: tig <      [git command output]\n"
652 "\n"
653 "Options:\n"
654 "  -v, --version   Show version and exit\n"
655 "  -h, --help      Show help message and exit";
657 /* Option and state variables. */
658 static bool opt_date                    = TRUE;
659 static bool opt_author                  = TRUE;
660 static bool opt_line_number             = FALSE;
661 static bool opt_line_graphics           = TRUE;
662 static bool opt_rev_graph               = FALSE;
663 static bool opt_show_refs               = TRUE;
664 static int opt_num_interval             = NUMBER_INTERVAL;
665 static int opt_tab_size                 = TAB_SIZE;
666 static int opt_author_cols              = AUTHOR_COLS-1;
667 static char opt_cmd[SIZEOF_STR]         = "";
668 static char opt_path[SIZEOF_STR]        = "";
669 static char opt_file[SIZEOF_STR]        = "";
670 static char opt_ref[SIZEOF_REF]         = "";
671 static char opt_head[SIZEOF_REF]        = "";
672 static char opt_head_rev[SIZEOF_REV]    = "";
673 static char opt_remote[SIZEOF_REF]      = "";
674 static FILE *opt_pipe                   = NULL;
675 static char opt_encoding[20]            = "UTF-8";
676 static bool opt_utf8                    = TRUE;
677 static char opt_codeset[20]             = "UTF-8";
678 static iconv_t opt_iconv                = ICONV_NONE;
679 static char opt_search[SIZEOF_STR]      = "";
680 static char opt_cdup[SIZEOF_STR]        = "";
681 static char opt_git_dir[SIZEOF_STR]     = "";
682 static signed char opt_is_inside_work_tree      = -1; /* set to TRUE or FALSE */
683 static char opt_editor[SIZEOF_STR]      = "";
684 static FILE *opt_tty                    = NULL;
686 #define is_initial_commit()     (!*opt_head_rev)
687 #define is_head_commit(rev)     (!strcmp((rev), "HEAD") || !strcmp(opt_head_rev, (rev)))
689 static enum request
690 parse_options(int argc, const char *argv[])
692         enum request request = REQ_VIEW_MAIN;
693         size_t buf_size;
694         const char *subcommand;
695         bool seen_dashdash = FALSE;
696         int i;
698         if (!isatty(STDIN_FILENO)) {
699                 opt_pipe = stdin;
700                 return REQ_VIEW_PAGER;
701         }
703         if (argc <= 1)
704                 return REQ_VIEW_MAIN;
706         subcommand = argv[1];
707         if (!strcmp(subcommand, "status") || !strcmp(subcommand, "-S")) {
708                 if (!strcmp(subcommand, "-S"))
709                         warn("`-S' has been deprecated; use `tig status' instead");
710                 if (argc > 2)
711                         warn("ignoring arguments after `%s'", subcommand);
712                 return REQ_VIEW_STATUS;
714         } else if (!strcmp(subcommand, "blame")) {
715                 if (argc <= 2 || argc > 4)
716                         die("invalid number of options to blame\n\n%s", usage);
718                 i = 2;
719                 if (argc == 4) {
720                         string_ncopy(opt_ref, argv[i], strlen(argv[i]));
721                         i++;
722                 }
724                 string_ncopy(opt_file, argv[i], strlen(argv[i]));
725                 return REQ_VIEW_BLAME;
727         } else if (!strcmp(subcommand, "show")) {
728                 request = REQ_VIEW_DIFF;
730         } else if (!strcmp(subcommand, "log") || !strcmp(subcommand, "diff")) {
731                 request = subcommand[0] == 'l' ? REQ_VIEW_LOG : REQ_VIEW_DIFF;
732                 warn("`tig %s' has been deprecated", subcommand);
734         } else {
735                 subcommand = NULL;
736         }
738         if (!subcommand)
739                 /* XXX: This is vulnerable to the user overriding
740                  * options required for the main view parser. */
741                 string_copy(opt_cmd, TIG_MAIN_BASE);
742         else
743                 string_format(opt_cmd, "git %s", subcommand);
745         buf_size = strlen(opt_cmd);
747         for (i = 1 + !!subcommand; i < argc; i++) {
748                 const char *opt = argv[i];
750                 if (seen_dashdash || !strcmp(opt, "--")) {
751                         seen_dashdash = TRUE;
753                 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
754                         printf("tig version %s\n", TIG_VERSION);
755                         return REQ_NONE;
757                 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
758                         printf("%s\n", usage);
759                         return REQ_NONE;
760                 }
762                 opt_cmd[buf_size++] = ' ';
763                 buf_size = sq_quote(opt_cmd, buf_size, opt);
764                 if (buf_size >= sizeof(opt_cmd))
765                         die("command too long");
766         }
768         opt_cmd[buf_size] = 0;
770         return request;
774 /*
775  * Line-oriented content detection.
776  */
778 #define LINE_INFO \
779 LINE(DIFF_HEADER,  "diff --git ",       COLOR_YELLOW,   COLOR_DEFAULT,  0), \
780 LINE(DIFF_CHUNK,   "@@",                COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
781 LINE(DIFF_ADD,     "+",                 COLOR_GREEN,    COLOR_DEFAULT,  0), \
782 LINE(DIFF_DEL,     "-",                 COLOR_RED,      COLOR_DEFAULT,  0), \
783 LINE(DIFF_INDEX,        "index ",         COLOR_BLUE,   COLOR_DEFAULT,  0), \
784 LINE(DIFF_OLDMODE,      "old file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
785 LINE(DIFF_NEWMODE,      "new file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
786 LINE(DIFF_COPY_FROM,    "copy from",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
787 LINE(DIFF_COPY_TO,      "copy to",        COLOR_YELLOW, COLOR_DEFAULT,  0), \
788 LINE(DIFF_RENAME_FROM,  "rename from",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
789 LINE(DIFF_RENAME_TO,    "rename to",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
790 LINE(DIFF_SIMILARITY,   "similarity ",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
791 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
792 LINE(DIFF_TREE,         "diff-tree ",     COLOR_BLUE,   COLOR_DEFAULT,  0), \
793 LINE(PP_AUTHOR,    "Author: ",          COLOR_CYAN,     COLOR_DEFAULT,  0), \
794 LINE(PP_COMMIT,    "Commit: ",          COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
795 LINE(PP_MERGE,     "Merge: ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
796 LINE(PP_DATE,      "Date:   ",          COLOR_YELLOW,   COLOR_DEFAULT,  0), \
797 LINE(PP_ADATE,     "AuthorDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
798 LINE(PP_CDATE,     "CommitDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
799 LINE(PP_REFS,      "Refs: ",            COLOR_RED,      COLOR_DEFAULT,  0), \
800 LINE(COMMIT,       "commit ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
801 LINE(PARENT,       "parent ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
802 LINE(TREE,         "tree ",             COLOR_BLUE,     COLOR_DEFAULT,  0), \
803 LINE(AUTHOR,       "author ",           COLOR_CYAN,     COLOR_DEFAULT,  0), \
804 LINE(COMMITTER,    "committer ",        COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
805 LINE(SIGNOFF,      "    Signed-off-by", COLOR_YELLOW,   COLOR_DEFAULT,  0), \
806 LINE(ACKED,        "    Acked-by",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
807 LINE(DEFAULT,      "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
808 LINE(CURSOR,       "",                  COLOR_WHITE,    COLOR_GREEN,    A_BOLD), \
809 LINE(STATUS,       "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
810 LINE(DELIMITER,    "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
811 LINE(DATE,         "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
812 LINE(LINE_NUMBER,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
813 LINE(TITLE_BLUR,   "",                  COLOR_WHITE,    COLOR_BLUE,     0), \
814 LINE(TITLE_FOCUS,  "",                  COLOR_WHITE,    COLOR_BLUE,     A_BOLD), \
815 LINE(MAIN_AUTHOR,  "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
816 LINE(MAIN_COMMIT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
817 LINE(MAIN_TAG,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
818 LINE(MAIN_LOCAL_TAG,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
819 LINE(MAIN_REMOTE,  "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
820 LINE(MAIN_TRACKED, "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_BOLD), \
821 LINE(MAIN_REF,     "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
822 LINE(MAIN_HEAD,    "",                  COLOR_CYAN,     COLOR_DEFAULT,  A_BOLD), \
823 LINE(MAIN_REVGRAPH,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
824 LINE(TREE_DIR,     "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
825 LINE(TREE_FILE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
826 LINE(STAT_HEAD,    "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
827 LINE(STAT_SECTION, "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
828 LINE(STAT_NONE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
829 LINE(STAT_STAGED,  "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
830 LINE(STAT_UNSTAGED,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
831 LINE(STAT_UNTRACKED,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
832 LINE(BLAME_ID,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0)
834 enum line_type {
835 #define LINE(type, line, fg, bg, attr) \
836         LINE_##type
837         LINE_INFO,
838         LINE_NONE
839 #undef  LINE
840 };
842 struct line_info {
843         const char *name;       /* Option name. */
844         int namelen;            /* Size of option name. */
845         const char *line;       /* The start of line to match. */
846         int linelen;            /* Size of string to match. */
847         int fg, bg, attr;       /* Color and text attributes for the lines. */
848 };
850 static struct line_info line_info[] = {
851 #define LINE(type, line, fg, bg, attr) \
852         { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
853         LINE_INFO
854 #undef  LINE
855 };
857 static enum line_type
858 get_line_type(const char *line)
860         int linelen = strlen(line);
861         enum line_type type;
863         for (type = 0; type < ARRAY_SIZE(line_info); type++)
864                 /* Case insensitive search matches Signed-off-by lines better. */
865                 if (linelen >= line_info[type].linelen &&
866                     !strncasecmp(line_info[type].line, line, line_info[type].linelen))
867                         return type;
869         return LINE_DEFAULT;
872 static inline int
873 get_line_attr(enum line_type type)
875         assert(type < ARRAY_SIZE(line_info));
876         return COLOR_PAIR(type) | line_info[type].attr;
879 static struct line_info *
880 get_line_info(const char *name)
882         size_t namelen = strlen(name);
883         enum line_type type;
885         for (type = 0; type < ARRAY_SIZE(line_info); type++)
886                 if (namelen == line_info[type].namelen &&
887                     !string_enum_compare(line_info[type].name, name, namelen))
888                         return &line_info[type];
890         return NULL;
893 static void
894 init_colors(void)
896         int default_bg = line_info[LINE_DEFAULT].bg;
897         int default_fg = line_info[LINE_DEFAULT].fg;
898         enum line_type type;
900         start_color();
902         if (assume_default_colors(default_fg, default_bg) == ERR) {
903                 default_bg = COLOR_BLACK;
904                 default_fg = COLOR_WHITE;
905         }
907         for (type = 0; type < ARRAY_SIZE(line_info); type++) {
908                 struct line_info *info = &line_info[type];
909                 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
910                 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
912                 init_pair(type, fg, bg);
913         }
916 struct line {
917         enum line_type type;
919         /* State flags */
920         unsigned int selected:1;
921         unsigned int dirty:1;
923         void *data;             /* User data */
924 };
927 /*
928  * Keys
929  */
931 struct keybinding {
932         int alias;
933         enum request request;
934 };
936 static struct keybinding default_keybindings[] = {
937         /* View switching */
938         { 'm',          REQ_VIEW_MAIN },
939         { 'd',          REQ_VIEW_DIFF },
940         { 'l',          REQ_VIEW_LOG },
941         { 't',          REQ_VIEW_TREE },
942         { 'f',          REQ_VIEW_BLOB },
943         { 'B',          REQ_VIEW_BLAME },
944         { 'p',          REQ_VIEW_PAGER },
945         { 'h',          REQ_VIEW_HELP },
946         { 'S',          REQ_VIEW_STATUS },
947         { 'c',          REQ_VIEW_STAGE },
949         /* View manipulation */
950         { 'q',          REQ_VIEW_CLOSE },
951         { KEY_TAB,      REQ_VIEW_NEXT },
952         { KEY_RETURN,   REQ_ENTER },
953         { KEY_UP,       REQ_PREVIOUS },
954         { KEY_DOWN,     REQ_NEXT },
955         { 'R',          REQ_REFRESH },
956         { KEY_F(5),     REQ_REFRESH },
957         { 'O',          REQ_MAXIMIZE },
959         /* Cursor navigation */
960         { 'k',          REQ_MOVE_UP },
961         { 'j',          REQ_MOVE_DOWN },
962         { KEY_HOME,     REQ_MOVE_FIRST_LINE },
963         { KEY_END,      REQ_MOVE_LAST_LINE },
964         { KEY_NPAGE,    REQ_MOVE_PAGE_DOWN },
965         { ' ',          REQ_MOVE_PAGE_DOWN },
966         { KEY_PPAGE,    REQ_MOVE_PAGE_UP },
967         { 'b',          REQ_MOVE_PAGE_UP },
968         { '-',          REQ_MOVE_PAGE_UP },
970         /* Scrolling */
971         { KEY_IC,       REQ_SCROLL_LINE_UP },
972         { KEY_DC,       REQ_SCROLL_LINE_DOWN },
973         { 'w',          REQ_SCROLL_PAGE_UP },
974         { 's',          REQ_SCROLL_PAGE_DOWN },
976         /* Searching */
977         { '/',          REQ_SEARCH },
978         { '?',          REQ_SEARCH_BACK },
979         { 'n',          REQ_FIND_NEXT },
980         { 'N',          REQ_FIND_PREV },
982         /* Misc */
983         { 'Q',          REQ_QUIT },
984         { 'z',          REQ_STOP_LOADING },
985         { 'v',          REQ_SHOW_VERSION },
986         { 'r',          REQ_SCREEN_REDRAW },
987         { '.',          REQ_TOGGLE_LINENO },
988         { 'D',          REQ_TOGGLE_DATE },
989         { 'A',          REQ_TOGGLE_AUTHOR },
990         { 'g',          REQ_TOGGLE_REV_GRAPH },
991         { 'F',          REQ_TOGGLE_REFS },
992         { ':',          REQ_PROMPT },
993         { 'u',          REQ_STATUS_UPDATE },
994         { '!',          REQ_STATUS_REVERT },
995         { 'M',          REQ_STATUS_MERGE },
996         { '@',          REQ_STAGE_NEXT },
997         { ',',          REQ_TREE_PARENT },
998         { 'e',          REQ_EDIT },
1000         /* Using the ncurses SIGWINCH handler. */
1001         { KEY_RESIZE,   REQ_SCREEN_RESIZE },
1002 };
1004 #define KEYMAP_INFO \
1005         KEYMAP_(GENERIC), \
1006         KEYMAP_(MAIN), \
1007         KEYMAP_(DIFF), \
1008         KEYMAP_(LOG), \
1009         KEYMAP_(TREE), \
1010         KEYMAP_(BLOB), \
1011         KEYMAP_(BLAME), \
1012         KEYMAP_(PAGER), \
1013         KEYMAP_(HELP), \
1014         KEYMAP_(STATUS), \
1015         KEYMAP_(STAGE)
1017 enum keymap {
1018 #define KEYMAP_(name) KEYMAP_##name
1019         KEYMAP_INFO
1020 #undef  KEYMAP_
1021 };
1023 static struct int_map keymap_table[] = {
1024 #define KEYMAP_(name) { #name, STRING_SIZE(#name), KEYMAP_##name }
1025         KEYMAP_INFO
1026 #undef  KEYMAP_
1027 };
1029 #define set_keymap(map, name) \
1030         set_from_int_map(keymap_table, ARRAY_SIZE(keymap_table), map, name, strlen(name))
1032 struct keybinding_table {
1033         struct keybinding *data;
1034         size_t size;
1035 };
1037 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1039 static void
1040 add_keybinding(enum keymap keymap, enum request request, int key)
1042         struct keybinding_table *table = &keybindings[keymap];
1044         table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1045         if (!table->data)
1046                 die("Failed to allocate keybinding");
1047         table->data[table->size].alias = key;
1048         table->data[table->size++].request = request;
1051 /* Looks for a key binding first in the given map, then in the generic map, and
1052  * lastly in the default keybindings. */
1053 static enum request
1054 get_keybinding(enum keymap keymap, int key)
1056         size_t i;
1058         for (i = 0; i < keybindings[keymap].size; i++)
1059                 if (keybindings[keymap].data[i].alias == key)
1060                         return keybindings[keymap].data[i].request;
1062         for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1063                 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1064                         return keybindings[KEYMAP_GENERIC].data[i].request;
1066         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1067                 if (default_keybindings[i].alias == key)
1068                         return default_keybindings[i].request;
1070         return (enum request) key;
1074 struct key {
1075         const char *name;
1076         int value;
1077 };
1079 static struct key key_table[] = {
1080         { "Enter",      KEY_RETURN },
1081         { "Space",      ' ' },
1082         { "Backspace",  KEY_BACKSPACE },
1083         { "Tab",        KEY_TAB },
1084         { "Escape",     KEY_ESC },
1085         { "Left",       KEY_LEFT },
1086         { "Right",      KEY_RIGHT },
1087         { "Up",         KEY_UP },
1088         { "Down",       KEY_DOWN },
1089         { "Insert",     KEY_IC },
1090         { "Delete",     KEY_DC },
1091         { "Hash",       '#' },
1092         { "Home",       KEY_HOME },
1093         { "End",        KEY_END },
1094         { "PageUp",     KEY_PPAGE },
1095         { "PageDown",   KEY_NPAGE },
1096         { "F1",         KEY_F(1) },
1097         { "F2",         KEY_F(2) },
1098         { "F3",         KEY_F(3) },
1099         { "F4",         KEY_F(4) },
1100         { "F5",         KEY_F(5) },
1101         { "F6",         KEY_F(6) },
1102         { "F7",         KEY_F(7) },
1103         { "F8",         KEY_F(8) },
1104         { "F9",         KEY_F(9) },
1105         { "F10",        KEY_F(10) },
1106         { "F11",        KEY_F(11) },
1107         { "F12",        KEY_F(12) },
1108 };
1110 static int
1111 get_key_value(const char *name)
1113         int i;
1115         for (i = 0; i < ARRAY_SIZE(key_table); i++)
1116                 if (!strcasecmp(key_table[i].name, name))
1117                         return key_table[i].value;
1119         if (strlen(name) == 1 && isprint(*name))
1120                 return (int) *name;
1122         return ERR;
1125 static const char *
1126 get_key_name(int key_value)
1128         static char key_char[] = "'X'";
1129         const char *seq = NULL;
1130         int key;
1132         for (key = 0; key < ARRAY_SIZE(key_table); key++)
1133                 if (key_table[key].value == key_value)
1134                         seq = key_table[key].name;
1136         if (seq == NULL &&
1137             key_value < 127 &&
1138             isprint(key_value)) {
1139                 key_char[1] = (char) key_value;
1140                 seq = key_char;
1141         }
1143         return seq ? seq : "(no key)";
1146 static const char *
1147 get_key(enum request request)
1149         static char buf[BUFSIZ];
1150         size_t pos = 0;
1151         char *sep = "";
1152         int i;
1154         buf[pos] = 0;
1156         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1157                 struct keybinding *keybinding = &default_keybindings[i];
1159                 if (keybinding->request != request)
1160                         continue;
1162                 if (!string_format_from(buf, &pos, "%s%s", sep,
1163                                         get_key_name(keybinding->alias)))
1164                         return "Too many keybindings!";
1165                 sep = ", ";
1166         }
1168         return buf;
1171 struct run_request {
1172         enum keymap keymap;
1173         int key;
1174         const char *argv[SIZEOF_ARG];
1175 };
1177 static struct run_request *run_request;
1178 static size_t run_requests;
1180 static enum request
1181 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1183         struct run_request *req;
1185         if (argc >= ARRAY_SIZE(req->argv) - 1)
1186                 return REQ_NONE;
1188         req = realloc(run_request, (run_requests + 1) * sizeof(*run_request));
1189         if (!req)
1190                 return REQ_NONE;
1192         run_request = req;
1193         req = &run_request[run_requests];
1194         req->keymap = keymap;
1195         req->key = key;
1196         req->argv[0] = NULL;
1198         if (!format_argv(req->argv, argv, FORMAT_NONE))
1199                 return REQ_NONE;
1201         return REQ_NONE + ++run_requests;
1204 static struct run_request *
1205 get_run_request(enum request request)
1207         if (request <= REQ_NONE)
1208                 return NULL;
1209         return &run_request[request - REQ_NONE - 1];
1212 static void
1213 add_builtin_run_requests(void)
1215         const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1216         const char *gc[] = { "git", "gc", NULL };
1217         struct {
1218                 enum keymap keymap;
1219                 int key;
1220                 int argc;
1221                 const char **argv;
1222         } reqs[] = {
1223                 { KEYMAP_MAIN,    'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1224                 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1225         };
1226         int i;
1228         for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1229                 enum request req;
1231                 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1232                 if (req != REQ_NONE)
1233                         add_keybinding(reqs[i].keymap, req, reqs[i].key);
1234         }
1237 /*
1238  * User config file handling.
1239  */
1241 static struct int_map color_map[] = {
1242 #define COLOR_MAP(name) { #name, STRING_SIZE(#name), COLOR_##name }
1243         COLOR_MAP(DEFAULT),
1244         COLOR_MAP(BLACK),
1245         COLOR_MAP(BLUE),
1246         COLOR_MAP(CYAN),
1247         COLOR_MAP(GREEN),
1248         COLOR_MAP(MAGENTA),
1249         COLOR_MAP(RED),
1250         COLOR_MAP(WHITE),
1251         COLOR_MAP(YELLOW),
1252 };
1254 #define set_color(color, name) \
1255         set_from_int_map(color_map, ARRAY_SIZE(color_map), color, name, strlen(name))
1257 static struct int_map attr_map[] = {
1258 #define ATTR_MAP(name) { #name, STRING_SIZE(#name), A_##name }
1259         ATTR_MAP(NORMAL),
1260         ATTR_MAP(BLINK),
1261         ATTR_MAP(BOLD),
1262         ATTR_MAP(DIM),
1263         ATTR_MAP(REVERSE),
1264         ATTR_MAP(STANDOUT),
1265         ATTR_MAP(UNDERLINE),
1266 };
1268 #define set_attribute(attr, name) \
1269         set_from_int_map(attr_map, ARRAY_SIZE(attr_map), attr, name, strlen(name))
1271 static int   config_lineno;
1272 static bool  config_errors;
1273 static const char *config_msg;
1275 /* Wants: object fgcolor bgcolor [attr] */
1276 static int
1277 option_color_command(int argc, const char *argv[])
1279         struct line_info *info;
1281         if (argc != 3 && argc != 4) {
1282                 config_msg = "Wrong number of arguments given to color command";
1283                 return ERR;
1284         }
1286         info = get_line_info(argv[0]);
1287         if (!info) {
1288                 if (!string_enum_compare(argv[0], "main-delim", strlen("main-delim"))) {
1289                         info = get_line_info("delimiter");
1291                 } else if (!string_enum_compare(argv[0], "main-date", strlen("main-date"))) {
1292                         info = get_line_info("date");
1294                 } else {
1295                         config_msg = "Unknown color name";
1296                         return ERR;
1297                 }
1298         }
1300         if (set_color(&info->fg, argv[1]) == ERR ||
1301             set_color(&info->bg, argv[2]) == ERR) {
1302                 config_msg = "Unknown color";
1303                 return ERR;
1304         }
1306         if (argc == 4 && set_attribute(&info->attr, argv[3]) == ERR) {
1307                 config_msg = "Unknown attribute";
1308                 return ERR;
1309         }
1311         return OK;
1314 static bool parse_bool(const char *s)
1316         return (!strcmp(s, "1") || !strcmp(s, "true") ||
1317                 !strcmp(s, "yes")) ? TRUE : FALSE;
1320 static int
1321 parse_int(const char *s, int default_value, int min, int max)
1323         int value = atoi(s);
1325         return (value < min || value > max) ? default_value : value;
1328 /* Wants: name = value */
1329 static int
1330 option_set_command(int argc, const char *argv[])
1332         if (argc != 3) {
1333                 config_msg = "Wrong number of arguments given to set command";
1334                 return ERR;
1335         }
1337         if (strcmp(argv[1], "=")) {
1338                 config_msg = "No value assigned";
1339                 return ERR;
1340         }
1342         if (!strcmp(argv[0], "show-author")) {
1343                 opt_author = parse_bool(argv[2]);
1344                 return OK;
1345         }
1347         if (!strcmp(argv[0], "show-date")) {
1348                 opt_date = parse_bool(argv[2]);
1349                 return OK;
1350         }
1352         if (!strcmp(argv[0], "show-rev-graph")) {
1353                 opt_rev_graph = parse_bool(argv[2]);
1354                 return OK;
1355         }
1357         if (!strcmp(argv[0], "show-refs")) {
1358                 opt_show_refs = parse_bool(argv[2]);
1359                 return OK;
1360         }
1362         if (!strcmp(argv[0], "show-line-numbers")) {
1363                 opt_line_number = parse_bool(argv[2]);
1364                 return OK;
1365         }
1367         if (!strcmp(argv[0], "line-graphics")) {
1368                 opt_line_graphics = parse_bool(argv[2]);
1369                 return OK;
1370         }
1372         if (!strcmp(argv[0], "line-number-interval")) {
1373                 opt_num_interval = parse_int(argv[2], opt_num_interval, 1, 1024);
1374                 return OK;
1375         }
1377         if (!strcmp(argv[0], "author-width")) {
1378                 opt_author_cols = parse_int(argv[2], opt_author_cols, 0, 1024);
1379                 return OK;
1380         }
1382         if (!strcmp(argv[0], "tab-size")) {
1383                 opt_tab_size = parse_int(argv[2], opt_tab_size, 1, 1024);
1384                 return OK;
1385         }
1387         if (!strcmp(argv[0], "commit-encoding")) {
1388                 const char *arg = argv[2];
1389                 int arglen = strlen(arg);
1391                 switch (arg[0]) {
1392                 case '"':
1393                 case '\'':
1394                         if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1395                                 config_msg = "Unmatched quotation";
1396                                 return ERR;
1397                         }
1398                         arg += 1; arglen -= 2;
1399                 default:
1400                         string_ncopy(opt_encoding, arg, strlen(arg));
1401                         return OK;
1402                 }
1403         }
1405         config_msg = "Unknown variable name";
1406         return ERR;
1409 /* Wants: mode request key */
1410 static int
1411 option_bind_command(int argc, const char *argv[])
1413         enum request request;
1414         int keymap;
1415         int key;
1417         if (argc < 3) {
1418                 config_msg = "Wrong number of arguments given to bind command";
1419                 return ERR;
1420         }
1422         if (set_keymap(&keymap, argv[0]) == ERR) {
1423                 config_msg = "Unknown key map";
1424                 return ERR;
1425         }
1427         key = get_key_value(argv[1]);
1428         if (key == ERR) {
1429                 config_msg = "Unknown key";
1430                 return ERR;
1431         }
1433         request = get_request(argv[2]);
1434         if (request == REQ_NONE) {
1435                 const char *obsolete[] = { "cherry-pick" };
1436                 size_t namelen = strlen(argv[2]);
1437                 int i;
1439                 for (i = 0; i < ARRAY_SIZE(obsolete); i++) {
1440                         if (namelen == strlen(obsolete[i]) &&
1441                             !string_enum_compare(obsolete[i], argv[2], namelen)) {
1442                                 config_msg = "Obsolete request name";
1443                                 return ERR;
1444                         }
1445                 }
1446         }
1447         if (request == REQ_NONE && *argv[2]++ == '!')
1448                 request = add_run_request(keymap, key, argc - 2, argv + 2);
1449         if (request == REQ_NONE) {
1450                 config_msg = "Unknown request name";
1451                 return ERR;
1452         }
1454         add_keybinding(keymap, request, key);
1456         return OK;
1459 static int
1460 set_option(const char *opt, char *value)
1462         const char *argv[SIZEOF_ARG];
1463         int argc = 0;
1465         if (!argv_from_string(argv, &argc, value)) {
1466                 config_msg = "Too many option arguments";
1467                 return ERR;
1468         }
1470         if (!strcmp(opt, "color"))
1471                 return option_color_command(argc, argv);
1473         if (!strcmp(opt, "set"))
1474                 return option_set_command(argc, argv);
1476         if (!strcmp(opt, "bind"))
1477                 return option_bind_command(argc, argv);
1479         config_msg = "Unknown option command";
1480         return ERR;
1483 static int
1484 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1486         int status = OK;
1488         config_lineno++;
1489         config_msg = "Internal error";
1491         /* Check for comment markers, since read_properties() will
1492          * only ensure opt and value are split at first " \t". */
1493         optlen = strcspn(opt, "#");
1494         if (optlen == 0)
1495                 return OK;
1497         if (opt[optlen] != 0) {
1498                 config_msg = "No option value";
1499                 status = ERR;
1501         }  else {
1502                 /* Look for comment endings in the value. */
1503                 size_t len = strcspn(value, "#");
1505                 if (len < valuelen) {
1506                         valuelen = len;
1507                         value[valuelen] = 0;
1508                 }
1510                 status = set_option(opt, value);
1511         }
1513         if (status == ERR) {
1514                 fprintf(stderr, "Error on line %d, near '%.*s': %s\n",
1515                         config_lineno, (int) optlen, opt, config_msg);
1516                 config_errors = TRUE;
1517         }
1519         /* Always keep going if errors are encountered. */
1520         return OK;
1523 static void
1524 load_option_file(const char *path)
1526         FILE *file;
1528         /* It's ok that the file doesn't exist. */
1529         file = fopen(path, "r");
1530         if (!file)
1531                 return;
1533         config_lineno = 0;
1534         config_errors = FALSE;
1536         if (read_properties(file, " \t", read_option) == ERR ||
1537             config_errors == TRUE)
1538                 fprintf(stderr, "Errors while loading %s.\n", path);
1541 static int
1542 load_options(void)
1544         const char *home = getenv("HOME");
1545         const char *tigrc_user = getenv("TIGRC_USER");
1546         const char *tigrc_system = getenv("TIGRC_SYSTEM");
1547         char buf[SIZEOF_STR];
1549         add_builtin_run_requests();
1551         if (!tigrc_system) {
1552                 if (!string_format(buf, "%s/tigrc", SYSCONFDIR))
1553                         return ERR;
1554                 tigrc_system = buf;
1555         }
1556         load_option_file(tigrc_system);
1558         if (!tigrc_user) {
1559                 if (!home || !string_format(buf, "%s/.tigrc", home))
1560                         return ERR;
1561                 tigrc_user = buf;
1562         }
1563         load_option_file(tigrc_user);
1565         return OK;
1569 /*
1570  * The viewer
1571  */
1573 struct view;
1574 struct view_ops;
1576 /* The display array of active views and the index of the current view. */
1577 static struct view *display[2];
1578 static unsigned int current_view;
1580 /* Reading from the prompt? */
1581 static bool input_mode = FALSE;
1583 #define foreach_displayed_view(view, i) \
1584         for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1586 #define displayed_views()       (display[1] != NULL ? 2 : 1)
1588 /* Current head and commit ID */
1589 static char ref_blob[SIZEOF_REF]        = "";
1590 static char ref_commit[SIZEOF_REF]      = "HEAD";
1591 static char ref_head[SIZEOF_REF]        = "HEAD";
1593 struct view {
1594         const char *name;       /* View name */
1595         const char *cmd_env;    /* Command line set via environment */
1596         const char *id;         /* Points to either of ref_{head,commit,blob} */
1598         struct view_ops *ops;   /* View operations */
1600         enum keymap keymap;     /* What keymap does this view have */
1601         bool git_dir;           /* Whether the view requires a git directory. */
1603         char ref[SIZEOF_REF];   /* Hovered commit reference */
1604         char vid[SIZEOF_REF];   /* View ID. Set to id member when updating. */
1606         int height, width;      /* The width and height of the main window */
1607         WINDOW *win;            /* The main window */
1608         WINDOW *title;          /* The title window living below the main window */
1610         /* Navigation */
1611         unsigned long offset;   /* Offset of the window top */
1612         unsigned long lineno;   /* Current line number */
1614         /* Searching */
1615         char grep[SIZEOF_STR];  /* Search string */
1616         regex_t *regex;         /* Pre-compiled regex */
1618         /* If non-NULL, points to the view that opened this view. If this view
1619          * is closed tig will switch back to the parent view. */
1620         struct view *parent;
1622         /* Buffering */
1623         size_t lines;           /* Total number of lines */
1624         struct line *line;      /* Line index */
1625         size_t line_alloc;      /* Total number of allocated lines */
1626         size_t line_size;       /* Total number of used lines */
1627         unsigned int digits;    /* Number of digits in the lines member. */
1629         /* Drawing */
1630         struct line *curline;   /* Line currently being drawn. */
1631         enum line_type curtype; /* Attribute currently used for drawing. */
1632         unsigned long col;      /* Column when drawing. */
1634         /* Loading */
1635         struct io io;
1636         struct io *pipe;
1637         time_t start_time;
1638 };
1640 struct view_ops {
1641         /* What type of content being displayed. Used in the title bar. */
1642         const char *type;
1643         /* Default command arguments. */
1644         const char **argv;
1645         /* Open and reads in all view content. */
1646         bool (*open)(struct view *view);
1647         /* Read one line; updates view->line. */
1648         bool (*read)(struct view *view, char *data);
1649         /* Draw one line; @lineno must be < view->height. */
1650         bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
1651         /* Depending on view handle a special requests. */
1652         enum request (*request)(struct view *view, enum request request, struct line *line);
1653         /* Search for regex in a line. */
1654         bool (*grep)(struct view *view, struct line *line);
1655         /* Select line */
1656         void (*select)(struct view *view, struct line *line);
1657 };
1659 static struct view_ops blame_ops;
1660 static struct view_ops blob_ops;
1661 static struct view_ops diff_ops;
1662 static struct view_ops help_ops;
1663 static struct view_ops log_ops;
1664 static struct view_ops main_ops;
1665 static struct view_ops pager_ops;
1666 static struct view_ops stage_ops;
1667 static struct view_ops status_ops;
1668 static struct view_ops tree_ops;
1670 #define VIEW_STR(name, env, ref, ops, map, git) \
1671         { name, #env, ref, ops, map, git }
1673 #define VIEW_(id, name, ops, git, ref) \
1674         VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
1677 static struct view views[] = {
1678         VIEW_(MAIN,   "main",   &main_ops,   TRUE,  ref_head),
1679         VIEW_(DIFF,   "diff",   &diff_ops,   TRUE,  ref_commit),
1680         VIEW_(LOG,    "log",    &log_ops,    TRUE,  ref_head),
1681         VIEW_(TREE,   "tree",   &tree_ops,   TRUE,  ref_commit),
1682         VIEW_(BLOB,   "blob",   &blob_ops,   TRUE,  ref_blob),
1683         VIEW_(BLAME,  "blame",  &blame_ops,  TRUE,  ref_commit),
1684         VIEW_(HELP,   "help",   &help_ops,   FALSE, ""),
1685         VIEW_(PAGER,  "pager",  &pager_ops,  FALSE, "stdin"),
1686         VIEW_(STATUS, "status", &status_ops, TRUE,  ""),
1687         VIEW_(STAGE,  "stage",  &stage_ops,  TRUE,  ""),
1688 };
1690 #define VIEW(req)       (&views[(req) - REQ_OFFSET - 1])
1691 #define VIEW_REQ(view)  ((view) - views + REQ_OFFSET + 1)
1693 #define foreach_view(view, i) \
1694         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1696 #define view_is_displayed(view) \
1697         (view == display[0] || view == display[1])
1700 enum line_graphic {
1701         LINE_GRAPHIC_VLINE
1702 };
1704 static int line_graphics[] = {
1705         /* LINE_GRAPHIC_VLINE: */ '|'
1706 };
1708 static inline void
1709 set_view_attr(struct view *view, enum line_type type)
1711         if (!view->curline->selected && view->curtype != type) {
1712                 wattrset(view->win, get_line_attr(type));
1713                 wchgat(view->win, -1, 0, type, NULL);
1714                 view->curtype = type;
1715         }
1718 static int
1719 draw_chars(struct view *view, enum line_type type, const char *string,
1720            int max_len, bool use_tilde)
1722         int len = 0;
1723         int col = 0;
1724         int trimmed = FALSE;
1726         if (max_len <= 0)
1727                 return 0;
1729         if (opt_utf8) {
1730                 len = utf8_length(string, &col, max_len, &trimmed, use_tilde);
1731         } else {
1732                 col = len = strlen(string);
1733                 if (len > max_len) {
1734                         if (use_tilde) {
1735                                 max_len -= 1;
1736                         }
1737                         col = len = max_len;
1738                         trimmed = TRUE;
1739                 }
1740         }
1742         set_view_attr(view, type);
1743         waddnstr(view->win, string, len);
1744         if (trimmed && use_tilde) {
1745                 set_view_attr(view, LINE_DELIMITER);
1746                 waddch(view->win, '~');
1747                 col++;
1748         }
1750         return col;
1753 static int
1754 draw_space(struct view *view, enum line_type type, int max, int spaces)
1756         static char space[] = "                    ";
1757         int col = 0;
1759         spaces = MIN(max, spaces);
1761         while (spaces > 0) {
1762                 int len = MIN(spaces, sizeof(space) - 1);
1764                 col += draw_chars(view, type, space, spaces, FALSE);
1765                 spaces -= len;
1766         }
1768         return col;
1771 static bool
1772 draw_lineno(struct view *view, unsigned int lineno)
1774         char number[10];
1775         int digits3 = view->digits < 3 ? 3 : view->digits;
1776         int max_number = MIN(digits3, STRING_SIZE(number));
1777         int max = view->width - view->col;
1778         int col;
1780         if (max < max_number)
1781                 max_number = max;
1783         lineno += view->offset + 1;
1784         if (lineno == 1 || (lineno % opt_num_interval) == 0) {
1785                 static char fmt[] = "%1ld";
1787                 if (view->digits <= 9)
1788                         fmt[1] = '0' + digits3;
1790                 if (!string_format(number, fmt, lineno))
1791                         number[0] = 0;
1792                 col = draw_chars(view, LINE_LINE_NUMBER, number, max_number, TRUE);
1793         } else {
1794                 col = draw_space(view, LINE_LINE_NUMBER, max_number, max_number);
1795         }
1797         if (col < max) {
1798                 set_view_attr(view, LINE_DEFAULT);
1799                 waddch(view->win, line_graphics[LINE_GRAPHIC_VLINE]);
1800                 col++;
1801         }
1803         if (col < max)
1804                 col += draw_space(view, LINE_DEFAULT, max - col, 1);
1805         view->col += col;
1807         return view->width - view->col <= 0;
1810 static bool
1811 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
1813         view->col += draw_chars(view, type, string, view->width - view->col, trim);
1814         return view->width - view->col <= 0;
1817 static bool
1818 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
1820         int max = view->width - view->col;
1821         int i;
1823         if (max < size)
1824                 size = max;
1826         set_view_attr(view, type);
1827         /* Using waddch() instead of waddnstr() ensures that
1828          * they'll be rendered correctly for the cursor line. */
1829         for (i = 0; i < size; i++)
1830                 waddch(view->win, graphic[i]);
1832         view->col += size;
1833         if (size < max) {
1834                 waddch(view->win, ' ');
1835                 view->col++;
1836         }
1838         return view->width - view->col <= 0;
1841 static bool
1842 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
1844         int max = MIN(view->width - view->col, len);
1845         int col;
1847         if (text)
1848                 col = draw_chars(view, type, text, max - 1, trim);
1849         else
1850                 col = draw_space(view, type, max - 1, max - 1);
1852         view->col += col + draw_space(view, LINE_DEFAULT, max - col, max - col);
1853         return view->width - view->col <= 0;
1856 static bool
1857 draw_date(struct view *view, struct tm *time)
1859         char buf[DATE_COLS];
1860         char *date;
1861         int timelen = 0;
1863         if (time)
1864                 timelen = strftime(buf, sizeof(buf), DATE_FORMAT, time);
1865         date = timelen ? buf : NULL;
1867         return draw_field(view, LINE_DATE, date, DATE_COLS, FALSE);
1870 static bool
1871 draw_view_line(struct view *view, unsigned int lineno)
1873         struct line *line;
1874         bool selected = (view->offset + lineno == view->lineno);
1875         bool draw_ok;
1877         assert(view_is_displayed(view));
1879         if (view->offset + lineno >= view->lines)
1880                 return FALSE;
1882         line = &view->line[view->offset + lineno];
1884         wmove(view->win, lineno, 0);
1885         view->col = 0;
1886         view->curline = line;
1887         view->curtype = LINE_NONE;
1888         line->selected = FALSE;
1890         if (selected) {
1891                 set_view_attr(view, LINE_CURSOR);
1892                 line->selected = TRUE;
1893                 view->ops->select(view, line);
1894         } else if (line->selected) {
1895                 wclrtoeol(view->win);
1896         }
1898         scrollok(view->win, FALSE);
1899         draw_ok = view->ops->draw(view, line, lineno);
1900         scrollok(view->win, TRUE);
1902         return draw_ok;
1905 static void
1906 redraw_view_dirty(struct view *view)
1908         bool dirty = FALSE;
1909         int lineno;
1911         for (lineno = 0; lineno < view->height; lineno++) {
1912                 struct line *line = &view->line[view->offset + lineno];
1914                 if (!line->dirty)
1915                         continue;
1916                 line->dirty = 0;
1917                 dirty = TRUE;
1918                 if (!draw_view_line(view, lineno))
1919                         break;
1920         }
1922         if (!dirty)
1923                 return;
1924         redrawwin(view->win);
1925         if (input_mode)
1926                 wnoutrefresh(view->win);
1927         else
1928                 wrefresh(view->win);
1931 static void
1932 redraw_view_from(struct view *view, int lineno)
1934         assert(0 <= lineno && lineno < view->height);
1936         for (; lineno < view->height; lineno++) {
1937                 if (!draw_view_line(view, lineno))
1938                         break;
1939         }
1941         redrawwin(view->win);
1942         if (input_mode)
1943                 wnoutrefresh(view->win);
1944         else
1945                 wrefresh(view->win);
1948 static void
1949 redraw_view(struct view *view)
1951         wclear(view->win);
1952         redraw_view_from(view, 0);
1956 static void
1957 update_view_title(struct view *view)
1959         char buf[SIZEOF_STR];
1960         char state[SIZEOF_STR];
1961         size_t bufpos = 0, statelen = 0;
1963         assert(view_is_displayed(view));
1965         if (view != VIEW(REQ_VIEW_STATUS) && (view->lines || view->pipe)) {
1966                 unsigned int view_lines = view->offset + view->height;
1967                 unsigned int lines = view->lines
1968                                    ? MIN(view_lines, view->lines) * 100 / view->lines
1969                                    : 0;
1971                 string_format_from(state, &statelen, "- %s %d of %d (%d%%)",
1972                                    view->ops->type,
1973                                    view->lineno + 1,
1974                                    view->lines,
1975                                    lines);
1977                 if (view->pipe) {
1978                         time_t secs = time(NULL) - view->start_time;
1980                         /* Three git seconds are a long time ... */
1981                         if (secs > 2)
1982                                 string_format_from(state, &statelen, " %lds", secs);
1983                 }
1984         }
1986         string_format_from(buf, &bufpos, "[%s]", view->name);
1987         if (*view->ref && bufpos < view->width) {
1988                 size_t refsize = strlen(view->ref);
1989                 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
1991                 if (minsize < view->width)
1992                         refsize = view->width - minsize + 7;
1993                 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
1994         }
1996         if (statelen && bufpos < view->width) {
1997                 string_format_from(buf, &bufpos, " %s", state);
1998         }
2000         if (view == display[current_view])
2001                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2002         else
2003                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2005         mvwaddnstr(view->title, 0, 0, buf, bufpos);
2006         wclrtoeol(view->title);
2007         wmove(view->title, 0, view->width - 1);
2009         if (input_mode)
2010                 wnoutrefresh(view->title);
2011         else
2012                 wrefresh(view->title);
2015 static void
2016 resize_display(void)
2018         int offset, i;
2019         struct view *base = display[0];
2020         struct view *view = display[1] ? display[1] : display[0];
2022         /* Setup window dimensions */
2024         getmaxyx(stdscr, base->height, base->width);
2026         /* Make room for the status window. */
2027         base->height -= 1;
2029         if (view != base) {
2030                 /* Horizontal split. */
2031                 view->width   = base->width;
2032                 view->height  = SCALE_SPLIT_VIEW(base->height);
2033                 base->height -= view->height;
2035                 /* Make room for the title bar. */
2036                 view->height -= 1;
2037         }
2039         /* Make room for the title bar. */
2040         base->height -= 1;
2042         offset = 0;
2044         foreach_displayed_view (view, i) {
2045                 if (!view->win) {
2046                         view->win = newwin(view->height, 0, offset, 0);
2047                         if (!view->win)
2048                                 die("Failed to create %s view", view->name);
2050                         scrollok(view->win, TRUE);
2052                         view->title = newwin(1, 0, offset + view->height, 0);
2053                         if (!view->title)
2054                                 die("Failed to create title window");
2056                 } else {
2057                         wresize(view->win, view->height, view->width);
2058                         mvwin(view->win,   offset, 0);
2059                         mvwin(view->title, offset + view->height, 0);
2060                 }
2062                 offset += view->height + 1;
2063         }
2066 static void
2067 redraw_display(void)
2069         struct view *view;
2070         int i;
2072         foreach_displayed_view (view, i) {
2073                 redraw_view(view);
2074                 update_view_title(view);
2075         }
2078 static void
2079 update_display_cursor(struct view *view)
2081         /* Move the cursor to the right-most column of the cursor line.
2082          *
2083          * XXX: This could turn out to be a bit expensive, but it ensures that
2084          * the cursor does not jump around. */
2085         if (view->lines) {
2086                 wmove(view->win, view->lineno - view->offset, view->width - 1);
2087                 wrefresh(view->win);
2088         }
2091 /*
2092  * Navigation
2093  */
2095 /* Scrolling backend */
2096 static void
2097 do_scroll_view(struct view *view, int lines)
2099         bool redraw_current_line = FALSE;
2101         /* The rendering expects the new offset. */
2102         view->offset += lines;
2104         assert(0 <= view->offset && view->offset < view->lines);
2105         assert(lines);
2107         /* Move current line into the view. */
2108         if (view->lineno < view->offset) {
2109                 view->lineno = view->offset;
2110                 redraw_current_line = TRUE;
2111         } else if (view->lineno >= view->offset + view->height) {
2112                 view->lineno = view->offset + view->height - 1;
2113                 redraw_current_line = TRUE;
2114         }
2116         assert(view->offset <= view->lineno && view->lineno < view->lines);
2118         /* Redraw the whole screen if scrolling is pointless. */
2119         if (view->height < ABS(lines)) {
2120                 redraw_view(view);
2122         } else {
2123                 int line = lines > 0 ? view->height - lines : 0;
2124                 int end = line + ABS(lines);
2126                 wscrl(view->win, lines);
2128                 for (; line < end; line++) {
2129                         if (!draw_view_line(view, line))
2130                                 break;
2131                 }
2133                 if (redraw_current_line)
2134                         draw_view_line(view, view->lineno - view->offset);
2135         }
2137         redrawwin(view->win);
2138         wrefresh(view->win);
2139         report("");
2142 /* Scroll frontend */
2143 static void
2144 scroll_view(struct view *view, enum request request)
2146         int lines = 1;
2148         assert(view_is_displayed(view));
2150         switch (request) {
2151         case REQ_SCROLL_PAGE_DOWN:
2152                 lines = view->height;
2153         case REQ_SCROLL_LINE_DOWN:
2154                 if (view->offset + lines > view->lines)
2155                         lines = view->lines - view->offset;
2157                 if (lines == 0 || view->offset + view->height >= view->lines) {
2158                         report("Cannot scroll beyond the last line");
2159                         return;
2160                 }
2161                 break;
2163         case REQ_SCROLL_PAGE_UP:
2164                 lines = view->height;
2165         case REQ_SCROLL_LINE_UP:
2166                 if (lines > view->offset)
2167                         lines = view->offset;
2169                 if (lines == 0) {
2170                         report("Cannot scroll beyond the first line");
2171                         return;
2172                 }
2174                 lines = -lines;
2175                 break;
2177         default:
2178                 die("request %d not handled in switch", request);
2179         }
2181         do_scroll_view(view, lines);
2184 /* Cursor moving */
2185 static void
2186 move_view(struct view *view, enum request request)
2188         int scroll_steps = 0;
2189         int steps;
2191         switch (request) {
2192         case REQ_MOVE_FIRST_LINE:
2193                 steps = -view->lineno;
2194                 break;
2196         case REQ_MOVE_LAST_LINE:
2197                 steps = view->lines - view->lineno - 1;
2198                 break;
2200         case REQ_MOVE_PAGE_UP:
2201                 steps = view->height > view->lineno
2202                       ? -view->lineno : -view->height;
2203                 break;
2205         case REQ_MOVE_PAGE_DOWN:
2206                 steps = view->lineno + view->height >= view->lines
2207                       ? view->lines - view->lineno - 1 : view->height;
2208                 break;
2210         case REQ_MOVE_UP:
2211                 steps = -1;
2212                 break;
2214         case REQ_MOVE_DOWN:
2215                 steps = 1;
2216                 break;
2218         default:
2219                 die("request %d not handled in switch", request);
2220         }
2222         if (steps <= 0 && view->lineno == 0) {
2223                 report("Cannot move beyond the first line");
2224                 return;
2226         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2227                 report("Cannot move beyond the last line");
2228                 return;
2229         }
2231         /* Move the current line */
2232         view->lineno += steps;
2233         assert(0 <= view->lineno && view->lineno < view->lines);
2235         /* Check whether the view needs to be scrolled */
2236         if (view->lineno < view->offset ||
2237             view->lineno >= view->offset + view->height) {
2238                 scroll_steps = steps;
2239                 if (steps < 0 && -steps > view->offset) {
2240                         scroll_steps = -view->offset;
2242                 } else if (steps > 0) {
2243                         if (view->lineno == view->lines - 1 &&
2244                             view->lines > view->height) {
2245                                 scroll_steps = view->lines - view->offset - 1;
2246                                 if (scroll_steps >= view->height)
2247                                         scroll_steps -= view->height - 1;
2248                         }
2249                 }
2250         }
2252         if (!view_is_displayed(view)) {
2253                 view->offset += scroll_steps;
2254                 assert(0 <= view->offset && view->offset < view->lines);
2255                 view->ops->select(view, &view->line[view->lineno]);
2256                 return;
2257         }
2259         /* Repaint the old "current" line if we be scrolling */
2260         if (ABS(steps) < view->height)
2261                 draw_view_line(view, view->lineno - steps - view->offset);
2263         if (scroll_steps) {
2264                 do_scroll_view(view, scroll_steps);
2265                 return;
2266         }
2268         /* Draw the current line */
2269         draw_view_line(view, view->lineno - view->offset);
2271         redrawwin(view->win);
2272         wrefresh(view->win);
2273         report("");
2277 /*
2278  * Searching
2279  */
2281 static void search_view(struct view *view, enum request request);
2283 static bool
2284 find_next_line(struct view *view, unsigned long lineno, struct line *line)
2286         assert(view_is_displayed(view));
2288         if (!view->ops->grep(view, line))
2289                 return FALSE;
2291         if (lineno - view->offset >= view->height) {
2292                 view->offset = lineno;
2293                 view->lineno = lineno;
2294                 redraw_view(view);
2296         } else {
2297                 unsigned long old_lineno = view->lineno - view->offset;
2299                 view->lineno = lineno;
2300                 draw_view_line(view, old_lineno);
2302                 draw_view_line(view, view->lineno - view->offset);
2303                 redrawwin(view->win);
2304                 wrefresh(view->win);
2305         }
2307         report("Line %ld matches '%s'", lineno + 1, view->grep);
2308         return TRUE;
2311 static void
2312 find_next(struct view *view, enum request request)
2314         unsigned long lineno = view->lineno;
2315         int direction;
2317         if (!*view->grep) {
2318                 if (!*opt_search)
2319                         report("No previous search");
2320                 else
2321                         search_view(view, request);
2322                 return;
2323         }
2325         switch (request) {
2326         case REQ_SEARCH:
2327         case REQ_FIND_NEXT:
2328                 direction = 1;
2329                 break;
2331         case REQ_SEARCH_BACK:
2332         case REQ_FIND_PREV:
2333                 direction = -1;
2334                 break;
2336         default:
2337                 return;
2338         }
2340         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2341                 lineno += direction;
2343         /* Note, lineno is unsigned long so will wrap around in which case it
2344          * will become bigger than view->lines. */
2345         for (; lineno < view->lines; lineno += direction) {
2346                 struct line *line = &view->line[lineno];
2348                 if (find_next_line(view, lineno, line))
2349                         return;
2350         }
2352         report("No match found for '%s'", view->grep);
2355 static void
2356 search_view(struct view *view, enum request request)
2358         int regex_err;
2360         if (view->regex) {
2361                 regfree(view->regex);
2362                 *view->grep = 0;
2363         } else {
2364                 view->regex = calloc(1, sizeof(*view->regex));
2365                 if (!view->regex)
2366                         return;
2367         }
2369         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2370         if (regex_err != 0) {
2371                 char buf[SIZEOF_STR] = "unknown error";
2373                 regerror(regex_err, view->regex, buf, sizeof(buf));
2374                 report("Search failed: %s", buf);
2375                 return;
2376         }
2378         string_copy(view->grep, opt_search);
2380         find_next(view, request);
2383 /*
2384  * Incremental updating
2385  */
2387 static void
2388 reset_view(struct view *view)
2390         int i;
2392         for (i = 0; i < view->lines; i++)
2393                 free(view->line[i].data);
2394         free(view->line);
2396         view->line = NULL;
2397         view->offset = 0;
2398         view->lines  = 0;
2399         view->lineno = 0;
2400         view->line_size = 0;
2401         view->line_alloc = 0;
2402         view->vid[0] = 0;
2405 static void
2406 free_argv(const char *argv[])
2408         int argc;
2410         for (argc = 0; argv[argc]; argc++)
2411                 free((void *) argv[argc]);
2414 static bool
2415 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
2417         char buf[SIZEOF_STR];
2418         int argc;
2419         bool noreplace = flags == FORMAT_NONE;
2421         free_argv(dst_argv);
2423         for (argc = 0; src_argv[argc]; argc++) {
2424                 const char *arg = src_argv[argc];
2425                 size_t bufpos = 0;
2427                 while (arg) {
2428                         char *next = strstr(arg, "%(");
2429                         int len = next - arg;
2430                         const char *value;
2432                         if (!next || noreplace) {
2433                                 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
2434                                         noreplace = TRUE;
2435                                 len = strlen(arg);
2436                                 value = "";
2438                         } else if (!prefixcmp(next, "%(directory)")) {
2439                                 value = opt_path;
2441                         } else if (!prefixcmp(next, "%(file)")) {
2442                                 value = opt_file;
2444                         } else if (!prefixcmp(next, "%(ref)")) {
2445                                 value = *opt_ref ? opt_ref : "HEAD";
2447                         } else if (!prefixcmp(next, "%(head)")) {
2448                                 value = ref_head;
2450                         } else if (!prefixcmp(next, "%(commit)")) {
2451                                 value = ref_commit;
2453                         } else if (!prefixcmp(next, "%(blob)")) {
2454                                 value = ref_blob;
2456                         } else {
2457                                 report("Unknown replacement: `%s`", next);
2458                                 return FALSE;
2459                         }
2461                         if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
2462                                 return FALSE;
2464                         arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
2465                 }
2467                 dst_argv[argc] = strdup(buf);
2468                 if (!dst_argv[argc])
2469                         break;
2470         }
2472         dst_argv[argc] = NULL;
2474         return src_argv[argc] == NULL;
2477 static bool
2478 format_command(char dst[], const char *src_argv[], enum format_flags flags)
2480         const char *dst_argv[SIZEOF_ARG * 2] = { NULL };
2481         int bufsize = 0;
2482         int argc;
2484         if (!format_argv(dst_argv, src_argv, flags)) {
2485                 free_argv(dst_argv);
2486                 return FALSE;
2487         }
2489         for (argc = 0; dst_argv[argc] && bufsize < SIZEOF_STR; argc++) {
2490                 if (bufsize > 0)
2491                         dst[bufsize++] = ' ';
2492                 bufsize = sq_quote(dst, bufsize, dst_argv[argc]);
2493         }
2495         if (bufsize < SIZEOF_STR)
2496                 dst[bufsize] = 0;
2497         free_argv(dst_argv);
2499         return src_argv[argc] == NULL && bufsize < SIZEOF_STR;
2502 static void
2503 end_update(struct view *view, bool force)
2505         if (!view->pipe)
2506                 return;
2507         while (!view->ops->read(view, NULL))
2508                 if (!force)
2509                         return;
2510         set_nonblocking_input(FALSE);
2511         done_io(view->pipe);
2512         view->pipe = NULL;
2515 static void
2516 setup_update(struct view *view, const char *vid)
2518         set_nonblocking_input(TRUE);
2519         reset_view(view);
2520         string_copy_rev(view->vid, vid);
2521         view->pipe = &view->io;
2522         view->start_time = time(NULL);
2525 static bool
2526 prepare_update(struct view *view, const char *argv[], const char *dir,
2527                enum format_flags flags)
2529         if (view->pipe)
2530                 end_update(view, TRUE);
2531         return init_io_rd(&view->io, argv, dir, flags);
2534 static bool
2535 begin_update(struct view *view, bool refresh)
2537         if (init_io_fd(&view->io, opt_pipe)) {
2538                 opt_pipe = NULL;
2540         } else if (opt_cmd[0]) {
2541                 if (!run_io(&view->io, IO_RD, opt_cmd))
2542                         return FALSE;
2543                 view->ref[0] = 0;
2544                 opt_cmd[0] = 0;
2546         } else if (refresh) {
2547                 if (!start_io(&view->io))
2548                         return FALSE;
2550         } else {
2551                 if (view == VIEW(REQ_VIEW_TREE) && strcmp(view->vid, view->id))
2552                         opt_path[0] = 0;
2554                 if (!run_io_rd(&view->io, view->ops->argv, FORMAT_ALL))
2555                         return FALSE;
2557                 /* Put the current ref_* value to the view title ref
2558                  * member. This is needed by the blob view. Most other
2559                  * views sets it automatically after loading because the
2560                  * first line is a commit line. */
2561                 string_copy_rev(view->ref, view->id);
2562         }
2564         setup_update(view, view->id);
2566         return TRUE;
2569 #define ITEM_CHUNK_SIZE 256
2570 static void *
2571 realloc_items(void *mem, size_t *size, size_t new_size, size_t item_size)
2573         size_t num_chunks = *size / ITEM_CHUNK_SIZE;
2574         size_t num_chunks_new = (new_size + ITEM_CHUNK_SIZE - 1) / ITEM_CHUNK_SIZE;
2576         if (mem == NULL || num_chunks != num_chunks_new) {
2577                 *size = num_chunks_new * ITEM_CHUNK_SIZE;
2578                 mem = realloc(mem, *size * item_size);
2579         }
2581         return mem;
2584 static struct line *
2585 realloc_lines(struct view *view, size_t line_size)
2587         size_t alloc = view->line_alloc;
2588         struct line *tmp = realloc_items(view->line, &alloc, line_size,
2589                                          sizeof(*view->line));
2591         if (!tmp)
2592                 return NULL;
2594         view->line = tmp;
2595         view->line_alloc = alloc;
2596         view->line_size = line_size;
2597         return view->line;
2600 static bool
2601 update_view(struct view *view)
2603         char out_buffer[BUFSIZ * 2];
2604         char *line;
2605         /* The number of lines to read. If too low it will cause too much
2606          * redrawing (and possible flickering), if too high responsiveness
2607          * will suffer. */
2608         unsigned long lines = view->height;
2609         int redraw_from = -1;
2611         if (!view->pipe)
2612                 return TRUE;
2614         /* Only redraw if lines are visible. */
2615         if (view->offset + view->height >= view->lines)
2616                 redraw_from = view->lines - view->offset;
2618         /* FIXME: This is probably not perfect for backgrounded views. */
2619         if (!realloc_lines(view, view->lines + lines))
2620                 goto alloc_error;
2622         while ((line = io_gets(view->pipe))) {
2623                 size_t linelen = strlen(line);
2625                 if (linelen)
2626                         line[linelen - 1] = 0;
2628                 if (opt_iconv != ICONV_NONE) {
2629                         ICONV_CONST char *inbuf = line;
2630                         size_t inlen = linelen;
2632                         char *outbuf = out_buffer;
2633                         size_t outlen = sizeof(out_buffer);
2635                         size_t ret;
2637                         ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2638                         if (ret != (size_t) -1) {
2639                                 line = out_buffer;
2640                                 linelen = strlen(out_buffer);
2641                         }
2642                 }
2644                 if (!view->ops->read(view, line))
2645                         goto alloc_error;
2647                 if (lines-- == 1)
2648                         break;
2649         }
2651         {
2652                 int digits;
2654                 lines = view->lines;
2655                 for (digits = 0; lines; digits++)
2656                         lines /= 10;
2658                 /* Keep the displayed view in sync with line number scaling. */
2659                 if (digits != view->digits) {
2660                         view->digits = digits;
2661                         redraw_from = 0;
2662                 }
2663         }
2665         if (io_error(view->pipe)) {
2666                 report("Failed to read: %s", io_strerror(view->pipe));
2667                 end_update(view, TRUE);
2669         } else if (io_eof(view->pipe)) {
2670                 report("");
2671                 end_update(view, FALSE);
2672         }
2674         if (!view_is_displayed(view))
2675                 return TRUE;
2677         if (view == VIEW(REQ_VIEW_TREE)) {
2678                 /* Clear the view and redraw everything since the tree sorting
2679                  * might have rearranged things. */
2680                 redraw_view(view);
2682         } else if (redraw_from >= 0) {
2683                 /* If this is an incremental update, redraw the previous line
2684                  * since for commits some members could have changed when
2685                  * loading the main view. */
2686                 if (redraw_from > 0)
2687                         redraw_from--;
2689                 /* Since revision graph visualization requires knowledge
2690                  * about the parent commit, it causes a further one-off
2691                  * needed to be redrawn for incremental updates. */
2692                 if (redraw_from > 0 && opt_rev_graph)
2693                         redraw_from--;
2695                 /* Incrementally draw avoids flickering. */
2696                 redraw_view_from(view, redraw_from);
2697         }
2699         if (view == VIEW(REQ_VIEW_BLAME))
2700                 redraw_view_dirty(view);
2702         /* Update the title _after_ the redraw so that if the redraw picks up a
2703          * commit reference in view->ref it'll be available here. */
2704         update_view_title(view);
2705         return TRUE;
2707 alloc_error:
2708         report("Allocation failure");
2709         end_update(view, TRUE);
2710         return FALSE;
2713 static struct line *
2714 add_line_data(struct view *view, void *data, enum line_type type)
2716         struct line *line = &view->line[view->lines++];
2718         memset(line, 0, sizeof(*line));
2719         line->type = type;
2720         line->data = data;
2722         return line;
2725 static struct line *
2726 add_line_text(struct view *view, const char *text, enum line_type type)
2728         char *data = text ? strdup(text) : NULL;
2730         return data ? add_line_data(view, data, type) : NULL;
2734 /*
2735  * View opening
2736  */
2738 enum open_flags {
2739         OPEN_DEFAULT = 0,       /* Use default view switching. */
2740         OPEN_SPLIT = 1,         /* Split current view. */
2741         OPEN_BACKGROUNDED = 2,  /* Backgrounded. */
2742         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
2743         OPEN_NOMAXIMIZE = 8,    /* Do not maximize the current view. */
2744         OPEN_REFRESH = 16,      /* Refresh view using previous command. */
2745         OPEN_PREPARED = 32,     /* Open already prepared command. */
2746 };
2748 static void
2749 open_view(struct view *prev, enum request request, enum open_flags flags)
2751         bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
2752         bool split = !!(flags & OPEN_SPLIT);
2753         bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
2754         bool nomaximize = !!(flags & (OPEN_NOMAXIMIZE | OPEN_REFRESH));
2755         struct view *view = VIEW(request);
2756         int nviews = displayed_views();
2757         struct view *base_view = display[0];
2759         if (view == prev && nviews == 1 && !reload) {
2760                 report("Already in %s view", view->name);
2761                 return;
2762         }
2764         if (view->git_dir && !opt_git_dir[0]) {
2765                 report("The %s view is disabled in pager view", view->name);
2766                 return;
2767         }
2769         if (split) {
2770                 display[1] = view;
2771                 if (!backgrounded)
2772                         current_view = 1;
2773         } else if (!nomaximize) {
2774                 /* Maximize the current view. */
2775                 memset(display, 0, sizeof(display));
2776                 current_view = 0;
2777                 display[current_view] = view;
2778         }
2780         /* Resize the view when switching between split- and full-screen,
2781          * or when switching between two different full-screen views. */
2782         if (nviews != displayed_views() ||
2783             (nviews == 1 && base_view != display[0]))
2784                 resize_display();
2786         if (view->pipe)
2787                 end_update(view, TRUE);
2789         if (view->ops->open) {
2790                 if (!view->ops->open(view)) {
2791                         report("Failed to load %s view", view->name);
2792                         return;
2793                 }
2795         } else if ((reload || strcmp(view->vid, view->id)) &&
2796                    !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
2797                 report("Failed to load %s view", view->name);
2798                 return;
2799         }
2801         if (split && prev->lineno - prev->offset >= prev->height) {
2802                 /* Take the title line into account. */
2803                 int lines = prev->lineno - prev->offset - prev->height + 1;
2805                 /* Scroll the view that was split if the current line is
2806                  * outside the new limited view. */
2807                 do_scroll_view(prev, lines);
2808         }
2810         if (prev && view != prev) {
2811                 if (split && !backgrounded) {
2812                         /* "Blur" the previous view. */
2813                         update_view_title(prev);
2814                 }
2816                 view->parent = prev;
2817         }
2819         if (view->pipe && view->lines == 0) {
2820                 /* Clear the old view and let the incremental updating refill
2821                  * the screen. */
2822                 werase(view->win);
2823                 report("");
2824         } else if (view_is_displayed(view)) {
2825                 redraw_view(view);
2826                 report("");
2827         }
2829         /* If the view is backgrounded the above calls to report()
2830          * won't redraw the view title. */
2831         if (backgrounded)
2832                 update_view_title(view);
2835 static void
2836 open_external_viewer(const char *argv[], const char *dir)
2838         def_prog_mode();           /* save current tty modes */
2839         endwin();                  /* restore original tty modes */
2840         run_io_fg(argv, dir);
2841         fprintf(stderr, "Press Enter to continue");
2842         getc(opt_tty);
2843         reset_prog_mode();
2844         redraw_display();
2847 static void
2848 open_mergetool(const char *file)
2850         const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
2852         open_external_viewer(mergetool_argv, NULL);
2855 static void
2856 open_editor(bool from_root, const char *file)
2858         const char *editor_argv[] = { "vi", file, NULL };
2859         const char *editor;
2861         editor = getenv("GIT_EDITOR");
2862         if (!editor && *opt_editor)
2863                 editor = opt_editor;
2864         if (!editor)
2865                 editor = getenv("VISUAL");
2866         if (!editor)
2867                 editor = getenv("EDITOR");
2868         if (!editor)
2869                 editor = "vi";
2871         editor_argv[0] = editor;
2872         open_external_viewer(editor_argv, from_root ? opt_cdup : NULL);
2875 static void
2876 open_run_request(enum request request)
2878         struct run_request *req = get_run_request(request);
2879         const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
2881         if (!req) {
2882                 report("Unknown run request");
2883                 return;
2884         }
2886         if (format_argv(argv, req->argv, FORMAT_ALL))
2887                 open_external_viewer(argv, NULL);
2888         free_argv(argv);
2891 /*
2892  * User request switch noodle
2893  */
2895 static int
2896 view_driver(struct view *view, enum request request)
2898         int i;
2900         if (request == REQ_NONE) {
2901                 doupdate();
2902                 return TRUE;
2903         }
2905         if (request > REQ_NONE) {
2906                 open_run_request(request);
2907                 /* FIXME: When all views can refresh always do this. */
2908                 if (view == VIEW(REQ_VIEW_STATUS) ||
2909                     view == VIEW(REQ_VIEW_MAIN) ||
2910                     view == VIEW(REQ_VIEW_LOG) ||
2911                     view == VIEW(REQ_VIEW_STAGE))
2912                         request = REQ_REFRESH;
2913                 else
2914                         return TRUE;
2915         }
2917         if (view && view->lines) {
2918                 request = view->ops->request(view, request, &view->line[view->lineno]);
2919                 if (request == REQ_NONE)
2920                         return TRUE;
2921         }
2923         switch (request) {
2924         case REQ_MOVE_UP:
2925         case REQ_MOVE_DOWN:
2926         case REQ_MOVE_PAGE_UP:
2927         case REQ_MOVE_PAGE_DOWN:
2928         case REQ_MOVE_FIRST_LINE:
2929         case REQ_MOVE_LAST_LINE:
2930                 move_view(view, request);
2931                 break;
2933         case REQ_SCROLL_LINE_DOWN:
2934         case REQ_SCROLL_LINE_UP:
2935         case REQ_SCROLL_PAGE_DOWN:
2936         case REQ_SCROLL_PAGE_UP:
2937                 scroll_view(view, request);
2938                 break;
2940         case REQ_VIEW_BLAME:
2941                 if (!opt_file[0]) {
2942                         report("No file chosen, press %s to open tree view",
2943                                get_key(REQ_VIEW_TREE));
2944                         break;
2945                 }
2946                 open_view(view, request, OPEN_DEFAULT);
2947                 break;
2949         case REQ_VIEW_BLOB:
2950                 if (!ref_blob[0]) {
2951                         report("No file chosen, press %s to open tree view",
2952                                get_key(REQ_VIEW_TREE));
2953                         break;
2954                 }
2955                 open_view(view, request, OPEN_DEFAULT);
2956                 break;
2958         case REQ_VIEW_PAGER:
2959                 if (!opt_pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
2960                         report("No pager content, press %s to run command from prompt",
2961                                get_key(REQ_PROMPT));
2962                         break;
2963                 }
2964                 open_view(view, request, OPEN_DEFAULT);
2965                 break;
2967         case REQ_VIEW_STAGE:
2968                 if (!VIEW(REQ_VIEW_STAGE)->lines) {
2969                         report("No stage content, press %s to open the status view and choose file",
2970                                get_key(REQ_VIEW_STATUS));
2971                         break;
2972                 }
2973                 open_view(view, request, OPEN_DEFAULT);
2974                 break;
2976         case REQ_VIEW_STATUS:
2977                 if (opt_is_inside_work_tree == FALSE) {
2978                         report("The status view requires a working tree");
2979                         break;
2980                 }
2981                 open_view(view, request, OPEN_DEFAULT);
2982                 break;
2984         case REQ_VIEW_MAIN:
2985         case REQ_VIEW_DIFF:
2986         case REQ_VIEW_LOG:
2987         case REQ_VIEW_TREE:
2988         case REQ_VIEW_HELP:
2989                 open_view(view, request, OPEN_DEFAULT);
2990                 break;
2992         case REQ_NEXT:
2993         case REQ_PREVIOUS:
2994                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
2996                 if ((view == VIEW(REQ_VIEW_DIFF) &&
2997                      view->parent == VIEW(REQ_VIEW_MAIN)) ||
2998                    (view == VIEW(REQ_VIEW_DIFF) &&
2999                      view->parent == VIEW(REQ_VIEW_BLAME)) ||
3000                    (view == VIEW(REQ_VIEW_STAGE) &&
3001                      view->parent == VIEW(REQ_VIEW_STATUS)) ||
3002                    (view == VIEW(REQ_VIEW_BLOB) &&
3003                      view->parent == VIEW(REQ_VIEW_TREE))) {
3004                         int line;
3006                         view = view->parent;
3007                         line = view->lineno;
3008                         move_view(view, request);
3009                         if (view_is_displayed(view))
3010                                 update_view_title(view);
3011                         if (line != view->lineno)
3012                                 view->ops->request(view, REQ_ENTER,
3013                                                    &view->line[view->lineno]);
3015                 } else {
3016                         move_view(view, request);
3017                 }
3018                 break;
3020         case REQ_VIEW_NEXT:
3021         {
3022                 int nviews = displayed_views();
3023                 int next_view = (current_view + 1) % nviews;
3025                 if (next_view == current_view) {
3026                         report("Only one view is displayed");
3027                         break;
3028                 }
3030                 current_view = next_view;
3031                 /* Blur out the title of the previous view. */
3032                 update_view_title(view);
3033                 report("");
3034                 break;
3035         }
3036         case REQ_REFRESH:
3037                 report("Refreshing is not yet supported for the %s view", view->name);
3038                 break;
3040         case REQ_MAXIMIZE:
3041                 if (displayed_views() == 2)
3042                         open_view(view, VIEW_REQ(view), OPEN_DEFAULT);
3043                 break;
3045         case REQ_TOGGLE_LINENO:
3046                 opt_line_number = !opt_line_number;
3047                 redraw_display();
3048                 break;
3050         case REQ_TOGGLE_DATE:
3051                 opt_date = !opt_date;
3052                 redraw_display();
3053                 break;
3055         case REQ_TOGGLE_AUTHOR:
3056                 opt_author = !opt_author;
3057                 redraw_display();
3058                 break;
3060         case REQ_TOGGLE_REV_GRAPH:
3061                 opt_rev_graph = !opt_rev_graph;
3062                 redraw_display();
3063                 break;
3065         case REQ_TOGGLE_REFS:
3066                 opt_show_refs = !opt_show_refs;
3067                 redraw_display();
3068                 break;
3070         case REQ_SEARCH:
3071         case REQ_SEARCH_BACK:
3072                 search_view(view, request);
3073                 break;
3075         case REQ_FIND_NEXT:
3076         case REQ_FIND_PREV:
3077                 find_next(view, request);
3078                 break;
3080         case REQ_STOP_LOADING:
3081                 for (i = 0; i < ARRAY_SIZE(views); i++) {
3082                         view = &views[i];
3083                         if (view->pipe)
3084                                 report("Stopped loading the %s view", view->name),
3085                         end_update(view, TRUE);
3086                 }
3087                 break;
3089         case REQ_SHOW_VERSION:
3090                 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3091                 return TRUE;
3093         case REQ_SCREEN_RESIZE:
3094                 resize_display();
3095                 /* Fall-through */
3096         case REQ_SCREEN_REDRAW:
3097                 redraw_display();
3098                 break;
3100         case REQ_EDIT:
3101                 report("Nothing to edit");
3102                 break;
3104         case REQ_ENTER:
3105                 report("Nothing to enter");
3106                 break;
3108         case REQ_VIEW_CLOSE:
3109                 /* XXX: Mark closed views by letting view->parent point to the
3110                  * view itself. Parents to closed view should never be
3111                  * followed. */
3112                 if (view->parent &&
3113                     view->parent->parent != view->parent) {
3114                         memset(display, 0, sizeof(display));
3115                         current_view = 0;
3116                         display[current_view] = view->parent;
3117                         view->parent = view;
3118                         resize_display();
3119                         redraw_display();
3120                         report("");
3121                         break;
3122                 }
3123                 /* Fall-through */
3124         case REQ_QUIT:
3125                 return FALSE;
3127         default:
3128                 report("Unknown key, press 'h' for help");
3129                 return TRUE;
3130         }
3132         return TRUE;
3136 /*
3137  * Pager backend
3138  */
3140 static bool
3141 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3143         char *text = line->data;
3145         if (opt_line_number && draw_lineno(view, lineno))
3146                 return TRUE;
3148         draw_text(view, line->type, text, TRUE);
3149         return TRUE;
3152 static bool
3153 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3155         const char *describe_argv[] = { "git", "describe", commit_id, NULL };
3156         char refbuf[SIZEOF_STR];
3157         char *ref = NULL;
3159         if (run_io_buf(describe_argv, refbuf, sizeof(refbuf)))
3160                 ref = chomp_string(refbuf);
3162         if (!ref || !*ref)
3163                 return TRUE;
3165         /* This is the only fatal call, since it can "corrupt" the buffer. */
3166         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
3167                 return FALSE;
3169         return TRUE;
3172 static void
3173 add_pager_refs(struct view *view, struct line *line)
3175         char buf[SIZEOF_STR];
3176         char *commit_id = (char *)line->data + STRING_SIZE("commit ");
3177         struct ref **refs;
3178         size_t bufpos = 0, refpos = 0;
3179         const char *sep = "Refs: ";
3180         bool is_tag = FALSE;
3182         assert(line->type == LINE_COMMIT);
3184         refs = get_refs(commit_id);
3185         if (!refs) {
3186                 if (view == VIEW(REQ_VIEW_DIFF))
3187                         goto try_add_describe_ref;
3188                 return;
3189         }
3191         do {
3192                 struct ref *ref = refs[refpos];
3193                 const char *fmt = ref->tag    ? "%s[%s]" :
3194                                   ref->remote ? "%s<%s>" : "%s%s";
3196                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
3197                         return;
3198                 sep = ", ";
3199                 if (ref->tag)
3200                         is_tag = TRUE;
3201         } while (refs[refpos++]->next);
3203         if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
3204 try_add_describe_ref:
3205                 /* Add <tag>-g<commit_id> "fake" reference. */
3206                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
3207                         return;
3208         }
3210         if (bufpos == 0)
3211                 return;
3213         if (!realloc_lines(view, view->line_size + 1))
3214                 return;
3216         add_line_text(view, buf, LINE_PP_REFS);
3219 static bool
3220 pager_read(struct view *view, char *data)
3222         struct line *line;
3224         if (!data)
3225                 return TRUE;
3227         line = add_line_text(view, data, get_line_type(data));
3228         if (!line)
3229                 return FALSE;
3231         if (line->type == LINE_COMMIT &&
3232             (view == VIEW(REQ_VIEW_DIFF) ||
3233              view == VIEW(REQ_VIEW_LOG)))
3234                 add_pager_refs(view, line);
3236         return TRUE;
3239 static enum request
3240 pager_request(struct view *view, enum request request, struct line *line)
3242         int split = 0;
3244         if (request != REQ_ENTER)
3245                 return request;
3247         if (line->type == LINE_COMMIT &&
3248            (view == VIEW(REQ_VIEW_LOG) ||
3249             view == VIEW(REQ_VIEW_PAGER))) {
3250                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3251                 split = 1;
3252         }
3254         /* Always scroll the view even if it was split. That way
3255          * you can use Enter to scroll through the log view and
3256          * split open each commit diff. */
3257         scroll_view(view, REQ_SCROLL_LINE_DOWN);
3259         /* FIXME: A minor workaround. Scrolling the view will call report("")
3260          * but if we are scrolling a non-current view this won't properly
3261          * update the view title. */
3262         if (split)
3263                 update_view_title(view);
3265         return REQ_NONE;
3268 static bool
3269 pager_grep(struct view *view, struct line *line)
3271         regmatch_t pmatch;
3272         char *text = line->data;
3274         if (!*text)
3275                 return FALSE;
3277         if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
3278                 return FALSE;
3280         return TRUE;
3283 static void
3284 pager_select(struct view *view, struct line *line)
3286         if (line->type == LINE_COMMIT) {
3287                 char *text = (char *)line->data + STRING_SIZE("commit ");
3289                 if (view != VIEW(REQ_VIEW_PAGER))
3290                         string_copy_rev(view->ref, text);
3291                 string_copy_rev(ref_commit, text);
3292         }
3295 static struct view_ops pager_ops = {
3296         "line",
3297         NULL,
3298         NULL,
3299         pager_read,
3300         pager_draw,
3301         pager_request,
3302         pager_grep,
3303         pager_select,
3304 };
3306 static const char *log_argv[SIZEOF_ARG] = {
3307         "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3308 };
3310 static enum request
3311 log_request(struct view *view, enum request request, struct line *line)
3313         switch (request) {
3314         case REQ_REFRESH:
3315                 load_refs();
3316                 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
3317                 return REQ_NONE;
3318         default:
3319                 return pager_request(view, request, line);
3320         }
3323 static struct view_ops log_ops = {
3324         "line",
3325         log_argv,
3326         NULL,
3327         pager_read,
3328         pager_draw,
3329         log_request,
3330         pager_grep,
3331         pager_select,
3332 };
3334 static const char *diff_argv[SIZEOF_ARG] = {
3335         "git", "show", "--pretty=fuller", "--no-color", "--root",
3336                 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
3337 };
3339 static struct view_ops diff_ops = {
3340         "line",
3341         diff_argv,
3342         NULL,
3343         pager_read,
3344         pager_draw,
3345         pager_request,
3346         pager_grep,
3347         pager_select,
3348 };
3350 /*
3351  * Help backend
3352  */
3354 static bool
3355 help_open(struct view *view)
3357         char buf[BUFSIZ];
3358         int lines = ARRAY_SIZE(req_info) + 2;
3359         int i;
3361         if (view->lines > 0)
3362                 return TRUE;
3364         for (i = 0; i < ARRAY_SIZE(req_info); i++)
3365                 if (!req_info[i].request)
3366                         lines++;
3368         lines += run_requests + 1;
3370         view->line = calloc(lines, sizeof(*view->line));
3371         if (!view->line)
3372                 return FALSE;
3374         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
3376         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
3377                 const char *key;
3379                 if (req_info[i].request == REQ_NONE)
3380                         continue;
3382                 if (!req_info[i].request) {
3383                         add_line_text(view, "", LINE_DEFAULT);
3384                         add_line_text(view, req_info[i].help, LINE_DEFAULT);
3385                         continue;
3386                 }
3388                 key = get_key(req_info[i].request);
3389                 if (!*key)
3390                         key = "(no key defined)";
3392                 if (!string_format(buf, "    %-25s %s", key, req_info[i].help))
3393                         continue;
3395                 add_line_text(view, buf, LINE_DEFAULT);
3396         }
3398         if (run_requests) {
3399                 add_line_text(view, "", LINE_DEFAULT);
3400                 add_line_text(view, "External commands:", LINE_DEFAULT);
3401         }
3403         for (i = 0; i < run_requests; i++) {
3404                 struct run_request *req = get_run_request(REQ_NONE + i + 1);
3405                 const char *key;
3406                 char cmd[SIZEOF_STR];
3407                 size_t bufpos;
3408                 int argc;
3410                 if (!req)
3411                         continue;
3413                 key = get_key_name(req->key);
3414                 if (!*key)
3415                         key = "(no key defined)";
3417                 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
3418                         if (!string_format_from(cmd, &bufpos, "%s%s",
3419                                                 argc ? " " : "", req->argv[argc]))
3420                                 return REQ_NONE;
3422                 if (!string_format(buf, "    %-10s %-14s `%s`",
3423                                    keymap_table[req->keymap].name, key, cmd))
3424                         continue;
3426                 add_line_text(view, buf, LINE_DEFAULT);
3427         }
3429         return TRUE;
3432 static struct view_ops help_ops = {
3433         "line",
3434         NULL,
3435         help_open,
3436         NULL,
3437         pager_draw,
3438         pager_request,
3439         pager_grep,
3440         pager_select,
3441 };
3444 /*
3445  * Tree backend
3446  */
3448 struct tree_stack_entry {
3449         struct tree_stack_entry *prev;  /* Entry below this in the stack */
3450         unsigned long lineno;           /* Line number to restore */
3451         char *name;                     /* Position of name in opt_path */
3452 };
3454 /* The top of the path stack. */
3455 static struct tree_stack_entry *tree_stack = NULL;
3456 unsigned long tree_lineno = 0;
3458 static void
3459 pop_tree_stack_entry(void)
3461         struct tree_stack_entry *entry = tree_stack;
3463         tree_lineno = entry->lineno;
3464         entry->name[0] = 0;
3465         tree_stack = entry->prev;
3466         free(entry);
3469 static void
3470 push_tree_stack_entry(const char *name, unsigned long lineno)
3472         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
3473         size_t pathlen = strlen(opt_path);
3475         if (!entry)
3476                 return;
3478         entry->prev = tree_stack;
3479         entry->name = opt_path + pathlen;
3480         tree_stack = entry;
3482         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
3483                 pop_tree_stack_entry();
3484                 return;
3485         }
3487         /* Move the current line to the first tree entry. */
3488         tree_lineno = 1;
3489         entry->lineno = lineno;
3492 /* Parse output from git-ls-tree(1):
3493  *
3494  * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile
3495  * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README
3496  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
3497  * 100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38 web.conf
3498  */
3500 #define SIZEOF_TREE_ATTR \
3501         STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t")
3503 #define TREE_UP_FORMAT "040000 tree %s\t.."
3505 static int
3506 tree_compare_entry(enum line_type type1, const char *name1,
3507                    enum line_type type2, const char *name2)
3509         if (type1 != type2) {
3510                 if (type1 == LINE_TREE_DIR)
3511                         return -1;
3512                 return 1;
3513         }
3515         return strcmp(name1, name2);
3518 static const char *
3519 tree_path(struct line *line)
3521         const char *path = line->data;
3523         return path + SIZEOF_TREE_ATTR;
3526 static bool
3527 tree_read(struct view *view, char *text)
3529         size_t textlen = text ? strlen(text) : 0;
3530         char buf[SIZEOF_STR];
3531         unsigned long pos;
3532         enum line_type type;
3533         bool first_read = view->lines == 0;
3535         if (!text)
3536                 return TRUE;
3537         if (textlen <= SIZEOF_TREE_ATTR)
3538                 return FALSE;
3540         type = text[STRING_SIZE("100644 ")] == 't'
3541              ? LINE_TREE_DIR : LINE_TREE_FILE;
3543         if (first_read) {
3544                 /* Add path info line */
3545                 if (!string_format(buf, "Directory path /%s", opt_path) ||
3546                     !realloc_lines(view, view->line_size + 1) ||
3547                     !add_line_text(view, buf, LINE_DEFAULT))
3548                         return FALSE;
3550                 /* Insert "link" to parent directory. */
3551                 if (*opt_path) {
3552                         if (!string_format(buf, TREE_UP_FORMAT, view->ref) ||
3553                             !realloc_lines(view, view->line_size + 1) ||
3554                             !add_line_text(view, buf, LINE_TREE_DIR))
3555                                 return FALSE;
3556                 }
3557         }
3559         /* Strip the path part ... */
3560         if (*opt_path) {
3561                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
3562                 size_t striplen = strlen(opt_path);
3563                 char *path = text + SIZEOF_TREE_ATTR;
3565                 if (pathlen > striplen)
3566                         memmove(path, path + striplen,
3567                                 pathlen - striplen + 1);
3568         }
3570         /* Skip "Directory ..." and ".." line. */
3571         for (pos = 1 + !!*opt_path; pos < view->lines; pos++) {
3572                 struct line *line = &view->line[pos];
3573                 const char *path1 = tree_path(line);
3574                 char *path2 = text + SIZEOF_TREE_ATTR;
3575                 int cmp = tree_compare_entry(line->type, path1, type, path2);
3577                 if (cmp <= 0)
3578                         continue;
3580                 text = strdup(text);
3581                 if (!text)
3582                         return FALSE;
3584                 if (view->lines > pos)
3585                         memmove(&view->line[pos + 1], &view->line[pos],
3586                                 (view->lines - pos) * sizeof(*line));
3588                 line = &view->line[pos];
3589                 line->data = text;
3590                 line->type = type;
3591                 view->lines++;
3592                 return TRUE;
3593         }
3595         if (!add_line_text(view, text, type))
3596                 return FALSE;
3598         if (tree_lineno > view->lineno) {
3599                 view->lineno = tree_lineno;
3600                 tree_lineno = 0;
3601         }
3603         return TRUE;
3606 static enum request
3607 tree_request(struct view *view, enum request request, struct line *line)
3609         enum open_flags flags;
3611         switch (request) {
3612         case REQ_VIEW_BLAME:
3613                 if (line->type != LINE_TREE_FILE) {
3614                         report("Blame only supported for files");
3615                         return REQ_NONE;
3616                 }
3618                 string_copy(opt_ref, view->vid);
3619                 return request;
3621         case REQ_EDIT:
3622                 if (line->type != LINE_TREE_FILE) {
3623                         report("Edit only supported for files");
3624                 } else if (!is_head_commit(view->vid)) {
3625                         report("Edit only supported for files in the current work tree");
3626                 } else {
3627                         open_editor(TRUE, opt_file);
3628                 }
3629                 return REQ_NONE;
3631         case REQ_TREE_PARENT:
3632                 if (!*opt_path) {
3633                         /* quit view if at top of tree */
3634                         return REQ_VIEW_CLOSE;
3635                 }
3636                 /* fake 'cd  ..' */
3637                 line = &view->line[1];
3638                 break;
3640         case REQ_ENTER:
3641                 break;
3643         default:
3644                 return request;
3645         }
3647         /* Cleanup the stack if the tree view is at a different tree. */
3648         while (!*opt_path && tree_stack)
3649                 pop_tree_stack_entry();
3651         switch (line->type) {
3652         case LINE_TREE_DIR:
3653                 /* Depending on whether it is a subdir or parent (updir?) link
3654                  * mangle the path buffer. */
3655                 if (line == &view->line[1] && *opt_path) {
3656                         pop_tree_stack_entry();
3658                 } else {
3659                         const char *basename = tree_path(line);
3661                         push_tree_stack_entry(basename, view->lineno);
3662                 }
3664                 /* Trees and subtrees share the same ID, so they are not not
3665                  * unique like blobs. */
3666                 flags = OPEN_RELOAD;
3667                 request = REQ_VIEW_TREE;
3668                 break;
3670         case LINE_TREE_FILE:
3671                 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
3672                 request = REQ_VIEW_BLOB;
3673                 break;
3675         default:
3676                 return TRUE;
3677         }
3679         open_view(view, request, flags);
3680         if (request == REQ_VIEW_TREE) {
3681                 view->lineno = tree_lineno;
3682         }
3684         return REQ_NONE;
3687 static void
3688 tree_select(struct view *view, struct line *line)
3690         char *text = (char *)line->data + STRING_SIZE("100644 blob ");
3692         if (line->type == LINE_TREE_FILE) {
3693                 string_copy_rev(ref_blob, text);
3694                 string_format(opt_file, "%s%s", opt_path, tree_path(line));
3696         } else if (line->type != LINE_TREE_DIR) {
3697                 return;
3698         }
3700         string_copy_rev(view->ref, text);
3703 static const char *tree_argv[SIZEOF_ARG] = {
3704         "git", "ls-tree", "%(commit)", "%(directory)", NULL
3705 };
3707 static struct view_ops tree_ops = {
3708         "file",
3709         tree_argv,
3710         NULL,
3711         tree_read,
3712         pager_draw,
3713         tree_request,
3714         pager_grep,
3715         tree_select,
3716 };
3718 static bool
3719 blob_read(struct view *view, char *line)
3721         if (!line)
3722                 return TRUE;
3723         return add_line_text(view, line, LINE_DEFAULT) != NULL;
3726 static const char *blob_argv[SIZEOF_ARG] = {
3727         "git", "cat-file", "blob", "%(blob)", NULL
3728 };
3730 static struct view_ops blob_ops = {
3731         "line",
3732         blob_argv,
3733         NULL,
3734         blob_read,
3735         pager_draw,
3736         pager_request,
3737         pager_grep,
3738         pager_select,
3739 };
3741 /*
3742  * Blame backend
3743  *
3744  * Loading the blame view is a two phase job:
3745  *
3746  *  1. File content is read either using opt_file from the
3747  *     filesystem or using git-cat-file.
3748  *  2. Then blame information is incrementally added by
3749  *     reading output from git-blame.
3750  */
3752 static const char *blame_head_argv[] = {
3753         "git", "blame", "--incremental", "--", "%(file)", NULL
3754 };
3756 static const char *blame_ref_argv[] = {
3757         "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
3758 };
3760 static const char *blame_cat_file_argv[] = {
3761         "git", "cat-file", "blob", "%(ref):%(file)", NULL
3762 };
3764 struct blame_commit {
3765         char id[SIZEOF_REV];            /* SHA1 ID. */
3766         char title[128];                /* First line of the commit message. */
3767         char author[75];                /* Author of the commit. */
3768         struct tm time;                 /* Date from the author ident. */
3769         char filename[128];             /* Name of file. */
3770 };
3772 struct blame {
3773         struct blame_commit *commit;
3774         char text[1];
3775 };
3777 static bool
3778 blame_open(struct view *view)
3780         if (*opt_ref || !init_io_fd(&view->io, fopen(opt_file, "r"))) {
3781                 if (!run_io_rd(&view->io, blame_cat_file_argv, FORMAT_ALL))
3782                         return FALSE;
3783         }
3785         setup_update(view, opt_file);
3786         string_format(view->ref, "%s ...", opt_file);
3788         return TRUE;
3791 static struct blame_commit *
3792 get_blame_commit(struct view *view, const char *id)
3794         size_t i;
3796         for (i = 0; i < view->lines; i++) {
3797                 struct blame *blame = view->line[i].data;
3799                 if (!blame->commit)
3800                         continue;
3802                 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
3803                         return blame->commit;
3804         }
3806         {
3807                 struct blame_commit *commit = calloc(1, sizeof(*commit));
3809                 if (commit)
3810                         string_ncopy(commit->id, id, SIZEOF_REV);
3811                 return commit;
3812         }
3815 static bool
3816 parse_number(const char **posref, size_t *number, size_t min, size_t max)
3818         const char *pos = *posref;
3820         *posref = NULL;
3821         pos = strchr(pos + 1, ' ');
3822         if (!pos || !isdigit(pos[1]))
3823                 return FALSE;
3824         *number = atoi(pos + 1);
3825         if (*number < min || *number > max)
3826                 return FALSE;
3828         *posref = pos;
3829         return TRUE;
3832 static struct blame_commit *
3833 parse_blame_commit(struct view *view, const char *text, int *blamed)
3835         struct blame_commit *commit;
3836         struct blame *blame;
3837         const char *pos = text + SIZEOF_REV - 1;
3838         size_t lineno;
3839         size_t group;
3841         if (strlen(text) <= SIZEOF_REV || *pos != ' ')
3842                 return NULL;
3844         if (!parse_number(&pos, &lineno, 1, view->lines) ||
3845             !parse_number(&pos, &group, 1, view->lines - lineno + 1))
3846                 return NULL;
3848         commit = get_blame_commit(view, text);
3849         if (!commit)
3850                 return NULL;
3852         *blamed += group;
3853         while (group--) {
3854                 struct line *line = &view->line[lineno + group - 1];
3856                 blame = line->data;
3857                 blame->commit = commit;
3858                 line->dirty = 1;
3859         }
3861         return commit;
3864 static bool
3865 blame_read_file(struct view *view, const char *line, bool *read_file)
3867         if (!line) {
3868                 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
3869                 struct io io = {};
3871                 if (view->lines == 0 && !view->parent)
3872                         die("No blame exist for %s", view->vid);
3874                 if (view->lines == 0 || !run_io_rd(&io, argv, FORMAT_ALL)) {
3875                         report("Failed to load blame data");
3876                         return TRUE;
3877                 }
3879                 done_io(view->pipe);
3880                 view->io = io;
3881                 *read_file = FALSE;
3882                 return FALSE;
3884         } else {
3885                 size_t linelen = strlen(line);
3886                 struct blame *blame = malloc(sizeof(*blame) + linelen);
3888                 blame->commit = NULL;
3889                 strncpy(blame->text, line, linelen);
3890                 blame->text[linelen] = 0;
3891                 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
3892         }
3895 static bool
3896 match_blame_header(const char *name, char **line)
3898         size_t namelen = strlen(name);
3899         bool matched = !strncmp(name, *line, namelen);
3901         if (matched)
3902                 *line += namelen;
3904         return matched;
3907 static bool
3908 blame_read(struct view *view, char *line)
3910         static struct blame_commit *commit = NULL;
3911         static int blamed = 0;
3912         static time_t author_time;
3913         static bool read_file = TRUE;
3915         if (read_file)
3916                 return blame_read_file(view, line, &read_file);
3918         if (!line) {
3919                 /* Reset all! */
3920                 commit = NULL;
3921                 blamed = 0;
3922                 read_file = TRUE;
3923                 string_format(view->ref, "%s", view->vid);
3924                 if (view_is_displayed(view)) {
3925                         update_view_title(view);
3926                         redraw_view_from(view, 0);
3927                 }
3928                 return TRUE;
3929         }
3931         if (!commit) {
3932                 commit = parse_blame_commit(view, line, &blamed);
3933                 string_format(view->ref, "%s %2d%%", view->vid,
3934                               blamed * 100 / view->lines);
3936         } else if (match_blame_header("author ", &line)) {
3937                 string_ncopy(commit->author, line, strlen(line));
3939         } else if (match_blame_header("author-time ", &line)) {
3940                 author_time = (time_t) atol(line);
3942         } else if (match_blame_header("author-tz ", &line)) {
3943                 long tz;
3945                 tz  = ('0' - line[1]) * 60 * 60 * 10;
3946                 tz += ('0' - line[2]) * 60 * 60;
3947                 tz += ('0' - line[3]) * 60;
3948                 tz += ('0' - line[4]) * 60;
3950                 if (line[0] == '-')
3951                         tz = -tz;
3953                 author_time -= tz;
3954                 gmtime_r(&author_time, &commit->time);
3956         } else if (match_blame_header("summary ", &line)) {
3957                 string_ncopy(commit->title, line, strlen(line));
3959         } else if (match_blame_header("filename ", &line)) {
3960                 string_ncopy(commit->filename, line, strlen(line));
3961                 commit = NULL;
3962         }
3964         return TRUE;
3967 static bool
3968 blame_draw(struct view *view, struct line *line, unsigned int lineno)
3970         struct blame *blame = line->data;
3971         struct tm *time = NULL;
3972         const char *id = NULL, *author = NULL;
3974         if (blame->commit && *blame->commit->filename) {
3975                 id = blame->commit->id;
3976                 author = blame->commit->author;
3977                 time = &blame->commit->time;
3978         }
3980         if (opt_date && draw_date(view, time))
3981                 return TRUE;
3983         if (opt_author &&
3984             draw_field(view, LINE_MAIN_AUTHOR, author, opt_author_cols, TRUE))
3985                 return TRUE;
3987         if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
3988                 return TRUE;
3990         if (draw_lineno(view, lineno))
3991                 return TRUE;
3993         draw_text(view, LINE_DEFAULT, blame->text, TRUE);
3994         return TRUE;
3997 static enum request
3998 blame_request(struct view *view, enum request request, struct line *line)
4000         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4001         struct blame *blame = line->data;
4003         switch (request) {
4004         case REQ_VIEW_BLAME:
4005                 if (!blame->commit || !strcmp(blame->commit->id, NULL_ID)) {
4006                         report("Commit ID unknown");
4007                         break;
4008                 }
4009                 string_copy(opt_ref, blame->commit->id);
4010                 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4011                 return request;
4013         case REQ_ENTER:
4014                 if (!blame->commit) {
4015                         report("No commit loaded yet");
4016                         break;
4017                 }
4019                 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
4020                     !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
4021                         break;
4023                 if (!strcmp(blame->commit->id, NULL_ID)) {
4024                         struct view *diff = VIEW(REQ_VIEW_DIFF);
4025                         const char *diff_index_argv[] = {
4026                                 "git", "diff-index", "--root", "--cached",
4027                                         "--patch-with-stat", "-C", "-M",
4028                                         "HEAD", "--", view->vid, NULL
4029                         };
4031                         if (!prepare_update(diff, diff_index_argv, NULL, FORMAT_DASH)) {
4032                                 report("Failed to allocate diff command");
4033                                 break;
4034                         }
4035                         flags |= OPEN_PREPARED;
4036                 }
4038                 open_view(view, REQ_VIEW_DIFF, flags);
4039                 break;
4041         default:
4042                 return request;
4043         }
4045         return REQ_NONE;
4048 static bool
4049 blame_grep(struct view *view, struct line *line)
4051         struct blame *blame = line->data;
4052         struct blame_commit *commit = blame->commit;
4053         regmatch_t pmatch;
4055 #define MATCH(text, on)                                                 \
4056         (on && *text && regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4058         if (commit) {
4059                 char buf[DATE_COLS + 1];
4061                 if (MATCH(commit->title, 1) ||
4062                     MATCH(commit->author, opt_author) ||
4063                     MATCH(commit->id, opt_date))
4064                         return TRUE;
4066                 if (strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time) &&
4067                     MATCH(buf, 1))
4068                         return TRUE;
4069         }
4071         return MATCH(blame->text, 1);
4073 #undef MATCH
4076 static void
4077 blame_select(struct view *view, struct line *line)
4079         struct blame *blame = line->data;
4080         struct blame_commit *commit = blame->commit;
4082         if (!commit)
4083                 return;
4085         if (!strcmp(commit->id, NULL_ID))
4086                 string_ncopy(ref_commit, "HEAD", 4);
4087         else
4088                 string_copy_rev(ref_commit, commit->id);
4091 static struct view_ops blame_ops = {
4092         "line",
4093         NULL,
4094         blame_open,
4095         blame_read,
4096         blame_draw,
4097         blame_request,
4098         blame_grep,
4099         blame_select,
4100 };
4102 /*
4103  * Status backend
4104  */
4106 struct status {
4107         char status;
4108         struct {
4109                 mode_t mode;
4110                 char rev[SIZEOF_REV];
4111                 char name[SIZEOF_STR];
4112         } old;
4113         struct {
4114                 mode_t mode;
4115                 char rev[SIZEOF_REV];
4116                 char name[SIZEOF_STR];
4117         } new;
4118 };
4120 static char status_onbranch[SIZEOF_STR];
4121 static struct status stage_status;
4122 static enum line_type stage_line_type;
4123 static size_t stage_chunks;
4124 static int *stage_chunk;
4126 /* This should work even for the "On branch" line. */
4127 static inline bool
4128 status_has_none(struct view *view, struct line *line)
4130         return line < view->line + view->lines && !line[1].data;
4133 /* Get fields from the diff line:
4134  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
4135  */
4136 static inline bool
4137 status_get_diff(struct status *file, const char *buf, size_t bufsize)
4139         const char *old_mode = buf +  1;
4140         const char *new_mode = buf +  8;
4141         const char *old_rev  = buf + 15;
4142         const char *new_rev  = buf + 56;
4143         const char *status   = buf + 97;
4145         if (bufsize < 99 ||
4146             old_mode[-1] != ':' ||
4147             new_mode[-1] != ' ' ||
4148             old_rev[-1]  != ' ' ||
4149             new_rev[-1]  != ' ' ||
4150             status[-1]   != ' ')
4151                 return FALSE;
4153         file->status = *status;
4155         string_copy_rev(file->old.rev, old_rev);
4156         string_copy_rev(file->new.rev, new_rev);
4158         file->old.mode = strtoul(old_mode, NULL, 8);
4159         file->new.mode = strtoul(new_mode, NULL, 8);
4161         file->old.name[0] = file->new.name[0] = 0;
4163         return TRUE;
4166 static bool
4167 status_run(struct view *view, const char cmd[], char status, enum line_type type)
4169         struct status *file = NULL;
4170         struct status *unmerged = NULL;
4171         char buf[SIZEOF_STR * 4];
4172         size_t bufsize = 0;
4173         FILE *pipe;
4175         pipe = popen(cmd, "r");
4176         if (!pipe)
4177                 return FALSE;
4179         add_line_data(view, NULL, type);
4181         while (!feof(pipe) && !ferror(pipe)) {
4182                 char *sep;
4183                 size_t readsize;
4185                 readsize = fread(buf + bufsize, 1, sizeof(buf) - bufsize, pipe);
4186                 if (!readsize)
4187                         break;
4188                 bufsize += readsize;
4190                 /* Process while we have NUL chars. */
4191                 while ((sep = memchr(buf, 0, bufsize))) {
4192                         size_t sepsize = sep - buf + 1;
4194                         if (!file) {
4195                                 if (!realloc_lines(view, view->line_size + 1))
4196                                         goto error_out;
4198                                 file = calloc(1, sizeof(*file));
4199                                 if (!file)
4200                                         goto error_out;
4202                                 add_line_data(view, file, type);
4203                         }
4205                         /* Parse diff info part. */
4206                         if (status) {
4207                                 file->status = status;
4208                                 if (status == 'A')
4209                                         string_copy(file->old.rev, NULL_ID);
4211                         } else if (!file->status) {
4212                                 if (!status_get_diff(file, buf, sepsize))
4213                                         goto error_out;
4215                                 bufsize -= sepsize;
4216                                 memmove(buf, sep + 1, bufsize);
4218                                 sep = memchr(buf, 0, bufsize);
4219                                 if (!sep)
4220                                         break;
4221                                 sepsize = sep - buf + 1;
4223                                 /* Collapse all 'M'odified entries that
4224                                  * follow a associated 'U'nmerged entry.
4225                                  */
4226                                 if (file->status == 'U') {
4227                                         unmerged = file;
4229                                 } else if (unmerged) {
4230                                         int collapse = !strcmp(buf, unmerged->new.name);
4232                                         unmerged = NULL;
4233                                         if (collapse) {
4234                                                 free(file);
4235                                                 view->lines--;
4236                                                 continue;
4237                                         }
4238                                 }
4239                         }
4241                         /* Grab the old name for rename/copy. */
4242                         if (!*file->old.name &&
4243                             (file->status == 'R' || file->status == 'C')) {
4244                                 sepsize = sep - buf + 1;
4245                                 string_ncopy(file->old.name, buf, sepsize);
4246                                 bufsize -= sepsize;
4247                                 memmove(buf, sep + 1, bufsize);
4249                                 sep = memchr(buf, 0, bufsize);
4250                                 if (!sep)
4251                                         break;
4252                                 sepsize = sep - buf + 1;
4253                         }
4255                         /* git-ls-files just delivers a NUL separated
4256                          * list of file names similar to the second half
4257                          * of the git-diff-* output. */
4258                         string_ncopy(file->new.name, buf, sepsize);
4259                         if (!*file->old.name)
4260                                 string_copy(file->old.name, file->new.name);
4261                         bufsize -= sepsize;
4262                         memmove(buf, sep + 1, bufsize);
4263                         file = NULL;
4264                 }
4265         }
4267         if (ferror(pipe)) {
4268 error_out:
4269                 pclose(pipe);
4270                 return FALSE;
4271         }
4273         if (!view->line[view->lines - 1].data)
4274                 add_line_data(view, NULL, LINE_STAT_NONE);
4276         pclose(pipe);
4277         return TRUE;
4280 /* Don't show unmerged entries in the staged section. */
4281 #define STATUS_DIFF_INDEX_CMD "git diff-index -z --diff-filter=ACDMRTXB --cached -M HEAD"
4282 #define STATUS_DIFF_FILES_CMD "git diff-files -z"
4283 #define STATUS_LIST_OTHER_CMD \
4284         "git ls-files -z --others --exclude-standard"
4285 #define STATUS_LIST_NO_HEAD_CMD \
4286         "git ls-files -z --cached --exclude-standard"
4288 #define STATUS_DIFF_INDEX_SHOW_CMD \
4289         "git diff-index --root --patch-with-stat -C -M --cached HEAD -- %s %s 2>/dev/null"
4291 #define STATUS_DIFF_FILES_SHOW_CMD \
4292         "git diff-files --root --patch-with-stat -C -M -- %s %s 2>/dev/null"
4294 #define STATUS_DIFF_NO_HEAD_SHOW_CMD \
4295         "git diff --no-color --patch-with-stat /dev/null %s 2>/dev/null"
4297 /* First parse staged info using git-diff-index(1), then parse unstaged
4298  * info using git-diff-files(1), and finally untracked files using
4299  * git-ls-files(1). */
4300 static bool
4301 status_open(struct view *view)
4303         unsigned long prev_lineno = view->lineno;
4305         reset_view(view);
4307         if (!realloc_lines(view, view->line_size + 7))
4308                 return FALSE;
4310         add_line_data(view, NULL, LINE_STAT_HEAD);
4311         if (is_initial_commit())
4312                 string_copy(status_onbranch, "Initial commit");
4313         else if (!*opt_head)
4314                 string_copy(status_onbranch, "Not currently on any branch");
4315         else if (!string_format(status_onbranch, "On branch %s", opt_head))
4316                 return FALSE;
4318         system("git update-index -q --refresh >/dev/null 2>/dev/null");
4320         if (is_initial_commit()) {
4321                 if (!status_run(view, STATUS_LIST_NO_HEAD_CMD, 'A', LINE_STAT_STAGED))
4322                         return FALSE;
4323         } else if (!status_run(view, STATUS_DIFF_INDEX_CMD, 0, LINE_STAT_STAGED)) {
4324                 return FALSE;
4325         }
4327         if (!status_run(view, STATUS_DIFF_FILES_CMD, 0, LINE_STAT_UNSTAGED) ||
4328             !status_run(view, STATUS_LIST_OTHER_CMD, '?', LINE_STAT_UNTRACKED))
4329                 return FALSE;
4331         /* If all went well restore the previous line number to stay in
4332          * the context or select a line with something that can be
4333          * updated. */
4334         if (prev_lineno >= view->lines)
4335                 prev_lineno = view->lines - 1;
4336         while (prev_lineno < view->lines && !view->line[prev_lineno].data)
4337                 prev_lineno++;
4338         while (prev_lineno > 0 && !view->line[prev_lineno].data)
4339                 prev_lineno--;
4341         /* If the above fails, always skip the "On branch" line. */
4342         if (prev_lineno < view->lines)
4343                 view->lineno = prev_lineno;
4344         else
4345                 view->lineno = 1;
4347         if (view->lineno < view->offset)
4348                 view->offset = view->lineno;
4349         else if (view->offset + view->height <= view->lineno)
4350                 view->offset = view->lineno - view->height + 1;
4352         return TRUE;
4355 static bool
4356 status_draw(struct view *view, struct line *line, unsigned int lineno)
4358         struct status *status = line->data;
4359         enum line_type type;
4360         const char *text;
4362         if (!status) {
4363                 switch (line->type) {
4364                 case LINE_STAT_STAGED:
4365                         type = LINE_STAT_SECTION;
4366                         text = "Changes to be committed:";
4367                         break;
4369                 case LINE_STAT_UNSTAGED:
4370                         type = LINE_STAT_SECTION;
4371                         text = "Changed but not updated:";
4372                         break;
4374                 case LINE_STAT_UNTRACKED:
4375                         type = LINE_STAT_SECTION;
4376                         text = "Untracked files:";
4377                         break;
4379                 case LINE_STAT_NONE:
4380                         type = LINE_DEFAULT;
4381                         text = "    (no files)";
4382                         break;
4384                 case LINE_STAT_HEAD:
4385                         type = LINE_STAT_HEAD;
4386                         text = status_onbranch;
4387                         break;
4389                 default:
4390                         return FALSE;
4391                 }
4392         } else {
4393                 static char buf[] = { '?', ' ', ' ', ' ', 0 };
4395                 buf[0] = status->status;
4396                 if (draw_text(view, line->type, buf, TRUE))
4397                         return TRUE;
4398                 type = LINE_DEFAULT;
4399                 text = status->new.name;
4400         }
4402         draw_text(view, type, text, TRUE);
4403         return TRUE;
4406 static enum request
4407 status_enter(struct view *view, struct line *line)
4409         struct status *status = line->data;
4410         char oldpath[SIZEOF_STR] = "";
4411         char newpath[SIZEOF_STR] = "";
4412         const char *info;
4413         size_t cmdsize = 0;
4414         enum open_flags split;
4416         if (line->type == LINE_STAT_NONE ||
4417             (!status && line[1].type == LINE_STAT_NONE)) {
4418                 report("No file to diff");
4419                 return REQ_NONE;
4420         }
4422         if (status) {
4423                 if (sq_quote(oldpath, 0, status->old.name) >= sizeof(oldpath))
4424                         return REQ_QUIT;
4425                 /* Diffs for unmerged entries are empty when pasing the
4426                  * new path, so leave it empty. */
4427                 if (status->status != 'U' &&
4428                     sq_quote(newpath, 0, status->new.name) >= sizeof(newpath))
4429                         return REQ_QUIT;
4430         }
4432         if (opt_cdup[0] &&
4433             line->type != LINE_STAT_UNTRACKED &&
4434             !string_format_from(opt_cmd, &cmdsize, "cd %s;", opt_cdup))
4435                 return REQ_QUIT;
4437         switch (line->type) {
4438         case LINE_STAT_STAGED:
4439                 if (is_initial_commit()) {
4440                         if (!string_format_from(opt_cmd, &cmdsize,
4441                                                 STATUS_DIFF_NO_HEAD_SHOW_CMD,
4442                                                 newpath))
4443                                 return REQ_QUIT;
4444                 } else {
4445                         if (!string_format_from(opt_cmd, &cmdsize,
4446                                                 STATUS_DIFF_INDEX_SHOW_CMD,
4447                                                 oldpath, newpath))
4448                                 return REQ_QUIT;
4449                 }
4451                 if (status)
4452                         info = "Staged changes to %s";
4453                 else
4454                         info = "Staged changes";
4455                 break;
4457         case LINE_STAT_UNSTAGED:
4458                 if (!string_format_from(opt_cmd, &cmdsize,
4459                                         STATUS_DIFF_FILES_SHOW_CMD, oldpath, newpath))
4460                         return REQ_QUIT;
4461                 if (status)
4462                         info = "Unstaged changes to %s";
4463                 else
4464                         info = "Unstaged changes";
4465                 break;
4467         case LINE_STAT_UNTRACKED:
4468                 if (opt_pipe)
4469                         return REQ_QUIT;
4471                 if (!status) {
4472                         report("No file to show");
4473                         return REQ_NONE;
4474                 }
4476                 if (!suffixcmp(status->new.name, -1, "/")) {
4477                         report("Cannot display a directory");
4478                         return REQ_NONE;
4479                 }
4481                 opt_pipe = fopen(status->new.name, "r");
4482                 info = "Untracked file %s";
4483                 break;
4485         case LINE_STAT_HEAD:
4486                 return REQ_NONE;
4488         default:
4489                 die("line type %d not handled in switch", line->type);
4490         }
4492         split = view_is_displayed(view) ? OPEN_SPLIT : 0;
4493         open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH | split);
4494         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
4495                 if (status) {
4496                         stage_status = *status;
4497                 } else {
4498                         memset(&stage_status, 0, sizeof(stage_status));
4499                 }
4501                 stage_line_type = line->type;
4502                 stage_chunks = 0;
4503                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
4504         }
4506         return REQ_NONE;
4509 static bool
4510 status_exists(struct status *status, enum line_type type)
4512         struct view *view = VIEW(REQ_VIEW_STATUS);
4513         struct line *line;
4515         for (line = view->line; line < view->line + view->lines; line++) {
4516                 struct status *pos = line->data;
4518                 if (line->type == type && pos &&
4519                     !strcmp(status->new.name, pos->new.name))
4520                         return TRUE;
4521         }
4523         return FALSE;
4527 static FILE *
4528 status_update_prepare(enum line_type type)
4530         char cmd[SIZEOF_STR];
4531         size_t cmdsize = 0;
4533         if (opt_cdup[0] &&
4534             type != LINE_STAT_UNTRACKED &&
4535             !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
4536                 return NULL;
4538         switch (type) {
4539         case LINE_STAT_STAGED:
4540                 string_add(cmd, cmdsize, "git update-index -z --index-info");
4541                 break;
4543         case LINE_STAT_UNSTAGED:
4544         case LINE_STAT_UNTRACKED:
4545                 string_add(cmd, cmdsize, "git update-index -z --add --remove --stdin");
4546                 break;
4548         default:
4549                 die("line type %d not handled in switch", type);
4550         }
4552         return popen(cmd, "w");
4555 static bool
4556 status_update_write(FILE *pipe, struct status *status, enum line_type type)
4558         char buf[SIZEOF_STR];
4559         size_t bufsize = 0;
4560         size_t written = 0;
4562         switch (type) {
4563         case LINE_STAT_STAGED:
4564                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
4565                                         status->old.mode,
4566                                         status->old.rev,
4567                                         status->old.name, 0))
4568                         return FALSE;
4569                 break;
4571         case LINE_STAT_UNSTAGED:
4572         case LINE_STAT_UNTRACKED:
4573                 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
4574                         return FALSE;
4575                 break;
4577         default:
4578                 die("line type %d not handled in switch", type);
4579         }
4581         while (!ferror(pipe) && written < bufsize) {
4582                 written += fwrite(buf + written, 1, bufsize - written, pipe);
4583         }
4585         return written == bufsize;
4588 static bool
4589 status_update_file(struct status *status, enum line_type type)
4591         FILE *pipe = status_update_prepare(type);
4592         bool result;
4594         if (!pipe)
4595                 return FALSE;
4597         result = status_update_write(pipe, status, type);
4598         pclose(pipe);
4599         return result;
4602 static bool
4603 status_update_files(struct view *view, struct line *line)
4605         FILE *pipe = status_update_prepare(line->type);
4606         bool result = TRUE;
4607         struct line *pos = view->line + view->lines;
4608         int files = 0;
4609         int file, done;
4611         if (!pipe)
4612                 return FALSE;
4614         for (pos = line; pos < view->line + view->lines && pos->data; pos++)
4615                 files++;
4617         for (file = 0, done = 0; result && file < files; line++, file++) {
4618                 int almost_done = file * 100 / files;
4620                 if (almost_done > done) {
4621                         done = almost_done;
4622                         string_format(view->ref, "updating file %u of %u (%d%% done)",
4623                                       file, files, done);
4624                         update_view_title(view);
4625                 }
4626                 result = status_update_write(pipe, line->data, line->type);
4627         }
4629         pclose(pipe);
4630         return result;
4633 static bool
4634 status_update(struct view *view)
4636         struct line *line = &view->line[view->lineno];
4638         assert(view->lines);
4640         if (!line->data) {
4641                 /* This should work even for the "On branch" line. */
4642                 if (line < view->line + view->lines && !line[1].data) {
4643                         report("Nothing to update");
4644                         return FALSE;
4645                 }
4647                 if (!status_update_files(view, line + 1)) {
4648                         report("Failed to update file status");
4649                         return FALSE;
4650                 }
4652         } else if (!status_update_file(line->data, line->type)) {
4653                 report("Failed to update file status");
4654                 return FALSE;
4655         }
4657         return TRUE;
4660 static bool
4661 status_revert(struct status *status, enum line_type type, bool has_none)
4663         if (!status || type != LINE_STAT_UNSTAGED) {
4664                 if (type == LINE_STAT_STAGED) {
4665                         report("Cannot revert changes to staged files");
4666                 } else if (type == LINE_STAT_UNTRACKED) {
4667                         report("Cannot revert changes to untracked files");
4668                 } else if (has_none) {
4669                         report("Nothing to revert");
4670                 } else {
4671                         report("Cannot revert changes to multiple files");
4672                 }
4673                 return FALSE;
4675         } else {
4676                 const char *checkout_argv[] = {
4677                         "git", "checkout", "--", status->old.name, NULL
4678                 };
4680                 if (!prompt_yesno("Are you sure you want to overwrite any changes?"))
4681                         return FALSE;
4682                 return run_io_fg(checkout_argv, opt_cdup);
4683         }
4686 static enum request
4687 status_request(struct view *view, enum request request, struct line *line)
4689         struct status *status = line->data;
4691         switch (request) {
4692         case REQ_STATUS_UPDATE:
4693                 if (!status_update(view))
4694                         return REQ_NONE;
4695                 break;
4697         case REQ_STATUS_REVERT:
4698                 if (!status_revert(status, line->type, status_has_none(view, line)))
4699                         return REQ_NONE;
4700                 break;
4702         case REQ_STATUS_MERGE:
4703                 if (!status || status->status != 'U') {
4704                         report("Merging only possible for files with unmerged status ('U').");
4705                         return REQ_NONE;
4706                 }
4707                 open_mergetool(status->new.name);
4708                 break;
4710         case REQ_EDIT:
4711                 if (!status)
4712                         return request;
4713                 if (status->status == 'D') {
4714                         report("File has been deleted.");
4715                         return REQ_NONE;
4716                 }
4718                 open_editor(status->status != '?', status->new.name);
4719                 break;
4721         case REQ_VIEW_BLAME:
4722                 if (status) {
4723                         string_copy(opt_file, status->new.name);
4724                         opt_ref[0] = 0;
4725                 }
4726                 return request;
4728         case REQ_ENTER:
4729                 /* After returning the status view has been split to
4730                  * show the stage view. No further reloading is
4731                  * necessary. */
4732                 status_enter(view, line);
4733                 return REQ_NONE;
4735         case REQ_REFRESH:
4736                 /* Simply reload the view. */
4737                 break;
4739         default:
4740                 return request;
4741         }
4743         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
4745         return REQ_NONE;
4748 static void
4749 status_select(struct view *view, struct line *line)
4751         struct status *status = line->data;
4752         char file[SIZEOF_STR] = "all files";
4753         const char *text;
4754         const char *key;
4756         if (status && !string_format(file, "'%s'", status->new.name))
4757                 return;
4759         if (!status && line[1].type == LINE_STAT_NONE)
4760                 line++;
4762         switch (line->type) {
4763         case LINE_STAT_STAGED:
4764                 text = "Press %s to unstage %s for commit";
4765                 break;
4767         case LINE_STAT_UNSTAGED:
4768                 text = "Press %s to stage %s for commit";
4769                 break;
4771         case LINE_STAT_UNTRACKED:
4772                 text = "Press %s to stage %s for addition";
4773                 break;
4775         case LINE_STAT_HEAD:
4776         case LINE_STAT_NONE:
4777                 text = "Nothing to update";
4778                 break;
4780         default:
4781                 die("line type %d not handled in switch", line->type);
4782         }
4784         if (status && status->status == 'U') {
4785                 text = "Press %s to resolve conflict in %s";
4786                 key = get_key(REQ_STATUS_MERGE);
4788         } else {
4789                 key = get_key(REQ_STATUS_UPDATE);
4790         }
4792         string_format(view->ref, text, key, file);
4795 static bool
4796 status_grep(struct view *view, struct line *line)
4798         struct status *status = line->data;
4799         enum { S_STATUS, S_NAME, S_END } state;
4800         char buf[2] = "?";
4801         regmatch_t pmatch;
4803         if (!status)
4804                 return FALSE;
4806         for (state = S_STATUS; state < S_END; state++) {
4807                 const char *text;
4809                 switch (state) {
4810                 case S_NAME:    text = status->new.name;        break;
4811                 case S_STATUS:
4812                         buf[0] = status->status;
4813                         text = buf;
4814                         break;
4816                 default:
4817                         return FALSE;
4818                 }
4820                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4821                         return TRUE;
4822         }
4824         return FALSE;
4827 static struct view_ops status_ops = {
4828         "file",
4829         NULL,
4830         status_open,
4831         NULL,
4832         status_draw,
4833         status_request,
4834         status_grep,
4835         status_select,
4836 };
4839 static bool
4840 stage_diff_line(FILE *pipe, struct line *line)
4842         const char *buf = line->data;
4843         size_t bufsize = strlen(buf);
4844         size_t written = 0;
4846         while (!ferror(pipe) && written < bufsize) {
4847                 written += fwrite(buf + written, 1, bufsize - written, pipe);
4848         }
4850         fputc('\n', pipe);
4852         return written == bufsize;
4855 static bool
4856 stage_diff_write(FILE *pipe, struct line *line, struct line *end)
4858         while (line < end) {
4859                 if (!stage_diff_line(pipe, line++))
4860                         return FALSE;
4861                 if (line->type == LINE_DIFF_CHUNK ||
4862                     line->type == LINE_DIFF_HEADER)
4863                         break;
4864         }
4866         return TRUE;
4869 static struct line *
4870 stage_diff_find(struct view *view, struct line *line, enum line_type type)
4872         for (; view->line < line; line--)
4873                 if (line->type == type)
4874                         return line;
4876         return NULL;
4879 static bool
4880 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
4882         char cmd[SIZEOF_STR];
4883         size_t cmdsize = 0;
4884         struct line *diff_hdr;
4885         FILE *pipe;
4887         diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
4888         if (!diff_hdr)
4889                 return FALSE;
4891         if (opt_cdup[0] &&
4892             !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
4893                 return FALSE;
4895         if (!string_format_from(cmd, &cmdsize,
4896                                 "git apply --whitespace=nowarn %s %s - && "
4897                                 "git update-index -q --unmerged --refresh 2>/dev/null",
4898                                 revert ? "" : "--cached",
4899                                 revert || stage_line_type == LINE_STAT_STAGED ? "-R" : ""))
4900                 return FALSE;
4902         pipe = popen(cmd, "w");
4903         if (!pipe)
4904                 return FALSE;
4906         if (!stage_diff_write(pipe, diff_hdr, chunk) ||
4907             !stage_diff_write(pipe, chunk, view->line + view->lines))
4908                 chunk = NULL;
4910         pclose(pipe);
4912         return chunk ? TRUE : FALSE;
4915 static bool
4916 stage_update(struct view *view, struct line *line)
4918         struct line *chunk = NULL;
4920         if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
4921                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
4923         if (chunk) {
4924                 if (!stage_apply_chunk(view, chunk, FALSE)) {
4925                         report("Failed to apply chunk");
4926                         return FALSE;
4927                 }
4929         } else if (!stage_status.status) {
4930                 view = VIEW(REQ_VIEW_STATUS);
4932                 for (line = view->line; line < view->line + view->lines; line++)
4933                         if (line->type == stage_line_type)
4934                                 break;
4936                 if (!status_update_files(view, line + 1)) {
4937                         report("Failed to update files");
4938                         return FALSE;
4939                 }
4941         } else if (!status_update_file(&stage_status, stage_line_type)) {
4942                 report("Failed to update file");
4943                 return FALSE;
4944         }
4946         return TRUE;
4949 static bool
4950 stage_revert(struct view *view, struct line *line)
4952         struct line *chunk = NULL;
4954         if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
4955                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
4957         if (chunk) {
4958                 if (!prompt_yesno("Are you sure you want to revert changes?"))
4959                         return FALSE;
4961                 if (!stage_apply_chunk(view, chunk, TRUE)) {
4962                         report("Failed to revert chunk");
4963                         return FALSE;
4964                 }
4965                 return TRUE;
4967         } else {
4968                 return status_revert(stage_status.status ? &stage_status : NULL,
4969                                      stage_line_type, FALSE);
4970         }
4974 static void
4975 stage_next(struct view *view, struct line *line)
4977         int i;
4979         if (!stage_chunks) {
4980                 static size_t alloc = 0;
4981                 int *tmp;
4983                 for (line = view->line; line < view->line + view->lines; line++) {
4984                         if (line->type != LINE_DIFF_CHUNK)
4985                                 continue;
4987                         tmp = realloc_items(stage_chunk, &alloc,
4988                                             stage_chunks, sizeof(*tmp));
4989                         if (!tmp) {
4990                                 report("Allocation failure");
4991                                 return;
4992                         }
4994                         stage_chunk = tmp;
4995                         stage_chunk[stage_chunks++] = line - view->line;
4996                 }
4997         }
4999         for (i = 0; i < stage_chunks; i++) {
5000                 if (stage_chunk[i] > view->lineno) {
5001                         do_scroll_view(view, stage_chunk[i] - view->lineno);
5002                         report("Chunk %d of %d", i + 1, stage_chunks);
5003                         return;
5004                 }
5005         }
5007         report("No next chunk found");
5010 static enum request
5011 stage_request(struct view *view, enum request request, struct line *line)
5013         switch (request) {
5014         case REQ_STATUS_UPDATE:
5015                 if (!stage_update(view, line))
5016                         return REQ_NONE;
5017                 break;
5019         case REQ_STATUS_REVERT:
5020                 if (!stage_revert(view, line))
5021                         return REQ_NONE;
5022                 break;
5024         case REQ_STAGE_NEXT:
5025                 if (stage_line_type == LINE_STAT_UNTRACKED) {
5026                         report("File is untracked; press %s to add",
5027                                get_key(REQ_STATUS_UPDATE));
5028                         return REQ_NONE;
5029                 }
5030                 stage_next(view, line);
5031                 return REQ_NONE;
5033         case REQ_EDIT:
5034                 if (!stage_status.new.name[0])
5035                         return request;
5036                 if (stage_status.status == 'D') {
5037                         report("File has been deleted.");
5038                         return REQ_NONE;
5039                 }
5041                 open_editor(stage_status.status != '?', stage_status.new.name);
5042                 break;
5044         case REQ_REFRESH:
5045                 /* Reload everything ... */
5046                 break;
5048         case REQ_VIEW_BLAME:
5049                 if (stage_status.new.name[0]) {
5050                         string_copy(opt_file, stage_status.new.name);
5051                         opt_ref[0] = 0;
5052                 }
5053                 return request;
5055         case REQ_ENTER:
5056                 return pager_request(view, request, line);
5058         default:
5059                 return request;
5060         }
5062         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD | OPEN_NOMAXIMIZE);
5064         /* Check whether the staged entry still exists, and close the
5065          * stage view if it doesn't. */
5066         if (!status_exists(&stage_status, stage_line_type))
5067                 return REQ_VIEW_CLOSE;
5069         if (stage_line_type == LINE_STAT_UNTRACKED) {
5070                 if (!suffixcmp(stage_status.new.name, -1, "/")) {
5071                         report("Cannot display a directory");
5072                         return REQ_NONE;
5073                 }
5075                 opt_pipe = fopen(stage_status.new.name, "r");
5076         }
5077         open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
5079         return REQ_NONE;
5082 static struct view_ops stage_ops = {
5083         "line",
5084         NULL,
5085         NULL,
5086         pager_read,
5087         pager_draw,
5088         stage_request,
5089         pager_grep,
5090         pager_select,
5091 };
5094 /*
5095  * Revision graph
5096  */
5098 struct commit {
5099         char id[SIZEOF_REV];            /* SHA1 ID. */
5100         char title[128];                /* First line of the commit message. */
5101         char author[75];                /* Author of the commit. */
5102         struct tm time;                 /* Date from the author ident. */
5103         struct ref **refs;              /* Repository references. */
5104         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
5105         size_t graph_size;              /* The width of the graph array. */
5106         bool has_parents;               /* Rewritten --parents seen. */
5107 };
5109 /* Size of rev graph with no  "padding" columns */
5110 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
5112 struct rev_graph {
5113         struct rev_graph *prev, *next, *parents;
5114         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
5115         size_t size;
5116         struct commit *commit;
5117         size_t pos;
5118         unsigned int boundary:1;
5119 };
5121 /* Parents of the commit being visualized. */
5122 static struct rev_graph graph_parents[4];
5124 /* The current stack of revisions on the graph. */
5125 static struct rev_graph graph_stacks[4] = {
5126         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
5127         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
5128         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
5129         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
5130 };
5132 static inline bool
5133 graph_parent_is_merge(struct rev_graph *graph)
5135         return graph->parents->size > 1;
5138 static inline void
5139 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
5141         struct commit *commit = graph->commit;
5143         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
5144                 commit->graph[commit->graph_size++] = symbol;
5147 static void
5148 clear_rev_graph(struct rev_graph *graph)
5150         graph->boundary = 0;
5151         graph->size = graph->pos = 0;
5152         graph->commit = NULL;
5153         memset(graph->parents, 0, sizeof(*graph->parents));
5156 static void
5157 done_rev_graph(struct rev_graph *graph)
5159         if (graph_parent_is_merge(graph) &&
5160             graph->pos < graph->size - 1 &&
5161             graph->next->size == graph->size + graph->parents->size - 1) {
5162                 size_t i = graph->pos + graph->parents->size - 1;
5164                 graph->commit->graph_size = i * 2;
5165                 while (i < graph->next->size - 1) {
5166                         append_to_rev_graph(graph, ' ');
5167                         append_to_rev_graph(graph, '\\');
5168                         i++;
5169                 }
5170         }
5172         clear_rev_graph(graph);
5175 static void
5176 push_rev_graph(struct rev_graph *graph, const char *parent)
5178         int i;
5180         /* "Collapse" duplicate parents lines.
5181          *
5182          * FIXME: This needs to also update update the drawn graph but
5183          * for now it just serves as a method for pruning graph lines. */
5184         for (i = 0; i < graph->size; i++)
5185                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
5186                         return;
5188         if (graph->size < SIZEOF_REVITEMS) {
5189                 string_copy_rev(graph->rev[graph->size++], parent);
5190         }
5193 static chtype
5194 get_rev_graph_symbol(struct rev_graph *graph)
5196         chtype symbol;
5198         if (graph->boundary)
5199                 symbol = REVGRAPH_BOUND;
5200         else if (graph->parents->size == 0)
5201                 symbol = REVGRAPH_INIT;
5202         else if (graph_parent_is_merge(graph))
5203                 symbol = REVGRAPH_MERGE;
5204         else if (graph->pos >= graph->size)
5205                 symbol = REVGRAPH_BRANCH;
5206         else
5207                 symbol = REVGRAPH_COMMIT;
5209         return symbol;
5212 static void
5213 draw_rev_graph(struct rev_graph *graph)
5215         struct rev_filler {
5216                 chtype separator, line;
5217         };
5218         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
5219         static struct rev_filler fillers[] = {
5220                 { ' ',  '|' },
5221                 { '`',  '.' },
5222                 { '\'', ' ' },
5223                 { '/',  ' ' },
5224         };
5225         chtype symbol = get_rev_graph_symbol(graph);
5226         struct rev_filler *filler;
5227         size_t i;
5229         if (opt_line_graphics)
5230                 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
5232         filler = &fillers[DEFAULT];
5234         for (i = 0; i < graph->pos; i++) {
5235                 append_to_rev_graph(graph, filler->line);
5236                 if (graph_parent_is_merge(graph->prev) &&
5237                     graph->prev->pos == i)
5238                         filler = &fillers[RSHARP];
5240                 append_to_rev_graph(graph, filler->separator);
5241         }
5243         /* Place the symbol for this revision. */
5244         append_to_rev_graph(graph, symbol);
5246         if (graph->prev->size > graph->size)
5247                 filler = &fillers[RDIAG];
5248         else
5249                 filler = &fillers[DEFAULT];
5251         i++;
5253         for (; i < graph->size; i++) {
5254                 append_to_rev_graph(graph, filler->separator);
5255                 append_to_rev_graph(graph, filler->line);
5256                 if (graph_parent_is_merge(graph->prev) &&
5257                     i < graph->prev->pos + graph->parents->size)
5258                         filler = &fillers[RSHARP];
5259                 if (graph->prev->size > graph->size)
5260                         filler = &fillers[LDIAG];
5261         }
5263         if (graph->prev->size > graph->size) {
5264                 append_to_rev_graph(graph, filler->separator);
5265                 if (filler->line != ' ')
5266                         append_to_rev_graph(graph, filler->line);
5267         }
5270 /* Prepare the next rev graph */
5271 static void
5272 prepare_rev_graph(struct rev_graph *graph)
5274         size_t i;
5276         /* First, traverse all lines of revisions up to the active one. */
5277         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
5278                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
5279                         break;
5281                 push_rev_graph(graph->next, graph->rev[graph->pos]);
5282         }
5284         /* Interleave the new revision parent(s). */
5285         for (i = 0; !graph->boundary && i < graph->parents->size; i++)
5286                 push_rev_graph(graph->next, graph->parents->rev[i]);
5288         /* Lastly, put any remaining revisions. */
5289         for (i = graph->pos + 1; i < graph->size; i++)
5290                 push_rev_graph(graph->next, graph->rev[i]);
5293 static void
5294 update_rev_graph(struct rev_graph *graph)
5296         /* If this is the finalizing update ... */
5297         if (graph->commit)
5298                 prepare_rev_graph(graph);
5300         /* Graph visualization needs a one rev look-ahead,
5301          * so the first update doesn't visualize anything. */
5302         if (!graph->prev->commit)
5303                 return;
5305         draw_rev_graph(graph->prev);
5306         done_rev_graph(graph->prev->prev);
5310 /*
5311  * Main view backend
5312  */
5314 static const char *main_argv[SIZEOF_ARG] = {
5315         "git", "log", "--no-color", "--pretty=raw", "--parents",
5316                       "--topo-order", "%(head)", NULL
5317 };
5319 static bool
5320 main_draw(struct view *view, struct line *line, unsigned int lineno)
5322         struct commit *commit = line->data;
5324         if (!*commit->author)
5325                 return FALSE;
5327         if (opt_date && draw_date(view, &commit->time))
5328                 return TRUE;
5330         if (opt_author &&
5331             draw_field(view, LINE_MAIN_AUTHOR, commit->author, opt_author_cols, TRUE))
5332                 return TRUE;
5334         if (opt_rev_graph && commit->graph_size &&
5335             draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
5336                 return TRUE;
5338         if (opt_show_refs && commit->refs) {
5339                 size_t i = 0;
5341                 do {
5342                         enum line_type type;
5344                         if (commit->refs[i]->head)
5345                                 type = LINE_MAIN_HEAD;
5346                         else if (commit->refs[i]->ltag)
5347                                 type = LINE_MAIN_LOCAL_TAG;
5348                         else if (commit->refs[i]->tag)
5349                                 type = LINE_MAIN_TAG;
5350                         else if (commit->refs[i]->tracked)
5351                                 type = LINE_MAIN_TRACKED;
5352                         else if (commit->refs[i]->remote)
5353                                 type = LINE_MAIN_REMOTE;
5354                         else
5355                                 type = LINE_MAIN_REF;
5357                         if (draw_text(view, type, "[", TRUE) ||
5358                             draw_text(view, type, commit->refs[i]->name, TRUE) ||
5359                             draw_text(view, type, "]", TRUE))
5360                                 return TRUE;
5362                         if (draw_text(view, LINE_DEFAULT, " ", TRUE))
5363                                 return TRUE;
5364                 } while (commit->refs[i++]->next);
5365         }
5367         draw_text(view, LINE_DEFAULT, commit->title, TRUE);
5368         return TRUE;
5371 /* Reads git log --pretty=raw output and parses it into the commit struct. */
5372 static bool
5373 main_read(struct view *view, char *line)
5375         static struct rev_graph *graph = graph_stacks;
5376         enum line_type type;
5377         struct commit *commit;
5379         if (!line) {
5380                 int i;
5382                 if (!view->lines && !view->parent)
5383                         die("No revisions match the given arguments.");
5384                 if (view->lines > 0) {
5385                         commit = view->line[view->lines - 1].data;
5386                         if (!*commit->author) {
5387                                 view->lines--;
5388                                 free(commit);
5389                                 graph->commit = NULL;
5390                         }
5391                 }
5392                 update_rev_graph(graph);
5394                 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
5395                         clear_rev_graph(&graph_stacks[i]);
5396                 return TRUE;
5397         }
5399         type = get_line_type(line);
5400         if (type == LINE_COMMIT) {
5401                 commit = calloc(1, sizeof(struct commit));
5402                 if (!commit)
5403                         return FALSE;
5405                 line += STRING_SIZE("commit ");
5406                 if (*line == '-') {
5407                         graph->boundary = 1;
5408                         line++;
5409                 }
5411                 string_copy_rev(commit->id, line);
5412                 commit->refs = get_refs(commit->id);
5413                 graph->commit = commit;
5414                 add_line_data(view, commit, LINE_MAIN_COMMIT);
5416                 while ((line = strchr(line, ' '))) {
5417                         line++;
5418                         push_rev_graph(graph->parents, line);
5419                         commit->has_parents = TRUE;
5420                 }
5421                 return TRUE;
5422         }
5424         if (!view->lines)
5425                 return TRUE;
5426         commit = view->line[view->lines - 1].data;
5428         switch (type) {
5429         case LINE_PARENT:
5430                 if (commit->has_parents)
5431                         break;
5432                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
5433                 break;
5435         case LINE_AUTHOR:
5436         {
5437                 /* Parse author lines where the name may be empty:
5438                  *      author  <email@address.tld> 1138474660 +0100
5439                  */
5440                 char *ident = line + STRING_SIZE("author ");
5441                 char *nameend = strchr(ident, '<');
5442                 char *emailend = strchr(ident, '>');
5444                 if (!nameend || !emailend)
5445                         break;
5447                 update_rev_graph(graph);
5448                 graph = graph->next;
5450                 *nameend = *emailend = 0;
5451                 ident = chomp_string(ident);
5452                 if (!*ident) {
5453                         ident = chomp_string(nameend + 1);
5454                         if (!*ident)
5455                                 ident = "Unknown";
5456                 }
5458                 string_ncopy(commit->author, ident, strlen(ident));
5460                 /* Parse epoch and timezone */
5461                 if (emailend[1] == ' ') {
5462                         char *secs = emailend + 2;
5463                         char *zone = strchr(secs, ' ');
5464                         time_t time = (time_t) atol(secs);
5466                         if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
5467                                 long tz;
5469                                 zone++;
5470                                 tz  = ('0' - zone[1]) * 60 * 60 * 10;
5471                                 tz += ('0' - zone[2]) * 60 * 60;
5472                                 tz += ('0' - zone[3]) * 60;
5473                                 tz += ('0' - zone[4]) * 60;
5475                                 if (zone[0] == '-')
5476                                         tz = -tz;
5478                                 time -= tz;
5479                         }
5481                         gmtime_r(&time, &commit->time);
5482                 }
5483                 break;
5484         }
5485         default:
5486                 /* Fill in the commit title if it has not already been set. */
5487                 if (commit->title[0])
5488                         break;
5490                 /* Require titles to start with a non-space character at the
5491                  * offset used by git log. */
5492                 if (strncmp(line, "    ", 4))
5493                         break;
5494                 line += 4;
5495                 /* Well, if the title starts with a whitespace character,
5496                  * try to be forgiving.  Otherwise we end up with no title. */
5497                 while (isspace(*line))
5498                         line++;
5499                 if (*line == '\0')
5500                         break;
5501                 /* FIXME: More graceful handling of titles; append "..." to
5502                  * shortened titles, etc. */
5504                 string_ncopy(commit->title, line, strlen(line));
5505         }
5507         return TRUE;
5510 static enum request
5511 main_request(struct view *view, enum request request, struct line *line)
5513         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
5515         switch (request) {
5516         case REQ_ENTER:
5517                 open_view(view, REQ_VIEW_DIFF, flags);
5518                 break;
5519         case REQ_REFRESH:
5520                 load_refs();
5521                 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
5522                 break;
5523         default:
5524                 return request;
5525         }
5527         return REQ_NONE;
5530 static bool
5531 grep_refs(struct ref **refs, regex_t *regex)
5533         regmatch_t pmatch;
5534         size_t i = 0;
5536         if (!refs)
5537                 return FALSE;
5538         do {
5539                 if (regexec(regex, refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
5540                         return TRUE;
5541         } while (refs[i++]->next);
5543         return FALSE;
5546 static bool
5547 main_grep(struct view *view, struct line *line)
5549         struct commit *commit = line->data;
5550         enum { S_TITLE, S_AUTHOR, S_DATE, S_REFS, S_END } state;
5551         char buf[DATE_COLS + 1];
5552         regmatch_t pmatch;
5554         for (state = S_TITLE; state < S_END; state++) {
5555                 char *text;
5557                 switch (state) {
5558                 case S_TITLE:   text = commit->title;   break;
5559                 case S_AUTHOR:
5560                         if (!opt_author)
5561                                 continue;
5562                         text = commit->author;
5563                         break;
5564                 case S_DATE:
5565                         if (!opt_date)
5566                                 continue;
5567                         if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
5568                                 continue;
5569                         text = buf;
5570                         break;
5571                 case S_REFS:
5572                         if (!opt_show_refs)
5573                                 continue;
5574                         if (grep_refs(commit->refs, view->regex) == TRUE)
5575                                 return TRUE;
5576                         continue;
5577                 default:
5578                         return FALSE;
5579                 }
5581                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
5582                         return TRUE;
5583         }
5585         return FALSE;
5588 static void
5589 main_select(struct view *view, struct line *line)
5591         struct commit *commit = line->data;
5593         string_copy_rev(view->ref, commit->id);
5594         string_copy_rev(ref_commit, view->ref);
5597 static struct view_ops main_ops = {
5598         "commit",
5599         main_argv,
5600         NULL,
5601         main_read,
5602         main_draw,
5603         main_request,
5604         main_grep,
5605         main_select,
5606 };
5609 /*
5610  * Unicode / UTF-8 handling
5611  *
5612  * NOTE: Much of the following code for dealing with unicode is derived from
5613  * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
5614  * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
5615  */
5617 /* I've (over)annotated a lot of code snippets because I am not entirely
5618  * confident that the approach taken by this small UTF-8 interface is correct.
5619  * --jonas */
5621 static inline int
5622 unicode_width(unsigned long c)
5624         if (c >= 0x1100 &&
5625            (c <= 0x115f                         /* Hangul Jamo */
5626             || c == 0x2329
5627             || c == 0x232a
5628             || (c >= 0x2e80  && c <= 0xa4cf && c != 0x303f)
5629                                                 /* CJK ... Yi */
5630             || (c >= 0xac00  && c <= 0xd7a3)    /* Hangul Syllables */
5631             || (c >= 0xf900  && c <= 0xfaff)    /* CJK Compatibility Ideographs */
5632             || (c >= 0xfe30  && c <= 0xfe6f)    /* CJK Compatibility Forms */
5633             || (c >= 0xff00  && c <= 0xff60)    /* Fullwidth Forms */
5634             || (c >= 0xffe0  && c <= 0xffe6)
5635             || (c >= 0x20000 && c <= 0x2fffd)
5636             || (c >= 0x30000 && c <= 0x3fffd)))
5637                 return 2;
5639         if (c == '\t')
5640                 return opt_tab_size;
5642         return 1;
5645 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
5646  * Illegal bytes are set one. */
5647 static const unsigned char utf8_bytes[256] = {
5648         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,
5649         1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
5650         1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
5651         1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
5652         1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
5653         1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
5654         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,
5655         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,
5656 };
5658 /* Decode UTF-8 multi-byte representation into a unicode character. */
5659 static inline unsigned long
5660 utf8_to_unicode(const char *string, size_t length)
5662         unsigned long unicode;
5664         switch (length) {
5665         case 1:
5666                 unicode  =   string[0];
5667                 break;
5668         case 2:
5669                 unicode  =  (string[0] & 0x1f) << 6;
5670                 unicode +=  (string[1] & 0x3f);
5671                 break;
5672         case 3:
5673                 unicode  =  (string[0] & 0x0f) << 12;
5674                 unicode += ((string[1] & 0x3f) << 6);
5675                 unicode +=  (string[2] & 0x3f);
5676                 break;
5677         case 4:
5678                 unicode  =  (string[0] & 0x0f) << 18;
5679                 unicode += ((string[1] & 0x3f) << 12);
5680                 unicode += ((string[2] & 0x3f) << 6);
5681                 unicode +=  (string[3] & 0x3f);
5682                 break;
5683         case 5:
5684                 unicode  =  (string[0] & 0x0f) << 24;
5685                 unicode += ((string[1] & 0x3f) << 18);
5686                 unicode += ((string[2] & 0x3f) << 12);
5687                 unicode += ((string[3] & 0x3f) << 6);
5688                 unicode +=  (string[4] & 0x3f);
5689                 break;
5690         case 6:
5691                 unicode  =  (string[0] & 0x01) << 30;
5692                 unicode += ((string[1] & 0x3f) << 24);
5693                 unicode += ((string[2] & 0x3f) << 18);
5694                 unicode += ((string[3] & 0x3f) << 12);
5695                 unicode += ((string[4] & 0x3f) << 6);
5696                 unicode +=  (string[5] & 0x3f);
5697                 break;
5698         default:
5699                 die("Invalid unicode length");
5700         }
5702         /* Invalid characters could return the special 0xfffd value but NUL
5703          * should be just as good. */
5704         return unicode > 0xffff ? 0 : unicode;
5707 /* Calculates how much of string can be shown within the given maximum width
5708  * and sets trimmed parameter to non-zero value if all of string could not be
5709  * shown. If the reserve flag is TRUE, it will reserve at least one
5710  * trailing character, which can be useful when drawing a delimiter.
5711  *
5712  * Returns the number of bytes to output from string to satisfy max_width. */
5713 static size_t
5714 utf8_length(const char *string, int *width, size_t max_width, int *trimmed, bool reserve)
5716         const char *start = string;
5717         const char *end = strchr(string, '\0');
5718         unsigned char last_bytes = 0;
5719         size_t last_ucwidth = 0;
5721         *width = 0;
5722         *trimmed = 0;
5724         while (string < end) {
5725                 int c = *(unsigned char *) string;
5726                 unsigned char bytes = utf8_bytes[c];
5727                 size_t ucwidth;
5728                 unsigned long unicode;
5730                 if (string + bytes > end)
5731                         break;
5733                 /* Change representation to figure out whether
5734                  * it is a single- or double-width character. */
5736                 unicode = utf8_to_unicode(string, bytes);
5737                 /* FIXME: Graceful handling of invalid unicode character. */
5738                 if (!unicode)
5739                         break;
5741                 ucwidth = unicode_width(unicode);
5742                 *width  += ucwidth;
5743                 if (*width > max_width) {
5744                         *trimmed = 1;
5745                         *width -= ucwidth;
5746                         if (reserve && *width == max_width) {
5747                                 string -= last_bytes;
5748                                 *width -= last_ucwidth;
5749                         }
5750                         break;
5751                 }
5753                 string  += bytes;
5754                 last_bytes = bytes;
5755                 last_ucwidth = ucwidth;
5756         }
5758         return string - start;
5762 /*
5763  * Status management
5764  */
5766 /* Whether or not the curses interface has been initialized. */
5767 static bool cursed = FALSE;
5769 /* The status window is used for polling keystrokes. */
5770 static WINDOW *status_win;
5772 static bool status_empty = TRUE;
5774 /* Update status and title window. */
5775 static void
5776 report(const char *msg, ...)
5778         struct view *view = display[current_view];
5780         if (input_mode)
5781                 return;
5783         if (!view) {
5784                 char buf[SIZEOF_STR];
5785                 va_list args;
5787                 va_start(args, msg);
5788                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
5789                         buf[sizeof(buf) - 1] = 0;
5790                         buf[sizeof(buf) - 2] = '.';
5791                         buf[sizeof(buf) - 3] = '.';
5792                         buf[sizeof(buf) - 4] = '.';
5793                 }
5794                 va_end(args);
5795                 die("%s", buf);
5796         }
5798         if (!status_empty || *msg) {
5799                 va_list args;
5801                 va_start(args, msg);
5803                 wmove(status_win, 0, 0);
5804                 if (*msg) {
5805                         vwprintw(status_win, msg, args);
5806                         status_empty = FALSE;
5807                 } else {
5808                         status_empty = TRUE;
5809                 }
5810                 wclrtoeol(status_win);
5811                 wrefresh(status_win);
5813                 va_end(args);
5814         }
5816         update_view_title(view);
5817         update_display_cursor(view);
5820 /* Controls when nodelay should be in effect when polling user input. */
5821 static void
5822 set_nonblocking_input(bool loading)
5824         static unsigned int loading_views;
5826         if ((loading == FALSE && loading_views-- == 1) ||
5827             (loading == TRUE  && loading_views++ == 0))
5828                 nodelay(status_win, loading);
5831 static void
5832 init_display(void)
5834         int x, y;
5836         /* Initialize the curses library */
5837         if (isatty(STDIN_FILENO)) {
5838                 cursed = !!initscr();
5839                 opt_tty = stdin;
5840         } else {
5841                 /* Leave stdin and stdout alone when acting as a pager. */
5842                 opt_tty = fopen("/dev/tty", "r+");
5843                 if (!opt_tty)
5844                         die("Failed to open /dev/tty");
5845                 cursed = !!newterm(NULL, opt_tty, opt_tty);
5846         }
5848         if (!cursed)
5849                 die("Failed to initialize curses");
5851         nonl();         /* Tell curses not to do NL->CR/NL on output */
5852         cbreak();       /* Take input chars one at a time, no wait for \n */
5853         noecho();       /* Don't echo input */
5854         leaveok(stdscr, TRUE);
5856         if (has_colors())
5857                 init_colors();
5859         getmaxyx(stdscr, y, x);
5860         status_win = newwin(1, 0, y - 1, 0);
5861         if (!status_win)
5862                 die("Failed to create status window");
5864         /* Enable keyboard mapping */
5865         keypad(status_win, TRUE);
5866         wbkgdset(status_win, get_line_attr(LINE_STATUS));
5868         TABSIZE = opt_tab_size;
5869         if (opt_line_graphics) {
5870                 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
5871         }
5874 static bool
5875 prompt_yesno(const char *prompt)
5877         enum { WAIT, STOP, CANCEL  } status = WAIT;
5878         bool answer = FALSE;
5880         while (status == WAIT) {
5881                 struct view *view;
5882                 int i, key;
5884                 input_mode = TRUE;
5886                 foreach_view (view, i)
5887                         update_view(view);
5889                 input_mode = FALSE;
5891                 mvwprintw(status_win, 0, 0, "%s [Yy]/[Nn]", prompt);
5892                 wclrtoeol(status_win);
5894                 /* Refresh, accept single keystroke of input */
5895                 key = wgetch(status_win);
5896                 switch (key) {
5897                 case ERR:
5898                         break;
5900                 case 'y':
5901                 case 'Y':
5902                         answer = TRUE;
5903                         status = STOP;
5904                         break;
5906                 case KEY_ESC:
5907                 case KEY_RETURN:
5908                 case KEY_ENTER:
5909                 case KEY_BACKSPACE:
5910                 case 'n':
5911                 case 'N':
5912                 case '\n':
5913                 default:
5914                         answer = FALSE;
5915                         status = CANCEL;
5916                 }
5917         }
5919         /* Clear the status window */
5920         status_empty = FALSE;
5921         report("");
5923         return answer;
5926 static char *
5927 read_prompt(const char *prompt)
5929         enum { READING, STOP, CANCEL } status = READING;
5930         static char buf[SIZEOF_STR];
5931         int pos = 0;
5933         while (status == READING) {
5934                 struct view *view;
5935                 int i, key;
5937                 input_mode = TRUE;
5939                 foreach_view (view, i)
5940                         update_view(view);
5942                 input_mode = FALSE;
5944                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
5945                 wclrtoeol(status_win);
5947                 /* Refresh, accept single keystroke of input */
5948                 key = wgetch(status_win);
5949                 switch (key) {
5950                 case KEY_RETURN:
5951                 case KEY_ENTER:
5952                 case '\n':
5953                         status = pos ? STOP : CANCEL;
5954                         break;
5956                 case KEY_BACKSPACE:
5957                         if (pos > 0)
5958                                 pos--;
5959                         else
5960                                 status = CANCEL;
5961                         break;
5963                 case KEY_ESC:
5964                         status = CANCEL;
5965                         break;
5967                 case ERR:
5968                         break;
5970                 default:
5971                         if (pos >= sizeof(buf)) {
5972                                 report("Input string too long");
5973                                 return NULL;
5974                         }
5976                         if (isprint(key))
5977                                 buf[pos++] = (char) key;
5978                 }
5979         }
5981         /* Clear the status window */
5982         status_empty = FALSE;
5983         report("");
5985         if (status == CANCEL)
5986                 return NULL;
5988         buf[pos++] = 0;
5990         return buf;
5993 /*
5994  * Repository references
5995  */
5997 static struct ref *refs = NULL;
5998 static size_t refs_alloc = 0;
5999 static size_t refs_size = 0;
6001 /* Id <-> ref store */
6002 static struct ref ***id_refs = NULL;
6003 static size_t id_refs_alloc = 0;
6004 static size_t id_refs_size = 0;
6006 static int
6007 compare_refs(const void *ref1_, const void *ref2_)
6009         const struct ref *ref1 = *(const struct ref **)ref1_;
6010         const struct ref *ref2 = *(const struct ref **)ref2_;
6012         if (ref1->tag != ref2->tag)
6013                 return ref2->tag - ref1->tag;
6014         if (ref1->ltag != ref2->ltag)
6015                 return ref2->ltag - ref2->ltag;
6016         if (ref1->head != ref2->head)
6017                 return ref2->head - ref1->head;
6018         if (ref1->tracked != ref2->tracked)
6019                 return ref2->tracked - ref1->tracked;
6020         if (ref1->remote != ref2->remote)
6021                 return ref2->remote - ref1->remote;
6022         return strcmp(ref1->name, ref2->name);
6025 static struct ref **
6026 get_refs(const char *id)
6028         struct ref ***tmp_id_refs;
6029         struct ref **ref_list = NULL;
6030         size_t ref_list_alloc = 0;
6031         size_t ref_list_size = 0;
6032         size_t i;
6034         for (i = 0; i < id_refs_size; i++)
6035                 if (!strcmp(id, id_refs[i][0]->id))
6036                         return id_refs[i];
6038         tmp_id_refs = realloc_items(id_refs, &id_refs_alloc, id_refs_size + 1,
6039                                     sizeof(*id_refs));
6040         if (!tmp_id_refs)
6041                 return NULL;
6043         id_refs = tmp_id_refs;
6045         for (i = 0; i < refs_size; i++) {
6046                 struct ref **tmp;
6048                 if (strcmp(id, refs[i].id))
6049                         continue;
6051                 tmp = realloc_items(ref_list, &ref_list_alloc,
6052                                     ref_list_size + 1, sizeof(*ref_list));
6053                 if (!tmp) {
6054                         if (ref_list)
6055                                 free(ref_list);
6056                         return NULL;
6057                 }
6059                 ref_list = tmp;
6060                 ref_list[ref_list_size] = &refs[i];
6061                 /* XXX: The properties of the commit chains ensures that we can
6062                  * safely modify the shared ref. The repo references will
6063                  * always be similar for the same id. */
6064                 ref_list[ref_list_size]->next = 1;
6066                 ref_list_size++;
6067         }
6069         if (ref_list) {
6070                 qsort(ref_list, ref_list_size, sizeof(*ref_list), compare_refs);
6071                 ref_list[ref_list_size - 1]->next = 0;
6072                 id_refs[id_refs_size++] = ref_list;
6073         }
6075         return ref_list;
6078 static int
6079 read_ref(char *id, size_t idlen, char *name, size_t namelen)
6081         struct ref *ref;
6082         bool tag = FALSE;
6083         bool ltag = FALSE;
6084         bool remote = FALSE;
6085         bool tracked = FALSE;
6086         bool check_replace = FALSE;
6087         bool head = FALSE;
6089         if (!prefixcmp(name, "refs/tags/")) {
6090                 if (!suffixcmp(name, namelen, "^{}")) {
6091                         namelen -= 3;
6092                         name[namelen] = 0;
6093                         if (refs_size > 0 && refs[refs_size - 1].ltag == TRUE)
6094                                 check_replace = TRUE;
6095                 } else {
6096                         ltag = TRUE;
6097                 }
6099                 tag = TRUE;
6100                 namelen -= STRING_SIZE("refs/tags/");
6101                 name    += STRING_SIZE("refs/tags/");
6103         } else if (!prefixcmp(name, "refs/remotes/")) {
6104                 remote = TRUE;
6105                 namelen -= STRING_SIZE("refs/remotes/");
6106                 name    += STRING_SIZE("refs/remotes/");
6107                 tracked  = !strcmp(opt_remote, name);
6109         } else if (!prefixcmp(name, "refs/heads/")) {
6110                 namelen -= STRING_SIZE("refs/heads/");
6111                 name    += STRING_SIZE("refs/heads/");
6112                 head     = !strncmp(opt_head, name, namelen);
6114         } else if (!strcmp(name, "HEAD")) {
6115                 string_ncopy(opt_head_rev, id, idlen);
6116                 return OK;
6117         }
6119         if (check_replace && !strcmp(name, refs[refs_size - 1].name)) {
6120                 /* it's an annotated tag, replace the previous sha1 with the
6121                  * resolved commit id; relies on the fact git-ls-remote lists
6122                  * the commit id of an annotated tag right before the commit id
6123                  * it points to. */
6124                 refs[refs_size - 1].ltag = ltag;
6125                 string_copy_rev(refs[refs_size - 1].id, id);
6127                 return OK;
6128         }
6129         refs = realloc_items(refs, &refs_alloc, refs_size + 1, sizeof(*refs));
6130         if (!refs)
6131                 return ERR;
6133         ref = &refs[refs_size++];
6134         ref->name = malloc(namelen + 1);
6135         if (!ref->name)
6136                 return ERR;
6138         strncpy(ref->name, name, namelen);
6139         ref->name[namelen] = 0;
6140         ref->head = head;
6141         ref->tag = tag;
6142         ref->ltag = ltag;
6143         ref->remote = remote;
6144         ref->tracked = tracked;
6145         string_copy_rev(ref->id, id);
6147         return OK;
6150 static int
6151 load_refs(void)
6153         const char *cmd_env = getenv("TIG_LS_REMOTE");
6154         const char *cmd = cmd_env && *cmd_env ? cmd_env : TIG_LS_REMOTE;
6156         if (!*opt_git_dir)
6157                 return OK;
6159         while (refs_size > 0)
6160                 free(refs[--refs_size].name);
6161         while (id_refs_size > 0)
6162                 free(id_refs[--id_refs_size]);
6164         return read_properties(popen(cmd, "r"), "\t", read_ref);
6167 static int
6168 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
6170         if (!strcmp(name, "i18n.commitencoding"))
6171                 string_ncopy(opt_encoding, value, valuelen);
6173         if (!strcmp(name, "core.editor"))
6174                 string_ncopy(opt_editor, value, valuelen);
6176         /* branch.<head>.remote */
6177         if (*opt_head &&
6178             !strncmp(name, "branch.", 7) &&
6179             !strncmp(name + 7, opt_head, strlen(opt_head)) &&
6180             !strcmp(name + 7 + strlen(opt_head), ".remote"))
6181                 string_ncopy(opt_remote, value, valuelen);
6183         if (*opt_head && *opt_remote &&
6184             !strncmp(name, "branch.", 7) &&
6185             !strncmp(name + 7, opt_head, strlen(opt_head)) &&
6186             !strcmp(name + 7 + strlen(opt_head), ".merge")) {
6187                 size_t from = strlen(opt_remote);
6189                 if (!prefixcmp(value, "refs/heads/")) {
6190                         value += STRING_SIZE("refs/heads/");
6191                         valuelen -= STRING_SIZE("refs/heads/");
6192                 }
6194                 if (!string_format_from(opt_remote, &from, "/%s", value))
6195                         opt_remote[0] = 0;
6196         }
6198         return OK;
6201 static int
6202 load_git_config(void)
6204         return read_properties(popen("git " GIT_CONFIG " --list", "r"),
6205                                "=", read_repo_config_option);
6208 static int
6209 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
6211         if (!opt_git_dir[0]) {
6212                 string_ncopy(opt_git_dir, name, namelen);
6214         } else if (opt_is_inside_work_tree == -1) {
6215                 /* This can be 3 different values depending on the
6216                  * version of git being used. If git-rev-parse does not
6217                  * understand --is-inside-work-tree it will simply echo
6218                  * the option else either "true" or "false" is printed.
6219                  * Default to true for the unknown case. */
6220                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
6222         } else if (opt_cdup[0] == ' ') {
6223                 string_ncopy(opt_cdup, name, namelen);
6224         } else {
6225                 if (!prefixcmp(name, "refs/heads/")) {
6226                         namelen -= STRING_SIZE("refs/heads/");
6227                         name    += STRING_SIZE("refs/heads/");
6228                         string_ncopy(opt_head, name, namelen);
6229                 }
6230         }
6232         return OK;
6235 static int
6236 load_repo_info(void)
6238         int result;
6239         FILE *pipe = popen("(git rev-parse --git-dir --is-inside-work-tree "
6240                            " --show-cdup; git symbolic-ref HEAD) 2>/dev/null", "r");
6242         /* XXX: The line outputted by "--show-cdup" can be empty so
6243          * initialize it to something invalid to make it possible to
6244          * detect whether it has been set or not. */
6245         opt_cdup[0] = ' ';
6247         result = read_properties(pipe, "=", read_repo_info);
6248         if (opt_cdup[0] == ' ')
6249                 opt_cdup[0] = 0;
6251         return result;
6254 static int
6255 read_properties(FILE *pipe, const char *separators,
6256                 int (*read_property)(char *, size_t, char *, size_t))
6258         char buffer[BUFSIZ];
6259         char *name;
6260         int state = OK;
6262         if (!pipe)
6263                 return ERR;
6265         while (state == OK && (name = fgets(buffer, sizeof(buffer), pipe))) {
6266                 char *value;
6267                 size_t namelen;
6268                 size_t valuelen;
6270                 name = chomp_string(name);
6271                 namelen = strcspn(name, separators);
6273                 if (name[namelen]) {
6274                         name[namelen] = 0;
6275                         value = chomp_string(name + namelen + 1);
6276                         valuelen = strlen(value);
6278                 } else {
6279                         value = "";
6280                         valuelen = 0;
6281                 }
6283                 state = read_property(name, namelen, value, valuelen);
6284         }
6286         if (state != ERR && ferror(pipe))
6287                 state = ERR;
6289         pclose(pipe);
6291         return state;
6295 /*
6296  * Main
6297  */
6299 static void __NORETURN
6300 quit(int sig)
6302         /* XXX: Restore tty modes and let the OS cleanup the rest! */
6303         if (cursed)
6304                 endwin();
6305         exit(0);
6308 static void __NORETURN
6309 die(const char *err, ...)
6311         va_list args;
6313         endwin();
6315         va_start(args, err);
6316         fputs("tig: ", stderr);
6317         vfprintf(stderr, err, args);
6318         fputs("\n", stderr);
6319         va_end(args);
6321         exit(1);
6324 static void
6325 warn(const char *msg, ...)
6327         va_list args;
6329         va_start(args, msg);
6330         fputs("tig warning: ", stderr);
6331         vfprintf(stderr, msg, args);
6332         fputs("\n", stderr);
6333         va_end(args);
6336 int
6337 main(int argc, const char *argv[])
6339         struct view *view;
6340         enum request request;
6341         size_t i;
6343         signal(SIGINT, quit);
6345         if (setlocale(LC_ALL, "")) {
6346                 char *codeset = nl_langinfo(CODESET);
6348                 string_ncopy(opt_codeset, codeset, strlen(codeset));
6349         }
6351         if (load_repo_info() == ERR)
6352                 die("Failed to load repo info.");
6354         if (load_options() == ERR)
6355                 die("Failed to load user config.");
6357         if (load_git_config() == ERR)
6358                 die("Failed to load repo config.");
6360         request = parse_options(argc, argv);
6361         if (request == REQ_NONE)
6362                 return 0;
6364         /* Require a git repository unless when running in pager mode. */
6365         if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
6366                 die("Not a git repository");
6368         if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
6369                 opt_utf8 = FALSE;
6371         if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
6372                 opt_iconv = iconv_open(opt_codeset, opt_encoding);
6373                 if (opt_iconv == ICONV_NONE)
6374                         die("Failed to initialize character set conversion");
6375         }
6377         if (load_refs() == ERR)
6378                 die("Failed to load refs.");
6380         foreach_view (view, i)
6381                 argv_from_env(view->ops->argv, view->cmd_env);
6383         init_display();
6385         while (view_driver(display[current_view], request)) {
6386                 int key;
6387                 int i;
6389                 foreach_view (view, i)
6390                         update_view(view);
6391                 view = display[current_view];
6393                 /* Refresh, accept single keystroke of input */
6394                 key = wgetch(status_win);
6396                 /* wgetch() with nodelay() enabled returns ERR when there's no
6397                  * input. */
6398                 if (key == ERR) {
6399                         request = REQ_NONE;
6400                         continue;
6401                 }
6403                 request = get_keybinding(view->keymap, key);
6405                 /* Some low-level request handling. This keeps access to
6406                  * status_win restricted. */
6407                 switch (request) {
6408                 case REQ_PROMPT:
6409                 {
6410                         char *cmd = read_prompt(":");
6412                         if (cmd) {
6413                                 struct view *next = VIEW(REQ_VIEW_PAGER);
6414                                 const char *argv[SIZEOF_ARG] = { "git" };
6415                                 int argc = 1;
6417                                 /* When running random commands, initially show the
6418                                  * command in the title. However, it maybe later be
6419                                  * overwritten if a commit line is selected. */
6420                                 string_ncopy(next->ref, cmd, strlen(cmd));
6422                                 if (!argv_from_string(argv, &argc, cmd)) {
6423                                         report("Too many arguments");
6424                                 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
6425                                         report("Failed to format command");
6426                                 } else {
6427                                         open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
6428                                 }
6429                         }
6431                         request = REQ_NONE;
6432                         break;
6433                 }
6434                 case REQ_SEARCH:
6435                 case REQ_SEARCH_BACK:
6436                 {
6437                         const char *prompt = request == REQ_SEARCH ? "/" : "?";
6438                         char *search = read_prompt(prompt);
6440                         if (search)
6441                                 string_ncopy(opt_search, search, strlen(search));
6442                         else
6443                                 request = REQ_NONE;
6444                         break;
6445                 }
6446                 case REQ_SCREEN_RESIZE:
6447                 {
6448                         int height, width;
6450                         getmaxyx(stdscr, height, width);
6452                         /* Resize the status view and let the view driver take
6453                          * care of resizing the displayed views. */
6454                         wresize(status_win, 1, width);
6455                         mvwin(status_win, height - 1, 0);
6456                         wrefresh(status_win);
6457                         break;
6458                 }
6459                 default:
6460                         break;
6461                 }
6462         }
6464         quit(0);
6466         return 0;