Code

Add blame view
[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 #include <curses.h>
50 #if __GNUC__ >= 3
51 #define __NORETURN __attribute__((__noreturn__))
52 #else
53 #define __NORETURN
54 #endif
56 static void __NORETURN die(const char *err, ...);
57 static void warn(const char *msg, ...);
58 static void report(const char *msg, ...);
59 static int read_properties(FILE *pipe, const char *separators, int (*read)(char *, size_t, char *, size_t));
60 static void set_nonblocking_input(bool loading);
61 static size_t utf8_length(const char *string, size_t max_width, int *trimmed, bool reserve);
63 #define ABS(x)          ((x) >= 0  ? (x) : -(x))
64 #define MIN(x, y)       ((x) < (y) ? (x) :  (y))
66 #define ARRAY_SIZE(x)   (sizeof(x) / sizeof(x[0]))
67 #define STRING_SIZE(x)  (sizeof(x) - 1)
69 #define SIZEOF_STR      1024    /* Default string size. */
70 #define SIZEOF_REF      256     /* Size of symbolic or SHA1 ID. */
71 #define SIZEOF_REV      41      /* Holds a SHA-1 and an ending NUL */
73 /* Revision graph */
75 #define REVGRAPH_INIT   'I'
76 #define REVGRAPH_MERGE  'M'
77 #define REVGRAPH_BRANCH '+'
78 #define REVGRAPH_COMMIT '*'
79 #define REVGRAPH_BOUND  '^'
80 #define REVGRAPH_LINE   '|'
82 #define SIZEOF_REVGRAPH 19      /* Size of revision ancestry graphics. */
84 /* This color name can be used to refer to the default term colors. */
85 #define COLOR_DEFAULT   (-1)
87 #define ICONV_NONE      ((iconv_t) -1)
88 #ifndef ICONV_CONST
89 #define ICONV_CONST     /* nothing */
90 #endif
92 /* The format and size of the date column in the main view. */
93 #define DATE_FORMAT     "%Y-%m-%d %H:%M"
94 #define DATE_COLS       STRING_SIZE("2006-04-29 14:21 ")
96 #define AUTHOR_COLS     20
97 #define ID_COLS         8
99 /* The default interval between line numbers. */
100 #define NUMBER_INTERVAL 5
102 #define TABSIZE         8
104 #define SCALE_SPLIT_VIEW(height)        ((height) * 2 / 3)
106 #ifndef GIT_CONFIG
107 #define GIT_CONFIG "git config"
108 #endif
110 #define TIG_LS_REMOTE \
111         "git ls-remote $(git rev-parse --git-dir) 2>/dev/null"
113 #define TIG_DIFF_CMD \
114         "git show --pretty=fuller --no-color --root --patch-with-stat --find-copies-harder -C %s 2>/dev/null"
116 #define TIG_LOG_CMD     \
117         "git log --no-color --cc --stat -n100 %s 2>/dev/null"
119 #define TIG_MAIN_CMD \
120         "git log --no-color --topo-order --parents --boundary --pretty=raw %s 2>/dev/null"
122 #define TIG_TREE_CMD    \
123         "git ls-tree %s %s"
125 #define TIG_BLOB_CMD    \
126         "git cat-file blob %s"
128 /* XXX: Needs to be defined to the empty string. */
129 #define TIG_HELP_CMD    ""
130 #define TIG_PAGER_CMD   ""
131 #define TIG_STATUS_CMD  ""
132 #define TIG_STAGE_CMD   ""
133 #define TIG_BLAME_CMD   ""
135 /* Some ascii-shorthands fitted into the ncurses namespace. */
136 #define KEY_TAB         '\t'
137 #define KEY_RETURN      '\r'
138 #define KEY_ESC         27
141 struct ref {
142         char *name;             /* Ref name; tag or head names are shortened. */
143         char id[SIZEOF_REV];    /* Commit SHA1 ID */
144         unsigned int tag:1;     /* Is it a tag? */
145         unsigned int ltag:1;    /* If so, is the tag local? */
146         unsigned int remote:1;  /* Is it a remote ref? */
147         unsigned int next:1;    /* For ref lists: are there more refs? */
148 };
150 static struct ref **get_refs(char *id);
152 struct int_map {
153         const char *name;
154         int namelen;
155         int value;
156 };
158 static int
159 set_from_int_map(struct int_map *map, size_t map_size,
160                  int *value, const char *name, int namelen)
163         int i;
165         for (i = 0; i < map_size; i++)
166                 if (namelen == map[i].namelen &&
167                     !strncasecmp(name, map[i].name, namelen)) {
168                         *value = map[i].value;
169                         return OK;
170                 }
172         return ERR;
176 /*
177  * String helpers
178  */
180 static inline void
181 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
183         if (srclen > dstlen - 1)
184                 srclen = dstlen - 1;
186         strncpy(dst, src, srclen);
187         dst[srclen] = 0;
190 /* Shorthands for safely copying into a fixed buffer. */
192 #define string_copy(dst, src) \
193         string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
195 #define string_ncopy(dst, src, srclen) \
196         string_ncopy_do(dst, sizeof(dst), src, srclen)
198 #define string_copy_rev(dst, src) \
199         string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
201 #define string_add(dst, from, src) \
202         string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
204 static char *
205 chomp_string(char *name)
207         int namelen;
209         while (isspace(*name))
210                 name++;
212         namelen = strlen(name) - 1;
213         while (namelen > 0 && isspace(name[namelen]))
214                 name[namelen--] = 0;
216         return name;
219 static bool
220 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
222         va_list args;
223         size_t pos = bufpos ? *bufpos : 0;
225         va_start(args, fmt);
226         pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
227         va_end(args);
229         if (bufpos)
230                 *bufpos = pos;
232         return pos >= bufsize ? FALSE : TRUE;
235 #define string_format(buf, fmt, args...) \
236         string_nformat(buf, sizeof(buf), NULL, fmt, args)
238 #define string_format_from(buf, from, fmt, args...) \
239         string_nformat(buf, sizeof(buf), from, fmt, args)
241 static int
242 string_enum_compare(const char *str1, const char *str2, int len)
244         size_t i;
246 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
248         /* Diff-Header == DIFF_HEADER */
249         for (i = 0; i < len; i++) {
250                 if (toupper(str1[i]) == toupper(str2[i]))
251                         continue;
253                 if (string_enum_sep(str1[i]) &&
254                     string_enum_sep(str2[i]))
255                         continue;
257                 return str1[i] - str2[i];
258         }
260         return 0;
263 /* Shell quoting
264  *
265  * NOTE: The following is a slightly modified copy of the git project's shell
266  * quoting routines found in the quote.c file.
267  *
268  * Help to copy the thing properly quoted for the shell safety.  any single
269  * quote is replaced with '\'', any exclamation point is replaced with '\!',
270  * and the whole thing is enclosed in a
271  *
272  * E.g.
273  *  original     sq_quote     result
274  *  name     ==> name      ==> 'name'
275  *  a b      ==> a b       ==> 'a b'
276  *  a'b      ==> a'\''b    ==> 'a'\''b'
277  *  a!b      ==> a'\!'b    ==> 'a'\!'b'
278  */
280 static size_t
281 sq_quote(char buf[SIZEOF_STR], size_t bufsize, const char *src)
283         char c;
285 #define BUFPUT(x) do { if (bufsize < SIZEOF_STR) buf[bufsize++] = (x); } while (0)
287         BUFPUT('\'');
288         while ((c = *src++)) {
289                 if (c == '\'' || c == '!') {
290                         BUFPUT('\'');
291                         BUFPUT('\\');
292                         BUFPUT(c);
293                         BUFPUT('\'');
294                 } else {
295                         BUFPUT(c);
296                 }
297         }
298         BUFPUT('\'');
300         if (bufsize < SIZEOF_STR)
301                 buf[bufsize] = 0;
303         return bufsize;
307 /*
308  * User requests
309  */
311 #define REQ_INFO \
312         /* XXX: Keep the view request first and in sync with views[]. */ \
313         REQ_GROUP("View switching") \
314         REQ_(VIEW_MAIN,         "Show main view"), \
315         REQ_(VIEW_DIFF,         "Show diff view"), \
316         REQ_(VIEW_LOG,          "Show log view"), \
317         REQ_(VIEW_TREE,         "Show tree view"), \
318         REQ_(VIEW_BLOB,         "Show blob view"), \
319         REQ_(VIEW_BLAME,        "Show blame view"), \
320         REQ_(VIEW_HELP,         "Show help page"), \
321         REQ_(VIEW_PAGER,        "Show pager view"), \
322         REQ_(VIEW_STATUS,       "Show status view"), \
323         REQ_(VIEW_STAGE,        "Show stage view"), \
324         \
325         REQ_GROUP("View manipulation") \
326         REQ_(ENTER,             "Enter current line and scroll"), \
327         REQ_(NEXT,              "Move to next"), \
328         REQ_(PREVIOUS,          "Move to previous"), \
329         REQ_(VIEW_NEXT,         "Move focus to next view"), \
330         REQ_(REFRESH,           "Reload and refresh"), \
331         REQ_(VIEW_CLOSE,        "Close the current view"), \
332         REQ_(QUIT,              "Close all views and quit"), \
333         \
334         REQ_GROUP("Cursor navigation") \
335         REQ_(MOVE_UP,           "Move cursor one line up"), \
336         REQ_(MOVE_DOWN,         "Move cursor one line down"), \
337         REQ_(MOVE_PAGE_DOWN,    "Move cursor one page down"), \
338         REQ_(MOVE_PAGE_UP,      "Move cursor one page up"), \
339         REQ_(MOVE_FIRST_LINE,   "Move cursor to first line"), \
340         REQ_(MOVE_LAST_LINE,    "Move cursor to last line"), \
341         \
342         REQ_GROUP("Scrolling") \
343         REQ_(SCROLL_LINE_UP,    "Scroll one line up"), \
344         REQ_(SCROLL_LINE_DOWN,  "Scroll one line down"), \
345         REQ_(SCROLL_PAGE_UP,    "Scroll one page up"), \
346         REQ_(SCROLL_PAGE_DOWN,  "Scroll one page down"), \
347         \
348         REQ_GROUP("Searching") \
349         REQ_(SEARCH,            "Search the view"), \
350         REQ_(SEARCH_BACK,       "Search backwards in the view"), \
351         REQ_(FIND_NEXT,         "Find next search match"), \
352         REQ_(FIND_PREV,         "Find previous search match"), \
353         \
354         REQ_GROUP("Misc") \
355         REQ_(PROMPT,            "Bring up the prompt"), \
356         REQ_(SCREEN_REDRAW,     "Redraw the screen"), \
357         REQ_(SCREEN_RESIZE,     "Resize the screen"), \
358         REQ_(SHOW_VERSION,      "Show version information"), \
359         REQ_(STOP_LOADING,      "Stop all loading views"), \
360         REQ_(TOGGLE_LINENO,     "Toggle line numbers"), \
361         REQ_(TOGGLE_DATE,       "Toggle date display"), \
362         REQ_(TOGGLE_AUTHOR,     "Toggle author display"), \
363         REQ_(TOGGLE_REV_GRAPH,  "Toggle revision graph visualization"), \
364         REQ_(TOGGLE_REFS,       "Toggle reference display (tags/branches)"), \
365         REQ_(STATUS_UPDATE,     "Update file status"), \
366         REQ_(STATUS_MERGE,      "Merge file using external tool"), \
367         REQ_(TREE_PARENT,       "Switch to parent directory in tree view"), \
368         REQ_(EDIT,              "Open in editor"), \
369         REQ_(NONE,              "Do nothing")
372 /* User action requests. */
373 enum request {
374 #define REQ_GROUP(help)
375 #define REQ_(req, help) REQ_##req
377         /* Offset all requests to avoid conflicts with ncurses getch values. */
378         REQ_OFFSET = KEY_MAX + 1,
379         REQ_INFO
381 #undef  REQ_GROUP
382 #undef  REQ_
383 };
385 struct request_info {
386         enum request request;
387         char *name;
388         int namelen;
389         char *help;
390 };
392 static struct request_info req_info[] = {
393 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
394 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
395         REQ_INFO
396 #undef  REQ_GROUP
397 #undef  REQ_
398 };
400 static enum request
401 get_request(const char *name)
403         int namelen = strlen(name);
404         int i;
406         for (i = 0; i < ARRAY_SIZE(req_info); i++)
407                 if (req_info[i].namelen == namelen &&
408                     !string_enum_compare(req_info[i].name, name, namelen))
409                         return req_info[i].request;
411         return REQ_NONE;
415 /*
416  * Options
417  */
419 static const char usage[] =
420 "tig " TIG_VERSION " (" __DATE__ ")\n"
421 "\n"
422 "Usage: tig        [options] [revs] [--] [paths]\n"
423 "   or: tig show   [options] [revs] [--] [paths]\n"
424 "   or: tig blame  [rev] path\n"
425 "   or: tig status\n"
426 "   or: tig <      [git command output]\n"
427 "\n"
428 "Options:\n"
429 "  -v, --version   Show version and exit\n"
430 "  -h, --help      Show help message and exit";
432 /* Option and state variables. */
433 static bool opt_date                    = TRUE;
434 static bool opt_author                  = TRUE;
435 static bool opt_line_number             = FALSE;
436 static bool opt_rev_graph               = FALSE;
437 static bool opt_show_refs               = TRUE;
438 static int opt_num_interval             = NUMBER_INTERVAL;
439 static int opt_tab_size                 = TABSIZE;
440 static enum request opt_request         = REQ_VIEW_MAIN;
441 static char opt_cmd[SIZEOF_STR]         = "";
442 static char opt_path[SIZEOF_STR]        = "";
443 static char opt_ref[SIZEOF_REF]         = "";
444 static FILE *opt_pipe                   = NULL;
445 static char opt_encoding[20]            = "UTF-8";
446 static bool opt_utf8                    = TRUE;
447 static char opt_codeset[20]             = "UTF-8";
448 static iconv_t opt_iconv                = ICONV_NONE;
449 static char opt_search[SIZEOF_STR]      = "";
450 static char opt_cdup[SIZEOF_STR]        = "";
451 static char opt_git_dir[SIZEOF_STR]     = "";
452 static signed char opt_is_inside_work_tree      = -1; /* set to TRUE or FALSE */
453 static char opt_editor[SIZEOF_STR]      = "";
455 enum option_type {
456         OPT_NONE,
457         OPT_INT,
458 };
460 static bool
461 check_option(char *opt, char short_name, char *name, enum option_type type, ...)
463         va_list args;
464         char *value = "";
465         int *number;
467         if (opt[0] != '-')
468                 return FALSE;
470         if (opt[1] == '-') {
471                 int namelen = strlen(name);
473                 opt += 2;
475                 if (strncmp(opt, name, namelen))
476                         return FALSE;
478                 if (opt[namelen] == '=')
479                         value = opt + namelen + 1;
481         } else {
482                 if (!short_name || opt[1] != short_name)
483                         return FALSE;
484                 value = opt + 2;
485         }
487         va_start(args, type);
488         if (type == OPT_INT) {
489                 number = va_arg(args, int *);
490                 if (isdigit(*value))
491                         *number = atoi(value);
492         }
493         va_end(args);
495         return TRUE;
498 /* Returns the index of log or diff command or -1 to exit. */
499 static bool
500 parse_options(int argc, char *argv[])
502         char *altargv[1024];
503         int altargc = 0;
504         char *subcommand;
505         int i;
507         if (argc <= 1)
508                 return TRUE;
510         subcommand = argv[1];
511         if (!strcmp(subcommand, "status")) {
512                 opt_request = REQ_VIEW_STATUS;
513                 if (argc > 2)
514                         warn("ignoring arguments after `%s'", subcommand);
515                 return TRUE;
517         } else if (!strcmp(subcommand, "blame")) {
518                 opt_request = REQ_VIEW_BLAME;
519                 if (argc <= 2 || argc > 4)
520                         die("invalid number of options to blame\n\n%s", usage);
522                 i = 2;
523                 if (argc == 4) {
524                         string_ncopy(opt_ref, argv[i], strlen(argv[i]));
525                         i++;
526                 }
528                 string_ncopy(opt_path, argv[i], strlen(argv[i]));
529                 return TRUE;
531         } else if (!strcmp(subcommand, "show")) {
532                 opt_request = REQ_VIEW_DIFF;
534         } else if (!strcmp(subcommand, "log") || !strcmp(subcommand, "diff")) {
535                 opt_request = subcommand[0] == 'l'
536                             ? REQ_VIEW_LOG : REQ_VIEW_DIFF;
537                 warn("`tig %s' has been deprecated", subcommand);
539         } else {
540                 subcommand = NULL;
541         }
543         for (i = 1 + !!subcommand; i < argc; i++) {
544                 char *opt = argv[i];
546                 if (opt[0] && opt[0] != '-')
547                         break;
549                 if (!strcmp(opt, "--")) {
550                         i++;
551                         break;
552                 }
554                 if (check_option(opt, 'v', "version", OPT_NONE)) {
555                         printf("tig version %s\n", TIG_VERSION);
556                         return FALSE;
557                 }
559                 if (check_option(opt, 'h', "help", OPT_NONE)) {
560                         printf(usage);
561                         return FALSE;
562                 }
564                 if (!strcmp(opt, "-S")) {
565                         warn("`%s' has been deprecated; use `tig status' instead", opt);
566                         opt_request = REQ_VIEW_STATUS;
567                         continue;
568                 }
570                 if (!strcmp(opt, "-l")) {
571                         opt_request = REQ_VIEW_LOG;
572                 } else if (!strcmp(opt, "-d")) {
573                         opt_request = REQ_VIEW_DIFF;
574                 } else if (check_option(opt, 'n', "line-number", OPT_INT, &opt_num_interval)) {
575                         opt_line_number = TRUE;
576                 } else if (check_option(opt, 'b', "tab-size", OPT_INT, &opt_tab_size)) {
577                         opt_tab_size = MIN(opt_tab_size, TABSIZE);
578                 } else {
579                         if (altargc >= ARRAY_SIZE(altargv))
580                                 die("maximum number of arguments exceeded");
581                         altargv[altargc++] = opt;
582                         continue;
583                 }
585                 warn("`%s' has been deprecated", opt);
586         }
588         if (!isatty(STDIN_FILENO)) {
589                 opt_request = REQ_VIEW_PAGER;
590                 opt_pipe = stdin;
592         } else if (i < argc || altargc > 0) {
593                 int alti = 0;
594                 size_t buf_size;
596                 if (opt_request == REQ_VIEW_MAIN)
597                         /* XXX: This is vulnerable to the user overriding
598                          * options required for the main view parser. */
599                         string_copy(opt_cmd, "git log --no-color --pretty=raw --boundary --parents");
600                 else
601                         string_format(opt_cmd, "git %s", subcommand);
602                 buf_size = strlen(opt_cmd);
604                 while (buf_size < sizeof(opt_cmd) && alti < altargc) {
605                         opt_cmd[buf_size++] = ' ';
606                         buf_size = sq_quote(opt_cmd, buf_size, altargv[alti++]);
607                 }
609                 while (buf_size < sizeof(opt_cmd) && i < argc) {
610                         opt_cmd[buf_size++] = ' ';
611                         buf_size = sq_quote(opt_cmd, buf_size, argv[i++]);
612                 }
614                 if (buf_size >= sizeof(opt_cmd))
615                         die("command too long");
617                 opt_cmd[buf_size] = 0;
618         }
620         return TRUE;
624 /*
625  * Line-oriented content detection.
626  */
628 #define LINE_INFO \
629 LINE(DIFF_HEADER,  "diff --git ",       COLOR_YELLOW,   COLOR_DEFAULT,  0), \
630 LINE(DIFF_CHUNK,   "@@",                COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
631 LINE(DIFF_ADD,     "+",                 COLOR_GREEN,    COLOR_DEFAULT,  0), \
632 LINE(DIFF_DEL,     "-",                 COLOR_RED,      COLOR_DEFAULT,  0), \
633 LINE(DIFF_INDEX,        "index ",         COLOR_BLUE,   COLOR_DEFAULT,  0), \
634 LINE(DIFF_OLDMODE,      "old file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
635 LINE(DIFF_NEWMODE,      "new file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
636 LINE(DIFF_COPY_FROM,    "copy from",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
637 LINE(DIFF_COPY_TO,      "copy to",        COLOR_YELLOW, COLOR_DEFAULT,  0), \
638 LINE(DIFF_RENAME_FROM,  "rename from",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
639 LINE(DIFF_RENAME_TO,    "rename to",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
640 LINE(DIFF_SIMILARITY,   "similarity ",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
641 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
642 LINE(DIFF_TREE,         "diff-tree ",     COLOR_BLUE,   COLOR_DEFAULT,  0), \
643 LINE(PP_AUTHOR,    "Author: ",          COLOR_CYAN,     COLOR_DEFAULT,  0), \
644 LINE(PP_COMMIT,    "Commit: ",          COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
645 LINE(PP_MERGE,     "Merge: ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
646 LINE(PP_DATE,      "Date:   ",          COLOR_YELLOW,   COLOR_DEFAULT,  0), \
647 LINE(PP_ADATE,     "AuthorDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
648 LINE(PP_CDATE,     "CommitDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
649 LINE(PP_REFS,      "Refs: ",            COLOR_RED,      COLOR_DEFAULT,  0), \
650 LINE(COMMIT,       "commit ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
651 LINE(PARENT,       "parent ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
652 LINE(TREE,         "tree ",             COLOR_BLUE,     COLOR_DEFAULT,  0), \
653 LINE(AUTHOR,       "author ",           COLOR_CYAN,     COLOR_DEFAULT,  0), \
654 LINE(COMMITTER,    "committer ",        COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
655 LINE(SIGNOFF,      "    Signed-off-by", COLOR_YELLOW,   COLOR_DEFAULT,  0), \
656 LINE(ACKED,        "    Acked-by",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
657 LINE(DEFAULT,      "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
658 LINE(CURSOR,       "",                  COLOR_WHITE,    COLOR_GREEN,    A_BOLD), \
659 LINE(STATUS,       "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
660 LINE(TITLE_BLUR,   "",                  COLOR_WHITE,    COLOR_BLUE,     0), \
661 LINE(TITLE_FOCUS,  "",                  COLOR_WHITE,    COLOR_BLUE,     A_BOLD), \
662 LINE(MAIN_DATE,    "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
663 LINE(MAIN_AUTHOR,  "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
664 LINE(MAIN_COMMIT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
665 LINE(MAIN_DELIM,   "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
666 LINE(MAIN_TAG,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
667 LINE(MAIN_LOCAL_TAG,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
668 LINE(MAIN_REMOTE,  "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_BOLD), \
669 LINE(MAIN_REF,     "",                  COLOR_CYAN,     COLOR_DEFAULT,  A_BOLD), \
670 LINE(MAIN_REVGRAPH,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
671 LINE(TREE_DIR,     "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
672 LINE(TREE_FILE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
673 LINE(STAT_SECTION, "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
674 LINE(STAT_NONE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
675 LINE(STAT_STAGED,  "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
676 LINE(STAT_UNSTAGED,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
677 LINE(STAT_UNTRACKED,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
678 LINE(BLAME_DATE,    "",                 COLOR_BLUE,     COLOR_DEFAULT,  0), \
679 LINE(BLAME_AUTHOR,  "",                 COLOR_GREEN,    COLOR_DEFAULT,  0), \
680 LINE(BLAME_COMMIT, "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
681 LINE(BLAME_ID,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
682 LINE(BLAME_LINENO, "",                  COLOR_CYAN,     COLOR_DEFAULT,  0)
684 enum line_type {
685 #define LINE(type, line, fg, bg, attr) \
686         LINE_##type
687         LINE_INFO
688 #undef  LINE
689 };
691 struct line_info {
692         const char *name;       /* Option name. */
693         int namelen;            /* Size of option name. */
694         const char *line;       /* The start of line to match. */
695         int linelen;            /* Size of string to match. */
696         int fg, bg, attr;       /* Color and text attributes for the lines. */
697 };
699 static struct line_info line_info[] = {
700 #define LINE(type, line, fg, bg, attr) \
701         { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
702         LINE_INFO
703 #undef  LINE
704 };
706 static enum line_type
707 get_line_type(char *line)
709         int linelen = strlen(line);
710         enum line_type type;
712         for (type = 0; type < ARRAY_SIZE(line_info); type++)
713                 /* Case insensitive search matches Signed-off-by lines better. */
714                 if (linelen >= line_info[type].linelen &&
715                     !strncasecmp(line_info[type].line, line, line_info[type].linelen))
716                         return type;
718         return LINE_DEFAULT;
721 static inline int
722 get_line_attr(enum line_type type)
724         assert(type < ARRAY_SIZE(line_info));
725         return COLOR_PAIR(type) | line_info[type].attr;
728 static struct line_info *
729 get_line_info(char *name, int namelen)
731         enum line_type type;
733         for (type = 0; type < ARRAY_SIZE(line_info); type++)
734                 if (namelen == line_info[type].namelen &&
735                     !string_enum_compare(line_info[type].name, name, namelen))
736                         return &line_info[type];
738         return NULL;
741 static void
742 init_colors(void)
744         int default_bg = line_info[LINE_DEFAULT].bg;
745         int default_fg = line_info[LINE_DEFAULT].fg;
746         enum line_type type;
748         start_color();
750         if (assume_default_colors(default_fg, default_bg) == ERR) {
751                 default_bg = COLOR_BLACK;
752                 default_fg = COLOR_WHITE;
753         }
755         for (type = 0; type < ARRAY_SIZE(line_info); type++) {
756                 struct line_info *info = &line_info[type];
757                 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
758                 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
760                 init_pair(type, fg, bg);
761         }
764 struct line {
765         enum line_type type;
767         /* State flags */
768         unsigned int selected:1;
769         unsigned int dirty:1;
771         void *data;             /* User data */
772 };
775 /*
776  * Keys
777  */
779 struct keybinding {
780         int alias;
781         enum request request;
782         struct keybinding *next;
783 };
785 static struct keybinding default_keybindings[] = {
786         /* View switching */
787         { 'm',          REQ_VIEW_MAIN },
788         { 'd',          REQ_VIEW_DIFF },
789         { 'l',          REQ_VIEW_LOG },
790         { 't',          REQ_VIEW_TREE },
791         { 'f',          REQ_VIEW_BLOB },
792         { 'B',          REQ_VIEW_BLAME },
793         { 'p',          REQ_VIEW_PAGER },
794         { 'h',          REQ_VIEW_HELP },
795         { 'S',          REQ_VIEW_STATUS },
796         { 'c',          REQ_VIEW_STAGE },
798         /* View manipulation */
799         { 'q',          REQ_VIEW_CLOSE },
800         { KEY_TAB,      REQ_VIEW_NEXT },
801         { KEY_RETURN,   REQ_ENTER },
802         { KEY_UP,       REQ_PREVIOUS },
803         { KEY_DOWN,     REQ_NEXT },
804         { 'R',          REQ_REFRESH },
806         /* Cursor navigation */
807         { 'k',          REQ_MOVE_UP },
808         { 'j',          REQ_MOVE_DOWN },
809         { KEY_HOME,     REQ_MOVE_FIRST_LINE },
810         { KEY_END,      REQ_MOVE_LAST_LINE },
811         { KEY_NPAGE,    REQ_MOVE_PAGE_DOWN },
812         { ' ',          REQ_MOVE_PAGE_DOWN },
813         { KEY_PPAGE,    REQ_MOVE_PAGE_UP },
814         { 'b',          REQ_MOVE_PAGE_UP },
815         { '-',          REQ_MOVE_PAGE_UP },
817         /* Scrolling */
818         { KEY_IC,       REQ_SCROLL_LINE_UP },
819         { KEY_DC,       REQ_SCROLL_LINE_DOWN },
820         { 'w',          REQ_SCROLL_PAGE_UP },
821         { 's',          REQ_SCROLL_PAGE_DOWN },
823         /* Searching */
824         { '/',          REQ_SEARCH },
825         { '?',          REQ_SEARCH_BACK },
826         { 'n',          REQ_FIND_NEXT },
827         { 'N',          REQ_FIND_PREV },
829         /* Misc */
830         { 'Q',          REQ_QUIT },
831         { 'z',          REQ_STOP_LOADING },
832         { 'v',          REQ_SHOW_VERSION },
833         { 'r',          REQ_SCREEN_REDRAW },
834         { '.',          REQ_TOGGLE_LINENO },
835         { 'D',          REQ_TOGGLE_DATE },
836         { 'A',          REQ_TOGGLE_AUTHOR },
837         { 'g',          REQ_TOGGLE_REV_GRAPH },
838         { 'F',          REQ_TOGGLE_REFS },
839         { ':',          REQ_PROMPT },
840         { 'u',          REQ_STATUS_UPDATE },
841         { 'M',          REQ_STATUS_MERGE },
842         { ',',          REQ_TREE_PARENT },
843         { 'e',          REQ_EDIT },
845         /* Using the ncurses SIGWINCH handler. */
846         { KEY_RESIZE,   REQ_SCREEN_RESIZE },
847 };
849 #define KEYMAP_INFO \
850         KEYMAP_(GENERIC), \
851         KEYMAP_(MAIN), \
852         KEYMAP_(DIFF), \
853         KEYMAP_(LOG), \
854         KEYMAP_(TREE), \
855         KEYMAP_(BLOB), \
856         KEYMAP_(BLAME), \
857         KEYMAP_(PAGER), \
858         KEYMAP_(HELP), \
859         KEYMAP_(STATUS), \
860         KEYMAP_(STAGE)
862 enum keymap {
863 #define KEYMAP_(name) KEYMAP_##name
864         KEYMAP_INFO
865 #undef  KEYMAP_
866 };
868 static struct int_map keymap_table[] = {
869 #define KEYMAP_(name) { #name, STRING_SIZE(#name), KEYMAP_##name }
870         KEYMAP_INFO
871 #undef  KEYMAP_
872 };
874 #define set_keymap(map, name) \
875         set_from_int_map(keymap_table, ARRAY_SIZE(keymap_table), map, name, strlen(name))
877 static struct keybinding *keybindings[ARRAY_SIZE(keymap_table)];
879 static void
880 add_keybinding(enum keymap keymap, enum request request, int key)
882         struct keybinding *keybinding;
884         keybinding = calloc(1, sizeof(*keybinding));
885         if (!keybinding)
886                 die("Failed to allocate keybinding");
888         keybinding->alias = key;
889         keybinding->request = request;
890         keybinding->next = keybindings[keymap];
891         keybindings[keymap] = keybinding;
894 /* Looks for a key binding first in the given map, then in the generic map, and
895  * lastly in the default keybindings. */
896 static enum request
897 get_keybinding(enum keymap keymap, int key)
899         struct keybinding *kbd;
900         int i;
902         for (kbd = keybindings[keymap]; kbd; kbd = kbd->next)
903                 if (kbd->alias == key)
904                         return kbd->request;
906         for (kbd = keybindings[KEYMAP_GENERIC]; kbd; kbd = kbd->next)
907                 if (kbd->alias == key)
908                         return kbd->request;
910         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
911                 if (default_keybindings[i].alias == key)
912                         return default_keybindings[i].request;
914         return (enum request) key;
918 struct key {
919         char *name;
920         int value;
921 };
923 static struct key key_table[] = {
924         { "Enter",      KEY_RETURN },
925         { "Space",      ' ' },
926         { "Backspace",  KEY_BACKSPACE },
927         { "Tab",        KEY_TAB },
928         { "Escape",     KEY_ESC },
929         { "Left",       KEY_LEFT },
930         { "Right",      KEY_RIGHT },
931         { "Up",         KEY_UP },
932         { "Down",       KEY_DOWN },
933         { "Insert",     KEY_IC },
934         { "Delete",     KEY_DC },
935         { "Hash",       '#' },
936         { "Home",       KEY_HOME },
937         { "End",        KEY_END },
938         { "PageUp",     KEY_PPAGE },
939         { "PageDown",   KEY_NPAGE },
940         { "F1",         KEY_F(1) },
941         { "F2",         KEY_F(2) },
942         { "F3",         KEY_F(3) },
943         { "F4",         KEY_F(4) },
944         { "F5",         KEY_F(5) },
945         { "F6",         KEY_F(6) },
946         { "F7",         KEY_F(7) },
947         { "F8",         KEY_F(8) },
948         { "F9",         KEY_F(9) },
949         { "F10",        KEY_F(10) },
950         { "F11",        KEY_F(11) },
951         { "F12",        KEY_F(12) },
952 };
954 static int
955 get_key_value(const char *name)
957         int i;
959         for (i = 0; i < ARRAY_SIZE(key_table); i++)
960                 if (!strcasecmp(key_table[i].name, name))
961                         return key_table[i].value;
963         if (strlen(name) == 1 && isprint(*name))
964                 return (int) *name;
966         return ERR;
969 static char *
970 get_key_name(int key_value)
972         static char key_char[] = "'X'";
973         char *seq = NULL;
974         int key;
976         for (key = 0; key < ARRAY_SIZE(key_table); key++)
977                 if (key_table[key].value == key_value)
978                         seq = key_table[key].name;
980         if (seq == NULL &&
981             key_value < 127 &&
982             isprint(key_value)) {
983                 key_char[1] = (char) key_value;
984                 seq = key_char;
985         }
987         return seq ? seq : "'?'";
990 static char *
991 get_key(enum request request)
993         static char buf[BUFSIZ];
994         size_t pos = 0;
995         char *sep = "";
996         int i;
998         buf[pos] = 0;
1000         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1001                 struct keybinding *keybinding = &default_keybindings[i];
1003                 if (keybinding->request != request)
1004                         continue;
1006                 if (!string_format_from(buf, &pos, "%s%s", sep,
1007                                         get_key_name(keybinding->alias)))
1008                         return "Too many keybindings!";
1009                 sep = ", ";
1010         }
1012         return buf;
1015 struct run_request {
1016         enum keymap keymap;
1017         int key;
1018         char cmd[SIZEOF_STR];
1019 };
1021 static struct run_request *run_request;
1022 static size_t run_requests;
1024 static enum request
1025 add_run_request(enum keymap keymap, int key, int argc, char **argv)
1027         struct run_request *tmp;
1028         struct run_request req = { keymap, key };
1029         size_t bufpos;
1031         for (bufpos = 0; argc > 0; argc--, argv++)
1032                 if (!string_format_from(req.cmd, &bufpos, "%s ", *argv))
1033                         return REQ_NONE;
1035         req.cmd[bufpos - 1] = 0;
1037         tmp = realloc(run_request, (run_requests + 1) * sizeof(*run_request));
1038         if (!tmp)
1039                 return REQ_NONE;
1041         run_request = tmp;
1042         run_request[run_requests++] = req;
1044         return REQ_NONE + run_requests;
1047 static struct run_request *
1048 get_run_request(enum request request)
1050         if (request <= REQ_NONE)
1051                 return NULL;
1052         return &run_request[request - REQ_NONE - 1];
1055 static void
1056 add_builtin_run_requests(void)
1058         struct {
1059                 enum keymap keymap;
1060                 int key;
1061                 char *argv[1];
1062         } reqs[] = {
1063                 { KEYMAP_MAIN,    'C', { "git cherry-pick %(commit)" } },
1064                 { KEYMAP_GENERIC, 'G', { "git gc" } },
1065         };
1066         int i;
1068         for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1069                 enum request req;
1071                 req = add_run_request(reqs[i].keymap, reqs[i].key, 1, reqs[i].argv);
1072                 if (req != REQ_NONE)
1073                         add_keybinding(reqs[i].keymap, req, reqs[i].key);
1074         }
1077 /*
1078  * User config file handling.
1079  */
1081 static struct int_map color_map[] = {
1082 #define COLOR_MAP(name) { #name, STRING_SIZE(#name), COLOR_##name }
1083         COLOR_MAP(DEFAULT),
1084         COLOR_MAP(BLACK),
1085         COLOR_MAP(BLUE),
1086         COLOR_MAP(CYAN),
1087         COLOR_MAP(GREEN),
1088         COLOR_MAP(MAGENTA),
1089         COLOR_MAP(RED),
1090         COLOR_MAP(WHITE),
1091         COLOR_MAP(YELLOW),
1092 };
1094 #define set_color(color, name) \
1095         set_from_int_map(color_map, ARRAY_SIZE(color_map), color, name, strlen(name))
1097 static struct int_map attr_map[] = {
1098 #define ATTR_MAP(name) { #name, STRING_SIZE(#name), A_##name }
1099         ATTR_MAP(NORMAL),
1100         ATTR_MAP(BLINK),
1101         ATTR_MAP(BOLD),
1102         ATTR_MAP(DIM),
1103         ATTR_MAP(REVERSE),
1104         ATTR_MAP(STANDOUT),
1105         ATTR_MAP(UNDERLINE),
1106 };
1108 #define set_attribute(attr, name) \
1109         set_from_int_map(attr_map, ARRAY_SIZE(attr_map), attr, name, strlen(name))
1111 static int   config_lineno;
1112 static bool  config_errors;
1113 static char *config_msg;
1115 /* Wants: object fgcolor bgcolor [attr] */
1116 static int
1117 option_color_command(int argc, char *argv[])
1119         struct line_info *info;
1121         if (argc != 3 && argc != 4) {
1122                 config_msg = "Wrong number of arguments given to color command";
1123                 return ERR;
1124         }
1126         info = get_line_info(argv[0], strlen(argv[0]));
1127         if (!info) {
1128                 config_msg = "Unknown color name";
1129                 return ERR;
1130         }
1132         if (set_color(&info->fg, argv[1]) == ERR ||
1133             set_color(&info->bg, argv[2]) == ERR) {
1134                 config_msg = "Unknown color";
1135                 return ERR;
1136         }
1138         if (argc == 4 && set_attribute(&info->attr, argv[3]) == ERR) {
1139                 config_msg = "Unknown attribute";
1140                 return ERR;
1141         }
1143         return OK;
1146 static bool parse_bool(const char *s)
1148         return (!strcmp(s, "1") || !strcmp(s, "true") ||
1149                 !strcmp(s, "yes")) ? TRUE : FALSE;
1152 /* Wants: name = value */
1153 static int
1154 option_set_command(int argc, char *argv[])
1156         if (argc != 3) {
1157                 config_msg = "Wrong number of arguments given to set command";
1158                 return ERR;
1159         }
1161         if (strcmp(argv[1], "=")) {
1162                 config_msg = "No value assigned";
1163                 return ERR;
1164         }
1166         if (!strcmp(argv[0], "show-author")) {
1167                 opt_author = parse_bool(argv[2]);
1168                 return OK;
1169         }
1171         if (!strcmp(argv[0], "show-date")) {
1172                 opt_date = parse_bool(argv[2]);
1173                 return OK;
1174         }
1176         if (!strcmp(argv[0], "show-rev-graph")) {
1177                 opt_rev_graph = parse_bool(argv[2]);
1178                 return OK;
1179         }
1181         if (!strcmp(argv[0], "show-refs")) {
1182                 opt_show_refs = parse_bool(argv[2]);
1183                 return OK;
1184         }
1186         if (!strcmp(argv[0], "show-line-numbers")) {
1187                 opt_line_number = parse_bool(argv[2]);
1188                 return OK;
1189         }
1191         if (!strcmp(argv[0], "line-number-interval")) {
1192                 opt_num_interval = atoi(argv[2]);
1193                 return OK;
1194         }
1196         if (!strcmp(argv[0], "tab-size")) {
1197                 opt_tab_size = atoi(argv[2]);
1198                 return OK;
1199         }
1201         if (!strcmp(argv[0], "commit-encoding")) {
1202                 char *arg = argv[2];
1203                 int delimiter = *arg;
1204                 int i;
1206                 switch (delimiter) {
1207                 case '"':
1208                 case '\'':
1209                         for (arg++, i = 0; arg[i]; i++)
1210                                 if (arg[i] == delimiter) {
1211                                         arg[i] = 0;
1212                                         break;
1213                                 }
1214                 default:
1215                         string_ncopy(opt_encoding, arg, strlen(arg));
1216                         return OK;
1217                 }
1218         }
1220         config_msg = "Unknown variable name";
1221         return ERR;
1224 /* Wants: mode request key */
1225 static int
1226 option_bind_command(int argc, char *argv[])
1228         enum request request;
1229         int keymap;
1230         int key;
1232         if (argc < 3) {
1233                 config_msg = "Wrong number of arguments given to bind command";
1234                 return ERR;
1235         }
1237         if (set_keymap(&keymap, argv[0]) == ERR) {
1238                 config_msg = "Unknown key map";
1239                 return ERR;
1240         }
1242         key = get_key_value(argv[1]);
1243         if (key == ERR) {
1244                 config_msg = "Unknown key";
1245                 return ERR;
1246         }
1248         request = get_request(argv[2]);
1249         if (request == REQ_NONE) {
1250                 const char *obsolete[] = { "cherry-pick" };
1251                 size_t namelen = strlen(argv[2]);
1252                 int i;
1254                 for (i = 0; i < ARRAY_SIZE(obsolete); i++) {
1255                         if (namelen == strlen(obsolete[i]) &&
1256                             !string_enum_compare(obsolete[i], argv[2], namelen)) {
1257                                 config_msg = "Obsolete request name";
1258                                 return ERR;
1259                         }
1260                 }
1261         }
1262         if (request == REQ_NONE && *argv[2]++ == '!')
1263                 request = add_run_request(keymap, key, argc - 2, argv + 2);
1264         if (request == REQ_NONE) {
1265                 config_msg = "Unknown request name";
1266                 return ERR;
1267         }
1269         add_keybinding(keymap, request, key);
1271         return OK;
1274 static int
1275 set_option(char *opt, char *value)
1277         char *argv[16];
1278         int valuelen;
1279         int argc = 0;
1281         /* Tokenize */
1282         while (argc < ARRAY_SIZE(argv) && (valuelen = strcspn(value, " \t"))) {
1283                 argv[argc++] = value;
1284                 value += valuelen;
1286                 /* Nothing more to tokenize or last available token. */
1287                 if (!*value || argc >= ARRAY_SIZE(argv))
1288                         break;
1290                 *value++ = 0;
1291                 while (isspace(*value))
1292                         value++;
1293         }
1295         if (!strcmp(opt, "color"))
1296                 return option_color_command(argc, argv);
1298         if (!strcmp(opt, "set"))
1299                 return option_set_command(argc, argv);
1301         if (!strcmp(opt, "bind"))
1302                 return option_bind_command(argc, argv);
1304         config_msg = "Unknown option command";
1305         return ERR;
1308 static int
1309 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1311         int status = OK;
1313         config_lineno++;
1314         config_msg = "Internal error";
1316         /* Check for comment markers, since read_properties() will
1317          * only ensure opt and value are split at first " \t". */
1318         optlen = strcspn(opt, "#");
1319         if (optlen == 0)
1320                 return OK;
1322         if (opt[optlen] != 0) {
1323                 config_msg = "No option value";
1324                 status = ERR;
1326         }  else {
1327                 /* Look for comment endings in the value. */
1328                 size_t len = strcspn(value, "#");
1330                 if (len < valuelen) {
1331                         valuelen = len;
1332                         value[valuelen] = 0;
1333                 }
1335                 status = set_option(opt, value);
1336         }
1338         if (status == ERR) {
1339                 fprintf(stderr, "Error on line %d, near '%.*s': %s\n",
1340                         config_lineno, (int) optlen, opt, config_msg);
1341                 config_errors = TRUE;
1342         }
1344         /* Always keep going if errors are encountered. */
1345         return OK;
1348 static void
1349 load_option_file(const char *path)
1351         FILE *file;
1353         /* It's ok that the file doesn't exist. */
1354         file = fopen(path, "r");
1355         if (!file)
1356                 return;
1358         config_lineno = 0;
1359         config_errors = FALSE;
1361         if (read_properties(file, " \t", read_option) == ERR ||
1362             config_errors == TRUE)
1363                 fprintf(stderr, "Errors while loading %s.\n", path);
1366 static int
1367 load_options(void)
1369         char *home = getenv("HOME");
1370         char *tigrc_user = getenv("TIGRC_USER");
1371         char *tigrc_system = getenv("TIGRC_SYSTEM");
1372         char buf[SIZEOF_STR];
1374         add_builtin_run_requests();
1376         if (!tigrc_system) {
1377                 if (!string_format(buf, "%s/tigrc", SYSCONFDIR))
1378                         return ERR;
1379                 tigrc_system = buf;
1380         }
1381         load_option_file(tigrc_system);
1383         if (!tigrc_user) {
1384                 if (!home || !string_format(buf, "%s/.tigrc", home))
1385                         return ERR;
1386                 tigrc_user = buf;
1387         }
1388         load_option_file(tigrc_user);
1390         return OK;
1394 /*
1395  * The viewer
1396  */
1398 struct view;
1399 struct view_ops;
1401 /* The display array of active views and the index of the current view. */
1402 static struct view *display[2];
1403 static unsigned int current_view;
1405 /* Reading from the prompt? */
1406 static bool input_mode = FALSE;
1408 #define foreach_displayed_view(view, i) \
1409         for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1411 #define displayed_views()       (display[1] != NULL ? 2 : 1)
1413 /* Current head and commit ID */
1414 static char ref_blob[SIZEOF_REF]        = "";
1415 static char ref_commit[SIZEOF_REF]      = "HEAD";
1416 static char ref_head[SIZEOF_REF]        = "HEAD";
1418 struct view {
1419         const char *name;       /* View name */
1420         const char *cmd_fmt;    /* Default command line format */
1421         const char *cmd_env;    /* Command line set via environment */
1422         const char *id;         /* Points to either of ref_{head,commit,blob} */
1424         struct view_ops *ops;   /* View operations */
1426         enum keymap keymap;     /* What keymap does this view have */
1428         char cmd[SIZEOF_STR];   /* Command buffer */
1429         char ref[SIZEOF_REF];   /* Hovered commit reference */
1430         char vid[SIZEOF_REF];   /* View ID. Set to id member when updating. */
1432         int height, width;      /* The width and height of the main window */
1433         WINDOW *win;            /* The main window */
1434         WINDOW *title;          /* The title window living below the main window */
1436         /* Navigation */
1437         unsigned long offset;   /* Offset of the window top */
1438         unsigned long lineno;   /* Current line number */
1440         /* Searching */
1441         char grep[SIZEOF_STR];  /* Search string */
1442         regex_t *regex;         /* Pre-compiled regex */
1444         /* If non-NULL, points to the view that opened this view. If this view
1445          * is closed tig will switch back to the parent view. */
1446         struct view *parent;
1448         /* Buffering */
1449         size_t lines;           /* Total number of lines */
1450         struct line *line;      /* Line index */
1451         size_t line_alloc;      /* Total number of allocated lines */
1452         size_t line_size;       /* Total number of used lines */
1453         unsigned int digits;    /* Number of digits in the lines member. */
1455         /* Loading */
1456         FILE *pipe;
1457         time_t start_time;
1458 };
1460 struct view_ops {
1461         /* What type of content being displayed. Used in the title bar. */
1462         const char *type;
1463         /* Open and reads in all view content. */
1464         bool (*open)(struct view *view);
1465         /* Read one line; updates view->line. */
1466         bool (*read)(struct view *view, char *data);
1467         /* Draw one line; @lineno must be < view->height. */
1468         bool (*draw)(struct view *view, struct line *line, unsigned int lineno, bool selected);
1469         /* Depending on view handle a special requests. */
1470         enum request (*request)(struct view *view, enum request request, struct line *line);
1471         /* Search for regex in a line. */
1472         bool (*grep)(struct view *view, struct line *line);
1473         /* Select line */
1474         void (*select)(struct view *view, struct line *line);
1475 };
1477 static struct view_ops pager_ops;
1478 static struct view_ops main_ops;
1479 static struct view_ops tree_ops;
1480 static struct view_ops blob_ops;
1481 static struct view_ops blame_ops;
1482 static struct view_ops help_ops;
1483 static struct view_ops status_ops;
1484 static struct view_ops stage_ops;
1486 #define VIEW_STR(name, cmd, env, ref, ops, map) \
1487         { name, cmd, #env, ref, ops, map}
1489 #define VIEW_(id, name, ops, ref) \
1490         VIEW_STR(name, TIG_##id##_CMD,  TIG_##id##_CMD, ref, ops, KEYMAP_##id)
1493 static struct view views[] = {
1494         VIEW_(MAIN,   "main",   &main_ops,   ref_head),
1495         VIEW_(DIFF,   "diff",   &pager_ops,  ref_commit),
1496         VIEW_(LOG,    "log",    &pager_ops,  ref_head),
1497         VIEW_(TREE,   "tree",   &tree_ops,   ref_commit),
1498         VIEW_(BLOB,   "blob",   &blob_ops,   ref_blob),
1499         VIEW_(BLAME,  "blame",  &blame_ops,  ref_commit),
1500         VIEW_(HELP,   "help",   &help_ops,   ""),
1501         VIEW_(PAGER,  "pager",  &pager_ops,  "stdin"),
1502         VIEW_(STATUS, "status", &status_ops, ""),
1503         VIEW_(STAGE,  "stage",  &stage_ops,  ""),
1504 };
1506 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
1508 #define foreach_view(view, i) \
1509         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1511 #define view_is_displayed(view) \
1512         (view == display[0] || view == display[1])
1514 static int
1515 draw_text(struct view *view, const char *string, int max_len,
1516           bool use_tilde, int tilde_attr)
1518         int len = 0;
1519         int trimmed = FALSE;
1521         if (max_len <= 0)
1522                 return 0;
1524         if (opt_utf8) {
1525                 len = utf8_length(string, max_len, &trimmed, use_tilde);
1526         } else {
1527                 len = strlen(string);
1528                 if (len > max_len) {
1529                         if (use_tilde) {
1530                                 max_len -= 1;
1531                         }
1532                         len = max_len;
1533                         trimmed = TRUE;
1534                 }
1535         }
1537         waddnstr(view->win, string, len);
1538         if (trimmed && use_tilde) {
1539                 if (tilde_attr != -1)
1540                         wattrset(view->win, tilde_attr);
1541                 waddch(view->win, '~');
1542                 len++;
1543         }
1545         return len;
1548 static bool
1549 draw_view_line(struct view *view, unsigned int lineno)
1551         struct line *line;
1552         bool selected = (view->offset + lineno == view->lineno);
1553         bool draw_ok;
1555         assert(view_is_displayed(view));
1557         if (view->offset + lineno >= view->lines)
1558                 return FALSE;
1560         line = &view->line[view->offset + lineno];
1562         if (selected) {
1563                 line->selected = TRUE;
1564                 view->ops->select(view, line);
1565         } else if (line->selected) {
1566                 line->selected = FALSE;
1567                 wmove(view->win, lineno, 0);
1568                 wclrtoeol(view->win);
1569         }
1571         scrollok(view->win, FALSE);
1572         draw_ok = view->ops->draw(view, line, lineno, selected);
1573         scrollok(view->win, TRUE);
1575         return draw_ok;
1578 static void
1579 redraw_view_dirty(struct view *view)
1581         bool dirty = FALSE;
1582         int lineno;
1584         for (lineno = 0; lineno < view->height; lineno++) {
1585                 struct line *line = &view->line[view->offset + lineno];
1587                 if (!line->dirty)
1588                         continue;
1589                 line->dirty = 0;
1590                 dirty = TRUE;
1591                 if (!draw_view_line(view, lineno))
1592                         break;
1593         }
1595         if (!dirty)
1596                 return;
1597         redrawwin(view->win);
1598         if (input_mode)
1599                 wnoutrefresh(view->win);
1600         else
1601                 wrefresh(view->win);
1604 static void
1605 redraw_view_from(struct view *view, int lineno)
1607         assert(0 <= lineno && lineno < view->height);
1609         for (; lineno < view->height; lineno++) {
1610                 if (!draw_view_line(view, lineno))
1611                         break;
1612         }
1614         redrawwin(view->win);
1615         if (input_mode)
1616                 wnoutrefresh(view->win);
1617         else
1618                 wrefresh(view->win);
1621 static void
1622 redraw_view(struct view *view)
1624         wclear(view->win);
1625         redraw_view_from(view, 0);
1629 static void
1630 update_view_title(struct view *view)
1632         char buf[SIZEOF_STR];
1633         char state[SIZEOF_STR];
1634         size_t bufpos = 0, statelen = 0;
1636         assert(view_is_displayed(view));
1638         if (view != VIEW(REQ_VIEW_STATUS) && (view->lines || view->pipe)) {
1639                 unsigned int view_lines = view->offset + view->height;
1640                 unsigned int lines = view->lines
1641                                    ? MIN(view_lines, view->lines) * 100 / view->lines
1642                                    : 0;
1644                 string_format_from(state, &statelen, "- %s %d of %d (%d%%)",
1645                                    view->ops->type,
1646                                    view->lineno + 1,
1647                                    view->lines,
1648                                    lines);
1650                 if (view->pipe) {
1651                         time_t secs = time(NULL) - view->start_time;
1653                         /* Three git seconds are a long time ... */
1654                         if (secs > 2)
1655                                 string_format_from(state, &statelen, " %lds", secs);
1656                 }
1657         }
1659         string_format_from(buf, &bufpos, "[%s]", view->name);
1660         if (*view->ref && bufpos < view->width) {
1661                 size_t refsize = strlen(view->ref);
1662                 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
1664                 if (minsize < view->width)
1665                         refsize = view->width - minsize + 7;
1666                 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
1667         }
1669         if (statelen && bufpos < view->width) {
1670                 string_format_from(buf, &bufpos, " %s", state);
1671         }
1673         if (view == display[current_view])
1674                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
1675         else
1676                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
1678         mvwaddnstr(view->title, 0, 0, buf, bufpos);
1679         wclrtoeol(view->title);
1680         wmove(view->title, 0, view->width - 1);
1682         if (input_mode)
1683                 wnoutrefresh(view->title);
1684         else
1685                 wrefresh(view->title);
1688 static void
1689 resize_display(void)
1691         int offset, i;
1692         struct view *base = display[0];
1693         struct view *view = display[1] ? display[1] : display[0];
1695         /* Setup window dimensions */
1697         getmaxyx(stdscr, base->height, base->width);
1699         /* Make room for the status window. */
1700         base->height -= 1;
1702         if (view != base) {
1703                 /* Horizontal split. */
1704                 view->width   = base->width;
1705                 view->height  = SCALE_SPLIT_VIEW(base->height);
1706                 base->height -= view->height;
1708                 /* Make room for the title bar. */
1709                 view->height -= 1;
1710         }
1712         /* Make room for the title bar. */
1713         base->height -= 1;
1715         offset = 0;
1717         foreach_displayed_view (view, i) {
1718                 if (!view->win) {
1719                         view->win = newwin(view->height, 0, offset, 0);
1720                         if (!view->win)
1721                                 die("Failed to create %s view", view->name);
1723                         scrollok(view->win, TRUE);
1725                         view->title = newwin(1, 0, offset + view->height, 0);
1726                         if (!view->title)
1727                                 die("Failed to create title window");
1729                 } else {
1730                         wresize(view->win, view->height, view->width);
1731                         mvwin(view->win,   offset, 0);
1732                         mvwin(view->title, offset + view->height, 0);
1733                 }
1735                 offset += view->height + 1;
1736         }
1739 static void
1740 redraw_display(void)
1742         struct view *view;
1743         int i;
1745         foreach_displayed_view (view, i) {
1746                 redraw_view(view);
1747                 update_view_title(view);
1748         }
1751 static void
1752 update_display_cursor(struct view *view)
1754         /* Move the cursor to the right-most column of the cursor line.
1755          *
1756          * XXX: This could turn out to be a bit expensive, but it ensures that
1757          * the cursor does not jump around. */
1758         if (view->lines) {
1759                 wmove(view->win, view->lineno - view->offset, view->width - 1);
1760                 wrefresh(view->win);
1761         }
1764 /*
1765  * Navigation
1766  */
1768 /* Scrolling backend */
1769 static void
1770 do_scroll_view(struct view *view, int lines)
1772         bool redraw_current_line = FALSE;
1774         /* The rendering expects the new offset. */
1775         view->offset += lines;
1777         assert(0 <= view->offset && view->offset < view->lines);
1778         assert(lines);
1780         /* Move current line into the view. */
1781         if (view->lineno < view->offset) {
1782                 view->lineno = view->offset;
1783                 redraw_current_line = TRUE;
1784         } else if (view->lineno >= view->offset + view->height) {
1785                 view->lineno = view->offset + view->height - 1;
1786                 redraw_current_line = TRUE;
1787         }
1789         assert(view->offset <= view->lineno && view->lineno < view->lines);
1791         /* Redraw the whole screen if scrolling is pointless. */
1792         if (view->height < ABS(lines)) {
1793                 redraw_view(view);
1795         } else {
1796                 int line = lines > 0 ? view->height - lines : 0;
1797                 int end = line + ABS(lines);
1799                 wscrl(view->win, lines);
1801                 for (; line < end; line++) {
1802                         if (!draw_view_line(view, line))
1803                                 break;
1804                 }
1806                 if (redraw_current_line)
1807                         draw_view_line(view, view->lineno - view->offset);
1808         }
1810         redrawwin(view->win);
1811         wrefresh(view->win);
1812         report("");
1815 /* Scroll frontend */
1816 static void
1817 scroll_view(struct view *view, enum request request)
1819         int lines = 1;
1821         assert(view_is_displayed(view));
1823         switch (request) {
1824         case REQ_SCROLL_PAGE_DOWN:
1825                 lines = view->height;
1826         case REQ_SCROLL_LINE_DOWN:
1827                 if (view->offset + lines > view->lines)
1828                         lines = view->lines - view->offset;
1830                 if (lines == 0 || view->offset + view->height >= view->lines) {
1831                         report("Cannot scroll beyond the last line");
1832                         return;
1833                 }
1834                 break;
1836         case REQ_SCROLL_PAGE_UP:
1837                 lines = view->height;
1838         case REQ_SCROLL_LINE_UP:
1839                 if (lines > view->offset)
1840                         lines = view->offset;
1842                 if (lines == 0) {
1843                         report("Cannot scroll beyond the first line");
1844                         return;
1845                 }
1847                 lines = -lines;
1848                 break;
1850         default:
1851                 die("request %d not handled in switch", request);
1852         }
1854         do_scroll_view(view, lines);
1857 /* Cursor moving */
1858 static void
1859 move_view(struct view *view, enum request request)
1861         int scroll_steps = 0;
1862         int steps;
1864         switch (request) {
1865         case REQ_MOVE_FIRST_LINE:
1866                 steps = -view->lineno;
1867                 break;
1869         case REQ_MOVE_LAST_LINE:
1870                 steps = view->lines - view->lineno - 1;
1871                 break;
1873         case REQ_MOVE_PAGE_UP:
1874                 steps = view->height > view->lineno
1875                       ? -view->lineno : -view->height;
1876                 break;
1878         case REQ_MOVE_PAGE_DOWN:
1879                 steps = view->lineno + view->height >= view->lines
1880                       ? view->lines - view->lineno - 1 : view->height;
1881                 break;
1883         case REQ_MOVE_UP:
1884                 steps = -1;
1885                 break;
1887         case REQ_MOVE_DOWN:
1888                 steps = 1;
1889                 break;
1891         default:
1892                 die("request %d not handled in switch", request);
1893         }
1895         if (steps <= 0 && view->lineno == 0) {
1896                 report("Cannot move beyond the first line");
1897                 return;
1899         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
1900                 report("Cannot move beyond the last line");
1901                 return;
1902         }
1904         /* Move the current line */
1905         view->lineno += steps;
1906         assert(0 <= view->lineno && view->lineno < view->lines);
1908         /* Check whether the view needs to be scrolled */
1909         if (view->lineno < view->offset ||
1910             view->lineno >= view->offset + view->height) {
1911                 scroll_steps = steps;
1912                 if (steps < 0 && -steps > view->offset) {
1913                         scroll_steps = -view->offset;
1915                 } else if (steps > 0) {
1916                         if (view->lineno == view->lines - 1 &&
1917                             view->lines > view->height) {
1918                                 scroll_steps = view->lines - view->offset - 1;
1919                                 if (scroll_steps >= view->height)
1920                                         scroll_steps -= view->height - 1;
1921                         }
1922                 }
1923         }
1925         if (!view_is_displayed(view)) {
1926                 view->offset += scroll_steps;
1927                 assert(0 <= view->offset && view->offset < view->lines);
1928                 view->ops->select(view, &view->line[view->lineno]);
1929                 return;
1930         }
1932         /* Repaint the old "current" line if we be scrolling */
1933         if (ABS(steps) < view->height)
1934                 draw_view_line(view, view->lineno - steps - view->offset);
1936         if (scroll_steps) {
1937                 do_scroll_view(view, scroll_steps);
1938                 return;
1939         }
1941         /* Draw the current line */
1942         draw_view_line(view, view->lineno - view->offset);
1944         redrawwin(view->win);
1945         wrefresh(view->win);
1946         report("");
1950 /*
1951  * Searching
1952  */
1954 static void search_view(struct view *view, enum request request);
1956 static bool
1957 find_next_line(struct view *view, unsigned long lineno, struct line *line)
1959         assert(view_is_displayed(view));
1961         if (!view->ops->grep(view, line))
1962                 return FALSE;
1964         if (lineno - view->offset >= view->height) {
1965                 view->offset = lineno;
1966                 view->lineno = lineno;
1967                 redraw_view(view);
1969         } else {
1970                 unsigned long old_lineno = view->lineno - view->offset;
1972                 view->lineno = lineno;
1973                 draw_view_line(view, old_lineno);
1975                 draw_view_line(view, view->lineno - view->offset);
1976                 redrawwin(view->win);
1977                 wrefresh(view->win);
1978         }
1980         report("Line %ld matches '%s'", lineno + 1, view->grep);
1981         return TRUE;
1984 static void
1985 find_next(struct view *view, enum request request)
1987         unsigned long lineno = view->lineno;
1988         int direction;
1990         if (!*view->grep) {
1991                 if (!*opt_search)
1992                         report("No previous search");
1993                 else
1994                         search_view(view, request);
1995                 return;
1996         }
1998         switch (request) {
1999         case REQ_SEARCH:
2000         case REQ_FIND_NEXT:
2001                 direction = 1;
2002                 break;
2004         case REQ_SEARCH_BACK:
2005         case REQ_FIND_PREV:
2006                 direction = -1;
2007                 break;
2009         default:
2010                 return;
2011         }
2013         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2014                 lineno += direction;
2016         /* Note, lineno is unsigned long so will wrap around in which case it
2017          * will become bigger than view->lines. */
2018         for (; lineno < view->lines; lineno += direction) {
2019                 struct line *line = &view->line[lineno];
2021                 if (find_next_line(view, lineno, line))
2022                         return;
2023         }
2025         report("No match found for '%s'", view->grep);
2028 static void
2029 search_view(struct view *view, enum request request)
2031         int regex_err;
2033         if (view->regex) {
2034                 regfree(view->regex);
2035                 *view->grep = 0;
2036         } else {
2037                 view->regex = calloc(1, sizeof(*view->regex));
2038                 if (!view->regex)
2039                         return;
2040         }
2042         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2043         if (regex_err != 0) {
2044                 char buf[SIZEOF_STR] = "unknown error";
2046                 regerror(regex_err, view->regex, buf, sizeof(buf));
2047                 report("Search failed: %s", buf);
2048                 return;
2049         }
2051         string_copy(view->grep, opt_search);
2053         find_next(view, request);
2056 /*
2057  * Incremental updating
2058  */
2060 static void
2061 end_update(struct view *view)
2063         if (!view->pipe)
2064                 return;
2065         set_nonblocking_input(FALSE);
2066         if (view->pipe == stdin)
2067                 fclose(view->pipe);
2068         else
2069                 pclose(view->pipe);
2070         view->pipe = NULL;
2073 static bool
2074 begin_update(struct view *view)
2076         if (view->pipe)
2077                 end_update(view);
2079         if (opt_cmd[0]) {
2080                 string_copy(view->cmd, opt_cmd);
2081                 opt_cmd[0] = 0;
2082                 /* When running random commands, initially show the
2083                  * command in the title. However, it maybe later be
2084                  * overwritten if a commit line is selected. */
2085                 if (view == VIEW(REQ_VIEW_PAGER))
2086                         string_copy(view->ref, view->cmd);
2087                 else
2088                         view->ref[0] = 0;
2090         } else if (view == VIEW(REQ_VIEW_TREE)) {
2091                 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
2092                 char path[SIZEOF_STR];
2094                 if (strcmp(view->vid, view->id))
2095                         opt_path[0] = path[0] = 0;
2096                 else if (sq_quote(path, 0, opt_path) >= sizeof(path))
2097                         return FALSE;
2099                 if (!string_format(view->cmd, format, view->id, path))
2100                         return FALSE;
2102         } else {
2103                 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
2104                 const char *id = view->id;
2106                 if (!string_format(view->cmd, format, id, id, id, id, id))
2107                         return FALSE;
2109                 /* Put the current ref_* value to the view title ref
2110                  * member. This is needed by the blob view. Most other
2111                  * views sets it automatically after loading because the
2112                  * first line is a commit line. */
2113                 string_copy_rev(view->ref, view->id);
2114         }
2116         /* Special case for the pager view. */
2117         if (opt_pipe) {
2118                 view->pipe = opt_pipe;
2119                 opt_pipe = NULL;
2120         } else {
2121                 view->pipe = popen(view->cmd, "r");
2122         }
2124         if (!view->pipe)
2125                 return FALSE;
2127         set_nonblocking_input(TRUE);
2129         view->offset = 0;
2130         view->lines  = 0;
2131         view->lineno = 0;
2132         string_copy_rev(view->vid, view->id);
2134         if (view->line) {
2135                 int i;
2137                 for (i = 0; i < view->lines; i++)
2138                         if (view->line[i].data)
2139                                 free(view->line[i].data);
2141                 free(view->line);
2142                 view->line = NULL;
2143         }
2145         view->start_time = time(NULL);
2147         return TRUE;
2150 #define ITEM_CHUNK_SIZE 256
2151 static void *
2152 realloc_items(void *mem, size_t *size, size_t new_size, size_t item_size)
2154         size_t num_chunks = *size / ITEM_CHUNK_SIZE;
2155         size_t num_chunks_new = (new_size + ITEM_CHUNK_SIZE - 1) / ITEM_CHUNK_SIZE;
2157         if (mem == NULL || num_chunks != num_chunks_new) {
2158                 *size = num_chunks_new * ITEM_CHUNK_SIZE;
2159                 mem = realloc(mem, *size * item_size);
2160         }
2162         return mem;
2165 static struct line *
2166 realloc_lines(struct view *view, size_t line_size)
2168         size_t alloc = view->line_alloc;
2169         struct line *tmp = realloc_items(view->line, &alloc, line_size,
2170                                          sizeof(*view->line));
2172         if (!tmp)
2173                 return NULL;
2175         view->line = tmp;
2176         view->line_alloc = alloc;
2177         view->line_size = line_size;
2178         return view->line;
2181 static bool
2182 update_view(struct view *view)
2184         char in_buffer[BUFSIZ];
2185         char out_buffer[BUFSIZ * 2];
2186         char *line;
2187         /* The number of lines to read. If too low it will cause too much
2188          * redrawing (and possible flickering), if too high responsiveness
2189          * will suffer. */
2190         unsigned long lines = view->height;
2191         int redraw_from = -1;
2193         if (!view->pipe)
2194                 return TRUE;
2196         /* Only redraw if lines are visible. */
2197         if (view->offset + view->height >= view->lines)
2198                 redraw_from = view->lines - view->offset;
2200         /* FIXME: This is probably not perfect for backgrounded views. */
2201         if (!realloc_lines(view, view->lines + lines))
2202                 goto alloc_error;
2204         while ((line = fgets(in_buffer, sizeof(in_buffer), view->pipe))) {
2205                 size_t linelen = strlen(line);
2207                 if (linelen)
2208                         line[linelen - 1] = 0;
2210                 if (opt_iconv != ICONV_NONE) {
2211                         ICONV_CONST char *inbuf = line;
2212                         size_t inlen = linelen;
2214                         char *outbuf = out_buffer;
2215                         size_t outlen = sizeof(out_buffer);
2217                         size_t ret;
2219                         ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2220                         if (ret != (size_t) -1) {
2221                                 line = out_buffer;
2222                                 linelen = strlen(out_buffer);
2223                         }
2224                 }
2226                 if (!view->ops->read(view, line))
2227                         goto alloc_error;
2229                 if (lines-- == 1)
2230                         break;
2231         }
2233         {
2234                 int digits;
2236                 lines = view->lines;
2237                 for (digits = 0; lines; digits++)
2238                         lines /= 10;
2240                 /* Keep the displayed view in sync with line number scaling. */
2241                 if (digits != view->digits) {
2242                         view->digits = digits;
2243                         redraw_from = 0;
2244                 }
2245         }
2247         if (!view_is_displayed(view))
2248                 goto check_pipe;
2250         if (view == VIEW(REQ_VIEW_TREE)) {
2251                 /* Clear the view and redraw everything since the tree sorting
2252                  * might have rearranged things. */
2253                 redraw_view(view);
2256         } else if (redraw_from >= 0) {
2257                 /* If this is an incremental update, redraw the previous line
2258                  * since for commits some members could have changed when
2259                  * loading the main view. */
2260                 if (redraw_from > 0)
2261                         redraw_from--;
2263                 /* Since revision graph visualization requires knowledge
2264                  * about the parent commit, it causes a further one-off
2265                  * needed to be redrawn for incremental updates. */
2266                 if (redraw_from > 0 && opt_rev_graph)
2267                         redraw_from--;
2269                 /* Incrementally draw avoids flickering. */
2270                 redraw_view_from(view, redraw_from);
2271         }
2273         if (view == VIEW(REQ_VIEW_BLAME))
2274                 redraw_view_dirty(view);
2276         /* Update the title _after_ the redraw so that if the redraw picks up a
2277          * commit reference in view->ref it'll be available here. */
2278         update_view_title(view);
2280 check_pipe:
2281         if (ferror(view->pipe)) {
2282                 report("Failed to read: %s", strerror(errno));
2283                 goto end;
2285         } else if (feof(view->pipe)) {
2286                 report("");
2287                 goto end;
2288         }
2290         return TRUE;
2292 alloc_error:
2293         report("Allocation failure");
2295 end:
2296         if (view->ops->read(view, NULL))
2297                 end_update(view);
2298         return FALSE;
2301 static struct line *
2302 add_line_data(struct view *view, void *data, enum line_type type)
2304         struct line *line = &view->line[view->lines++];
2306         memset(line, 0, sizeof(*line));
2307         line->type = type;
2308         line->data = data;
2310         return line;
2313 static struct line *
2314 add_line_text(struct view *view, char *data, enum line_type type)
2316         if (data)
2317                 data = strdup(data);
2319         return data ? add_line_data(view, data, type) : NULL;
2323 /*
2324  * View opening
2325  */
2327 enum open_flags {
2328         OPEN_DEFAULT = 0,       /* Use default view switching. */
2329         OPEN_SPLIT = 1,         /* Split current view. */
2330         OPEN_BACKGROUNDED = 2,  /* Backgrounded. */
2331         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
2332 };
2334 static void
2335 open_view(struct view *prev, enum request request, enum open_flags flags)
2337         bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
2338         bool split = !!(flags & OPEN_SPLIT);
2339         bool reload = !!(flags & OPEN_RELOAD);
2340         struct view *view = VIEW(request);
2341         int nviews = displayed_views();
2342         struct view *base_view = display[0];
2344         if (view == prev && nviews == 1 && !reload) {
2345                 report("Already in %s view", view->name);
2346                 return;
2347         }
2349         if (view->ops->open) {
2350                 if (!view->ops->open(view)) {
2351                         report("Failed to load %s view", view->name);
2352                         return;
2353                 }
2355         } else if ((reload || strcmp(view->vid, view->id)) &&
2356                    !begin_update(view)) {
2357                 report("Failed to load %s view", view->name);
2358                 return;
2359         }
2361         if (split) {
2362                 display[1] = view;
2363                 if (!backgrounded)
2364                         current_view = 1;
2365         } else {
2366                 /* Maximize the current view. */
2367                 memset(display, 0, sizeof(display));
2368                 current_view = 0;
2369                 display[current_view] = view;
2370         }
2372         /* Resize the view when switching between split- and full-screen,
2373          * or when switching between two different full-screen views. */
2374         if (nviews != displayed_views() ||
2375             (nviews == 1 && base_view != display[0]))
2376                 resize_display();
2378         if (split && prev->lineno - prev->offset >= prev->height) {
2379                 /* Take the title line into account. */
2380                 int lines = prev->lineno - prev->offset - prev->height + 1;
2382                 /* Scroll the view that was split if the current line is
2383                  * outside the new limited view. */
2384                 do_scroll_view(prev, lines);
2385         }
2387         if (prev && view != prev) {
2388                 if (split && !backgrounded) {
2389                         /* "Blur" the previous view. */
2390                         update_view_title(prev);
2391                 }
2393                 view->parent = prev;
2394         }
2396         if (view->pipe && view->lines == 0) {
2397                 /* Clear the old view and let the incremental updating refill
2398                  * the screen. */
2399                 wclear(view->win);
2400                 report("");
2401         } else {
2402                 redraw_view(view);
2403                 report("");
2404         }
2406         /* If the view is backgrounded the above calls to report()
2407          * won't redraw the view title. */
2408         if (backgrounded)
2409                 update_view_title(view);
2412 static void
2413 open_external_viewer(const char *cmd)
2415         def_prog_mode();           /* save current tty modes */
2416         endwin();                  /* restore original tty modes */
2417         system(cmd);
2418         fprintf(stderr, "Press Enter to continue");
2419         getc(stdin);
2420         reset_prog_mode();
2421         redraw_display();
2424 static void
2425 open_mergetool(const char *file)
2427         char cmd[SIZEOF_STR];
2428         char file_sq[SIZEOF_STR];
2430         if (sq_quote(file_sq, 0, file) < sizeof(file_sq) &&
2431             string_format(cmd, "git mergetool %s", file_sq)) {
2432                 open_external_viewer(cmd);
2433         }
2436 static void
2437 open_editor(bool from_root, const char *file)
2439         char cmd[SIZEOF_STR];
2440         char file_sq[SIZEOF_STR];
2441         char *editor;
2442         char *prefix = from_root ? opt_cdup : "";
2444         editor = getenv("GIT_EDITOR");
2445         if (!editor && *opt_editor)
2446                 editor = opt_editor;
2447         if (!editor)
2448                 editor = getenv("VISUAL");
2449         if (!editor)
2450                 editor = getenv("EDITOR");
2451         if (!editor)
2452                 editor = "vi";
2454         if (sq_quote(file_sq, 0, file) < sizeof(file_sq) &&
2455             string_format(cmd, "%s %s%s", editor, prefix, file_sq)) {
2456                 open_external_viewer(cmd);
2457         }
2460 static void
2461 open_run_request(enum request request)
2463         struct run_request *req = get_run_request(request);
2464         char buf[SIZEOF_STR * 2];
2465         size_t bufpos;
2466         char *cmd;
2468         if (!req) {
2469                 report("Unknown run request");
2470                 return;
2471         }
2473         bufpos = 0;
2474         cmd = req->cmd;
2476         while (cmd) {
2477                 char *next = strstr(cmd, "%(");
2478                 int len = next - cmd;
2479                 char *value;
2481                 if (!next) {
2482                         len = strlen(cmd);
2483                         value = "";
2485                 } else if (!strncmp(next, "%(head)", 7)) {
2486                         value = ref_head;
2488                 } else if (!strncmp(next, "%(commit)", 9)) {
2489                         value = ref_commit;
2491                 } else if (!strncmp(next, "%(blob)", 7)) {
2492                         value = ref_blob;
2494                 } else {
2495                         report("Unknown replacement in run request: `%s`", req->cmd);
2496                         return;
2497                 }
2499                 if (!string_format_from(buf, &bufpos, "%.*s%s", len, cmd, value))
2500                         return;
2502                 if (next)
2503                         next = strchr(next, ')') + 1;
2504                 cmd = next;
2505         }
2507         open_external_viewer(buf);
2510 /*
2511  * User request switch noodle
2512  */
2514 static int
2515 view_driver(struct view *view, enum request request)
2517         int i;
2519         if (request == REQ_NONE) {
2520                 doupdate();
2521                 return TRUE;
2522         }
2524         if (request > REQ_NONE) {
2525                 open_run_request(request);
2526                 return TRUE;
2527         }
2529         if (view && view->lines) {
2530                 request = view->ops->request(view, request, &view->line[view->lineno]);
2531                 if (request == REQ_NONE)
2532                         return TRUE;
2533         }
2535         switch (request) {
2536         case REQ_MOVE_UP:
2537         case REQ_MOVE_DOWN:
2538         case REQ_MOVE_PAGE_UP:
2539         case REQ_MOVE_PAGE_DOWN:
2540         case REQ_MOVE_FIRST_LINE:
2541         case REQ_MOVE_LAST_LINE:
2542                 move_view(view, request);
2543                 break;
2545         case REQ_SCROLL_LINE_DOWN:
2546         case REQ_SCROLL_LINE_UP:
2547         case REQ_SCROLL_PAGE_DOWN:
2548         case REQ_SCROLL_PAGE_UP:
2549                 scroll_view(view, request);
2550                 break;
2552         case REQ_VIEW_BLAME:
2553                 if (!opt_path[0]) {
2554                         report("No file chosen, press %s to open tree view",
2555                                get_key(REQ_VIEW_TREE));
2556                         break;
2557                 }
2558                 if (opt_path[strlen(opt_path) - 1] == '/') {
2559                         report("Cannot show blame for directory %s", opt_path);
2560                         break;
2561                 }
2562                 string_copy(opt_ref, ref_commit);
2563                 open_view(view, request, OPEN_DEFAULT);
2564                 break;
2566         case REQ_VIEW_BLOB:
2567                 if (!ref_blob[0]) {
2568                         report("No file chosen, press %s to open tree view",
2569                                get_key(REQ_VIEW_TREE));
2570                         break;
2571                 }
2572                 open_view(view, request, OPEN_DEFAULT);
2573                 break;
2575         case REQ_VIEW_PAGER:
2576                 if (!opt_pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
2577                         report("No pager content, press %s to run command from prompt",
2578                                get_key(REQ_PROMPT));
2579                         break;
2580                 }
2581                 open_view(view, request, OPEN_DEFAULT);
2582                 break;
2584         case REQ_VIEW_STAGE:
2585                 if (!VIEW(REQ_VIEW_STAGE)->lines) {
2586                         report("No stage content, press %s to open the status view and choose file",
2587                                get_key(REQ_VIEW_STATUS));
2588                         break;
2589                 }
2590                 open_view(view, request, OPEN_DEFAULT);
2591                 break;
2593         case REQ_VIEW_STATUS:
2594                 if (opt_is_inside_work_tree == FALSE) {
2595                         report("The status view requires a working tree");
2596                         break;
2597                 }
2598                 open_view(view, request, OPEN_DEFAULT);
2599                 break;
2601         case REQ_VIEW_MAIN:
2602         case REQ_VIEW_DIFF:
2603         case REQ_VIEW_LOG:
2604         case REQ_VIEW_TREE:
2605         case REQ_VIEW_HELP:
2606                 open_view(view, request, OPEN_DEFAULT);
2607                 break;
2609         case REQ_NEXT:
2610         case REQ_PREVIOUS:
2611                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
2613                 if ((view == VIEW(REQ_VIEW_DIFF) &&
2614                      view->parent == VIEW(REQ_VIEW_MAIN)) ||
2615                    (view == VIEW(REQ_VIEW_DIFF) &&
2616                      view->parent == VIEW(REQ_VIEW_BLAME)) ||
2617                    (view == VIEW(REQ_VIEW_STAGE) &&
2618                      view->parent == VIEW(REQ_VIEW_STATUS)) ||
2619                    (view == VIEW(REQ_VIEW_BLOB) &&
2620                      view->parent == VIEW(REQ_VIEW_TREE))) {
2621                         int line;
2623                         view = view->parent;
2624                         line = view->lineno;
2625                         move_view(view, request);
2626                         if (view_is_displayed(view))
2627                                 update_view_title(view);
2628                         if (line != view->lineno)
2629                                 view->ops->request(view, REQ_ENTER,
2630                                                    &view->line[view->lineno]);
2632                 } else {
2633                         move_view(view, request);
2634                 }
2635                 break;
2637         case REQ_VIEW_NEXT:
2638         {
2639                 int nviews = displayed_views();
2640                 int next_view = (current_view + 1) % nviews;
2642                 if (next_view == current_view) {
2643                         report("Only one view is displayed");
2644                         break;
2645                 }
2647                 current_view = next_view;
2648                 /* Blur out the title of the previous view. */
2649                 update_view_title(view);
2650                 report("");
2651                 break;
2652         }
2653         case REQ_REFRESH:
2654                 report("Refreshing is not yet supported for the %s view", view->name);
2655                 break;
2657         case REQ_TOGGLE_LINENO:
2658                 opt_line_number = !opt_line_number;
2659                 redraw_display();
2660                 break;
2662         case REQ_TOGGLE_DATE:
2663                 opt_date = !opt_date;
2664                 redraw_display();
2665                 break;
2667         case REQ_TOGGLE_AUTHOR:
2668                 opt_author = !opt_author;
2669                 redraw_display();
2670                 break;
2672         case REQ_TOGGLE_REV_GRAPH:
2673                 opt_rev_graph = !opt_rev_graph;
2674                 redraw_display();
2675                 break;
2677         case REQ_TOGGLE_REFS:
2678                 opt_show_refs = !opt_show_refs;
2679                 redraw_display();
2680                 break;
2682         case REQ_PROMPT:
2683                 /* Always reload^Wrerun commands from the prompt. */
2684                 open_view(view, opt_request, OPEN_RELOAD);
2685                 break;
2687         case REQ_SEARCH:
2688         case REQ_SEARCH_BACK:
2689                 search_view(view, request);
2690                 break;
2692         case REQ_FIND_NEXT:
2693         case REQ_FIND_PREV:
2694                 find_next(view, request);
2695                 break;
2697         case REQ_STOP_LOADING:
2698                 for (i = 0; i < ARRAY_SIZE(views); i++) {
2699                         view = &views[i];
2700                         if (view->pipe)
2701                                 report("Stopped loading the %s view", view->name),
2702                         end_update(view);
2703                 }
2704                 break;
2706         case REQ_SHOW_VERSION:
2707                 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
2708                 return TRUE;
2710         case REQ_SCREEN_RESIZE:
2711                 resize_display();
2712                 /* Fall-through */
2713         case REQ_SCREEN_REDRAW:
2714                 redraw_display();
2715                 break;
2717         case REQ_EDIT:
2718                 report("Nothing to edit");
2719                 break;
2722         case REQ_ENTER:
2723                 report("Nothing to enter");
2724                 break;
2727         case REQ_VIEW_CLOSE:
2728                 /* XXX: Mark closed views by letting view->parent point to the
2729                  * view itself. Parents to closed view should never be
2730                  * followed. */
2731                 if (view->parent &&
2732                     view->parent->parent != view->parent) {
2733                         memset(display, 0, sizeof(display));
2734                         current_view = 0;
2735                         display[current_view] = view->parent;
2736                         view->parent = view;
2737                         resize_display();
2738                         redraw_display();
2739                         break;
2740                 }
2741                 /* Fall-through */
2742         case REQ_QUIT:
2743                 return FALSE;
2745         default:
2746                 /* An unknown key will show most commonly used commands. */
2747                 report("Unknown key, press 'h' for help");
2748                 return TRUE;
2749         }
2751         return TRUE;
2755 /*
2756  * Pager backend
2757  */
2759 static bool
2760 pager_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
2762         char *text = line->data;
2763         enum line_type type = line->type;
2764         int attr;
2766         wmove(view->win, lineno, 0);
2768         if (selected) {
2769                 type = LINE_CURSOR;
2770                 wchgat(view->win, -1, 0, type, NULL);
2771         }
2773         attr = get_line_attr(type);
2774         wattrset(view->win, attr);
2776         if (opt_line_number || opt_tab_size < TABSIZE) {
2777                 static char spaces[] = "                    ";
2778                 int col_offset = 0, col = 0;
2780                 if (opt_line_number) {
2781                         unsigned long real_lineno = view->offset + lineno + 1;
2783                         if (real_lineno == 1 ||
2784                             (real_lineno % opt_num_interval) == 0) {
2785                                 wprintw(view->win, "%.*d", view->digits, real_lineno);
2787                         } else {
2788                                 waddnstr(view->win, spaces,
2789                                          MIN(view->digits, STRING_SIZE(spaces)));
2790                         }
2791                         waddstr(view->win, ": ");
2792                         col_offset = view->digits + 2;
2793                 }
2795                 while (text && col_offset + col < view->width) {
2796                         int cols_max = view->width - col_offset - col;
2797                         char *pos = text;
2798                         int cols;
2800                         if (*text == '\t') {
2801                                 text++;
2802                                 assert(sizeof(spaces) > TABSIZE);
2803                                 pos = spaces;
2804                                 cols = opt_tab_size - (col % opt_tab_size);
2806                         } else {
2807                                 text = strchr(text, '\t');
2808                                 cols = line ? text - pos : strlen(pos);
2809                         }
2811                         waddnstr(view->win, pos, MIN(cols, cols_max));
2812                         col += cols;
2813                 }
2815         } else {
2816                 int tilde_attr = get_line_attr(LINE_MAIN_DELIM);
2818                 draw_text(view, text, view->width, TRUE, tilde_attr);
2819         }
2821         return TRUE;
2824 static bool
2825 add_describe_ref(char *buf, size_t *bufpos, char *commit_id, const char *sep)
2827         char refbuf[SIZEOF_STR];
2828         char *ref = NULL;
2829         FILE *pipe;
2831         if (!string_format(refbuf, "git describe %s 2>/dev/null", commit_id))
2832                 return TRUE;
2834         pipe = popen(refbuf, "r");
2835         if (!pipe)
2836                 return TRUE;
2838         if ((ref = fgets(refbuf, sizeof(refbuf), pipe)))
2839                 ref = chomp_string(ref);
2840         pclose(pipe);
2842         if (!ref || !*ref)
2843                 return TRUE;
2845         /* This is the only fatal call, since it can "corrupt" the buffer. */
2846         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
2847                 return FALSE;
2849         return TRUE;
2852 static void
2853 add_pager_refs(struct view *view, struct line *line)
2855         char buf[SIZEOF_STR];
2856         char *commit_id = (char *)line->data + STRING_SIZE("commit ");
2857         struct ref **refs;
2858         size_t bufpos = 0, refpos = 0;
2859         const char *sep = "Refs: ";
2860         bool is_tag = FALSE;
2862         assert(line->type == LINE_COMMIT);
2864         refs = get_refs(commit_id);
2865         if (!refs) {
2866                 if (view == VIEW(REQ_VIEW_DIFF))
2867                         goto try_add_describe_ref;
2868                 return;
2869         }
2871         do {
2872                 struct ref *ref = refs[refpos];
2873                 char *fmt = ref->tag    ? "%s[%s]" :
2874                             ref->remote ? "%s<%s>" : "%s%s";
2876                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
2877                         return;
2878                 sep = ", ";
2879                 if (ref->tag)
2880                         is_tag = TRUE;
2881         } while (refs[refpos++]->next);
2883         if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
2884 try_add_describe_ref:
2885                 /* Add <tag>-g<commit_id> "fake" reference. */
2886                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
2887                         return;
2888         }
2890         if (bufpos == 0)
2891                 return;
2893         if (!realloc_lines(view, view->line_size + 1))
2894                 return;
2896         add_line_text(view, buf, LINE_PP_REFS);
2899 static bool
2900 pager_read(struct view *view, char *data)
2902         struct line *line;
2904         if (!data)
2905                 return TRUE;
2907         line = add_line_text(view, data, get_line_type(data));
2908         if (!line)
2909                 return FALSE;
2911         if (line->type == LINE_COMMIT &&
2912             (view == VIEW(REQ_VIEW_DIFF) ||
2913              view == VIEW(REQ_VIEW_LOG)))
2914                 add_pager_refs(view, line);
2916         return TRUE;
2919 static enum request
2920 pager_request(struct view *view, enum request request, struct line *line)
2922         int split = 0;
2924         if (request != REQ_ENTER)
2925                 return request;
2927         if (line->type == LINE_COMMIT &&
2928            (view == VIEW(REQ_VIEW_LOG) ||
2929             view == VIEW(REQ_VIEW_PAGER))) {
2930                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
2931                 split = 1;
2932         }
2934         /* Always scroll the view even if it was split. That way
2935          * you can use Enter to scroll through the log view and
2936          * split open each commit diff. */
2937         scroll_view(view, REQ_SCROLL_LINE_DOWN);
2939         /* FIXME: A minor workaround. Scrolling the view will call report("")
2940          * but if we are scrolling a non-current view this won't properly
2941          * update the view title. */
2942         if (split)
2943                 update_view_title(view);
2945         return REQ_NONE;
2948 static bool
2949 pager_grep(struct view *view, struct line *line)
2951         regmatch_t pmatch;
2952         char *text = line->data;
2954         if (!*text)
2955                 return FALSE;
2957         if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
2958                 return FALSE;
2960         return TRUE;
2963 static void
2964 pager_select(struct view *view, struct line *line)
2966         if (line->type == LINE_COMMIT) {
2967                 char *text = (char *)line->data + STRING_SIZE("commit ");
2969                 if (view != VIEW(REQ_VIEW_PAGER))
2970                         string_copy_rev(view->ref, text);
2971                 string_copy_rev(ref_commit, text);
2972         }
2975 static struct view_ops pager_ops = {
2976         "line",
2977         NULL,
2978         pager_read,
2979         pager_draw,
2980         pager_request,
2981         pager_grep,
2982         pager_select,
2983 };
2986 /*
2987  * Help backend
2988  */
2990 static bool
2991 help_open(struct view *view)
2993         char buf[BUFSIZ];
2994         int lines = ARRAY_SIZE(req_info) + 2;
2995         int i;
2997         if (view->lines > 0)
2998                 return TRUE;
3000         for (i = 0; i < ARRAY_SIZE(req_info); i++)
3001                 if (!req_info[i].request)
3002                         lines++;
3004         lines += run_requests + 1;
3006         view->line = calloc(lines, sizeof(*view->line));
3007         if (!view->line)
3008                 return FALSE;
3010         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
3012         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
3013                 char *key;
3015                 if (req_info[i].request == REQ_NONE)
3016                         continue;
3018                 if (!req_info[i].request) {
3019                         add_line_text(view, "", LINE_DEFAULT);
3020                         add_line_text(view, req_info[i].help, LINE_DEFAULT);
3021                         continue;
3022                 }
3024                 key = get_key(req_info[i].request);
3025                 if (!*key)
3026                         key = "(no key defined)";
3028                 if (!string_format(buf, "    %-25s %s", key, req_info[i].help))
3029                         continue;
3031                 add_line_text(view, buf, LINE_DEFAULT);
3032         }
3034         if (run_requests) {
3035                 add_line_text(view, "", LINE_DEFAULT);
3036                 add_line_text(view, "External commands:", LINE_DEFAULT);
3037         }
3039         for (i = 0; i < run_requests; i++) {
3040                 struct run_request *req = get_run_request(REQ_NONE + i + 1);
3041                 char *key;
3043                 if (!req)
3044                         continue;
3046                 key = get_key_name(req->key);
3047                 if (!*key)
3048                         key = "(no key defined)";
3050                 if (!string_format(buf, "    %-10s %-14s `%s`",
3051                                    keymap_table[req->keymap].name,
3052                                    key, req->cmd))
3053                         continue;
3055                 add_line_text(view, buf, LINE_DEFAULT);
3056         }
3058         return TRUE;
3061 static struct view_ops help_ops = {
3062         "line",
3063         help_open,
3064         NULL,
3065         pager_draw,
3066         pager_request,
3067         pager_grep,
3068         pager_select,
3069 };
3072 /*
3073  * Tree backend
3074  */
3076 struct tree_stack_entry {
3077         struct tree_stack_entry *prev;  /* Entry below this in the stack */
3078         unsigned long lineno;           /* Line number to restore */
3079         char *name;                     /* Position of name in opt_path */
3080 };
3082 /* The top of the path stack. */
3083 static struct tree_stack_entry *tree_stack = NULL;
3084 unsigned long tree_lineno = 0;
3086 static void
3087 pop_tree_stack_entry(void)
3089         struct tree_stack_entry *entry = tree_stack;
3091         tree_lineno = entry->lineno;
3092         entry->name[0] = 0;
3093         tree_stack = entry->prev;
3094         free(entry);
3097 static void
3098 push_tree_stack_entry(char *name, unsigned long lineno)
3100         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
3101         size_t pathlen = strlen(opt_path);
3103         if (!entry)
3104                 return;
3106         entry->prev = tree_stack;
3107         entry->name = opt_path + pathlen;
3108         tree_stack = entry;
3110         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
3111                 pop_tree_stack_entry();
3112                 return;
3113         }
3115         /* Move the current line to the first tree entry. */
3116         tree_lineno = 1;
3117         entry->lineno = lineno;
3120 /* Parse output from git-ls-tree(1):
3121  *
3122  * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile
3123  * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README
3124  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
3125  * 100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38 web.conf
3126  */
3128 #define SIZEOF_TREE_ATTR \
3129         STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t")
3131 #define TREE_UP_FORMAT "040000 tree %s\t.."
3133 static int
3134 tree_compare_entry(enum line_type type1, char *name1,
3135                    enum line_type type2, char *name2)
3137         if (type1 != type2) {
3138                 if (type1 == LINE_TREE_DIR)
3139                         return -1;
3140                 return 1;
3141         }
3143         return strcmp(name1, name2);
3146 static bool
3147 tree_read(struct view *view, char *text)
3149         size_t textlen = text ? strlen(text) : 0;
3150         char buf[SIZEOF_STR];
3151         unsigned long pos;
3152         enum line_type type;
3153         bool first_read = view->lines == 0;
3155         if (!text)
3156                 return TRUE;
3157         if (textlen <= SIZEOF_TREE_ATTR)
3158                 return FALSE;
3160         type = text[STRING_SIZE("100644 ")] == 't'
3161              ? LINE_TREE_DIR : LINE_TREE_FILE;
3163         if (first_read) {
3164                 /* Add path info line */
3165                 if (!string_format(buf, "Directory path /%s", opt_path) ||
3166                     !realloc_lines(view, view->line_size + 1) ||
3167                     !add_line_text(view, buf, LINE_DEFAULT))
3168                         return FALSE;
3170                 /* Insert "link" to parent directory. */
3171                 if (*opt_path) {
3172                         if (!string_format(buf, TREE_UP_FORMAT, view->ref) ||
3173                             !realloc_lines(view, view->line_size + 1) ||
3174                             !add_line_text(view, buf, LINE_TREE_DIR))
3175                                 return FALSE;
3176                 }
3177         }
3179         /* Strip the path part ... */
3180         if (*opt_path) {
3181                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
3182                 size_t striplen = strlen(opt_path);
3183                 char *path = text + SIZEOF_TREE_ATTR;
3185                 if (pathlen > striplen)
3186                         memmove(path, path + striplen,
3187                                 pathlen - striplen + 1);
3188         }
3190         /* Skip "Directory ..." and ".." line. */
3191         for (pos = 1 + !!*opt_path; pos < view->lines; pos++) {
3192                 struct line *line = &view->line[pos];
3193                 char *path1 = ((char *) line->data) + SIZEOF_TREE_ATTR;
3194                 char *path2 = text + SIZEOF_TREE_ATTR;
3195                 int cmp = tree_compare_entry(line->type, path1, type, path2);
3197                 if (cmp <= 0)
3198                         continue;
3200                 text = strdup(text);
3201                 if (!text)
3202                         return FALSE;
3204                 if (view->lines > pos)
3205                         memmove(&view->line[pos + 1], &view->line[pos],
3206                                 (view->lines - pos) * sizeof(*line));
3208                 line = &view->line[pos];
3209                 line->data = text;
3210                 line->type = type;
3211                 view->lines++;
3212                 return TRUE;
3213         }
3215         if (!add_line_text(view, text, type))
3216                 return FALSE;
3218         if (tree_lineno > view->lineno) {
3219                 view->lineno = tree_lineno;
3220                 tree_lineno = 0;
3221         }
3223         return TRUE;
3226 static enum request
3227 tree_request(struct view *view, enum request request, struct line *line)
3229         enum open_flags flags;
3231         if (request == REQ_TREE_PARENT) {
3232                 if (*opt_path) {
3233                         /* fake 'cd  ..' */
3234                         request = REQ_ENTER;
3235                         line = &view->line[1];
3236                 } else {
3237                         /* quit view if at top of tree */
3238                         return REQ_VIEW_CLOSE;
3239                 }
3240         }
3241         if (request != REQ_ENTER)
3242                 return request;
3244         /* Cleanup the stack if the tree view is at a different tree. */
3245         while (!*opt_path && tree_stack)
3246                 pop_tree_stack_entry();
3248         switch (line->type) {
3249         case LINE_TREE_DIR:
3250                 /* Depending on whether it is a subdir or parent (updir?) link
3251                  * mangle the path buffer. */
3252                 if (line == &view->line[1] && *opt_path) {
3253                         pop_tree_stack_entry();
3255                 } else {
3256                         char *data = line->data;
3257                         char *basename = data + SIZEOF_TREE_ATTR;
3259                         push_tree_stack_entry(basename, view->lineno);
3260                 }
3262                 /* Trees and subtrees share the same ID, so they are not not
3263                  * unique like blobs. */
3264                 flags = OPEN_RELOAD;
3265                 request = REQ_VIEW_TREE;
3266                 break;
3268         case LINE_TREE_FILE:
3269                 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
3270                 request = REQ_VIEW_BLOB;
3271                 break;
3273         default:
3274                 return TRUE;
3275         }
3277         open_view(view, request, flags);
3278         if (request == REQ_VIEW_TREE) {
3279                 view->lineno = tree_lineno;
3280         }
3282         return REQ_NONE;
3285 static void
3286 tree_select(struct view *view, struct line *line)
3288         char *text = (char *)line->data + STRING_SIZE("100644 blob ");
3290         if (line->type == LINE_TREE_FILE) {
3291                 string_copy_rev(ref_blob, text);
3293         } else if (line->type != LINE_TREE_DIR) {
3294                 return;
3295         }
3297         string_copy_rev(view->ref, text);
3300 static struct view_ops tree_ops = {
3301         "file",
3302         NULL,
3303         tree_read,
3304         pager_draw,
3305         tree_request,
3306         pager_grep,
3307         tree_select,
3308 };
3310 static bool
3311 blob_read(struct view *view, char *line)
3313         return add_line_text(view, line, LINE_DEFAULT) != NULL;
3316 static struct view_ops blob_ops = {
3317         "line",
3318         NULL,
3319         blob_read,
3320         pager_draw,
3321         pager_request,
3322         pager_grep,
3323         pager_select,
3324 };
3326 /*
3327  * Blame backend
3328  *
3329  * Loading the blame view is a two phase job:
3330  *
3331  *  1. File content is read either using opt_path from the
3332  *     filesystem or using git-cat-file.
3333  *  2. Then blame information is incrementally added by
3334  *     reading output from git-blame.
3335  */
3337 struct blame_commit {
3338         char id[SIZEOF_REV];            /* SHA1 ID. */
3339         char title[128];                /* First line of the commit message. */
3340         char author[75];                /* Author of the commit. */
3341         struct tm time;                 /* Date from the author ident. */
3342         char filename[128];             /* Name of file. */
3343 };
3345 struct blame {
3346         struct blame_commit *commit;
3347         unsigned int header:1;
3348         char text[1];
3349 };
3351 #define BLAME_CAT_FILE_CMD "git cat-file blob %s:%s"
3352 #define BLAME_INCREMENTAL_CMD "git blame --incremental %s %s"
3354 static bool
3355 blame_open(struct view *view)
3357         char path[SIZEOF_STR];
3358         char ref[SIZEOF_STR] = "";
3360         if (sq_quote(path, 0, opt_path) >= sizeof(path))
3361                 return FALSE;
3363         if (*opt_ref && sq_quote(ref, 0, opt_ref) >= sizeof(ref))
3364                 return FALSE;
3366         if (*opt_ref) {
3367                 if (!string_format(view->cmd, BLAME_CAT_FILE_CMD, ref, path))
3368                         return FALSE;
3369         } else {
3370                 view->pipe = fopen(opt_path, "r");
3371                 if (!view->pipe &&
3372                     !string_format(view->cmd, BLAME_CAT_FILE_CMD, "HEAD", path))
3373                         return FALSE;
3374         }
3376         if (!view->pipe)
3377                 view->pipe = popen(view->cmd, "r");
3378         if (!view->pipe)
3379                 return FALSE;
3381         if (!string_format(view->cmd, BLAME_INCREMENTAL_CMD, ref, path))
3382                 return FALSE;
3384         string_format(view->ref, "%s ...", opt_path);
3385         string_copy_rev(view->vid, opt_path);
3386         set_nonblocking_input(TRUE);
3388         if (view->line) {
3389                 int i;
3391                 for (i = 0; i < view->lines; i++)
3392                         free(view->line[i].data);
3393                 free(view->line);
3394         }
3396         view->lines = view->line_alloc = view->line_size = view->lineno = 0;
3397         view->offset = view->lines  = view->lineno = 0;
3398         view->line = NULL;
3399         view->start_time = time(NULL);
3402 static struct blame_commit *
3403 get_blame_commit(struct view *view, const char *id)
3405         size_t i;
3407         for (i = 0; i < view->lines; i++) {
3408                 struct blame *blame = view->line[i].data;
3410                 if (!blame->commit)
3411                         continue;
3413                 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
3414                         return blame->commit;
3415         }
3417         {
3418                 struct blame_commit *commit = calloc(1, sizeof(*commit));
3420                 if (commit)
3421                         string_ncopy(commit->id, id, SIZEOF_REV);
3422                 return commit;
3423         }
3426 static bool
3427 parse_number(char **posref, size_t *number, size_t min, size_t max)
3429         char *pos = *posref;
3431         *posref = NULL;
3432         pos = strchr(pos + 1, ' ');
3433         if (!pos || !isdigit(pos[1]))
3434                 return FALSE;
3435         *number = atoi(pos + 1);
3436         if (*number < min || *number > max)
3437                 return FALSE;
3439         *posref = pos;
3440         return TRUE;
3443 static struct blame_commit *
3444 parse_blame_commit(struct view *view, char *text, int *blamed)
3446         struct blame_commit *commit;
3447         struct blame *blame;
3448         char *pos = text + SIZEOF_REV - 1;
3449         size_t lineno;
3450         size_t group;
3451         struct line *line;
3453         if (strlen(text) <= SIZEOF_REV || *pos != ' ')
3454                 return NULL;
3456         if (!parse_number(&pos, &lineno, 1, view->lines) ||
3457             !parse_number(&pos, &group, 1, view->lines - lineno + 1))
3458                 return NULL;
3460         commit = get_blame_commit(view, text);
3461         if (!commit)
3462                 return NULL;
3464         *blamed += group;
3465         while (group--) {
3466                 struct line *line = &view->line[lineno + group - 1];
3468                 blame = line->data;
3469                 blame->commit = commit;
3470                 line->dirty = 1;
3471         }
3472         blame->header = 1;
3474         return commit;
3477 static bool
3478 blame_read_file(struct view *view, char *line)
3480         if (!line) {
3481                 FILE *pipe = NULL;
3483                 if (view->lines > 0)
3484                         pipe = popen(view->cmd, "r");
3485                 view->cmd[0] = 0;
3486                 if (!pipe) {
3487                         report("Failed to load blame data");
3488                         return TRUE;
3489                 }
3491                 fclose(view->pipe);
3492                 view->pipe = pipe;
3493                 return FALSE;
3495         } else {
3496                 size_t linelen = strlen(line);
3497                 struct blame *blame = malloc(sizeof(*blame) + linelen);
3499                 if (!line)
3500                         return FALSE;
3502                 blame->commit = NULL;
3503                 strncpy(blame->text, line, linelen);
3504                 blame->text[linelen] = 0;
3505                 return add_line_data(view, blame, LINE_BLAME_COMMIT) != NULL;
3506         }
3509 static bool
3510 match_blame_header(const char *name, char **line)
3512         size_t namelen = strlen(name);
3513         bool matched = !strncmp(name, *line, namelen);
3515         if (matched)
3516                 *line += namelen;
3518         return matched;
3521 static bool
3522 blame_read(struct view *view, char *line)
3524         static struct blame_commit *commit = NULL;
3525         static int blamed = 0;
3526         static time_t author_time;
3528         if (*view->cmd)
3529                 return blame_read_file(view, line);
3531         if (!line) {
3532                 /* Reset all! */
3533                 commit = NULL;
3534                 blamed = 0;
3535                 string_format(view->ref, "%s", view->vid);
3536                 if (view_is_displayed(view)) {
3537                         update_view_title(view);
3538                         redraw_view_from(view, 0);
3539                 }
3540                 return TRUE;
3541         }
3543         if (!commit) {
3544                 commit = parse_blame_commit(view, line, &blamed);
3545                 string_format(view->ref, "%s %2d%%", view->vid,
3546                               blamed * 100 / view->lines);
3548         } else if (match_blame_header("author ", &line)) {
3549                 string_ncopy(commit->author, line, strlen(line));
3551         } else if (match_blame_header("author-time ", &line)) {
3552                 author_time = (time_t) atol(line);
3554         } else if (match_blame_header("author-tz ", &line)) {
3555                 long tz;
3557                 tz  = ('0' - line[1]) * 60 * 60 * 10;
3558                 tz += ('0' - line[2]) * 60 * 60;
3559                 tz += ('0' - line[3]) * 60;
3560                 tz += ('0' - line[4]) * 60;
3562                 if (line[0] == '-')
3563                         tz = -tz;
3565                 author_time -= tz;
3566                 gmtime_r(&author_time, &commit->time);
3568         } else if (match_blame_header("summary ", &line)) {
3569                 string_ncopy(commit->title, line, strlen(line));
3571         } else if (match_blame_header("filename ", &line)) {
3572                 string_ncopy(commit->filename, line, strlen(line));
3573                 commit = NULL;
3574         }
3576         return TRUE;
3579 static bool
3580 blame_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
3582         int tilde_attr = -1;
3583         struct blame *blame = line->data;
3584         int col = 0;
3586         wmove(view->win, lineno, 0);
3588         if (selected) {
3589                 wattrset(view->win, get_line_attr(LINE_CURSOR));
3590                 wchgat(view->win, -1, 0, LINE_CURSOR, NULL);
3591         } else {
3592                 wattrset(view->win, A_NORMAL);
3593                 tilde_attr = get_line_attr(LINE_MAIN_DELIM);
3594         }
3596         if (opt_date) {
3597                 int n;
3599                 if (!selected)
3600                         wattrset(view->win, get_line_attr(LINE_MAIN_DATE));
3601                 if (blame->commit) {
3602                         char buf[DATE_COLS + 1];
3603                         int timelen;
3605                         timelen = strftime(buf, sizeof(buf), DATE_FORMAT, &blame->commit->time);
3606                         n = draw_text(view, buf, view->width - col, FALSE, tilde_attr);
3607                         draw_text(view, " ", view->width - col - n, FALSE, tilde_attr);
3608                 }
3610                 col += DATE_COLS;
3611                 wmove(view->win, lineno, col);
3612                 if (col >= view->width)
3613                         return TRUE;
3614         }
3616         if (opt_author) {
3617                 int max = MIN(AUTHOR_COLS - 1, view->width - col);
3619                 if (!selected)
3620                         wattrset(view->win, get_line_attr(LINE_MAIN_AUTHOR));
3621                 if (blame->commit)
3622                         draw_text(view, blame->commit->author, max, TRUE, tilde_attr);
3623                 col += AUTHOR_COLS;
3624                 if (col >= view->width)
3625                         return TRUE;
3626                 wmove(view->win, lineno, col);
3627         }
3629         {
3630                 int max = MIN(ID_COLS - 1, view->width - col);
3632                 if (!selected)
3633                         wattrset(view->win, get_line_attr(LINE_BLAME_ID));
3634                 if (blame->commit)
3635                         draw_text(view, blame->commit->id, max, FALSE, -1);
3636                 col += ID_COLS;
3637                 if (col >= view->width)
3638                         return TRUE;
3639                 wmove(view->win, lineno, col);
3640         }
3642         {
3643                 unsigned long real_lineno = view->offset + lineno + 1;
3644                 char number[10] = "          ";
3645                 int max = MIN(view->digits, STRING_SIZE(number));
3646                 bool showtrimmed = FALSE;
3648                 if (real_lineno == 1 ||
3649                     (real_lineno % opt_num_interval) == 0) {
3650                         char fmt[] = "%1ld";
3652                         if (view->digits <= 9)
3653                                 fmt[1] = '0' + view->digits;
3655                         if (!string_format(number, fmt, real_lineno))
3656                                 number[0] = 0;
3657                         showtrimmed = TRUE;
3658                 }
3660                 if (max > view->width - col)
3661                         max = view->width - col;
3662                 if (!selected)
3663                         wattrset(view->win, get_line_attr(LINE_BLAME_LINENO));
3664                 col += draw_text(view, number, max, showtrimmed, tilde_attr);
3665                 if (col >= view->width)
3666                         return TRUE;
3667         }
3669         if (!selected)
3670                 wattrset(view->win, A_NORMAL);
3672         if (col >= view->width)
3673                 return TRUE;
3674         waddch(view->win, ACS_VLINE);
3675         col++;
3676         if (col >= view->width)
3677                 return TRUE;
3678         waddch(view->win, ' ');
3679         col++;
3680         col += draw_text(view, blame->text, view->width - col, TRUE, tilde_attr);
3682         return TRUE;
3685 static enum request
3686 blame_request(struct view *view, enum request request, struct line *line)
3688         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
3689         struct blame *blame = line->data;
3691         switch (request) {
3692         case REQ_ENTER:
3693                 if (!blame->commit) {
3694                         report("No commit loaded yet");
3695                         break;
3696                 }
3698                 if (!strcmp(blame->commit->id, "0000000000000000000000000000000000000000")) {
3699                         char path[SIZEOF_STR];
3701                         if (sq_quote(path, 0, view->vid) >= sizeof(path))
3702                                 break;
3703                         string_format(opt_cmd, "git diff-index --root --patch-with-stat -C -M --cached HEAD -- %s 2>/dev/null", path);
3704                 }
3706                 open_view(view, REQ_VIEW_DIFF, flags);
3707                 break;
3709         default:
3710                 return request;
3711         }
3713         return REQ_NONE;
3716 static bool
3717 blame_grep(struct view *view, struct line *line)
3719         struct blame *blame = line->data;
3720         struct blame_commit *commit = blame->commit;
3721         regmatch_t pmatch;
3723 #define MATCH(text) \
3724         (*text && regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
3726         if (commit) {
3727                 char buf[DATE_COLS + 1];
3729                 if (MATCH(commit->title) ||
3730                     MATCH(commit->author) ||
3731                     MATCH(commit->id))
3732                         return TRUE;
3734                 if (strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time) &&
3735                     MATCH(buf))
3736                         return TRUE;
3737         }
3739         return MATCH(blame->text);
3741 #undef MATCH
3744 static void
3745 blame_select(struct view *view, struct line *line)
3747         struct blame *blame = line->data;
3748         struct blame_commit *commit = blame->commit;
3750         if (!commit)
3751                 return;
3753         if (!strcmp(commit->id, "0000000000000000000000000000000000000000"))
3754                 string_ncopy(ref_commit, "HEAD", 4);
3755         else
3756                 string_copy_rev(ref_commit, commit->id);
3759 static struct view_ops blame_ops = {
3760         "line",
3761         blame_open,
3762         blame_read,
3763         blame_draw,
3764         blame_request,
3765         blame_grep,
3766         blame_select,
3767 };
3769 /*
3770  * Status backend
3771  */
3773 struct status {
3774         char status;
3775         struct {
3776                 mode_t mode;
3777                 char rev[SIZEOF_REV];
3778                 char name[SIZEOF_STR];
3779         } old;
3780         struct {
3781                 mode_t mode;
3782                 char rev[SIZEOF_REV];
3783                 char name[SIZEOF_STR];
3784         } new;
3785 };
3787 static struct status stage_status;
3788 static enum line_type stage_line_type;
3790 /* Get fields from the diff line:
3791  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
3792  */
3793 static inline bool
3794 status_get_diff(struct status *file, char *buf, size_t bufsize)
3796         char *old_mode = buf +  1;
3797         char *new_mode = buf +  8;
3798         char *old_rev  = buf + 15;
3799         char *new_rev  = buf + 56;
3800         char *status   = buf + 97;
3802         if (bufsize < 99 ||
3803             old_mode[-1] != ':' ||
3804             new_mode[-1] != ' ' ||
3805             old_rev[-1]  != ' ' ||
3806             new_rev[-1]  != ' ' ||
3807             status[-1]   != ' ')
3808                 return FALSE;
3810         file->status = *status;
3812         string_copy_rev(file->old.rev, old_rev);
3813         string_copy_rev(file->new.rev, new_rev);
3815         file->old.mode = strtoul(old_mode, NULL, 8);
3816         file->new.mode = strtoul(new_mode, NULL, 8);
3818         file->old.name[0] = file->new.name[0] = 0;
3820         return TRUE;
3823 static bool
3824 status_run(struct view *view, const char cmd[], bool diff, enum line_type type)
3826         struct status *file = NULL;
3827         struct status *unmerged = NULL;
3828         char buf[SIZEOF_STR * 4];
3829         size_t bufsize = 0;
3830         FILE *pipe;
3832         pipe = popen(cmd, "r");
3833         if (!pipe)
3834                 return FALSE;
3836         add_line_data(view, NULL, type);
3838         while (!feof(pipe) && !ferror(pipe)) {
3839                 char *sep;
3840                 size_t readsize;
3842                 readsize = fread(buf + bufsize, 1, sizeof(buf) - bufsize, pipe);
3843                 if (!readsize)
3844                         break;
3845                 bufsize += readsize;
3847                 /* Process while we have NUL chars. */
3848                 while ((sep = memchr(buf, 0, bufsize))) {
3849                         size_t sepsize = sep - buf + 1;
3851                         if (!file) {
3852                                 if (!realloc_lines(view, view->line_size + 1))
3853                                         goto error_out;
3855                                 file = calloc(1, sizeof(*file));
3856                                 if (!file)
3857                                         goto error_out;
3859                                 add_line_data(view, file, type);
3860                         }
3862                         /* Parse diff info part. */
3863                         if (!diff) {
3864                                 file->status = '?';
3866                         } else if (!file->status) {
3867                                 if (!status_get_diff(file, buf, sepsize))
3868                                         goto error_out;
3870                                 bufsize -= sepsize;
3871                                 memmove(buf, sep + 1, bufsize);
3873                                 sep = memchr(buf, 0, bufsize);
3874                                 if (!sep)
3875                                         break;
3876                                 sepsize = sep - buf + 1;
3878                                 /* Collapse all 'M'odified entries that
3879                                  * follow a associated 'U'nmerged entry.
3880                                  */
3881                                 if (file->status == 'U') {
3882                                         unmerged = file;
3884                                 } else if (unmerged) {
3885                                         int collapse = !strcmp(buf, unmerged->new.name);
3887                                         unmerged = NULL;
3888                                         if (collapse) {
3889                                                 free(file);
3890                                                 view->lines--;
3891                                                 continue;
3892                                         }
3893                                 }
3894                         }
3896                         /* Grab the old name for rename/copy. */
3897                         if (!*file->old.name &&
3898                             (file->status == 'R' || file->status == 'C')) {
3899                                 sepsize = sep - buf + 1;
3900                                 string_ncopy(file->old.name, buf, sepsize);
3901                                 bufsize -= sepsize;
3902                                 memmove(buf, sep + 1, bufsize);
3904                                 sep = memchr(buf, 0, bufsize);
3905                                 if (!sep)
3906                                         break;
3907                                 sepsize = sep - buf + 1;
3908                         }
3910                         /* git-ls-files just delivers a NUL separated
3911                          * list of file names similar to the second half
3912                          * of the git-diff-* output. */
3913                         string_ncopy(file->new.name, buf, sepsize);
3914                         if (!*file->old.name)
3915                                 string_copy(file->old.name, file->new.name);
3916                         bufsize -= sepsize;
3917                         memmove(buf, sep + 1, bufsize);
3918                         file = NULL;
3919                 }
3920         }
3922         if (ferror(pipe)) {
3923 error_out:
3924                 pclose(pipe);
3925                 return FALSE;
3926         }
3928         if (!view->line[view->lines - 1].data)
3929                 add_line_data(view, NULL, LINE_STAT_NONE);
3931         pclose(pipe);
3932         return TRUE;
3935 /* Don't show unmerged entries in the staged section. */
3936 #define STATUS_DIFF_INDEX_CMD "git diff-index -z --diff-filter=ACDMRTXB --cached -M HEAD"
3937 #define STATUS_DIFF_FILES_CMD "git diff-files -z"
3938 #define STATUS_LIST_OTHER_CMD \
3939         "git ls-files -z --others --exclude-per-directory=.gitignore"
3941 #define STATUS_DIFF_INDEX_SHOW_CMD \
3942         "git diff-index --root --patch-with-stat -C -M --cached HEAD -- %s %s 2>/dev/null"
3944 #define STATUS_DIFF_FILES_SHOW_CMD \
3945         "git diff-files --root --patch-with-stat -C -M -- %s %s 2>/dev/null"
3947 /* First parse staged info using git-diff-index(1), then parse unstaged
3948  * info using git-diff-files(1), and finally untracked files using
3949  * git-ls-files(1). */
3950 static bool
3951 status_open(struct view *view)
3953         struct stat statbuf;
3954         char exclude[SIZEOF_STR];
3955         char cmd[SIZEOF_STR];
3956         unsigned long prev_lineno = view->lineno;
3957         size_t i;
3959         for (i = 0; i < view->lines; i++)
3960                 free(view->line[i].data);
3961         free(view->line);
3962         view->lines = view->line_alloc = view->line_size = view->lineno = 0;
3963         view->line = NULL;
3965         if (!realloc_lines(view, view->line_size + 6))
3966                 return FALSE;
3968         if (!string_format(exclude, "%s/info/exclude", opt_git_dir))
3969                 return FALSE;
3971         string_copy(cmd, STATUS_LIST_OTHER_CMD);
3973         if (stat(exclude, &statbuf) >= 0) {
3974                 size_t cmdsize = strlen(cmd);
3976                 if (!string_format_from(cmd, &cmdsize, " %s", "--exclude-from=") ||
3977                     sq_quote(cmd, cmdsize, exclude) >= sizeof(cmd))
3978                         return FALSE;
3979         }
3981         system("git update-index -q --refresh");
3983         if (!status_run(view, STATUS_DIFF_INDEX_CMD, TRUE, LINE_STAT_STAGED) ||
3984             !status_run(view, STATUS_DIFF_FILES_CMD, TRUE, LINE_STAT_UNSTAGED) ||
3985             !status_run(view, cmd, FALSE, LINE_STAT_UNTRACKED))
3986                 return FALSE;
3988         /* If all went well restore the previous line number to stay in
3989          * the context. */
3990         if (prev_lineno < view->lines)
3991                 view->lineno = prev_lineno;
3992         else
3993                 view->lineno = view->lines - 1;
3995         return TRUE;
3998 static bool
3999 status_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
4001         struct status *status = line->data;
4002         int tilde_attr = get_line_attr(LINE_MAIN_DELIM);
4004         wmove(view->win, lineno, 0);
4006         if (selected) {
4007                 wattrset(view->win, get_line_attr(LINE_CURSOR));
4008                 wchgat(view->win, -1, 0, LINE_CURSOR, NULL);
4009                 tilde_attr = -1;
4011         } else if (!status && line->type != LINE_STAT_NONE) {
4012                 wattrset(view->win, get_line_attr(LINE_STAT_SECTION));
4013                 wchgat(view->win, -1, 0, LINE_STAT_SECTION, NULL);
4015         } else {
4016                 wattrset(view->win, get_line_attr(line->type));
4017         }
4019         if (!status) {
4020                 char *text;
4022                 switch (line->type) {
4023                 case LINE_STAT_STAGED:
4024                         text = "Changes to be committed:";
4025                         break;
4027                 case LINE_STAT_UNSTAGED:
4028                         text = "Changed but not updated:";
4029                         break;
4031                 case LINE_STAT_UNTRACKED:
4032                         text = "Untracked files:";
4033                         break;
4035                 case LINE_STAT_NONE:
4036                         text = "    (no files)";
4037                         break;
4039                 default:
4040                         return FALSE;
4041                 }
4043                 draw_text(view, text, view->width, TRUE, tilde_attr);
4044                 return TRUE;
4045         }
4047         waddch(view->win, status->status);
4048         if (!selected)
4049                 wattrset(view->win, A_NORMAL);
4050         wmove(view->win, lineno, 4);
4051         if (view->width < 5)
4052                 return TRUE;
4054         draw_text(view, status->new.name, view->width - 5, TRUE, tilde_attr);
4055         return TRUE;
4058 static enum request
4059 status_enter(struct view *view, struct line *line)
4061         struct status *status = line->data;
4062         char oldpath[SIZEOF_STR] = "";
4063         char newpath[SIZEOF_STR] = "";
4064         char *info;
4065         size_t cmdsize = 0;
4067         if (line->type == LINE_STAT_NONE ||
4068             (!status && line[1].type == LINE_STAT_NONE)) {
4069                 report("No file to diff");
4070                 return REQ_NONE;
4071         }
4073         if (status) {
4074                 if (sq_quote(oldpath, 0, status->old.name) >= sizeof(oldpath))
4075                         return REQ_QUIT;
4076                 /* Diffs for unmerged entries are empty when pasing the
4077                  * new path, so leave it empty. */
4078                 if (status->status != 'U' &&
4079                     sq_quote(newpath, 0, status->new.name) >= sizeof(newpath))
4080                         return REQ_QUIT;
4081         }
4083         if (opt_cdup[0] &&
4084             line->type != LINE_STAT_UNTRACKED &&
4085             !string_format_from(opt_cmd, &cmdsize, "cd %s;", opt_cdup))
4086                 return REQ_QUIT;
4088         switch (line->type) {
4089         case LINE_STAT_STAGED:
4090                 if (!string_format_from(opt_cmd, &cmdsize,
4091                                         STATUS_DIFF_INDEX_SHOW_CMD, oldpath, newpath))
4092                         return REQ_QUIT;
4093                 if (status)
4094                         info = "Staged changes to %s";
4095                 else
4096                         info = "Staged changes";
4097                 break;
4099         case LINE_STAT_UNSTAGED:
4100                 if (!string_format_from(opt_cmd, &cmdsize,
4101                                         STATUS_DIFF_FILES_SHOW_CMD, oldpath, newpath))
4102                         return REQ_QUIT;
4103                 if (status)
4104                         info = "Unstaged changes to %s";
4105                 else
4106                         info = "Unstaged changes";
4107                 break;
4109         case LINE_STAT_UNTRACKED:
4110                 if (opt_pipe)
4111                         return REQ_QUIT;
4114                 if (!status) {
4115                         report("No file to show");
4116                         return REQ_NONE;
4117                 }
4119                 opt_pipe = fopen(status->new.name, "r");
4120                 info = "Untracked file %s";
4121                 break;
4123         default:
4124                 die("line type %d not handled in switch", line->type);
4125         }
4127         open_view(view, REQ_VIEW_STAGE, OPEN_RELOAD | OPEN_SPLIT);
4128         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
4129                 if (status) {
4130                         stage_status = *status;
4131                 } else {
4132                         memset(&stage_status, 0, sizeof(stage_status));
4133                 }
4135                 stage_line_type = line->type;
4136                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
4137         }
4139         return REQ_NONE;
4143 static bool
4144 status_update_file(struct view *view, struct status *status, enum line_type type)
4146         char cmd[SIZEOF_STR];
4147         char buf[SIZEOF_STR];
4148         size_t cmdsize = 0;
4149         size_t bufsize = 0;
4150         size_t written = 0;
4151         FILE *pipe;
4153         if (opt_cdup[0] &&
4154             type != LINE_STAT_UNTRACKED &&
4155             !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
4156                 return FALSE;
4158         switch (type) {
4159         case LINE_STAT_STAGED:
4160                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
4161                                         status->old.mode,
4162                                         status->old.rev,
4163                                         status->old.name, 0))
4164                         return FALSE;
4166                 string_add(cmd, cmdsize, "git update-index -z --index-info");
4167                 break;
4169         case LINE_STAT_UNSTAGED:
4170         case LINE_STAT_UNTRACKED:
4171                 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
4172                         return FALSE;
4174                 string_add(cmd, cmdsize, "git update-index -z --add --remove --stdin");
4175                 break;
4177         default:
4178                 die("line type %d not handled in switch", type);
4179         }
4181         pipe = popen(cmd, "w");
4182         if (!pipe)
4183                 return FALSE;
4185         while (!ferror(pipe) && written < bufsize) {
4186                 written += fwrite(buf + written, 1, bufsize - written, pipe);
4187         }
4189         pclose(pipe);
4191         if (written != bufsize)
4192                 return FALSE;
4194         return TRUE;
4197 static void
4198 status_update(struct view *view)
4200         struct line *line = &view->line[view->lineno];
4202         assert(view->lines);
4204         if (!line->data) {
4205                 while (++line < view->line + view->lines && line->data) {
4206                         if (!status_update_file(view, line->data, line->type))
4207                                 report("Failed to update file status");
4208                 }
4210                 if (!line[-1].data) {
4211                         report("Nothing to update");
4212                         return;
4213                 }
4215         } else if (!status_update_file(view, line->data, line->type)) {
4216                 report("Failed to update file status");
4217         }
4220 static enum request
4221 status_request(struct view *view, enum request request, struct line *line)
4223         struct status *status = line->data;
4225         switch (request) {
4226         case REQ_STATUS_UPDATE:
4227                 status_update(view);
4228                 break;
4230         case REQ_STATUS_MERGE:
4231                 if (!status || status->status != 'U') {
4232                         report("Merging only possible for files with unmerged status ('U').");
4233                         return REQ_NONE;
4234                 }
4235                 open_mergetool(status->new.name);
4236                 break;
4238         case REQ_EDIT:
4239                 if (!status)
4240                         return request;
4242                 open_editor(status->status != '?', status->new.name);
4243                 break;
4245         case REQ_VIEW_BLAME:
4246                 if (status) {
4247                         string_copy(opt_path, status->new.name);
4248                         opt_ref[0] = 0;
4249                 }
4250                 return request;
4252         case REQ_ENTER:
4253                 /* After returning the status view has been split to
4254                  * show the stage view. No further reloading is
4255                  * necessary. */
4256                 status_enter(view, line);
4257                 return REQ_NONE;
4259         case REQ_REFRESH:
4260                 /* Simply reload the view. */
4261                 break;
4263         default:
4264                 return request;
4265         }
4267         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
4269         return REQ_NONE;
4272 static void
4273 status_select(struct view *view, struct line *line)
4275         struct status *status = line->data;
4276         char file[SIZEOF_STR] = "all files";
4277         char *text;
4278         char *key;
4280         if (status && !string_format(file, "'%s'", status->new.name))
4281                 return;
4283         if (!status && line[1].type == LINE_STAT_NONE)
4284                 line++;
4286         switch (line->type) {
4287         case LINE_STAT_STAGED:
4288                 text = "Press %s to unstage %s for commit";
4289                 break;
4291         case LINE_STAT_UNSTAGED:
4292                 text = "Press %s to stage %s for commit";
4293                 break;
4295         case LINE_STAT_UNTRACKED:
4296                 text = "Press %s to stage %s for addition";
4297                 break;
4299         case LINE_STAT_NONE:
4300                 text = "Nothing to update";
4301                 break;
4303         default:
4304                 die("line type %d not handled in switch", line->type);
4305         }
4307         if (status && status->status == 'U') {
4308                 text = "Press %s to resolve conflict in %s";
4309                 key = get_key(REQ_STATUS_MERGE);
4311         } else {
4312                 key = get_key(REQ_STATUS_UPDATE);
4313         }
4315         string_format(view->ref, text, key, file);
4318 static bool
4319 status_grep(struct view *view, struct line *line)
4321         struct status *status = line->data;
4322         enum { S_STATUS, S_NAME, S_END } state;
4323         char buf[2] = "?";
4324         regmatch_t pmatch;
4326         if (!status)
4327                 return FALSE;
4329         for (state = S_STATUS; state < S_END; state++) {
4330                 char *text;
4332                 switch (state) {
4333                 case S_NAME:    text = status->new.name;        break;
4334                 case S_STATUS:
4335                         buf[0] = status->status;
4336                         text = buf;
4337                         break;
4339                 default:
4340                         return FALSE;
4341                 }
4343                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4344                         return TRUE;
4345         }
4347         return FALSE;
4350 static struct view_ops status_ops = {
4351         "file",
4352         status_open,
4353         NULL,
4354         status_draw,
4355         status_request,
4356         status_grep,
4357         status_select,
4358 };
4361 static bool
4362 stage_diff_line(FILE *pipe, struct line *line)
4364         char *buf = line->data;
4365         size_t bufsize = strlen(buf);
4366         size_t written = 0;
4368         while (!ferror(pipe) && written < bufsize) {
4369                 written += fwrite(buf + written, 1, bufsize - written, pipe);
4370         }
4372         fputc('\n', pipe);
4374         return written == bufsize;
4377 static struct line *
4378 stage_diff_hdr(struct view *view, struct line *line)
4380         int diff_hdr_dir = line->type == LINE_DIFF_CHUNK ? -1 : 1;
4381         struct line *diff_hdr;
4383         if (line->type == LINE_DIFF_CHUNK)
4384                 diff_hdr = line - 1;
4385         else
4386                 diff_hdr = view->line + 1;
4388         while (diff_hdr > view->line && diff_hdr < view->line + view->lines) {
4389                 if (diff_hdr->type == LINE_DIFF_HEADER)
4390                         return diff_hdr;
4392                 diff_hdr += diff_hdr_dir;
4393         }
4395         return NULL;
4398 static bool
4399 stage_update_chunk(struct view *view, struct line *line)
4401         char cmd[SIZEOF_STR];
4402         size_t cmdsize = 0;
4403         struct line *diff_hdr, *diff_chunk, *diff_end;
4404         FILE *pipe;
4406         diff_hdr = stage_diff_hdr(view, line);
4407         if (!diff_hdr)
4408                 return FALSE;
4410         if (opt_cdup[0] &&
4411             !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
4412                 return FALSE;
4414         if (!string_format_from(cmd, &cmdsize,
4415                                 "git apply --cached %s - && "
4416                                 "git update-index -q --unmerged --refresh 2>/dev/null",
4417                                 stage_line_type == LINE_STAT_STAGED ? "-R" : ""))
4418                 return FALSE;
4420         pipe = popen(cmd, "w");
4421         if (!pipe)
4422                 return FALSE;
4424         diff_end = view->line + view->lines;
4425         if (line->type != LINE_DIFF_CHUNK) {
4426                 diff_chunk = diff_hdr;
4428         } else {
4429                 for (diff_chunk = line + 1; diff_chunk < diff_end; diff_chunk++)
4430                         if (diff_chunk->type == LINE_DIFF_CHUNK ||
4431                             diff_chunk->type == LINE_DIFF_HEADER)
4432                                 diff_end = diff_chunk;
4434                 diff_chunk = line;
4436                 while (diff_hdr->type != LINE_DIFF_CHUNK) {
4437                         switch (diff_hdr->type) {
4438                         case LINE_DIFF_HEADER:
4439                         case LINE_DIFF_INDEX:
4440                         case LINE_DIFF_ADD:
4441                         case LINE_DIFF_DEL:
4442                                 break;
4444                         default:
4445                                 diff_hdr++;
4446                                 continue;
4447                         }
4449                         if (!stage_diff_line(pipe, diff_hdr++)) {
4450                                 pclose(pipe);
4451                                 return FALSE;
4452                         }
4453                 }
4454         }
4456         while (diff_chunk < diff_end && stage_diff_line(pipe, diff_chunk))
4457                 diff_chunk++;
4459         pclose(pipe);
4461         if (diff_chunk != diff_end)
4462                 return FALSE;
4464         return TRUE;
4467 static void
4468 stage_update(struct view *view, struct line *line)
4470         if (stage_line_type != LINE_STAT_UNTRACKED &&
4471             (line->type == LINE_DIFF_CHUNK || !stage_status.status)) {
4472                 if (!stage_update_chunk(view, line)) {
4473                         report("Failed to apply chunk");
4474                         return;
4475                 }
4477         } else if (!status_update_file(view, &stage_status, stage_line_type)) {
4478                 report("Failed to update file");
4479                 return;
4480         }
4482         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
4484         view = VIEW(REQ_VIEW_STATUS);
4485         if (view_is_displayed(view))
4486                 status_enter(view, &view->line[view->lineno]);
4489 static enum request
4490 stage_request(struct view *view, enum request request, struct line *line)
4492         switch (request) {
4493         case REQ_STATUS_UPDATE:
4494                 stage_update(view, line);
4495                 break;
4497         case REQ_EDIT:
4498                 if (!stage_status.new.name[0])
4499                         return request;
4501                 open_editor(stage_status.status != '?', stage_status.new.name);
4502                 break;
4504         case REQ_VIEW_BLAME:
4505                 if (stage_status.new.name[0]) {
4506                         string_copy(opt_path, stage_status.new.name);
4507                         opt_ref[0] = 0;
4508                 }
4509                 return request;
4511         case REQ_ENTER:
4512                 pager_request(view, request, line);
4513                 break;
4515         default:
4516                 return request;
4517         }
4519         return REQ_NONE;
4522 static struct view_ops stage_ops = {
4523         "line",
4524         NULL,
4525         pager_read,
4526         pager_draw,
4527         stage_request,
4528         pager_grep,
4529         pager_select,
4530 };
4533 /*
4534  * Revision graph
4535  */
4537 struct commit {
4538         char id[SIZEOF_REV];            /* SHA1 ID. */
4539         char title[128];                /* First line of the commit message. */
4540         char author[75];                /* Author of the commit. */
4541         struct tm time;                 /* Date from the author ident. */
4542         struct ref **refs;              /* Repository references. */
4543         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
4544         size_t graph_size;              /* The width of the graph array. */
4545         bool has_parents;               /* Rewritten --parents seen. */
4546 };
4548 /* Size of rev graph with no  "padding" columns */
4549 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
4551 struct rev_graph {
4552         struct rev_graph *prev, *next, *parents;
4553         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
4554         size_t size;
4555         struct commit *commit;
4556         size_t pos;
4557         unsigned int boundary:1;
4558 };
4560 /* Parents of the commit being visualized. */
4561 static struct rev_graph graph_parents[4];
4563 /* The current stack of revisions on the graph. */
4564 static struct rev_graph graph_stacks[4] = {
4565         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
4566         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
4567         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
4568         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
4569 };
4571 static inline bool
4572 graph_parent_is_merge(struct rev_graph *graph)
4574         return graph->parents->size > 1;
4577 static inline void
4578 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
4580         struct commit *commit = graph->commit;
4582         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
4583                 commit->graph[commit->graph_size++] = symbol;
4586 static void
4587 done_rev_graph(struct rev_graph *graph)
4589         if (graph_parent_is_merge(graph) &&
4590             graph->pos < graph->size - 1 &&
4591             graph->next->size == graph->size + graph->parents->size - 1) {
4592                 size_t i = graph->pos + graph->parents->size - 1;
4594                 graph->commit->graph_size = i * 2;
4595                 while (i < graph->next->size - 1) {
4596                         append_to_rev_graph(graph, ' ');
4597                         append_to_rev_graph(graph, '\\');
4598                         i++;
4599                 }
4600         }
4602         graph->size = graph->pos = 0;
4603         graph->commit = NULL;
4604         memset(graph->parents, 0, sizeof(*graph->parents));
4607 static void
4608 push_rev_graph(struct rev_graph *graph, char *parent)
4610         int i;
4612         /* "Collapse" duplicate parents lines.
4613          *
4614          * FIXME: This needs to also update update the drawn graph but
4615          * for now it just serves as a method for pruning graph lines. */
4616         for (i = 0; i < graph->size; i++)
4617                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
4618                         return;
4620         if (graph->size < SIZEOF_REVITEMS) {
4621                 string_copy_rev(graph->rev[graph->size++], parent);
4622         }
4625 static chtype
4626 get_rev_graph_symbol(struct rev_graph *graph)
4628         chtype symbol;
4630         if (graph->boundary)
4631                 symbol = REVGRAPH_BOUND;
4632         else if (graph->parents->size == 0)
4633                 symbol = REVGRAPH_INIT;
4634         else if (graph_parent_is_merge(graph))
4635                 symbol = REVGRAPH_MERGE;
4636         else if (graph->pos >= graph->size)
4637                 symbol = REVGRAPH_BRANCH;
4638         else
4639                 symbol = REVGRAPH_COMMIT;
4641         return symbol;
4644 static void
4645 draw_rev_graph(struct rev_graph *graph)
4647         struct rev_filler {
4648                 chtype separator, line;
4649         };
4650         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
4651         static struct rev_filler fillers[] = {
4652                 { ' ',  REVGRAPH_LINE },
4653                 { '`',  '.' },
4654                 { '\'', ' ' },
4655                 { '/',  ' ' },
4656         };
4657         chtype symbol = get_rev_graph_symbol(graph);
4658         struct rev_filler *filler;
4659         size_t i;
4661         filler = &fillers[DEFAULT];
4663         for (i = 0; i < graph->pos; i++) {
4664                 append_to_rev_graph(graph, filler->line);
4665                 if (graph_parent_is_merge(graph->prev) &&
4666                     graph->prev->pos == i)
4667                         filler = &fillers[RSHARP];
4669                 append_to_rev_graph(graph, filler->separator);
4670         }
4672         /* Place the symbol for this revision. */
4673         append_to_rev_graph(graph, symbol);
4675         if (graph->prev->size > graph->size)
4676                 filler = &fillers[RDIAG];
4677         else
4678                 filler = &fillers[DEFAULT];
4680         i++;
4682         for (; i < graph->size; i++) {
4683                 append_to_rev_graph(graph, filler->separator);
4684                 append_to_rev_graph(graph, filler->line);
4685                 if (graph_parent_is_merge(graph->prev) &&
4686                     i < graph->prev->pos + graph->parents->size)
4687                         filler = &fillers[RSHARP];
4688                 if (graph->prev->size > graph->size)
4689                         filler = &fillers[LDIAG];
4690         }
4692         if (graph->prev->size > graph->size) {
4693                 append_to_rev_graph(graph, filler->separator);
4694                 if (filler->line != ' ')
4695                         append_to_rev_graph(graph, filler->line);
4696         }
4699 /* Prepare the next rev graph */
4700 static void
4701 prepare_rev_graph(struct rev_graph *graph)
4703         size_t i;
4705         /* First, traverse all lines of revisions up to the active one. */
4706         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
4707                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
4708                         break;
4710                 push_rev_graph(graph->next, graph->rev[graph->pos]);
4711         }
4713         /* Interleave the new revision parent(s). */
4714         for (i = 0; !graph->boundary && i < graph->parents->size; i++)
4715                 push_rev_graph(graph->next, graph->parents->rev[i]);
4717         /* Lastly, put any remaining revisions. */
4718         for (i = graph->pos + 1; i < graph->size; i++)
4719                 push_rev_graph(graph->next, graph->rev[i]);
4722 static void
4723 update_rev_graph(struct rev_graph *graph)
4725         /* If this is the finalizing update ... */
4726         if (graph->commit)
4727                 prepare_rev_graph(graph);
4729         /* Graph visualization needs a one rev look-ahead,
4730          * so the first update doesn't visualize anything. */
4731         if (!graph->prev->commit)
4732                 return;
4734         draw_rev_graph(graph->prev);
4735         done_rev_graph(graph->prev->prev);
4739 /*
4740  * Main view backend
4741  */
4743 static bool
4744 main_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
4746         char buf[DATE_COLS + 1];
4747         struct commit *commit = line->data;
4748         enum line_type type;
4749         int col = 0;
4750         size_t timelen;
4751         int tilde_attr;
4752         int space;
4754         if (!*commit->author)
4755                 return FALSE;
4757         space = view->width;
4758         wmove(view->win, lineno, col);
4760         if (selected) {
4761                 type = LINE_CURSOR;
4762                 wattrset(view->win, get_line_attr(type));
4763                 wchgat(view->win, -1, 0, type, NULL);
4764                 tilde_attr = -1;
4765         } else {
4766                 type = LINE_MAIN_COMMIT;
4767                 wattrset(view->win, get_line_attr(LINE_MAIN_DATE));
4768                 tilde_attr = get_line_attr(LINE_MAIN_DELIM);
4769         }
4771         if (opt_date) {
4772                 int n;
4774                 timelen = strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time);
4775                 n = draw_text(view, buf, view->width - col, FALSE, tilde_attr);
4776                 draw_text(view, " ", view->width - col - n, FALSE, tilde_attr);
4778                 col += DATE_COLS;
4779                 wmove(view->win, lineno, col);
4780                 if (col >= view->width)
4781                         return TRUE;
4782         }
4783         if (type != LINE_CURSOR)
4784                 wattrset(view->win, get_line_attr(LINE_MAIN_AUTHOR));
4786         if (opt_author) {
4787                 int max_len;
4789                 max_len = view->width - col;
4790                 if (max_len > AUTHOR_COLS - 1)
4791                         max_len = AUTHOR_COLS - 1;
4792                 draw_text(view, commit->author, max_len, TRUE, tilde_attr);
4793                 col += AUTHOR_COLS;
4794                 if (col >= view->width)
4795                         return TRUE;
4796         }
4798         if (opt_rev_graph && commit->graph_size) {
4799                 size_t graph_size = view->width - col;
4800                 size_t i;
4802                 if (type != LINE_CURSOR)
4803                         wattrset(view->win, get_line_attr(LINE_MAIN_REVGRAPH));
4804                 wmove(view->win, lineno, col);
4805                 if (graph_size > commit->graph_size)
4806                         graph_size = commit->graph_size;
4807                 /* Using waddch() instead of waddnstr() ensures that
4808                  * they'll be rendered correctly for the cursor line. */
4809                 for (i = 0; i < graph_size; i++)
4810                         waddch(view->win, commit->graph[i]);
4812                 col += commit->graph_size + 1;
4813                 if (col >= view->width)
4814                         return TRUE;
4815                 waddch(view->win, ' ');
4816         }
4817         if (type != LINE_CURSOR)
4818                 wattrset(view->win, A_NORMAL);
4820         wmove(view->win, lineno, col);
4822         if (opt_show_refs && commit->refs) {
4823                 size_t i = 0;
4825                 do {
4826                         if (type == LINE_CURSOR)
4827                                 ;
4828                         else if (commit->refs[i]->ltag)
4829                                 wattrset(view->win, get_line_attr(LINE_MAIN_LOCAL_TAG));
4830                         else if (commit->refs[i]->tag)
4831                                 wattrset(view->win, get_line_attr(LINE_MAIN_TAG));
4832                         else if (commit->refs[i]->remote)
4833                                 wattrset(view->win, get_line_attr(LINE_MAIN_REMOTE));
4834                         else
4835                                 wattrset(view->win, get_line_attr(LINE_MAIN_REF));
4837                         col += draw_text(view, "[", view->width - col, TRUE, tilde_attr);
4838                         col += draw_text(view, commit->refs[i]->name, view->width - col,
4839                                          TRUE, tilde_attr);
4840                         col += draw_text(view, "]", view->width - col, TRUE, tilde_attr);
4841                         if (type != LINE_CURSOR)
4842                                 wattrset(view->win, A_NORMAL);
4843                         col += draw_text(view, " ", view->width - col, TRUE, tilde_attr);
4844                         if (col >= view->width)
4845                                 return TRUE;
4846                 } while (commit->refs[i++]->next);
4847         }
4849         if (type != LINE_CURSOR)
4850                 wattrset(view->win, get_line_attr(type));
4852         draw_text(view, commit->title, view->width - col, TRUE, tilde_attr);
4853         return TRUE;
4856 /* Reads git log --pretty=raw output and parses it into the commit struct. */
4857 static bool
4858 main_read(struct view *view, char *line)
4860         static struct rev_graph *graph = graph_stacks;
4861         enum line_type type;
4862         struct commit *commit;
4864         if (!line) {
4865                 update_rev_graph(graph);
4866                 return TRUE;
4867         }
4869         type = get_line_type(line);
4870         if (type == LINE_COMMIT) {
4871                 commit = calloc(1, sizeof(struct commit));
4872                 if (!commit)
4873                         return FALSE;
4875                 line += STRING_SIZE("commit ");
4876                 if (*line == '-') {
4877                         graph->boundary = 1;
4878                         line++;
4879                 }
4881                 string_copy_rev(commit->id, line);
4882                 commit->refs = get_refs(commit->id);
4883                 graph->commit = commit;
4884                 add_line_data(view, commit, LINE_MAIN_COMMIT);
4886                 while ((line = strchr(line, ' '))) {
4887                         line++;
4888                         push_rev_graph(graph->parents, line);
4889                         commit->has_parents = TRUE;
4890                 }
4891                 return TRUE;
4892         }
4894         if (!view->lines)
4895                 return TRUE;
4896         commit = view->line[view->lines - 1].data;
4898         switch (type) {
4899         case LINE_PARENT:
4900                 if (commit->has_parents)
4901                         break;
4902                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
4903                 break;
4905         case LINE_AUTHOR:
4906         {
4907                 /* Parse author lines where the name may be empty:
4908                  *      author  <email@address.tld> 1138474660 +0100
4909                  */
4910                 char *ident = line + STRING_SIZE("author ");
4911                 char *nameend = strchr(ident, '<');
4912                 char *emailend = strchr(ident, '>');
4914                 if (!nameend || !emailend)
4915                         break;
4917                 update_rev_graph(graph);
4918                 graph = graph->next;
4920                 *nameend = *emailend = 0;
4921                 ident = chomp_string(ident);
4922                 if (!*ident) {
4923                         ident = chomp_string(nameend + 1);
4924                         if (!*ident)
4925                                 ident = "Unknown";
4926                 }
4928                 string_ncopy(commit->author, ident, strlen(ident));
4930                 /* Parse epoch and timezone */
4931                 if (emailend[1] == ' ') {
4932                         char *secs = emailend + 2;
4933                         char *zone = strchr(secs, ' ');
4934                         time_t time = (time_t) atol(secs);
4936                         if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
4937                                 long tz;
4939                                 zone++;
4940                                 tz  = ('0' - zone[1]) * 60 * 60 * 10;
4941                                 tz += ('0' - zone[2]) * 60 * 60;
4942                                 tz += ('0' - zone[3]) * 60;
4943                                 tz += ('0' - zone[4]) * 60;
4945                                 if (zone[0] == '-')
4946                                         tz = -tz;
4948                                 time -= tz;
4949                         }
4951                         gmtime_r(&time, &commit->time);
4952                 }
4953                 break;
4954         }
4955         default:
4956                 /* Fill in the commit title if it has not already been set. */
4957                 if (commit->title[0])
4958                         break;
4960                 /* Require titles to start with a non-space character at the
4961                  * offset used by git log. */
4962                 if (strncmp(line, "    ", 4))
4963                         break;
4964                 line += 4;
4965                 /* Well, if the title starts with a whitespace character,
4966                  * try to be forgiving.  Otherwise we end up with no title. */
4967                 while (isspace(*line))
4968                         line++;
4969                 if (*line == '\0')
4970                         break;
4971                 /* FIXME: More graceful handling of titles; append "..." to
4972                  * shortened titles, etc. */
4974                 string_ncopy(commit->title, line, strlen(line));
4975         }
4977         return TRUE;
4980 static enum request
4981 main_request(struct view *view, enum request request, struct line *line)
4983         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4985         if (request == REQ_ENTER)
4986                 open_view(view, REQ_VIEW_DIFF, flags);
4987         else
4988                 return request;
4990         return REQ_NONE;
4993 static bool
4994 main_grep(struct view *view, struct line *line)
4996         struct commit *commit = line->data;
4997         enum { S_TITLE, S_AUTHOR, S_DATE, S_END } state;
4998         char buf[DATE_COLS + 1];
4999         regmatch_t pmatch;
5001         for (state = S_TITLE; state < S_END; state++) {
5002                 char *text;
5004                 switch (state) {
5005                 case S_TITLE:   text = commit->title;   break;
5006                 case S_AUTHOR:  text = commit->author;  break;
5007                 case S_DATE:
5008                         if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
5009                                 continue;
5010                         text = buf;
5011                         break;
5013                 default:
5014                         return FALSE;
5015                 }
5017                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
5018                         return TRUE;
5019         }
5021         return FALSE;
5024 static void
5025 main_select(struct view *view, struct line *line)
5027         struct commit *commit = line->data;
5029         string_copy_rev(view->ref, commit->id);
5030         string_copy_rev(ref_commit, view->ref);
5033 static struct view_ops main_ops = {
5034         "commit",
5035         NULL,
5036         main_read,
5037         main_draw,
5038         main_request,
5039         main_grep,
5040         main_select,
5041 };
5044 /*
5045  * Unicode / UTF-8 handling
5046  *
5047  * NOTE: Much of the following code for dealing with unicode is derived from
5048  * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
5049  * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
5050  */
5052 /* I've (over)annotated a lot of code snippets because I am not entirely
5053  * confident that the approach taken by this small UTF-8 interface is correct.
5054  * --jonas */
5056 static inline int
5057 unicode_width(unsigned long c)
5059         if (c >= 0x1100 &&
5060            (c <= 0x115f                         /* Hangul Jamo */
5061             || c == 0x2329
5062             || c == 0x232a
5063             || (c >= 0x2e80  && c <= 0xa4cf && c != 0x303f)
5064                                                 /* CJK ... Yi */
5065             || (c >= 0xac00  && c <= 0xd7a3)    /* Hangul Syllables */
5066             || (c >= 0xf900  && c <= 0xfaff)    /* CJK Compatibility Ideographs */
5067             || (c >= 0xfe30  && c <= 0xfe6f)    /* CJK Compatibility Forms */
5068             || (c >= 0xff00  && c <= 0xff60)    /* Fullwidth Forms */
5069             || (c >= 0xffe0  && c <= 0xffe6)
5070             || (c >= 0x20000 && c <= 0x2fffd)
5071             || (c >= 0x30000 && c <= 0x3fffd)))
5072                 return 2;
5074         if (c == '\t')
5075                 return opt_tab_size;
5077         return 1;
5080 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
5081  * Illegal bytes are set one. */
5082 static const unsigned char utf8_bytes[256] = {
5083         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,
5084         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,
5085         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,
5086         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,
5087         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,
5088         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,
5089         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,
5090         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,
5091 };
5093 /* Decode UTF-8 multi-byte representation into a unicode character. */
5094 static inline unsigned long
5095 utf8_to_unicode(const char *string, size_t length)
5097         unsigned long unicode;
5099         switch (length) {
5100         case 1:
5101                 unicode  =   string[0];
5102                 break;
5103         case 2:
5104                 unicode  =  (string[0] & 0x1f) << 6;
5105                 unicode +=  (string[1] & 0x3f);
5106                 break;
5107         case 3:
5108                 unicode  =  (string[0] & 0x0f) << 12;
5109                 unicode += ((string[1] & 0x3f) << 6);
5110                 unicode +=  (string[2] & 0x3f);
5111                 break;
5112         case 4:
5113                 unicode  =  (string[0] & 0x0f) << 18;
5114                 unicode += ((string[1] & 0x3f) << 12);
5115                 unicode += ((string[2] & 0x3f) << 6);
5116                 unicode +=  (string[3] & 0x3f);
5117                 break;
5118         case 5:
5119                 unicode  =  (string[0] & 0x0f) << 24;
5120                 unicode += ((string[1] & 0x3f) << 18);
5121                 unicode += ((string[2] & 0x3f) << 12);
5122                 unicode += ((string[3] & 0x3f) << 6);
5123                 unicode +=  (string[4] & 0x3f);
5124                 break;
5125         case 6:
5126                 unicode  =  (string[0] & 0x01) << 30;
5127                 unicode += ((string[1] & 0x3f) << 24);
5128                 unicode += ((string[2] & 0x3f) << 18);
5129                 unicode += ((string[3] & 0x3f) << 12);
5130                 unicode += ((string[4] & 0x3f) << 6);
5131                 unicode +=  (string[5] & 0x3f);
5132                 break;
5133         default:
5134                 die("Invalid unicode length");
5135         }
5137         /* Invalid characters could return the special 0xfffd value but NUL
5138          * should be just as good. */
5139         return unicode > 0xffff ? 0 : unicode;
5142 /* Calculates how much of string can be shown within the given maximum width
5143  * and sets trimmed parameter to non-zero value if all of string could not be
5144  * shown. If the reserve flag is TRUE, it will reserve at least one
5145  * trailing character, which can be useful when drawing a delimiter.
5146  *
5147  * Returns the number of bytes to output from string to satisfy max_width. */
5148 static size_t
5149 utf8_length(const char *string, size_t max_width, int *trimmed, bool reserve)
5151         const char *start = string;
5152         const char *end = strchr(string, '\0');
5153         unsigned char last_bytes = 0;
5154         size_t width = 0;
5156         *trimmed = 0;
5158         while (string < end) {
5159                 int c = *(unsigned char *) string;
5160                 unsigned char bytes = utf8_bytes[c];
5161                 size_t ucwidth;
5162                 unsigned long unicode;
5164                 if (string + bytes > end)
5165                         break;
5167                 /* Change representation to figure out whether
5168                  * it is a single- or double-width character. */
5170                 unicode = utf8_to_unicode(string, bytes);
5171                 /* FIXME: Graceful handling of invalid unicode character. */
5172                 if (!unicode)
5173                         break;
5175                 ucwidth = unicode_width(unicode);
5176                 width  += ucwidth;
5177                 if (width > max_width) {
5178                         *trimmed = 1;
5179                         if (reserve && width - ucwidth == max_width) {
5180                                 string -= last_bytes;
5181                         }
5182                         break;
5183                 }
5185                 string  += bytes;
5186                 last_bytes = bytes;
5187         }
5189         return string - start;
5193 /*
5194  * Status management
5195  */
5197 /* Whether or not the curses interface has been initialized. */
5198 static bool cursed = FALSE;
5200 /* The status window is used for polling keystrokes. */
5201 static WINDOW *status_win;
5203 static bool status_empty = TRUE;
5205 /* Update status and title window. */
5206 static void
5207 report(const char *msg, ...)
5209         struct view *view = display[current_view];
5211         if (input_mode)
5212                 return;
5214         if (!view) {
5215                 char buf[SIZEOF_STR];
5216                 va_list args;
5218                 va_start(args, msg);
5219                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
5220                         buf[sizeof(buf) - 1] = 0;
5221                         buf[sizeof(buf) - 2] = '.';
5222                         buf[sizeof(buf) - 3] = '.';
5223                         buf[sizeof(buf) - 4] = '.';
5224                 }
5225                 va_end(args);
5226                 die("%s", buf);
5227         }
5229         if (!status_empty || *msg) {
5230                 va_list args;
5232                 va_start(args, msg);
5234                 wmove(status_win, 0, 0);
5235                 if (*msg) {
5236                         vwprintw(status_win, msg, args);
5237                         status_empty = FALSE;
5238                 } else {
5239                         status_empty = TRUE;
5240                 }
5241                 wclrtoeol(status_win);
5242                 wrefresh(status_win);
5244                 va_end(args);
5245         }
5247         update_view_title(view);
5248         update_display_cursor(view);
5251 /* Controls when nodelay should be in effect when polling user input. */
5252 static void
5253 set_nonblocking_input(bool loading)
5255         static unsigned int loading_views;
5257         if ((loading == FALSE && loading_views-- == 1) ||
5258             (loading == TRUE  && loading_views++ == 0))
5259                 nodelay(status_win, loading);
5262 static void
5263 init_display(void)
5265         int x, y;
5267         /* Initialize the curses library */
5268         if (isatty(STDIN_FILENO)) {
5269                 cursed = !!initscr();
5270         } else {
5271                 /* Leave stdin and stdout alone when acting as a pager. */
5272                 FILE *io = fopen("/dev/tty", "r+");
5274                 if (!io)
5275                         die("Failed to open /dev/tty");
5276                 cursed = !!newterm(NULL, io, io);
5277         }
5279         if (!cursed)
5280                 die("Failed to initialize curses");
5282         nonl();         /* Tell curses not to do NL->CR/NL on output */
5283         cbreak();       /* Take input chars one at a time, no wait for \n */
5284         noecho();       /* Don't echo input */
5285         leaveok(stdscr, TRUE);
5287         if (has_colors())
5288                 init_colors();
5290         getmaxyx(stdscr, y, x);
5291         status_win = newwin(1, 0, y - 1, 0);
5292         if (!status_win)
5293                 die("Failed to create status window");
5295         /* Enable keyboard mapping */
5296         keypad(status_win, TRUE);
5297         wbkgdset(status_win, get_line_attr(LINE_STATUS));
5300 static char *
5301 read_prompt(const char *prompt)
5303         enum { READING, STOP, CANCEL } status = READING;
5304         static char buf[sizeof(opt_cmd) - STRING_SIZE("git \0")];
5305         int pos = 0;
5307         while (status == READING) {
5308                 struct view *view;
5309                 int i, key;
5311                 input_mode = TRUE;
5313                 foreach_view (view, i)
5314                         update_view(view);
5316                 input_mode = FALSE;
5318                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
5319                 wclrtoeol(status_win);
5321                 /* Refresh, accept single keystroke of input */
5322                 key = wgetch(status_win);
5323                 switch (key) {
5324                 case KEY_RETURN:
5325                 case KEY_ENTER:
5326                 case '\n':
5327                         status = pos ? STOP : CANCEL;
5328                         break;
5330                 case KEY_BACKSPACE:
5331                         if (pos > 0)
5332                                 pos--;
5333                         else
5334                                 status = CANCEL;
5335                         break;
5337                 case KEY_ESC:
5338                         status = CANCEL;
5339                         break;
5341                 case ERR:
5342                         break;
5344                 default:
5345                         if (pos >= sizeof(buf)) {
5346                                 report("Input string too long");
5347                                 return NULL;
5348                         }
5350                         if (isprint(key))
5351                                 buf[pos++] = (char) key;
5352                 }
5353         }
5355         /* Clear the status window */
5356         status_empty = FALSE;
5357         report("");
5359         if (status == CANCEL)
5360                 return NULL;
5362         buf[pos++] = 0;
5364         return buf;
5367 /*
5368  * Repository references
5369  */
5371 static struct ref *refs = NULL;
5372 static size_t refs_alloc = 0;
5373 static size_t refs_size = 0;
5375 /* Id <-> ref store */
5376 static struct ref ***id_refs = NULL;
5377 static size_t id_refs_alloc = 0;
5378 static size_t id_refs_size = 0;
5380 static struct ref **
5381 get_refs(char *id)
5383         struct ref ***tmp_id_refs;
5384         struct ref **ref_list = NULL;
5385         size_t ref_list_alloc = 0;
5386         size_t ref_list_size = 0;
5387         size_t i;
5389         for (i = 0; i < id_refs_size; i++)
5390                 if (!strcmp(id, id_refs[i][0]->id))
5391                         return id_refs[i];
5393         tmp_id_refs = realloc_items(id_refs, &id_refs_alloc, id_refs_size + 1,
5394                                     sizeof(*id_refs));
5395         if (!tmp_id_refs)
5396                 return NULL;
5398         id_refs = tmp_id_refs;
5400         for (i = 0; i < refs_size; i++) {
5401                 struct ref **tmp;
5403                 if (strcmp(id, refs[i].id))
5404                         continue;
5406                 tmp = realloc_items(ref_list, &ref_list_alloc,
5407                                     ref_list_size + 1, sizeof(*ref_list));
5408                 if (!tmp) {
5409                         if (ref_list)
5410                                 free(ref_list);
5411                         return NULL;
5412                 }
5414                 ref_list = tmp;
5415                 if (ref_list_size > 0)
5416                         ref_list[ref_list_size - 1]->next = 1;
5417                 ref_list[ref_list_size] = &refs[i];
5419                 /* XXX: The properties of the commit chains ensures that we can
5420                  * safely modify the shared ref. The repo references will
5421                  * always be similar for the same id. */
5422                 ref_list[ref_list_size]->next = 0;
5423                 ref_list_size++;
5424         }
5426         if (ref_list)
5427                 id_refs[id_refs_size++] = ref_list;
5429         return ref_list;
5432 static int
5433 read_ref(char *id, size_t idlen, char *name, size_t namelen)
5435         struct ref *ref;
5436         bool tag = FALSE;
5437         bool ltag = FALSE;
5438         bool remote = FALSE;
5439         bool check_replace = FALSE;
5441         if (!strncmp(name, "refs/tags/", STRING_SIZE("refs/tags/"))) {
5442                 if (!strcmp(name + namelen - 3, "^{}")) {
5443                         namelen -= 3;
5444                         name[namelen] = 0;
5445                         if (refs_size > 0 && refs[refs_size - 1].ltag == TRUE)
5446                                 check_replace = TRUE;
5447                 } else {
5448                         ltag = TRUE;
5449                 }
5451                 tag = TRUE;
5452                 namelen -= STRING_SIZE("refs/tags/");
5453                 name    += STRING_SIZE("refs/tags/");
5455         } else if (!strncmp(name, "refs/remotes/", STRING_SIZE("refs/remotes/"))) {
5456                 remote = TRUE;
5457                 namelen -= STRING_SIZE("refs/remotes/");
5458                 name    += STRING_SIZE("refs/remotes/");
5460         } else if (!strncmp(name, "refs/heads/", STRING_SIZE("refs/heads/"))) {
5461                 namelen -= STRING_SIZE("refs/heads/");
5462                 name    += STRING_SIZE("refs/heads/");
5464         } else if (!strcmp(name, "HEAD")) {
5465                 return OK;
5466         }
5468         if (check_replace && !strcmp(name, refs[refs_size - 1].name)) {
5469                 /* it's an annotated tag, replace the previous sha1 with the
5470                  * resolved commit id; relies on the fact git-ls-remote lists
5471                  * the commit id of an annotated tag right beofre the commit id
5472                  * it points to. */
5473                 refs[refs_size - 1].ltag = ltag;
5474                 string_copy_rev(refs[refs_size - 1].id, id);
5476                 return OK;
5477         }
5478         refs = realloc_items(refs, &refs_alloc, refs_size + 1, sizeof(*refs));
5479         if (!refs)
5480                 return ERR;
5482         ref = &refs[refs_size++];
5483         ref->name = malloc(namelen + 1);
5484         if (!ref->name)
5485                 return ERR;
5487         strncpy(ref->name, name, namelen);
5488         ref->name[namelen] = 0;
5489         ref->tag = tag;
5490         ref->ltag = ltag;
5491         ref->remote = remote;
5492         string_copy_rev(ref->id, id);
5494         return OK;
5497 static int
5498 load_refs(void)
5500         const char *cmd_env = getenv("TIG_LS_REMOTE");
5501         const char *cmd = cmd_env && *cmd_env ? cmd_env : TIG_LS_REMOTE;
5503         return read_properties(popen(cmd, "r"), "\t", read_ref);
5506 static int
5507 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
5509         if (!strcmp(name, "i18n.commitencoding"))
5510                 string_ncopy(opt_encoding, value, valuelen);
5512         if (!strcmp(name, "core.editor"))
5513                 string_ncopy(opt_editor, value, valuelen);
5515         return OK;
5518 static int
5519 load_repo_config(void)
5521         return read_properties(popen(GIT_CONFIG " --list", "r"),
5522                                "=", read_repo_config_option);
5525 static int
5526 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
5528         if (!opt_git_dir[0]) {
5529                 string_ncopy(opt_git_dir, name, namelen);
5531         } else if (opt_is_inside_work_tree == -1) {
5532                 /* This can be 3 different values depending on the
5533                  * version of git being used. If git-rev-parse does not
5534                  * understand --is-inside-work-tree it will simply echo
5535                  * the option else either "true" or "false" is printed.
5536                  * Default to true for the unknown case. */
5537                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
5539         } else {
5540                 string_ncopy(opt_cdup, name, namelen);
5541         }
5543         return OK;
5546 /* XXX: The line outputted by "--show-cdup" can be empty so the option
5547  * must be the last one! */
5548 static int
5549 load_repo_info(void)
5551         return read_properties(popen("git rev-parse --git-dir --is-inside-work-tree --show-cdup 2>/dev/null", "r"),
5552                                "=", read_repo_info);
5555 static int
5556 read_properties(FILE *pipe, const char *separators,
5557                 int (*read_property)(char *, size_t, char *, size_t))
5559         char buffer[BUFSIZ];
5560         char *name;
5561         int state = OK;
5563         if (!pipe)
5564                 return ERR;
5566         while (state == OK && (name = fgets(buffer, sizeof(buffer), pipe))) {
5567                 char *value;
5568                 size_t namelen;
5569                 size_t valuelen;
5571                 name = chomp_string(name);
5572                 namelen = strcspn(name, separators);
5574                 if (name[namelen]) {
5575                         name[namelen] = 0;
5576                         value = chomp_string(name + namelen + 1);
5577                         valuelen = strlen(value);
5579                 } else {
5580                         value = "";
5581                         valuelen = 0;
5582                 }
5584                 state = read_property(name, namelen, value, valuelen);
5585         }
5587         if (state != ERR && ferror(pipe))
5588                 state = ERR;
5590         pclose(pipe);
5592         return state;
5596 /*
5597  * Main
5598  */
5600 static void __NORETURN
5601 quit(int sig)
5603         /* XXX: Restore tty modes and let the OS cleanup the rest! */
5604         if (cursed)
5605                 endwin();
5606         exit(0);
5609 static void __NORETURN
5610 die(const char *err, ...)
5612         va_list args;
5614         endwin();
5616         va_start(args, err);
5617         fputs("tig: ", stderr);
5618         vfprintf(stderr, err, args);
5619         fputs("\n", stderr);
5620         va_end(args);
5622         exit(1);
5625 static void
5626 warn(const char *msg, ...)
5628         va_list args;
5630         va_start(args, msg);
5631         fputs("tig warning: ", stderr);
5632         vfprintf(stderr, msg, args);
5633         fputs("\n", stderr);
5634         va_end(args);
5637 int
5638 main(int argc, char *argv[])
5640         struct view *view;
5641         enum request request;
5642         size_t i;
5644         signal(SIGINT, quit);
5646         if (setlocale(LC_ALL, "")) {
5647                 char *codeset = nl_langinfo(CODESET);
5649                 string_ncopy(opt_codeset, codeset, strlen(codeset));
5650         }
5652         if (load_repo_info() == ERR)
5653                 die("Failed to load repo info.");
5655         if (load_options() == ERR)
5656                 die("Failed to load user config.");
5658         /* Load the repo config file so options can be overwritten from
5659          * the command line. */
5660         if (load_repo_config() == ERR)
5661                 die("Failed to load repo config.");
5663         if (!parse_options(argc, argv))
5664                 return 0;
5666         /* Require a git repository unless when running in pager mode. */
5667         if (!opt_git_dir[0])
5668                 die("Not a git repository");
5670         if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
5671                 opt_utf8 = FALSE;
5673         if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
5674                 opt_iconv = iconv_open(opt_codeset, opt_encoding);
5675                 if (opt_iconv == ICONV_NONE)
5676                         die("Failed to initialize character set conversion");
5677         }
5679         if (load_refs() == ERR)
5680                 die("Failed to load refs.");
5682         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
5683                 view->cmd_env = getenv(view->cmd_env);
5685         request = opt_request;
5687         init_display();
5689         while (view_driver(display[current_view], request)) {
5690                 int key;
5691                 int i;
5693                 foreach_view (view, i)
5694                         update_view(view);
5696                 /* Refresh, accept single keystroke of input */
5697                 key = wgetch(status_win);
5699                 /* wgetch() with nodelay() enabled returns ERR when there's no
5700                  * input. */
5701                 if (key == ERR) {
5702                         request = REQ_NONE;
5703                         continue;
5704                 }
5706                 request = get_keybinding(display[current_view]->keymap, key);
5708                 /* Some low-level request handling. This keeps access to
5709                  * status_win restricted. */
5710                 switch (request) {
5711                 case REQ_PROMPT:
5712                 {
5713                         char *cmd = read_prompt(":");
5715                         if (cmd && string_format(opt_cmd, "git %s", cmd)) {
5716                                 if (strncmp(cmd, "show", 4) && isspace(cmd[4])) {
5717                                         opt_request = REQ_VIEW_DIFF;
5718                                 } else {
5719                                         opt_request = REQ_VIEW_PAGER;
5720                                 }
5721                                 break;
5722                         }
5724                         request = REQ_NONE;
5725                         break;
5726                 }
5727                 case REQ_SEARCH:
5728                 case REQ_SEARCH_BACK:
5729                 {
5730                         const char *prompt = request == REQ_SEARCH
5731                                            ? "/" : "?";
5732                         char *search = read_prompt(prompt);
5734                         if (search)
5735                                 string_ncopy(opt_search, search, strlen(search));
5736                         else
5737                                 request = REQ_NONE;
5738                         break;
5739                 }
5740                 case REQ_SCREEN_RESIZE:
5741                 {
5742                         int height, width;
5744                         getmaxyx(stdscr, height, width);
5746                         /* Resize the status view and let the view driver take
5747                          * care of resizing the displayed views. */
5748                         wresize(status_win, 1, width);
5749                         mvwin(status_win, height - 1, 0);
5750                         wrefresh(status_win);
5751                         break;
5752                 }
5753                 default:
5754                         break;
5755                 }
5756         }
5758         quit(0);
5760         return 0;