Code

IO API: unify tree view and the default path in begin_update
[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 {
2549                 if (view == VIEW(REQ_VIEW_TREE) && 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                 /* Put the current ref_* value to the view title ref
2556                  * member. This is needed by the blob view. Most other
2557                  * views sets it automatically after loading because the
2558                  * first line is a commit line. */
2559                 string_copy_rev(view->ref, view->id);
2560         }
2562         setup_update(view, view->id);
2564         return TRUE;
2567 #define ITEM_CHUNK_SIZE 256
2568 static void *
2569 realloc_items(void *mem, size_t *size, size_t new_size, size_t item_size)
2571         size_t num_chunks = *size / ITEM_CHUNK_SIZE;
2572         size_t num_chunks_new = (new_size + ITEM_CHUNK_SIZE - 1) / ITEM_CHUNK_SIZE;
2574         if (mem == NULL || num_chunks != num_chunks_new) {
2575                 *size = num_chunks_new * ITEM_CHUNK_SIZE;
2576                 mem = realloc(mem, *size * item_size);
2577         }
2579         return mem;
2582 static struct line *
2583 realloc_lines(struct view *view, size_t line_size)
2585         size_t alloc = view->line_alloc;
2586         struct line *tmp = realloc_items(view->line, &alloc, line_size,
2587                                          sizeof(*view->line));
2589         if (!tmp)
2590                 return NULL;
2592         view->line = tmp;
2593         view->line_alloc = alloc;
2594         view->line_size = line_size;
2595         return view->line;
2598 static bool
2599 update_view(struct view *view)
2601         char out_buffer[BUFSIZ * 2];
2602         char *line;
2603         /* The number of lines to read. If too low it will cause too much
2604          * redrawing (and possible flickering), if too high responsiveness
2605          * will suffer. */
2606         unsigned long lines = view->height;
2607         int redraw_from = -1;
2609         if (!view->pipe)
2610                 return TRUE;
2612         /* Only redraw if lines are visible. */
2613         if (view->offset + view->height >= view->lines)
2614                 redraw_from = view->lines - view->offset;
2616         /* FIXME: This is probably not perfect for backgrounded views. */
2617         if (!realloc_lines(view, view->lines + lines))
2618                 goto alloc_error;
2620         while ((line = io_gets(view->pipe))) {
2621                 size_t linelen = strlen(line);
2623                 if (linelen)
2624                         line[linelen - 1] = 0;
2626                 if (opt_iconv != ICONV_NONE) {
2627                         ICONV_CONST char *inbuf = line;
2628                         size_t inlen = linelen;
2630                         char *outbuf = out_buffer;
2631                         size_t outlen = sizeof(out_buffer);
2633                         size_t ret;
2635                         ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2636                         if (ret != (size_t) -1) {
2637                                 line = out_buffer;
2638                                 linelen = strlen(out_buffer);
2639                         }
2640                 }
2642                 if (!view->ops->read(view, line))
2643                         goto alloc_error;
2645                 if (lines-- == 1)
2646                         break;
2647         }
2649         {
2650                 int digits;
2652                 lines = view->lines;
2653                 for (digits = 0; lines; digits++)
2654                         lines /= 10;
2656                 /* Keep the displayed view in sync with line number scaling. */
2657                 if (digits != view->digits) {
2658                         view->digits = digits;
2659                         redraw_from = 0;
2660                 }
2661         }
2663         if (io_error(view->pipe)) {
2664                 report("Failed to read: %s", io_strerror(view->pipe));
2665                 end_update(view, TRUE);
2667         } else if (io_eof(view->pipe)) {
2668                 report("");
2669                 end_update(view, FALSE);
2670         }
2672         if (!view_is_displayed(view))
2673                 return TRUE;
2675         if (view == VIEW(REQ_VIEW_TREE)) {
2676                 /* Clear the view and redraw everything since the tree sorting
2677                  * might have rearranged things. */
2678                 redraw_view(view);
2680         } else if (redraw_from >= 0) {
2681                 /* If this is an incremental update, redraw the previous line
2682                  * since for commits some members could have changed when
2683                  * loading the main view. */
2684                 if (redraw_from > 0)
2685                         redraw_from--;
2687                 /* Since revision graph visualization requires knowledge
2688                  * about the parent commit, it causes a further one-off
2689                  * needed to be redrawn for incremental updates. */
2690                 if (redraw_from > 0 && opt_rev_graph)
2691                         redraw_from--;
2693                 /* Incrementally draw avoids flickering. */
2694                 redraw_view_from(view, redraw_from);
2695         }
2697         if (view == VIEW(REQ_VIEW_BLAME))
2698                 redraw_view_dirty(view);
2700         /* Update the title _after_ the redraw so that if the redraw picks up a
2701          * commit reference in view->ref it'll be available here. */
2702         update_view_title(view);
2703         return TRUE;
2705 alloc_error:
2706         report("Allocation failure");
2707         end_update(view, TRUE);
2708         return FALSE;
2711 static struct line *
2712 add_line_data(struct view *view, void *data, enum line_type type)
2714         struct line *line = &view->line[view->lines++];
2716         memset(line, 0, sizeof(*line));
2717         line->type = type;
2718         line->data = data;
2720         return line;
2723 static struct line *
2724 add_line_text(struct view *view, const char *text, enum line_type type)
2726         char *data = text ? strdup(text) : NULL;
2728         return data ? add_line_data(view, data, type) : NULL;
2732 /*
2733  * View opening
2734  */
2736 enum open_flags {
2737         OPEN_DEFAULT = 0,       /* Use default view switching. */
2738         OPEN_SPLIT = 1,         /* Split current view. */
2739         OPEN_BACKGROUNDED = 2,  /* Backgrounded. */
2740         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
2741         OPEN_NOMAXIMIZE = 8,    /* Do not maximize the current view. */
2742         OPEN_REFRESH = 16,      /* Refresh view using previous command. */
2743         OPEN_PREPARED = 32,     /* Open already prepared command. */
2744 };
2746 static void
2747 open_view(struct view *prev, enum request request, enum open_flags flags)
2749         bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
2750         bool split = !!(flags & OPEN_SPLIT);
2751         bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
2752         bool nomaximize = !!(flags & (OPEN_NOMAXIMIZE | OPEN_REFRESH));
2753         struct view *view = VIEW(request);
2754         int nviews = displayed_views();
2755         struct view *base_view = display[0];
2757         if (view == prev && nviews == 1 && !reload) {
2758                 report("Already in %s view", view->name);
2759                 return;
2760         }
2762         if (view->git_dir && !opt_git_dir[0]) {
2763                 report("The %s view is disabled in pager view", view->name);
2764                 return;
2765         }
2767         if (split) {
2768                 display[1] = view;
2769                 if (!backgrounded)
2770                         current_view = 1;
2771         } else if (!nomaximize) {
2772                 /* Maximize the current view. */
2773                 memset(display, 0, sizeof(display));
2774                 current_view = 0;
2775                 display[current_view] = view;
2776         }
2778         /* Resize the view when switching between split- and full-screen,
2779          * or when switching between two different full-screen views. */
2780         if (nviews != displayed_views() ||
2781             (nviews == 1 && base_view != display[0]))
2782                 resize_display();
2784         if (view->pipe)
2785                 end_update(view, TRUE);
2787         if (view->ops->open) {
2788                 if (!view->ops->open(view)) {
2789                         report("Failed to load %s view", view->name);
2790                         return;
2791                 }
2793         } else if ((reload || strcmp(view->vid, view->id)) &&
2794                    !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
2795                 report("Failed to load %s view", view->name);
2796                 return;
2797         }
2799         if (split && prev->lineno - prev->offset >= prev->height) {
2800                 /* Take the title line into account. */
2801                 int lines = prev->lineno - prev->offset - prev->height + 1;
2803                 /* Scroll the view that was split if the current line is
2804                  * outside the new limited view. */
2805                 do_scroll_view(prev, lines);
2806         }
2808         if (prev && view != prev) {
2809                 if (split && !backgrounded) {
2810                         /* "Blur" the previous view. */
2811                         update_view_title(prev);
2812                 }
2814                 view->parent = prev;
2815         }
2817         if (view->pipe && view->lines == 0) {
2818                 /* Clear the old view and let the incremental updating refill
2819                  * the screen. */
2820                 werase(view->win);
2821                 report("");
2822         } else if (view_is_displayed(view)) {
2823                 redraw_view(view);
2824                 report("");
2825         }
2827         /* If the view is backgrounded the above calls to report()
2828          * won't redraw the view title. */
2829         if (backgrounded)
2830                 update_view_title(view);
2833 static void
2834 open_external_viewer(const char *argv[], const char *dir)
2836         def_prog_mode();           /* save current tty modes */
2837         endwin();                  /* restore original tty modes */
2838         run_io_fg(argv, dir);
2839         fprintf(stderr, "Press Enter to continue");
2840         getc(opt_tty);
2841         reset_prog_mode();
2842         redraw_display();
2845 static void
2846 open_mergetool(const char *file)
2848         const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
2850         open_external_viewer(mergetool_argv, NULL);
2853 static void
2854 open_editor(bool from_root, const char *file)
2856         const char *editor_argv[] = { "vi", file, NULL };
2857         const char *editor;
2859         editor = getenv("GIT_EDITOR");
2860         if (!editor && *opt_editor)
2861                 editor = opt_editor;
2862         if (!editor)
2863                 editor = getenv("VISUAL");
2864         if (!editor)
2865                 editor = getenv("EDITOR");
2866         if (!editor)
2867                 editor = "vi";
2869         editor_argv[0] = editor;
2870         open_external_viewer(editor_argv, from_root ? opt_cdup : NULL);
2873 static void
2874 open_run_request(enum request request)
2876         struct run_request *req = get_run_request(request);
2877         const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
2879         if (!req) {
2880                 report("Unknown run request");
2881                 return;
2882         }
2884         if (format_argv(argv, req->argv, FORMAT_ALL))
2885                 open_external_viewer(argv, NULL);
2886         free_argv(argv);
2889 /*
2890  * User request switch noodle
2891  */
2893 static int
2894 view_driver(struct view *view, enum request request)
2896         int i;
2898         if (request == REQ_NONE) {
2899                 doupdate();
2900                 return TRUE;
2901         }
2903         if (request > REQ_NONE) {
2904                 open_run_request(request);
2905                 /* FIXME: When all views can refresh always do this. */
2906                 if (view == VIEW(REQ_VIEW_STATUS) ||
2907                     view == VIEW(REQ_VIEW_MAIN) ||
2908                     view == VIEW(REQ_VIEW_LOG) ||
2909                     view == VIEW(REQ_VIEW_STAGE))
2910                         request = REQ_REFRESH;
2911                 else
2912                         return TRUE;
2913         }
2915         if (view && view->lines) {
2916                 request = view->ops->request(view, request, &view->line[view->lineno]);
2917                 if (request == REQ_NONE)
2918                         return TRUE;
2919         }
2921         switch (request) {
2922         case REQ_MOVE_UP:
2923         case REQ_MOVE_DOWN:
2924         case REQ_MOVE_PAGE_UP:
2925         case REQ_MOVE_PAGE_DOWN:
2926         case REQ_MOVE_FIRST_LINE:
2927         case REQ_MOVE_LAST_LINE:
2928                 move_view(view, request);
2929                 break;
2931         case REQ_SCROLL_LINE_DOWN:
2932         case REQ_SCROLL_LINE_UP:
2933         case REQ_SCROLL_PAGE_DOWN:
2934         case REQ_SCROLL_PAGE_UP:
2935                 scroll_view(view, request);
2936                 break;
2938         case REQ_VIEW_BLAME:
2939                 if (!opt_file[0]) {
2940                         report("No file chosen, press %s to open tree view",
2941                                get_key(REQ_VIEW_TREE));
2942                         break;
2943                 }
2944                 open_view(view, request, OPEN_DEFAULT);
2945                 break;
2947         case REQ_VIEW_BLOB:
2948                 if (!ref_blob[0]) {
2949                         report("No file chosen, press %s to open tree view",
2950                                get_key(REQ_VIEW_TREE));
2951                         break;
2952                 }
2953                 open_view(view, request, OPEN_DEFAULT);
2954                 break;
2956         case REQ_VIEW_PAGER:
2957                 if (!opt_pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
2958                         report("No pager content, press %s to run command from prompt",
2959                                get_key(REQ_PROMPT));
2960                         break;
2961                 }
2962                 open_view(view, request, OPEN_DEFAULT);
2963                 break;
2965         case REQ_VIEW_STAGE:
2966                 if (!VIEW(REQ_VIEW_STAGE)->lines) {
2967                         report("No stage content, press %s to open the status view and choose file",
2968                                get_key(REQ_VIEW_STATUS));
2969                         break;
2970                 }
2971                 open_view(view, request, OPEN_DEFAULT);
2972                 break;
2974         case REQ_VIEW_STATUS:
2975                 if (opt_is_inside_work_tree == FALSE) {
2976                         report("The status view requires a working tree");
2977                         break;
2978                 }
2979                 open_view(view, request, OPEN_DEFAULT);
2980                 break;
2982         case REQ_VIEW_MAIN:
2983         case REQ_VIEW_DIFF:
2984         case REQ_VIEW_LOG:
2985         case REQ_VIEW_TREE:
2986         case REQ_VIEW_HELP:
2987                 open_view(view, request, OPEN_DEFAULT);
2988                 break;
2990         case REQ_NEXT:
2991         case REQ_PREVIOUS:
2992                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
2994                 if ((view == VIEW(REQ_VIEW_DIFF) &&
2995                      view->parent == VIEW(REQ_VIEW_MAIN)) ||
2996                    (view == VIEW(REQ_VIEW_DIFF) &&
2997                      view->parent == VIEW(REQ_VIEW_BLAME)) ||
2998                    (view == VIEW(REQ_VIEW_STAGE) &&
2999                      view->parent == VIEW(REQ_VIEW_STATUS)) ||
3000                    (view == VIEW(REQ_VIEW_BLOB) &&
3001                      view->parent == VIEW(REQ_VIEW_TREE))) {
3002                         int line;
3004                         view = view->parent;
3005                         line = view->lineno;
3006                         move_view(view, request);
3007                         if (view_is_displayed(view))
3008                                 update_view_title(view);
3009                         if (line != view->lineno)
3010                                 view->ops->request(view, REQ_ENTER,
3011                                                    &view->line[view->lineno]);
3013                 } else {
3014                         move_view(view, request);
3015                 }
3016                 break;
3018         case REQ_VIEW_NEXT:
3019         {
3020                 int nviews = displayed_views();
3021                 int next_view = (current_view + 1) % nviews;
3023                 if (next_view == current_view) {
3024                         report("Only one view is displayed");
3025                         break;
3026                 }
3028                 current_view = next_view;
3029                 /* Blur out the title of the previous view. */
3030                 update_view_title(view);
3031                 report("");
3032                 break;
3033         }
3034         case REQ_REFRESH:
3035                 report("Refreshing is not yet supported for the %s view", view->name);
3036                 break;
3038         case REQ_MAXIMIZE:
3039                 if (displayed_views() == 2)
3040                         open_view(view, VIEW_REQ(view), OPEN_DEFAULT);
3041                 break;
3043         case REQ_TOGGLE_LINENO:
3044                 opt_line_number = !opt_line_number;
3045                 redraw_display();
3046                 break;
3048         case REQ_TOGGLE_DATE:
3049                 opt_date = !opt_date;
3050                 redraw_display();
3051                 break;
3053         case REQ_TOGGLE_AUTHOR:
3054                 opt_author = !opt_author;
3055                 redraw_display();
3056                 break;
3058         case REQ_TOGGLE_REV_GRAPH:
3059                 opt_rev_graph = !opt_rev_graph;
3060                 redraw_display();
3061                 break;
3063         case REQ_TOGGLE_REFS:
3064                 opt_show_refs = !opt_show_refs;
3065                 redraw_display();
3066                 break;
3068         case REQ_SEARCH:
3069         case REQ_SEARCH_BACK:
3070                 search_view(view, request);
3071                 break;
3073         case REQ_FIND_NEXT:
3074         case REQ_FIND_PREV:
3075                 find_next(view, request);
3076                 break;
3078         case REQ_STOP_LOADING:
3079                 for (i = 0; i < ARRAY_SIZE(views); i++) {
3080                         view = &views[i];
3081                         if (view->pipe)
3082                                 report("Stopped loading the %s view", view->name),
3083                         end_update(view, TRUE);
3084                 }
3085                 break;
3087         case REQ_SHOW_VERSION:
3088                 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3089                 return TRUE;
3091         case REQ_SCREEN_RESIZE:
3092                 resize_display();
3093                 /* Fall-through */
3094         case REQ_SCREEN_REDRAW:
3095                 redraw_display();
3096                 break;
3098         case REQ_EDIT:
3099                 report("Nothing to edit");
3100                 break;
3102         case REQ_ENTER:
3103                 report("Nothing to enter");
3104                 break;
3106         case REQ_VIEW_CLOSE:
3107                 /* XXX: Mark closed views by letting view->parent point to the
3108                  * view itself. Parents to closed view should never be
3109                  * followed. */
3110                 if (view->parent &&
3111                     view->parent->parent != view->parent) {
3112                         memset(display, 0, sizeof(display));
3113                         current_view = 0;
3114                         display[current_view] = view->parent;
3115                         view->parent = view;
3116                         resize_display();
3117                         redraw_display();
3118                         report("");
3119                         break;
3120                 }
3121                 /* Fall-through */
3122         case REQ_QUIT:
3123                 return FALSE;
3125         default:
3126                 report("Unknown key, press 'h' for help");
3127                 return TRUE;
3128         }
3130         return TRUE;
3134 /*
3135  * Pager backend
3136  */
3138 static bool
3139 pager_draw(struct view *view, struct line *line, unsigned int lineno)
3141         char *text = line->data;
3143         if (opt_line_number && draw_lineno(view, lineno))
3144                 return TRUE;
3146         draw_text(view, line->type, text, TRUE);
3147         return TRUE;
3150 static bool
3151 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
3153         char refbuf[SIZEOF_STR];
3154         char *ref = NULL;
3155         FILE *pipe;
3157         if (!string_format(refbuf, "git describe %s 2>/dev/null", commit_id))
3158                 return TRUE;
3160         pipe = popen(refbuf, "r");
3161         if (!pipe)
3162                 return TRUE;
3164         if ((ref = fgets(refbuf, sizeof(refbuf), pipe)))
3165                 ref = chomp_string(ref);
3166         pclose(pipe);
3168         if (!ref || !*ref)
3169                 return TRUE;
3171         /* This is the only fatal call, since it can "corrupt" the buffer. */
3172         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
3173                 return FALSE;
3175         return TRUE;
3178 static void
3179 add_pager_refs(struct view *view, struct line *line)
3181         char buf[SIZEOF_STR];
3182         char *commit_id = (char *)line->data + STRING_SIZE("commit ");
3183         struct ref **refs;
3184         size_t bufpos = 0, refpos = 0;
3185         const char *sep = "Refs: ";
3186         bool is_tag = FALSE;
3188         assert(line->type == LINE_COMMIT);
3190         refs = get_refs(commit_id);
3191         if (!refs) {
3192                 if (view == VIEW(REQ_VIEW_DIFF))
3193                         goto try_add_describe_ref;
3194                 return;
3195         }
3197         do {
3198                 struct ref *ref = refs[refpos];
3199                 const char *fmt = ref->tag    ? "%s[%s]" :
3200                                   ref->remote ? "%s<%s>" : "%s%s";
3202                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
3203                         return;
3204                 sep = ", ";
3205                 if (ref->tag)
3206                         is_tag = TRUE;
3207         } while (refs[refpos++]->next);
3209         if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
3210 try_add_describe_ref:
3211                 /* Add <tag>-g<commit_id> "fake" reference. */
3212                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
3213                         return;
3214         }
3216         if (bufpos == 0)
3217                 return;
3219         if (!realloc_lines(view, view->line_size + 1))
3220                 return;
3222         add_line_text(view, buf, LINE_PP_REFS);
3225 static bool
3226 pager_read(struct view *view, char *data)
3228         struct line *line;
3230         if (!data)
3231                 return TRUE;
3233         line = add_line_text(view, data, get_line_type(data));
3234         if (!line)
3235                 return FALSE;
3237         if (line->type == LINE_COMMIT &&
3238             (view == VIEW(REQ_VIEW_DIFF) ||
3239              view == VIEW(REQ_VIEW_LOG)))
3240                 add_pager_refs(view, line);
3242         return TRUE;
3245 static enum request
3246 pager_request(struct view *view, enum request request, struct line *line)
3248         int split = 0;
3250         if (request != REQ_ENTER)
3251                 return request;
3253         if (line->type == LINE_COMMIT &&
3254            (view == VIEW(REQ_VIEW_LOG) ||
3255             view == VIEW(REQ_VIEW_PAGER))) {
3256                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3257                 split = 1;
3258         }
3260         /* Always scroll the view even if it was split. That way
3261          * you can use Enter to scroll through the log view and
3262          * split open each commit diff. */
3263         scroll_view(view, REQ_SCROLL_LINE_DOWN);
3265         /* FIXME: A minor workaround. Scrolling the view will call report("")
3266          * but if we are scrolling a non-current view this won't properly
3267          * update the view title. */
3268         if (split)
3269                 update_view_title(view);
3271         return REQ_NONE;
3274 static bool
3275 pager_grep(struct view *view, struct line *line)
3277         regmatch_t pmatch;
3278         char *text = line->data;
3280         if (!*text)
3281                 return FALSE;
3283         if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
3284                 return FALSE;
3286         return TRUE;
3289 static void
3290 pager_select(struct view *view, struct line *line)
3292         if (line->type == LINE_COMMIT) {
3293                 char *text = (char *)line->data + STRING_SIZE("commit ");
3295                 if (view != VIEW(REQ_VIEW_PAGER))
3296                         string_copy_rev(view->ref, text);
3297                 string_copy_rev(ref_commit, text);
3298         }
3301 static struct view_ops pager_ops = {
3302         "line",
3303         NULL,
3304         NULL,
3305         pager_read,
3306         pager_draw,
3307         pager_request,
3308         pager_grep,
3309         pager_select,
3310 };
3312 static const char *log_argv[SIZEOF_ARG] = {
3313         "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
3314 };
3316 static enum request
3317 log_request(struct view *view, enum request request, struct line *line)
3319         switch (request) {
3320         case REQ_REFRESH:
3321                 load_refs();
3322                 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
3323                 return REQ_NONE;
3324         default:
3325                 return pager_request(view, request, line);
3326         }
3329 static struct view_ops log_ops = {
3330         "line",
3331         log_argv,
3332         NULL,
3333         pager_read,
3334         pager_draw,
3335         log_request,
3336         pager_grep,
3337         pager_select,
3338 };
3340 static const char *diff_argv[SIZEOF_ARG] = {
3341         "git", "show", "--pretty=fuller", "--no-color", "--root",
3342                 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
3343 };
3345 static struct view_ops diff_ops = {
3346         "line",
3347         diff_argv,
3348         NULL,
3349         pager_read,
3350         pager_draw,
3351         pager_request,
3352         pager_grep,
3353         pager_select,
3354 };
3356 /*
3357  * Help backend
3358  */
3360 static bool
3361 help_open(struct view *view)
3363         char buf[BUFSIZ];
3364         int lines = ARRAY_SIZE(req_info) + 2;
3365         int i;
3367         if (view->lines > 0)
3368                 return TRUE;
3370         for (i = 0; i < ARRAY_SIZE(req_info); i++)
3371                 if (!req_info[i].request)
3372                         lines++;
3374         lines += run_requests + 1;
3376         view->line = calloc(lines, sizeof(*view->line));
3377         if (!view->line)
3378                 return FALSE;
3380         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
3382         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
3383                 const char *key;
3385                 if (req_info[i].request == REQ_NONE)
3386                         continue;
3388                 if (!req_info[i].request) {
3389                         add_line_text(view, "", LINE_DEFAULT);
3390                         add_line_text(view, req_info[i].help, LINE_DEFAULT);
3391                         continue;
3392                 }
3394                 key = get_key(req_info[i].request);
3395                 if (!*key)
3396                         key = "(no key defined)";
3398                 if (!string_format(buf, "    %-25s %s", key, req_info[i].help))
3399                         continue;
3401                 add_line_text(view, buf, LINE_DEFAULT);
3402         }
3404         if (run_requests) {
3405                 add_line_text(view, "", LINE_DEFAULT);
3406                 add_line_text(view, "External commands:", LINE_DEFAULT);
3407         }
3409         for (i = 0; i < run_requests; i++) {
3410                 struct run_request *req = get_run_request(REQ_NONE + i + 1);
3411                 const char *key;
3412                 char cmd[SIZEOF_STR];
3413                 size_t bufpos;
3414                 int argc;
3416                 if (!req)
3417                         continue;
3419                 key = get_key_name(req->key);
3420                 if (!*key)
3421                         key = "(no key defined)";
3423                 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
3424                         if (!string_format_from(cmd, &bufpos, "%s%s",
3425                                                 argc ? " " : "", req->argv[argc]))
3426                                 return REQ_NONE;
3428                 if (!string_format(buf, "    %-10s %-14s `%s`",
3429                                    keymap_table[req->keymap].name, key, cmd))
3430                         continue;
3432                 add_line_text(view, buf, LINE_DEFAULT);
3433         }
3435         return TRUE;
3438 static struct view_ops help_ops = {
3439         "line",
3440         NULL,
3441         help_open,
3442         NULL,
3443         pager_draw,
3444         pager_request,
3445         pager_grep,
3446         pager_select,
3447 };
3450 /*
3451  * Tree backend
3452  */
3454 struct tree_stack_entry {
3455         struct tree_stack_entry *prev;  /* Entry below this in the stack */
3456         unsigned long lineno;           /* Line number to restore */
3457         char *name;                     /* Position of name in opt_path */
3458 };
3460 /* The top of the path stack. */
3461 static struct tree_stack_entry *tree_stack = NULL;
3462 unsigned long tree_lineno = 0;
3464 static void
3465 pop_tree_stack_entry(void)
3467         struct tree_stack_entry *entry = tree_stack;
3469         tree_lineno = entry->lineno;
3470         entry->name[0] = 0;
3471         tree_stack = entry->prev;
3472         free(entry);
3475 static void
3476 push_tree_stack_entry(const char *name, unsigned long lineno)
3478         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
3479         size_t pathlen = strlen(opt_path);
3481         if (!entry)
3482                 return;
3484         entry->prev = tree_stack;
3485         entry->name = opt_path + pathlen;
3486         tree_stack = entry;
3488         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
3489                 pop_tree_stack_entry();
3490                 return;
3491         }
3493         /* Move the current line to the first tree entry. */
3494         tree_lineno = 1;
3495         entry->lineno = lineno;
3498 /* Parse output from git-ls-tree(1):
3499  *
3500  * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile
3501  * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README
3502  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
3503  * 100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38 web.conf
3504  */
3506 #define SIZEOF_TREE_ATTR \
3507         STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t")
3509 #define TREE_UP_FORMAT "040000 tree %s\t.."
3511 static int
3512 tree_compare_entry(enum line_type type1, const char *name1,
3513                    enum line_type type2, const char *name2)
3515         if (type1 != type2) {
3516                 if (type1 == LINE_TREE_DIR)
3517                         return -1;
3518                 return 1;
3519         }
3521         return strcmp(name1, name2);
3524 static const char *
3525 tree_path(struct line *line)
3527         const char *path = line->data;
3529         return path + SIZEOF_TREE_ATTR;
3532 static bool
3533 tree_read(struct view *view, char *text)
3535         size_t textlen = text ? strlen(text) : 0;
3536         char buf[SIZEOF_STR];
3537         unsigned long pos;
3538         enum line_type type;
3539         bool first_read = view->lines == 0;
3541         if (!text)
3542                 return TRUE;
3543         if (textlen <= SIZEOF_TREE_ATTR)
3544                 return FALSE;
3546         type = text[STRING_SIZE("100644 ")] == 't'
3547              ? LINE_TREE_DIR : LINE_TREE_FILE;
3549         if (first_read) {
3550                 /* Add path info line */
3551                 if (!string_format(buf, "Directory path /%s", opt_path) ||
3552                     !realloc_lines(view, view->line_size + 1) ||
3553                     !add_line_text(view, buf, LINE_DEFAULT))
3554                         return FALSE;
3556                 /* Insert "link" to parent directory. */
3557                 if (*opt_path) {
3558                         if (!string_format(buf, TREE_UP_FORMAT, view->ref) ||
3559                             !realloc_lines(view, view->line_size + 1) ||
3560                             !add_line_text(view, buf, LINE_TREE_DIR))
3561                                 return FALSE;
3562                 }
3563         }
3565         /* Strip the path part ... */
3566         if (*opt_path) {
3567                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
3568                 size_t striplen = strlen(opt_path);
3569                 char *path = text + SIZEOF_TREE_ATTR;
3571                 if (pathlen > striplen)
3572                         memmove(path, path + striplen,
3573                                 pathlen - striplen + 1);
3574         }
3576         /* Skip "Directory ..." and ".." line. */
3577         for (pos = 1 + !!*opt_path; pos < view->lines; pos++) {
3578                 struct line *line = &view->line[pos];
3579                 const char *path1 = tree_path(line);
3580                 char *path2 = text + SIZEOF_TREE_ATTR;
3581                 int cmp = tree_compare_entry(line->type, path1, type, path2);
3583                 if (cmp <= 0)
3584                         continue;
3586                 text = strdup(text);
3587                 if (!text)
3588                         return FALSE;
3590                 if (view->lines > pos)
3591                         memmove(&view->line[pos + 1], &view->line[pos],
3592                                 (view->lines - pos) * sizeof(*line));
3594                 line = &view->line[pos];
3595                 line->data = text;
3596                 line->type = type;
3597                 view->lines++;
3598                 return TRUE;
3599         }
3601         if (!add_line_text(view, text, type))
3602                 return FALSE;
3604         if (tree_lineno > view->lineno) {
3605                 view->lineno = tree_lineno;
3606                 tree_lineno = 0;
3607         }
3609         return TRUE;
3612 static enum request
3613 tree_request(struct view *view, enum request request, struct line *line)
3615         enum open_flags flags;
3617         switch (request) {
3618         case REQ_VIEW_BLAME:
3619                 if (line->type != LINE_TREE_FILE) {
3620                         report("Blame only supported for files");
3621                         return REQ_NONE;
3622                 }
3624                 string_copy(opt_ref, view->vid);
3625                 return request;
3627         case REQ_EDIT:
3628                 if (line->type != LINE_TREE_FILE) {
3629                         report("Edit only supported for files");
3630                 } else if (!is_head_commit(view->vid)) {
3631                         report("Edit only supported for files in the current work tree");
3632                 } else {
3633                         open_editor(TRUE, opt_file);
3634                 }
3635                 return REQ_NONE;
3637         case REQ_TREE_PARENT:
3638                 if (!*opt_path) {
3639                         /* quit view if at top of tree */
3640                         return REQ_VIEW_CLOSE;
3641                 }
3642                 /* fake 'cd  ..' */
3643                 line = &view->line[1];
3644                 break;
3646         case REQ_ENTER:
3647                 break;
3649         default:
3650                 return request;
3651         }
3653         /* Cleanup the stack if the tree view is at a different tree. */
3654         while (!*opt_path && tree_stack)
3655                 pop_tree_stack_entry();
3657         switch (line->type) {
3658         case LINE_TREE_DIR:
3659                 /* Depending on whether it is a subdir or parent (updir?) link
3660                  * mangle the path buffer. */
3661                 if (line == &view->line[1] && *opt_path) {
3662                         pop_tree_stack_entry();
3664                 } else {
3665                         const char *basename = tree_path(line);
3667                         push_tree_stack_entry(basename, view->lineno);
3668                 }
3670                 /* Trees and subtrees share the same ID, so they are not not
3671                  * unique like blobs. */
3672                 flags = OPEN_RELOAD;
3673                 request = REQ_VIEW_TREE;
3674                 break;
3676         case LINE_TREE_FILE:
3677                 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
3678                 request = REQ_VIEW_BLOB;
3679                 break;
3681         default:
3682                 return TRUE;
3683         }
3685         open_view(view, request, flags);
3686         if (request == REQ_VIEW_TREE) {
3687                 view->lineno = tree_lineno;
3688         }
3690         return REQ_NONE;
3693 static void
3694 tree_select(struct view *view, struct line *line)
3696         char *text = (char *)line->data + STRING_SIZE("100644 blob ");
3698         if (line->type == LINE_TREE_FILE) {
3699                 string_copy_rev(ref_blob, text);
3700                 string_format(opt_file, "%s%s", opt_path, tree_path(line));
3702         } else if (line->type != LINE_TREE_DIR) {
3703                 return;
3704         }
3706         string_copy_rev(view->ref, text);
3709 static const char *tree_argv[SIZEOF_ARG] = {
3710         "git", "ls-tree", "%(commit)", "%(directory)", NULL
3711 };
3713 static struct view_ops tree_ops = {
3714         "file",
3715         tree_argv,
3716         NULL,
3717         tree_read,
3718         pager_draw,
3719         tree_request,
3720         pager_grep,
3721         tree_select,
3722 };
3724 static bool
3725 blob_read(struct view *view, char *line)
3727         if (!line)
3728                 return TRUE;
3729         return add_line_text(view, line, LINE_DEFAULT) != NULL;
3732 static const char *blob_argv[SIZEOF_ARG] = {
3733         "git", "cat-file", "blob", "%(blob)", NULL
3734 };
3736 static struct view_ops blob_ops = {
3737         "line",
3738         blob_argv,
3739         NULL,
3740         blob_read,
3741         pager_draw,
3742         pager_request,
3743         pager_grep,
3744         pager_select,
3745 };
3747 /*
3748  * Blame backend
3749  *
3750  * Loading the blame view is a two phase job:
3751  *
3752  *  1. File content is read either using opt_file from the
3753  *     filesystem or using git-cat-file.
3754  *  2. Then blame information is incrementally added by
3755  *     reading output from git-blame.
3756  */
3758 struct blame_commit {
3759         char id[SIZEOF_REV];            /* SHA1 ID. */
3760         char title[128];                /* First line of the commit message. */
3761         char author[75];                /* Author of the commit. */
3762         struct tm time;                 /* Date from the author ident. */
3763         char filename[128];             /* Name of file. */
3764 };
3766 struct blame {
3767         struct blame_commit *commit;
3768         char text[1];
3769 };
3771 #define BLAME_CAT_FILE_CMD "git cat-file blob %s:%s"
3772 #define BLAME_INCREMENTAL_CMD "git blame --incremental %s -- %s"
3774 static bool
3775 blame_open(struct view *view)
3777         char path[SIZEOF_STR];
3778         char ref[SIZEOF_STR] = "";
3780         if (sq_quote(path, 0, opt_file) >= sizeof(path))
3781                 return FALSE;
3783         if (*opt_ref && sq_quote(ref, 0, opt_ref) >= sizeof(ref))
3784                 return FALSE;
3786         if (*opt_ref || !init_io_fd(&view->io, fopen(opt_file, "r"))) {
3787                 const char *id = *opt_ref ? ref : "HEAD";
3789                 if (!run_io_format(&view->io, BLAME_CAT_FILE_CMD, id, path))
3790                         return FALSE;
3791         }
3793         setup_update(view, opt_file);
3794         string_format(view->ref, "%s ...", opt_file);
3796         return TRUE;
3799 static struct blame_commit *
3800 get_blame_commit(struct view *view, const char *id)
3802         size_t i;
3804         for (i = 0; i < view->lines; i++) {
3805                 struct blame *blame = view->line[i].data;
3807                 if (!blame->commit)
3808                         continue;
3810                 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
3811                         return blame->commit;
3812         }
3814         {
3815                 struct blame_commit *commit = calloc(1, sizeof(*commit));
3817                 if (commit)
3818                         string_ncopy(commit->id, id, SIZEOF_REV);
3819                 return commit;
3820         }
3823 static bool
3824 parse_number(const char **posref, size_t *number, size_t min, size_t max)
3826         const char *pos = *posref;
3828         *posref = NULL;
3829         pos = strchr(pos + 1, ' ');
3830         if (!pos || !isdigit(pos[1]))
3831                 return FALSE;
3832         *number = atoi(pos + 1);
3833         if (*number < min || *number > max)
3834                 return FALSE;
3836         *posref = pos;
3837         return TRUE;
3840 static struct blame_commit *
3841 parse_blame_commit(struct view *view, const char *text, int *blamed)
3843         struct blame_commit *commit;
3844         struct blame *blame;
3845         const char *pos = text + SIZEOF_REV - 1;
3846         size_t lineno;
3847         size_t group;
3849         if (strlen(text) <= SIZEOF_REV || *pos != ' ')
3850                 return NULL;
3852         if (!parse_number(&pos, &lineno, 1, view->lines) ||
3853             !parse_number(&pos, &group, 1, view->lines - lineno + 1))
3854                 return NULL;
3856         commit = get_blame_commit(view, text);
3857         if (!commit)
3858                 return NULL;
3860         *blamed += group;
3861         while (group--) {
3862                 struct line *line = &view->line[lineno + group - 1];
3864                 blame = line->data;
3865                 blame->commit = commit;
3866                 line->dirty = 1;
3867         }
3869         return commit;
3872 static bool
3873 blame_read_file(struct view *view, const char *line, bool *read_file)
3875         if (!line) {
3876                 char ref[SIZEOF_STR] = "";
3877                 char path[SIZEOF_STR];
3878                 struct io io = {};
3880                 if (view->lines == 0 && !view->parent)
3881                         die("No blame exist for %s", view->vid);
3883                 if (view->lines == 0 ||
3884                     sq_quote(path, 0, opt_file) >= sizeof(path) ||
3885                     (*opt_ref && sq_quote(ref, 0, opt_ref) >= sizeof(ref)) ||
3886                     !run_io_format(&io, BLAME_INCREMENTAL_CMD, ref, path)) {
3887                         report("Failed to load blame data");
3888                         return TRUE;
3889                 }
3891                 done_io(view->pipe);
3892                 view->io = io;
3893                 *read_file = FALSE;
3894                 return FALSE;
3896         } else {
3897                 size_t linelen = strlen(line);
3898                 struct blame *blame = malloc(sizeof(*blame) + linelen);
3900                 blame->commit = NULL;
3901                 strncpy(blame->text, line, linelen);
3902                 blame->text[linelen] = 0;
3903                 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
3904         }
3907 static bool
3908 match_blame_header(const char *name, char **line)
3910         size_t namelen = strlen(name);
3911         bool matched = !strncmp(name, *line, namelen);
3913         if (matched)
3914                 *line += namelen;
3916         return matched;
3919 static bool
3920 blame_read(struct view *view, char *line)
3922         static struct blame_commit *commit = NULL;
3923         static int blamed = 0;
3924         static time_t author_time;
3925         static bool read_file = TRUE;
3927         if (read_file)
3928                 return blame_read_file(view, line, &read_file);
3930         if (!line) {
3931                 /* Reset all! */
3932                 commit = NULL;
3933                 blamed = 0;
3934                 read_file = TRUE;
3935                 string_format(view->ref, "%s", view->vid);
3936                 if (view_is_displayed(view)) {
3937                         update_view_title(view);
3938                         redraw_view_from(view, 0);
3939                 }
3940                 return TRUE;
3941         }
3943         if (!commit) {
3944                 commit = parse_blame_commit(view, line, &blamed);
3945                 string_format(view->ref, "%s %2d%%", view->vid,
3946                               blamed * 100 / view->lines);
3948         } else if (match_blame_header("author ", &line)) {
3949                 string_ncopy(commit->author, line, strlen(line));
3951         } else if (match_blame_header("author-time ", &line)) {
3952                 author_time = (time_t) atol(line);
3954         } else if (match_blame_header("author-tz ", &line)) {
3955                 long tz;
3957                 tz  = ('0' - line[1]) * 60 * 60 * 10;
3958                 tz += ('0' - line[2]) * 60 * 60;
3959                 tz += ('0' - line[3]) * 60;
3960                 tz += ('0' - line[4]) * 60;
3962                 if (line[0] == '-')
3963                         tz = -tz;
3965                 author_time -= tz;
3966                 gmtime_r(&author_time, &commit->time);
3968         } else if (match_blame_header("summary ", &line)) {
3969                 string_ncopy(commit->title, line, strlen(line));
3971         } else if (match_blame_header("filename ", &line)) {
3972                 string_ncopy(commit->filename, line, strlen(line));
3973                 commit = NULL;
3974         }
3976         return TRUE;
3979 static bool
3980 blame_draw(struct view *view, struct line *line, unsigned int lineno)
3982         struct blame *blame = line->data;
3983         struct tm *time = NULL;
3984         const char *id = NULL, *author = NULL;
3986         if (blame->commit && *blame->commit->filename) {
3987                 id = blame->commit->id;
3988                 author = blame->commit->author;
3989                 time = &blame->commit->time;
3990         }
3992         if (opt_date && draw_date(view, time))
3993                 return TRUE;
3995         if (opt_author &&
3996             draw_field(view, LINE_MAIN_AUTHOR, author, opt_author_cols, TRUE))
3997                 return TRUE;
3999         if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
4000                 return TRUE;
4002         if (draw_lineno(view, lineno))
4003                 return TRUE;
4005         draw_text(view, LINE_DEFAULT, blame->text, TRUE);
4006         return TRUE;
4009 static enum request
4010 blame_request(struct view *view, enum request request, struct line *line)
4012         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4013         struct blame *blame = line->data;
4015         switch (request) {
4016         case REQ_VIEW_BLAME:
4017                 if (!blame->commit || !strcmp(blame->commit->id, NULL_ID)) {
4018                         report("Commit ID unknown");
4019                         break;
4020                 }
4021                 string_copy(opt_ref, blame->commit->id);
4022                 open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
4023                 return request;
4025         case REQ_ENTER:
4026                 if (!blame->commit) {
4027                         report("No commit loaded yet");
4028                         break;
4029                 }
4031                 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
4032                     !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
4033                         break;
4035                 if (!strcmp(blame->commit->id, NULL_ID)) {
4036                         char path[SIZEOF_STR];
4038                         if (sq_quote(path, 0, view->vid) >= sizeof(path))
4039                                 break;
4040                         string_format(opt_cmd, "git diff-index --root --patch-with-stat -C -M --cached HEAD -- %s 2>/dev/null", path);
4041                 }
4043                 open_view(view, REQ_VIEW_DIFF, flags);
4044                 break;
4046         default:
4047                 return request;
4048         }
4050         return REQ_NONE;
4053 static bool
4054 blame_grep(struct view *view, struct line *line)
4056         struct blame *blame = line->data;
4057         struct blame_commit *commit = blame->commit;
4058         regmatch_t pmatch;
4060 #define MATCH(text, on)                                                 \
4061         (on && *text && regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4063         if (commit) {
4064                 char buf[DATE_COLS + 1];
4066                 if (MATCH(commit->title, 1) ||
4067                     MATCH(commit->author, opt_author) ||
4068                     MATCH(commit->id, opt_date))
4069                         return TRUE;
4071                 if (strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time) &&
4072                     MATCH(buf, 1))
4073                         return TRUE;
4074         }
4076         return MATCH(blame->text, 1);
4078 #undef MATCH
4081 static void
4082 blame_select(struct view *view, struct line *line)
4084         struct blame *blame = line->data;
4085         struct blame_commit *commit = blame->commit;
4087         if (!commit)
4088                 return;
4090         if (!strcmp(commit->id, NULL_ID))
4091                 string_ncopy(ref_commit, "HEAD", 4);
4092         else
4093                 string_copy_rev(ref_commit, commit->id);
4096 static struct view_ops blame_ops = {
4097         "line",
4098         NULL,
4099         blame_open,
4100         blame_read,
4101         blame_draw,
4102         blame_request,
4103         blame_grep,
4104         blame_select,
4105 };
4107 /*
4108  * Status backend
4109  */
4111 struct status {
4112         char status;
4113         struct {
4114                 mode_t mode;
4115                 char rev[SIZEOF_REV];
4116                 char name[SIZEOF_STR];
4117         } old;
4118         struct {
4119                 mode_t mode;
4120                 char rev[SIZEOF_REV];
4121                 char name[SIZEOF_STR];
4122         } new;
4123 };
4125 static char status_onbranch[SIZEOF_STR];
4126 static struct status stage_status;
4127 static enum line_type stage_line_type;
4128 static size_t stage_chunks;
4129 static int *stage_chunk;
4131 /* This should work even for the "On branch" line. */
4132 static inline bool
4133 status_has_none(struct view *view, struct line *line)
4135         return line < view->line + view->lines && !line[1].data;
4138 /* Get fields from the diff line:
4139  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
4140  */
4141 static inline bool
4142 status_get_diff(struct status *file, const char *buf, size_t bufsize)
4144         const char *old_mode = buf +  1;
4145         const char *new_mode = buf +  8;
4146         const char *old_rev  = buf + 15;
4147         const char *new_rev  = buf + 56;
4148         const char *status   = buf + 97;
4150         if (bufsize < 99 ||
4151             old_mode[-1] != ':' ||
4152             new_mode[-1] != ' ' ||
4153             old_rev[-1]  != ' ' ||
4154             new_rev[-1]  != ' ' ||
4155             status[-1]   != ' ')
4156                 return FALSE;
4158         file->status = *status;
4160         string_copy_rev(file->old.rev, old_rev);
4161         string_copy_rev(file->new.rev, new_rev);
4163         file->old.mode = strtoul(old_mode, NULL, 8);
4164         file->new.mode = strtoul(new_mode, NULL, 8);
4166         file->old.name[0] = file->new.name[0] = 0;
4168         return TRUE;
4171 static bool
4172 status_run(struct view *view, const char cmd[], char status, enum line_type type)
4174         struct status *file = NULL;
4175         struct status *unmerged = NULL;
4176         char buf[SIZEOF_STR * 4];
4177         size_t bufsize = 0;
4178         FILE *pipe;
4180         pipe = popen(cmd, "r");
4181         if (!pipe)
4182                 return FALSE;
4184         add_line_data(view, NULL, type);
4186         while (!feof(pipe) && !ferror(pipe)) {
4187                 char *sep;
4188                 size_t readsize;
4190                 readsize = fread(buf + bufsize, 1, sizeof(buf) - bufsize, pipe);
4191                 if (!readsize)
4192                         break;
4193                 bufsize += readsize;
4195                 /* Process while we have NUL chars. */
4196                 while ((sep = memchr(buf, 0, bufsize))) {
4197                         size_t sepsize = sep - buf + 1;
4199                         if (!file) {
4200                                 if (!realloc_lines(view, view->line_size + 1))
4201                                         goto error_out;
4203                                 file = calloc(1, sizeof(*file));
4204                                 if (!file)
4205                                         goto error_out;
4207                                 add_line_data(view, file, type);
4208                         }
4210                         /* Parse diff info part. */
4211                         if (status) {
4212                                 file->status = status;
4213                                 if (status == 'A')
4214                                         string_copy(file->old.rev, NULL_ID);
4216                         } else if (!file->status) {
4217                                 if (!status_get_diff(file, buf, sepsize))
4218                                         goto error_out;
4220                                 bufsize -= sepsize;
4221                                 memmove(buf, sep + 1, bufsize);
4223                                 sep = memchr(buf, 0, bufsize);
4224                                 if (!sep)
4225                                         break;
4226                                 sepsize = sep - buf + 1;
4228                                 /* Collapse all 'M'odified entries that
4229                                  * follow a associated 'U'nmerged entry.
4230                                  */
4231                                 if (file->status == 'U') {
4232                                         unmerged = file;
4234                                 } else if (unmerged) {
4235                                         int collapse = !strcmp(buf, unmerged->new.name);
4237                                         unmerged = NULL;
4238                                         if (collapse) {
4239                                                 free(file);
4240                                                 view->lines--;
4241                                                 continue;
4242                                         }
4243                                 }
4244                         }
4246                         /* Grab the old name for rename/copy. */
4247                         if (!*file->old.name &&
4248                             (file->status == 'R' || file->status == 'C')) {
4249                                 sepsize = sep - buf + 1;
4250                                 string_ncopy(file->old.name, buf, sepsize);
4251                                 bufsize -= sepsize;
4252                                 memmove(buf, sep + 1, bufsize);
4254                                 sep = memchr(buf, 0, bufsize);
4255                                 if (!sep)
4256                                         break;
4257                                 sepsize = sep - buf + 1;
4258                         }
4260                         /* git-ls-files just delivers a NUL separated
4261                          * list of file names similar to the second half
4262                          * of the git-diff-* output. */
4263                         string_ncopy(file->new.name, buf, sepsize);
4264                         if (!*file->old.name)
4265                                 string_copy(file->old.name, file->new.name);
4266                         bufsize -= sepsize;
4267                         memmove(buf, sep + 1, bufsize);
4268                         file = NULL;
4269                 }
4270         }
4272         if (ferror(pipe)) {
4273 error_out:
4274                 pclose(pipe);
4275                 return FALSE;
4276         }
4278         if (!view->line[view->lines - 1].data)
4279                 add_line_data(view, NULL, LINE_STAT_NONE);
4281         pclose(pipe);
4282         return TRUE;
4285 /* Don't show unmerged entries in the staged section. */
4286 #define STATUS_DIFF_INDEX_CMD "git diff-index -z --diff-filter=ACDMRTXB --cached -M HEAD"
4287 #define STATUS_DIFF_FILES_CMD "git diff-files -z"
4288 #define STATUS_LIST_OTHER_CMD \
4289         "git ls-files -z --others --exclude-standard"
4290 #define STATUS_LIST_NO_HEAD_CMD \
4291         "git ls-files -z --cached --exclude-standard"
4293 #define STATUS_DIFF_INDEX_SHOW_CMD \
4294         "git diff-index --root --patch-with-stat -C -M --cached HEAD -- %s %s 2>/dev/null"
4296 #define STATUS_DIFF_FILES_SHOW_CMD \
4297         "git diff-files --root --patch-with-stat -C -M -- %s %s 2>/dev/null"
4299 #define STATUS_DIFF_NO_HEAD_SHOW_CMD \
4300         "git diff --no-color --patch-with-stat /dev/null %s 2>/dev/null"
4302 /* First parse staged info using git-diff-index(1), then parse unstaged
4303  * info using git-diff-files(1), and finally untracked files using
4304  * git-ls-files(1). */
4305 static bool
4306 status_open(struct view *view)
4308         unsigned long prev_lineno = view->lineno;
4310         reset_view(view);
4312         if (!realloc_lines(view, view->line_size + 7))
4313                 return FALSE;
4315         add_line_data(view, NULL, LINE_STAT_HEAD);
4316         if (is_initial_commit())
4317                 string_copy(status_onbranch, "Initial commit");
4318         else if (!*opt_head)
4319                 string_copy(status_onbranch, "Not currently on any branch");
4320         else if (!string_format(status_onbranch, "On branch %s", opt_head))
4321                 return FALSE;
4323         system("git update-index -q --refresh >/dev/null 2>/dev/null");
4325         if (is_initial_commit()) {
4326                 if (!status_run(view, STATUS_LIST_NO_HEAD_CMD, 'A', LINE_STAT_STAGED))
4327                         return FALSE;
4328         } else if (!status_run(view, STATUS_DIFF_INDEX_CMD, 0, LINE_STAT_STAGED)) {
4329                 return FALSE;
4330         }
4332         if (!status_run(view, STATUS_DIFF_FILES_CMD, 0, LINE_STAT_UNSTAGED) ||
4333             !status_run(view, STATUS_LIST_OTHER_CMD, '?', LINE_STAT_UNTRACKED))
4334                 return FALSE;
4336         /* If all went well restore the previous line number to stay in
4337          * the context or select a line with something that can be
4338          * updated. */
4339         if (prev_lineno >= view->lines)
4340                 prev_lineno = view->lines - 1;
4341         while (prev_lineno < view->lines && !view->line[prev_lineno].data)
4342                 prev_lineno++;
4343         while (prev_lineno > 0 && !view->line[prev_lineno].data)
4344                 prev_lineno--;
4346         /* If the above fails, always skip the "On branch" line. */
4347         if (prev_lineno < view->lines)
4348                 view->lineno = prev_lineno;
4349         else
4350                 view->lineno = 1;
4352         if (view->lineno < view->offset)
4353                 view->offset = view->lineno;
4354         else if (view->offset + view->height <= view->lineno)
4355                 view->offset = view->lineno - view->height + 1;
4357         return TRUE;
4360 static bool
4361 status_draw(struct view *view, struct line *line, unsigned int lineno)
4363         struct status *status = line->data;
4364         enum line_type type;
4365         const char *text;
4367         if (!status) {
4368                 switch (line->type) {
4369                 case LINE_STAT_STAGED:
4370                         type = LINE_STAT_SECTION;
4371                         text = "Changes to be committed:";
4372                         break;
4374                 case LINE_STAT_UNSTAGED:
4375                         type = LINE_STAT_SECTION;
4376                         text = "Changed but not updated:";
4377                         break;
4379                 case LINE_STAT_UNTRACKED:
4380                         type = LINE_STAT_SECTION;
4381                         text = "Untracked files:";
4382                         break;
4384                 case LINE_STAT_NONE:
4385                         type = LINE_DEFAULT;
4386                         text = "    (no files)";
4387                         break;
4389                 case LINE_STAT_HEAD:
4390                         type = LINE_STAT_HEAD;
4391                         text = status_onbranch;
4392                         break;
4394                 default:
4395                         return FALSE;
4396                 }
4397         } else {
4398                 static char buf[] = { '?', ' ', ' ', ' ', 0 };
4400                 buf[0] = status->status;
4401                 if (draw_text(view, line->type, buf, TRUE))
4402                         return TRUE;
4403                 type = LINE_DEFAULT;
4404                 text = status->new.name;
4405         }
4407         draw_text(view, type, text, TRUE);
4408         return TRUE;
4411 static enum request
4412 status_enter(struct view *view, struct line *line)
4414         struct status *status = line->data;
4415         char oldpath[SIZEOF_STR] = "";
4416         char newpath[SIZEOF_STR] = "";
4417         const char *info;
4418         size_t cmdsize = 0;
4419         enum open_flags split;
4421         if (line->type == LINE_STAT_NONE ||
4422             (!status && line[1].type == LINE_STAT_NONE)) {
4423                 report("No file to diff");
4424                 return REQ_NONE;
4425         }
4427         if (status) {
4428                 if (sq_quote(oldpath, 0, status->old.name) >= sizeof(oldpath))
4429                         return REQ_QUIT;
4430                 /* Diffs for unmerged entries are empty when pasing the
4431                  * new path, so leave it empty. */
4432                 if (status->status != 'U' &&
4433                     sq_quote(newpath, 0, status->new.name) >= sizeof(newpath))
4434                         return REQ_QUIT;
4435         }
4437         if (opt_cdup[0] &&
4438             line->type != LINE_STAT_UNTRACKED &&
4439             !string_format_from(opt_cmd, &cmdsize, "cd %s;", opt_cdup))
4440                 return REQ_QUIT;
4442         switch (line->type) {
4443         case LINE_STAT_STAGED:
4444                 if (is_initial_commit()) {
4445                         if (!string_format_from(opt_cmd, &cmdsize,
4446                                                 STATUS_DIFF_NO_HEAD_SHOW_CMD,
4447                                                 newpath))
4448                                 return REQ_QUIT;
4449                 } else {
4450                         if (!string_format_from(opt_cmd, &cmdsize,
4451                                                 STATUS_DIFF_INDEX_SHOW_CMD,
4452                                                 oldpath, newpath))
4453                                 return REQ_QUIT;
4454                 }
4456                 if (status)
4457                         info = "Staged changes to %s";
4458                 else
4459                         info = "Staged changes";
4460                 break;
4462         case LINE_STAT_UNSTAGED:
4463                 if (!string_format_from(opt_cmd, &cmdsize,
4464                                         STATUS_DIFF_FILES_SHOW_CMD, oldpath, newpath))
4465                         return REQ_QUIT;
4466                 if (status)
4467                         info = "Unstaged changes to %s";
4468                 else
4469                         info = "Unstaged changes";
4470                 break;
4472         case LINE_STAT_UNTRACKED:
4473                 if (opt_pipe)
4474                         return REQ_QUIT;
4476                 if (!status) {
4477                         report("No file to show");
4478                         return REQ_NONE;
4479                 }
4481                 if (!suffixcmp(status->new.name, -1, "/")) {
4482                         report("Cannot display a directory");
4483                         return REQ_NONE;
4484                 }
4486                 opt_pipe = fopen(status->new.name, "r");
4487                 info = "Untracked file %s";
4488                 break;
4490         case LINE_STAT_HEAD:
4491                 return REQ_NONE;
4493         default:
4494                 die("line type %d not handled in switch", line->type);
4495         }
4497         split = view_is_displayed(view) ? OPEN_SPLIT : 0;
4498         open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH | split);
4499         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
4500                 if (status) {
4501                         stage_status = *status;
4502                 } else {
4503                         memset(&stage_status, 0, sizeof(stage_status));
4504                 }
4506                 stage_line_type = line->type;
4507                 stage_chunks = 0;
4508                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
4509         }
4511         return REQ_NONE;
4514 static bool
4515 status_exists(struct status *status, enum line_type type)
4517         struct view *view = VIEW(REQ_VIEW_STATUS);
4518         struct line *line;
4520         for (line = view->line; line < view->line + view->lines; line++) {
4521                 struct status *pos = line->data;
4523                 if (line->type == type && pos &&
4524                     !strcmp(status->new.name, pos->new.name))
4525                         return TRUE;
4526         }
4528         return FALSE;
4532 static FILE *
4533 status_update_prepare(enum line_type type)
4535         char cmd[SIZEOF_STR];
4536         size_t cmdsize = 0;
4538         if (opt_cdup[0] &&
4539             type != LINE_STAT_UNTRACKED &&
4540             !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
4541                 return NULL;
4543         switch (type) {
4544         case LINE_STAT_STAGED:
4545                 string_add(cmd, cmdsize, "git update-index -z --index-info");
4546                 break;
4548         case LINE_STAT_UNSTAGED:
4549         case LINE_STAT_UNTRACKED:
4550                 string_add(cmd, cmdsize, "git update-index -z --add --remove --stdin");
4551                 break;
4553         default:
4554                 die("line type %d not handled in switch", type);
4555         }
4557         return popen(cmd, "w");
4560 static bool
4561 status_update_write(FILE *pipe, struct status *status, enum line_type type)
4563         char buf[SIZEOF_STR];
4564         size_t bufsize = 0;
4565         size_t written = 0;
4567         switch (type) {
4568         case LINE_STAT_STAGED:
4569                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
4570                                         status->old.mode,
4571                                         status->old.rev,
4572                                         status->old.name, 0))
4573                         return FALSE;
4574                 break;
4576         case LINE_STAT_UNSTAGED:
4577         case LINE_STAT_UNTRACKED:
4578                 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
4579                         return FALSE;
4580                 break;
4582         default:
4583                 die("line type %d not handled in switch", type);
4584         }
4586         while (!ferror(pipe) && written < bufsize) {
4587                 written += fwrite(buf + written, 1, bufsize - written, pipe);
4588         }
4590         return written == bufsize;
4593 static bool
4594 status_update_file(struct status *status, enum line_type type)
4596         FILE *pipe = status_update_prepare(type);
4597         bool result;
4599         if (!pipe)
4600                 return FALSE;
4602         result = status_update_write(pipe, status, type);
4603         pclose(pipe);
4604         return result;
4607 static bool
4608 status_update_files(struct view *view, struct line *line)
4610         FILE *pipe = status_update_prepare(line->type);
4611         bool result = TRUE;
4612         struct line *pos = view->line + view->lines;
4613         int files = 0;
4614         int file, done;
4616         if (!pipe)
4617                 return FALSE;
4619         for (pos = line; pos < view->line + view->lines && pos->data; pos++)
4620                 files++;
4622         for (file = 0, done = 0; result && file < files; line++, file++) {
4623                 int almost_done = file * 100 / files;
4625                 if (almost_done > done) {
4626                         done = almost_done;
4627                         string_format(view->ref, "updating file %u of %u (%d%% done)",
4628                                       file, files, done);
4629                         update_view_title(view);
4630                 }
4631                 result = status_update_write(pipe, line->data, line->type);
4632         }
4634         pclose(pipe);
4635         return result;
4638 static bool
4639 status_update(struct view *view)
4641         struct line *line = &view->line[view->lineno];
4643         assert(view->lines);
4645         if (!line->data) {
4646                 /* This should work even for the "On branch" line. */
4647                 if (line < view->line + view->lines && !line[1].data) {
4648                         report("Nothing to update");
4649                         return FALSE;
4650                 }
4652                 if (!status_update_files(view, line + 1)) {
4653                         report("Failed to update file status");
4654                         return FALSE;
4655                 }
4657         } else if (!status_update_file(line->data, line->type)) {
4658                 report("Failed to update file status");
4659                 return FALSE;
4660         }
4662         return TRUE;
4665 static bool
4666 status_revert(struct status *status, enum line_type type, bool has_none)
4668         if (!status || type != LINE_STAT_UNSTAGED) {
4669                 if (type == LINE_STAT_STAGED) {
4670                         report("Cannot revert changes to staged files");
4671                 } else if (type == LINE_STAT_UNTRACKED) {
4672                         report("Cannot revert changes to untracked files");
4673                 } else if (has_none) {
4674                         report("Nothing to revert");
4675                 } else {
4676                         report("Cannot revert changes to multiple files");
4677                 }
4678                 return FALSE;
4680         } else {
4681                 const char *checkout_argv[] = {
4682                         "git", "checkout", "--", status->old.name, NULL
4683                 };
4685                 if (!prompt_yesno("Are you sure you want to overwrite any changes?"))
4686                         return FALSE;
4687                 return run_io_fg(checkout_argv, opt_cdup);
4688         }
4691 static enum request
4692 status_request(struct view *view, enum request request, struct line *line)
4694         struct status *status = line->data;
4696         switch (request) {
4697         case REQ_STATUS_UPDATE:
4698                 if (!status_update(view))
4699                         return REQ_NONE;
4700                 break;
4702         case REQ_STATUS_REVERT:
4703                 if (!status_revert(status, line->type, status_has_none(view, line)))
4704                         return REQ_NONE;
4705                 break;
4707         case REQ_STATUS_MERGE:
4708                 if (!status || status->status != 'U') {
4709                         report("Merging only possible for files with unmerged status ('U').");
4710                         return REQ_NONE;
4711                 }
4712                 open_mergetool(status->new.name);
4713                 break;
4715         case REQ_EDIT:
4716                 if (!status)
4717                         return request;
4718                 if (status->status == 'D') {
4719                         report("File has been deleted.");
4720                         return REQ_NONE;
4721                 }
4723                 open_editor(status->status != '?', status->new.name);
4724                 break;
4726         case REQ_VIEW_BLAME:
4727                 if (status) {
4728                         string_copy(opt_file, status->new.name);
4729                         opt_ref[0] = 0;
4730                 }
4731                 return request;
4733         case REQ_ENTER:
4734                 /* After returning the status view has been split to
4735                  * show the stage view. No further reloading is
4736                  * necessary. */
4737                 status_enter(view, line);
4738                 return REQ_NONE;
4740         case REQ_REFRESH:
4741                 /* Simply reload the view. */
4742                 break;
4744         default:
4745                 return request;
4746         }
4748         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
4750         return REQ_NONE;
4753 static void
4754 status_select(struct view *view, struct line *line)
4756         struct status *status = line->data;
4757         char file[SIZEOF_STR] = "all files";
4758         const char *text;
4759         const char *key;
4761         if (status && !string_format(file, "'%s'", status->new.name))
4762                 return;
4764         if (!status && line[1].type == LINE_STAT_NONE)
4765                 line++;
4767         switch (line->type) {
4768         case LINE_STAT_STAGED:
4769                 text = "Press %s to unstage %s for commit";
4770                 break;
4772         case LINE_STAT_UNSTAGED:
4773                 text = "Press %s to stage %s for commit";
4774                 break;
4776         case LINE_STAT_UNTRACKED:
4777                 text = "Press %s to stage %s for addition";
4778                 break;
4780         case LINE_STAT_HEAD:
4781         case LINE_STAT_NONE:
4782                 text = "Nothing to update";
4783                 break;
4785         default:
4786                 die("line type %d not handled in switch", line->type);
4787         }
4789         if (status && status->status == 'U') {
4790                 text = "Press %s to resolve conflict in %s";
4791                 key = get_key(REQ_STATUS_MERGE);
4793         } else {
4794                 key = get_key(REQ_STATUS_UPDATE);
4795         }
4797         string_format(view->ref, text, key, file);
4800 static bool
4801 status_grep(struct view *view, struct line *line)
4803         struct status *status = line->data;
4804         enum { S_STATUS, S_NAME, S_END } state;
4805         char buf[2] = "?";
4806         regmatch_t pmatch;
4808         if (!status)
4809                 return FALSE;
4811         for (state = S_STATUS; state < S_END; state++) {
4812                 const char *text;
4814                 switch (state) {
4815                 case S_NAME:    text = status->new.name;        break;
4816                 case S_STATUS:
4817                         buf[0] = status->status;
4818                         text = buf;
4819                         break;
4821                 default:
4822                         return FALSE;
4823                 }
4825                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4826                         return TRUE;
4827         }
4829         return FALSE;
4832 static struct view_ops status_ops = {
4833         "file",
4834         NULL,
4835         status_open,
4836         NULL,
4837         status_draw,
4838         status_request,
4839         status_grep,
4840         status_select,
4841 };
4844 static bool
4845 stage_diff_line(FILE *pipe, struct line *line)
4847         const char *buf = line->data;
4848         size_t bufsize = strlen(buf);
4849         size_t written = 0;
4851         while (!ferror(pipe) && written < bufsize) {
4852                 written += fwrite(buf + written, 1, bufsize - written, pipe);
4853         }
4855         fputc('\n', pipe);
4857         return written == bufsize;
4860 static bool
4861 stage_diff_write(FILE *pipe, struct line *line, struct line *end)
4863         while (line < end) {
4864                 if (!stage_diff_line(pipe, line++))
4865                         return FALSE;
4866                 if (line->type == LINE_DIFF_CHUNK ||
4867                     line->type == LINE_DIFF_HEADER)
4868                         break;
4869         }
4871         return TRUE;
4874 static struct line *
4875 stage_diff_find(struct view *view, struct line *line, enum line_type type)
4877         for (; view->line < line; line--)
4878                 if (line->type == type)
4879                         return line;
4881         return NULL;
4884 static bool
4885 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
4887         char cmd[SIZEOF_STR];
4888         size_t cmdsize = 0;
4889         struct line *diff_hdr;
4890         FILE *pipe;
4892         diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
4893         if (!diff_hdr)
4894                 return FALSE;
4896         if (opt_cdup[0] &&
4897             !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
4898                 return FALSE;
4900         if (!string_format_from(cmd, &cmdsize,
4901                                 "git apply --whitespace=nowarn %s %s - && "
4902                                 "git update-index -q --unmerged --refresh 2>/dev/null",
4903                                 revert ? "" : "--cached",
4904                                 revert || stage_line_type == LINE_STAT_STAGED ? "-R" : ""))
4905                 return FALSE;
4907         pipe = popen(cmd, "w");
4908         if (!pipe)
4909                 return FALSE;
4911         if (!stage_diff_write(pipe, diff_hdr, chunk) ||
4912             !stage_diff_write(pipe, chunk, view->line + view->lines))
4913                 chunk = NULL;
4915         pclose(pipe);
4917         return chunk ? TRUE : FALSE;
4920 static bool
4921 stage_update(struct view *view, struct line *line)
4923         struct line *chunk = NULL;
4925         if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
4926                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
4928         if (chunk) {
4929                 if (!stage_apply_chunk(view, chunk, FALSE)) {
4930                         report("Failed to apply chunk");
4931                         return FALSE;
4932                 }
4934         } else if (!stage_status.status) {
4935                 view = VIEW(REQ_VIEW_STATUS);
4937                 for (line = view->line; line < view->line + view->lines; line++)
4938                         if (line->type == stage_line_type)
4939                                 break;
4941                 if (!status_update_files(view, line + 1)) {
4942                         report("Failed to update files");
4943                         return FALSE;
4944                 }
4946         } else if (!status_update_file(&stage_status, stage_line_type)) {
4947                 report("Failed to update file");
4948                 return FALSE;
4949         }
4951         return TRUE;
4954 static bool
4955 stage_revert(struct view *view, struct line *line)
4957         struct line *chunk = NULL;
4959         if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
4960                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
4962         if (chunk) {
4963                 if (!prompt_yesno("Are you sure you want to revert changes?"))
4964                         return FALSE;
4966                 if (!stage_apply_chunk(view, chunk, TRUE)) {
4967                         report("Failed to revert chunk");
4968                         return FALSE;
4969                 }
4970                 return TRUE;
4972         } else {
4973                 return status_revert(stage_status.status ? &stage_status : NULL,
4974                                      stage_line_type, FALSE);
4975         }
4979 static void
4980 stage_next(struct view *view, struct line *line)
4982         int i;
4984         if (!stage_chunks) {
4985                 static size_t alloc = 0;
4986                 int *tmp;
4988                 for (line = view->line; line < view->line + view->lines; line++) {
4989                         if (line->type != LINE_DIFF_CHUNK)
4990                                 continue;
4992                         tmp = realloc_items(stage_chunk, &alloc,
4993                                             stage_chunks, sizeof(*tmp));
4994                         if (!tmp) {
4995                                 report("Allocation failure");
4996                                 return;
4997                         }
4999                         stage_chunk = tmp;
5000                         stage_chunk[stage_chunks++] = line - view->line;
5001                 }
5002         }
5004         for (i = 0; i < stage_chunks; i++) {
5005                 if (stage_chunk[i] > view->lineno) {
5006                         do_scroll_view(view, stage_chunk[i] - view->lineno);
5007                         report("Chunk %d of %d", i + 1, stage_chunks);
5008                         return;
5009                 }
5010         }
5012         report("No next chunk found");
5015 static enum request
5016 stage_request(struct view *view, enum request request, struct line *line)
5018         switch (request) {
5019         case REQ_STATUS_UPDATE:
5020                 if (!stage_update(view, line))
5021                         return REQ_NONE;
5022                 break;
5024         case REQ_STATUS_REVERT:
5025                 if (!stage_revert(view, line))
5026                         return REQ_NONE;
5027                 break;
5029         case REQ_STAGE_NEXT:
5030                 if (stage_line_type == LINE_STAT_UNTRACKED) {
5031                         report("File is untracked; press %s to add",
5032                                get_key(REQ_STATUS_UPDATE));
5033                         return REQ_NONE;
5034                 }
5035                 stage_next(view, line);
5036                 return REQ_NONE;
5038         case REQ_EDIT:
5039                 if (!stage_status.new.name[0])
5040                         return request;
5041                 if (stage_status.status == 'D') {
5042                         report("File has been deleted.");
5043                         return REQ_NONE;
5044                 }
5046                 open_editor(stage_status.status != '?', stage_status.new.name);
5047                 break;
5049         case REQ_REFRESH:
5050                 /* Reload everything ... */
5051                 break;
5053         case REQ_VIEW_BLAME:
5054                 if (stage_status.new.name[0]) {
5055                         string_copy(opt_file, stage_status.new.name);
5056                         opt_ref[0] = 0;
5057                 }
5058                 return request;
5060         case REQ_ENTER:
5061                 return pager_request(view, request, line);
5063         default:
5064                 return request;
5065         }
5067         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD | OPEN_NOMAXIMIZE);
5069         /* Check whether the staged entry still exists, and close the
5070          * stage view if it doesn't. */
5071         if (!status_exists(&stage_status, stage_line_type))
5072                 return REQ_VIEW_CLOSE;
5074         if (stage_line_type == LINE_STAT_UNTRACKED) {
5075                 if (!suffixcmp(stage_status.new.name, -1, "/")) {
5076                         report("Cannot display a directory");
5077                         return REQ_NONE;
5078                 }
5080                 opt_pipe = fopen(stage_status.new.name, "r");
5081         }
5082         open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
5084         return REQ_NONE;
5087 static struct view_ops stage_ops = {
5088         "line",
5089         NULL,
5090         NULL,
5091         pager_read,
5092         pager_draw,
5093         stage_request,
5094         pager_grep,
5095         pager_select,
5096 };
5099 /*
5100  * Revision graph
5101  */
5103 struct commit {
5104         char id[SIZEOF_REV];            /* SHA1 ID. */
5105         char title[128];                /* First line of the commit message. */
5106         char author[75];                /* Author of the commit. */
5107         struct tm time;                 /* Date from the author ident. */
5108         struct ref **refs;              /* Repository references. */
5109         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
5110         size_t graph_size;              /* The width of the graph array. */
5111         bool has_parents;               /* Rewritten --parents seen. */
5112 };
5114 /* Size of rev graph with no  "padding" columns */
5115 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
5117 struct rev_graph {
5118         struct rev_graph *prev, *next, *parents;
5119         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
5120         size_t size;
5121         struct commit *commit;
5122         size_t pos;
5123         unsigned int boundary:1;
5124 };
5126 /* Parents of the commit being visualized. */
5127 static struct rev_graph graph_parents[4];
5129 /* The current stack of revisions on the graph. */
5130 static struct rev_graph graph_stacks[4] = {
5131         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
5132         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
5133         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
5134         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
5135 };
5137 static inline bool
5138 graph_parent_is_merge(struct rev_graph *graph)
5140         return graph->parents->size > 1;
5143 static inline void
5144 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
5146         struct commit *commit = graph->commit;
5148         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
5149                 commit->graph[commit->graph_size++] = symbol;
5152 static void
5153 clear_rev_graph(struct rev_graph *graph)
5155         graph->boundary = 0;
5156         graph->size = graph->pos = 0;
5157         graph->commit = NULL;
5158         memset(graph->parents, 0, sizeof(*graph->parents));
5161 static void
5162 done_rev_graph(struct rev_graph *graph)
5164         if (graph_parent_is_merge(graph) &&
5165             graph->pos < graph->size - 1 &&
5166             graph->next->size == graph->size + graph->parents->size - 1) {
5167                 size_t i = graph->pos + graph->parents->size - 1;
5169                 graph->commit->graph_size = i * 2;
5170                 while (i < graph->next->size - 1) {
5171                         append_to_rev_graph(graph, ' ');
5172                         append_to_rev_graph(graph, '\\');
5173                         i++;
5174                 }
5175         }
5177         clear_rev_graph(graph);
5180 static void
5181 push_rev_graph(struct rev_graph *graph, const char *parent)
5183         int i;
5185         /* "Collapse" duplicate parents lines.
5186          *
5187          * FIXME: This needs to also update update the drawn graph but
5188          * for now it just serves as a method for pruning graph lines. */
5189         for (i = 0; i < graph->size; i++)
5190                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
5191                         return;
5193         if (graph->size < SIZEOF_REVITEMS) {
5194                 string_copy_rev(graph->rev[graph->size++], parent);
5195         }
5198 static chtype
5199 get_rev_graph_symbol(struct rev_graph *graph)
5201         chtype symbol;
5203         if (graph->boundary)
5204                 symbol = REVGRAPH_BOUND;
5205         else if (graph->parents->size == 0)
5206                 symbol = REVGRAPH_INIT;
5207         else if (graph_parent_is_merge(graph))
5208                 symbol = REVGRAPH_MERGE;
5209         else if (graph->pos >= graph->size)
5210                 symbol = REVGRAPH_BRANCH;
5211         else
5212                 symbol = REVGRAPH_COMMIT;
5214         return symbol;
5217 static void
5218 draw_rev_graph(struct rev_graph *graph)
5220         struct rev_filler {
5221                 chtype separator, line;
5222         };
5223         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
5224         static struct rev_filler fillers[] = {
5225                 { ' ',  '|' },
5226                 { '`',  '.' },
5227                 { '\'', ' ' },
5228                 { '/',  ' ' },
5229         };
5230         chtype symbol = get_rev_graph_symbol(graph);
5231         struct rev_filler *filler;
5232         size_t i;
5234         if (opt_line_graphics)
5235                 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
5237         filler = &fillers[DEFAULT];
5239         for (i = 0; i < graph->pos; i++) {
5240                 append_to_rev_graph(graph, filler->line);
5241                 if (graph_parent_is_merge(graph->prev) &&
5242                     graph->prev->pos == i)
5243                         filler = &fillers[RSHARP];
5245                 append_to_rev_graph(graph, filler->separator);
5246         }
5248         /* Place the symbol for this revision. */
5249         append_to_rev_graph(graph, symbol);
5251         if (graph->prev->size > graph->size)
5252                 filler = &fillers[RDIAG];
5253         else
5254                 filler = &fillers[DEFAULT];
5256         i++;
5258         for (; i < graph->size; i++) {
5259                 append_to_rev_graph(graph, filler->separator);
5260                 append_to_rev_graph(graph, filler->line);
5261                 if (graph_parent_is_merge(graph->prev) &&
5262                     i < graph->prev->pos + graph->parents->size)
5263                         filler = &fillers[RSHARP];
5264                 if (graph->prev->size > graph->size)
5265                         filler = &fillers[LDIAG];
5266         }
5268         if (graph->prev->size > graph->size) {
5269                 append_to_rev_graph(graph, filler->separator);
5270                 if (filler->line != ' ')
5271                         append_to_rev_graph(graph, filler->line);
5272         }
5275 /* Prepare the next rev graph */
5276 static void
5277 prepare_rev_graph(struct rev_graph *graph)
5279         size_t i;
5281         /* First, traverse all lines of revisions up to the active one. */
5282         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
5283                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
5284                         break;
5286                 push_rev_graph(graph->next, graph->rev[graph->pos]);
5287         }
5289         /* Interleave the new revision parent(s). */
5290         for (i = 0; !graph->boundary && i < graph->parents->size; i++)
5291                 push_rev_graph(graph->next, graph->parents->rev[i]);
5293         /* Lastly, put any remaining revisions. */
5294         for (i = graph->pos + 1; i < graph->size; i++)
5295                 push_rev_graph(graph->next, graph->rev[i]);
5298 static void
5299 update_rev_graph(struct rev_graph *graph)
5301         /* If this is the finalizing update ... */
5302         if (graph->commit)
5303                 prepare_rev_graph(graph);
5305         /* Graph visualization needs a one rev look-ahead,
5306          * so the first update doesn't visualize anything. */
5307         if (!graph->prev->commit)
5308                 return;
5310         draw_rev_graph(graph->prev);
5311         done_rev_graph(graph->prev->prev);
5315 /*
5316  * Main view backend
5317  */
5319 static const char *main_argv[SIZEOF_ARG] = {
5320         "git", "log", "--no-color", "--pretty=raw", "--parents",
5321                       "--topo-order", "%(head)", NULL
5322 };
5324 static bool
5325 main_draw(struct view *view, struct line *line, unsigned int lineno)
5327         struct commit *commit = line->data;
5329         if (!*commit->author)
5330                 return FALSE;
5332         if (opt_date && draw_date(view, &commit->time))
5333                 return TRUE;
5335         if (opt_author &&
5336             draw_field(view, LINE_MAIN_AUTHOR, commit->author, opt_author_cols, TRUE))
5337                 return TRUE;
5339         if (opt_rev_graph && commit->graph_size &&
5340             draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
5341                 return TRUE;
5343         if (opt_show_refs && commit->refs) {
5344                 size_t i = 0;
5346                 do {
5347                         enum line_type type;
5349                         if (commit->refs[i]->head)
5350                                 type = LINE_MAIN_HEAD;
5351                         else if (commit->refs[i]->ltag)
5352                                 type = LINE_MAIN_LOCAL_TAG;
5353                         else if (commit->refs[i]->tag)
5354                                 type = LINE_MAIN_TAG;
5355                         else if (commit->refs[i]->tracked)
5356                                 type = LINE_MAIN_TRACKED;
5357                         else if (commit->refs[i]->remote)
5358                                 type = LINE_MAIN_REMOTE;
5359                         else
5360                                 type = LINE_MAIN_REF;
5362                         if (draw_text(view, type, "[", TRUE) ||
5363                             draw_text(view, type, commit->refs[i]->name, TRUE) ||
5364                             draw_text(view, type, "]", TRUE))
5365                                 return TRUE;
5367                         if (draw_text(view, LINE_DEFAULT, " ", TRUE))
5368                                 return TRUE;
5369                 } while (commit->refs[i++]->next);
5370         }
5372         draw_text(view, LINE_DEFAULT, commit->title, TRUE);
5373         return TRUE;
5376 /* Reads git log --pretty=raw output and parses it into the commit struct. */
5377 static bool
5378 main_read(struct view *view, char *line)
5380         static struct rev_graph *graph = graph_stacks;
5381         enum line_type type;
5382         struct commit *commit;
5384         if (!line) {
5385                 int i;
5387                 if (!view->lines && !view->parent)
5388                         die("No revisions match the given arguments.");
5389                 if (view->lines > 0) {
5390                         commit = view->line[view->lines - 1].data;
5391                         if (!*commit->author) {
5392                                 view->lines--;
5393                                 free(commit);
5394                                 graph->commit = NULL;
5395                         }
5396                 }
5397                 update_rev_graph(graph);
5399                 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
5400                         clear_rev_graph(&graph_stacks[i]);
5401                 return TRUE;
5402         }
5404         type = get_line_type(line);
5405         if (type == LINE_COMMIT) {
5406                 commit = calloc(1, sizeof(struct commit));
5407                 if (!commit)
5408                         return FALSE;
5410                 line += STRING_SIZE("commit ");
5411                 if (*line == '-') {
5412                         graph->boundary = 1;
5413                         line++;
5414                 }
5416                 string_copy_rev(commit->id, line);
5417                 commit->refs = get_refs(commit->id);
5418                 graph->commit = commit;
5419                 add_line_data(view, commit, LINE_MAIN_COMMIT);
5421                 while ((line = strchr(line, ' '))) {
5422                         line++;
5423                         push_rev_graph(graph->parents, line);
5424                         commit->has_parents = TRUE;
5425                 }
5426                 return TRUE;
5427         }
5429         if (!view->lines)
5430                 return TRUE;
5431         commit = view->line[view->lines - 1].data;
5433         switch (type) {
5434         case LINE_PARENT:
5435                 if (commit->has_parents)
5436                         break;
5437                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
5438                 break;
5440         case LINE_AUTHOR:
5441         {
5442                 /* Parse author lines where the name may be empty:
5443                  *      author  <email@address.tld> 1138474660 +0100
5444                  */
5445                 char *ident = line + STRING_SIZE("author ");
5446                 char *nameend = strchr(ident, '<');
5447                 char *emailend = strchr(ident, '>');
5449                 if (!nameend || !emailend)
5450                         break;
5452                 update_rev_graph(graph);
5453                 graph = graph->next;
5455                 *nameend = *emailend = 0;
5456                 ident = chomp_string(ident);
5457                 if (!*ident) {
5458                         ident = chomp_string(nameend + 1);
5459                         if (!*ident)
5460                                 ident = "Unknown";
5461                 }
5463                 string_ncopy(commit->author, ident, strlen(ident));
5465                 /* Parse epoch and timezone */
5466                 if (emailend[1] == ' ') {
5467                         char *secs = emailend + 2;
5468                         char *zone = strchr(secs, ' ');
5469                         time_t time = (time_t) atol(secs);
5471                         if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
5472                                 long tz;
5474                                 zone++;
5475                                 tz  = ('0' - zone[1]) * 60 * 60 * 10;
5476                                 tz += ('0' - zone[2]) * 60 * 60;
5477                                 tz += ('0' - zone[3]) * 60;
5478                                 tz += ('0' - zone[4]) * 60;
5480                                 if (zone[0] == '-')
5481                                         tz = -tz;
5483                                 time -= tz;
5484                         }
5486                         gmtime_r(&time, &commit->time);
5487                 }
5488                 break;
5489         }
5490         default:
5491                 /* Fill in the commit title if it has not already been set. */
5492                 if (commit->title[0])
5493                         break;
5495                 /* Require titles to start with a non-space character at the
5496                  * offset used by git log. */
5497                 if (strncmp(line, "    ", 4))
5498                         break;
5499                 line += 4;
5500                 /* Well, if the title starts with a whitespace character,
5501                  * try to be forgiving.  Otherwise we end up with no title. */
5502                 while (isspace(*line))
5503                         line++;
5504                 if (*line == '\0')
5505                         break;
5506                 /* FIXME: More graceful handling of titles; append "..." to
5507                  * shortened titles, etc. */
5509                 string_ncopy(commit->title, line, strlen(line));
5510         }
5512         return TRUE;
5515 static enum request
5516 main_request(struct view *view, enum request request, struct line *line)
5518         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
5520         switch (request) {
5521         case REQ_ENTER:
5522                 open_view(view, REQ_VIEW_DIFF, flags);
5523                 break;
5524         case REQ_REFRESH:
5525                 load_refs();
5526                 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
5527                 break;
5528         default:
5529                 return request;
5530         }
5532         return REQ_NONE;
5535 static bool
5536 grep_refs(struct ref **refs, regex_t *regex)
5538         regmatch_t pmatch;
5539         size_t i = 0;
5541         if (!refs)
5542                 return FALSE;
5543         do {
5544                 if (regexec(regex, refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
5545                         return TRUE;
5546         } while (refs[i++]->next);
5548         return FALSE;
5551 static bool
5552 main_grep(struct view *view, struct line *line)
5554         struct commit *commit = line->data;
5555         enum { S_TITLE, S_AUTHOR, S_DATE, S_REFS, S_END } state;
5556         char buf[DATE_COLS + 1];
5557         regmatch_t pmatch;
5559         for (state = S_TITLE; state < S_END; state++) {
5560                 char *text;
5562                 switch (state) {
5563                 case S_TITLE:   text = commit->title;   break;
5564                 case S_AUTHOR:
5565                         if (!opt_author)
5566                                 continue;
5567                         text = commit->author;
5568                         break;
5569                 case S_DATE:
5570                         if (!opt_date)
5571                                 continue;
5572                         if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
5573                                 continue;
5574                         text = buf;
5575                         break;
5576                 case S_REFS:
5577                         if (!opt_show_refs)
5578                                 continue;
5579                         if (grep_refs(commit->refs, view->regex) == TRUE)
5580                                 return TRUE;
5581                         continue;
5582                 default:
5583                         return FALSE;
5584                 }
5586                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
5587                         return TRUE;
5588         }
5590         return FALSE;
5593 static void
5594 main_select(struct view *view, struct line *line)
5596         struct commit *commit = line->data;
5598         string_copy_rev(view->ref, commit->id);
5599         string_copy_rev(ref_commit, view->ref);
5602 static struct view_ops main_ops = {
5603         "commit",
5604         main_argv,
5605         NULL,
5606         main_read,
5607         main_draw,
5608         main_request,
5609         main_grep,
5610         main_select,
5611 };
5614 /*
5615  * Unicode / UTF-8 handling
5616  *
5617  * NOTE: Much of the following code for dealing with unicode is derived from
5618  * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
5619  * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
5620  */
5622 /* I've (over)annotated a lot of code snippets because I am not entirely
5623  * confident that the approach taken by this small UTF-8 interface is correct.
5624  * --jonas */
5626 static inline int
5627 unicode_width(unsigned long c)
5629         if (c >= 0x1100 &&
5630            (c <= 0x115f                         /* Hangul Jamo */
5631             || c == 0x2329
5632             || c == 0x232a
5633             || (c >= 0x2e80  && c <= 0xa4cf && c != 0x303f)
5634                                                 /* CJK ... Yi */
5635             || (c >= 0xac00  && c <= 0xd7a3)    /* Hangul Syllables */
5636             || (c >= 0xf900  && c <= 0xfaff)    /* CJK Compatibility Ideographs */
5637             || (c >= 0xfe30  && c <= 0xfe6f)    /* CJK Compatibility Forms */
5638             || (c >= 0xff00  && c <= 0xff60)    /* Fullwidth Forms */
5639             || (c >= 0xffe0  && c <= 0xffe6)
5640             || (c >= 0x20000 && c <= 0x2fffd)
5641             || (c >= 0x30000 && c <= 0x3fffd)))
5642                 return 2;
5644         if (c == '\t')
5645                 return opt_tab_size;
5647         return 1;
5650 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
5651  * Illegal bytes are set one. */
5652 static const unsigned char utf8_bytes[256] = {
5653         1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
5654         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,
5655         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,
5656         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,
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         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,
5660         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,
5661 };
5663 /* Decode UTF-8 multi-byte representation into a unicode character. */
5664 static inline unsigned long
5665 utf8_to_unicode(const char *string, size_t length)
5667         unsigned long unicode;
5669         switch (length) {
5670         case 1:
5671                 unicode  =   string[0];
5672                 break;
5673         case 2:
5674                 unicode  =  (string[0] & 0x1f) << 6;
5675                 unicode +=  (string[1] & 0x3f);
5676                 break;
5677         case 3:
5678                 unicode  =  (string[0] & 0x0f) << 12;
5679                 unicode += ((string[1] & 0x3f) << 6);
5680                 unicode +=  (string[2] & 0x3f);
5681                 break;
5682         case 4:
5683                 unicode  =  (string[0] & 0x0f) << 18;
5684                 unicode += ((string[1] & 0x3f) << 12);
5685                 unicode += ((string[2] & 0x3f) << 6);
5686                 unicode +=  (string[3] & 0x3f);
5687                 break;
5688         case 5:
5689                 unicode  =  (string[0] & 0x0f) << 24;
5690                 unicode += ((string[1] & 0x3f) << 18);
5691                 unicode += ((string[2] & 0x3f) << 12);
5692                 unicode += ((string[3] & 0x3f) << 6);
5693                 unicode +=  (string[4] & 0x3f);
5694                 break;
5695         case 6:
5696                 unicode  =  (string[0] & 0x01) << 30;
5697                 unicode += ((string[1] & 0x3f) << 24);
5698                 unicode += ((string[2] & 0x3f) << 18);
5699                 unicode += ((string[3] & 0x3f) << 12);
5700                 unicode += ((string[4] & 0x3f) << 6);
5701                 unicode +=  (string[5] & 0x3f);
5702                 break;
5703         default:
5704                 die("Invalid unicode length");
5705         }
5707         /* Invalid characters could return the special 0xfffd value but NUL
5708          * should be just as good. */
5709         return unicode > 0xffff ? 0 : unicode;
5712 /* Calculates how much of string can be shown within the given maximum width
5713  * and sets trimmed parameter to non-zero value if all of string could not be
5714  * shown. If the reserve flag is TRUE, it will reserve at least one
5715  * trailing character, which can be useful when drawing a delimiter.
5716  *
5717  * Returns the number of bytes to output from string to satisfy max_width. */
5718 static size_t
5719 utf8_length(const char *string, int *width, size_t max_width, int *trimmed, bool reserve)
5721         const char *start = string;
5722         const char *end = strchr(string, '\0');
5723         unsigned char last_bytes = 0;
5724         size_t last_ucwidth = 0;
5726         *width = 0;
5727         *trimmed = 0;
5729         while (string < end) {
5730                 int c = *(unsigned char *) string;
5731                 unsigned char bytes = utf8_bytes[c];
5732                 size_t ucwidth;
5733                 unsigned long unicode;
5735                 if (string + bytes > end)
5736                         break;
5738                 /* Change representation to figure out whether
5739                  * it is a single- or double-width character. */
5741                 unicode = utf8_to_unicode(string, bytes);
5742                 /* FIXME: Graceful handling of invalid unicode character. */
5743                 if (!unicode)
5744                         break;
5746                 ucwidth = unicode_width(unicode);
5747                 *width  += ucwidth;
5748                 if (*width > max_width) {
5749                         *trimmed = 1;
5750                         *width -= ucwidth;
5751                         if (reserve && *width == max_width) {
5752                                 string -= last_bytes;
5753                                 *width -= last_ucwidth;
5754                         }
5755                         break;
5756                 }
5758                 string  += bytes;
5759                 last_bytes = bytes;
5760                 last_ucwidth = ucwidth;
5761         }
5763         return string - start;
5767 /*
5768  * Status management
5769  */
5771 /* Whether or not the curses interface has been initialized. */
5772 static bool cursed = FALSE;
5774 /* The status window is used for polling keystrokes. */
5775 static WINDOW *status_win;
5777 static bool status_empty = TRUE;
5779 /* Update status and title window. */
5780 static void
5781 report(const char *msg, ...)
5783         struct view *view = display[current_view];
5785         if (input_mode)
5786                 return;
5788         if (!view) {
5789                 char buf[SIZEOF_STR];
5790                 va_list args;
5792                 va_start(args, msg);
5793                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
5794                         buf[sizeof(buf) - 1] = 0;
5795                         buf[sizeof(buf) - 2] = '.';
5796                         buf[sizeof(buf) - 3] = '.';
5797                         buf[sizeof(buf) - 4] = '.';
5798                 }
5799                 va_end(args);
5800                 die("%s", buf);
5801         }
5803         if (!status_empty || *msg) {
5804                 va_list args;
5806                 va_start(args, msg);
5808                 wmove(status_win, 0, 0);
5809                 if (*msg) {
5810                         vwprintw(status_win, msg, args);
5811                         status_empty = FALSE;
5812                 } else {
5813                         status_empty = TRUE;
5814                 }
5815                 wclrtoeol(status_win);
5816                 wrefresh(status_win);
5818                 va_end(args);
5819         }
5821         update_view_title(view);
5822         update_display_cursor(view);
5825 /* Controls when nodelay should be in effect when polling user input. */
5826 static void
5827 set_nonblocking_input(bool loading)
5829         static unsigned int loading_views;
5831         if ((loading == FALSE && loading_views-- == 1) ||
5832             (loading == TRUE  && loading_views++ == 0))
5833                 nodelay(status_win, loading);
5836 static void
5837 init_display(void)
5839         int x, y;
5841         /* Initialize the curses library */
5842         if (isatty(STDIN_FILENO)) {
5843                 cursed = !!initscr();
5844                 opt_tty = stdin;
5845         } else {
5846                 /* Leave stdin and stdout alone when acting as a pager. */
5847                 opt_tty = fopen("/dev/tty", "r+");
5848                 if (!opt_tty)
5849                         die("Failed to open /dev/tty");
5850                 cursed = !!newterm(NULL, opt_tty, opt_tty);
5851         }
5853         if (!cursed)
5854                 die("Failed to initialize curses");
5856         nonl();         /* Tell curses not to do NL->CR/NL on output */
5857         cbreak();       /* Take input chars one at a time, no wait for \n */
5858         noecho();       /* Don't echo input */
5859         leaveok(stdscr, TRUE);
5861         if (has_colors())
5862                 init_colors();
5864         getmaxyx(stdscr, y, x);
5865         status_win = newwin(1, 0, y - 1, 0);
5866         if (!status_win)
5867                 die("Failed to create status window");
5869         /* Enable keyboard mapping */
5870         keypad(status_win, TRUE);
5871         wbkgdset(status_win, get_line_attr(LINE_STATUS));
5873         TABSIZE = opt_tab_size;
5874         if (opt_line_graphics) {
5875                 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
5876         }
5879 static bool
5880 prompt_yesno(const char *prompt)
5882         enum { WAIT, STOP, CANCEL  } status = WAIT;
5883         bool answer = FALSE;
5885         while (status == WAIT) {
5886                 struct view *view;
5887                 int i, key;
5889                 input_mode = TRUE;
5891                 foreach_view (view, i)
5892                         update_view(view);
5894                 input_mode = FALSE;
5896                 mvwprintw(status_win, 0, 0, "%s [Yy]/[Nn]", prompt);
5897                 wclrtoeol(status_win);
5899                 /* Refresh, accept single keystroke of input */
5900                 key = wgetch(status_win);
5901                 switch (key) {
5902                 case ERR:
5903                         break;
5905                 case 'y':
5906                 case 'Y':
5907                         answer = TRUE;
5908                         status = STOP;
5909                         break;
5911                 case KEY_ESC:
5912                 case KEY_RETURN:
5913                 case KEY_ENTER:
5914                 case KEY_BACKSPACE:
5915                 case 'n':
5916                 case 'N':
5917                 case '\n':
5918                 default:
5919                         answer = FALSE;
5920                         status = CANCEL;
5921                 }
5922         }
5924         /* Clear the status window */
5925         status_empty = FALSE;
5926         report("");
5928         return answer;
5931 static char *
5932 read_prompt(const char *prompt)
5934         enum { READING, STOP, CANCEL } status = READING;
5935         static char buf[SIZEOF_STR];
5936         int pos = 0;
5938         while (status == READING) {
5939                 struct view *view;
5940                 int i, key;
5942                 input_mode = TRUE;
5944                 foreach_view (view, i)
5945                         update_view(view);
5947                 input_mode = FALSE;
5949                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
5950                 wclrtoeol(status_win);
5952                 /* Refresh, accept single keystroke of input */
5953                 key = wgetch(status_win);
5954                 switch (key) {
5955                 case KEY_RETURN:
5956                 case KEY_ENTER:
5957                 case '\n':
5958                         status = pos ? STOP : CANCEL;
5959                         break;
5961                 case KEY_BACKSPACE:
5962                         if (pos > 0)
5963                                 pos--;
5964                         else
5965                                 status = CANCEL;
5966                         break;
5968                 case KEY_ESC:
5969                         status = CANCEL;
5970                         break;
5972                 case ERR:
5973                         break;
5975                 default:
5976                         if (pos >= sizeof(buf)) {
5977                                 report("Input string too long");
5978                                 return NULL;
5979                         }
5981                         if (isprint(key))
5982                                 buf[pos++] = (char) key;
5983                 }
5984         }
5986         /* Clear the status window */
5987         status_empty = FALSE;
5988         report("");
5990         if (status == CANCEL)
5991                 return NULL;
5993         buf[pos++] = 0;
5995         return buf;
5998 /*
5999  * Repository references
6000  */
6002 static struct ref *refs = NULL;
6003 static size_t refs_alloc = 0;
6004 static size_t refs_size = 0;
6006 /* Id <-> ref store */
6007 static struct ref ***id_refs = NULL;
6008 static size_t id_refs_alloc = 0;
6009 static size_t id_refs_size = 0;
6011 static int
6012 compare_refs(const void *ref1_, const void *ref2_)
6014         const struct ref *ref1 = *(const struct ref **)ref1_;
6015         const struct ref *ref2 = *(const struct ref **)ref2_;
6017         if (ref1->tag != ref2->tag)
6018                 return ref2->tag - ref1->tag;
6019         if (ref1->ltag != ref2->ltag)
6020                 return ref2->ltag - ref2->ltag;
6021         if (ref1->head != ref2->head)
6022                 return ref2->head - ref1->head;
6023         if (ref1->tracked != ref2->tracked)
6024                 return ref2->tracked - ref1->tracked;
6025         if (ref1->remote != ref2->remote)
6026                 return ref2->remote - ref1->remote;
6027         return strcmp(ref1->name, ref2->name);
6030 static struct ref **
6031 get_refs(const char *id)
6033         struct ref ***tmp_id_refs;
6034         struct ref **ref_list = NULL;
6035         size_t ref_list_alloc = 0;
6036         size_t ref_list_size = 0;
6037         size_t i;
6039         for (i = 0; i < id_refs_size; i++)
6040                 if (!strcmp(id, id_refs[i][0]->id))
6041                         return id_refs[i];
6043         tmp_id_refs = realloc_items(id_refs, &id_refs_alloc, id_refs_size + 1,
6044                                     sizeof(*id_refs));
6045         if (!tmp_id_refs)
6046                 return NULL;
6048         id_refs = tmp_id_refs;
6050         for (i = 0; i < refs_size; i++) {
6051                 struct ref **tmp;
6053                 if (strcmp(id, refs[i].id))
6054                         continue;
6056                 tmp = realloc_items(ref_list, &ref_list_alloc,
6057                                     ref_list_size + 1, sizeof(*ref_list));
6058                 if (!tmp) {
6059                         if (ref_list)
6060                                 free(ref_list);
6061                         return NULL;
6062                 }
6064                 ref_list = tmp;
6065                 ref_list[ref_list_size] = &refs[i];
6066                 /* XXX: The properties of the commit chains ensures that we can
6067                  * safely modify the shared ref. The repo references will
6068                  * always be similar for the same id. */
6069                 ref_list[ref_list_size]->next = 1;
6071                 ref_list_size++;
6072         }
6074         if (ref_list) {
6075                 qsort(ref_list, ref_list_size, sizeof(*ref_list), compare_refs);
6076                 ref_list[ref_list_size - 1]->next = 0;
6077                 id_refs[id_refs_size++] = ref_list;
6078         }
6080         return ref_list;
6083 static int
6084 read_ref(char *id, size_t idlen, char *name, size_t namelen)
6086         struct ref *ref;
6087         bool tag = FALSE;
6088         bool ltag = FALSE;
6089         bool remote = FALSE;
6090         bool tracked = FALSE;
6091         bool check_replace = FALSE;
6092         bool head = FALSE;
6094         if (!prefixcmp(name, "refs/tags/")) {
6095                 if (!suffixcmp(name, namelen, "^{}")) {
6096                         namelen -= 3;
6097                         name[namelen] = 0;
6098                         if (refs_size > 0 && refs[refs_size - 1].ltag == TRUE)
6099                                 check_replace = TRUE;
6100                 } else {
6101                         ltag = TRUE;
6102                 }
6104                 tag = TRUE;
6105                 namelen -= STRING_SIZE("refs/tags/");
6106                 name    += STRING_SIZE("refs/tags/");
6108         } else if (!prefixcmp(name, "refs/remotes/")) {
6109                 remote = TRUE;
6110                 namelen -= STRING_SIZE("refs/remotes/");
6111                 name    += STRING_SIZE("refs/remotes/");
6112                 tracked  = !strcmp(opt_remote, name);
6114         } else if (!prefixcmp(name, "refs/heads/")) {
6115                 namelen -= STRING_SIZE("refs/heads/");
6116                 name    += STRING_SIZE("refs/heads/");
6117                 head     = !strncmp(opt_head, name, namelen);
6119         } else if (!strcmp(name, "HEAD")) {
6120                 string_ncopy(opt_head_rev, id, idlen);
6121                 return OK;
6122         }
6124         if (check_replace && !strcmp(name, refs[refs_size - 1].name)) {
6125                 /* it's an annotated tag, replace the previous sha1 with the
6126                  * resolved commit id; relies on the fact git-ls-remote lists
6127                  * the commit id of an annotated tag right before the commit id
6128                  * it points to. */
6129                 refs[refs_size - 1].ltag = ltag;
6130                 string_copy_rev(refs[refs_size - 1].id, id);
6132                 return OK;
6133         }
6134         refs = realloc_items(refs, &refs_alloc, refs_size + 1, sizeof(*refs));
6135         if (!refs)
6136                 return ERR;
6138         ref = &refs[refs_size++];
6139         ref->name = malloc(namelen + 1);
6140         if (!ref->name)
6141                 return ERR;
6143         strncpy(ref->name, name, namelen);
6144         ref->name[namelen] = 0;
6145         ref->head = head;
6146         ref->tag = tag;
6147         ref->ltag = ltag;
6148         ref->remote = remote;
6149         ref->tracked = tracked;
6150         string_copy_rev(ref->id, id);
6152         return OK;
6155 static int
6156 load_refs(void)
6158         const char *cmd_env = getenv("TIG_LS_REMOTE");
6159         const char *cmd = cmd_env && *cmd_env ? cmd_env : TIG_LS_REMOTE;
6161         if (!*opt_git_dir)
6162                 return OK;
6164         while (refs_size > 0)
6165                 free(refs[--refs_size].name);
6166         while (id_refs_size > 0)
6167                 free(id_refs[--id_refs_size]);
6169         return read_properties(popen(cmd, "r"), "\t", read_ref);
6172 static int
6173 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
6175         if (!strcmp(name, "i18n.commitencoding"))
6176                 string_ncopy(opt_encoding, value, valuelen);
6178         if (!strcmp(name, "core.editor"))
6179                 string_ncopy(opt_editor, value, valuelen);
6181         /* branch.<head>.remote */
6182         if (*opt_head &&
6183             !strncmp(name, "branch.", 7) &&
6184             !strncmp(name + 7, opt_head, strlen(opt_head)) &&
6185             !strcmp(name + 7 + strlen(opt_head), ".remote"))
6186                 string_ncopy(opt_remote, value, valuelen);
6188         if (*opt_head && *opt_remote &&
6189             !strncmp(name, "branch.", 7) &&
6190             !strncmp(name + 7, opt_head, strlen(opt_head)) &&
6191             !strcmp(name + 7 + strlen(opt_head), ".merge")) {
6192                 size_t from = strlen(opt_remote);
6194                 if (!prefixcmp(value, "refs/heads/")) {
6195                         value += STRING_SIZE("refs/heads/");
6196                         valuelen -= STRING_SIZE("refs/heads/");
6197                 }
6199                 if (!string_format_from(opt_remote, &from, "/%s", value))
6200                         opt_remote[0] = 0;
6201         }
6203         return OK;
6206 static int
6207 load_git_config(void)
6209         return read_properties(popen("git " GIT_CONFIG " --list", "r"),
6210                                "=", read_repo_config_option);
6213 static int
6214 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
6216         if (!opt_git_dir[0]) {
6217                 string_ncopy(opt_git_dir, name, namelen);
6219         } else if (opt_is_inside_work_tree == -1) {
6220                 /* This can be 3 different values depending on the
6221                  * version of git being used. If git-rev-parse does not
6222                  * understand --is-inside-work-tree it will simply echo
6223                  * the option else either "true" or "false" is printed.
6224                  * Default to true for the unknown case. */
6225                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
6227         } else if (opt_cdup[0] == ' ') {
6228                 string_ncopy(opt_cdup, name, namelen);
6229         } else {
6230                 if (!prefixcmp(name, "refs/heads/")) {
6231                         namelen -= STRING_SIZE("refs/heads/");
6232                         name    += STRING_SIZE("refs/heads/");
6233                         string_ncopy(opt_head, name, namelen);
6234                 }
6235         }
6237         return OK;
6240 static int
6241 load_repo_info(void)
6243         int result;
6244         FILE *pipe = popen("(git rev-parse --git-dir --is-inside-work-tree "
6245                            " --show-cdup; git symbolic-ref HEAD) 2>/dev/null", "r");
6247         /* XXX: The line outputted by "--show-cdup" can be empty so
6248          * initialize it to something invalid to make it possible to
6249          * detect whether it has been set or not. */
6250         opt_cdup[0] = ' ';
6252         result = read_properties(pipe, "=", read_repo_info);
6253         if (opt_cdup[0] == ' ')
6254                 opt_cdup[0] = 0;
6256         return result;
6259 static int
6260 read_properties(FILE *pipe, const char *separators,
6261                 int (*read_property)(char *, size_t, char *, size_t))
6263         char buffer[BUFSIZ];
6264         char *name;
6265         int state = OK;
6267         if (!pipe)
6268                 return ERR;
6270         while (state == OK && (name = fgets(buffer, sizeof(buffer), pipe))) {
6271                 char *value;
6272                 size_t namelen;
6273                 size_t valuelen;
6275                 name = chomp_string(name);
6276                 namelen = strcspn(name, separators);
6278                 if (name[namelen]) {
6279                         name[namelen] = 0;
6280                         value = chomp_string(name + namelen + 1);
6281                         valuelen = strlen(value);
6283                 } else {
6284                         value = "";
6285                         valuelen = 0;
6286                 }
6288                 state = read_property(name, namelen, value, valuelen);
6289         }
6291         if (state != ERR && ferror(pipe))
6292                 state = ERR;
6294         pclose(pipe);
6296         return state;
6300 /*
6301  * Main
6302  */
6304 static void __NORETURN
6305 quit(int sig)
6307         /* XXX: Restore tty modes and let the OS cleanup the rest! */
6308         if (cursed)
6309                 endwin();
6310         exit(0);
6313 static void __NORETURN
6314 die(const char *err, ...)
6316         va_list args;
6318         endwin();
6320         va_start(args, err);
6321         fputs("tig: ", stderr);
6322         vfprintf(stderr, err, args);
6323         fputs("\n", stderr);
6324         va_end(args);
6326         exit(1);
6329 static void
6330 warn(const char *msg, ...)
6332         va_list args;
6334         va_start(args, msg);
6335         fputs("tig warning: ", stderr);
6336         vfprintf(stderr, msg, args);
6337         fputs("\n", stderr);
6338         va_end(args);
6341 int
6342 main(int argc, const char *argv[])
6344         struct view *view;
6345         enum request request;
6346         size_t i;
6348         signal(SIGINT, quit);
6350         if (setlocale(LC_ALL, "")) {
6351                 char *codeset = nl_langinfo(CODESET);
6353                 string_ncopy(opt_codeset, codeset, strlen(codeset));
6354         }
6356         if (load_repo_info() == ERR)
6357                 die("Failed to load repo info.");
6359         if (load_options() == ERR)
6360                 die("Failed to load user config.");
6362         if (load_git_config() == ERR)
6363                 die("Failed to load repo config.");
6365         request = parse_options(argc, argv);
6366         if (request == REQ_NONE)
6367                 return 0;
6369         /* Require a git repository unless when running in pager mode. */
6370         if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
6371                 die("Not a git repository");
6373         if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
6374                 opt_utf8 = FALSE;
6376         if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
6377                 opt_iconv = iconv_open(opt_codeset, opt_encoding);
6378                 if (opt_iconv == ICONV_NONE)
6379                         die("Failed to initialize character set conversion");
6380         }
6382         if (load_refs() == ERR)
6383                 die("Failed to load refs.");
6385         foreach_view (view, i)
6386                 argv_from_env(view->ops->argv, view->cmd_env);
6388         init_display();
6390         while (view_driver(display[current_view], request)) {
6391                 int key;
6392                 int i;
6394                 foreach_view (view, i)
6395                         update_view(view);
6396                 view = display[current_view];
6398                 /* Refresh, accept single keystroke of input */
6399                 key = wgetch(status_win);
6401                 /* wgetch() with nodelay() enabled returns ERR when there's no
6402                  * input. */
6403                 if (key == ERR) {
6404                         request = REQ_NONE;
6405                         continue;
6406                 }
6408                 request = get_keybinding(view->keymap, key);
6410                 /* Some low-level request handling. This keeps access to
6411                  * status_win restricted. */
6412                 switch (request) {
6413                 case REQ_PROMPT:
6414                 {
6415                         char *cmd = read_prompt(":");
6417                         if (cmd) {
6418                                 struct view *next = VIEW(REQ_VIEW_PAGER);
6419                                 const char *argv[SIZEOF_ARG] = { "git" };
6420                                 int argc = 1;
6422                                 /* When running random commands, initially show the
6423                                  * command in the title. However, it maybe later be
6424                                  * overwritten if a commit line is selected. */
6425                                 string_ncopy(next->ref, cmd, strlen(cmd));
6427                                 if (!argv_from_string(argv, &argc, cmd)) {
6428                                         report("Too many arguments");
6429                                 } else if (!prepare_update(next, argv, NULL, FORMAT_DASH)) {
6430                                         report("Failed to format command");
6431                                 } else {
6432                                         open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
6433                                 }
6434                         }
6436                         request = REQ_NONE;
6437                         break;
6438                 }
6439                 case REQ_SEARCH:
6440                 case REQ_SEARCH_BACK:
6441                 {
6442                         const char *prompt = request == REQ_SEARCH ? "/" : "?";
6443                         char *search = read_prompt(prompt);
6445                         if (search)
6446                                 string_ncopy(opt_search, search, strlen(search));
6447                         else
6448                                 request = REQ_NONE;
6449                         break;
6450                 }
6451                 case REQ_SCREEN_RESIZE:
6452                 {
6453                         int height, width;
6455                         getmaxyx(stdscr, height, width);
6457                         /* Resize the status view and let the view driver take
6458                          * care of resizing the displayed views. */
6459                         wresize(status_win, 1, width);
6460                         mvwin(status_win, height - 1, 0);
6461                         wrefresh(status_win);
6462                         break;
6463                 }
6464                 default:
6465                         break;
6466                 }
6467         }
6469         quit(0);
6471         return 0;