Code

IO API: use for the main, log, diff, tree and blob views
[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_format(struct io *io, const char *cmd, ...)
467         va_list args;
469         va_start(args, cmd);
470         init_io(io, NULL, IO_RD);
472         if (vsnprintf(io->sh, sizeof(io->sh), cmd, args) >= sizeof(io->sh))
473                 io->sh[0] = 0;
474         va_end(args);
476         return io->sh[0] ? start_io(io) : FALSE;
479 static bool
480 run_io_rd(struct io *io, const char **argv, enum format_flags flags)
482         return init_io_rd(io, argv, NULL, flags) && start_io(io);
485 static bool
486 io_eof(struct io *io)
488         return feof(io->pipe);
491 static int
492 io_error(struct io *io)
494         return io->error;
497 static bool
498 io_strerror(struct io *io)
500         return strerror(io->error);
503 static char *
504 io_gets(struct io *io)
506         if (!io->buf) {
507                 io->buf = malloc(BUFSIZ);
508                 if (!io->buf)
509                         return NULL;
510                 io->bufalloc = BUFSIZ;
511         }
513         if (!fgets(io->buf, io->bufalloc, io->pipe)) {
514                 if (ferror(io->pipe))
515                         io->error = errno;
516                 return NULL;
517         }
519         return io->buf;
523 /*
524  * User requests
525  */
527 #define REQ_INFO \
528         /* XXX: Keep the view request first and in sync with views[]. */ \
529         REQ_GROUP("View switching") \
530         REQ_(VIEW_MAIN,         "Show main view"), \
531         REQ_(VIEW_DIFF,         "Show diff view"), \
532         REQ_(VIEW_LOG,          "Show log view"), \
533         REQ_(VIEW_TREE,         "Show tree view"), \
534         REQ_(VIEW_BLOB,         "Show blob view"), \
535         REQ_(VIEW_BLAME,        "Show blame view"), \
536         REQ_(VIEW_HELP,         "Show help page"), \
537         REQ_(VIEW_PAGER,        "Show pager view"), \
538         REQ_(VIEW_STATUS,       "Show status view"), \
539         REQ_(VIEW_STAGE,        "Show stage view"), \
540         \
541         REQ_GROUP("View manipulation") \
542         REQ_(ENTER,             "Enter current line and scroll"), \
543         REQ_(NEXT,              "Move to next"), \
544         REQ_(PREVIOUS,          "Move to previous"), \
545         REQ_(VIEW_NEXT,         "Move focus to next view"), \
546         REQ_(REFRESH,           "Reload and refresh"), \
547         REQ_(MAXIMIZE,          "Maximize the current view"), \
548         REQ_(VIEW_CLOSE,        "Close the current view"), \
549         REQ_(QUIT,              "Close all views and quit"), \
550         \
551         REQ_GROUP("View specific requests") \
552         REQ_(STATUS_UPDATE,     "Update file status"), \
553         REQ_(STATUS_REVERT,     "Revert file changes"), \
554         REQ_(STATUS_MERGE,      "Merge file using external tool"), \
555         REQ_(STAGE_NEXT,        "Find next chunk to stage"), \
556         REQ_(TREE_PARENT,       "Switch to parent directory in tree view"), \
557         \
558         REQ_GROUP("Cursor navigation") \
559         REQ_(MOVE_UP,           "Move cursor one line up"), \
560         REQ_(MOVE_DOWN,         "Move cursor one line down"), \
561         REQ_(MOVE_PAGE_DOWN,    "Move cursor one page down"), \
562         REQ_(MOVE_PAGE_UP,      "Move cursor one page up"), \
563         REQ_(MOVE_FIRST_LINE,   "Move cursor to first line"), \
564         REQ_(MOVE_LAST_LINE,    "Move cursor to last line"), \
565         \
566         REQ_GROUP("Scrolling") \
567         REQ_(SCROLL_LINE_UP,    "Scroll one line up"), \
568         REQ_(SCROLL_LINE_DOWN,  "Scroll one line down"), \
569         REQ_(SCROLL_PAGE_UP,    "Scroll one page up"), \
570         REQ_(SCROLL_PAGE_DOWN,  "Scroll one page down"), \
571         \
572         REQ_GROUP("Searching") \
573         REQ_(SEARCH,            "Search the view"), \
574         REQ_(SEARCH_BACK,       "Search backwards in the view"), \
575         REQ_(FIND_NEXT,         "Find next search match"), \
576         REQ_(FIND_PREV,         "Find previous search match"), \
577         \
578         REQ_GROUP("Option manipulation") \
579         REQ_(TOGGLE_LINENO,     "Toggle line numbers"), \
580         REQ_(TOGGLE_DATE,       "Toggle date display"), \
581         REQ_(TOGGLE_AUTHOR,     "Toggle author display"), \
582         REQ_(TOGGLE_REV_GRAPH,  "Toggle revision graph visualization"), \
583         REQ_(TOGGLE_REFS,       "Toggle reference display (tags/branches)"), \
584         \
585         REQ_GROUP("Misc") \
586         REQ_(PROMPT,            "Bring up the prompt"), \
587         REQ_(SCREEN_REDRAW,     "Redraw the screen"), \
588         REQ_(SCREEN_RESIZE,     "Resize the screen"), \
589         REQ_(SHOW_VERSION,      "Show version information"), \
590         REQ_(STOP_LOADING,      "Stop all loading views"), \
591         REQ_(EDIT,              "Open in editor"), \
592         REQ_(NONE,              "Do nothing")
595 /* User action requests. */
596 enum request {
597 #define REQ_GROUP(help)
598 #define REQ_(req, help) REQ_##req
600         /* Offset all requests to avoid conflicts with ncurses getch values. */
601         REQ_OFFSET = KEY_MAX + 1,
602         REQ_INFO
604 #undef  REQ_GROUP
605 #undef  REQ_
606 };
608 struct request_info {
609         enum request request;
610         const char *name;
611         int namelen;
612         const char *help;
613 };
615 static struct request_info req_info[] = {
616 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
617 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
618         REQ_INFO
619 #undef  REQ_GROUP
620 #undef  REQ_
621 };
623 static enum request
624 get_request(const char *name)
626         int namelen = strlen(name);
627         int i;
629         for (i = 0; i < ARRAY_SIZE(req_info); i++)
630                 if (req_info[i].namelen == namelen &&
631                     !string_enum_compare(req_info[i].name, name, namelen))
632                         return req_info[i].request;
634         return REQ_NONE;
638 /*
639  * Options
640  */
642 static const char usage[] =
643 "tig " TIG_VERSION " (" __DATE__ ")\n"
644 "\n"
645 "Usage: tig        [options] [revs] [--] [paths]\n"
646 "   or: tig show   [options] [revs] [--] [paths]\n"
647 "   or: tig blame  [rev] path\n"
648 "   or: tig status\n"
649 "   or: tig <      [git command output]\n"
650 "\n"
651 "Options:\n"
652 "  -v, --version   Show version and exit\n"
653 "  -h, --help      Show help message and exit";
655 /* Option and state variables. */
656 static bool opt_date                    = TRUE;
657 static bool opt_author                  = TRUE;
658 static bool opt_line_number             = FALSE;
659 static bool opt_line_graphics           = TRUE;
660 static bool opt_rev_graph               = FALSE;
661 static bool opt_show_refs               = TRUE;
662 static int opt_num_interval             = NUMBER_INTERVAL;
663 static int opt_tab_size                 = TAB_SIZE;
664 static int opt_author_cols              = AUTHOR_COLS-1;
665 static char opt_cmd[SIZEOF_STR]         = "";
666 static char opt_path[SIZEOF_STR]        = "";
667 static char opt_file[SIZEOF_STR]        = "";
668 static char opt_ref[SIZEOF_REF]         = "";
669 static char opt_head[SIZEOF_REF]        = "";
670 static char opt_head_rev[SIZEOF_REV]    = "";
671 static char opt_remote[SIZEOF_REF]      = "";
672 static FILE *opt_pipe                   = NULL;
673 static char opt_encoding[20]            = "UTF-8";
674 static bool opt_utf8                    = TRUE;
675 static char opt_codeset[20]             = "UTF-8";
676 static iconv_t opt_iconv                = ICONV_NONE;
677 static char opt_search[SIZEOF_STR]      = "";
678 static char opt_cdup[SIZEOF_STR]        = "";
679 static char opt_git_dir[SIZEOF_STR]     = "";
680 static signed char opt_is_inside_work_tree      = -1; /* set to TRUE or FALSE */
681 static char opt_editor[SIZEOF_STR]      = "";
682 static FILE *opt_tty                    = NULL;
684 #define is_initial_commit()     (!*opt_head_rev)
685 #define is_head_commit(rev)     (!strcmp((rev), "HEAD") || !strcmp(opt_head_rev, (rev)))
687 static enum request
688 parse_options(int argc, const char *argv[])
690         enum request request = REQ_VIEW_MAIN;
691         size_t buf_size;
692         const char *subcommand;
693         bool seen_dashdash = FALSE;
694         int i;
696         if (!isatty(STDIN_FILENO)) {
697                 opt_pipe = stdin;
698                 return REQ_VIEW_PAGER;
699         }
701         if (argc <= 1)
702                 return REQ_VIEW_MAIN;
704         subcommand = argv[1];
705         if (!strcmp(subcommand, "status") || !strcmp(subcommand, "-S")) {
706                 if (!strcmp(subcommand, "-S"))
707                         warn("`-S' has been deprecated; use `tig status' instead");
708                 if (argc > 2)
709                         warn("ignoring arguments after `%s'", subcommand);
710                 return REQ_VIEW_STATUS;
712         } else if (!strcmp(subcommand, "blame")) {
713                 if (argc <= 2 || argc > 4)
714                         die("invalid number of options to blame\n\n%s", usage);
716                 i = 2;
717                 if (argc == 4) {
718                         string_ncopy(opt_ref, argv[i], strlen(argv[i]));
719                         i++;
720                 }
722                 string_ncopy(opt_file, argv[i], strlen(argv[i]));
723                 return REQ_VIEW_BLAME;
725         } else if (!strcmp(subcommand, "show")) {
726                 request = REQ_VIEW_DIFF;
728         } else if (!strcmp(subcommand, "log") || !strcmp(subcommand, "diff")) {
729                 request = subcommand[0] == 'l' ? REQ_VIEW_LOG : REQ_VIEW_DIFF;
730                 warn("`tig %s' has been deprecated", subcommand);
732         } else {
733                 subcommand = NULL;
734         }
736         if (!subcommand)
737                 /* XXX: This is vulnerable to the user overriding
738                  * options required for the main view parser. */
739                 string_copy(opt_cmd, TIG_MAIN_BASE);
740         else
741                 string_format(opt_cmd, "git %s", subcommand);
743         buf_size = strlen(opt_cmd);
745         for (i = 1 + !!subcommand; i < argc; i++) {
746                 const char *opt = argv[i];
748                 if (seen_dashdash || !strcmp(opt, "--")) {
749                         seen_dashdash = TRUE;
751                 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
752                         printf("tig version %s\n", TIG_VERSION);
753                         return REQ_NONE;
755                 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
756                         printf("%s\n", usage);
757                         return REQ_NONE;
758                 }
760                 opt_cmd[buf_size++] = ' ';
761                 buf_size = sq_quote(opt_cmd, buf_size, opt);
762                 if (buf_size >= sizeof(opt_cmd))
763                         die("command too long");
764         }
766         opt_cmd[buf_size] = 0;
768         return request;
772 /*
773  * Line-oriented content detection.
774  */
776 #define LINE_INFO \
777 LINE(DIFF_HEADER,  "diff --git ",       COLOR_YELLOW,   COLOR_DEFAULT,  0), \
778 LINE(DIFF_CHUNK,   "@@",                COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
779 LINE(DIFF_ADD,     "+",                 COLOR_GREEN,    COLOR_DEFAULT,  0), \
780 LINE(DIFF_DEL,     "-",                 COLOR_RED,      COLOR_DEFAULT,  0), \
781 LINE(DIFF_INDEX,        "index ",         COLOR_BLUE,   COLOR_DEFAULT,  0), \
782 LINE(DIFF_OLDMODE,      "old file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
783 LINE(DIFF_NEWMODE,      "new file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
784 LINE(DIFF_COPY_FROM,    "copy from",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
785 LINE(DIFF_COPY_TO,      "copy to",        COLOR_YELLOW, COLOR_DEFAULT,  0), \
786 LINE(DIFF_RENAME_FROM,  "rename from",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
787 LINE(DIFF_RENAME_TO,    "rename to",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
788 LINE(DIFF_SIMILARITY,   "similarity ",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
789 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
790 LINE(DIFF_TREE,         "diff-tree ",     COLOR_BLUE,   COLOR_DEFAULT,  0), \
791 LINE(PP_AUTHOR,    "Author: ",          COLOR_CYAN,     COLOR_DEFAULT,  0), \
792 LINE(PP_COMMIT,    "Commit: ",          COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
793 LINE(PP_MERGE,     "Merge: ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
794 LINE(PP_DATE,      "Date:   ",          COLOR_YELLOW,   COLOR_DEFAULT,  0), \
795 LINE(PP_ADATE,     "AuthorDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
796 LINE(PP_CDATE,     "CommitDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
797 LINE(PP_REFS,      "Refs: ",            COLOR_RED,      COLOR_DEFAULT,  0), \
798 LINE(COMMIT,       "commit ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
799 LINE(PARENT,       "parent ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
800 LINE(TREE,         "tree ",             COLOR_BLUE,     COLOR_DEFAULT,  0), \
801 LINE(AUTHOR,       "author ",           COLOR_CYAN,     COLOR_DEFAULT,  0), \
802 LINE(COMMITTER,    "committer ",        COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
803 LINE(SIGNOFF,      "    Signed-off-by", COLOR_YELLOW,   COLOR_DEFAULT,  0), \
804 LINE(ACKED,        "    Acked-by",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
805 LINE(DEFAULT,      "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
806 LINE(CURSOR,       "",                  COLOR_WHITE,    COLOR_GREEN,    A_BOLD), \
807 LINE(STATUS,       "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
808 LINE(DELIMITER,    "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
809 LINE(DATE,         "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
810 LINE(LINE_NUMBER,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
811 LINE(TITLE_BLUR,   "",                  COLOR_WHITE,    COLOR_BLUE,     0), \
812 LINE(TITLE_FOCUS,  "",                  COLOR_WHITE,    COLOR_BLUE,     A_BOLD), \
813 LINE(MAIN_AUTHOR,  "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
814 LINE(MAIN_COMMIT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
815 LINE(MAIN_TAG,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
816 LINE(MAIN_LOCAL_TAG,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
817 LINE(MAIN_REMOTE,  "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
818 LINE(MAIN_TRACKED, "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_BOLD), \
819 LINE(MAIN_REF,     "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
820 LINE(MAIN_HEAD,    "",                  COLOR_CYAN,     COLOR_DEFAULT,  A_BOLD), \
821 LINE(MAIN_REVGRAPH,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
822 LINE(TREE_DIR,     "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
823 LINE(TREE_FILE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
824 LINE(STAT_HEAD,    "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
825 LINE(STAT_SECTION, "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
826 LINE(STAT_NONE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
827 LINE(STAT_STAGED,  "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
828 LINE(STAT_UNSTAGED,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
829 LINE(STAT_UNTRACKED,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
830 LINE(BLAME_ID,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0)
832 enum line_type {
833 #define LINE(type, line, fg, bg, attr) \
834         LINE_##type
835         LINE_INFO,
836         LINE_NONE
837 #undef  LINE
838 };
840 struct line_info {
841         const char *name;       /* Option name. */
842         int namelen;            /* Size of option name. */
843         const char *line;       /* The start of line to match. */
844         int linelen;            /* Size of string to match. */
845         int fg, bg, attr;       /* Color and text attributes for the lines. */
846 };
848 static struct line_info line_info[] = {
849 #define LINE(type, line, fg, bg, attr) \
850         { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
851         LINE_INFO
852 #undef  LINE
853 };
855 static enum line_type
856 get_line_type(const char *line)
858         int linelen = strlen(line);
859         enum line_type type;
861         for (type = 0; type < ARRAY_SIZE(line_info); type++)
862                 /* Case insensitive search matches Signed-off-by lines better. */
863                 if (linelen >= line_info[type].linelen &&
864                     !strncasecmp(line_info[type].line, line, line_info[type].linelen))
865                         return type;
867         return LINE_DEFAULT;
870 static inline int
871 get_line_attr(enum line_type type)
873         assert(type < ARRAY_SIZE(line_info));
874         return COLOR_PAIR(type) | line_info[type].attr;
877 static struct line_info *
878 get_line_info(const char *name)
880         size_t namelen = strlen(name);
881         enum line_type type;
883         for (type = 0; type < ARRAY_SIZE(line_info); type++)
884                 if (namelen == line_info[type].namelen &&
885                     !string_enum_compare(line_info[type].name, name, namelen))
886                         return &line_info[type];
888         return NULL;
891 static void
892 init_colors(void)
894         int default_bg = line_info[LINE_DEFAULT].bg;
895         int default_fg = line_info[LINE_DEFAULT].fg;
896         enum line_type type;
898         start_color();
900         if (assume_default_colors(default_fg, default_bg) == ERR) {
901                 default_bg = COLOR_BLACK;
902                 default_fg = COLOR_WHITE;
903         }
905         for (type = 0; type < ARRAY_SIZE(line_info); type++) {
906                 struct line_info *info = &line_info[type];
907                 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
908                 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
910                 init_pair(type, fg, bg);
911         }
914 struct line {
915         enum line_type type;
917         /* State flags */
918         unsigned int selected:1;
919         unsigned int dirty:1;
921         void *data;             /* User data */
922 };
925 /*
926  * Keys
927  */
929 struct keybinding {
930         int alias;
931         enum request request;
932 };
934 static struct keybinding default_keybindings[] = {
935         /* View switching */
936         { 'm',          REQ_VIEW_MAIN },
937         { 'd',          REQ_VIEW_DIFF },
938         { 'l',          REQ_VIEW_LOG },
939         { 't',          REQ_VIEW_TREE },
940         { 'f',          REQ_VIEW_BLOB },
941         { 'B',          REQ_VIEW_BLAME },
942         { 'p',          REQ_VIEW_PAGER },
943         { 'h',          REQ_VIEW_HELP },
944         { 'S',          REQ_VIEW_STATUS },
945         { 'c',          REQ_VIEW_STAGE },
947         /* View manipulation */
948         { 'q',          REQ_VIEW_CLOSE },
949         { KEY_TAB,      REQ_VIEW_NEXT },
950         { KEY_RETURN,   REQ_ENTER },
951         { KEY_UP,       REQ_PREVIOUS },
952         { KEY_DOWN,     REQ_NEXT },
953         { 'R',          REQ_REFRESH },
954         { KEY_F(5),     REQ_REFRESH },
955         { 'O',          REQ_MAXIMIZE },
957         /* Cursor navigation */
958         { 'k',          REQ_MOVE_UP },
959         { 'j',          REQ_MOVE_DOWN },
960         { KEY_HOME,     REQ_MOVE_FIRST_LINE },
961         { KEY_END,      REQ_MOVE_LAST_LINE },
962         { KEY_NPAGE,    REQ_MOVE_PAGE_DOWN },
963         { ' ',          REQ_MOVE_PAGE_DOWN },
964         { KEY_PPAGE,    REQ_MOVE_PAGE_UP },
965         { 'b',          REQ_MOVE_PAGE_UP },
966         { '-',          REQ_MOVE_PAGE_UP },
968         /* Scrolling */
969         { KEY_IC,       REQ_SCROLL_LINE_UP },
970         { KEY_DC,       REQ_SCROLL_LINE_DOWN },
971         { 'w',          REQ_SCROLL_PAGE_UP },
972         { 's',          REQ_SCROLL_PAGE_DOWN },
974         /* Searching */
975         { '/',          REQ_SEARCH },
976         { '?',          REQ_SEARCH_BACK },
977         { 'n',          REQ_FIND_NEXT },
978         { 'N',          REQ_FIND_PREV },
980         /* Misc */
981         { 'Q',          REQ_QUIT },
982         { 'z',          REQ_STOP_LOADING },
983         { 'v',          REQ_SHOW_VERSION },
984         { 'r',          REQ_SCREEN_REDRAW },
985         { '.',          REQ_TOGGLE_LINENO },
986         { 'D',          REQ_TOGGLE_DATE },
987         { 'A',          REQ_TOGGLE_AUTHOR },
988         { 'g',          REQ_TOGGLE_REV_GRAPH },
989         { 'F',          REQ_TOGGLE_REFS },
990         { ':',          REQ_PROMPT },
991         { 'u',          REQ_STATUS_UPDATE },
992         { '!',          REQ_STATUS_REVERT },
993         { 'M',          REQ_STATUS_MERGE },
994         { '@',          REQ_STAGE_NEXT },
995         { ',',          REQ_TREE_PARENT },
996         { 'e',          REQ_EDIT },
998         /* Using the ncurses SIGWINCH handler. */
999         { KEY_RESIZE,   REQ_SCREEN_RESIZE },
1000 };
1002 #define KEYMAP_INFO \
1003         KEYMAP_(GENERIC), \
1004         KEYMAP_(MAIN), \
1005         KEYMAP_(DIFF), \
1006         KEYMAP_(LOG), \
1007         KEYMAP_(TREE), \
1008         KEYMAP_(BLOB), \
1009         KEYMAP_(BLAME), \
1010         KEYMAP_(PAGER), \
1011         KEYMAP_(HELP), \
1012         KEYMAP_(STATUS), \
1013         KEYMAP_(STAGE)
1015 enum keymap {
1016 #define KEYMAP_(name) KEYMAP_##name
1017         KEYMAP_INFO
1018 #undef  KEYMAP_
1019 };
1021 static struct int_map keymap_table[] = {
1022 #define KEYMAP_(name) { #name, STRING_SIZE(#name), KEYMAP_##name }
1023         KEYMAP_INFO
1024 #undef  KEYMAP_
1025 };
1027 #define set_keymap(map, name) \
1028         set_from_int_map(keymap_table, ARRAY_SIZE(keymap_table), map, name, strlen(name))
1030 struct keybinding_table {
1031         struct keybinding *data;
1032         size_t size;
1033 };
1035 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1037 static void
1038 add_keybinding(enum keymap keymap, enum request request, int key)
1040         struct keybinding_table *table = &keybindings[keymap];
1042         table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1043         if (!table->data)
1044                 die("Failed to allocate keybinding");
1045         table->data[table->size].alias = key;
1046         table->data[table->size++].request = request;
1049 /* Looks for a key binding first in the given map, then in the generic map, and
1050  * lastly in the default keybindings. */
1051 static enum request
1052 get_keybinding(enum keymap keymap, int key)
1054         size_t i;
1056         for (i = 0; i < keybindings[keymap].size; i++)
1057                 if (keybindings[keymap].data[i].alias == key)
1058                         return keybindings[keymap].data[i].request;
1060         for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1061                 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1062                         return keybindings[KEYMAP_GENERIC].data[i].request;
1064         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1065                 if (default_keybindings[i].alias == key)
1066                         return default_keybindings[i].request;
1068         return (enum request) key;
1072 struct key {
1073         const char *name;
1074         int value;
1075 };
1077 static struct key key_table[] = {
1078         { "Enter",      KEY_RETURN },
1079         { "Space",      ' ' },
1080         { "Backspace",  KEY_BACKSPACE },
1081         { "Tab",        KEY_TAB },
1082         { "Escape",     KEY_ESC },
1083         { "Left",       KEY_LEFT },
1084         { "Right",      KEY_RIGHT },
1085         { "Up",         KEY_UP },
1086         { "Down",       KEY_DOWN },
1087         { "Insert",     KEY_IC },
1088         { "Delete",     KEY_DC },
1089         { "Hash",       '#' },
1090         { "Home",       KEY_HOME },
1091         { "End",        KEY_END },
1092         { "PageUp",     KEY_PPAGE },
1093         { "PageDown",   KEY_NPAGE },
1094         { "F1",         KEY_F(1) },
1095         { "F2",         KEY_F(2) },
1096         { "F3",         KEY_F(3) },
1097         { "F4",         KEY_F(4) },
1098         { "F5",         KEY_F(5) },
1099         { "F6",         KEY_F(6) },
1100         { "F7",         KEY_F(7) },
1101         { "F8",         KEY_F(8) },
1102         { "F9",         KEY_F(9) },
1103         { "F10",        KEY_F(10) },
1104         { "F11",        KEY_F(11) },
1105         { "F12",        KEY_F(12) },
1106 };
1108 static int
1109 get_key_value(const char *name)
1111         int i;
1113         for (i = 0; i < ARRAY_SIZE(key_table); i++)
1114                 if (!strcasecmp(key_table[i].name, name))
1115                         return key_table[i].value;
1117         if (strlen(name) == 1 && isprint(*name))
1118                 return (int) *name;
1120         return ERR;
1123 static const char *
1124 get_key_name(int key_value)
1126         static char key_char[] = "'X'";
1127         const char *seq = NULL;
1128         int key;
1130         for (key = 0; key < ARRAY_SIZE(key_table); key++)
1131                 if (key_table[key].value == key_value)
1132                         seq = key_table[key].name;
1134         if (seq == NULL &&
1135             key_value < 127 &&
1136             isprint(key_value)) {
1137                 key_char[1] = (char) key_value;
1138                 seq = key_char;
1139         }
1141         return seq ? seq : "(no key)";
1144 static const char *
1145 get_key(enum request request)
1147         static char buf[BUFSIZ];
1148         size_t pos = 0;
1149         char *sep = "";
1150         int i;
1152         buf[pos] = 0;
1154         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1155                 struct keybinding *keybinding = &default_keybindings[i];
1157                 if (keybinding->request != request)
1158                         continue;
1160                 if (!string_format_from(buf, &pos, "%s%s", sep,
1161                                         get_key_name(keybinding->alias)))
1162                         return "Too many keybindings!";
1163                 sep = ", ";
1164         }
1166         return buf;
1169 struct run_request {
1170         enum keymap keymap;
1171         int key;
1172         const char *argv[SIZEOF_ARG];
1173 };
1175 static struct run_request *run_request;
1176 static size_t run_requests;
1178 static enum request
1179 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1181         struct run_request *req;
1183         if (argc >= ARRAY_SIZE(req->argv) - 1)
1184                 return REQ_NONE;
1186         req = realloc(run_request, (run_requests + 1) * sizeof(*run_request));
1187         if (!req)
1188                 return REQ_NONE;
1190         run_request = req;
1191         req = &run_request[run_requests];
1192         req->keymap = keymap;
1193         req->key = key;
1194         req->argv[0] = NULL;
1196         if (!format_argv(req->argv, argv, FORMAT_NONE))
1197                 return REQ_NONE;
1199         return REQ_NONE + ++run_requests;
1202 static struct run_request *
1203 get_run_request(enum request request)
1205         if (request <= REQ_NONE)
1206                 return NULL;
1207         return &run_request[request - REQ_NONE - 1];
1210 static void
1211 add_builtin_run_requests(void)
1213         const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1214         const char *gc[] = { "git", "gc", NULL };
1215         struct {
1216                 enum keymap keymap;
1217                 int key;
1218                 int argc;
1219                 const char **argv;
1220         } reqs[] = {
1221                 { KEYMAP_MAIN,    'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1222                 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1223         };
1224         int i;
1226         for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1227                 enum request req;
1229                 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1230                 if (req != REQ_NONE)
1231                         add_keybinding(reqs[i].keymap, req, reqs[i].key);
1232         }
1235 /*
1236  * User config file handling.
1237  */
1239 static struct int_map color_map[] = {
1240 #define COLOR_MAP(name) { #name, STRING_SIZE(#name), COLOR_##name }
1241         COLOR_MAP(DEFAULT),
1242         COLOR_MAP(BLACK),
1243         COLOR_MAP(BLUE),
1244         COLOR_MAP(CYAN),
1245         COLOR_MAP(GREEN),
1246         COLOR_MAP(MAGENTA),
1247         COLOR_MAP(RED),
1248         COLOR_MAP(WHITE),
1249         COLOR_MAP(YELLOW),
1250 };
1252 #define set_color(color, name) \
1253         set_from_int_map(color_map, ARRAY_SIZE(color_map), color, name, strlen(name))
1255 static struct int_map attr_map[] = {
1256 #define ATTR_MAP(name) { #name, STRING_SIZE(#name), A_##name }
1257         ATTR_MAP(NORMAL),
1258         ATTR_MAP(BLINK),
1259         ATTR_MAP(BOLD),
1260         ATTR_MAP(DIM),
1261         ATTR_MAP(REVERSE),
1262         ATTR_MAP(STANDOUT),
1263         ATTR_MAP(UNDERLINE),
1264 };
1266 #define set_attribute(attr, name) \
1267         set_from_int_map(attr_map, ARRAY_SIZE(attr_map), attr, name, strlen(name))
1269 static int   config_lineno;
1270 static bool  config_errors;
1271 static const char *config_msg;
1273 /* Wants: object fgcolor bgcolor [attr] */
1274 static int
1275 option_color_command(int argc, const char *argv[])
1277         struct line_info *info;
1279         if (argc != 3 && argc != 4) {
1280                 config_msg = "Wrong number of arguments given to color command";
1281                 return ERR;
1282         }
1284         info = get_line_info(argv[0]);
1285         if (!info) {
1286                 if (!string_enum_compare(argv[0], "main-delim", strlen("main-delim"))) {
1287                         info = get_line_info("delimiter");
1289                 } else if (!string_enum_compare(argv[0], "main-date", strlen("main-date"))) {
1290                         info = get_line_info("date");
1292                 } else {
1293                         config_msg = "Unknown color name";
1294                         return ERR;
1295                 }
1296         }
1298         if (set_color(&info->fg, argv[1]) == ERR ||
1299             set_color(&info->bg, argv[2]) == ERR) {
1300                 config_msg = "Unknown color";
1301                 return ERR;
1302         }
1304         if (argc == 4 && set_attribute(&info->attr, argv[3]) == ERR) {
1305                 config_msg = "Unknown attribute";
1306                 return ERR;
1307         }
1309         return OK;
1312 static bool parse_bool(const char *s)
1314         return (!strcmp(s, "1") || !strcmp(s, "true") ||
1315                 !strcmp(s, "yes")) ? TRUE : FALSE;
1318 static int
1319 parse_int(const char *s, int default_value, int min, int max)
1321         int value = atoi(s);
1323         return (value < min || value > max) ? default_value : value;
1326 /* Wants: name = value */
1327 static int
1328 option_set_command(int argc, const char *argv[])
1330         if (argc != 3) {
1331                 config_msg = "Wrong number of arguments given to set command";
1332                 return ERR;
1333         }
1335         if (strcmp(argv[1], "=")) {
1336                 config_msg = "No value assigned";
1337                 return ERR;
1338         }
1340         if (!strcmp(argv[0], "show-author")) {
1341                 opt_author = parse_bool(argv[2]);
1342                 return OK;
1343         }
1345         if (!strcmp(argv[0], "show-date")) {
1346                 opt_date = parse_bool(argv[2]);
1347                 return OK;
1348         }
1350         if (!strcmp(argv[0], "show-rev-graph")) {
1351                 opt_rev_graph = parse_bool(argv[2]);
1352                 return OK;
1353         }
1355         if (!strcmp(argv[0], "show-refs")) {
1356                 opt_show_refs = parse_bool(argv[2]);
1357                 return OK;
1358         }
1360         if (!strcmp(argv[0], "show-line-numbers")) {
1361                 opt_line_number = parse_bool(argv[2]);
1362                 return OK;
1363         }
1365         if (!strcmp(argv[0], "line-graphics")) {
1366                 opt_line_graphics = parse_bool(argv[2]);
1367                 return OK;
1368         }
1370         if (!strcmp(argv[0], "line-number-interval")) {
1371                 opt_num_interval = parse_int(argv[2], opt_num_interval, 1, 1024);
1372                 return OK;
1373         }
1375         if (!strcmp(argv[0], "author-width")) {
1376                 opt_author_cols = parse_int(argv[2], opt_author_cols, 0, 1024);
1377                 return OK;
1378         }
1380         if (!strcmp(argv[0], "tab-size")) {
1381                 opt_tab_size = parse_int(argv[2], opt_tab_size, 1, 1024);
1382                 return OK;
1383         }
1385         if (!strcmp(argv[0], "commit-encoding")) {
1386                 const char *arg = argv[2];
1387                 int arglen = strlen(arg);
1389                 switch (arg[0]) {
1390                 case '"':
1391                 case '\'':
1392                         if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1393                                 config_msg = "Unmatched quotation";
1394                                 return ERR;
1395                         }
1396                         arg += 1; arglen -= 2;
1397                 default:
1398                         string_ncopy(opt_encoding, arg, strlen(arg));
1399                         return OK;
1400                 }
1401         }
1403         config_msg = "Unknown variable name";
1404         return ERR;
1407 /* Wants: mode request key */
1408 static int
1409 option_bind_command(int argc, const char *argv[])
1411         enum request request;
1412         int keymap;
1413         int key;
1415         if (argc < 3) {
1416                 config_msg = "Wrong number of arguments given to bind command";
1417                 return ERR;
1418         }
1420         if (set_keymap(&keymap, argv[0]) == ERR) {
1421                 config_msg = "Unknown key map";
1422                 return ERR;
1423         }
1425         key = get_key_value(argv[1]);
1426         if (key == ERR) {
1427                 config_msg = "Unknown key";
1428                 return ERR;
1429         }
1431         request = get_request(argv[2]);
1432         if (request == REQ_NONE) {
1433                 const char *obsolete[] = { "cherry-pick" };
1434                 size_t namelen = strlen(argv[2]);
1435                 int i;
1437                 for (i = 0; i < ARRAY_SIZE(obsolete); i++) {
1438                         if (namelen == strlen(obsolete[i]) &&
1439                             !string_enum_compare(obsolete[i], argv[2], namelen)) {
1440                                 config_msg = "Obsolete request name";
1441                                 return ERR;
1442                         }
1443                 }
1444         }
1445         if (request == REQ_NONE && *argv[2]++ == '!')
1446                 request = add_run_request(keymap, key, argc - 2, argv + 2);
1447         if (request == REQ_NONE) {
1448                 config_msg = "Unknown request name";
1449                 return ERR;
1450         }
1452         add_keybinding(keymap, request, key);
1454         return OK;
1457 static int
1458 set_option(const char *opt, char *value)
1460         const char *argv[SIZEOF_ARG];
1461         int argc = 0;
1463         if (!argv_from_string(argv, &argc, value)) {
1464                 config_msg = "Too many option arguments";
1465                 return ERR;
1466         }
1468         if (!strcmp(opt, "color"))
1469                 return option_color_command(argc, argv);
1471         if (!strcmp(opt, "set"))
1472                 return option_set_command(argc, argv);
1474         if (!strcmp(opt, "bind"))
1475                 return option_bind_command(argc, argv);
1477         config_msg = "Unknown option command";
1478         return ERR;
1481 static int
1482 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1484         int status = OK;
1486         config_lineno++;
1487         config_msg = "Internal error";
1489         /* Check for comment markers, since read_properties() will
1490          * only ensure opt and value are split at first " \t". */
1491         optlen = strcspn(opt, "#");
1492         if (optlen == 0)
1493                 return OK;
1495         if (opt[optlen] != 0) {
1496                 config_msg = "No option value";
1497                 status = ERR;
1499         }  else {
1500                 /* Look for comment endings in the value. */
1501                 size_t len = strcspn(value, "#");
1503                 if (len < valuelen) {
1504                         valuelen = len;
1505                         value[valuelen] = 0;
1506                 }
1508                 status = set_option(opt, value);
1509         }
1511         if (status == ERR) {
1512                 fprintf(stderr, "Error on line %d, near '%.*s': %s\n",
1513                         config_lineno, (int) optlen, opt, config_msg);
1514                 config_errors = TRUE;
1515         }
1517         /* Always keep going if errors are encountered. */
1518         return OK;
1521 static void
1522 load_option_file(const char *path)
1524         FILE *file;
1526         /* It's ok that the file doesn't exist. */
1527         file = fopen(path, "r");
1528         if (!file)
1529                 return;
1531         config_lineno = 0;
1532         config_errors = FALSE;
1534         if (read_properties(file, " \t", read_option) == ERR ||
1535             config_errors == TRUE)
1536                 fprintf(stderr, "Errors while loading %s.\n", path);
1539 static int
1540 load_options(void)
1542         const char *home = getenv("HOME");
1543         const char *tigrc_user = getenv("TIGRC_USER");
1544         const char *tigrc_system = getenv("TIGRC_SYSTEM");
1545         char buf[SIZEOF_STR];
1547         add_builtin_run_requests();
1549         if (!tigrc_system) {
1550                 if (!string_format(buf, "%s/tigrc", SYSCONFDIR))
1551                         return ERR;
1552                 tigrc_system = buf;
1553         }
1554         load_option_file(tigrc_system);
1556         if (!tigrc_user) {
1557                 if (!home || !string_format(buf, "%s/.tigrc", home))
1558                         return ERR;
1559                 tigrc_user = buf;
1560         }
1561         load_option_file(tigrc_user);
1563         return OK;
1567 /*
1568  * The viewer
1569  */
1571 struct view;
1572 struct view_ops;
1574 /* The display array of active views and the index of the current view. */
1575 static struct view *display[2];
1576 static unsigned int current_view;
1578 /* Reading from the prompt? */
1579 static bool input_mode = FALSE;
1581 #define foreach_displayed_view(view, i) \
1582         for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1584 #define displayed_views()       (display[1] != NULL ? 2 : 1)
1586 /* Current head and commit ID */
1587 static char ref_blob[SIZEOF_REF]        = "";
1588 static char ref_commit[SIZEOF_REF]      = "HEAD";
1589 static char ref_head[SIZEOF_REF]        = "HEAD";
1591 struct view {
1592         const char *name;       /* View name */
1593         const char *cmd_env;    /* Command line set via environment */
1594         const char *id;         /* Points to either of ref_{head,commit,blob} */
1596         struct view_ops *ops;   /* View operations */
1598         enum keymap keymap;     /* What keymap does this view have */
1599         bool git_dir;           /* Whether the view requires a git directory. */
1601         char ref[SIZEOF_REF];   /* Hovered commit reference */
1602         char vid[SIZEOF_REF];   /* View ID. Set to id member when updating. */
1604         int height, width;      /* The width and height of the main window */
1605         WINDOW *win;            /* The main window */
1606         WINDOW *title;          /* The title window living below the main window */
1608         /* Navigation */
1609         unsigned long offset;   /* Offset of the window top */
1610         unsigned long lineno;   /* Current line number */
1612         /* Searching */
1613         char grep[SIZEOF_STR];  /* Search string */
1614         regex_t *regex;         /* Pre-compiled regex */
1616         /* If non-NULL, points to the view that opened this view. If this view
1617          * is closed tig will switch back to the parent view. */
1618         struct view *parent;
1620         /* Buffering */
1621         size_t lines;           /* Total number of lines */
1622         struct line *line;      /* Line index */
1623         size_t line_alloc;      /* Total number of allocated lines */
1624         size_t line_size;       /* Total number of used lines */
1625         unsigned int digits;    /* Number of digits in the lines member. */
1627         /* Drawing */
1628         struct line *curline;   /* Line currently being drawn. */
1629         enum line_type curtype; /* Attribute currently used for drawing. */
1630         unsigned long col;      /* Column when drawing. */
1632         /* Loading */
1633         struct io io;
1634         struct io *pipe;
1635         time_t start_time;
1636 };
1638 struct view_ops {
1639         /* What type of content being displayed. Used in the title bar. */
1640         const char *type;
1641         /* Default command arguments. */
1642         const char **argv;
1643         /* Open and reads in all view content. */
1644         bool (*open)(struct view *view);
1645         /* Read one line; updates view->line. */
1646         bool (*read)(struct view *view, char *data);
1647         /* Draw one line; @lineno must be < view->height. */
1648         bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
1649         /* Depending on view handle a special requests. */
1650         enum request (*request)(struct view *view, enum request request, struct line *line);
1651         /* Search for regex in a line. */
1652         bool (*grep)(struct view *view, struct line *line);
1653         /* Select line */
1654         void (*select)(struct view *view, struct line *line);
1655 };
1657 static struct view_ops blame_ops;
1658 static struct view_ops blob_ops;
1659 static struct view_ops diff_ops;
1660 static struct view_ops help_ops;
1661 static struct view_ops log_ops;
1662 static struct view_ops main_ops;
1663 static struct view_ops pager_ops;
1664 static struct view_ops stage_ops;
1665 static struct view_ops status_ops;
1666 static struct view_ops tree_ops;
1668 #define VIEW_STR(name, env, ref, ops, map, git) \
1669         { name, #env, ref, ops, map, git }
1671 #define VIEW_(id, name, ops, git, ref) \
1672         VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
1675 static struct view views[] = {
1676         VIEW_(MAIN,   "main",   &main_ops,   TRUE,  ref_head),
1677         VIEW_(DIFF,   "diff",   &diff_ops,   TRUE,  ref_commit),
1678         VIEW_(LOG,    "log",    &log_ops,    TRUE,  ref_head),
1679         VIEW_(TREE,   "tree",   &tree_ops,   TRUE,  ref_commit),
1680         VIEW_(BLOB,   "blob",   &blob_ops,   TRUE,  ref_blob),
1681         VIEW_(BLAME,  "blame",  &blame_ops,  TRUE,  ref_commit),
1682         VIEW_(HELP,   "help",   &help_ops,   FALSE, ""),
1683         VIEW_(PAGER,  "pager",  &pager_ops,  FALSE, "stdin"),
1684         VIEW_(STATUS, "status", &status_ops, TRUE,  ""),
1685         VIEW_(STAGE,  "stage",  &stage_ops,  TRUE,  ""),
1686 };
1688 #define VIEW(req)       (&views[(req) - REQ_OFFSET - 1])
1689 #define VIEW_REQ(view)  ((view) - views + REQ_OFFSET + 1)
1691 #define foreach_view(view, i) \
1692         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1694 #define view_is_displayed(view) \
1695         (view == display[0] || view == display[1])
1698 enum line_graphic {
1699         LINE_GRAPHIC_VLINE
1700 };
1702 static int line_graphics[] = {
1703         /* LINE_GRAPHIC_VLINE: */ '|'
1704 };
1706 static inline void
1707 set_view_attr(struct view *view, enum line_type type)
1709         if (!view->curline->selected && view->curtype != type) {
1710                 wattrset(view->win, get_line_attr(type));
1711                 wchgat(view->win, -1, 0, type, NULL);
1712                 view->curtype = type;
1713         }
1716 static int
1717 draw_chars(struct view *view, enum line_type type, const char *string,
1718            int max_len, bool use_tilde)
1720         int len = 0;
1721         int col = 0;
1722         int trimmed = FALSE;
1724         if (max_len <= 0)
1725                 return 0;
1727         if (opt_utf8) {
1728                 len = utf8_length(string, &col, max_len, &trimmed, use_tilde);
1729         } else {
1730                 col = len = strlen(string);
1731                 if (len > max_len) {
1732                         if (use_tilde) {
1733                                 max_len -= 1;
1734                         }
1735                         col = len = max_len;
1736                         trimmed = TRUE;
1737                 }
1738         }
1740         set_view_attr(view, type);
1741         waddnstr(view->win, string, len);
1742         if (trimmed && use_tilde) {
1743                 set_view_attr(view, LINE_DELIMITER);
1744                 waddch(view->win, '~');
1745                 col++;
1746         }
1748         return col;
1751 static int
1752 draw_space(struct view *view, enum line_type type, int max, int spaces)
1754         static char space[] = "                    ";
1755         int col = 0;
1757         spaces = MIN(max, spaces);
1759         while (spaces > 0) {
1760                 int len = MIN(spaces, sizeof(space) - 1);
1762                 col += draw_chars(view, type, space, spaces, FALSE);
1763                 spaces -= len;
1764         }
1766         return col;
1769 static bool
1770 draw_lineno(struct view *view, unsigned int lineno)
1772         char number[10];
1773         int digits3 = view->digits < 3 ? 3 : view->digits;
1774         int max_number = MIN(digits3, STRING_SIZE(number));
1775         int max = view->width - view->col;
1776         int col;
1778         if (max < max_number)
1779                 max_number = max;
1781         lineno += view->offset + 1;
1782         if (lineno == 1 || (lineno % opt_num_interval) == 0) {
1783                 static char fmt[] = "%1ld";
1785                 if (view->digits <= 9)
1786                         fmt[1] = '0' + digits3;
1788                 if (!string_format(number, fmt, lineno))
1789                         number[0] = 0;
1790                 col = draw_chars(view, LINE_LINE_NUMBER, number, max_number, TRUE);
1791         } else {
1792                 col = draw_space(view, LINE_LINE_NUMBER, max_number, max_number);
1793         }
1795         if (col < max) {
1796                 set_view_attr(view, LINE_DEFAULT);
1797                 waddch(view->win, line_graphics[LINE_GRAPHIC_VLINE]);
1798                 col++;
1799         }
1801         if (col < max)
1802                 col += draw_space(view, LINE_DEFAULT, max - col, 1);
1803         view->col += col;
1805         return view->width - view->col <= 0;
1808 static bool
1809 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
1811         view->col += draw_chars(view, type, string, view->width - view->col, trim);
1812         return view->width - view->col <= 0;
1815 static bool
1816 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
1818         int max = view->width - view->col;
1819         int i;
1821         if (max < size)
1822                 size = max;
1824         set_view_attr(view, type);
1825         /* Using waddch() instead of waddnstr() ensures that
1826          * they'll be rendered correctly for the cursor line. */
1827         for (i = 0; i < size; i++)
1828                 waddch(view->win, graphic[i]);
1830         view->col += size;
1831         if (size < max) {
1832                 waddch(view->win, ' ');
1833                 view->col++;
1834         }
1836         return view->width - view->col <= 0;
1839 static bool
1840 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
1842         int max = MIN(view->width - view->col, len);
1843         int col;
1845         if (text)
1846                 col = draw_chars(view, type, text, max - 1, trim);
1847         else
1848                 col = draw_space(view, type, max - 1, max - 1);
1850         view->col += col + draw_space(view, LINE_DEFAULT, max - col, max - col);
1851         return view->width - view->col <= 0;
1854 static bool
1855 draw_date(struct view *view, struct tm *time)
1857         char buf[DATE_COLS];
1858         char *date;
1859         int timelen = 0;
1861         if (time)
1862                 timelen = strftime(buf, sizeof(buf), DATE_FORMAT, time);
1863         date = timelen ? buf : NULL;
1865         return draw_field(view, LINE_DATE, date, DATE_COLS, FALSE);
1868 static bool
1869 draw_view_line(struct view *view, unsigned int lineno)
1871         struct line *line;
1872         bool selected = (view->offset + lineno == view->lineno);
1873         bool draw_ok;
1875         assert(view_is_displayed(view));
1877         if (view->offset + lineno >= view->lines)
1878                 return FALSE;
1880         line = &view->line[view->offset + lineno];
1882         wmove(view->win, lineno, 0);
1883         view->col = 0;
1884         view->curline = line;
1885         view->curtype = LINE_NONE;
1886         line->selected = FALSE;
1888         if (selected) {
1889                 set_view_attr(view, LINE_CURSOR);
1890                 line->selected = TRUE;
1891                 view->ops->select(view, line);
1892         } else if (line->selected) {
1893                 wclrtoeol(view->win);
1894         }
1896         scrollok(view->win, FALSE);
1897         draw_ok = view->ops->draw(view, line, lineno);
1898         scrollok(view->win, TRUE);
1900         return draw_ok;
1903 static void
1904 redraw_view_dirty(struct view *view)
1906         bool dirty = FALSE;
1907         int lineno;
1909         for (lineno = 0; lineno < view->height; lineno++) {
1910                 struct line *line = &view->line[view->offset + lineno];
1912                 if (!line->dirty)
1913                         continue;
1914                 line->dirty = 0;
1915                 dirty = TRUE;
1916                 if (!draw_view_line(view, lineno))
1917                         break;
1918         }
1920         if (!dirty)
1921                 return;
1922         redrawwin(view->win);
1923         if (input_mode)
1924                 wnoutrefresh(view->win);
1925         else
1926                 wrefresh(view->win);
1929 static void
1930 redraw_view_from(struct view *view, int lineno)
1932         assert(0 <= lineno && lineno < view->height);
1934         for (; lineno < view->height; lineno++) {
1935                 if (!draw_view_line(view, lineno))
1936                         break;
1937         }
1939         redrawwin(view->win);
1940         if (input_mode)
1941                 wnoutrefresh(view->win);
1942         else
1943                 wrefresh(view->win);
1946 static void
1947 redraw_view(struct view *view)
1949         wclear(view->win);
1950         redraw_view_from(view, 0);
1954 static void
1955 update_view_title(struct view *view)
1957         char buf[SIZEOF_STR];
1958         char state[SIZEOF_STR];
1959         size_t bufpos = 0, statelen = 0;
1961         assert(view_is_displayed(view));
1963         if (view != VIEW(REQ_VIEW_STATUS) && (view->lines || view->pipe)) {
1964                 unsigned int view_lines = view->offset + view->height;
1965                 unsigned int lines = view->lines
1966                                    ? MIN(view_lines, view->lines) * 100 / view->lines
1967                                    : 0;
1969                 string_format_from(state, &statelen, "- %s %d of %d (%d%%)",
1970                                    view->ops->type,
1971                                    view->lineno + 1,
1972                                    view->lines,
1973                                    lines);
1975                 if (view->pipe) {
1976                         time_t secs = time(NULL) - view->start_time;
1978                         /* Three git seconds are a long time ... */
1979                         if (secs > 2)
1980                                 string_format_from(state, &statelen, " %lds", secs);
1981                 }
1982         }
1984         string_format_from(buf, &bufpos, "[%s]", view->name);
1985         if (*view->ref && bufpos < view->width) {
1986                 size_t refsize = strlen(view->ref);
1987                 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
1989                 if (minsize < view->width)
1990                         refsize = view->width - minsize + 7;
1991                 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
1992         }
1994         if (statelen && bufpos < view->width) {
1995                 string_format_from(buf, &bufpos, " %s", state);
1996         }
1998         if (view == display[current_view])
1999                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2000         else
2001                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2003         mvwaddnstr(view->title, 0, 0, buf, bufpos);
2004         wclrtoeol(view->title);
2005         wmove(view->title, 0, view->width - 1);
2007         if (input_mode)
2008                 wnoutrefresh(view->title);
2009         else
2010                 wrefresh(view->title);
2013 static void
2014 resize_display(void)
2016         int offset, i;
2017         struct view *base = display[0];
2018         struct view *view = display[1] ? display[1] : display[0];
2020         /* Setup window dimensions */
2022         getmaxyx(stdscr, base->height, base->width);
2024         /* Make room for the status window. */
2025         base->height -= 1;
2027         if (view != base) {
2028                 /* Horizontal split. */
2029                 view->width   = base->width;
2030                 view->height  = SCALE_SPLIT_VIEW(base->height);
2031                 base->height -= view->height;
2033                 /* Make room for the title bar. */
2034                 view->height -= 1;
2035         }
2037         /* Make room for the title bar. */
2038         base->height -= 1;
2040         offset = 0;
2042         foreach_displayed_view (view, i) {
2043                 if (!view->win) {
2044                         view->win = newwin(view->height, 0, offset, 0);
2045                         if (!view->win)
2046                                 die("Failed to create %s view", view->name);
2048                         scrollok(view->win, TRUE);
2050                         view->title = newwin(1, 0, offset + view->height, 0);
2051                         if (!view->title)
2052                                 die("Failed to create title window");
2054                 } else {
2055                         wresize(view->win, view->height, view->width);
2056                         mvwin(view->win,   offset, 0);
2057                         mvwin(view->title, offset + view->height, 0);
2058                 }
2060                 offset += view->height + 1;
2061         }
2064 static void
2065 redraw_display(void)
2067         struct view *view;
2068         int i;
2070         foreach_displayed_view (view, i) {
2071                 redraw_view(view);
2072                 update_view_title(view);
2073         }
2076 static void
2077 update_display_cursor(struct view *view)
2079         /* Move the cursor to the right-most column of the cursor line.
2080          *
2081          * XXX: This could turn out to be a bit expensive, but it ensures that
2082          * the cursor does not jump around. */
2083         if (view->lines) {
2084                 wmove(view->win, view->lineno - view->offset, view->width - 1);
2085                 wrefresh(view->win);
2086         }
2089 /*
2090  * Navigation
2091  */
2093 /* Scrolling backend */
2094 static void
2095 do_scroll_view(struct view *view, int lines)
2097         bool redraw_current_line = FALSE;
2099         /* The rendering expects the new offset. */
2100         view->offset += lines;
2102         assert(0 <= view->offset && view->offset < view->lines);
2103         assert(lines);
2105         /* Move current line into the view. */
2106         if (view->lineno < view->offset) {
2107                 view->lineno = view->offset;
2108                 redraw_current_line = TRUE;
2109         } else if (view->lineno >= view->offset + view->height) {
2110                 view->lineno = view->offset + view->height - 1;
2111                 redraw_current_line = TRUE;
2112         }
2114         assert(view->offset <= view->lineno && view->lineno < view->lines);
2116         /* Redraw the whole screen if scrolling is pointless. */
2117         if (view->height < ABS(lines)) {
2118                 redraw_view(view);
2120         } else {
2121                 int line = lines > 0 ? view->height - lines : 0;
2122                 int end = line + ABS(lines);
2124                 wscrl(view->win, lines);
2126                 for (; line < end; line++) {
2127                         if (!draw_view_line(view, line))
2128                                 break;
2129                 }
2131                 if (redraw_current_line)
2132                         draw_view_line(view, view->lineno - view->offset);
2133         }
2135         redrawwin(view->win);
2136         wrefresh(view->win);
2137         report("");
2140 /* Scroll frontend */
2141 static void
2142 scroll_view(struct view *view, enum request request)
2144         int lines = 1;
2146         assert(view_is_displayed(view));
2148         switch (request) {
2149         case REQ_SCROLL_PAGE_DOWN:
2150                 lines = view->height;
2151         case REQ_SCROLL_LINE_DOWN:
2152                 if (view->offset + lines > view->lines)
2153                         lines = view->lines - view->offset;
2155                 if (lines == 0 || view->offset + view->height >= view->lines) {
2156                         report("Cannot scroll beyond the last line");
2157                         return;
2158                 }
2159                 break;
2161         case REQ_SCROLL_PAGE_UP:
2162                 lines = view->height;
2163         case REQ_SCROLL_LINE_UP:
2164                 if (lines > view->offset)
2165                         lines = view->offset;
2167                 if (lines == 0) {
2168                         report("Cannot scroll beyond the first line");
2169                         return;
2170                 }
2172                 lines = -lines;
2173                 break;
2175         default:
2176                 die("request %d not handled in switch", request);
2177         }
2179         do_scroll_view(view, lines);
2182 /* Cursor moving */
2183 static void
2184 move_view(struct view *view, enum request request)
2186         int scroll_steps = 0;
2187         int steps;
2189         switch (request) {
2190         case REQ_MOVE_FIRST_LINE:
2191                 steps = -view->lineno;
2192                 break;
2194         case REQ_MOVE_LAST_LINE:
2195                 steps = view->lines - view->lineno - 1;
2196                 break;
2198         case REQ_MOVE_PAGE_UP:
2199                 steps = view->height > view->lineno
2200                       ? -view->lineno : -view->height;
2201                 break;
2203         case REQ_MOVE_PAGE_DOWN:
2204                 steps = view->lineno + view->height >= view->lines
2205                       ? view->lines - view->lineno - 1 : view->height;
2206                 break;
2208         case REQ_MOVE_UP:
2209                 steps = -1;
2210                 break;
2212         case REQ_MOVE_DOWN:
2213                 steps = 1;
2214                 break;
2216         default:
2217                 die("request %d not handled in switch", request);
2218         }
2220         if (steps <= 0 && view->lineno == 0) {
2221                 report("Cannot move beyond the first line");
2222                 return;
2224         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2225                 report("Cannot move beyond the last line");
2226                 return;
2227         }
2229         /* Move the current line */
2230         view->lineno += steps;
2231         assert(0 <= view->lineno && view->lineno < view->lines);
2233         /* Check whether the view needs to be scrolled */
2234         if (view->lineno < view->offset ||
2235             view->lineno >= view->offset + view->height) {
2236                 scroll_steps = steps;
2237                 if (steps < 0 && -steps > view->offset) {
2238                         scroll_steps = -view->offset;
2240                 } else if (steps > 0) {
2241                         if (view->lineno == view->lines - 1 &&
2242                             view->lines > view->height) {
2243                                 scroll_steps = view->lines - view->offset - 1;
2244                                 if (scroll_steps >= view->height)
2245                                         scroll_steps -= view->height - 1;
2246                         }
2247                 }
2248         }
2250         if (!view_is_displayed(view)) {
2251                 view->offset += scroll_steps;
2252                 assert(0 <= view->offset && view->offset < view->lines);
2253                 view->ops->select(view, &view->line[view->lineno]);
2254                 return;
2255         }
2257         /* Repaint the old "current" line if we be scrolling */
2258         if (ABS(steps) < view->height)
2259                 draw_view_line(view, view->lineno - steps - view->offset);
2261         if (scroll_steps) {
2262                 do_scroll_view(view, scroll_steps);
2263                 return;
2264         }
2266         /* Draw the current line */
2267         draw_view_line(view, view->lineno - view->offset);
2269         redrawwin(view->win);
2270         wrefresh(view->win);
2271         report("");
2275 /*
2276  * Searching
2277  */
2279 static void search_view(struct view *view, enum request request);
2281 static bool
2282 find_next_line(struct view *view, unsigned long lineno, struct line *line)
2284         assert(view_is_displayed(view));
2286         if (!view->ops->grep(view, line))
2287                 return FALSE;
2289         if (lineno - view->offset >= view->height) {
2290                 view->offset = lineno;
2291                 view->lineno = lineno;
2292                 redraw_view(view);
2294         } else {
2295                 unsigned long old_lineno = view->lineno - view->offset;
2297                 view->lineno = lineno;
2298                 draw_view_line(view, old_lineno);
2300                 draw_view_line(view, view->lineno - view->offset);
2301                 redrawwin(view->win);
2302                 wrefresh(view->win);
2303         }
2305         report("Line %ld matches '%s'", lineno + 1, view->grep);
2306         return TRUE;
2309 static void
2310 find_next(struct view *view, enum request request)
2312         unsigned long lineno = view->lineno;
2313         int direction;
2315         if (!*view->grep) {
2316                 if (!*opt_search)
2317                         report("No previous search");
2318                 else
2319                         search_view(view, request);
2320                 return;
2321         }
2323         switch (request) {
2324         case REQ_SEARCH:
2325         case REQ_FIND_NEXT:
2326                 direction = 1;
2327                 break;
2329         case REQ_SEARCH_BACK:
2330         case REQ_FIND_PREV:
2331                 direction = -1;
2332                 break;
2334         default:
2335                 return;
2336         }
2338         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2339                 lineno += direction;
2341         /* Note, lineno is unsigned long so will wrap around in which case it
2342          * will become bigger than view->lines. */
2343         for (; lineno < view->lines; lineno += direction) {
2344                 struct line *line = &view->line[lineno];
2346                 if (find_next_line(view, lineno, line))
2347                         return;
2348         }
2350         report("No match found for '%s'", view->grep);
2353 static void
2354 search_view(struct view *view, enum request request)
2356         int regex_err;
2358         if (view->regex) {
2359                 regfree(view->regex);
2360                 *view->grep = 0;
2361         } else {
2362                 view->regex = calloc(1, sizeof(*view->regex));
2363                 if (!view->regex)
2364                         return;
2365         }
2367         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2368         if (regex_err != 0) {
2369                 char buf[SIZEOF_STR] = "unknown error";
2371                 regerror(regex_err, view->regex, buf, sizeof(buf));
2372                 report("Search failed: %s", buf);
2373                 return;
2374         }
2376         string_copy(view->grep, opt_search);
2378         find_next(view, request);
2381 /*
2382  * Incremental updating
2383  */
2385 static void
2386 reset_view(struct view *view)
2388         int i;
2390         for (i = 0; i < view->lines; i++)
2391                 free(view->line[i].data);
2392         free(view->line);
2394         view->line = NULL;
2395         view->offset = 0;
2396         view->lines  = 0;
2397         view->lineno = 0;
2398         view->line_size = 0;
2399         view->line_alloc = 0;
2400         view->vid[0] = 0;
2403 static void
2404 free_argv(const char *argv[])
2406         int argc;
2408         for (argc = 0; argv[argc]; argc++)
2409                 free((void *) argv[argc]);
2412 static bool
2413 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
2415         char buf[SIZEOF_STR];
2416         int argc;
2417         bool noreplace = flags == FORMAT_NONE;
2419         free_argv(dst_argv);
2421         for (argc = 0; src_argv[argc]; argc++) {
2422                 const char *arg = src_argv[argc];
2423                 size_t bufpos = 0;
2425                 while (arg) {
2426                         char *next = strstr(arg, "%(");
2427                         int len = next - arg;
2428                         const char *value;
2430                         if (!next || noreplace) {
2431                                 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
2432                                         noreplace = TRUE;
2433                                 len = strlen(arg);
2434                                 value = "";
2436                         } else if (!prefixcmp(next, "%(directory)")) {
2437                                 value = opt_path;
2439                         } else if (!prefixcmp(next, "%(file)")) {
2440                                 value = opt_file;
2442                         } else if (!prefixcmp(next, "%(ref)")) {
2443                                 value = *opt_ref ? opt_ref : "HEAD";
2445                         } else if (!prefixcmp(next, "%(head)")) {
2446                                 value = ref_head;
2448                         } else if (!prefixcmp(next, "%(commit)")) {
2449                                 value = ref_commit;
2451                         } else if (!prefixcmp(next, "%(blob)")) {
2452                                 value = ref_blob;
2454                         } else {
2455                                 report("Unknown replacement: `%s`", next);
2456                                 return FALSE;
2457                         }
2459                         if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
2460                                 return FALSE;
2462                         arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
2463                 }
2465                 dst_argv[argc] = strdup(buf);
2466                 if (!dst_argv[argc])
2467                         break;
2468         }
2470         dst_argv[argc] = NULL;
2472         return src_argv[argc] == NULL;
2475 static bool
2476 format_command(char dst[], const char *src_argv[], enum format_flags flags)
2478         const char *dst_argv[SIZEOF_ARG * 2] = { NULL };
2479         int bufsize = 0;
2480         int argc;
2482         if (!format_argv(dst_argv, src_argv, flags)) {
2483                 free_argv(dst_argv);
2484                 return FALSE;
2485         }
2487         for (argc = 0; dst_argv[argc] && bufsize < SIZEOF_STR; argc++) {
2488                 if (bufsize > 0)
2489                         dst[bufsize++] = ' ';
2490                 bufsize = sq_quote(dst, bufsize, dst_argv[argc]);
2491         }
2493         if (bufsize < SIZEOF_STR)
2494                 dst[bufsize] = 0;
2495         free_argv(dst_argv);
2497         return src_argv[argc] == NULL && bufsize < SIZEOF_STR;
2500 static void
2501 end_update(struct view *view, bool force)
2503         if (!view->pipe)
2504                 return;
2505         while (!view->ops->read(view, NULL))
2506                 if (!force)
2507                         return;
2508         set_nonblocking_input(FALSE);
2509         done_io(view->pipe);
2510         view->pipe = NULL;
2513 static void
2514 setup_update(struct view *view, const char *vid)
2516         set_nonblocking_input(TRUE);
2517         reset_view(view);
2518         string_copy_rev(view->vid, vid);
2519         view->pipe = &view->io;
2520         view->start_time = time(NULL);
2523 static bool
2524 prepare_update(struct view *view, const char *argv[], const char *dir,
2525                enum format_flags flags)
2527         if (view->pipe)
2528                 end_update(view, TRUE);
2529         return init_io_rd(&view->io, argv, dir, flags);
2532 static bool
2533 begin_update(struct view *view, bool refresh)
2535         if (init_io_fd(&view->io, opt_pipe)) {
2536                 opt_pipe = NULL;
2538         } else if (opt_cmd[0]) {
2539                 if (!run_io(&view->io, IO_RD, opt_cmd))
2540                         return FALSE;
2541                 view->ref[0] = 0;
2542                 opt_cmd[0] = 0;
2544         } else if (refresh) {
2545                 if (!start_io(&view->io))
2546                         return FALSE;
2548         } else if (view == VIEW(REQ_VIEW_TREE)) {
2549                 if (strcmp(view->vid, view->id))
2550                         opt_path[0] = 0;
2552                 if (!run_io_rd(&view->io, view->ops->argv, FORMAT_ALL))
2553                         return FALSE;
2555         } else {
2556                 if (!run_io_rd(&view->io, view->ops->argv, FORMAT_ALL))
2557                         return FALSE;
2559                 /* Put the current ref_* value to the view title ref
2560                  * member. This is needed by the blob view. Most other
2561                  * views sets it automatically after loading because the
2562                  * first line is a commit line. */
2563                 string_copy_rev(view->ref, view->id);
2564         }
2566         setup_update(view, view->id);
2568         return TRUE;
2571 #define ITEM_CHUNK_SIZE 256
2572 static void *
2573 realloc_items(void *mem, size_t *size, size_t new_size, size_t item_size)
2575         size_t num_chunks = *size / ITEM_CHUNK_SIZE;
2576         size_t num_chunks_new = (new_size + ITEM_CHUNK_SIZE - 1) / ITEM_CHUNK_SIZE;
2578         if (mem == NULL || num_chunks != num_chunks_new) {
2579                 *size = num_chunks_new * ITEM_CHUNK_SIZE;
2580                 mem = realloc(mem, *size * item_size);
2581         }
2583         return mem;
2586 static struct line *
2587 realloc_lines(struct view *view, size_t line_size)
2589         size_t alloc = view->line_alloc;
2590         struct line *tmp = realloc_items(view->line, &alloc, line_size,
2591                                          sizeof(*view->line));
2593         if (!tmp)
2594                 return NULL;
2596         view->line = tmp;
2597         view->line_alloc = alloc;
2598         view->line_size = line_size;
2599         return view->line;
2602 static bool
2603 update_view(struct view *view)
2605         char out_buffer[BUFSIZ * 2];
2606         char *line;
2607         /* The number of lines to read. If too low it will cause too much
2608          * redrawing (and possible flickering), if too high responsiveness
2609          * will suffer. */
2610         unsigned long lines = view->height;
2611         int redraw_from = -1;
2613         if (!view->pipe)
2614                 return TRUE;
2616         /* Only redraw if lines are visible. */
2617         if (view->offset + view->height >= view->lines)
2618                 redraw_from = view->lines - view->offset;
2620         /* FIXME: This is probably not perfect for backgrounded views. */
2621         if (!realloc_lines(view, view->lines + lines))
2622                 goto alloc_error;
2624         while ((line = io_gets(view->pipe))) {
2625                 size_t linelen = strlen(line);
2627                 if (linelen)
2628                         line[linelen - 1] = 0;
2630                 if (opt_iconv != ICONV_NONE) {
2631                         ICONV_CONST char *inbuf = line;
2632                         size_t inlen = linelen;
2634                         char *outbuf = out_buffer;
2635                         size_t outlen = sizeof(out_buffer);
2637                         size_t ret;
2639                         ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2640                         if (ret != (size_t) -1) {
2641                                 line = out_buffer;
2642                                 linelen = strlen(out_buffer);
2643                         }
2644                 }
2646                 if (!view->ops->read(view, line))
2647                         goto alloc_error;
2649                 if (lines-- == 1)
2650                         break;
2651         }
2653         {
2654                 int digits;
2656                 lines = view->lines;
2657                 for (digits = 0; lines; digits++)
2658                         lines /= 10;
2660                 /* Keep the displayed view in sync with line number scaling. */
2661                 if (digits != view->digits) {
2662                         view->digits = digits;
2663                         redraw_from = 0;
2664                 }
2665         }
2667         if (io_error(view->pipe)) {
2668                 report("Failed to read: %s", io_strerror(view->pipe));
2669                 end_update(view, TRUE);
2671         } else if (io_eof(view->pipe)) {
2672                 report("");
2673                 end_update(view, FALSE);
2674         }
2676         if (!view_is_displayed(view))
2677                 return TRUE;
2679         if (view == VIEW(REQ_VIEW_TREE)) {
2680                 /* Clear the view and redraw everything since the tree sorting
2681                  * might have rearranged things. */
2682                 redraw_view(view);
2684         } else if (redraw_from >= 0) {
2685                 /* If this is an incremental update, redraw the previous line
2686                  * since for commits some members could have changed when
2687                  * loading the main view. */
2688                 if (redraw_from > 0)
2689                         redraw_from--;
2691                 /* Since revision graph visualization requires knowledge
2692                  * about the parent commit, it causes a further one-off
2693                  * needed to be redrawn for incremental updates. */
2694                 if (redraw_from > 0 && opt_rev_graph)
2695                         redraw_from--;
2697                 /* Incrementally draw avoids flickering. */
2698                 redraw_view_from(view, redraw_from);
2699         }
2701         if (view == VIEW(REQ_VIEW_BLAME))
2702                 redraw_view_dirty(view);
2704         /* Update the title _after_ the redraw so that if the redraw picks up a
2705          * commit reference in view->ref it'll be available here. */
2706         update_view_title(view);
2707         return TRUE;
2709 alloc_error:
2710         report("Allocation failure");
2711         end_update(view, TRUE);
2712         return FALSE;
2715 static struct line *
2716 add_line_data(struct view *view, void *data, enum line_type type)
2718         struct line *line = &view->line[view->lines++];
2720         memset(line, 0, sizeof(*line));
2721         line->type = type;
2722         line->data = data;
2724         return line;
2727 static struct line *
2728 add_line_text(struct view *view, const char *text, enum line_type type)
2730         char *data = text ? strdup(text) : NULL;
2732         return data ? add_line_data(view, data, type) : NULL;
2736 /*
2737  * View opening
2738  */
2740 enum open_flags {
2741         OPEN_DEFAULT = 0,       /* Use default view switching. */
2742         OPEN_SPLIT = 1,         /* Split current view. */
2743         OPEN_BACKGROUNDED = 2,  /* Backgrounded. */
2744         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
2745         OPEN_NOMAXIMIZE = 8,    /* Do not maximize the current view. */
2746         OPEN_REFRESH = 16,      /* Refresh view using previous command. */
2747         OPEN_PREPARED = 32,     /* Open already prepared command. */
2748 };
2750 static void
2751 open_view(struct view *prev, enum request request, enum open_flags flags)
2753         bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
2754         bool split = !!(flags & OPEN_SPLIT);
2755         bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
2756         bool nomaximize = !!(flags & (OPEN_NOMAXIMIZE | OPEN_REFRESH));
2757         struct view *view = VIEW(request);
2758         int nviews = displayed_views();
2759         struct view *base_view = display[0];
2761         if (view == prev && nviews == 1 && !reload) {
2762                 report("Already in %s view", view->name);
2763                 return;
2764         }
2766         if (view->git_dir && !opt_git_dir[0]) {
2767                 report("The %s view is disabled in pager view", view->name);
2768                 return;
2769         }
2771         if (split) {
2772                 display[1] = view;
2773                 if (!backgrounded)
2774                         current_view = 1;
2775         } else if (!nomaximize) {
2776                 /* Maximize the current view. */
2777                 memset(display, 0, sizeof(display));
2778                 current_view = 0;
2779                 display[current_view] = view;
2780         }
2782         /* Resize the view when switching between split- and full-screen,
2783          * or when switching between two different full-screen views. */
2784         if (nviews != displayed_views() ||
2785             (nviews == 1 && base_view != display[0]))
2786                 resize_display();
2788         if (view->pipe)
2789                 end_update(view, TRUE);
2791         if (view->ops->open) {
2792                 if (!view->ops->open(view)) {
2793                         report("Failed to load %s view", view->name);
2794                         return;
2795                 }
2797         } else if ((reload || strcmp(view->vid, view->id)) &&
2798                    !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
2799                 report("Failed to load %s view", view->name);
2800                 return;
2801         }
2803         if (split && prev->lineno - prev->offset >= prev->height) {
2804                 /* Take the title line into account. */
2805                 int lines = prev->lineno - prev->offset - prev->height + 1;
2807                 /* Scroll the view that was split if the current line is
2808                  * outside the new limited view. */
2809                 do_scroll_view(prev, lines);
2810         }
2812         if (prev && view != prev) {
2813                 if (split && !backgrounded) {
2814                         /* "Blur" the previous view. */
2815                         update_view_title(prev);
2816                 }
2818                 view->parent = prev;
2819         }
2821         if (view->pipe && view->lines == 0) {
2822                 /* Clear the old view and let the incremental updating refill
2823                  * the screen. */
2824                 werase(view->win);
2825                 report("");
2826         } else if (view_is_displayed(view)) {
2827                 redraw_view(view);
2828                 report("");
2829         }
2831         /* If the view is backgrounded the above calls to report()
2832          * won't redraw the view title. */
2833         if (backgrounded)
2834                 update_view_title(view);
2837 static void
2838 open_external_viewer(const char *argv[], const char *dir)
2840         def_prog_mode();           /* save current tty modes */
2841         endwin();                  /* restore original tty modes */
2842         run_io_fg(argv, dir);
2843         fprintf(stderr, "Press Enter to continue");
2844         getc(opt_tty);
2845         reset_prog_mode();
2846         redraw_display();
2849 static void
2850 open_mergetool(const char *file)
2852         const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
2854         open_external_viewer(mergetool_argv, NULL);
2857 static void
2858 open_editor(bool from_root, const char *file)
2860         const char *editor_argv[] = { "vi", file, NULL };
2861         const char *editor;
2863         editor = getenv("GIT_EDITOR");
2864         if (!editor && *opt_editor)
2865                 editor = opt_editor;
2866         if (!editor)
2867                 editor = getenv("VISUAL");
2868         if (!editor)
2869                 editor = getenv("EDITOR");
2870         if (!editor)
2871                 editor = "vi";
2873         editor_argv[0] = editor;
2874         open_external_viewer(editor_argv, from_root ? opt_cdup : NULL);
2877 static void
2878 open_run_request(enum request request)
2880         struct run_request *req = get_run_request(request);
2881         const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
2883         if (!req) {
2884                 report("Unknown run request");
2885                 return;
2886         }
2888         if (format_argv(argv, req->argv, FORMAT_ALL))
2889                 open_external_viewer(argv, NULL);
2890         free_argv(argv);
2893 /*
2894  * User request switch noodle
2895  */
2897 static int
2898 view_driver(struct view *view, enum request request)
2900         int i;
2902         if (request == REQ_NONE) {
2903                 doupdate();
2904                 return TRUE;
2905         }
2907         if (request > REQ_NONE) {
2908                 open_run_request(request);
2909                 /* FIXME: When all views can refresh always do this. */
2910                 if (view == VIEW(REQ_VIEW_STATUS) ||
2911                     view == VIEW(REQ_VIEW_MAIN) ||
2912                     view == VIEW(REQ_VIEW_LOG) ||
2913                     view == VIEW(REQ_VIEW_STAGE))
2914                         request = REQ_REFRESH;
2915                 else
2916                         return TRUE;
2917         }
2919         if (view && view->lines) {
2920                 request = view->ops->request(view, request, &view->line[view->lineno]);
2921                 if (request == REQ_NONE)
2922                         return TRUE;
2923         }
2925         switch (request) {
2926         case REQ_MOVE_UP:
2927         case REQ_MOVE_DOWN:
2928         case REQ_MOVE_PAGE_UP:
2929         case REQ_MOVE_PAGE_DOWN:
2930         case REQ_MOVE_FIRST_LINE:
2931         case REQ_MOVE_LAST_LINE:
2932                 move_view(view, request);
2933                 break;
2935         case REQ_SCROLL_LINE_DOWN:
2936         case REQ_SCROLL_LINE_UP:
2937         case REQ_SCROLL_PAGE_DOWN:
2938         case REQ_SCROLL_PAGE_UP:
2939                 scroll_view(view, request);
2940                 break;
2942         case REQ_VIEW_BLAME:
2943                 if (!opt_file[0]) {
2944                         report("No file chosen, press %s to open tree view",
2945                                get_key(REQ_VIEW_TREE));
2946                         break;
2947                 }
2948                 open_view(view, request, OPEN_DEFAULT);
2949                 break;
2951         case REQ_VIEW_BLOB:
2952                 if (!ref_blob[0]) {
2953                         report("No file chosen, press %s to open tree view",
2954                                get_key(REQ_VIEW_TREE));
2955                         break;
2956                 }
2957                 open_view(view, request, OPEN_DEFAULT);
2958                 break;
2960         case REQ_VIEW_PAGER:
2961                 if (!opt_pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
2962                         report("No pager content, press %s to run command from prompt",
2963                                get_key(REQ_PROMPT));
2964                         break;
2965                 }
2966                 open_view(view, request, OPEN_DEFAULT);
2967                 break;
2969         case REQ_VIEW_STAGE:
2970                 if (!VIEW(REQ_VIEW_STAGE)->lines) {
2971                         report("No stage content, press %s to open the status view and choose file",
2972                                get_key(REQ_VIEW_STATUS));
2973                         break;
2974                 }
2975                 open_view(view, request, OPEN_DEFAULT);
2976                 break;
2978         case REQ_VIEW_STATUS:
2979                 if (opt_is_inside_work_tree == FALSE) {
2980                         report("The status view requires a working tree");
2981                         break;
2982                 }
2983                 open_view(view, request, OPEN_DEFAULT);
2984                 break;
2986         case REQ_VIEW_MAIN:
2987         case REQ_VIEW_DIFF:
2988         case REQ_VIEW_LOG:
2989         case REQ_VIEW_TREE:
2990         case REQ_VIEW_HELP:
2991                 open_view(view, request, OPEN_DEFAULT);
2992                 break;
2994         case REQ_NEXT:
2995         case REQ_PREVIOUS:
2996                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
2998                 if ((view == VIEW(REQ_VIEW_DIFF) &&
2999                      view->parent == VIEW(REQ_VIEW_MAIN)) ||
3000                    (view == VIEW(REQ_VIEW_DIFF) &&
3001                      view->parent == VIEW(REQ_VIEW_BLAME)) ||
3002                    (view == VIEW(REQ_VIEW_STAGE) &&
3003                      view->parent == VIEW(REQ_VIEW_STATUS)) ||
3004                    (view == VIEW(REQ_VIEW_BLOB) &&
3005                      view->parent == VIEW(REQ_VIEW_TREE))) {
3006                         int line;
3008                         view = view->parent;
3009                         line = view->lineno;
3010                         move_view(view, request);
3011                         if (view_is_displayed(view))
3012                                 update_view_title(view);
3013                         if (line != view->lineno)
3014                                 view->ops->request(view, REQ_ENTER,
3015                                                    &view->line[view->lineno]);
3017                 } else {
3018                         move_view(view, request);
3019                 }
3020                 break;
3022         case REQ_VIEW_NEXT:
3023         {
3024                 int nviews = displayed_views();
3025                 int next_view = (current_view + 1) % nviews;
3027                 if (next_view == current_view) {
3028                         report("Only one view is displayed");
3029                         break;
3030                 }
3032                 current_view = next_view;
3033                 /* Blur out the title of the previous view. */
3034                 update_view_title(view);
3035                 report("");
3036                 break;
3037         }
3038         case REQ_REFRESH:
3039                 report("Refreshing is not yet supported for the %s view", view->name);
3040                 break;
3042         case REQ_MAXIMIZE:
3043                 if (displayed_views() == 2)
3044                         open_view(view, VIEW_REQ(view), OPEN_DEFAULT);
3045                 break;
3047         case REQ_TOGGLE_LINENO:
3048                 opt_line_number = !opt_line_number;
3049                 redraw_display();
3050                 break;
3052         case REQ_TOGGLE_DATE:
3053                 opt_date = !opt_date;
3054                 redraw_display();
3055                 break;
3057         case REQ_TOGGLE_AUTHOR:
3058                 opt_author = !opt_author;
3059                 redraw_display();
3060                 break;
3062         case REQ_TOGGLE_REV_GRAPH:
3063                 opt_rev_graph = !opt_rev_graph;
3064                 redraw_display();
3065                 break;
3067         case REQ_TOGGLE_REFS:
3068                 opt_show_refs = !opt_show_refs;
3069                 redraw_display();
3070                 break;
3072         case REQ_SEARCH:
3073         case REQ_SEARCH_BACK:
3074                 search_view(view, request);
3075                 break;
3077         case REQ_FIND_NEXT:
3078         case REQ_FIND_PREV:
3079                 find_next(view, request);
3080                 break;
3082         case REQ_STOP_LOADING:
3083                 for (i = 0; i < ARRAY_SIZE(views); i++) {
3084                         view = &views[i];
3085                         if (view->pipe)
3086                                 report("Stopped loading the %s view", view->name),
3087                         end_update(view, TRUE);
3088                 }
3089                 break;
3091         case REQ_SHOW_VERSION:
3092                 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3093                 return TRUE;
3095         case REQ_SCREEN_RESIZE:
3096                 resize_display();
3097                 /* Fall-through */
3098         case REQ_SCREEN_REDRAW:
3099                 redraw_display();
3100                 break;
3102         case REQ_EDIT:
3103                 report("Nothing to edit");
3104                 break;
3106         case REQ_ENTER:
3107                 report("Nothing to enter");
3108                 break;
3110         case REQ_VIEW_CLOSE:
3111                 /* XXX: Mark closed views by letting view->parent point to the
3112                  * view itself. Parents to closed view should never be
3113                  * followed. */
3114                 if (view->parent &&
3115                     view->parent->parent != view->parent) {
3116                         memset(display, 0, sizeof(display));
3117                         current_view = 0;
3118                         display[current_view] = view->parent;
3119                         view->parent = view;
3120                         resize_display();
3121                         redraw_display();
3122                         report("");
3123                         break;
3124                 }
3125                 /* Fall-through */
3126         case REQ_QUIT:
3127                 return FALSE;
3129         default:
3130                 report("Unknown key, press 'h' for help");
3131                 return TRUE;
3132         }
3134         return TRUE;
3138 /*
3139  * Pager backend
3140  */
3142 static bool
3143 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3145         char *text = line->data;
3147         if (opt_line_number && draw_lineno(view, lineno))
3148                 return TRUE;
3150         draw_text(view, line->type, text, TRUE);
3151         return TRUE;
3154 static bool
3155 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3157         char refbuf[SIZEOF_STR];
3158         char *ref = NULL;
3159         FILE *pipe;
3161         if (!string_format(refbuf, "git describe %s 2>/dev/null", commit_id))
3162                 return TRUE;
3164         pipe = popen(refbuf, "r");
3165         if (!pipe)
3166                 return TRUE;
3168         if ((ref = fgets(refbuf, sizeof(refbuf), pipe)))
3169                 ref = chomp_string(ref);
3170         pclose(pipe);
3172         if (!ref || !*ref)
3173                 return TRUE;
3175         /* This is the only fatal call, since it can "corrupt" the buffer. */
3176         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
3177                 return FALSE;
3179         return TRUE;
3182 static void
3183 add_pager_refs(struct view *view, struct line *line)
3185         char buf[SIZEOF_STR];
3186         char *commit_id = (char *)line->data + STRING_SIZE("commit ");
3187         struct ref **refs;
3188         size_t bufpos = 0, refpos = 0;
3189         const char *sep = "Refs: ";
3190         bool is_tag = FALSE;
3192         assert(line->type == LINE_COMMIT);
3194         refs = get_refs(commit_id);
3195         if (!refs) {
3196                 if (view == VIEW(REQ_VIEW_DIFF))
3197                         goto try_add_describe_ref;
3198                 return;
3199         }
3201         do {
3202                 struct ref *ref = refs[refpos];
3203                 const char *fmt = ref->tag    ? "%s[%s]" :
3204                                   ref->remote ? "%s<%s>" : "%s%s";
3206                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
3207                         return;
3208                 sep = ", ";
3209                 if (ref->tag)
3210                         is_tag = TRUE;
3211         } while (refs[refpos++]->next);
3213         if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
3214 try_add_describe_ref:
3215                 /* Add <tag>-g<commit_id> "fake" reference. */
3216                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
3217                         return;
3218         }
3220         if (bufpos == 0)
3221                 return;
3223         if (!realloc_lines(view, view->line_size + 1))
3224                 return;
3226         add_line_text(view, buf, LINE_PP_REFS);
3229 static bool
3230 pager_read(struct view *view, char *data)
3232         struct line *line;
3234         if (!data)
3235                 return TRUE;
3237         line = add_line_text(view, data, get_line_type(data));
3238         if (!line)
3239                 return FALSE;
3241         if (line->type == LINE_COMMIT &&
3242             (view == VIEW(REQ_VIEW_DIFF) ||
3243              view == VIEW(REQ_VIEW_LOG)))
3244                 add_pager_refs(view, line);
3246         return TRUE;
3249 static enum request
3250 pager_request(struct view *view, enum request request, struct line *line)
3252         int split = 0;
3254         if (request != REQ_ENTER)
3255                 return request;
3257         if (line->type == LINE_COMMIT &&
3258            (view == VIEW(REQ_VIEW_LOG) ||
3259             view == VIEW(REQ_VIEW_PAGER))) {
3260                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3261                 split = 1;
3262         }
3264         /* Always scroll the view even if it was split. That way
3265          * you can use Enter to scroll through the log view and
3266          * split open each commit diff. */
3267         scroll_view(view, REQ_SCROLL_LINE_DOWN);
3269         /* FIXME: A minor workaround. Scrolling the view will call report("")
3270          * but if we are scrolling a non-current view this won't properly
3271          * update the view title. */
3272         if (split)
3273                 update_view_title(view);
3275         return REQ_NONE;
3278 static bool
3279 pager_grep(struct view *view, struct line *line)
3281         regmatch_t pmatch;
3282         char *text = line->data;
3284         if (!*text)
3285                 return FALSE;
3287         if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
3288                 return FALSE;
3290         return TRUE;
3293 static void
3294 pager_select(struct view *view, struct line *line)
3296         if (line->type == LINE_COMMIT) {
3297                 char *text = (char *)line->data + STRING_SIZE("commit ");
3299                 if (view != VIEW(REQ_VIEW_PAGER))
3300                         string_copy_rev(view->ref, text);
3301                 string_copy_rev(ref_commit, text);
3302         }
3305 static struct view_ops pager_ops = {
3306         "line",
3307         NULL,
3308         NULL,
3309         pager_read,
3310         pager_draw,
3311         pager_request,
3312         pager_grep,
3313         pager_select,
3314 };
3316 static const char *log_argv[SIZEOF_ARG] = {
3317         "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3318 };
3320 static enum request
3321 log_request(struct view *view, enum request request, struct line *line)
3323         switch (request) {
3324         case REQ_REFRESH:
3325                 load_refs();
3326                 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
3327                 return REQ_NONE;
3328         default:
3329                 return pager_request(view, request, line);
3330         }
3333 static struct view_ops log_ops = {
3334         "line",
3335         log_argv,
3336         NULL,
3337         pager_read,
3338         pager_draw,
3339         log_request,
3340         pager_grep,
3341         pager_select,
3342 };
3344 static const char *diff_argv[SIZEOF_ARG] = {
3345         "git", "show", "--pretty=fuller", "--no-color", "--root",
3346                 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
3347 };
3349 static struct view_ops diff_ops = {
3350         "line",
3351         diff_argv,
3352         NULL,
3353         pager_read,
3354         pager_draw,
3355         pager_request,
3356         pager_grep,
3357         pager_select,
3358 };
3360 /*
3361  * Help backend
3362  */
3364 static bool
3365 help_open(struct view *view)
3367         char buf[BUFSIZ];
3368         int lines = ARRAY_SIZE(req_info) + 2;
3369         int i;
3371         if (view->lines > 0)
3372                 return TRUE;
3374         for (i = 0; i < ARRAY_SIZE(req_info); i++)
3375                 if (!req_info[i].request)
3376                         lines++;
3378         lines += run_requests + 1;
3380         view->line = calloc(lines, sizeof(*view->line));
3381         if (!view->line)
3382                 return FALSE;
3384         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
3386         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
3387                 const char *key;
3389                 if (req_info[i].request == REQ_NONE)
3390                         continue;
3392                 if (!req_info[i].request) {
3393                         add_line_text(view, "", LINE_DEFAULT);
3394                         add_line_text(view, req_info[i].help, LINE_DEFAULT);
3395                         continue;
3396                 }
3398                 key = get_key(req_info[i].request);
3399                 if (!*key)
3400                         key = "(no key defined)";
3402                 if (!string_format(buf, "    %-25s %s", key, req_info[i].help))
3403                         continue;
3405                 add_line_text(view, buf, LINE_DEFAULT);
3406         }
3408         if (run_requests) {
3409                 add_line_text(view, "", LINE_DEFAULT);
3410                 add_line_text(view, "External commands:", LINE_DEFAULT);
3411         }
3413         for (i = 0; i < run_requests; i++) {
3414                 struct run_request *req = get_run_request(REQ_NONE + i + 1);
3415                 const char *key;
3416                 char cmd[SIZEOF_STR];
3417                 size_t bufpos;
3418                 int argc;
3420                 if (!req)
3421                         continue;
3423                 key = get_key_name(req->key);
3424                 if (!*key)
3425                         key = "(no key defined)";
3427                 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
3428                         if (!string_format_from(cmd, &bufpos, "%s%s",
3429                                                 argc ? " " : "", req->argv[argc]))
3430                                 return REQ_NONE;
3432                 if (!string_format(buf, "    %-10s %-14s `%s`",
3433                                    keymap_table[req->keymap].name, key, cmd))
3434                         continue;
3436                 add_line_text(view, buf, LINE_DEFAULT);
3437         }
3439         return TRUE;
3442 static struct view_ops help_ops = {
3443         "line",
3444         NULL,
3445         help_open,
3446         NULL,
3447         pager_draw,
3448         pager_request,
3449         pager_grep,
3450         pager_select,
3451 };
3454 /*
3455  * Tree backend
3456  */
3458 struct tree_stack_entry {
3459         struct tree_stack_entry *prev;  /* Entry below this in the stack */
3460         unsigned long lineno;           /* Line number to restore */
3461         char *name;                     /* Position of name in opt_path */
3462 };
3464 /* The top of the path stack. */
3465 static struct tree_stack_entry *tree_stack = NULL;
3466 unsigned long tree_lineno = 0;
3468 static void
3469 pop_tree_stack_entry(void)
3471         struct tree_stack_entry *entry = tree_stack;
3473         tree_lineno = entry->lineno;
3474         entry->name[0] = 0;
3475         tree_stack = entry->prev;
3476         free(entry);
3479 static void
3480 push_tree_stack_entry(const char *name, unsigned long lineno)
3482         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
3483         size_t pathlen = strlen(opt_path);
3485         if (!entry)
3486                 return;
3488         entry->prev = tree_stack;
3489         entry->name = opt_path + pathlen;
3490         tree_stack = entry;
3492         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
3493                 pop_tree_stack_entry();
3494                 return;
3495         }
3497         /* Move the current line to the first tree entry. */
3498         tree_lineno = 1;
3499         entry->lineno = lineno;
3502 /* Parse output from git-ls-tree(1):
3503  *
3504  * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile
3505  * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README
3506  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
3507  * 100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38 web.conf
3508  */
3510 #define SIZEOF_TREE_ATTR \
3511         STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t")
3513 #define TREE_UP_FORMAT "040000 tree %s\t.."
3515 static int
3516 tree_compare_entry(enum line_type type1, const char *name1,
3517                    enum line_type type2, const char *name2)
3519         if (type1 != type2) {
3520                 if (type1 == LINE_TREE_DIR)
3521                         return -1;
3522                 return 1;
3523         }
3525         return strcmp(name1, name2);
3528 static const char *
3529 tree_path(struct line *line)
3531         const char *path = line->data;
3533         return path + SIZEOF_TREE_ATTR;
3536 static bool
3537 tree_read(struct view *view, char *text)
3539         size_t textlen = text ? strlen(text) : 0;
3540         char buf[SIZEOF_STR];
3541         unsigned long pos;
3542         enum line_type type;
3543         bool first_read = view->lines == 0;
3545         if (!text)
3546                 return TRUE;
3547         if (textlen <= SIZEOF_TREE_ATTR)
3548                 return FALSE;
3550         type = text[STRING_SIZE("100644 ")] == 't'
3551              ? LINE_TREE_DIR : LINE_TREE_FILE;
3553         if (first_read) {
3554                 /* Add path info line */
3555                 if (!string_format(buf, "Directory path /%s", opt_path) ||
3556                     !realloc_lines(view, view->line_size + 1) ||
3557                     !add_line_text(view, buf, LINE_DEFAULT))
3558                         return FALSE;
3560                 /* Insert "link" to parent directory. */
3561                 if (*opt_path) {
3562                         if (!string_format(buf, TREE_UP_FORMAT, view->ref) ||
3563                             !realloc_lines(view, view->line_size + 1) ||
3564                             !add_line_text(view, buf, LINE_TREE_DIR))
3565                                 return FALSE;
3566                 }
3567         }
3569         /* Strip the path part ... */
3570         if (*opt_path) {
3571                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
3572                 size_t striplen = strlen(opt_path);
3573                 char *path = text + SIZEOF_TREE_ATTR;
3575                 if (pathlen > striplen)
3576                         memmove(path, path + striplen,
3577                                 pathlen - striplen + 1);
3578         }
3580         /* Skip "Directory ..." and ".." line. */
3581         for (pos = 1 + !!*opt_path; pos < view->lines; pos++) {
3582                 struct line *line = &view->line[pos];
3583                 const char *path1 = tree_path(line);
3584                 char *path2 = text + SIZEOF_TREE_ATTR;
3585                 int cmp = tree_compare_entry(line->type, path1, type, path2);
3587                 if (cmp <= 0)
3588                         continue;
3590                 text = strdup(text);
3591                 if (!text)
3592                         return FALSE;
3594                 if (view->lines > pos)
3595                         memmove(&view->line[pos + 1], &view->line[pos],
3596                                 (view->lines - pos) * sizeof(*line));
3598                 line = &view->line[pos];
3599                 line->data = text;
3600                 line->type = type;
3601                 view->lines++;
3602                 return TRUE;
3603         }
3605         if (!add_line_text(view, text, type))
3606                 return FALSE;
3608         if (tree_lineno > view->lineno) {
3609                 view->lineno = tree_lineno;
3610                 tree_lineno = 0;
3611         }
3613         return TRUE;
3616 static enum request
3617 tree_request(struct view *view, enum request request, struct line *line)
3619         enum open_flags flags;
3621         switch (request) {
3622         case REQ_VIEW_BLAME:
3623                 if (line->type != LINE_TREE_FILE) {
3624                         report("Blame only supported for files");
3625                         return REQ_NONE;
3626                 }
3628                 string_copy(opt_ref, view->vid);
3629                 return request;
3631         case REQ_EDIT:
3632                 if (line->type != LINE_TREE_FILE) {
3633                         report("Edit only supported for files");
3634                 } else if (!is_head_commit(view->vid)) {
3635                         report("Edit only supported for files in the current work tree");
3636                 } else {
3637                         open_editor(TRUE, opt_file);
3638                 }
3639                 return REQ_NONE;
3641         case REQ_TREE_PARENT:
3642                 if (!*opt_path) {
3643                         /* quit view if at top of tree */
3644                         return REQ_VIEW_CLOSE;
3645                 }
3646                 /* fake 'cd  ..' */
3647                 line = &view->line[1];
3648                 break;
3650         case REQ_ENTER:
3651                 break;
3653         default:
3654                 return request;
3655         }
3657         /* Cleanup the stack if the tree view is at a different tree. */
3658         while (!*opt_path && tree_stack)
3659                 pop_tree_stack_entry();
3661         switch (line->type) {
3662         case LINE_TREE_DIR:
3663                 /* Depending on whether it is a subdir or parent (updir?) link
3664                  * mangle the path buffer. */
3665                 if (line == &view->line[1] && *opt_path) {
3666                         pop_tree_stack_entry();
3668                 } else {
3669                         const char *basename = tree_path(line);
3671                         push_tree_stack_entry(basename, view->lineno);
3672                 }
3674                 /* Trees and subtrees share the same ID, so they are not not
3675                  * unique like blobs. */
3676                 flags = OPEN_RELOAD;
3677                 request = REQ_VIEW_TREE;
3678                 break;
3680         case LINE_TREE_FILE:
3681                 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
3682                 request = REQ_VIEW_BLOB;
3683                 break;
3685         default:
3686                 return TRUE;
3687         }
3689         open_view(view, request, flags);
3690         if (request == REQ_VIEW_TREE) {
3691                 view->lineno = tree_lineno;
3692         }
3694         return REQ_NONE;
3697 static void
3698 tree_select(struct view *view, struct line *line)
3700         char *text = (char *)line->data + STRING_SIZE("100644 blob ");
3702         if (line->type == LINE_TREE_FILE) {
3703                 string_copy_rev(ref_blob, text);
3704                 string_format(opt_file, "%s%s", opt_path, tree_path(line));
3706         } else if (line->type != LINE_TREE_DIR) {
3707                 return;
3708         }
3710         string_copy_rev(view->ref, text);
3713 static const char *tree_argv[SIZEOF_ARG] = {
3714         "git", "ls-tree", "%(commit)", "%(directory)", NULL
3715 };
3717 static struct view_ops tree_ops = {
3718         "file",
3719         tree_argv,
3720         NULL,
3721         tree_read,
3722         pager_draw,
3723         tree_request,
3724         pager_grep,
3725         tree_select,
3726 };
3728 static bool
3729 blob_read(struct view *view, char *line)
3731         if (!line)
3732                 return TRUE;
3733         return add_line_text(view, line, LINE_DEFAULT) != NULL;
3736 static const char *blob_argv[SIZEOF_ARG] = {
3737         "git", "cat-file", "blob", "%(blob)", NULL
3738 };
3740 static struct view_ops blob_ops = {
3741         "line",
3742         blob_argv,
3743         NULL,
3744         blob_read,
3745         pager_draw,
3746         pager_request,
3747         pager_grep,
3748         pager_select,
3749 };
3751 /*
3752  * Blame backend
3753  *
3754  * Loading the blame view is a two phase job:
3755  *
3756  *  1. File content is read either using opt_file from the
3757  *     filesystem or using git-cat-file.
3758  *  2. Then blame information is incrementally added by
3759  *     reading output from git-blame.
3760  */
3762 struct blame_commit {
3763         char id[SIZEOF_REV];            /* SHA1 ID. */
3764         char title[128];                /* First line of the commit message. */
3765         char author[75];                /* Author of the commit. */
3766         struct tm time;                 /* Date from the author ident. */
3767         char filename[128];             /* Name of file. */
3768 };
3770 struct blame {
3771         struct blame_commit *commit;
3772         char text[1];
3773 };
3775 #define BLAME_CAT_FILE_CMD "git cat-file blob %s:%s"
3776 #define BLAME_INCREMENTAL_CMD "git blame --incremental %s -- %s"
3778 static bool
3779 blame_open(struct view *view)
3781         char path[SIZEOF_STR];
3782         char ref[SIZEOF_STR] = "";
3784         if (sq_quote(path, 0, opt_file) >= sizeof(path))
3785                 return FALSE;
3787         if (*opt_ref && sq_quote(ref, 0, opt_ref) >= sizeof(ref))
3788                 return FALSE;
3790         if (*opt_ref || !init_io_fd(&view->io, fopen(opt_file, "r"))) {
3791                 const char *id = *opt_ref ? ref : "HEAD";
3793                 if (!run_io_format(&view->io, BLAME_CAT_FILE_CMD, id, path))
3794                         return FALSE;
3795         }
3797         setup_update(view, opt_file);
3798         string_format(view->ref, "%s ...", opt_file);
3800         return TRUE;
3803 static struct blame_commit *
3804 get_blame_commit(struct view *view, const char *id)
3806         size_t i;
3808         for (i = 0; i < view->lines; i++) {
3809                 struct blame *blame = view->line[i].data;
3811                 if (!blame->commit)
3812                         continue;
3814                 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
3815                         return blame->commit;
3816         }
3818         {
3819                 struct blame_commit *commit = calloc(1, sizeof(*commit));
3821                 if (commit)
3822                         string_ncopy(commit->id, id, SIZEOF_REV);
3823                 return commit;
3824         }
3827 static bool
3828 parse_number(const char **posref, size_t *number, size_t min, size_t max)
3830         const char *pos = *posref;
3832         *posref = NULL;
3833         pos = strchr(pos + 1, ' ');
3834         if (!pos || !isdigit(pos[1]))
3835                 return FALSE;
3836         *number = atoi(pos + 1);
3837         if (*number < min || *number > max)
3838                 return FALSE;
3840         *posref = pos;
3841         return TRUE;
3844 static struct blame_commit *
3845 parse_blame_commit(struct view *view, const char *text, int *blamed)
3847         struct blame_commit *commit;
3848         struct blame *blame;
3849         const char *pos = text + SIZEOF_REV - 1;
3850         size_t lineno;
3851         size_t group;
3853         if (strlen(text) <= SIZEOF_REV || *pos != ' ')
3854                 return NULL;
3856         if (!parse_number(&pos, &lineno, 1, view->lines) ||
3857             !parse_number(&pos, &group, 1, view->lines - lineno + 1))
3858                 return NULL;
3860         commit = get_blame_commit(view, text);
3861         if (!commit)
3862                 return NULL;
3864         *blamed += group;
3865         while (group--) {
3866                 struct line *line = &view->line[lineno + group - 1];
3868                 blame = line->data;
3869                 blame->commit = commit;
3870                 line->dirty = 1;
3871         }
3873         return commit;
3876 static bool
3877 blame_read_file(struct view *view, const char *line, bool *read_file)
3879         if (!line) {
3880                 char ref[SIZEOF_STR] = "";
3881                 char path[SIZEOF_STR];
3882                 struct io io = {};
3884                 if (view->lines == 0 && !view->parent)
3885                         die("No blame exist for %s", view->vid);
3887                 if (view->lines == 0 ||
3888                     sq_quote(path, 0, opt_file) >= sizeof(path) ||
3889                     (*opt_ref && sq_quote(ref, 0, opt_ref) >= sizeof(ref)) ||
3890                     !run_io_format(&io, BLAME_INCREMENTAL_CMD, ref, path)) {
3891                         report("Failed to load blame data");
3892                         return TRUE;
3893                 }
3895                 done_io(view->pipe);
3896                 view->io = io;
3897                 *read_file = FALSE;
3898                 return FALSE;
3900         } else {
3901                 size_t linelen = strlen(line);
3902                 struct blame *blame = malloc(sizeof(*blame) + linelen);
3904                 blame->commit = NULL;
3905                 strncpy(blame->text, line, linelen);
3906                 blame->text[linelen] = 0;
3907                 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
3908         }
3911 static bool
3912 match_blame_header(const char *name, char **line)
3914         size_t namelen = strlen(name);
3915         bool matched = !strncmp(name, *line, namelen);
3917         if (matched)
3918                 *line += namelen;
3920         return matched;
3923 static bool
3924 blame_read(struct view *view, char *line)
3926         static struct blame_commit *commit = NULL;
3927         static int blamed = 0;
3928         static time_t author_time;
3929         static bool read_file = TRUE;
3931         if (read_file)
3932                 return blame_read_file(view, line, &read_file);
3934         if (!line) {
3935                 /* Reset all! */
3936                 commit = NULL;
3937                 blamed = 0;
3938                 read_file = TRUE;
3939                 string_format(view->ref, "%s", view->vid);
3940                 if (view_is_displayed(view)) {
3941                         update_view_title(view);
3942                         redraw_view_from(view, 0);
3943                 }
3944                 return TRUE;
3945         }
3947         if (!commit) {
3948                 commit = parse_blame_commit(view, line, &blamed);
3949                 string_format(view->ref, "%s %2d%%", view->vid,
3950                               blamed * 100 / view->lines);
3952         } else if (match_blame_header("author ", &line)) {
3953                 string_ncopy(commit->author, line, strlen(line));
3955         } else if (match_blame_header("author-time ", &line)) {
3956                 author_time = (time_t) atol(line);
3958         } else if (match_blame_header("author-tz ", &line)) {
3959                 long tz;
3961                 tz  = ('0' - line[1]) * 60 * 60 * 10;
3962                 tz += ('0' - line[2]) * 60 * 60;
3963                 tz += ('0' - line[3]) * 60;
3964                 tz += ('0' - line[4]) * 60;
3966                 if (line[0] == '-')
3967                         tz = -tz;
3969                 author_time -= tz;
3970                 gmtime_r(&author_time, &commit->time);
3972         } else if (match_blame_header("summary ", &line)) {
3973                 string_ncopy(commit->title, line, strlen(line));
3975         } else if (match_blame_header("filename ", &line)) {
3976                 string_ncopy(commit->filename, line, strlen(line));
3977                 commit = NULL;
3978         }
3980         return TRUE;
3983 static bool
3984 blame_draw(struct view *view, struct line *line, unsigned int lineno)
3986         struct blame *blame = line->data;
3987         struct tm *time = NULL;
3988         const char *id = NULL, *author = NULL;
3990         if (blame->commit && *blame->commit->filename) {
3991                 id = blame->commit->id;
3992                 author = blame->commit->author;
3993                 time = &blame->commit->time;
3994         }
3996         if (opt_date && draw_date(view, time))
3997                 return TRUE;
3999         if (opt_author &&
4000             draw_field(view, LINE_MAIN_AUTHOR, author, opt_author_cols, TRUE))
4001                 return TRUE;
4003         if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
4004                 return TRUE;
4006         if (draw_lineno(view, lineno))
4007                 return TRUE;
4009         draw_text(view, LINE_DEFAULT, blame->text, TRUE);
4010         return TRUE;
4013 static enum request
4014 blame_request(struct view *view, enum request request, struct line *line)
4016         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4017         struct blame *blame = line->data;
4019         switch (request) {
4020         case REQ_VIEW_BLAME:
4021                 if (!blame->commit || !strcmp(blame->commit->id, NULL_ID)) {
4022                         report("Commit ID unknown");
4023                         break;
4024                 }
4025                 string_copy(opt_ref, blame->commit->id);
4026                 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4027                 return request;
4029         case REQ_ENTER:
4030                 if (!blame->commit) {
4031                         report("No commit loaded yet");
4032                         break;
4033                 }
4035                 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
4036                     !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
4037                         break;
4039                 if (!strcmp(blame->commit->id, NULL_ID)) {
4040                         char path[SIZEOF_STR];
4042                         if (sq_quote(path, 0, view->vid) >= sizeof(path))
4043                                 break;
4044                         string_format(opt_cmd, "git diff-index --root --patch-with-stat -C -M --cached HEAD -- %s 2>/dev/null", path);
4045                 }
4047                 open_view(view, REQ_VIEW_DIFF, flags);
4048                 break;
4050         default:
4051                 return request;
4052         }
4054         return REQ_NONE;
4057 static bool
4058 blame_grep(struct view *view, struct line *line)
4060         struct blame *blame = line->data;
4061         struct blame_commit *commit = blame->commit;
4062         regmatch_t pmatch;
4064 #define MATCH(text, on)                                                 \
4065         (on && *text && regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4067         if (commit) {
4068                 char buf[DATE_COLS + 1];
4070                 if (MATCH(commit->title, 1) ||
4071                     MATCH(commit->author, opt_author) ||
4072                     MATCH(commit->id, opt_date))
4073                         return TRUE;
4075                 if (strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time) &&
4076                     MATCH(buf, 1))
4077                         return TRUE;
4078         }
4080         return MATCH(blame->text, 1);
4082 #undef MATCH
4085 static void
4086 blame_select(struct view *view, struct line *line)
4088         struct blame *blame = line->data;
4089         struct blame_commit *commit = blame->commit;
4091         if (!commit)
4092                 return;
4094         if (!strcmp(commit->id, NULL_ID))
4095                 string_ncopy(ref_commit, "HEAD", 4);
4096         else
4097                 string_copy_rev(ref_commit, commit->id);
4100 static struct view_ops blame_ops = {
4101         "line",
4102         NULL,
4103         blame_open,
4104         blame_read,
4105         blame_draw,
4106         blame_request,
4107         blame_grep,
4108         blame_select,
4109 };
4111 /*
4112  * Status backend
4113  */
4115 struct status {
4116         char status;
4117         struct {
4118                 mode_t mode;
4119                 char rev[SIZEOF_REV];
4120                 char name[SIZEOF_STR];
4121         } old;
4122         struct {
4123                 mode_t mode;
4124                 char rev[SIZEOF_REV];
4125                 char name[SIZEOF_STR];
4126         } new;
4127 };
4129 static char status_onbranch[SIZEOF_STR];
4130 static struct status stage_status;
4131 static enum line_type stage_line_type;
4132 static size_t stage_chunks;
4133 static int *stage_chunk;
4135 /* This should work even for the "On branch" line. */
4136 static inline bool
4137 status_has_none(struct view *view, struct line *line)
4139         return line < view->line + view->lines && !line[1].data;
4142 /* Get fields from the diff line:
4143  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
4144  */
4145 static inline bool
4146 status_get_diff(struct status *file, const char *buf, size_t bufsize)
4148         const char *old_mode = buf +  1;
4149         const char *new_mode = buf +  8;
4150         const char *old_rev  = buf + 15;
4151         const char *new_rev  = buf + 56;
4152         const char *status   = buf + 97;
4154         if (bufsize < 99 ||
4155             old_mode[-1] != ':' ||
4156             new_mode[-1] != ' ' ||
4157             old_rev[-1]  != ' ' ||
4158             new_rev[-1]  != ' ' ||
4159             status[-1]   != ' ')
4160                 return FALSE;
4162         file->status = *status;
4164         string_copy_rev(file->old.rev, old_rev);
4165         string_copy_rev(file->new.rev, new_rev);
4167         file->old.mode = strtoul(old_mode, NULL, 8);
4168         file->new.mode = strtoul(new_mode, NULL, 8);
4170         file->old.name[0] = file->new.name[0] = 0;
4172         return TRUE;
4175 static bool
4176 status_run(struct view *view, const char cmd[], char status, enum line_type type)
4178         struct status *file = NULL;
4179         struct status *unmerged = NULL;
4180         char buf[SIZEOF_STR * 4];
4181         size_t bufsize = 0;
4182         FILE *pipe;
4184         pipe = popen(cmd, "r");
4185         if (!pipe)
4186                 return FALSE;
4188         add_line_data(view, NULL, type);
4190         while (!feof(pipe) && !ferror(pipe)) {
4191                 char *sep;
4192                 size_t readsize;
4194                 readsize = fread(buf + bufsize, 1, sizeof(buf) - bufsize, pipe);
4195                 if (!readsize)
4196                         break;
4197                 bufsize += readsize;
4199                 /* Process while we have NUL chars. */
4200                 while ((sep = memchr(buf, 0, bufsize))) {
4201                         size_t sepsize = sep - buf + 1;
4203                         if (!file) {
4204                                 if (!realloc_lines(view, view->line_size + 1))
4205                                         goto error_out;
4207                                 file = calloc(1, sizeof(*file));
4208                                 if (!file)
4209                                         goto error_out;
4211                                 add_line_data(view, file, type);
4212                         }
4214                         /* Parse diff info part. */
4215                         if (status) {
4216                                 file->status = status;
4217                                 if (status == 'A')
4218                                         string_copy(file->old.rev, NULL_ID);
4220                         } else if (!file->status) {
4221                                 if (!status_get_diff(file, buf, sepsize))
4222                                         goto error_out;
4224                                 bufsize -= sepsize;
4225                                 memmove(buf, sep + 1, bufsize);
4227                                 sep = memchr(buf, 0, bufsize);
4228                                 if (!sep)
4229                                         break;
4230                                 sepsize = sep - buf + 1;
4232                                 /* Collapse all 'M'odified entries that
4233                                  * follow a associated 'U'nmerged entry.
4234                                  */
4235                                 if (file->status == 'U') {
4236                                         unmerged = file;
4238                                 } else if (unmerged) {
4239                                         int collapse = !strcmp(buf, unmerged->new.name);
4241                                         unmerged = NULL;
4242                                         if (collapse) {
4243                                                 free(file);
4244                                                 view->lines--;
4245                                                 continue;
4246                                         }
4247                                 }
4248                         }
4250                         /* Grab the old name for rename/copy. */
4251                         if (!*file->old.name &&
4252                             (file->status == 'R' || file->status == 'C')) {
4253                                 sepsize = sep - buf + 1;
4254                                 string_ncopy(file->old.name, buf, sepsize);
4255                                 bufsize -= sepsize;
4256                                 memmove(buf, sep + 1, bufsize);
4258                                 sep = memchr(buf, 0, bufsize);
4259                                 if (!sep)
4260                                         break;
4261                                 sepsize = sep - buf + 1;
4262                         }
4264                         /* git-ls-files just delivers a NUL separated
4265                          * list of file names similar to the second half
4266                          * of the git-diff-* output. */
4267                         string_ncopy(file->new.name, buf, sepsize);
4268                         if (!*file->old.name)
4269                                 string_copy(file->old.name, file->new.name);
4270                         bufsize -= sepsize;
4271                         memmove(buf, sep + 1, bufsize);
4272                         file = NULL;
4273                 }
4274         }
4276         if (ferror(pipe)) {
4277 error_out:
4278                 pclose(pipe);
4279                 return FALSE;
4280         }
4282         if (!view->line[view->lines - 1].data)
4283                 add_line_data(view, NULL, LINE_STAT_NONE);
4285         pclose(pipe);
4286         return TRUE;
4289 /* Don't show unmerged entries in the staged section. */
4290 #define STATUS_DIFF_INDEX_CMD "git diff-index -z --diff-filter=ACDMRTXB --cached -M HEAD"
4291 #define STATUS_DIFF_FILES_CMD "git diff-files -z"
4292 #define STATUS_LIST_OTHER_CMD \
4293         "git ls-files -z --others --exclude-standard"
4294 #define STATUS_LIST_NO_HEAD_CMD \
4295         "git ls-files -z --cached --exclude-standard"
4297 #define STATUS_DIFF_INDEX_SHOW_CMD \
4298         "git diff-index --root --patch-with-stat -C -M --cached HEAD -- %s %s 2>/dev/null"
4300 #define STATUS_DIFF_FILES_SHOW_CMD \
4301         "git diff-files --root --patch-with-stat -C -M -- %s %s 2>/dev/null"
4303 #define STATUS_DIFF_NO_HEAD_SHOW_CMD \
4304         "git diff --no-color --patch-with-stat /dev/null %s 2>/dev/null"
4306 /* First parse staged info using git-diff-index(1), then parse unstaged
4307  * info using git-diff-files(1), and finally untracked files using
4308  * git-ls-files(1). */
4309 static bool
4310 status_open(struct view *view)
4312         unsigned long prev_lineno = view->lineno;
4314         reset_view(view);
4316         if (!realloc_lines(view, view->line_size + 7))
4317                 return FALSE;
4319         add_line_data(view, NULL, LINE_STAT_HEAD);
4320         if (is_initial_commit())
4321                 string_copy(status_onbranch, "Initial commit");
4322         else if (!*opt_head)
4323                 string_copy(status_onbranch, "Not currently on any branch");
4324         else if (!string_format(status_onbranch, "On branch %s", opt_head))
4325                 return FALSE;
4327         system("git update-index -q --refresh >/dev/null 2>/dev/null");
4329         if (is_initial_commit()) {
4330                 if (!status_run(view, STATUS_LIST_NO_HEAD_CMD, 'A', LINE_STAT_STAGED))
4331                         return FALSE;
4332         } else if (!status_run(view, STATUS_DIFF_INDEX_CMD, 0, LINE_STAT_STAGED)) {
4333                 return FALSE;
4334         }
4336         if (!status_run(view, STATUS_DIFF_FILES_CMD, 0, LINE_STAT_UNSTAGED) ||
4337             !status_run(view, STATUS_LIST_OTHER_CMD, '?', LINE_STAT_UNTRACKED))
4338                 return FALSE;
4340         /* If all went well restore the previous line number to stay in
4341          * the context or select a line with something that can be
4342          * updated. */
4343         if (prev_lineno >= view->lines)
4344                 prev_lineno = view->lines - 1;
4345         while (prev_lineno < view->lines && !view->line[prev_lineno].data)
4346                 prev_lineno++;
4347         while (prev_lineno > 0 && !view->line[prev_lineno].data)
4348                 prev_lineno--;
4350         /* If the above fails, always skip the "On branch" line. */
4351         if (prev_lineno < view->lines)
4352                 view->lineno = prev_lineno;
4353         else
4354                 view->lineno = 1;
4356         if (view->lineno < view->offset)
4357                 view->offset = view->lineno;
4358         else if (view->offset + view->height <= view->lineno)
4359                 view->offset = view->lineno - view->height + 1;
4361         return TRUE;
4364 static bool
4365 status_draw(struct view *view, struct line *line, unsigned int lineno)
4367         struct status *status = line->data;
4368         enum line_type type;
4369         const char *text;
4371         if (!status) {
4372                 switch (line->type) {
4373                 case LINE_STAT_STAGED:
4374                         type = LINE_STAT_SECTION;
4375                         text = "Changes to be committed:";
4376                         break;
4378                 case LINE_STAT_UNSTAGED:
4379                         type = LINE_STAT_SECTION;
4380                         text = "Changed but not updated:";
4381                         break;
4383                 case LINE_STAT_UNTRACKED:
4384                         type = LINE_STAT_SECTION;
4385                         text = "Untracked files:";
4386                         break;
4388                 case LINE_STAT_NONE:
4389                         type = LINE_DEFAULT;
4390                         text = "    (no files)";
4391                         break;
4393                 case LINE_STAT_HEAD:
4394                         type = LINE_STAT_HEAD;
4395                         text = status_onbranch;
4396                         break;
4398                 default:
4399                         return FALSE;
4400                 }
4401         } else {
4402                 static char buf[] = { '?', ' ', ' ', ' ', 0 };
4404                 buf[0] = status->status;
4405                 if (draw_text(view, line->type, buf, TRUE))
4406                         return TRUE;
4407                 type = LINE_DEFAULT;
4408                 text = status->new.name;
4409         }
4411         draw_text(view, type, text, TRUE);
4412         return TRUE;
4415 static enum request
4416 status_enter(struct view *view, struct line *line)
4418         struct status *status = line->data;
4419         char oldpath[SIZEOF_STR] = "";
4420         char newpath[SIZEOF_STR] = "";
4421         const char *info;
4422         size_t cmdsize = 0;
4423         enum open_flags split;
4425         if (line->type == LINE_STAT_NONE ||
4426             (!status && line[1].type == LINE_STAT_NONE)) {
4427                 report("No file to diff");
4428                 return REQ_NONE;
4429         }
4431         if (status) {
4432                 if (sq_quote(oldpath, 0, status->old.name) >= sizeof(oldpath))
4433                         return REQ_QUIT;
4434                 /* Diffs for unmerged entries are empty when pasing the
4435                  * new path, so leave it empty. */
4436                 if (status->status != 'U' &&
4437                     sq_quote(newpath, 0, status->new.name) >= sizeof(newpath))
4438                         return REQ_QUIT;
4439         }
4441         if (opt_cdup[0] &&
4442             line->type != LINE_STAT_UNTRACKED &&
4443             !string_format_from(opt_cmd, &cmdsize, "cd %s;", opt_cdup))
4444                 return REQ_QUIT;
4446         switch (line->type) {
4447         case LINE_STAT_STAGED:
4448                 if (is_initial_commit()) {
4449                         if (!string_format_from(opt_cmd, &cmdsize,
4450                                                 STATUS_DIFF_NO_HEAD_SHOW_CMD,
4451                                                 newpath))
4452                                 return REQ_QUIT;
4453                 } else {
4454                         if (!string_format_from(opt_cmd, &cmdsize,
4455                                                 STATUS_DIFF_INDEX_SHOW_CMD,
4456                                                 oldpath, newpath))
4457                                 return REQ_QUIT;
4458                 }
4460                 if (status)
4461                         info = "Staged changes to %s";
4462                 else
4463                         info = "Staged changes";
4464                 break;
4466         case LINE_STAT_UNSTAGED:
4467                 if (!string_format_from(opt_cmd, &cmdsize,
4468                                         STATUS_DIFF_FILES_SHOW_CMD, oldpath, newpath))
4469                         return REQ_QUIT;
4470                 if (status)
4471                         info = "Unstaged changes to %s";
4472                 else
4473                         info = "Unstaged changes";
4474                 break;
4476         case LINE_STAT_UNTRACKED:
4477                 if (opt_pipe)
4478                         return REQ_QUIT;
4480                 if (!status) {
4481                         report("No file to show");
4482                         return REQ_NONE;
4483                 }
4485                 if (!suffixcmp(status->new.name, -1, "/")) {
4486                         report("Cannot display a directory");
4487                         return REQ_NONE;
4488                 }
4490                 opt_pipe = fopen(status->new.name, "r");
4491                 info = "Untracked file %s";
4492                 break;
4494         case LINE_STAT_HEAD:
4495                 return REQ_NONE;
4497         default:
4498                 die("line type %d not handled in switch", line->type);
4499         }
4501         split = view_is_displayed(view) ? OPEN_SPLIT : 0;
4502         open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH | split);
4503         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
4504                 if (status) {
4505                         stage_status = *status;
4506                 } else {
4507                         memset(&stage_status, 0, sizeof(stage_status));
4508                 }
4510                 stage_line_type = line->type;
4511                 stage_chunks = 0;
4512                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
4513         }
4515         return REQ_NONE;
4518 static bool
4519 status_exists(struct status *status, enum line_type type)
4521         struct view *view = VIEW(REQ_VIEW_STATUS);
4522         struct line *line;
4524         for (line = view->line; line < view->line + view->lines; line++) {
4525                 struct status *pos = line->data;
4527                 if (line->type == type && pos &&
4528                     !strcmp(status->new.name, pos->new.name))
4529                         return TRUE;
4530         }
4532         return FALSE;
4536 static FILE *
4537 status_update_prepare(enum line_type type)
4539         char cmd[SIZEOF_STR];
4540         size_t cmdsize = 0;
4542         if (opt_cdup[0] &&
4543             type != LINE_STAT_UNTRACKED &&
4544             !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
4545                 return NULL;
4547         switch (type) {
4548         case LINE_STAT_STAGED:
4549                 string_add(cmd, cmdsize, "git update-index -z --index-info");
4550                 break;
4552         case LINE_STAT_UNSTAGED:
4553         case LINE_STAT_UNTRACKED:
4554                 string_add(cmd, cmdsize, "git update-index -z --add --remove --stdin");
4555                 break;
4557         default:
4558                 die("line type %d not handled in switch", type);
4559         }
4561         return popen(cmd, "w");
4564 static bool
4565 status_update_write(FILE *pipe, struct status *status, enum line_type type)
4567         char buf[SIZEOF_STR];
4568         size_t bufsize = 0;
4569         size_t written = 0;
4571         switch (type) {
4572         case LINE_STAT_STAGED:
4573                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
4574                                         status->old.mode,
4575                                         status->old.rev,
4576                                         status->old.name, 0))
4577                         return FALSE;
4578                 break;
4580         case LINE_STAT_UNSTAGED:
4581         case LINE_STAT_UNTRACKED:
4582                 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
4583                         return FALSE;
4584                 break;
4586         default:
4587                 die("line type %d not handled in switch", type);
4588         }
4590         while (!ferror(pipe) && written < bufsize) {
4591                 written += fwrite(buf + written, 1, bufsize - written, pipe);
4592         }
4594         return written == bufsize;
4597 static bool
4598 status_update_file(struct status *status, enum line_type type)
4600         FILE *pipe = status_update_prepare(type);
4601         bool result;
4603         if (!pipe)
4604                 return FALSE;
4606         result = status_update_write(pipe, status, type);
4607         pclose(pipe);
4608         return result;
4611 static bool
4612 status_update_files(struct view *view, struct line *line)
4614         FILE *pipe = status_update_prepare(line->type);
4615         bool result = TRUE;
4616         struct line *pos = view->line + view->lines;
4617         int files = 0;
4618         int file, done;
4620         if (!pipe)
4621                 return FALSE;
4623         for (pos = line; pos < view->line + view->lines && pos->data; pos++)
4624                 files++;
4626         for (file = 0, done = 0; result && file < files; line++, file++) {
4627                 int almost_done = file * 100 / files;
4629                 if (almost_done > done) {
4630                         done = almost_done;
4631                         string_format(view->ref, "updating file %u of %u (%d%% done)",
4632                                       file, files, done);
4633                         update_view_title(view);
4634                 }
4635                 result = status_update_write(pipe, line->data, line->type);
4636         }
4638         pclose(pipe);
4639         return result;
4642 static bool
4643 status_update(struct view *view)
4645         struct line *line = &view->line[view->lineno];
4647         assert(view->lines);
4649         if (!line->data) {
4650                 /* This should work even for the "On branch" line. */
4651                 if (line < view->line + view->lines && !line[1].data) {
4652                         report("Nothing to update");
4653                         return FALSE;
4654                 }
4656                 if (!status_update_files(view, line + 1)) {
4657                         report("Failed to update file status");
4658                         return FALSE;
4659                 }
4661         } else if (!status_update_file(line->data, line->type)) {
4662                 report("Failed to update file status");
4663                 return FALSE;
4664         }
4666         return TRUE;
4669 static bool
4670 status_revert(struct status *status, enum line_type type, bool has_none)
4672         if (!status || type != LINE_STAT_UNSTAGED) {
4673                 if (type == LINE_STAT_STAGED) {
4674                         report("Cannot revert changes to staged files");
4675                 } else if (type == LINE_STAT_UNTRACKED) {
4676                         report("Cannot revert changes to untracked files");
4677                 } else if (has_none) {
4678                         report("Nothing to revert");
4679                 } else {
4680                         report("Cannot revert changes to multiple files");
4681                 }
4682                 return FALSE;
4684         } else {
4685                 const char *checkout_argv[] = {
4686                         "git", "checkout", "--", status->old.name, NULL
4687                 };
4689                 if (!prompt_yesno("Are you sure you want to overwrite any changes?"))
4690                         return FALSE;
4691                 return run_io_fg(checkout_argv, opt_cdup);
4692         }
4695 static enum request
4696 status_request(struct view *view, enum request request, struct line *line)
4698         struct status *status = line->data;
4700         switch (request) {
4701         case REQ_STATUS_UPDATE:
4702                 if (!status_update(view))
4703                         return REQ_NONE;
4704                 break;
4706         case REQ_STATUS_REVERT:
4707                 if (!status_revert(status, line->type, status_has_none(view, line)))
4708                         return REQ_NONE;
4709                 break;
4711         case REQ_STATUS_MERGE:
4712                 if (!status || status->status != 'U') {
4713                         report("Merging only possible for files with unmerged status ('U').");
4714                         return REQ_NONE;
4715                 }
4716                 open_mergetool(status->new.name);
4717                 break;
4719         case REQ_EDIT:
4720                 if (!status)
4721                         return request;
4722                 if (status->status == 'D') {
4723                         report("File has been deleted.");
4724                         return REQ_NONE;
4725                 }
4727                 open_editor(status->status != '?', status->new.name);
4728                 break;
4730         case REQ_VIEW_BLAME:
4731                 if (status) {
4732                         string_copy(opt_file, status->new.name);
4733                         opt_ref[0] = 0;
4734                 }
4735                 return request;
4737         case REQ_ENTER:
4738                 /* After returning the status view has been split to
4739                  * show the stage view. No further reloading is
4740                  * necessary. */
4741                 status_enter(view, line);
4742                 return REQ_NONE;
4744         case REQ_REFRESH:
4745                 /* Simply reload the view. */
4746                 break;
4748         default:
4749                 return request;
4750         }
4752         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
4754         return REQ_NONE;
4757 static void
4758 status_select(struct view *view, struct line *line)
4760         struct status *status = line->data;
4761         char file[SIZEOF_STR] = "all files";
4762         const char *text;
4763         const char *key;
4765         if (status && !string_format(file, "'%s'", status->new.name))
4766                 return;
4768         if (!status && line[1].type == LINE_STAT_NONE)
4769                 line++;
4771         switch (line->type) {
4772         case LINE_STAT_STAGED:
4773                 text = "Press %s to unstage %s for commit";
4774                 break;
4776         case LINE_STAT_UNSTAGED:
4777                 text = "Press %s to stage %s for commit";
4778                 break;
4780         case LINE_STAT_UNTRACKED:
4781                 text = "Press %s to stage %s for addition";
4782                 break;
4784         case LINE_STAT_HEAD:
4785         case LINE_STAT_NONE:
4786                 text = "Nothing to update";
4787                 break;
4789         default:
4790                 die("line type %d not handled in switch", line->type);
4791         }
4793         if (status && status->status == 'U') {
4794                 text = "Press %s to resolve conflict in %s";
4795                 key = get_key(REQ_STATUS_MERGE);
4797         } else {
4798                 key = get_key(REQ_STATUS_UPDATE);
4799         }
4801         string_format(view->ref, text, key, file);
4804 static bool
4805 status_grep(struct view *view, struct line *line)
4807         struct status *status = line->data;
4808         enum { S_STATUS, S_NAME, S_END } state;
4809         char buf[2] = "?";
4810         regmatch_t pmatch;
4812         if (!status)
4813                 return FALSE;
4815         for (state = S_STATUS; state < S_END; state++) {
4816                 const char *text;
4818                 switch (state) {
4819                 case S_NAME:    text = status->new.name;        break;
4820                 case S_STATUS:
4821                         buf[0] = status->status;
4822                         text = buf;
4823                         break;
4825                 default:
4826                         return FALSE;
4827                 }
4829                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4830                         return TRUE;
4831         }
4833         return FALSE;
4836 static struct view_ops status_ops = {
4837         "file",
4838         NULL,
4839         status_open,
4840         NULL,
4841         status_draw,
4842         status_request,
4843         status_grep,
4844         status_select,
4845 };
4848 static bool
4849 stage_diff_line(FILE *pipe, struct line *line)
4851         const char *buf = line->data;
4852         size_t bufsize = strlen(buf);
4853         size_t written = 0;
4855         while (!ferror(pipe) && written < bufsize) {
4856                 written += fwrite(buf + written, 1, bufsize - written, pipe);
4857         }
4859         fputc('\n', pipe);
4861         return written == bufsize;
4864 static bool
4865 stage_diff_write(FILE *pipe, struct line *line, struct line *end)
4867         while (line < end) {
4868                 if (!stage_diff_line(pipe, line++))
4869                         return FALSE;
4870                 if (line->type == LINE_DIFF_CHUNK ||
4871                     line->type == LINE_DIFF_HEADER)
4872                         break;
4873         }
4875         return TRUE;
4878 static struct line *
4879 stage_diff_find(struct view *view, struct line *line, enum line_type type)
4881         for (; view->line < line; line--)
4882                 if (line->type == type)
4883                         return line;
4885         return NULL;
4888 static bool
4889 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
4891         char cmd[SIZEOF_STR];
4892         size_t cmdsize = 0;
4893         struct line *diff_hdr;
4894         FILE *pipe;
4896         diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
4897         if (!diff_hdr)
4898                 return FALSE;
4900         if (opt_cdup[0] &&
4901             !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
4902                 return FALSE;
4904         if (!string_format_from(cmd, &cmdsize,
4905                                 "git apply --whitespace=nowarn %s %s - && "
4906                                 "git update-index -q --unmerged --refresh 2>/dev/null",
4907                                 revert ? "" : "--cached",
4908                                 revert || stage_line_type == LINE_STAT_STAGED ? "-R" : ""))
4909                 return FALSE;
4911         pipe = popen(cmd, "w");
4912         if (!pipe)
4913                 return FALSE;
4915         if (!stage_diff_write(pipe, diff_hdr, chunk) ||
4916             !stage_diff_write(pipe, chunk, view->line + view->lines))
4917                 chunk = NULL;
4919         pclose(pipe);
4921         return chunk ? TRUE : FALSE;
4924 static bool
4925 stage_update(struct view *view, struct line *line)
4927         struct line *chunk = NULL;
4929         if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
4930                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
4932         if (chunk) {
4933                 if (!stage_apply_chunk(view, chunk, FALSE)) {
4934                         report("Failed to apply chunk");
4935                         return FALSE;
4936                 }
4938         } else if (!stage_status.status) {
4939                 view = VIEW(REQ_VIEW_STATUS);
4941                 for (line = view->line; line < view->line + view->lines; line++)
4942                         if (line->type == stage_line_type)
4943                                 break;
4945                 if (!status_update_files(view, line + 1)) {
4946                         report("Failed to update files");
4947                         return FALSE;
4948                 }
4950         } else if (!status_update_file(&stage_status, stage_line_type)) {
4951                 report("Failed to update file");
4952                 return FALSE;
4953         }
4955         return TRUE;
4958 static bool
4959 stage_revert(struct view *view, struct line *line)
4961         struct line *chunk = NULL;
4963         if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
4964                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
4966         if (chunk) {
4967                 if (!prompt_yesno("Are you sure you want to revert changes?"))
4968                         return FALSE;
4970                 if (!stage_apply_chunk(view, chunk, TRUE)) {
4971                         report("Failed to revert chunk");
4972                         return FALSE;
4973                 }
4974                 return TRUE;
4976         } else {
4977                 return status_revert(stage_status.status ? &stage_status : NULL,
4978                                      stage_line_type, FALSE);
4979         }
4983 static void
4984 stage_next(struct view *view, struct line *line)
4986         int i;
4988         if (!stage_chunks) {
4989                 static size_t alloc = 0;
4990                 int *tmp;
4992                 for (line = view->line; line < view->line + view->lines; line++) {
4993                         if (line->type != LINE_DIFF_CHUNK)
4994                                 continue;
4996                         tmp = realloc_items(stage_chunk, &alloc,
4997                                             stage_chunks, sizeof(*tmp));
4998                         if (!tmp) {
4999                                 report("Allocation failure");
5000                                 return;
5001                         }
5003                         stage_chunk = tmp;
5004                         stage_chunk[stage_chunks++] = line - view->line;
5005                 }
5006         }
5008         for (i = 0; i < stage_chunks; i++) {
5009                 if (stage_chunk[i] > view->lineno) {
5010                         do_scroll_view(view, stage_chunk[i] - view->lineno);
5011                         report("Chunk %d of %d", i + 1, stage_chunks);
5012                         return;
5013                 }
5014         }
5016         report("No next chunk found");
5019 static enum request
5020 stage_request(struct view *view, enum request request, struct line *line)
5022         switch (request) {
5023         case REQ_STATUS_UPDATE:
5024                 if (!stage_update(view, line))
5025                         return REQ_NONE;
5026                 break;
5028         case REQ_STATUS_REVERT:
5029                 if (!stage_revert(view, line))
5030                         return REQ_NONE;
5031                 break;
5033         case REQ_STAGE_NEXT:
5034                 if (stage_line_type == LINE_STAT_UNTRACKED) {
5035                         report("File is untracked; press %s to add",
5036                                get_key(REQ_STATUS_UPDATE));
5037                         return REQ_NONE;
5038                 }
5039                 stage_next(view, line);
5040                 return REQ_NONE;
5042         case REQ_EDIT:
5043                 if (!stage_status.new.name[0])
5044                         return request;
5045                 if (stage_status.status == 'D') {
5046                         report("File has been deleted.");
5047                         return REQ_NONE;
5048                 }
5050                 open_editor(stage_status.status != '?', stage_status.new.name);
5051                 break;
5053         case REQ_REFRESH:
5054                 /* Reload everything ... */
5055                 break;
5057         case REQ_VIEW_BLAME:
5058                 if (stage_status.new.name[0]) {
5059                         string_copy(opt_file, stage_status.new.name);
5060                         opt_ref[0] = 0;
5061                 }
5062                 return request;
5064         case REQ_ENTER:
5065                 return pager_request(view, request, line);
5067         default:
5068                 return request;
5069         }
5071         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD | OPEN_NOMAXIMIZE);
5073         /* Check whether the staged entry still exists, and close the
5074          * stage view if it doesn't. */
5075         if (!status_exists(&stage_status, stage_line_type))
5076                 return REQ_VIEW_CLOSE;
5078         if (stage_line_type == LINE_STAT_UNTRACKED) {
5079                 if (!suffixcmp(stage_status.new.name, -1, "/")) {
5080                         report("Cannot display a directory");
5081                         return REQ_NONE;
5082                 }
5084                 opt_pipe = fopen(stage_status.new.name, "r");
5085         }
5086         open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
5088         return REQ_NONE;
5091 static struct view_ops stage_ops = {
5092         "line",
5093         NULL,
5094         NULL,
5095         pager_read,
5096         pager_draw,
5097         stage_request,
5098         pager_grep,
5099         pager_select,
5100 };
5103 /*
5104  * Revision graph
5105  */
5107 struct commit {
5108         char id[SIZEOF_REV];            /* SHA1 ID. */
5109         char title[128];                /* First line of the commit message. */
5110         char author[75];                /* Author of the commit. */
5111         struct tm time;                 /* Date from the author ident. */
5112         struct ref **refs;              /* Repository references. */
5113         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
5114         size_t graph_size;              /* The width of the graph array. */
5115         bool has_parents;               /* Rewritten --parents seen. */
5116 };
5118 /* Size of rev graph with no  "padding" columns */
5119 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
5121 struct rev_graph {
5122         struct rev_graph *prev, *next, *parents;
5123         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
5124         size_t size;
5125         struct commit *commit;
5126         size_t pos;
5127         unsigned int boundary:1;
5128 };
5130 /* Parents of the commit being visualized. */
5131 static struct rev_graph graph_parents[4];
5133 /* The current stack of revisions on the graph. */
5134 static struct rev_graph graph_stacks[4] = {
5135         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
5136         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
5137         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
5138         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
5139 };
5141 static inline bool
5142 graph_parent_is_merge(struct rev_graph *graph)
5144         return graph->parents->size > 1;
5147 static inline void
5148 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
5150         struct commit *commit = graph->commit;
5152         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
5153                 commit->graph[commit->graph_size++] = symbol;
5156 static void
5157 clear_rev_graph(struct rev_graph *graph)
5159         graph->boundary = 0;
5160         graph->size = graph->pos = 0;
5161         graph->commit = NULL;
5162         memset(graph->parents, 0, sizeof(*graph->parents));
5165 static void
5166 done_rev_graph(struct rev_graph *graph)
5168         if (graph_parent_is_merge(graph) &&
5169             graph->pos < graph->size - 1 &&
5170             graph->next->size == graph->size + graph->parents->size - 1) {
5171                 size_t i = graph->pos + graph->parents->size - 1;
5173                 graph->commit->graph_size = i * 2;
5174                 while (i < graph->next->size - 1) {
5175                         append_to_rev_graph(graph, ' ');
5176                         append_to_rev_graph(graph, '\\');
5177                         i++;
5178                 }
5179         }
5181         clear_rev_graph(graph);
5184 static void
5185 push_rev_graph(struct rev_graph *graph, const char *parent)
5187         int i;
5189         /* "Collapse" duplicate parents lines.
5190          *
5191          * FIXME: This needs to also update update the drawn graph but
5192          * for now it just serves as a method for pruning graph lines. */
5193         for (i = 0; i < graph->size; i++)
5194                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
5195                         return;
5197         if (graph->size < SIZEOF_REVITEMS) {
5198                 string_copy_rev(graph->rev[graph->size++], parent);
5199         }
5202 static chtype
5203 get_rev_graph_symbol(struct rev_graph *graph)
5205         chtype symbol;
5207         if (graph->boundary)
5208                 symbol = REVGRAPH_BOUND;
5209         else if (graph->parents->size == 0)
5210                 symbol = REVGRAPH_INIT;
5211         else if (graph_parent_is_merge(graph))
5212                 symbol = REVGRAPH_MERGE;
5213         else if (graph->pos >= graph->size)
5214                 symbol = REVGRAPH_BRANCH;
5215         else
5216                 symbol = REVGRAPH_COMMIT;
5218         return symbol;
5221 static void
5222 draw_rev_graph(struct rev_graph *graph)
5224         struct rev_filler {
5225                 chtype separator, line;
5226         };
5227         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
5228         static struct rev_filler fillers[] = {
5229                 { ' ',  '|' },
5230                 { '`',  '.' },
5231                 { '\'', ' ' },
5232                 { '/',  ' ' },
5233         };
5234         chtype symbol = get_rev_graph_symbol(graph);
5235         struct rev_filler *filler;
5236         size_t i;
5238         if (opt_line_graphics)
5239                 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
5241         filler = &fillers[DEFAULT];
5243         for (i = 0; i < graph->pos; i++) {
5244                 append_to_rev_graph(graph, filler->line);
5245                 if (graph_parent_is_merge(graph->prev) &&
5246                     graph->prev->pos == i)
5247                         filler = &fillers[RSHARP];
5249                 append_to_rev_graph(graph, filler->separator);
5250         }
5252         /* Place the symbol for this revision. */
5253         append_to_rev_graph(graph, symbol);
5255         if (graph->prev->size > graph->size)
5256                 filler = &fillers[RDIAG];
5257         else
5258                 filler = &fillers[DEFAULT];
5260         i++;
5262         for (; i < graph->size; i++) {
5263                 append_to_rev_graph(graph, filler->separator);
5264                 append_to_rev_graph(graph, filler->line);
5265                 if (graph_parent_is_merge(graph->prev) &&
5266                     i < graph->prev->pos + graph->parents->size)
5267                         filler = &fillers[RSHARP];
5268                 if (graph->prev->size > graph->size)
5269                         filler = &fillers[LDIAG];
5270         }
5272         if (graph->prev->size > graph->size) {
5273                 append_to_rev_graph(graph, filler->separator);
5274                 if (filler->line != ' ')
5275                         append_to_rev_graph(graph, filler->line);
5276         }
5279 /* Prepare the next rev graph */
5280 static void
5281 prepare_rev_graph(struct rev_graph *graph)
5283         size_t i;
5285         /* First, traverse all lines of revisions up to the active one. */
5286         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
5287                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
5288                         break;
5290                 push_rev_graph(graph->next, graph->rev[graph->pos]);
5291         }
5293         /* Interleave the new revision parent(s). */
5294         for (i = 0; !graph->boundary && i < graph->parents->size; i++)
5295                 push_rev_graph(graph->next, graph->parents->rev[i]);
5297         /* Lastly, put any remaining revisions. */
5298         for (i = graph->pos + 1; i < graph->size; i++)
5299                 push_rev_graph(graph->next, graph->rev[i]);
5302 static void
5303 update_rev_graph(struct rev_graph *graph)
5305         /* If this is the finalizing update ... */
5306         if (graph->commit)
5307                 prepare_rev_graph(graph);
5309         /* Graph visualization needs a one rev look-ahead,
5310          * so the first update doesn't visualize anything. */
5311         if (!graph->prev->commit)
5312                 return;
5314         draw_rev_graph(graph->prev);
5315         done_rev_graph(graph->prev->prev);
5319 /*
5320  * Main view backend
5321  */
5323 static const char *main_argv[SIZEOF_ARG] = {
5324         "git", "log", "--no-color", "--pretty=raw", "--parents",
5325                       "--topo-order", "%(head)", NULL
5326 };
5328 static bool
5329 main_draw(struct view *view, struct line *line, unsigned int lineno)
5331         struct commit *commit = line->data;
5333         if (!*commit->author)
5334                 return FALSE;
5336         if (opt_date && draw_date(view, &commit->time))
5337                 return TRUE;
5339         if (opt_author &&
5340             draw_field(view, LINE_MAIN_AUTHOR, commit->author, opt_author_cols, TRUE))
5341                 return TRUE;
5343         if (opt_rev_graph && commit->graph_size &&
5344             draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
5345                 return TRUE;
5347         if (opt_show_refs && commit->refs) {
5348                 size_t i = 0;
5350                 do {
5351                         enum line_type type;
5353                         if (commit->refs[i]->head)
5354                                 type = LINE_MAIN_HEAD;
5355                         else if (commit->refs[i]->ltag)
5356                                 type = LINE_MAIN_LOCAL_TAG;
5357                         else if (commit->refs[i]->tag)
5358                                 type = LINE_MAIN_TAG;
5359                         else if (commit->refs[i]->tracked)
5360                                 type = LINE_MAIN_TRACKED;
5361                         else if (commit->refs[i]->remote)
5362                                 type = LINE_MAIN_REMOTE;
5363                         else
5364                                 type = LINE_MAIN_REF;
5366                         if (draw_text(view, type, "[", TRUE) ||
5367                             draw_text(view, type, commit->refs[i]->name, TRUE) ||
5368                             draw_text(view, type, "]", TRUE))
5369                                 return TRUE;
5371                         if (draw_text(view, LINE_DEFAULT, " ", TRUE))
5372                                 return TRUE;
5373                 } while (commit->refs[i++]->next);
5374         }
5376         draw_text(view, LINE_DEFAULT, commit->title, TRUE);
5377         return TRUE;
5380 /* Reads git log --pretty=raw output and parses it into the commit struct. */
5381 static bool
5382 main_read(struct view *view, char *line)
5384         static struct rev_graph *graph = graph_stacks;
5385         enum line_type type;
5386         struct commit *commit;
5388         if (!line) {
5389                 int i;
5391                 if (!view->lines && !view->parent)
5392                         die("No revisions match the given arguments.");
5393                 if (view->lines > 0) {
5394                         commit = view->line[view->lines - 1].data;
5395                         if (!*commit->author) {
5396                                 view->lines--;
5397                                 free(commit);
5398                                 graph->commit = NULL;
5399                         }
5400                 }
5401                 update_rev_graph(graph);
5403                 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
5404                         clear_rev_graph(&graph_stacks[i]);
5405                 return TRUE;
5406         }
5408         type = get_line_type(line);
5409         if (type == LINE_COMMIT) {
5410                 commit = calloc(1, sizeof(struct commit));
5411                 if (!commit)
5412                         return FALSE;
5414                 line += STRING_SIZE("commit ");
5415                 if (*line == '-') {
5416                         graph->boundary = 1;
5417                         line++;
5418                 }
5420                 string_copy_rev(commit->id, line);
5421                 commit->refs = get_refs(commit->id);
5422                 graph->commit = commit;
5423                 add_line_data(view, commit, LINE_MAIN_COMMIT);
5425                 while ((line = strchr(line, ' '))) {
5426                         line++;
5427                         push_rev_graph(graph->parents, line);
5428                         commit->has_parents = TRUE;
5429                 }
5430                 return TRUE;
5431         }
5433         if (!view->lines)
5434                 return TRUE;
5435         commit = view->line[view->lines - 1].data;
5437         switch (type) {
5438         case LINE_PARENT:
5439                 if (commit->has_parents)
5440                         break;
5441                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
5442                 break;
5444         case LINE_AUTHOR:
5445         {
5446                 /* Parse author lines where the name may be empty:
5447                  *      author  <email@address.tld> 1138474660 +0100
5448                  */
5449                 char *ident = line + STRING_SIZE("author ");
5450                 char *nameend = strchr(ident, '<');
5451                 char *emailend = strchr(ident, '>');
5453                 if (!nameend || !emailend)
5454                         break;
5456                 update_rev_graph(graph);
5457                 graph = graph->next;
5459                 *nameend = *emailend = 0;
5460                 ident = chomp_string(ident);
5461                 if (!*ident) {
5462                         ident = chomp_string(nameend + 1);
5463                         if (!*ident)
5464                                 ident = "Unknown";
5465                 }
5467                 string_ncopy(commit->author, ident, strlen(ident));
5469                 /* Parse epoch and timezone */
5470                 if (emailend[1] == ' ') {
5471                         char *secs = emailend + 2;
5472                         char *zone = strchr(secs, ' ');
5473                         time_t time = (time_t) atol(secs);
5475                         if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
5476                                 long tz;
5478                                 zone++;
5479                                 tz  = ('0' - zone[1]) * 60 * 60 * 10;
5480                                 tz += ('0' - zone[2]) * 60 * 60;
5481                                 tz += ('0' - zone[3]) * 60;
5482                                 tz += ('0' - zone[4]) * 60;
5484                                 if (zone[0] == '-')
5485                                         tz = -tz;
5487                                 time -= tz;
5488                         }
5490                         gmtime_r(&time, &commit->time);
5491                 }
5492                 break;
5493         }
5494         default:
5495                 /* Fill in the commit title if it has not already been set. */
5496                 if (commit->title[0])
5497                         break;
5499                 /* Require titles to start with a non-space character at the
5500                  * offset used by git log. */
5501                 if (strncmp(line, "    ", 4))
5502                         break;
5503                 line += 4;
5504                 /* Well, if the title starts with a whitespace character,
5505                  * try to be forgiving.  Otherwise we end up with no title. */
5506                 while (isspace(*line))
5507                         line++;
5508                 if (*line == '\0')
5509                         break;
5510                 /* FIXME: More graceful handling of titles; append "..." to
5511                  * shortened titles, etc. */
5513                 string_ncopy(commit->title, line, strlen(line));
5514         }
5516         return TRUE;
5519 static enum request
5520 main_request(struct view *view, enum request request, struct line *line)
5522         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
5524         switch (request) {
5525         case REQ_ENTER:
5526                 open_view(view, REQ_VIEW_DIFF, flags);
5527                 break;
5528         case REQ_REFRESH:
5529                 load_refs();
5530                 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
5531                 break;
5532         default:
5533                 return request;
5534         }
5536         return REQ_NONE;
5539 static bool
5540 grep_refs(struct ref **refs, regex_t *regex)
5542         regmatch_t pmatch;
5543         size_t i = 0;
5545         if (!refs)
5546                 return FALSE;
5547         do {
5548                 if (regexec(regex, refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
5549                         return TRUE;
5550         } while (refs[i++]->next);
5552         return FALSE;
5555 static bool
5556 main_grep(struct view *view, struct line *line)
5558         struct commit *commit = line->data;
5559         enum { S_TITLE, S_AUTHOR, S_DATE, S_REFS, S_END } state;
5560         char buf[DATE_COLS + 1];
5561         regmatch_t pmatch;
5563         for (state = S_TITLE; state < S_END; state++) {
5564                 char *text;
5566                 switch (state) {
5567                 case S_TITLE:   text = commit->title;   break;
5568                 case S_AUTHOR:
5569                         if (!opt_author)
5570                                 continue;
5571                         text = commit->author;
5572                         break;
5573                 case S_DATE:
5574                         if (!opt_date)
5575                                 continue;
5576                         if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
5577                                 continue;
5578                         text = buf;
5579                         break;
5580                 case S_REFS:
5581                         if (!opt_show_refs)
5582                                 continue;
5583                         if (grep_refs(commit->refs, view->regex) == TRUE)
5584                                 return TRUE;
5585                         continue;
5586                 default:
5587                         return FALSE;
5588                 }
5590                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
5591                         return TRUE;
5592         }
5594         return FALSE;
5597 static void
5598 main_select(struct view *view, struct line *line)
5600         struct commit *commit = line->data;
5602         string_copy_rev(view->ref, commit->id);
5603         string_copy_rev(ref_commit, view->ref);
5606 static struct view_ops main_ops = {
5607         "commit",
5608         main_argv,
5609         NULL,
5610         main_read,
5611         main_draw,
5612         main_request,
5613         main_grep,
5614         main_select,
5615 };
5618 /*
5619  * Unicode / UTF-8 handling
5620  *
5621  * NOTE: Much of the following code for dealing with unicode is derived from
5622  * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
5623  * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
5624  */
5626 /* I've (over)annotated a lot of code snippets because I am not entirely
5627  * confident that the approach taken by this small UTF-8 interface is correct.
5628  * --jonas */
5630 static inline int
5631 unicode_width(unsigned long c)
5633         if (c >= 0x1100 &&
5634            (c <= 0x115f                         /* Hangul Jamo */
5635             || c == 0x2329
5636             || c == 0x232a
5637             || (c >= 0x2e80  && c <= 0xa4cf && c != 0x303f)
5638                                                 /* CJK ... Yi */
5639             || (c >= 0xac00  && c <= 0xd7a3)    /* Hangul Syllables */
5640             || (c >= 0xf900  && c <= 0xfaff)    /* CJK Compatibility Ideographs */
5641             || (c >= 0xfe30  && c <= 0xfe6f)    /* CJK Compatibility Forms */
5642             || (c >= 0xff00  && c <= 0xff60)    /* Fullwidth Forms */
5643             || (c >= 0xffe0  && c <= 0xffe6)
5644             || (c >= 0x20000 && c <= 0x2fffd)
5645             || (c >= 0x30000 && c <= 0x3fffd)))
5646                 return 2;
5648         if (c == '\t')
5649                 return opt_tab_size;
5651         return 1;
5654 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
5655  * Illegal bytes are set one. */
5656 static const unsigned char utf8_bytes[256] = {
5657         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,
5658         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,
5659         1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
5660         1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
5661         1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
5662         1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
5663         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,
5664         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,
5665 };
5667 /* Decode UTF-8 multi-byte representation into a unicode character. */
5668 static inline unsigned long
5669 utf8_to_unicode(const char *string, size_t length)
5671         unsigned long unicode;
5673         switch (length) {
5674         case 1:
5675                 unicode  =   string[0];
5676                 break;
5677         case 2:
5678                 unicode  =  (string[0] & 0x1f) << 6;
5679                 unicode +=  (string[1] & 0x3f);
5680                 break;
5681         case 3:
5682                 unicode  =  (string[0] & 0x0f) << 12;
5683                 unicode += ((string[1] & 0x3f) << 6);
5684                 unicode +=  (string[2] & 0x3f);
5685                 break;
5686         case 4:
5687                 unicode  =  (string[0] & 0x0f) << 18;
5688                 unicode += ((string[1] & 0x3f) << 12);
5689                 unicode += ((string[2] & 0x3f) << 6);
5690                 unicode +=  (string[3] & 0x3f);
5691                 break;
5692         case 5:
5693                 unicode  =  (string[0] & 0x0f) << 24;
5694                 unicode += ((string[1] & 0x3f) << 18);
5695                 unicode += ((string[2] & 0x3f) << 12);
5696                 unicode += ((string[3] & 0x3f) << 6);
5697                 unicode +=  (string[4] & 0x3f);
5698                 break;
5699         case 6:
5700                 unicode  =  (string[0] & 0x01) << 30;
5701                 unicode += ((string[1] & 0x3f) << 24);
5702                 unicode += ((string[2] & 0x3f) << 18);
5703                 unicode += ((string[3] & 0x3f) << 12);
5704                 unicode += ((string[4] & 0x3f) << 6);
5705                 unicode +=  (string[5] & 0x3f);
5706                 break;
5707         default:
5708                 die("Invalid unicode length");
5709         }
5711         /* Invalid characters could return the special 0xfffd value but NUL
5712          * should be just as good. */
5713         return unicode > 0xffff ? 0 : unicode;
5716 /* Calculates how much of string can be shown within the given maximum width
5717  * and sets trimmed parameter to non-zero value if all of string could not be
5718  * shown. If the reserve flag is TRUE, it will reserve at least one
5719  * trailing character, which can be useful when drawing a delimiter.
5720  *
5721  * Returns the number of bytes to output from string to satisfy max_width. */
5722 static size_t
5723 utf8_length(const char *string, int *width, size_t max_width, int *trimmed, bool reserve)
5725         const char *start = string;
5726         const char *end = strchr(string, '\0');
5727         unsigned char last_bytes = 0;
5728         size_t last_ucwidth = 0;
5730         *width = 0;
5731         *trimmed = 0;
5733         while (string < end) {
5734                 int c = *(unsigned char *) string;
5735                 unsigned char bytes = utf8_bytes[c];
5736                 size_t ucwidth;
5737                 unsigned long unicode;
5739                 if (string + bytes > end)
5740                         break;
5742                 /* Change representation to figure out whether
5743                  * it is a single- or double-width character. */
5745                 unicode = utf8_to_unicode(string, bytes);
5746                 /* FIXME: Graceful handling of invalid unicode character. */
5747                 if (!unicode)
5748                         break;
5750                 ucwidth = unicode_width(unicode);
5751                 *width  += ucwidth;
5752                 if (*width > max_width) {
5753                         *trimmed = 1;
5754                         *width -= ucwidth;
5755                         if (reserve && *width == max_width) {
5756                                 string -= last_bytes;
5757                                 *width -= last_ucwidth;
5758                         }
5759                         break;
5760                 }
5762                 string  += bytes;
5763                 last_bytes = bytes;
5764                 last_ucwidth = ucwidth;
5765         }
5767         return string - start;
5771 /*
5772  * Status management
5773  */
5775 /* Whether or not the curses interface has been initialized. */
5776 static bool cursed = FALSE;
5778 /* The status window is used for polling keystrokes. */
5779 static WINDOW *status_win;
5781 static bool status_empty = TRUE;
5783 /* Update status and title window. */
5784 static void
5785 report(const char *msg, ...)
5787         struct view *view = display[current_view];
5789         if (input_mode)
5790                 return;
5792         if (!view) {
5793                 char buf[SIZEOF_STR];
5794                 va_list args;
5796                 va_start(args, msg);
5797                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
5798                         buf[sizeof(buf) - 1] = 0;
5799                         buf[sizeof(buf) - 2] = '.';
5800                         buf[sizeof(buf) - 3] = '.';
5801                         buf[sizeof(buf) - 4] = '.';
5802                 }
5803                 va_end(args);
5804                 die("%s", buf);
5805         }
5807         if (!status_empty || *msg) {
5808                 va_list args;
5810                 va_start(args, msg);
5812                 wmove(status_win, 0, 0);
5813                 if (*msg) {
5814                         vwprintw(status_win, msg, args);
5815                         status_empty = FALSE;
5816                 } else {
5817                         status_empty = TRUE;
5818                 }
5819                 wclrtoeol(status_win);
5820                 wrefresh(status_win);
5822                 va_end(args);
5823         }
5825         update_view_title(view);
5826         update_display_cursor(view);
5829 /* Controls when nodelay should be in effect when polling user input. */
5830 static void
5831 set_nonblocking_input(bool loading)
5833         static unsigned int loading_views;
5835         if ((loading == FALSE && loading_views-- == 1) ||
5836             (loading == TRUE  && loading_views++ == 0))
5837                 nodelay(status_win, loading);
5840 static void
5841 init_display(void)
5843         int x, y;
5845         /* Initialize the curses library */
5846         if (isatty(STDIN_FILENO)) {
5847                 cursed = !!initscr();
5848                 opt_tty = stdin;
5849         } else {
5850                 /* Leave stdin and stdout alone when acting as a pager. */
5851                 opt_tty = fopen("/dev/tty", "r+");
5852                 if (!opt_tty)
5853                         die("Failed to open /dev/tty");
5854                 cursed = !!newterm(NULL, opt_tty, opt_tty);
5855         }
5857         if (!cursed)
5858                 die("Failed to initialize curses");
5860         nonl();         /* Tell curses not to do NL->CR/NL on output */
5861         cbreak();       /* Take input chars one at a time, no wait for \n */
5862         noecho();       /* Don't echo input */
5863         leaveok(stdscr, TRUE);
5865         if (has_colors())
5866                 init_colors();
5868         getmaxyx(stdscr, y, x);
5869         status_win = newwin(1, 0, y - 1, 0);
5870         if (!status_win)
5871                 die("Failed to create status window");
5873         /* Enable keyboard mapping */
5874         keypad(status_win, TRUE);
5875         wbkgdset(status_win, get_line_attr(LINE_STATUS));
5877         TABSIZE = opt_tab_size;
5878         if (opt_line_graphics) {
5879                 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
5880         }
5883 static bool
5884 prompt_yesno(const char *prompt)
5886         enum { WAIT, STOP, CANCEL  } status = WAIT;
5887         bool answer = FALSE;
5889         while (status == WAIT) {
5890                 struct view *view;
5891                 int i, key;
5893                 input_mode = TRUE;
5895                 foreach_view (view, i)
5896                         update_view(view);
5898                 input_mode = FALSE;
5900                 mvwprintw(status_win, 0, 0, "%s [Yy]/[Nn]", prompt);
5901                 wclrtoeol(status_win);
5903                 /* Refresh, accept single keystroke of input */
5904                 key = wgetch(status_win);
5905                 switch (key) {
5906                 case ERR:
5907                         break;
5909                 case 'y':
5910                 case 'Y':
5911                         answer = TRUE;
5912                         status = STOP;
5913                         break;
5915                 case KEY_ESC:
5916                 case KEY_RETURN:
5917                 case KEY_ENTER:
5918                 case KEY_BACKSPACE:
5919                 case 'n':
5920                 case 'N':
5921                 case '\n':
5922                 default:
5923                         answer = FALSE;
5924                         status = CANCEL;
5925                 }
5926         }
5928         /* Clear the status window */
5929         status_empty = FALSE;
5930         report("");
5932         return answer;
5935 static char *
5936 read_prompt(const char *prompt)
5938         enum { READING, STOP, CANCEL } status = READING;
5939         static char buf[SIZEOF_STR];
5940         int pos = 0;
5942         while (status == READING) {
5943                 struct view *view;
5944                 int i, key;
5946                 input_mode = TRUE;
5948                 foreach_view (view, i)
5949                         update_view(view);
5951                 input_mode = FALSE;
5953                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
5954                 wclrtoeol(status_win);
5956                 /* Refresh, accept single keystroke of input */
5957                 key = wgetch(status_win);
5958                 switch (key) {
5959                 case KEY_RETURN:
5960                 case KEY_ENTER:
5961                 case '\n':
5962                         status = pos ? STOP : CANCEL;
5963                         break;
5965                 case KEY_BACKSPACE:
5966                         if (pos > 0)
5967                                 pos--;
5968                         else
5969                                 status = CANCEL;
5970                         break;
5972                 case KEY_ESC:
5973                         status = CANCEL;
5974                         break;
5976                 case ERR:
5977                         break;
5979                 default:
5980                         if (pos >= sizeof(buf)) {
5981                                 report("Input string too long");
5982                                 return NULL;
5983                         }
5985                         if (isprint(key))
5986                                 buf[pos++] = (char) key;
5987                 }
5988         }
5990         /* Clear the status window */
5991         status_empty = FALSE;
5992         report("");
5994         if (status == CANCEL)
5995                 return NULL;
5997         buf[pos++] = 0;
5999         return buf;
6002 /*
6003  * Repository references
6004  */
6006 static struct ref *refs = NULL;
6007 static size_t refs_alloc = 0;
6008 static size_t refs_size = 0;
6010 /* Id <-> ref store */
6011 static struct ref ***id_refs = NULL;
6012 static size_t id_refs_alloc = 0;
6013 static size_t id_refs_size = 0;
6015 static int
6016 compare_refs(const void *ref1_, const void *ref2_)
6018         const struct ref *ref1 = *(const struct ref **)ref1_;
6019         const struct ref *ref2 = *(const struct ref **)ref2_;
6021         if (ref1->tag != ref2->tag)
6022                 return ref2->tag - ref1->tag;
6023         if (ref1->ltag != ref2->ltag)
6024                 return ref2->ltag - ref2->ltag;
6025         if (ref1->head != ref2->head)
6026                 return ref2->head - ref1->head;
6027         if (ref1->tracked != ref2->tracked)
6028                 return ref2->tracked - ref1->tracked;
6029         if (ref1->remote != ref2->remote)
6030                 return ref2->remote - ref1->remote;
6031         return strcmp(ref1->name, ref2->name);
6034 static struct ref **
6035 get_refs(const char *id)
6037         struct ref ***tmp_id_refs;
6038         struct ref **ref_list = NULL;
6039         size_t ref_list_alloc = 0;
6040         size_t ref_list_size = 0;
6041         size_t i;
6043         for (i = 0; i < id_refs_size; i++)
6044                 if (!strcmp(id, id_refs[i][0]->id))
6045                         return id_refs[i];
6047         tmp_id_refs = realloc_items(id_refs, &id_refs_alloc, id_refs_size + 1,
6048                                     sizeof(*id_refs));
6049         if (!tmp_id_refs)
6050                 return NULL;
6052         id_refs = tmp_id_refs;
6054         for (i = 0; i < refs_size; i++) {
6055                 struct ref **tmp;
6057                 if (strcmp(id, refs[i].id))
6058                         continue;
6060                 tmp = realloc_items(ref_list, &ref_list_alloc,
6061                                     ref_list_size + 1, sizeof(*ref_list));
6062                 if (!tmp) {
6063                         if (ref_list)
6064                                 free(ref_list);
6065                         return NULL;
6066                 }
6068                 ref_list = tmp;
6069                 ref_list[ref_list_size] = &refs[i];
6070                 /* XXX: The properties of the commit chains ensures that we can
6071                  * safely modify the shared ref. The repo references will
6072                  * always be similar for the same id. */
6073                 ref_list[ref_list_size]->next = 1;
6075                 ref_list_size++;
6076         }
6078         if (ref_list) {
6079                 qsort(ref_list, ref_list_size, sizeof(*ref_list), compare_refs);
6080                 ref_list[ref_list_size - 1]->next = 0;
6081                 id_refs[id_refs_size++] = ref_list;
6082         }
6084         return ref_list;
6087 static int
6088 read_ref(char *id, size_t idlen, char *name, size_t namelen)
6090         struct ref *ref;
6091         bool tag = FALSE;
6092         bool ltag = FALSE;
6093         bool remote = FALSE;
6094         bool tracked = FALSE;
6095         bool check_replace = FALSE;
6096         bool head = FALSE;
6098         if (!prefixcmp(name, "refs/tags/")) {
6099                 if (!suffixcmp(name, namelen, "^{}")) {
6100                         namelen -= 3;
6101                         name[namelen] = 0;
6102                         if (refs_size > 0 && refs[refs_size - 1].ltag == TRUE)
6103                                 check_replace = TRUE;
6104                 } else {
6105                         ltag = TRUE;
6106                 }
6108                 tag = TRUE;
6109                 namelen -= STRING_SIZE("refs/tags/");
6110                 name    += STRING_SIZE("refs/tags/");
6112         } else if (!prefixcmp(name, "refs/remotes/")) {
6113                 remote = TRUE;
6114                 namelen -= STRING_SIZE("refs/remotes/");
6115                 name    += STRING_SIZE("refs/remotes/");
6116                 tracked  = !strcmp(opt_remote, name);
6118         } else if (!prefixcmp(name, "refs/heads/")) {
6119                 namelen -= STRING_SIZE("refs/heads/");
6120                 name    += STRING_SIZE("refs/heads/");
6121                 head     = !strncmp(opt_head, name, namelen);
6123         } else if (!strcmp(name, "HEAD")) {
6124                 string_ncopy(opt_head_rev, id, idlen);
6125                 return OK;
6126         }
6128         if (check_replace && !strcmp(name, refs[refs_size - 1].name)) {
6129                 /* it's an annotated tag, replace the previous sha1 with the
6130                  * resolved commit id; relies on the fact git-ls-remote lists
6131                  * the commit id of an annotated tag right before the commit id
6132                  * it points to. */
6133                 refs[refs_size - 1].ltag = ltag;
6134                 string_copy_rev(refs[refs_size - 1].id, id);
6136                 return OK;
6137         }
6138         refs = realloc_items(refs, &refs_alloc, refs_size + 1, sizeof(*refs));
6139         if (!refs)
6140                 return ERR;
6142         ref = &refs[refs_size++];
6143         ref->name = malloc(namelen + 1);
6144         if (!ref->name)
6145                 return ERR;
6147         strncpy(ref->name, name, namelen);
6148         ref->name[namelen] = 0;
6149         ref->head = head;
6150         ref->tag = tag;
6151         ref->ltag = ltag;
6152         ref->remote = remote;
6153         ref->tracked = tracked;
6154         string_copy_rev(ref->id, id);
6156         return OK;
6159 static int
6160 load_refs(void)
6162         const char *cmd_env = getenv("TIG_LS_REMOTE");
6163         const char *cmd = cmd_env && *cmd_env ? cmd_env : TIG_LS_REMOTE;
6165         if (!*opt_git_dir)
6166                 return OK;
6168         while (refs_size > 0)
6169                 free(refs[--refs_size].name);
6170         while (id_refs_size > 0)
6171                 free(id_refs[--id_refs_size]);
6173         return read_properties(popen(cmd, "r"), "\t", read_ref);
6176 static int
6177 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
6179         if (!strcmp(name, "i18n.commitencoding"))
6180                 string_ncopy(opt_encoding, value, valuelen);
6182         if (!strcmp(name, "core.editor"))
6183                 string_ncopy(opt_editor, value, valuelen);
6185         /* branch.<head>.remote */
6186         if (*opt_head &&
6187             !strncmp(name, "branch.", 7) &&
6188             !strncmp(name + 7, opt_head, strlen(opt_head)) &&
6189             !strcmp(name + 7 + strlen(opt_head), ".remote"))
6190                 string_ncopy(opt_remote, value, valuelen);
6192         if (*opt_head && *opt_remote &&
6193             !strncmp(name, "branch.", 7) &&
6194             !strncmp(name + 7, opt_head, strlen(opt_head)) &&
6195             !strcmp(name + 7 + strlen(opt_head), ".merge")) {
6196                 size_t from = strlen(opt_remote);
6198                 if (!prefixcmp(value, "refs/heads/")) {
6199                         value += STRING_SIZE("refs/heads/");
6200                         valuelen -= STRING_SIZE("refs/heads/");
6201                 }
6203                 if (!string_format_from(opt_remote, &from, "/%s", value))
6204                         opt_remote[0] = 0;
6205         }
6207         return OK;
6210 static int
6211 load_git_config(void)
6213         return read_properties(popen("git " GIT_CONFIG " --list", "r"),
6214                                "=", read_repo_config_option);
6217 static int
6218 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
6220         if (!opt_git_dir[0]) {
6221                 string_ncopy(opt_git_dir, name, namelen);
6223         } else if (opt_is_inside_work_tree == -1) {
6224                 /* This can be 3 different values depending on the
6225                  * version of git being used. If git-rev-parse does not
6226                  * understand --is-inside-work-tree it will simply echo
6227                  * the option else either "true" or "false" is printed.
6228                  * Default to true for the unknown case. */
6229                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
6231         } else if (opt_cdup[0] == ' ') {
6232                 string_ncopy(opt_cdup, name, namelen);
6233         } else {
6234                 if (!prefixcmp(name, "refs/heads/")) {
6235                         namelen -= STRING_SIZE("refs/heads/");
6236                         name    += STRING_SIZE("refs/heads/");
6237                         string_ncopy(opt_head, name, namelen);
6238                 }
6239         }
6241         return OK;
6244 static int
6245 load_repo_info(void)
6247         int result;
6248         FILE *pipe = popen("(git rev-parse --git-dir --is-inside-work-tree "
6249                            " --show-cdup; git symbolic-ref HEAD) 2>/dev/null", "r");
6251         /* XXX: The line outputted by "--show-cdup" can be empty so
6252          * initialize it to something invalid to make it possible to
6253          * detect whether it has been set or not. */
6254         opt_cdup[0] = ' ';
6256         result = read_properties(pipe, "=", read_repo_info);
6257         if (opt_cdup[0] == ' ')
6258                 opt_cdup[0] = 0;
6260         return result;
6263 static int
6264 read_properties(FILE *pipe, const char *separators,
6265                 int (*read_property)(char *, size_t, char *, size_t))
6267         char buffer[BUFSIZ];
6268         char *name;
6269         int state = OK;
6271         if (!pipe)
6272                 return ERR;
6274         while (state == OK && (name = fgets(buffer, sizeof(buffer), pipe))) {
6275                 char *value;
6276                 size_t namelen;
6277                 size_t valuelen;
6279                 name = chomp_string(name);
6280                 namelen = strcspn(name, separators);
6282                 if (name[namelen]) {
6283                         name[namelen] = 0;
6284                         value = chomp_string(name + namelen + 1);
6285                         valuelen = strlen(value);
6287                 } else {
6288                         value = "";
6289                         valuelen = 0;
6290                 }
6292                 state = read_property(name, namelen, value, valuelen);
6293         }
6295         if (state != ERR && ferror(pipe))
6296                 state = ERR;
6298         pclose(pipe);
6300         return state;
6304 /*
6305  * Main
6306  */
6308 static void __NORETURN
6309 quit(int sig)
6311         /* XXX: Restore tty modes and let the OS cleanup the rest! */
6312         if (cursed)
6313                 endwin();
6314         exit(0);
6317 static void __NORETURN
6318 die(const char *err, ...)
6320         va_list args;
6322         endwin();
6324         va_start(args, err);
6325         fputs("tig: ", stderr);
6326         vfprintf(stderr, err, args);
6327         fputs("\n", stderr);
6328         va_end(args);
6330         exit(1);
6333 static void
6334 warn(const char *msg, ...)
6336         va_list args;
6338         va_start(args, msg);
6339         fputs("tig warning: ", stderr);
6340         vfprintf(stderr, msg, args);
6341         fputs("\n", stderr);
6342         va_end(args);
6345 int
6346 main(int argc, const char *argv[])
6348         struct view *view;
6349         enum request request;
6350         size_t i;
6352         signal(SIGINT, quit);
6354         if (setlocale(LC_ALL, "")) {
6355                 char *codeset = nl_langinfo(CODESET);
6357                 string_ncopy(opt_codeset, codeset, strlen(codeset));
6358         }
6360         if (load_repo_info() == ERR)
6361                 die("Failed to load repo info.");
6363         if (load_options() == ERR)
6364                 die("Failed to load user config.");
6366         if (load_git_config() == ERR)
6367                 die("Failed to load repo config.");
6369         request = parse_options(argc, argv);
6370         if (request == REQ_NONE)
6371                 return 0;
6373         /* Require a git repository unless when running in pager mode. */
6374         if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
6375                 die("Not a git repository");
6377         if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
6378                 opt_utf8 = FALSE;
6380         if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
6381                 opt_iconv = iconv_open(opt_codeset, opt_encoding);
6382                 if (opt_iconv == ICONV_NONE)
6383                         die("Failed to initialize character set conversion");
6384         }
6386         if (load_refs() == ERR)
6387                 die("Failed to load refs.");
6389         foreach_view (view, i)
6390                 argv_from_env(view->ops->argv, view->cmd_env);
6392         init_display();
6394         while (view_driver(display[current_view], request)) {
6395                 int key;
6396                 int i;
6398                 foreach_view (view, i)
6399                         update_view(view);
6400                 view = display[current_view];
6402                 /* Refresh, accept single keystroke of input */
6403                 key = wgetch(status_win);
6405                 /* wgetch() with nodelay() enabled returns ERR when there's no
6406                  * input. */
6407                 if (key == ERR) {
6408                         request = REQ_NONE;
6409                         continue;
6410                 }
6412                 request = get_keybinding(view->keymap, key);
6414                 /* Some low-level request handling. This keeps access to
6415                  * status_win restricted. */
6416                 switch (request) {
6417                 case REQ_PROMPT:
6418                 {
6419                         char *cmd = read_prompt(":");
6421                         if (cmd) {
6422                                 struct view *next = VIEW(REQ_VIEW_PAGER);
6423                                 const char *argv[SIZEOF_ARG] = { "git" };
6424                                 int argc = 1;
6426                                 /* When running random commands, initially show the
6427                                  * command in the title. However, it maybe later be
6428                                  * overwritten if a commit line is selected. */
6429                                 string_ncopy(next->ref, cmd, strlen(cmd));
6431                                 if (!argv_from_string(argv, &argc, cmd)) {
6432                                         report("Too many arguments");
6433                                 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
6434                                         report("Failed to format command");
6435                                 } else {
6436                                         open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
6437                                 }
6438                         }
6440                         request = REQ_NONE;
6441                         break;
6442                 }
6443                 case REQ_SEARCH:
6444                 case REQ_SEARCH_BACK:
6445                 {
6446                         const char *prompt = request == REQ_SEARCH ? "/" : "?";
6447                         char *search = read_prompt(prompt);
6449                         if (search)
6450                                 string_ncopy(opt_search, search, strlen(search));
6451                         else
6452                                 request = REQ_NONE;
6453                         break;
6454                 }
6455                 case REQ_SCREEN_RESIZE:
6456                 {
6457                         int height, width;
6459                         getmaxyx(stdscr, height, width);
6461                         /* Resize the status view and let the view driver take
6462                          * care of resizing the displayed views. */
6463                         wresize(status_win, 1, width);
6464                         mvwin(status_win, height - 1, 0);
6465                         wrefresh(status_win);
6466                         break;
6467                 }
6468                 default:
6469                         break;
6470                 }
6471         }
6473         quit(0);
6475         return 0;