Code

Replace opt_no_head with opt_head_rev in order to save the HEAD rev
[tig.git] / tig.c
1 /* Copyright (c) 2006-2008 Jonas Fonseca <fonseca@diku.dk>
2  *
3  * This program is free software; you can redistribute it and/or
4  * modify it under the terms of the GNU General Public License as
5  * published by the Free Software Foundation; either version 2 of
6  * the License, or (at your option) any later version.
7  *
8  * This program is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11  * GNU General Public License for more details.
12  */
14 #ifdef HAVE_CONFIG_H
15 #include "config.h"
16 #endif
18 #ifndef TIG_VERSION
19 #define TIG_VERSION "unknown-version"
20 #endif
22 #ifndef DEBUG
23 #define NDEBUG
24 #endif
26 #include <assert.h>
27 #include <errno.h>
28 #include <ctype.h>
29 #include <signal.h>
30 #include <stdarg.h>
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <sys/types.h>
35 #include <sys/stat.h>
36 #include <unistd.h>
37 #include <time.h>
39 #include <regex.h>
41 #include <locale.h>
42 #include <langinfo.h>
43 #include <iconv.h>
45 /* ncurses(3): Must be defined to have extended wide-character functions. */
46 #define _XOPEN_SOURCE_EXTENDED
48 #ifdef HAVE_NCURSESW_NCURSES_H
49 #include <ncursesw/ncurses.h>
50 #else
51 #ifdef HAVE_NCURSES_NCURSES_H
52 #include <ncurses/ncurses.h>
53 #else
54 #include <ncurses.h>
55 #endif
56 #endif
58 #if __GNUC__ >= 3
59 #define __NORETURN __attribute__((__noreturn__))
60 #else
61 #define __NORETURN
62 #endif
64 static void __NORETURN die(const char *err, ...);
65 static void warn(const char *msg, ...);
66 static void report(const char *msg, ...);
67 static int read_properties(FILE *pipe, const char *separators, int (*read)(char *, size_t, char *, size_t));
68 static void set_nonblocking_input(bool loading);
69 static size_t utf8_length(const char *string, int *width, size_t max_width, int *trimmed, bool reserve);
70 static bool prompt_yesno(const char *prompt);
71 static int load_refs(void);
73 #define ABS(x)          ((x) >= 0  ? (x) : -(x))
74 #define MIN(x, y)       ((x) < (y) ? (x) :  (y))
76 #define ARRAY_SIZE(x)   (sizeof(x) / sizeof(x[0]))
77 #define STRING_SIZE(x)  (sizeof(x) - 1)
79 #define SIZEOF_STR      1024    /* Default string size. */
80 #define SIZEOF_REF      256     /* Size of symbolic or SHA1 ID. */
81 #define SIZEOF_REV      41      /* Holds a SHA-1 and an ending NUL. */
82 #define SIZEOF_ARG      32      /* Default argument array size. */
84 /* Revision graph */
86 #define REVGRAPH_INIT   'I'
87 #define REVGRAPH_MERGE  'M'
88 #define REVGRAPH_BRANCH '+'
89 #define REVGRAPH_COMMIT '*'
90 #define REVGRAPH_BOUND  '^'
92 #define SIZEOF_REVGRAPH 19      /* Size of revision ancestry graphics. */
94 /* This color name can be used to refer to the default term colors. */
95 #define COLOR_DEFAULT   (-1)
97 #define ICONV_NONE      ((iconv_t) -1)
98 #ifndef ICONV_CONST
99 #define ICONV_CONST     /* nothing */
100 #endif
102 /* The format and size of the date column in the main view. */
103 #define DATE_FORMAT     "%Y-%m-%d %H:%M"
104 #define DATE_COLS       STRING_SIZE("2006-04-29 14:21 ")
106 #define AUTHOR_COLS     20
107 #define ID_COLS         8
109 /* The default interval between line numbers. */
110 #define NUMBER_INTERVAL 5
112 #define TAB_SIZE        8
114 #define SCALE_SPLIT_VIEW(height)        ((height) * 2 / 3)
116 #define NULL_ID         "0000000000000000000000000000000000000000"
118 #ifndef GIT_CONFIG
119 #define GIT_CONFIG "config"
120 #endif
122 #define TIG_LS_REMOTE \
123         "git ls-remote . 2>/dev/null"
125 #define TIG_DIFF_CMD \
126         "git show --pretty=fuller --no-color --root --patch-with-stat --find-copies-harder -C %s 2>/dev/null"
128 #define TIG_LOG_CMD     \
129         "git log --no-color --cc --stat -n100 %s 2>/dev/null"
131 #define TIG_MAIN_BASE \
132         "git log --no-color --pretty=raw --parents --topo-order"
134 #define TIG_MAIN_CMD \
135         TIG_MAIN_BASE " %s 2>/dev/null"
137 #define TIG_TREE_CMD    \
138         "git ls-tree %s %s"
140 #define TIG_BLOB_CMD    \
141         "git cat-file blob %s"
143 /* XXX: Needs to be defined to the empty string. */
144 #define TIG_HELP_CMD    ""
145 #define TIG_PAGER_CMD   ""
146 #define TIG_STATUS_CMD  ""
147 #define TIG_STAGE_CMD   ""
148 #define TIG_BLAME_CMD   ""
150 /* Some ascii-shorthands fitted into the ncurses namespace. */
151 #define KEY_TAB         '\t'
152 #define KEY_RETURN      '\r'
153 #define KEY_ESC         27
156 struct ref {
157         char *name;             /* Ref name; tag or head names are shortened. */
158         char id[SIZEOF_REV];    /* Commit SHA1 ID */
159         unsigned int head:1;    /* Is it the current HEAD? */
160         unsigned int tag:1;     /* Is it a tag? */
161         unsigned int ltag:1;    /* If so, is the tag local? */
162         unsigned int remote:1;  /* Is it a remote ref? */
163         unsigned int tracked:1; /* Is it the remote for the current HEAD? */
164         unsigned int next:1;    /* For ref lists: are there more refs? */
165 };
167 static struct ref **get_refs(const char *id);
169 struct int_map {
170         const char *name;
171         int namelen;
172         int value;
173 };
175 static int
176 set_from_int_map(struct int_map *map, size_t map_size,
177                  int *value, const char *name, int namelen)
180         int i;
182         for (i = 0; i < map_size; i++)
183                 if (namelen == map[i].namelen &&
184                     !strncasecmp(name, map[i].name, namelen)) {
185                         *value = map[i].value;
186                         return OK;
187                 }
189         return ERR;
193 /*
194  * String helpers
195  */
197 static inline void
198 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
200         if (srclen > dstlen - 1)
201                 srclen = dstlen - 1;
203         strncpy(dst, src, srclen);
204         dst[srclen] = 0;
207 /* Shorthands for safely copying into a fixed buffer. */
209 #define string_copy(dst, src) \
210         string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
212 #define string_ncopy(dst, src, srclen) \
213         string_ncopy_do(dst, sizeof(dst), src, srclen)
215 #define string_copy_rev(dst, src) \
216         string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
218 #define string_add(dst, from, src) \
219         string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
221 static char *
222 chomp_string(char *name)
224         int namelen;
226         while (isspace(*name))
227                 name++;
229         namelen = strlen(name) - 1;
230         while (namelen > 0 && isspace(name[namelen]))
231                 name[namelen--] = 0;
233         return name;
236 static bool
237 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
239         va_list args;
240         size_t pos = bufpos ? *bufpos : 0;
242         va_start(args, fmt);
243         pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
244         va_end(args);
246         if (bufpos)
247                 *bufpos = pos;
249         return pos >= bufsize ? FALSE : TRUE;
252 #define string_format(buf, fmt, args...) \
253         string_nformat(buf, sizeof(buf), NULL, fmt, args)
255 #define string_format_from(buf, from, fmt, args...) \
256         string_nformat(buf, sizeof(buf), from, fmt, args)
258 static int
259 string_enum_compare(const char *str1, const char *str2, int len)
261         size_t i;
263 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
265         /* Diff-Header == DIFF_HEADER */
266         for (i = 0; i < len; i++) {
267                 if (toupper(str1[i]) == toupper(str2[i]))
268                         continue;
270                 if (string_enum_sep(str1[i]) &&
271                     string_enum_sep(str2[i]))
272                         continue;
274                 return str1[i] - str2[i];
275         }
277         return 0;
280 #define prefixcmp(str1, str2) \
281         strncmp(str1, str2, STRING_SIZE(str2))
283 static inline int
284 suffixcmp(const char *str, int slen, const char *suffix)
286         size_t len = slen >= 0 ? slen : strlen(str);
287         size_t suffixlen = strlen(suffix);
289         return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
292 /* Shell quoting
293  *
294  * NOTE: The following is a slightly modified copy of the git project's shell
295  * quoting routines found in the quote.c file.
296  *
297  * Help to copy the thing properly quoted for the shell safety.  any single
298  * quote is replaced with '\'', any exclamation point is replaced with '\!',
299  * and the whole thing is enclosed in a
300  *
301  * E.g.
302  *  original     sq_quote     result
303  *  name     ==> name      ==> 'name'
304  *  a b      ==> a b       ==> 'a b'
305  *  a'b      ==> a'\''b    ==> 'a'\''b'
306  *  a!b      ==> a'\!'b    ==> 'a'\!'b'
307  */
309 static size_t
310 sq_quote(char buf[SIZEOF_STR], size_t bufsize, const char *src)
312         char c;
314 #define BUFPUT(x) do { if (bufsize < SIZEOF_STR) buf[bufsize++] = (x); } while (0)
316         BUFPUT('\'');
317         while ((c = *src++)) {
318                 if (c == '\'' || c == '!') {
319                         BUFPUT('\'');
320                         BUFPUT('\\');
321                         BUFPUT(c);
322                         BUFPUT('\'');
323                 } else {
324                         BUFPUT(c);
325                 }
326         }
327         BUFPUT('\'');
329         if (bufsize < SIZEOF_STR)
330                 buf[bufsize] = 0;
332         return bufsize;
336 /*
337  * User requests
338  */
340 #define REQ_INFO \
341         /* XXX: Keep the view request first and in sync with views[]. */ \
342         REQ_GROUP("View switching") \
343         REQ_(VIEW_MAIN,         "Show main view"), \
344         REQ_(VIEW_DIFF,         "Show diff view"), \
345         REQ_(VIEW_LOG,          "Show log view"), \
346         REQ_(VIEW_TREE,         "Show tree view"), \
347         REQ_(VIEW_BLOB,         "Show blob view"), \
348         REQ_(VIEW_BLAME,        "Show blame view"), \
349         REQ_(VIEW_HELP,         "Show help page"), \
350         REQ_(VIEW_PAGER,        "Show pager view"), \
351         REQ_(VIEW_STATUS,       "Show status view"), \
352         REQ_(VIEW_STAGE,        "Show stage view"), \
353         \
354         REQ_GROUP("View manipulation") \
355         REQ_(ENTER,             "Enter current line and scroll"), \
356         REQ_(NEXT,              "Move to next"), \
357         REQ_(PREVIOUS,          "Move to previous"), \
358         REQ_(VIEW_NEXT,         "Move focus to next view"), \
359         REQ_(REFRESH,           "Reload and refresh"), \
360         REQ_(MAXIMIZE,          "Maximize the current view"), \
361         REQ_(VIEW_CLOSE,        "Close the current view"), \
362         REQ_(QUIT,              "Close all views and quit"), \
363         \
364         REQ_GROUP("View specific requests") \
365         REQ_(STATUS_UPDATE,     "Update file status"), \
366         REQ_(STATUS_REVERT,     "Revert file changes"), \
367         REQ_(STATUS_MERGE,      "Merge file using external tool"), \
368         REQ_(STAGE_NEXT,        "Find next chunk to stage"), \
369         REQ_(TREE_PARENT,       "Switch to parent directory in tree view"), \
370         \
371         REQ_GROUP("Cursor navigation") \
372         REQ_(MOVE_UP,           "Move cursor one line up"), \
373         REQ_(MOVE_DOWN,         "Move cursor one line down"), \
374         REQ_(MOVE_PAGE_DOWN,    "Move cursor one page down"), \
375         REQ_(MOVE_PAGE_UP,      "Move cursor one page up"), \
376         REQ_(MOVE_FIRST_LINE,   "Move cursor to first line"), \
377         REQ_(MOVE_LAST_LINE,    "Move cursor to last line"), \
378         \
379         REQ_GROUP("Scrolling") \
380         REQ_(SCROLL_LINE_UP,    "Scroll one line up"), \
381         REQ_(SCROLL_LINE_DOWN,  "Scroll one line down"), \
382         REQ_(SCROLL_PAGE_UP,    "Scroll one page up"), \
383         REQ_(SCROLL_PAGE_DOWN,  "Scroll one page down"), \
384         \
385         REQ_GROUP("Searching") \
386         REQ_(SEARCH,            "Search the view"), \
387         REQ_(SEARCH_BACK,       "Search backwards in the view"), \
388         REQ_(FIND_NEXT,         "Find next search match"), \
389         REQ_(FIND_PREV,         "Find previous search match"), \
390         \
391         REQ_GROUP("Option manipulation") \
392         REQ_(TOGGLE_LINENO,     "Toggle line numbers"), \
393         REQ_(TOGGLE_DATE,       "Toggle date display"), \
394         REQ_(TOGGLE_AUTHOR,     "Toggle author display"), \
395         REQ_(TOGGLE_REV_GRAPH,  "Toggle revision graph visualization"), \
396         REQ_(TOGGLE_REFS,       "Toggle reference display (tags/branches)"), \
397         \
398         REQ_GROUP("Misc") \
399         REQ_(PROMPT,            "Bring up the prompt"), \
400         REQ_(SCREEN_REDRAW,     "Redraw the screen"), \
401         REQ_(SCREEN_RESIZE,     "Resize the screen"), \
402         REQ_(SHOW_VERSION,      "Show version information"), \
403         REQ_(STOP_LOADING,      "Stop all loading views"), \
404         REQ_(EDIT,              "Open in editor"), \
405         REQ_(NONE,              "Do nothing")
408 /* User action requests. */
409 enum request {
410 #define REQ_GROUP(help)
411 #define REQ_(req, help) REQ_##req
413         /* Offset all requests to avoid conflicts with ncurses getch values. */
414         REQ_OFFSET = KEY_MAX + 1,
415         REQ_INFO
417 #undef  REQ_GROUP
418 #undef  REQ_
419 };
421 struct request_info {
422         enum request request;
423         const char *name;
424         int namelen;
425         const char *help;
426 };
428 static struct request_info req_info[] = {
429 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
430 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
431         REQ_INFO
432 #undef  REQ_GROUP
433 #undef  REQ_
434 };
436 static enum request
437 get_request(const char *name)
439         int namelen = strlen(name);
440         int i;
442         for (i = 0; i < ARRAY_SIZE(req_info); i++)
443                 if (req_info[i].namelen == namelen &&
444                     !string_enum_compare(req_info[i].name, name, namelen))
445                         return req_info[i].request;
447         return REQ_NONE;
451 /*
452  * Options
453  */
455 static const char usage[] =
456 "tig " TIG_VERSION " (" __DATE__ ")\n"
457 "\n"
458 "Usage: tig        [options] [revs] [--] [paths]\n"
459 "   or: tig show   [options] [revs] [--] [paths]\n"
460 "   or: tig blame  [rev] path\n"
461 "   or: tig status\n"
462 "   or: tig <      [git command output]\n"
463 "\n"
464 "Options:\n"
465 "  -v, --version   Show version and exit\n"
466 "  -h, --help      Show help message and exit";
468 /* Option and state variables. */
469 static bool opt_date                    = TRUE;
470 static bool opt_author                  = TRUE;
471 static bool opt_line_number             = FALSE;
472 static bool opt_line_graphics           = TRUE;
473 static bool opt_rev_graph               = FALSE;
474 static bool opt_show_refs               = TRUE;
475 static int opt_num_interval             = NUMBER_INTERVAL;
476 static int opt_tab_size                 = TAB_SIZE;
477 static int opt_author_cols              = AUTHOR_COLS-1;
478 static char opt_cmd[SIZEOF_STR]         = "";
479 static char opt_path[SIZEOF_STR]        = "";
480 static char opt_file[SIZEOF_STR]        = "";
481 static char opt_ref[SIZEOF_REF]         = "";
482 static char opt_head[SIZEOF_REF]        = "";
483 static char opt_head_rev[SIZEOF_REV]    = "";
484 static char opt_remote[SIZEOF_REF]      = "";
485 static FILE *opt_pipe                   = NULL;
486 static char opt_encoding[20]            = "UTF-8";
487 static bool opt_utf8                    = TRUE;
488 static char opt_codeset[20]             = "UTF-8";
489 static iconv_t opt_iconv                = ICONV_NONE;
490 static char opt_search[SIZEOF_STR]      = "";
491 static char opt_cdup[SIZEOF_STR]        = "";
492 static char opt_git_dir[SIZEOF_STR]     = "";
493 static signed char opt_is_inside_work_tree      = -1; /* set to TRUE or FALSE */
494 static char opt_editor[SIZEOF_STR]      = "";
495 static FILE *opt_tty                    = NULL;
497 #define is_initial_commit()     (!*opt_head_rev)
499 static enum request
500 parse_options(int argc, const char *argv[])
502         enum request request = REQ_VIEW_MAIN;
503         size_t buf_size;
504         const char *subcommand;
505         bool seen_dashdash = FALSE;
506         int i;
508         if (!isatty(STDIN_FILENO)) {
509                 opt_pipe = stdin;
510                 return REQ_VIEW_PAGER;
511         }
513         if (argc <= 1)
514                 return REQ_VIEW_MAIN;
516         subcommand = argv[1];
517         if (!strcmp(subcommand, "status") || !strcmp(subcommand, "-S")) {
518                 if (!strcmp(subcommand, "-S"))
519                         warn("`-S' has been deprecated; use `tig status' instead");
520                 if (argc > 2)
521                         warn("ignoring arguments after `%s'", subcommand);
522                 return REQ_VIEW_STATUS;
524         } else if (!strcmp(subcommand, "blame")) {
525                 if (argc <= 2 || argc > 4)
526                         die("invalid number of options to blame\n\n%s", usage);
528                 i = 2;
529                 if (argc == 4) {
530                         string_ncopy(opt_ref, argv[i], strlen(argv[i]));
531                         i++;
532                 }
534                 string_ncopy(opt_file, argv[i], strlen(argv[i]));
535                 return REQ_VIEW_BLAME;
537         } else if (!strcmp(subcommand, "show")) {
538                 request = REQ_VIEW_DIFF;
540         } else if (!strcmp(subcommand, "log") || !strcmp(subcommand, "diff")) {
541                 request = subcommand[0] == 'l' ? REQ_VIEW_LOG : REQ_VIEW_DIFF;
542                 warn("`tig %s' has been deprecated", subcommand);
544         } else {
545                 subcommand = NULL;
546         }
548         if (!subcommand)
549                 /* XXX: This is vulnerable to the user overriding
550                  * options required for the main view parser. */
551                 string_copy(opt_cmd, TIG_MAIN_BASE);
552         else
553                 string_format(opt_cmd, "git %s", subcommand);
555         buf_size = strlen(opt_cmd);
557         for (i = 1 + !!subcommand; i < argc; i++) {
558                 const char *opt = argv[i];
560                 if (seen_dashdash || !strcmp(opt, "--")) {
561                         seen_dashdash = TRUE;
563                 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
564                         printf("tig version %s\n", TIG_VERSION);
565                         return REQ_NONE;
567                 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
568                         printf("%s\n", usage);
569                         return REQ_NONE;
570                 }
572                 opt_cmd[buf_size++] = ' ';
573                 buf_size = sq_quote(opt_cmd, buf_size, opt);
574                 if (buf_size >= sizeof(opt_cmd))
575                         die("command too long");
576         }
578         opt_cmd[buf_size] = 0;
580         return request;
584 /*
585  * Line-oriented content detection.
586  */
588 #define LINE_INFO \
589 LINE(DIFF_HEADER,  "diff --git ",       COLOR_YELLOW,   COLOR_DEFAULT,  0), \
590 LINE(DIFF_CHUNK,   "@@",                COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
591 LINE(DIFF_ADD,     "+",                 COLOR_GREEN,    COLOR_DEFAULT,  0), \
592 LINE(DIFF_DEL,     "-",                 COLOR_RED,      COLOR_DEFAULT,  0), \
593 LINE(DIFF_INDEX,        "index ",         COLOR_BLUE,   COLOR_DEFAULT,  0), \
594 LINE(DIFF_OLDMODE,      "old file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
595 LINE(DIFF_NEWMODE,      "new file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
596 LINE(DIFF_COPY_FROM,    "copy from",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
597 LINE(DIFF_COPY_TO,      "copy to",        COLOR_YELLOW, COLOR_DEFAULT,  0), \
598 LINE(DIFF_RENAME_FROM,  "rename from",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
599 LINE(DIFF_RENAME_TO,    "rename to",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
600 LINE(DIFF_SIMILARITY,   "similarity ",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
601 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
602 LINE(DIFF_TREE,         "diff-tree ",     COLOR_BLUE,   COLOR_DEFAULT,  0), \
603 LINE(PP_AUTHOR,    "Author: ",          COLOR_CYAN,     COLOR_DEFAULT,  0), \
604 LINE(PP_COMMIT,    "Commit: ",          COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
605 LINE(PP_MERGE,     "Merge: ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
606 LINE(PP_DATE,      "Date:   ",          COLOR_YELLOW,   COLOR_DEFAULT,  0), \
607 LINE(PP_ADATE,     "AuthorDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
608 LINE(PP_CDATE,     "CommitDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
609 LINE(PP_REFS,      "Refs: ",            COLOR_RED,      COLOR_DEFAULT,  0), \
610 LINE(COMMIT,       "commit ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
611 LINE(PARENT,       "parent ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
612 LINE(TREE,         "tree ",             COLOR_BLUE,     COLOR_DEFAULT,  0), \
613 LINE(AUTHOR,       "author ",           COLOR_CYAN,     COLOR_DEFAULT,  0), \
614 LINE(COMMITTER,    "committer ",        COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
615 LINE(SIGNOFF,      "    Signed-off-by", COLOR_YELLOW,   COLOR_DEFAULT,  0), \
616 LINE(ACKED,        "    Acked-by",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
617 LINE(DEFAULT,      "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
618 LINE(CURSOR,       "",                  COLOR_WHITE,    COLOR_GREEN,    A_BOLD), \
619 LINE(STATUS,       "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
620 LINE(DELIMITER,    "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
621 LINE(DATE,         "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
622 LINE(LINE_NUMBER,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
623 LINE(TITLE_BLUR,   "",                  COLOR_WHITE,    COLOR_BLUE,     0), \
624 LINE(TITLE_FOCUS,  "",                  COLOR_WHITE,    COLOR_BLUE,     A_BOLD), \
625 LINE(MAIN_AUTHOR,  "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
626 LINE(MAIN_COMMIT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
627 LINE(MAIN_TAG,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
628 LINE(MAIN_LOCAL_TAG,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
629 LINE(MAIN_REMOTE,  "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
630 LINE(MAIN_TRACKED, "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_BOLD), \
631 LINE(MAIN_REF,     "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
632 LINE(MAIN_HEAD,    "",                  COLOR_CYAN,     COLOR_DEFAULT,  A_BOLD), \
633 LINE(MAIN_REVGRAPH,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
634 LINE(TREE_DIR,     "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
635 LINE(TREE_FILE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
636 LINE(STAT_HEAD,    "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
637 LINE(STAT_SECTION, "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
638 LINE(STAT_NONE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
639 LINE(STAT_STAGED,  "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
640 LINE(STAT_UNSTAGED,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
641 LINE(STAT_UNTRACKED,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
642 LINE(BLAME_ID,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0)
644 enum line_type {
645 #define LINE(type, line, fg, bg, attr) \
646         LINE_##type
647         LINE_INFO,
648         LINE_NONE
649 #undef  LINE
650 };
652 struct line_info {
653         const char *name;       /* Option name. */
654         int namelen;            /* Size of option name. */
655         const char *line;       /* The start of line to match. */
656         int linelen;            /* Size of string to match. */
657         int fg, bg, attr;       /* Color and text attributes for the lines. */
658 };
660 static struct line_info line_info[] = {
661 #define LINE(type, line, fg, bg, attr) \
662         { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
663         LINE_INFO
664 #undef  LINE
665 };
667 static enum line_type
668 get_line_type(const char *line)
670         int linelen = strlen(line);
671         enum line_type type;
673         for (type = 0; type < ARRAY_SIZE(line_info); type++)
674                 /* Case insensitive search matches Signed-off-by lines better. */
675                 if (linelen >= line_info[type].linelen &&
676                     !strncasecmp(line_info[type].line, line, line_info[type].linelen))
677                         return type;
679         return LINE_DEFAULT;
682 static inline int
683 get_line_attr(enum line_type type)
685         assert(type < ARRAY_SIZE(line_info));
686         return COLOR_PAIR(type) | line_info[type].attr;
689 static struct line_info *
690 get_line_info(const char *name)
692         size_t namelen = strlen(name);
693         enum line_type type;
695         for (type = 0; type < ARRAY_SIZE(line_info); type++)
696                 if (namelen == line_info[type].namelen &&
697                     !string_enum_compare(line_info[type].name, name, namelen))
698                         return &line_info[type];
700         return NULL;
703 static void
704 init_colors(void)
706         int default_bg = line_info[LINE_DEFAULT].bg;
707         int default_fg = line_info[LINE_DEFAULT].fg;
708         enum line_type type;
710         start_color();
712         if (assume_default_colors(default_fg, default_bg) == ERR) {
713                 default_bg = COLOR_BLACK;
714                 default_fg = COLOR_WHITE;
715         }
717         for (type = 0; type < ARRAY_SIZE(line_info); type++) {
718                 struct line_info *info = &line_info[type];
719                 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
720                 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
722                 init_pair(type, fg, bg);
723         }
726 struct line {
727         enum line_type type;
729         /* State flags */
730         unsigned int selected:1;
731         unsigned int dirty:1;
733         void *data;             /* User data */
734 };
737 /*
738  * Keys
739  */
741 struct keybinding {
742         int alias;
743         enum request request;
744         struct keybinding *next;
745 };
747 static struct keybinding default_keybindings[] = {
748         /* View switching */
749         { 'm',          REQ_VIEW_MAIN },
750         { 'd',          REQ_VIEW_DIFF },
751         { 'l',          REQ_VIEW_LOG },
752         { 't',          REQ_VIEW_TREE },
753         { 'f',          REQ_VIEW_BLOB },
754         { 'B',          REQ_VIEW_BLAME },
755         { 'p',          REQ_VIEW_PAGER },
756         { 'h',          REQ_VIEW_HELP },
757         { 'S',          REQ_VIEW_STATUS },
758         { 'c',          REQ_VIEW_STAGE },
760         /* View manipulation */
761         { 'q',          REQ_VIEW_CLOSE },
762         { KEY_TAB,      REQ_VIEW_NEXT },
763         { KEY_RETURN,   REQ_ENTER },
764         { KEY_UP,       REQ_PREVIOUS },
765         { KEY_DOWN,     REQ_NEXT },
766         { 'R',          REQ_REFRESH },
767         { KEY_F(5),     REQ_REFRESH },
768         { 'O',          REQ_MAXIMIZE },
770         /* Cursor navigation */
771         { 'k',          REQ_MOVE_UP },
772         { 'j',          REQ_MOVE_DOWN },
773         { KEY_HOME,     REQ_MOVE_FIRST_LINE },
774         { KEY_END,      REQ_MOVE_LAST_LINE },
775         { KEY_NPAGE,    REQ_MOVE_PAGE_DOWN },
776         { ' ',          REQ_MOVE_PAGE_DOWN },
777         { KEY_PPAGE,    REQ_MOVE_PAGE_UP },
778         { 'b',          REQ_MOVE_PAGE_UP },
779         { '-',          REQ_MOVE_PAGE_UP },
781         /* Scrolling */
782         { KEY_IC,       REQ_SCROLL_LINE_UP },
783         { KEY_DC,       REQ_SCROLL_LINE_DOWN },
784         { 'w',          REQ_SCROLL_PAGE_UP },
785         { 's',          REQ_SCROLL_PAGE_DOWN },
787         /* Searching */
788         { '/',          REQ_SEARCH },
789         { '?',          REQ_SEARCH_BACK },
790         { 'n',          REQ_FIND_NEXT },
791         { 'N',          REQ_FIND_PREV },
793         /* Misc */
794         { 'Q',          REQ_QUIT },
795         { 'z',          REQ_STOP_LOADING },
796         { 'v',          REQ_SHOW_VERSION },
797         { 'r',          REQ_SCREEN_REDRAW },
798         { '.',          REQ_TOGGLE_LINENO },
799         { 'D',          REQ_TOGGLE_DATE },
800         { 'A',          REQ_TOGGLE_AUTHOR },
801         { 'g',          REQ_TOGGLE_REV_GRAPH },
802         { 'F',          REQ_TOGGLE_REFS },
803         { ':',          REQ_PROMPT },
804         { 'u',          REQ_STATUS_UPDATE },
805         { '!',          REQ_STATUS_REVERT },
806         { 'M',          REQ_STATUS_MERGE },
807         { '@',          REQ_STAGE_NEXT },
808         { ',',          REQ_TREE_PARENT },
809         { 'e',          REQ_EDIT },
811         /* Using the ncurses SIGWINCH handler. */
812         { KEY_RESIZE,   REQ_SCREEN_RESIZE },
813 };
815 #define KEYMAP_INFO \
816         KEYMAP_(GENERIC), \
817         KEYMAP_(MAIN), \
818         KEYMAP_(DIFF), \
819         KEYMAP_(LOG), \
820         KEYMAP_(TREE), \
821         KEYMAP_(BLOB), \
822         KEYMAP_(BLAME), \
823         KEYMAP_(PAGER), \
824         KEYMAP_(HELP), \
825         KEYMAP_(STATUS), \
826         KEYMAP_(STAGE)
828 enum keymap {
829 #define KEYMAP_(name) KEYMAP_##name
830         KEYMAP_INFO
831 #undef  KEYMAP_
832 };
834 static struct int_map keymap_table[] = {
835 #define KEYMAP_(name) { #name, STRING_SIZE(#name), KEYMAP_##name }
836         KEYMAP_INFO
837 #undef  KEYMAP_
838 };
840 #define set_keymap(map, name) \
841         set_from_int_map(keymap_table, ARRAY_SIZE(keymap_table), map, name, strlen(name))
843 static struct keybinding *keybindings[ARRAY_SIZE(keymap_table)];
845 static void
846 add_keybinding(enum keymap keymap, enum request request, int key)
848         struct keybinding *keybinding;
850         keybinding = calloc(1, sizeof(*keybinding));
851         if (!keybinding)
852                 die("Failed to allocate keybinding");
854         keybinding->alias = key;
855         keybinding->request = request;
856         keybinding->next = keybindings[keymap];
857         keybindings[keymap] = keybinding;
860 /* Looks for a key binding first in the given map, then in the generic map, and
861  * lastly in the default keybindings. */
862 static enum request
863 get_keybinding(enum keymap keymap, int key)
865         struct keybinding *kbd;
866         int i;
868         for (kbd = keybindings[keymap]; kbd; kbd = kbd->next)
869                 if (kbd->alias == key)
870                         return kbd->request;
872         for (kbd = keybindings[KEYMAP_GENERIC]; kbd; kbd = kbd->next)
873                 if (kbd->alias == key)
874                         return kbd->request;
876         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
877                 if (default_keybindings[i].alias == key)
878                         return default_keybindings[i].request;
880         return (enum request) key;
884 struct key {
885         const char *name;
886         int value;
887 };
889 static struct key key_table[] = {
890         { "Enter",      KEY_RETURN },
891         { "Space",      ' ' },
892         { "Backspace",  KEY_BACKSPACE },
893         { "Tab",        KEY_TAB },
894         { "Escape",     KEY_ESC },
895         { "Left",       KEY_LEFT },
896         { "Right",      KEY_RIGHT },
897         { "Up",         KEY_UP },
898         { "Down",       KEY_DOWN },
899         { "Insert",     KEY_IC },
900         { "Delete",     KEY_DC },
901         { "Hash",       '#' },
902         { "Home",       KEY_HOME },
903         { "End",        KEY_END },
904         { "PageUp",     KEY_PPAGE },
905         { "PageDown",   KEY_NPAGE },
906         { "F1",         KEY_F(1) },
907         { "F2",         KEY_F(2) },
908         { "F3",         KEY_F(3) },
909         { "F4",         KEY_F(4) },
910         { "F5",         KEY_F(5) },
911         { "F6",         KEY_F(6) },
912         { "F7",         KEY_F(7) },
913         { "F8",         KEY_F(8) },
914         { "F9",         KEY_F(9) },
915         { "F10",        KEY_F(10) },
916         { "F11",        KEY_F(11) },
917         { "F12",        KEY_F(12) },
918 };
920 static int
921 get_key_value(const char *name)
923         int i;
925         for (i = 0; i < ARRAY_SIZE(key_table); i++)
926                 if (!strcasecmp(key_table[i].name, name))
927                         return key_table[i].value;
929         if (strlen(name) == 1 && isprint(*name))
930                 return (int) *name;
932         return ERR;
935 static const char *
936 get_key_name(int key_value)
938         static char key_char[] = "'X'";
939         const char *seq = NULL;
940         int key;
942         for (key = 0; key < ARRAY_SIZE(key_table); key++)
943                 if (key_table[key].value == key_value)
944                         seq = key_table[key].name;
946         if (seq == NULL &&
947             key_value < 127 &&
948             isprint(key_value)) {
949                 key_char[1] = (char) key_value;
950                 seq = key_char;
951         }
953         return seq ? seq : "(no key)";
956 static const char *
957 get_key(enum request request)
959         static char buf[BUFSIZ];
960         size_t pos = 0;
961         char *sep = "";
962         int i;
964         buf[pos] = 0;
966         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
967                 struct keybinding *keybinding = &default_keybindings[i];
969                 if (keybinding->request != request)
970                         continue;
972                 if (!string_format_from(buf, &pos, "%s%s", sep,
973                                         get_key_name(keybinding->alias)))
974                         return "Too many keybindings!";
975                 sep = ", ";
976         }
978         return buf;
981 struct run_request {
982         enum keymap keymap;
983         int key;
984         char cmd[SIZEOF_STR];
985 };
987 static struct run_request *run_request;
988 static size_t run_requests;
990 static enum request
991 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
993         struct run_request *req;
994         char cmd[SIZEOF_STR];
995         size_t bufpos;
997         for (bufpos = 0; argc > 0; argc--, argv++)
998                 if (!string_format_from(cmd, &bufpos, "%s ", *argv))
999                         return REQ_NONE;
1001         req = realloc(run_request, (run_requests + 1) * sizeof(*run_request));
1002         if (!req)
1003                 return REQ_NONE;
1005         run_request = req;
1006         req = &run_request[run_requests++];
1007         string_copy(req->cmd, cmd);
1008         req->keymap = keymap;
1009         req->key = key;
1011         return REQ_NONE + run_requests;
1014 static struct run_request *
1015 get_run_request(enum request request)
1017         if (request <= REQ_NONE)
1018                 return NULL;
1019         return &run_request[request - REQ_NONE - 1];
1022 static void
1023 add_builtin_run_requests(void)
1025         struct {
1026                 enum keymap keymap;
1027                 int key;
1028                 const char *argv[1];
1029         } reqs[] = {
1030                 { KEYMAP_MAIN,    'C', { "git cherry-pick %(commit)" } },
1031                 { KEYMAP_GENERIC, 'G', { "git gc" } },
1032         };
1033         int i;
1035         for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1036                 enum request req;
1038                 req = add_run_request(reqs[i].keymap, reqs[i].key, 1, reqs[i].argv);
1039                 if (req != REQ_NONE)
1040                         add_keybinding(reqs[i].keymap, req, reqs[i].key);
1041         }
1044 /*
1045  * User config file handling.
1046  */
1048 static struct int_map color_map[] = {
1049 #define COLOR_MAP(name) { #name, STRING_SIZE(#name), COLOR_##name }
1050         COLOR_MAP(DEFAULT),
1051         COLOR_MAP(BLACK),
1052         COLOR_MAP(BLUE),
1053         COLOR_MAP(CYAN),
1054         COLOR_MAP(GREEN),
1055         COLOR_MAP(MAGENTA),
1056         COLOR_MAP(RED),
1057         COLOR_MAP(WHITE),
1058         COLOR_MAP(YELLOW),
1059 };
1061 #define set_color(color, name) \
1062         set_from_int_map(color_map, ARRAY_SIZE(color_map), color, name, strlen(name))
1064 static struct int_map attr_map[] = {
1065 #define ATTR_MAP(name) { #name, STRING_SIZE(#name), A_##name }
1066         ATTR_MAP(NORMAL),
1067         ATTR_MAP(BLINK),
1068         ATTR_MAP(BOLD),
1069         ATTR_MAP(DIM),
1070         ATTR_MAP(REVERSE),
1071         ATTR_MAP(STANDOUT),
1072         ATTR_MAP(UNDERLINE),
1073 };
1075 #define set_attribute(attr, name) \
1076         set_from_int_map(attr_map, ARRAY_SIZE(attr_map), attr, name, strlen(name))
1078 static int   config_lineno;
1079 static bool  config_errors;
1080 static const char *config_msg;
1082 /* Wants: object fgcolor bgcolor [attr] */
1083 static int
1084 option_color_command(int argc, const char *argv[])
1086         struct line_info *info;
1088         if (argc != 3 && argc != 4) {
1089                 config_msg = "Wrong number of arguments given to color command";
1090                 return ERR;
1091         }
1093         info = get_line_info(argv[0]);
1094         if (!info) {
1095                 if (!string_enum_compare(argv[0], "main-delim", strlen("main-delim"))) {
1096                         info = get_line_info("delimiter");
1098                 } else if (!string_enum_compare(argv[0], "main-date", strlen("main-date"))) {
1099                         info = get_line_info("date");
1101                 } else {
1102                         config_msg = "Unknown color name";
1103                         return ERR;
1104                 }
1105         }
1107         if (set_color(&info->fg, argv[1]) == ERR ||
1108             set_color(&info->bg, argv[2]) == ERR) {
1109                 config_msg = "Unknown color";
1110                 return ERR;
1111         }
1113         if (argc == 4 && set_attribute(&info->attr, argv[3]) == ERR) {
1114                 config_msg = "Unknown attribute";
1115                 return ERR;
1116         }
1118         return OK;
1121 static bool parse_bool(const char *s)
1123         return (!strcmp(s, "1") || !strcmp(s, "true") ||
1124                 !strcmp(s, "yes")) ? TRUE : FALSE;
1127 static int
1128 parse_int(const char *s, int default_value, int min, int max)
1130         int value = atoi(s);
1132         return (value < min || value > max) ? default_value : value;
1135 /* Wants: name = value */
1136 static int
1137 option_set_command(int argc, const char *argv[])
1139         if (argc != 3) {
1140                 config_msg = "Wrong number of arguments given to set command";
1141                 return ERR;
1142         }
1144         if (strcmp(argv[1], "=")) {
1145                 config_msg = "No value assigned";
1146                 return ERR;
1147         }
1149         if (!strcmp(argv[0], "show-author")) {
1150                 opt_author = parse_bool(argv[2]);
1151                 return OK;
1152         }
1154         if (!strcmp(argv[0], "show-date")) {
1155                 opt_date = parse_bool(argv[2]);
1156                 return OK;
1157         }
1159         if (!strcmp(argv[0], "show-rev-graph")) {
1160                 opt_rev_graph = parse_bool(argv[2]);
1161                 return OK;
1162         }
1164         if (!strcmp(argv[0], "show-refs")) {
1165                 opt_show_refs = parse_bool(argv[2]);
1166                 return OK;
1167         }
1169         if (!strcmp(argv[0], "show-line-numbers")) {
1170                 opt_line_number = parse_bool(argv[2]);
1171                 return OK;
1172         }
1174         if (!strcmp(argv[0], "line-graphics")) {
1175                 opt_line_graphics = parse_bool(argv[2]);
1176                 return OK;
1177         }
1179         if (!strcmp(argv[0], "line-number-interval")) {
1180                 opt_num_interval = parse_int(argv[2], opt_num_interval, 1, 1024);
1181                 return OK;
1182         }
1184         if (!strcmp(argv[0], "author-width")) {
1185                 opt_author_cols = parse_int(argv[2], opt_author_cols, 0, 1024);
1186                 return OK;
1187         }
1189         if (!strcmp(argv[0], "tab-size")) {
1190                 opt_tab_size = parse_int(argv[2], opt_tab_size, 1, 1024);
1191                 return OK;
1192         }
1194         if (!strcmp(argv[0], "commit-encoding")) {
1195                 const char *arg = argv[2];
1196                 int arglen = strlen(arg);
1198                 switch (arg[0]) {
1199                 case '"':
1200                 case '\'':
1201                         if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1202                                 config_msg = "Unmatched quotation";
1203                                 return ERR;
1204                         }
1205                         arg += 1; arglen -= 2;
1206                 default:
1207                         string_ncopy(opt_encoding, arg, strlen(arg));
1208                         return OK;
1209                 }
1210         }
1212         config_msg = "Unknown variable name";
1213         return ERR;
1216 /* Wants: mode request key */
1217 static int
1218 option_bind_command(int argc, const char *argv[])
1220         enum request request;
1221         int keymap;
1222         int key;
1224         if (argc < 3) {
1225                 config_msg = "Wrong number of arguments given to bind command";
1226                 return ERR;
1227         }
1229         if (set_keymap(&keymap, argv[0]) == ERR) {
1230                 config_msg = "Unknown key map";
1231                 return ERR;
1232         }
1234         key = get_key_value(argv[1]);
1235         if (key == ERR) {
1236                 config_msg = "Unknown key";
1237                 return ERR;
1238         }
1240         request = get_request(argv[2]);
1241         if (request == REQ_NONE) {
1242                 const char *obsolete[] = { "cherry-pick" };
1243                 size_t namelen = strlen(argv[2]);
1244                 int i;
1246                 for (i = 0; i < ARRAY_SIZE(obsolete); i++) {
1247                         if (namelen == strlen(obsolete[i]) &&
1248                             !string_enum_compare(obsolete[i], argv[2], namelen)) {
1249                                 config_msg = "Obsolete request name";
1250                                 return ERR;
1251                         }
1252                 }
1253         }
1254         if (request == REQ_NONE && *argv[2]++ == '!')
1255                 request = add_run_request(keymap, key, argc - 2, argv + 2);
1256         if (request == REQ_NONE) {
1257                 config_msg = "Unknown request name";
1258                 return ERR;
1259         }
1261         add_keybinding(keymap, request, key);
1263         return OK;
1266 static int
1267 set_option(const char *opt, char *value)
1269         const char *argv[SIZEOF_ARG];
1270         int valuelen;
1271         int argc = 0;
1273         /* Tokenize */
1274         while (argc < ARRAY_SIZE(argv) && (valuelen = strcspn(value, " \t"))) {
1275                 argv[argc++] = value;
1276                 value += valuelen;
1278                 /* Nothing more to tokenize or last available token. */
1279                 if (!*value || argc >= ARRAY_SIZE(argv))
1280                         break;
1282                 *value++ = 0;
1283                 while (isspace(*value))
1284                         value++;
1285         }
1287         if (!strcmp(opt, "color"))
1288                 return option_color_command(argc, argv);
1290         if (!strcmp(opt, "set"))
1291                 return option_set_command(argc, argv);
1293         if (!strcmp(opt, "bind"))
1294                 return option_bind_command(argc, argv);
1296         config_msg = "Unknown option command";
1297         return ERR;
1300 static int
1301 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1303         int status = OK;
1305         config_lineno++;
1306         config_msg = "Internal error";
1308         /* Check for comment markers, since read_properties() will
1309          * only ensure opt and value are split at first " \t". */
1310         optlen = strcspn(opt, "#");
1311         if (optlen == 0)
1312                 return OK;
1314         if (opt[optlen] != 0) {
1315                 config_msg = "No option value";
1316                 status = ERR;
1318         }  else {
1319                 /* Look for comment endings in the value. */
1320                 size_t len = strcspn(value, "#");
1322                 if (len < valuelen) {
1323                         valuelen = len;
1324                         value[valuelen] = 0;
1325                 }
1327                 status = set_option(opt, value);
1328         }
1330         if (status == ERR) {
1331                 fprintf(stderr, "Error on line %d, near '%.*s': %s\n",
1332                         config_lineno, (int) optlen, opt, config_msg);
1333                 config_errors = TRUE;
1334         }
1336         /* Always keep going if errors are encountered. */
1337         return OK;
1340 static void
1341 load_option_file(const char *path)
1343         FILE *file;
1345         /* It's ok that the file doesn't exist. */
1346         file = fopen(path, "r");
1347         if (!file)
1348                 return;
1350         config_lineno = 0;
1351         config_errors = FALSE;
1353         if (read_properties(file, " \t", read_option) == ERR ||
1354             config_errors == TRUE)
1355                 fprintf(stderr, "Errors while loading %s.\n", path);
1358 static int
1359 load_options(void)
1361         const char *home = getenv("HOME");
1362         const char *tigrc_user = getenv("TIGRC_USER");
1363         const char *tigrc_system = getenv("TIGRC_SYSTEM");
1364         char buf[SIZEOF_STR];
1366         add_builtin_run_requests();
1368         if (!tigrc_system) {
1369                 if (!string_format(buf, "%s/tigrc", SYSCONFDIR))
1370                         return ERR;
1371                 tigrc_system = buf;
1372         }
1373         load_option_file(tigrc_system);
1375         if (!tigrc_user) {
1376                 if (!home || !string_format(buf, "%s/.tigrc", home))
1377                         return ERR;
1378                 tigrc_user = buf;
1379         }
1380         load_option_file(tigrc_user);
1382         return OK;
1386 /*
1387  * The viewer
1388  */
1390 struct view;
1391 struct view_ops;
1393 /* The display array of active views and the index of the current view. */
1394 static struct view *display[2];
1395 static unsigned int current_view;
1397 /* Reading from the prompt? */
1398 static bool input_mode = FALSE;
1400 #define foreach_displayed_view(view, i) \
1401         for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1403 #define displayed_views()       (display[1] != NULL ? 2 : 1)
1405 /* Current head and commit ID */
1406 static char ref_blob[SIZEOF_REF]        = "";
1407 static char ref_commit[SIZEOF_REF]      = "HEAD";
1408 static char ref_head[SIZEOF_REF]        = "HEAD";
1410 struct view {
1411         const char *name;       /* View name */
1412         const char *cmd_fmt;    /* Default command line format */
1413         const char *cmd_env;    /* Command line set via environment */
1414         const char *id;         /* Points to either of ref_{head,commit,blob} */
1416         struct view_ops *ops;   /* View operations */
1418         enum keymap keymap;     /* What keymap does this view have */
1419         bool git_dir;           /* Whether the view requires a git directory. */
1421         char cmd[SIZEOF_STR];   /* Command buffer */
1422         char ref[SIZEOF_REF];   /* Hovered commit reference */
1423         char vid[SIZEOF_REF];   /* View ID. Set to id member when updating. */
1425         int height, width;      /* The width and height of the main window */
1426         WINDOW *win;            /* The main window */
1427         WINDOW *title;          /* The title window living below the main window */
1429         /* Navigation */
1430         unsigned long offset;   /* Offset of the window top */
1431         unsigned long lineno;   /* Current line number */
1433         /* Searching */
1434         char grep[SIZEOF_STR];  /* Search string */
1435         regex_t *regex;         /* Pre-compiled regex */
1437         /* If non-NULL, points to the view that opened this view. If this view
1438          * is closed tig will switch back to the parent view. */
1439         struct view *parent;
1441         /* Buffering */
1442         size_t lines;           /* Total number of lines */
1443         struct line *line;      /* Line index */
1444         size_t line_alloc;      /* Total number of allocated lines */
1445         size_t line_size;       /* Total number of used lines */
1446         unsigned int digits;    /* Number of digits in the lines member. */
1448         /* Drawing */
1449         struct line *curline;   /* Line currently being drawn. */
1450         enum line_type curtype; /* Attribute currently used for drawing. */
1451         unsigned long col;      /* Column when drawing. */
1453         /* Loading */
1454         FILE *pipe;
1455         time_t start_time;
1456 };
1458 struct view_ops {
1459         /* What type of content being displayed. Used in the title bar. */
1460         const char *type;
1461         /* Open and reads in all view content. */
1462         bool (*open)(struct view *view);
1463         /* Read one line; updates view->line. */
1464         bool (*read)(struct view *view, char *data);
1465         /* Draw one line; @lineno must be < view->height. */
1466         bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
1467         /* Depending on view handle a special requests. */
1468         enum request (*request)(struct view *view, enum request request, struct line *line);
1469         /* Search for regex in a line. */
1470         bool (*grep)(struct view *view, struct line *line);
1471         /* Select line */
1472         void (*select)(struct view *view, struct line *line);
1473 };
1475 static struct view_ops blame_ops;
1476 static struct view_ops blob_ops;
1477 static struct view_ops help_ops;
1478 static struct view_ops log_ops;
1479 static struct view_ops main_ops;
1480 static struct view_ops pager_ops;
1481 static struct view_ops stage_ops;
1482 static struct view_ops status_ops;
1483 static struct view_ops tree_ops;
1485 #define VIEW_STR(name, cmd, env, ref, ops, map, git) \
1486         { name, cmd, #env, ref, ops, map, git }
1488 #define VIEW_(id, name, ops, git, ref) \
1489         VIEW_STR(name, TIG_##id##_CMD,  TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
1492 static struct view views[] = {
1493         VIEW_(MAIN,   "main",   &main_ops,   TRUE,  ref_head),
1494         VIEW_(DIFF,   "diff",   &pager_ops,  TRUE,  ref_commit),
1495         VIEW_(LOG,    "log",    &log_ops,    TRUE,  ref_head),
1496         VIEW_(TREE,   "tree",   &tree_ops,   TRUE,  ref_commit),
1497         VIEW_(BLOB,   "blob",   &blob_ops,   TRUE,  ref_blob),
1498         VIEW_(BLAME,  "blame",  &blame_ops,  TRUE,  ref_commit),
1499         VIEW_(HELP,   "help",   &help_ops,   FALSE, ""),
1500         VIEW_(PAGER,  "pager",  &pager_ops,  FALSE, "stdin"),
1501         VIEW_(STATUS, "status", &status_ops, TRUE,  ""),
1502         VIEW_(STAGE,  "stage",  &stage_ops,  TRUE,  ""),
1503 };
1505 #define VIEW(req)       (&views[(req) - REQ_OFFSET - 1])
1506 #define VIEW_REQ(view)  ((view) - views + 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])
1515 enum line_graphic {
1516         LINE_GRAPHIC_VLINE
1517 };
1519 static int line_graphics[] = {
1520         /* LINE_GRAPHIC_VLINE: */ '|'
1521 };
1523 static inline void
1524 set_view_attr(struct view *view, enum line_type type)
1526         if (!view->curline->selected && view->curtype != type) {
1527                 wattrset(view->win, get_line_attr(type));
1528                 wchgat(view->win, -1, 0, type, NULL);
1529                 view->curtype = type;
1530         }
1533 static int
1534 draw_chars(struct view *view, enum line_type type, const char *string,
1535            int max_len, bool use_tilde)
1537         int len = 0;
1538         int col = 0;
1539         int trimmed = FALSE;
1541         if (max_len <= 0)
1542                 return 0;
1544         if (opt_utf8) {
1545                 len = utf8_length(string, &col, max_len, &trimmed, use_tilde);
1546         } else {
1547                 col = len = strlen(string);
1548                 if (len > max_len) {
1549                         if (use_tilde) {
1550                                 max_len -= 1;
1551                         }
1552                         col = len = max_len;
1553                         trimmed = TRUE;
1554                 }
1555         }
1557         set_view_attr(view, type);
1558         waddnstr(view->win, string, len);
1559         if (trimmed && use_tilde) {
1560                 set_view_attr(view, LINE_DELIMITER);
1561                 waddch(view->win, '~');
1562                 col++;
1563         }
1565         return col;
1568 static int
1569 draw_space(struct view *view, enum line_type type, int max, int spaces)
1571         static char space[] = "                    ";
1572         int col = 0;
1574         spaces = MIN(max, spaces);
1576         while (spaces > 0) {
1577                 int len = MIN(spaces, sizeof(space) - 1);
1579                 col += draw_chars(view, type, space, spaces, FALSE);
1580                 spaces -= len;
1581         }
1583         return col;
1586 static bool
1587 draw_lineno(struct view *view, unsigned int lineno)
1589         char number[10];
1590         int digits3 = view->digits < 3 ? 3 : view->digits;
1591         int max_number = MIN(digits3, STRING_SIZE(number));
1592         int max = view->width - view->col;
1593         int col;
1595         if (max < max_number)
1596                 max_number = max;
1598         lineno += view->offset + 1;
1599         if (lineno == 1 || (lineno % opt_num_interval) == 0) {
1600                 static char fmt[] = "%1ld";
1602                 if (view->digits <= 9)
1603                         fmt[1] = '0' + digits3;
1605                 if (!string_format(number, fmt, lineno))
1606                         number[0] = 0;
1607                 col = draw_chars(view, LINE_LINE_NUMBER, number, max_number, TRUE);
1608         } else {
1609                 col = draw_space(view, LINE_LINE_NUMBER, max_number, max_number);
1610         }
1612         if (col < max) {
1613                 set_view_attr(view, LINE_DEFAULT);
1614                 waddch(view->win, line_graphics[LINE_GRAPHIC_VLINE]);
1615                 col++;
1616         }
1618         if (col < max)
1619                 col += draw_space(view, LINE_DEFAULT, max - col, 1);
1620         view->col += col;
1622         return view->width - view->col <= 0;
1625 static bool
1626 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
1628         view->col += draw_chars(view, type, string, view->width - view->col, trim);
1629         return view->width - view->col <= 0;
1632 static bool
1633 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
1635         int max = view->width - view->col;
1636         int i;
1638         if (max < size)
1639                 size = max;
1641         set_view_attr(view, type);
1642         /* Using waddch() instead of waddnstr() ensures that
1643          * they'll be rendered correctly for the cursor line. */
1644         for (i = 0; i < size; i++)
1645                 waddch(view->win, graphic[i]);
1647         view->col += size;
1648         if (size < max) {
1649                 waddch(view->win, ' ');
1650                 view->col++;
1651         }
1653         return view->width - view->col <= 0;
1656 static bool
1657 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
1659         int max = MIN(view->width - view->col, len);
1660         int col;
1662         if (text)
1663                 col = draw_chars(view, type, text, max - 1, trim);
1664         else
1665                 col = draw_space(view, type, max - 1, max - 1);
1667         view->col += col + draw_space(view, LINE_DEFAULT, max - col, max - col);
1668         return view->width - view->col <= 0;
1671 static bool
1672 draw_date(struct view *view, struct tm *time)
1674         char buf[DATE_COLS];
1675         char *date;
1676         int timelen = 0;
1678         if (time)
1679                 timelen = strftime(buf, sizeof(buf), DATE_FORMAT, time);
1680         date = timelen ? buf : NULL;
1682         return draw_field(view, LINE_DATE, date, DATE_COLS, FALSE);
1685 static bool
1686 draw_view_line(struct view *view, unsigned int lineno)
1688         struct line *line;
1689         bool selected = (view->offset + lineno == view->lineno);
1690         bool draw_ok;
1692         assert(view_is_displayed(view));
1694         if (view->offset + lineno >= view->lines)
1695                 return FALSE;
1697         line = &view->line[view->offset + lineno];
1699         wmove(view->win, lineno, 0);
1700         view->col = 0;
1701         view->curline = line;
1702         view->curtype = LINE_NONE;
1703         line->selected = FALSE;
1705         if (selected) {
1706                 set_view_attr(view, LINE_CURSOR);
1707                 line->selected = TRUE;
1708                 view->ops->select(view, line);
1709         } else if (line->selected) {
1710                 wclrtoeol(view->win);
1711         }
1713         scrollok(view->win, FALSE);
1714         draw_ok = view->ops->draw(view, line, lineno);
1715         scrollok(view->win, TRUE);
1717         return draw_ok;
1720 static void
1721 redraw_view_dirty(struct view *view)
1723         bool dirty = FALSE;
1724         int lineno;
1726         for (lineno = 0; lineno < view->height; lineno++) {
1727                 struct line *line = &view->line[view->offset + lineno];
1729                 if (!line->dirty)
1730                         continue;
1731                 line->dirty = 0;
1732                 dirty = TRUE;
1733                 if (!draw_view_line(view, lineno))
1734                         break;
1735         }
1737         if (!dirty)
1738                 return;
1739         redrawwin(view->win);
1740         if (input_mode)
1741                 wnoutrefresh(view->win);
1742         else
1743                 wrefresh(view->win);
1746 static void
1747 redraw_view_from(struct view *view, int lineno)
1749         assert(0 <= lineno && lineno < view->height);
1751         for (; lineno < view->height; lineno++) {
1752                 if (!draw_view_line(view, lineno))
1753                         break;
1754         }
1756         redrawwin(view->win);
1757         if (input_mode)
1758                 wnoutrefresh(view->win);
1759         else
1760                 wrefresh(view->win);
1763 static void
1764 redraw_view(struct view *view)
1766         wclear(view->win);
1767         redraw_view_from(view, 0);
1771 static void
1772 update_view_title(struct view *view)
1774         char buf[SIZEOF_STR];
1775         char state[SIZEOF_STR];
1776         size_t bufpos = 0, statelen = 0;
1778         assert(view_is_displayed(view));
1780         if (view != VIEW(REQ_VIEW_STATUS) && (view->lines || view->pipe)) {
1781                 unsigned int view_lines = view->offset + view->height;
1782                 unsigned int lines = view->lines
1783                                    ? MIN(view_lines, view->lines) * 100 / view->lines
1784                                    : 0;
1786                 string_format_from(state, &statelen, "- %s %d of %d (%d%%)",
1787                                    view->ops->type,
1788                                    view->lineno + 1,
1789                                    view->lines,
1790                                    lines);
1792                 if (view->pipe) {
1793                         time_t secs = time(NULL) - view->start_time;
1795                         /* Three git seconds are a long time ... */
1796                         if (secs > 2)
1797                                 string_format_from(state, &statelen, " %lds", secs);
1798                 }
1799         }
1801         string_format_from(buf, &bufpos, "[%s]", view->name);
1802         if (*view->ref && bufpos < view->width) {
1803                 size_t refsize = strlen(view->ref);
1804                 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
1806                 if (minsize < view->width)
1807                         refsize = view->width - minsize + 7;
1808                 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
1809         }
1811         if (statelen && bufpos < view->width) {
1812                 string_format_from(buf, &bufpos, " %s", state);
1813         }
1815         if (view == display[current_view])
1816                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
1817         else
1818                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
1820         mvwaddnstr(view->title, 0, 0, buf, bufpos);
1821         wclrtoeol(view->title);
1822         wmove(view->title, 0, view->width - 1);
1824         if (input_mode)
1825                 wnoutrefresh(view->title);
1826         else
1827                 wrefresh(view->title);
1830 static void
1831 resize_display(void)
1833         int offset, i;
1834         struct view *base = display[0];
1835         struct view *view = display[1] ? display[1] : display[0];
1837         /* Setup window dimensions */
1839         getmaxyx(stdscr, base->height, base->width);
1841         /* Make room for the status window. */
1842         base->height -= 1;
1844         if (view != base) {
1845                 /* Horizontal split. */
1846                 view->width   = base->width;
1847                 view->height  = SCALE_SPLIT_VIEW(base->height);
1848                 base->height -= view->height;
1850                 /* Make room for the title bar. */
1851                 view->height -= 1;
1852         }
1854         /* Make room for the title bar. */
1855         base->height -= 1;
1857         offset = 0;
1859         foreach_displayed_view (view, i) {
1860                 if (!view->win) {
1861                         view->win = newwin(view->height, 0, offset, 0);
1862                         if (!view->win)
1863                                 die("Failed to create %s view", view->name);
1865                         scrollok(view->win, TRUE);
1867                         view->title = newwin(1, 0, offset + view->height, 0);
1868                         if (!view->title)
1869                                 die("Failed to create title window");
1871                 } else {
1872                         wresize(view->win, view->height, view->width);
1873                         mvwin(view->win,   offset, 0);
1874                         mvwin(view->title, offset + view->height, 0);
1875                 }
1877                 offset += view->height + 1;
1878         }
1881 static void
1882 redraw_display(void)
1884         struct view *view;
1885         int i;
1887         foreach_displayed_view (view, i) {
1888                 redraw_view(view);
1889                 update_view_title(view);
1890         }
1893 static void
1894 update_display_cursor(struct view *view)
1896         /* Move the cursor to the right-most column of the cursor line.
1897          *
1898          * XXX: This could turn out to be a bit expensive, but it ensures that
1899          * the cursor does not jump around. */
1900         if (view->lines) {
1901                 wmove(view->win, view->lineno - view->offset, view->width - 1);
1902                 wrefresh(view->win);
1903         }
1906 /*
1907  * Navigation
1908  */
1910 /* Scrolling backend */
1911 static void
1912 do_scroll_view(struct view *view, int lines)
1914         bool redraw_current_line = FALSE;
1916         /* The rendering expects the new offset. */
1917         view->offset += lines;
1919         assert(0 <= view->offset && view->offset < view->lines);
1920         assert(lines);
1922         /* Move current line into the view. */
1923         if (view->lineno < view->offset) {
1924                 view->lineno = view->offset;
1925                 redraw_current_line = TRUE;
1926         } else if (view->lineno >= view->offset + view->height) {
1927                 view->lineno = view->offset + view->height - 1;
1928                 redraw_current_line = TRUE;
1929         }
1931         assert(view->offset <= view->lineno && view->lineno < view->lines);
1933         /* Redraw the whole screen if scrolling is pointless. */
1934         if (view->height < ABS(lines)) {
1935                 redraw_view(view);
1937         } else {
1938                 int line = lines > 0 ? view->height - lines : 0;
1939                 int end = line + ABS(lines);
1941                 wscrl(view->win, lines);
1943                 for (; line < end; line++) {
1944                         if (!draw_view_line(view, line))
1945                                 break;
1946                 }
1948                 if (redraw_current_line)
1949                         draw_view_line(view, view->lineno - view->offset);
1950         }
1952         redrawwin(view->win);
1953         wrefresh(view->win);
1954         report("");
1957 /* Scroll frontend */
1958 static void
1959 scroll_view(struct view *view, enum request request)
1961         int lines = 1;
1963         assert(view_is_displayed(view));
1965         switch (request) {
1966         case REQ_SCROLL_PAGE_DOWN:
1967                 lines = view->height;
1968         case REQ_SCROLL_LINE_DOWN:
1969                 if (view->offset + lines > view->lines)
1970                         lines = view->lines - view->offset;
1972                 if (lines == 0 || view->offset + view->height >= view->lines) {
1973                         report("Cannot scroll beyond the last line");
1974                         return;
1975                 }
1976                 break;
1978         case REQ_SCROLL_PAGE_UP:
1979                 lines = view->height;
1980         case REQ_SCROLL_LINE_UP:
1981                 if (lines > view->offset)
1982                         lines = view->offset;
1984                 if (lines == 0) {
1985                         report("Cannot scroll beyond the first line");
1986                         return;
1987                 }
1989                 lines = -lines;
1990                 break;
1992         default:
1993                 die("request %d not handled in switch", request);
1994         }
1996         do_scroll_view(view, lines);
1999 /* Cursor moving */
2000 static void
2001 move_view(struct view *view, enum request request)
2003         int scroll_steps = 0;
2004         int steps;
2006         switch (request) {
2007         case REQ_MOVE_FIRST_LINE:
2008                 steps = -view->lineno;
2009                 break;
2011         case REQ_MOVE_LAST_LINE:
2012                 steps = view->lines - view->lineno - 1;
2013                 break;
2015         case REQ_MOVE_PAGE_UP:
2016                 steps = view->height > view->lineno
2017                       ? -view->lineno : -view->height;
2018                 break;
2020         case REQ_MOVE_PAGE_DOWN:
2021                 steps = view->lineno + view->height >= view->lines
2022                       ? view->lines - view->lineno - 1 : view->height;
2023                 break;
2025         case REQ_MOVE_UP:
2026                 steps = -1;
2027                 break;
2029         case REQ_MOVE_DOWN:
2030                 steps = 1;
2031                 break;
2033         default:
2034                 die("request %d not handled in switch", request);
2035         }
2037         if (steps <= 0 && view->lineno == 0) {
2038                 report("Cannot move beyond the first line");
2039                 return;
2041         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2042                 report("Cannot move beyond the last line");
2043                 return;
2044         }
2046         /* Move the current line */
2047         view->lineno += steps;
2048         assert(0 <= view->lineno && view->lineno < view->lines);
2050         /* Check whether the view needs to be scrolled */
2051         if (view->lineno < view->offset ||
2052             view->lineno >= view->offset + view->height) {
2053                 scroll_steps = steps;
2054                 if (steps < 0 && -steps > view->offset) {
2055                         scroll_steps = -view->offset;
2057                 } else if (steps > 0) {
2058                         if (view->lineno == view->lines - 1 &&
2059                             view->lines > view->height) {
2060                                 scroll_steps = view->lines - view->offset - 1;
2061                                 if (scroll_steps >= view->height)
2062                                         scroll_steps -= view->height - 1;
2063                         }
2064                 }
2065         }
2067         if (!view_is_displayed(view)) {
2068                 view->offset += scroll_steps;
2069                 assert(0 <= view->offset && view->offset < view->lines);
2070                 view->ops->select(view, &view->line[view->lineno]);
2071                 return;
2072         }
2074         /* Repaint the old "current" line if we be scrolling */
2075         if (ABS(steps) < view->height)
2076                 draw_view_line(view, view->lineno - steps - view->offset);
2078         if (scroll_steps) {
2079                 do_scroll_view(view, scroll_steps);
2080                 return;
2081         }
2083         /* Draw the current line */
2084         draw_view_line(view, view->lineno - view->offset);
2086         redrawwin(view->win);
2087         wrefresh(view->win);
2088         report("");
2092 /*
2093  * Searching
2094  */
2096 static void search_view(struct view *view, enum request request);
2098 static bool
2099 find_next_line(struct view *view, unsigned long lineno, struct line *line)
2101         assert(view_is_displayed(view));
2103         if (!view->ops->grep(view, line))
2104                 return FALSE;
2106         if (lineno - view->offset >= view->height) {
2107                 view->offset = lineno;
2108                 view->lineno = lineno;
2109                 redraw_view(view);
2111         } else {
2112                 unsigned long old_lineno = view->lineno - view->offset;
2114                 view->lineno = lineno;
2115                 draw_view_line(view, old_lineno);
2117                 draw_view_line(view, view->lineno - view->offset);
2118                 redrawwin(view->win);
2119                 wrefresh(view->win);
2120         }
2122         report("Line %ld matches '%s'", lineno + 1, view->grep);
2123         return TRUE;
2126 static void
2127 find_next(struct view *view, enum request request)
2129         unsigned long lineno = view->lineno;
2130         int direction;
2132         if (!*view->grep) {
2133                 if (!*opt_search)
2134                         report("No previous search");
2135                 else
2136                         search_view(view, request);
2137                 return;
2138         }
2140         switch (request) {
2141         case REQ_SEARCH:
2142         case REQ_FIND_NEXT:
2143                 direction = 1;
2144                 break;
2146         case REQ_SEARCH_BACK:
2147         case REQ_FIND_PREV:
2148                 direction = -1;
2149                 break;
2151         default:
2152                 return;
2153         }
2155         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2156                 lineno += direction;
2158         /* Note, lineno is unsigned long so will wrap around in which case it
2159          * will become bigger than view->lines. */
2160         for (; lineno < view->lines; lineno += direction) {
2161                 struct line *line = &view->line[lineno];
2163                 if (find_next_line(view, lineno, line))
2164                         return;
2165         }
2167         report("No match found for '%s'", view->grep);
2170 static void
2171 search_view(struct view *view, enum request request)
2173         int regex_err;
2175         if (view->regex) {
2176                 regfree(view->regex);
2177                 *view->grep = 0;
2178         } else {
2179                 view->regex = calloc(1, sizeof(*view->regex));
2180                 if (!view->regex)
2181                         return;
2182         }
2184         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2185         if (regex_err != 0) {
2186                 char buf[SIZEOF_STR] = "unknown error";
2188                 regerror(regex_err, view->regex, buf, sizeof(buf));
2189                 report("Search failed: %s", buf);
2190                 return;
2191         }
2193         string_copy(view->grep, opt_search);
2195         find_next(view, request);
2198 /*
2199  * Incremental updating
2200  */
2202 static void
2203 reset_view(struct view *view)
2205         int i;
2207         for (i = 0; i < view->lines; i++)
2208                 free(view->line[i].data);
2209         free(view->line);
2211         view->line = NULL;
2212         view->offset = 0;
2213         view->lines  = 0;
2214         view->lineno = 0;
2215         view->line_size = 0;
2216         view->line_alloc = 0;
2217         view->vid[0] = 0;
2220 static void
2221 end_update(struct view *view, bool force)
2223         if (!view->pipe)
2224                 return;
2225         while (!view->ops->read(view, NULL))
2226                 if (!force)
2227                         return;
2228         set_nonblocking_input(FALSE);
2229         if (view->pipe == stdin)
2230                 fclose(view->pipe);
2231         else
2232                 pclose(view->pipe);
2233         view->pipe = NULL;
2236 static bool
2237 begin_update(struct view *view, bool refresh)
2239         if (opt_cmd[0]) {
2240                 string_copy(view->cmd, opt_cmd);
2241                 opt_cmd[0] = 0;
2242                 /* When running random commands, initially show the
2243                  * command in the title. However, it maybe later be
2244                  * overwritten if a commit line is selected. */
2245                 if (view == VIEW(REQ_VIEW_PAGER))
2246                         string_copy(view->ref, view->cmd);
2247                 else
2248                         view->ref[0] = 0;
2250         } else if (view == VIEW(REQ_VIEW_TREE)) {
2251                 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
2252                 char path[SIZEOF_STR];
2254                 if (strcmp(view->vid, view->id))
2255                         opt_path[0] = path[0] = 0;
2256                 else if (sq_quote(path, 0, opt_path) >= sizeof(path))
2257                         return FALSE;
2259                 if (!string_format(view->cmd, format, view->id, path))
2260                         return FALSE;
2262         } else if (!refresh) {
2263                 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
2264                 const char *id = view->id;
2266                 if (!string_format(view->cmd, format, id, id, id, id, id))
2267                         return FALSE;
2269                 /* Put the current ref_* value to the view title ref
2270                  * member. This is needed by the blob view. Most other
2271                  * views sets it automatically after loading because the
2272                  * first line is a commit line. */
2273                 string_copy_rev(view->ref, view->id);
2274         }
2276         /* Special case for the pager view. */
2277         if (opt_pipe) {
2278                 view->pipe = opt_pipe;
2279                 opt_pipe = NULL;
2280         } else {
2281                 view->pipe = popen(view->cmd, "r");
2282         }
2284         if (!view->pipe)
2285                 return FALSE;
2287         set_nonblocking_input(TRUE);
2288         reset_view(view);
2289         string_copy_rev(view->vid, view->id);
2291         view->start_time = time(NULL);
2293         return TRUE;
2296 #define ITEM_CHUNK_SIZE 256
2297 static void *
2298 realloc_items(void *mem, size_t *size, size_t new_size, size_t item_size)
2300         size_t num_chunks = *size / ITEM_CHUNK_SIZE;
2301         size_t num_chunks_new = (new_size + ITEM_CHUNK_SIZE - 1) / ITEM_CHUNK_SIZE;
2303         if (mem == NULL || num_chunks != num_chunks_new) {
2304                 *size = num_chunks_new * ITEM_CHUNK_SIZE;
2305                 mem = realloc(mem, *size * item_size);
2306         }
2308         return mem;
2311 static struct line *
2312 realloc_lines(struct view *view, size_t line_size)
2314         size_t alloc = view->line_alloc;
2315         struct line *tmp = realloc_items(view->line, &alloc, line_size,
2316                                          sizeof(*view->line));
2318         if (!tmp)
2319                 return NULL;
2321         view->line = tmp;
2322         view->line_alloc = alloc;
2323         view->line_size = line_size;
2324         return view->line;
2327 static bool
2328 update_view(struct view *view)
2330         char in_buffer[BUFSIZ];
2331         char out_buffer[BUFSIZ * 2];
2332         char *line;
2333         /* The number of lines to read. If too low it will cause too much
2334          * redrawing (and possible flickering), if too high responsiveness
2335          * will suffer. */
2336         unsigned long lines = view->height;
2337         int redraw_from = -1;
2339         if (!view->pipe)
2340                 return TRUE;
2342         /* Only redraw if lines are visible. */
2343         if (view->offset + view->height >= view->lines)
2344                 redraw_from = view->lines - view->offset;
2346         /* FIXME: This is probably not perfect for backgrounded views. */
2347         if (!realloc_lines(view, view->lines + lines))
2348                 goto alloc_error;
2350         while ((line = fgets(in_buffer, sizeof(in_buffer), view->pipe))) {
2351                 size_t linelen = strlen(line);
2353                 if (linelen)
2354                         line[linelen - 1] = 0;
2356                 if (opt_iconv != ICONV_NONE) {
2357                         ICONV_CONST char *inbuf = line;
2358                         size_t inlen = linelen;
2360                         char *outbuf = out_buffer;
2361                         size_t outlen = sizeof(out_buffer);
2363                         size_t ret;
2365                         ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2366                         if (ret != (size_t) -1) {
2367                                 line = out_buffer;
2368                                 linelen = strlen(out_buffer);
2369                         }
2370                 }
2372                 if (!view->ops->read(view, line))
2373                         goto alloc_error;
2375                 if (lines-- == 1)
2376                         break;
2377         }
2379         {
2380                 int digits;
2382                 lines = view->lines;
2383                 for (digits = 0; lines; digits++)
2384                         lines /= 10;
2386                 /* Keep the displayed view in sync with line number scaling. */
2387                 if (digits != view->digits) {
2388                         view->digits = digits;
2389                         redraw_from = 0;
2390                 }
2391         }
2393         if (ferror(view->pipe) && errno != 0) {
2394                 report("Failed to read: %s", strerror(errno));
2395                 end_update(view, TRUE);
2397         } else if (feof(view->pipe)) {
2398                 report("");
2399                 end_update(view, FALSE);
2400         }
2402         if (view == VIEW(REQ_VIEW_TREE)) {
2403                 /* Clear the view and redraw everything since the tree sorting
2404                  * might have rearranged things. */
2405                 redraw_view(view);
2407         } else if (redraw_from >= 0) {
2408                 /* If this is an incremental update, redraw the previous line
2409                  * since for commits some members could have changed when
2410                  * loading the main view. */
2411                 if (redraw_from > 0)
2412                         redraw_from--;
2414                 /* Since revision graph visualization requires knowledge
2415                  * about the parent commit, it causes a further one-off
2416                  * needed to be redrawn for incremental updates. */
2417                 if (redraw_from > 0 && opt_rev_graph)
2418                         redraw_from--;
2420                 /* Incrementally draw avoids flickering. */
2421                 redraw_view_from(view, redraw_from);
2422         }
2424         if (view == VIEW(REQ_VIEW_BLAME))
2425                 redraw_view_dirty(view);
2427         /* Update the title _after_ the redraw so that if the redraw picks up a
2428          * commit reference in view->ref it'll be available here. */
2429         update_view_title(view);
2430         return TRUE;
2432 alloc_error:
2433         report("Allocation failure");
2434         end_update(view, TRUE);
2435         return FALSE;
2438 static struct line *
2439 add_line_data(struct view *view, void *data, enum line_type type)
2441         struct line *line = &view->line[view->lines++];
2443         memset(line, 0, sizeof(*line));
2444         line->type = type;
2445         line->data = data;
2447         return line;
2450 static struct line *
2451 add_line_text(struct view *view, const char *text, enum line_type type)
2453         char *data = text ? strdup(text) : NULL;
2455         return data ? add_line_data(view, data, type) : NULL;
2459 /*
2460  * View opening
2461  */
2463 enum open_flags {
2464         OPEN_DEFAULT = 0,       /* Use default view switching. */
2465         OPEN_SPLIT = 1,         /* Split current view. */
2466         OPEN_BACKGROUNDED = 2,  /* Backgrounded. */
2467         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
2468         OPEN_NOMAXIMIZE = 8,    /* Do not maximize the current view. */
2469         OPEN_REFRESH = 16,      /* Refresh view using previous command. */
2470 };
2472 static void
2473 open_view(struct view *prev, enum request request, enum open_flags flags)
2475         bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
2476         bool split = !!(flags & OPEN_SPLIT);
2477         bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH));
2478         bool nomaximize = !!(flags & (OPEN_NOMAXIMIZE | OPEN_REFRESH));
2479         struct view *view = VIEW(request);
2480         int nviews = displayed_views();
2481         struct view *base_view = display[0];
2483         if (view == prev && nviews == 1 && !reload) {
2484                 report("Already in %s view", view->name);
2485                 return;
2486         }
2488         if (view->git_dir && !opt_git_dir[0]) {
2489                 report("The %s view is disabled in pager view", view->name);
2490                 return;
2491         }
2493         if (split) {
2494                 display[1] = view;
2495                 if (!backgrounded)
2496                         current_view = 1;
2497         } else if (!nomaximize) {
2498                 /* Maximize the current view. */
2499                 memset(display, 0, sizeof(display));
2500                 current_view = 0;
2501                 display[current_view] = view;
2502         }
2504         /* Resize the view when switching between split- and full-screen,
2505          * or when switching between two different full-screen views. */
2506         if (nviews != displayed_views() ||
2507             (nviews == 1 && base_view != display[0]))
2508                 resize_display();
2510         if (view->pipe)
2511                 end_update(view, TRUE);
2513         if (view->ops->open) {
2514                 if (!view->ops->open(view)) {
2515                         report("Failed to load %s view", view->name);
2516                         return;
2517                 }
2519         } else if ((reload || strcmp(view->vid, view->id)) &&
2520                    !begin_update(view, flags & OPEN_REFRESH)) {
2521                 report("Failed to load %s view", view->name);
2522                 return;
2523         }
2525         if (split && prev->lineno - prev->offset >= prev->height) {
2526                 /* Take the title line into account. */
2527                 int lines = prev->lineno - prev->offset - prev->height + 1;
2529                 /* Scroll the view that was split if the current line is
2530                  * outside the new limited view. */
2531                 do_scroll_view(prev, lines);
2532         }
2534         if (prev && view != prev) {
2535                 if (split && !backgrounded) {
2536                         /* "Blur" the previous view. */
2537                         update_view_title(prev);
2538                 }
2540                 view->parent = prev;
2541         }
2543         if (view->pipe && view->lines == 0) {
2544                 /* Clear the old view and let the incremental updating refill
2545                  * the screen. */
2546                 werase(view->win);
2547                 report("");
2548         } else if (view_is_displayed(view)) {
2549                 redraw_view(view);
2550                 report("");
2551         }
2553         /* If the view is backgrounded the above calls to report()
2554          * won't redraw the view title. */
2555         if (backgrounded)
2556                 update_view_title(view);
2559 static bool
2560 run_confirm(const char *cmd, const char *prompt)
2562         bool confirmation = prompt_yesno(prompt);
2564         if (confirmation)
2565                 system(cmd);
2567         return confirmation;
2570 static void
2571 open_external_viewer(const char *cmd)
2573         def_prog_mode();           /* save current tty modes */
2574         endwin();                  /* restore original tty modes */
2575         system(cmd);
2576         fprintf(stderr, "Press Enter to continue");
2577         getc(opt_tty);
2578         reset_prog_mode();
2579         redraw_display();
2582 static void
2583 open_mergetool(const char *file)
2585         char cmd[SIZEOF_STR];
2586         char file_sq[SIZEOF_STR];
2588         if (sq_quote(file_sq, 0, file) < sizeof(file_sq) &&
2589             string_format(cmd, "git mergetool %s", file_sq)) {
2590                 open_external_viewer(cmd);
2591         }
2594 static void
2595 open_editor(bool from_root, const char *file)
2597         char cmd[SIZEOF_STR];
2598         char file_sq[SIZEOF_STR];
2599         const char *editor;
2600         char *prefix = from_root ? opt_cdup : "";
2602         editor = getenv("GIT_EDITOR");
2603         if (!editor && *opt_editor)
2604                 editor = opt_editor;
2605         if (!editor)
2606                 editor = getenv("VISUAL");
2607         if (!editor)
2608                 editor = getenv("EDITOR");
2609         if (!editor)
2610                 editor = "vi";
2612         if (sq_quote(file_sq, 0, file) < sizeof(file_sq) &&
2613             string_format(cmd, "%s %s%s", editor, prefix, file_sq)) {
2614                 open_external_viewer(cmd);
2615         }
2618 static void
2619 open_run_request(enum request request)
2621         struct run_request *req = get_run_request(request);
2622         char buf[SIZEOF_STR * 2];
2623         size_t bufpos;
2624         char *cmd;
2626         if (!req) {
2627                 report("Unknown run request");
2628                 return;
2629         }
2631         bufpos = 0;
2632         cmd = req->cmd;
2634         while (cmd) {
2635                 char *next = strstr(cmd, "%(");
2636                 int len = next - cmd;
2637                 char *value;
2639                 if (!next) {
2640                         len = strlen(cmd);
2641                         value = "";
2643                 } else if (!strncmp(next, "%(head)", 7)) {
2644                         value = ref_head;
2646                 } else if (!strncmp(next, "%(commit)", 9)) {
2647                         value = ref_commit;
2649                 } else if (!strncmp(next, "%(blob)", 7)) {
2650                         value = ref_blob;
2652                 } else {
2653                         report("Unknown replacement in run request: `%s`", req->cmd);
2654                         return;
2655                 }
2657                 if (!string_format_from(buf, &bufpos, "%.*s%s", len, cmd, value))
2658                         return;
2660                 if (next)
2661                         next = strchr(next, ')') + 1;
2662                 cmd = next;
2663         }
2665         open_external_viewer(buf);
2668 /*
2669  * User request switch noodle
2670  */
2672 static int
2673 view_driver(struct view *view, enum request request)
2675         int i;
2677         if (request == REQ_NONE) {
2678                 doupdate();
2679                 return TRUE;
2680         }
2682         if (request > REQ_NONE) {
2683                 open_run_request(request);
2684                 /* FIXME: When all views can refresh always do this. */
2685                 if (view == VIEW(REQ_VIEW_STATUS) ||
2686                     view == VIEW(REQ_VIEW_MAIN) ||
2687                     view == VIEW(REQ_VIEW_LOG) ||
2688                     view == VIEW(REQ_VIEW_STAGE))
2689                         request = REQ_REFRESH;
2690                 else
2691                         return TRUE;
2692         }
2694         if (view && view->lines) {
2695                 request = view->ops->request(view, request, &view->line[view->lineno]);
2696                 if (request == REQ_NONE)
2697                         return TRUE;
2698         }
2700         switch (request) {
2701         case REQ_MOVE_UP:
2702         case REQ_MOVE_DOWN:
2703         case REQ_MOVE_PAGE_UP:
2704         case REQ_MOVE_PAGE_DOWN:
2705         case REQ_MOVE_FIRST_LINE:
2706         case REQ_MOVE_LAST_LINE:
2707                 move_view(view, request);
2708                 break;
2710         case REQ_SCROLL_LINE_DOWN:
2711         case REQ_SCROLL_LINE_UP:
2712         case REQ_SCROLL_PAGE_DOWN:
2713         case REQ_SCROLL_PAGE_UP:
2714                 scroll_view(view, request);
2715                 break;
2717         case REQ_VIEW_BLAME:
2718                 if (!opt_file[0]) {
2719                         report("No file chosen, press %s to open tree view",
2720                                get_key(REQ_VIEW_TREE));
2721                         break;
2722                 }
2723                 open_view(view, request, OPEN_DEFAULT);
2724                 break;
2726         case REQ_VIEW_BLOB:
2727                 if (!ref_blob[0]) {
2728                         report("No file chosen, press %s to open tree view",
2729                                get_key(REQ_VIEW_TREE));
2730                         break;
2731                 }
2732                 open_view(view, request, OPEN_DEFAULT);
2733                 break;
2735         case REQ_VIEW_PAGER:
2736                 if (!opt_pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
2737                         report("No pager content, press %s to run command from prompt",
2738                                get_key(REQ_PROMPT));
2739                         break;
2740                 }
2741                 open_view(view, request, OPEN_DEFAULT);
2742                 break;
2744         case REQ_VIEW_STAGE:
2745                 if (!VIEW(REQ_VIEW_STAGE)->lines) {
2746                         report("No stage content, press %s to open the status view and choose file",
2747                                get_key(REQ_VIEW_STATUS));
2748                         break;
2749                 }
2750                 open_view(view, request, OPEN_DEFAULT);
2751                 break;
2753         case REQ_VIEW_STATUS:
2754                 if (opt_is_inside_work_tree == FALSE) {
2755                         report("The status view requires a working tree");
2756                         break;
2757                 }
2758                 open_view(view, request, OPEN_DEFAULT);
2759                 break;
2761         case REQ_VIEW_MAIN:
2762         case REQ_VIEW_DIFF:
2763         case REQ_VIEW_LOG:
2764         case REQ_VIEW_TREE:
2765         case REQ_VIEW_HELP:
2766                 open_view(view, request, OPEN_DEFAULT);
2767                 break;
2769         case REQ_NEXT:
2770         case REQ_PREVIOUS:
2771                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
2773                 if ((view == VIEW(REQ_VIEW_DIFF) &&
2774                      view->parent == VIEW(REQ_VIEW_MAIN)) ||
2775                    (view == VIEW(REQ_VIEW_DIFF) &&
2776                      view->parent == VIEW(REQ_VIEW_BLAME)) ||
2777                    (view == VIEW(REQ_VIEW_STAGE) &&
2778                      view->parent == VIEW(REQ_VIEW_STATUS)) ||
2779                    (view == VIEW(REQ_VIEW_BLOB) &&
2780                      view->parent == VIEW(REQ_VIEW_TREE))) {
2781                         int line;
2783                         view = view->parent;
2784                         line = view->lineno;
2785                         move_view(view, request);
2786                         if (view_is_displayed(view))
2787                                 update_view_title(view);
2788                         if (line != view->lineno)
2789                                 view->ops->request(view, REQ_ENTER,
2790                                                    &view->line[view->lineno]);
2792                 } else {
2793                         move_view(view, request);
2794                 }
2795                 break;
2797         case REQ_VIEW_NEXT:
2798         {
2799                 int nviews = displayed_views();
2800                 int next_view = (current_view + 1) % nviews;
2802                 if (next_view == current_view) {
2803                         report("Only one view is displayed");
2804                         break;
2805                 }
2807                 current_view = next_view;
2808                 /* Blur out the title of the previous view. */
2809                 update_view_title(view);
2810                 report("");
2811                 break;
2812         }
2813         case REQ_REFRESH:
2814                 report("Refreshing is not yet supported for the %s view", view->name);
2815                 break;
2817         case REQ_MAXIMIZE:
2818                 if (displayed_views() == 2)
2819                         open_view(view, VIEW_REQ(view), OPEN_DEFAULT);
2820                 break;
2822         case REQ_TOGGLE_LINENO:
2823                 opt_line_number = !opt_line_number;
2824                 redraw_display();
2825                 break;
2827         case REQ_TOGGLE_DATE:
2828                 opt_date = !opt_date;
2829                 redraw_display();
2830                 break;
2832         case REQ_TOGGLE_AUTHOR:
2833                 opt_author = !opt_author;
2834                 redraw_display();
2835                 break;
2837         case REQ_TOGGLE_REV_GRAPH:
2838                 opt_rev_graph = !opt_rev_graph;
2839                 redraw_display();
2840                 break;
2842         case REQ_TOGGLE_REFS:
2843                 opt_show_refs = !opt_show_refs;
2844                 redraw_display();
2845                 break;
2847         case REQ_SEARCH:
2848         case REQ_SEARCH_BACK:
2849                 search_view(view, request);
2850                 break;
2852         case REQ_FIND_NEXT:
2853         case REQ_FIND_PREV:
2854                 find_next(view, request);
2855                 break;
2857         case REQ_STOP_LOADING:
2858                 for (i = 0; i < ARRAY_SIZE(views); i++) {
2859                         view = &views[i];
2860                         if (view->pipe)
2861                                 report("Stopped loading the %s view", view->name),
2862                         end_update(view, TRUE);
2863                 }
2864                 break;
2866         case REQ_SHOW_VERSION:
2867                 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
2868                 return TRUE;
2870         case REQ_SCREEN_RESIZE:
2871                 resize_display();
2872                 /* Fall-through */
2873         case REQ_SCREEN_REDRAW:
2874                 redraw_display();
2875                 break;
2877         case REQ_EDIT:
2878                 report("Nothing to edit");
2879                 break;
2881         case REQ_ENTER:
2882                 report("Nothing to enter");
2883                 break;
2885         case REQ_VIEW_CLOSE:
2886                 /* XXX: Mark closed views by letting view->parent point to the
2887                  * view itself. Parents to closed view should never be
2888                  * followed. */
2889                 if (view->parent &&
2890                     view->parent->parent != view->parent) {
2891                         memset(display, 0, sizeof(display));
2892                         current_view = 0;
2893                         display[current_view] = view->parent;
2894                         view->parent = view;
2895                         resize_display();
2896                         redraw_display();
2897                         report("");
2898                         break;
2899                 }
2900                 /* Fall-through */
2901         case REQ_QUIT:
2902                 return FALSE;
2904         default:
2905                 report("Unknown key, press 'h' for help");
2906                 return TRUE;
2907         }
2909         return TRUE;
2913 /*
2914  * Pager backend
2915  */
2917 static bool
2918 pager_draw(struct view *view, struct line *line, unsigned int lineno)
2920         char *text = line->data;
2922         if (opt_line_number && draw_lineno(view, lineno))
2923                 return TRUE;
2925         draw_text(view, line->type, text, TRUE);
2926         return TRUE;
2929 static bool
2930 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
2932         char refbuf[SIZEOF_STR];
2933         char *ref = NULL;
2934         FILE *pipe;
2936         if (!string_format(refbuf, "git describe %s 2>/dev/null", commit_id))
2937                 return TRUE;
2939         pipe = popen(refbuf, "r");
2940         if (!pipe)
2941                 return TRUE;
2943         if ((ref = fgets(refbuf, sizeof(refbuf), pipe)))
2944                 ref = chomp_string(ref);
2945         pclose(pipe);
2947         if (!ref || !*ref)
2948                 return TRUE;
2950         /* This is the only fatal call, since it can "corrupt" the buffer. */
2951         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
2952                 return FALSE;
2954         return TRUE;
2957 static void
2958 add_pager_refs(struct view *view, struct line *line)
2960         char buf[SIZEOF_STR];
2961         char *commit_id = (char *)line->data + STRING_SIZE("commit ");
2962         struct ref **refs;
2963         size_t bufpos = 0, refpos = 0;
2964         const char *sep = "Refs: ";
2965         bool is_tag = FALSE;
2967         assert(line->type == LINE_COMMIT);
2969         refs = get_refs(commit_id);
2970         if (!refs) {
2971                 if (view == VIEW(REQ_VIEW_DIFF))
2972                         goto try_add_describe_ref;
2973                 return;
2974         }
2976         do {
2977                 struct ref *ref = refs[refpos];
2978                 const char *fmt = ref->tag    ? "%s[%s]" :
2979                                   ref->remote ? "%s<%s>" : "%s%s";
2981                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
2982                         return;
2983                 sep = ", ";
2984                 if (ref->tag)
2985                         is_tag = TRUE;
2986         } while (refs[refpos++]->next);
2988         if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
2989 try_add_describe_ref:
2990                 /* Add <tag>-g<commit_id> "fake" reference. */
2991                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
2992                         return;
2993         }
2995         if (bufpos == 0)
2996                 return;
2998         if (!realloc_lines(view, view->line_size + 1))
2999                 return;
3001         add_line_text(view, buf, LINE_PP_REFS);
3004 static bool
3005 pager_read(struct view *view, char *data)
3007         struct line *line;
3009         if (!data)
3010                 return TRUE;
3012         line = add_line_text(view, data, get_line_type(data));
3013         if (!line)
3014                 return FALSE;
3016         if (line->type == LINE_COMMIT &&
3017             (view == VIEW(REQ_VIEW_DIFF) ||
3018              view == VIEW(REQ_VIEW_LOG)))
3019                 add_pager_refs(view, line);
3021         return TRUE;
3024 static enum request
3025 pager_request(struct view *view, enum request request, struct line *line)
3027         int split = 0;
3029         if (request != REQ_ENTER)
3030                 return request;
3032         if (line->type == LINE_COMMIT &&
3033            (view == VIEW(REQ_VIEW_LOG) ||
3034             view == VIEW(REQ_VIEW_PAGER))) {
3035                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3036                 split = 1;
3037         }
3039         /* Always scroll the view even if it was split. That way
3040          * you can use Enter to scroll through the log view and
3041          * split open each commit diff. */
3042         scroll_view(view, REQ_SCROLL_LINE_DOWN);
3044         /* FIXME: A minor workaround. Scrolling the view will call report("")
3045          * but if we are scrolling a non-current view this won't properly
3046          * update the view title. */
3047         if (split)
3048                 update_view_title(view);
3050         return REQ_NONE;
3053 static bool
3054 pager_grep(struct view *view, struct line *line)
3056         regmatch_t pmatch;
3057         char *text = line->data;
3059         if (!*text)
3060                 return FALSE;
3062         if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
3063                 return FALSE;
3065         return TRUE;
3068 static void
3069 pager_select(struct view *view, struct line *line)
3071         if (line->type == LINE_COMMIT) {
3072                 char *text = (char *)line->data + STRING_SIZE("commit ");
3074                 if (view != VIEW(REQ_VIEW_PAGER))
3075                         string_copy_rev(view->ref, text);
3076                 string_copy_rev(ref_commit, text);
3077         }
3080 static struct view_ops pager_ops = {
3081         "line",
3082         NULL,
3083         pager_read,
3084         pager_draw,
3085         pager_request,
3086         pager_grep,
3087         pager_select,
3088 };
3090 static enum request
3091 log_request(struct view *view, enum request request, struct line *line)
3093         switch (request) {
3094         case REQ_REFRESH:
3095                 load_refs();
3096                 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
3097                 return REQ_NONE;
3098         default:
3099                 return pager_request(view, request, line);
3100         }
3103 static struct view_ops log_ops = {
3104         "line",
3105         NULL,
3106         pager_read,
3107         pager_draw,
3108         log_request,
3109         pager_grep,
3110         pager_select,
3111 };
3114 /*
3115  * Help backend
3116  */
3118 static bool
3119 help_open(struct view *view)
3121         char buf[BUFSIZ];
3122         int lines = ARRAY_SIZE(req_info) + 2;
3123         int i;
3125         if (view->lines > 0)
3126                 return TRUE;
3128         for (i = 0; i < ARRAY_SIZE(req_info); i++)
3129                 if (!req_info[i].request)
3130                         lines++;
3132         lines += run_requests + 1;
3134         view->line = calloc(lines, sizeof(*view->line));
3135         if (!view->line)
3136                 return FALSE;
3138         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
3140         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
3141                 const char *key;
3143                 if (req_info[i].request == REQ_NONE)
3144                         continue;
3146                 if (!req_info[i].request) {
3147                         add_line_text(view, "", LINE_DEFAULT);
3148                         add_line_text(view, req_info[i].help, LINE_DEFAULT);
3149                         continue;
3150                 }
3152                 key = get_key(req_info[i].request);
3153                 if (!*key)
3154                         key = "(no key defined)";
3156                 if (!string_format(buf, "    %-25s %s", key, req_info[i].help))
3157                         continue;
3159                 add_line_text(view, buf, LINE_DEFAULT);
3160         }
3162         if (run_requests) {
3163                 add_line_text(view, "", LINE_DEFAULT);
3164                 add_line_text(view, "External commands:", LINE_DEFAULT);
3165         }
3167         for (i = 0; i < run_requests; i++) {
3168                 struct run_request *req = get_run_request(REQ_NONE + i + 1);
3169                 const char *key;
3171                 if (!req)
3172                         continue;
3174                 key = get_key_name(req->key);
3175                 if (!*key)
3176                         key = "(no key defined)";
3178                 if (!string_format(buf, "    %-10s %-14s `%s`",
3179                                    keymap_table[req->keymap].name,
3180                                    key, req->cmd))
3181                         continue;
3183                 add_line_text(view, buf, LINE_DEFAULT);
3184         }
3186         return TRUE;
3189 static struct view_ops help_ops = {
3190         "line",
3191         help_open,
3192         NULL,
3193         pager_draw,
3194         pager_request,
3195         pager_grep,
3196         pager_select,
3197 };
3200 /*
3201  * Tree backend
3202  */
3204 struct tree_stack_entry {
3205         struct tree_stack_entry *prev;  /* Entry below this in the stack */
3206         unsigned long lineno;           /* Line number to restore */
3207         char *name;                     /* Position of name in opt_path */
3208 };
3210 /* The top of the path stack. */
3211 static struct tree_stack_entry *tree_stack = NULL;
3212 unsigned long tree_lineno = 0;
3214 static void
3215 pop_tree_stack_entry(void)
3217         struct tree_stack_entry *entry = tree_stack;
3219         tree_lineno = entry->lineno;
3220         entry->name[0] = 0;
3221         tree_stack = entry->prev;
3222         free(entry);
3225 static void
3226 push_tree_stack_entry(const char *name, unsigned long lineno)
3228         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
3229         size_t pathlen = strlen(opt_path);
3231         if (!entry)
3232                 return;
3234         entry->prev = tree_stack;
3235         entry->name = opt_path + pathlen;
3236         tree_stack = entry;
3238         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
3239                 pop_tree_stack_entry();
3240                 return;
3241         }
3243         /* Move the current line to the first tree entry. */
3244         tree_lineno = 1;
3245         entry->lineno = lineno;
3248 /* Parse output from git-ls-tree(1):
3249  *
3250  * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile
3251  * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README
3252  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
3253  * 100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38 web.conf
3254  */
3256 #define SIZEOF_TREE_ATTR \
3257         STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t")
3259 #define TREE_UP_FORMAT "040000 tree %s\t.."
3261 static int
3262 tree_compare_entry(enum line_type type1, const char *name1,
3263                    enum line_type type2, const char *name2)
3265         if (type1 != type2) {
3266                 if (type1 == LINE_TREE_DIR)
3267                         return -1;
3268                 return 1;
3269         }
3271         return strcmp(name1, name2);
3274 static const char *
3275 tree_path(struct line *line)
3277         const char *path = line->data;
3279         return path + SIZEOF_TREE_ATTR;
3282 static bool
3283 tree_read(struct view *view, char *text)
3285         size_t textlen = text ? strlen(text) : 0;
3286         char buf[SIZEOF_STR];
3287         unsigned long pos;
3288         enum line_type type;
3289         bool first_read = view->lines == 0;
3291         if (!text)
3292                 return TRUE;
3293         if (textlen <= SIZEOF_TREE_ATTR)
3294                 return FALSE;
3296         type = text[STRING_SIZE("100644 ")] == 't'
3297              ? LINE_TREE_DIR : LINE_TREE_FILE;
3299         if (first_read) {
3300                 /* Add path info line */
3301                 if (!string_format(buf, "Directory path /%s", opt_path) ||
3302                     !realloc_lines(view, view->line_size + 1) ||
3303                     !add_line_text(view, buf, LINE_DEFAULT))
3304                         return FALSE;
3306                 /* Insert "link" to parent directory. */
3307                 if (*opt_path) {
3308                         if (!string_format(buf, TREE_UP_FORMAT, view->ref) ||
3309                             !realloc_lines(view, view->line_size + 1) ||
3310                             !add_line_text(view, buf, LINE_TREE_DIR))
3311                                 return FALSE;
3312                 }
3313         }
3315         /* Strip the path part ... */
3316         if (*opt_path) {
3317                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
3318                 size_t striplen = strlen(opt_path);
3319                 char *path = text + SIZEOF_TREE_ATTR;
3321                 if (pathlen > striplen)
3322                         memmove(path, path + striplen,
3323                                 pathlen - striplen + 1);
3324         }
3326         /* Skip "Directory ..." and ".." line. */
3327         for (pos = 1 + !!*opt_path; pos < view->lines; pos++) {
3328                 struct line *line = &view->line[pos];
3329                 const char *path1 = tree_path(line);
3330                 char *path2 = text + SIZEOF_TREE_ATTR;
3331                 int cmp = tree_compare_entry(line->type, path1, type, path2);
3333                 if (cmp <= 0)
3334                         continue;
3336                 text = strdup(text);
3337                 if (!text)
3338                         return FALSE;
3340                 if (view->lines > pos)
3341                         memmove(&view->line[pos + 1], &view->line[pos],
3342                                 (view->lines - pos) * sizeof(*line));
3344                 line = &view->line[pos];
3345                 line->data = text;
3346                 line->type = type;
3347                 view->lines++;
3348                 return TRUE;
3349         }
3351         if (!add_line_text(view, text, type))
3352                 return FALSE;
3354         if (tree_lineno > view->lineno) {
3355                 view->lineno = tree_lineno;
3356                 tree_lineno = 0;
3357         }
3359         return TRUE;
3362 static enum request
3363 tree_request(struct view *view, enum request request, struct line *line)
3365         enum open_flags flags;
3367         switch (request) {
3368         case REQ_VIEW_BLAME:
3369                 if (line->type != LINE_TREE_FILE) {
3370                         report("Blame only supported for files");
3371                         return REQ_NONE;
3372                 }
3374                 string_copy(opt_ref, view->vid);
3375                 string_format(opt_file, "%s%s", opt_path, tree_path(line));
3376                 return request;
3378         case REQ_TREE_PARENT:
3379                 if (!*opt_path) {
3380                         /* quit view if at top of tree */
3381                         return REQ_VIEW_CLOSE;
3382                 }
3383                 /* fake 'cd  ..' */
3384                 line = &view->line[1];
3385                 break;
3387         case REQ_ENTER:
3388                 break;
3390         default:
3391                 return request;
3392         }
3394         /* Cleanup the stack if the tree view is at a different tree. */
3395         while (!*opt_path && tree_stack)
3396                 pop_tree_stack_entry();
3398         switch (line->type) {
3399         case LINE_TREE_DIR:
3400                 /* Depending on whether it is a subdir or parent (updir?) link
3401                  * mangle the path buffer. */
3402                 if (line == &view->line[1] && *opt_path) {
3403                         pop_tree_stack_entry();
3405                 } else {
3406                         const char *basename = tree_path(line);
3408                         push_tree_stack_entry(basename, view->lineno);
3409                 }
3411                 /* Trees and subtrees share the same ID, so they are not not
3412                  * unique like blobs. */
3413                 flags = OPEN_RELOAD;
3414                 request = REQ_VIEW_TREE;
3415                 break;
3417         case LINE_TREE_FILE:
3418                 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
3419                 request = REQ_VIEW_BLOB;
3420                 break;
3422         default:
3423                 return TRUE;
3424         }
3426         open_view(view, request, flags);
3427         if (request == REQ_VIEW_TREE) {
3428                 view->lineno = tree_lineno;
3429         }
3431         return REQ_NONE;
3434 static void
3435 tree_select(struct view *view, struct line *line)
3437         char *text = (char *)line->data + STRING_SIZE("100644 blob ");
3439         if (line->type == LINE_TREE_FILE) {
3440                 string_copy_rev(ref_blob, text);
3442         } else if (line->type != LINE_TREE_DIR) {
3443                 return;
3444         }
3446         string_copy_rev(view->ref, text);
3449 static struct view_ops tree_ops = {
3450         "file",
3451         NULL,
3452         tree_read,
3453         pager_draw,
3454         tree_request,
3455         pager_grep,
3456         tree_select,
3457 };
3459 static bool
3460 blob_read(struct view *view, char *line)
3462         if (!line)
3463                 return TRUE;
3464         return add_line_text(view, line, LINE_DEFAULT) != NULL;
3467 static struct view_ops blob_ops = {
3468         "line",
3469         NULL,
3470         blob_read,
3471         pager_draw,
3472         pager_request,
3473         pager_grep,
3474         pager_select,
3475 };
3477 /*
3478  * Blame backend
3479  *
3480  * Loading the blame view is a two phase job:
3481  *
3482  *  1. File content is read either using opt_file from the
3483  *     filesystem or using git-cat-file.
3484  *  2. Then blame information is incrementally added by
3485  *     reading output from git-blame.
3486  */
3488 struct blame_commit {
3489         char id[SIZEOF_REV];            /* SHA1 ID. */
3490         char title[128];                /* First line of the commit message. */
3491         char author[75];                /* Author of the commit. */
3492         struct tm time;                 /* Date from the author ident. */
3493         char filename[128];             /* Name of file. */
3494 };
3496 struct blame {
3497         struct blame_commit *commit;
3498         unsigned int header:1;
3499         char text[1];
3500 };
3502 #define BLAME_CAT_FILE_CMD "git cat-file blob %s:%s"
3503 #define BLAME_INCREMENTAL_CMD "git blame --incremental %s -- %s"
3505 static bool
3506 blame_open(struct view *view)
3508         char path[SIZEOF_STR];
3509         char ref[SIZEOF_STR] = "";
3511         if (sq_quote(path, 0, opt_file) >= sizeof(path))
3512                 return FALSE;
3514         if (*opt_ref && sq_quote(ref, 0, opt_ref) >= sizeof(ref))
3515                 return FALSE;
3517         if (*opt_ref) {
3518                 if (!string_format(view->cmd, BLAME_CAT_FILE_CMD, ref, path))
3519                         return FALSE;
3520         } else {
3521                 view->pipe = fopen(opt_file, "r");
3522                 if (!view->pipe &&
3523                     !string_format(view->cmd, BLAME_CAT_FILE_CMD, "HEAD", path))
3524                         return FALSE;
3525         }
3527         if (!view->pipe)
3528                 view->pipe = popen(view->cmd, "r");
3529         if (!view->pipe)
3530                 return FALSE;
3532         if (!string_format(view->cmd, BLAME_INCREMENTAL_CMD, ref, path))
3533                 return FALSE;
3535         reset_view(view);
3536         string_format(view->ref, "%s ...", opt_file);
3537         string_copy_rev(view->vid, opt_file);
3538         set_nonblocking_input(TRUE);
3539         view->start_time = time(NULL);
3541         return TRUE;
3544 static struct blame_commit *
3545 get_blame_commit(struct view *view, const char *id)
3547         size_t i;
3549         for (i = 0; i < view->lines; i++) {
3550                 struct blame *blame = view->line[i].data;
3552                 if (!blame->commit)
3553                         continue;
3555                 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
3556                         return blame->commit;
3557         }
3559         {
3560                 struct blame_commit *commit = calloc(1, sizeof(*commit));
3562                 if (commit)
3563                         string_ncopy(commit->id, id, SIZEOF_REV);
3564                 return commit;
3565         }
3568 static bool
3569 parse_number(const char **posref, size_t *number, size_t min, size_t max)
3571         const char *pos = *posref;
3573         *posref = NULL;
3574         pos = strchr(pos + 1, ' ');
3575         if (!pos || !isdigit(pos[1]))
3576                 return FALSE;
3577         *number = atoi(pos + 1);
3578         if (*number < min || *number > max)
3579                 return FALSE;
3581         *posref = pos;
3582         return TRUE;
3585 static struct blame_commit *
3586 parse_blame_commit(struct view *view, const char *text, int *blamed)
3588         struct blame_commit *commit;
3589         struct blame *blame;
3590         const char *pos = text + SIZEOF_REV - 1;
3591         size_t lineno;
3592         size_t group;
3594         if (strlen(text) <= SIZEOF_REV || *pos != ' ')
3595                 return NULL;
3597         if (!parse_number(&pos, &lineno, 1, view->lines) ||
3598             !parse_number(&pos, &group, 1, view->lines - lineno + 1))
3599                 return NULL;
3601         commit = get_blame_commit(view, text);
3602         if (!commit)
3603                 return NULL;
3605         *blamed += group;
3606         while (group--) {
3607                 struct line *line = &view->line[lineno + group - 1];
3609                 blame = line->data;
3610                 blame->commit = commit;
3611                 blame->header = !group;
3612                 line->dirty = 1;
3613         }
3615         return commit;
3618 static bool
3619 blame_read_file(struct view *view, const char *line)
3621         if (!line) {
3622                 FILE *pipe = NULL;
3624                 if (view->lines > 0)
3625                         pipe = popen(view->cmd, "r");
3626                 else if (!view->parent)
3627                         die("No blame exist for %s", view->vid);
3628                 view->cmd[0] = 0;
3629                 if (!pipe) {
3630                         report("Failed to load blame data");
3631                         return TRUE;
3632                 }
3634                 fclose(view->pipe);
3635                 view->pipe = pipe;
3636                 return FALSE;
3638         } else {
3639                 size_t linelen = strlen(line);
3640                 struct blame *blame = malloc(sizeof(*blame) + linelen);
3642                 blame->commit = NULL;
3643                 strncpy(blame->text, line, linelen);
3644                 blame->text[linelen] = 0;
3645                 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
3646         }
3649 static bool
3650 match_blame_header(const char *name, char **line)
3652         size_t namelen = strlen(name);
3653         bool matched = !strncmp(name, *line, namelen);
3655         if (matched)
3656                 *line += namelen;
3658         return matched;
3661 static bool
3662 blame_read(struct view *view, char *line)
3664         static struct blame_commit *commit = NULL;
3665         static int blamed = 0;
3666         static time_t author_time;
3668         if (*view->cmd)
3669                 return blame_read_file(view, line);
3671         if (!line) {
3672                 /* Reset all! */
3673                 commit = NULL;
3674                 blamed = 0;
3675                 string_format(view->ref, "%s", view->vid);
3676                 if (view_is_displayed(view)) {
3677                         update_view_title(view);
3678                         redraw_view_from(view, 0);
3679                 }
3680                 return TRUE;
3681         }
3683         if (!commit) {
3684                 commit = parse_blame_commit(view, line, &blamed);
3685                 string_format(view->ref, "%s %2d%%", view->vid,
3686                               blamed * 100 / view->lines);
3688         } else if (match_blame_header("author ", &line)) {
3689                 string_ncopy(commit->author, line, strlen(line));
3691         } else if (match_blame_header("author-time ", &line)) {
3692                 author_time = (time_t) atol(line);
3694         } else if (match_blame_header("author-tz ", &line)) {
3695                 long tz;
3697                 tz  = ('0' - line[1]) * 60 * 60 * 10;
3698                 tz += ('0' - line[2]) * 60 * 60;
3699                 tz += ('0' - line[3]) * 60;
3700                 tz += ('0' - line[4]) * 60;
3702                 if (line[0] == '-')
3703                         tz = -tz;
3705                 author_time -= tz;
3706                 gmtime_r(&author_time, &commit->time);
3708         } else if (match_blame_header("summary ", &line)) {
3709                 string_ncopy(commit->title, line, strlen(line));
3711         } else if (match_blame_header("filename ", &line)) {
3712                 string_ncopy(commit->filename, line, strlen(line));
3713                 commit = NULL;
3714         }
3716         return TRUE;
3719 static bool
3720 blame_draw(struct view *view, struct line *line, unsigned int lineno)
3722         struct blame *blame = line->data;
3723         struct tm *time = NULL;
3724         const char *id = NULL, *author = NULL;
3726         if (blame->commit && *blame->commit->filename) {
3727                 id = blame->commit->id;
3728                 author = blame->commit->author;
3729                 time = &blame->commit->time;
3730         }
3732         if (opt_date && draw_date(view, time))
3733                 return TRUE;
3735         if (opt_author &&
3736             draw_field(view, LINE_MAIN_AUTHOR, author, opt_author_cols, TRUE))
3737                 return TRUE;
3739         if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
3740                 return TRUE;
3742         if (draw_lineno(view, lineno))
3743                 return TRUE;
3745         draw_text(view, LINE_DEFAULT, blame->text, TRUE);
3746         return TRUE;
3749 static enum request
3750 blame_request(struct view *view, enum request request, struct line *line)
3752         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
3753         struct blame *blame = line->data;
3755         switch (request) {
3756         case REQ_ENTER:
3757                 if (!blame->commit) {
3758                         report("No commit loaded yet");
3759                         break;
3760                 }
3762                 if (!strcmp(blame->commit->id, NULL_ID)) {
3763                         char path[SIZEOF_STR];
3765                         if (sq_quote(path, 0, view->vid) >= sizeof(path))
3766                                 break;
3767                         string_format(opt_cmd, "git diff-index --root --patch-with-stat -C -M --cached HEAD -- %s 2>/dev/null", path);
3768                 }
3770                 open_view(view, REQ_VIEW_DIFF, flags);
3771                 break;
3773         default:
3774                 return request;
3775         }
3777         return REQ_NONE;
3780 static bool
3781 blame_grep(struct view *view, struct line *line)
3783         struct blame *blame = line->data;
3784         struct blame_commit *commit = blame->commit;
3785         regmatch_t pmatch;
3787 #define MATCH(text, on)                                                 \
3788         (on && *text && regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
3790         if (commit) {
3791                 char buf[DATE_COLS + 1];
3793                 if (MATCH(commit->title, 1) ||
3794                     MATCH(commit->author, opt_author) ||
3795                     MATCH(commit->id, opt_date))
3796                         return TRUE;
3798                 if (strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time) &&
3799                     MATCH(buf, 1))
3800                         return TRUE;
3801         }
3803         return MATCH(blame->text, 1);
3805 #undef MATCH
3808 static void
3809 blame_select(struct view *view, struct line *line)
3811         struct blame *blame = line->data;
3812         struct blame_commit *commit = blame->commit;
3814         if (!commit)
3815                 return;
3817         if (!strcmp(commit->id, NULL_ID))
3818                 string_ncopy(ref_commit, "HEAD", 4);
3819         else
3820                 string_copy_rev(ref_commit, commit->id);
3823 static struct view_ops blame_ops = {
3824         "line",
3825         blame_open,
3826         blame_read,
3827         blame_draw,
3828         blame_request,
3829         blame_grep,
3830         blame_select,
3831 };
3833 /*
3834  * Status backend
3835  */
3837 struct status {
3838         char status;
3839         struct {
3840                 mode_t mode;
3841                 char rev[SIZEOF_REV];
3842                 char name[SIZEOF_STR];
3843         } old;
3844         struct {
3845                 mode_t mode;
3846                 char rev[SIZEOF_REV];
3847                 char name[SIZEOF_STR];
3848         } new;
3849 };
3851 static char status_onbranch[SIZEOF_STR];
3852 static struct status stage_status;
3853 static enum line_type stage_line_type;
3854 static size_t stage_chunks;
3855 static int *stage_chunk;
3857 /* This should work even for the "On branch" line. */
3858 static inline bool
3859 status_has_none(struct view *view, struct line *line)
3861         return line < view->line + view->lines && !line[1].data;
3864 /* Get fields from the diff line:
3865  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
3866  */
3867 static inline bool
3868 status_get_diff(struct status *file, const char *buf, size_t bufsize)
3870         const char *old_mode = buf +  1;
3871         const char *new_mode = buf +  8;
3872         const char *old_rev  = buf + 15;
3873         const char *new_rev  = buf + 56;
3874         const char *status   = buf + 97;
3876         if (bufsize < 99 ||
3877             old_mode[-1] != ':' ||
3878             new_mode[-1] != ' ' ||
3879             old_rev[-1]  != ' ' ||
3880             new_rev[-1]  != ' ' ||
3881             status[-1]   != ' ')
3882                 return FALSE;
3884         file->status = *status;
3886         string_copy_rev(file->old.rev, old_rev);
3887         string_copy_rev(file->new.rev, new_rev);
3889         file->old.mode = strtoul(old_mode, NULL, 8);
3890         file->new.mode = strtoul(new_mode, NULL, 8);
3892         file->old.name[0] = file->new.name[0] = 0;
3894         return TRUE;
3897 static bool
3898 status_run(struct view *view, const char cmd[], char status, enum line_type type)
3900         struct status *file = NULL;
3901         struct status *unmerged = NULL;
3902         char buf[SIZEOF_STR * 4];
3903         size_t bufsize = 0;
3904         FILE *pipe;
3906         pipe = popen(cmd, "r");
3907         if (!pipe)
3908                 return FALSE;
3910         add_line_data(view, NULL, type);
3912         while (!feof(pipe) && !ferror(pipe)) {
3913                 char *sep;
3914                 size_t readsize;
3916                 readsize = fread(buf + bufsize, 1, sizeof(buf) - bufsize, pipe);
3917                 if (!readsize)
3918                         break;
3919                 bufsize += readsize;
3921                 /* Process while we have NUL chars. */
3922                 while ((sep = memchr(buf, 0, bufsize))) {
3923                         size_t sepsize = sep - buf + 1;
3925                         if (!file) {
3926                                 if (!realloc_lines(view, view->line_size + 1))
3927                                         goto error_out;
3929                                 file = calloc(1, sizeof(*file));
3930                                 if (!file)
3931                                         goto error_out;
3933                                 add_line_data(view, file, type);
3934                         }
3936                         /* Parse diff info part. */
3937                         if (status) {
3938                                 file->status = status;
3939                                 if (status == 'A')
3940                                         string_copy(file->old.rev, NULL_ID);
3942                         } else if (!file->status) {
3943                                 if (!status_get_diff(file, buf, sepsize))
3944                                         goto error_out;
3946                                 bufsize -= sepsize;
3947                                 memmove(buf, sep + 1, bufsize);
3949                                 sep = memchr(buf, 0, bufsize);
3950                                 if (!sep)
3951                                         break;
3952                                 sepsize = sep - buf + 1;
3954                                 /* Collapse all 'M'odified entries that
3955                                  * follow a associated 'U'nmerged entry.
3956                                  */
3957                                 if (file->status == 'U') {
3958                                         unmerged = file;
3960                                 } else if (unmerged) {
3961                                         int collapse = !strcmp(buf, unmerged->new.name);
3963                                         unmerged = NULL;
3964                                         if (collapse) {
3965                                                 free(file);
3966                                                 view->lines--;
3967                                                 continue;
3968                                         }
3969                                 }
3970                         }
3972                         /* Grab the old name for rename/copy. */
3973                         if (!*file->old.name &&
3974                             (file->status == 'R' || file->status == 'C')) {
3975                                 sepsize = sep - buf + 1;
3976                                 string_ncopy(file->old.name, buf, sepsize);
3977                                 bufsize -= sepsize;
3978                                 memmove(buf, sep + 1, bufsize);
3980                                 sep = memchr(buf, 0, bufsize);
3981                                 if (!sep)
3982                                         break;
3983                                 sepsize = sep - buf + 1;
3984                         }
3986                         /* git-ls-files just delivers a NUL separated
3987                          * list of file names similar to the second half
3988                          * of the git-diff-* output. */
3989                         string_ncopy(file->new.name, buf, sepsize);
3990                         if (!*file->old.name)
3991                                 string_copy(file->old.name, file->new.name);
3992                         bufsize -= sepsize;
3993                         memmove(buf, sep + 1, bufsize);
3994                         file = NULL;
3995                 }
3996         }
3998         if (ferror(pipe)) {
3999 error_out:
4000                 pclose(pipe);
4001                 return FALSE;
4002         }
4004         if (!view->line[view->lines - 1].data)
4005                 add_line_data(view, NULL, LINE_STAT_NONE);
4007         pclose(pipe);
4008         return TRUE;
4011 /* Don't show unmerged entries in the staged section. */
4012 #define STATUS_DIFF_INDEX_CMD "git diff-index -z --diff-filter=ACDMRTXB --cached -M HEAD"
4013 #define STATUS_DIFF_FILES_CMD "git diff-files -z"
4014 #define STATUS_LIST_OTHER_CMD \
4015         "git ls-files -z --others --exclude-standard"
4016 #define STATUS_LIST_NO_HEAD_CMD \
4017         "git ls-files -z --cached --exclude-standard"
4019 #define STATUS_DIFF_INDEX_SHOW_CMD \
4020         "git diff-index --root --patch-with-stat -C -M --cached HEAD -- %s %s 2>/dev/null"
4022 #define STATUS_DIFF_FILES_SHOW_CMD \
4023         "git diff-files --root --patch-with-stat -C -M -- %s %s 2>/dev/null"
4025 #define STATUS_DIFF_NO_HEAD_SHOW_CMD \
4026         "git diff --no-color --patch-with-stat /dev/null %s 2>/dev/null"
4028 /* First parse staged info using git-diff-index(1), then parse unstaged
4029  * info using git-diff-files(1), and finally untracked files using
4030  * git-ls-files(1). */
4031 static bool
4032 status_open(struct view *view)
4034         unsigned long prev_lineno = view->lineno;
4036         reset_view(view);
4038         if (!realloc_lines(view, view->line_size + 7))
4039                 return FALSE;
4041         add_line_data(view, NULL, LINE_STAT_HEAD);
4042         if (is_initial_commit())
4043                 string_copy(status_onbranch, "Initial commit");
4044         else if (!*opt_head)
4045                 string_copy(status_onbranch, "Not currently on any branch");
4046         else if (!string_format(status_onbranch, "On branch %s", opt_head))
4047                 return FALSE;
4049         system("git update-index -q --refresh >/dev/null 2>/dev/null");
4051         if (is_initial_commit()) {
4052                 if (!status_run(view, STATUS_LIST_NO_HEAD_CMD, 'A', LINE_STAT_STAGED))
4053                         return FALSE;
4054         } else if (!status_run(view, STATUS_DIFF_INDEX_CMD, 0, LINE_STAT_STAGED)) {
4055                 return FALSE;
4056         }
4058         if (!status_run(view, STATUS_DIFF_FILES_CMD, 0, LINE_STAT_UNSTAGED) ||
4059             !status_run(view, STATUS_LIST_OTHER_CMD, '?', LINE_STAT_UNTRACKED))
4060                 return FALSE;
4062         /* If all went well restore the previous line number to stay in
4063          * the context or select a line with something that can be
4064          * updated. */
4065         if (prev_lineno >= view->lines)
4066                 prev_lineno = view->lines - 1;
4067         while (prev_lineno < view->lines && !view->line[prev_lineno].data)
4068                 prev_lineno++;
4069         while (prev_lineno > 0 && !view->line[prev_lineno].data)
4070                 prev_lineno--;
4072         /* If the above fails, always skip the "On branch" line. */
4073         if (prev_lineno < view->lines)
4074                 view->lineno = prev_lineno;
4075         else
4076                 view->lineno = 1;
4078         if (view->lineno < view->offset)
4079                 view->offset = view->lineno;
4080         else if (view->offset + view->height <= view->lineno)
4081                 view->offset = view->lineno - view->height + 1;
4083         return TRUE;
4086 static bool
4087 status_draw(struct view *view, struct line *line, unsigned int lineno)
4089         struct status *status = line->data;
4090         enum line_type type;
4091         const char *text;
4093         if (!status) {
4094                 switch (line->type) {
4095                 case LINE_STAT_STAGED:
4096                         type = LINE_STAT_SECTION;
4097                         text = "Changes to be committed:";
4098                         break;
4100                 case LINE_STAT_UNSTAGED:
4101                         type = LINE_STAT_SECTION;
4102                         text = "Changed but not updated:";
4103                         break;
4105                 case LINE_STAT_UNTRACKED:
4106                         type = LINE_STAT_SECTION;
4107                         text = "Untracked files:";
4108                         break;
4110                 case LINE_STAT_NONE:
4111                         type = LINE_DEFAULT;
4112                         text = "    (no files)";
4113                         break;
4115                 case LINE_STAT_HEAD:
4116                         type = LINE_STAT_HEAD;
4117                         text = status_onbranch;
4118                         break;
4120                 default:
4121                         return FALSE;
4122                 }
4123         } else {
4124                 static char buf[] = { '?', ' ', ' ', ' ', 0 };
4126                 buf[0] = status->status;
4127                 if (draw_text(view, line->type, buf, TRUE))
4128                         return TRUE;
4129                 type = LINE_DEFAULT;
4130                 text = status->new.name;
4131         }
4133         draw_text(view, type, text, TRUE);
4134         return TRUE;
4137 static enum request
4138 status_enter(struct view *view, struct line *line)
4140         struct status *status = line->data;
4141         char oldpath[SIZEOF_STR] = "";
4142         char newpath[SIZEOF_STR] = "";
4143         const char *info;
4144         size_t cmdsize = 0;
4145         enum open_flags split;
4147         if (line->type == LINE_STAT_NONE ||
4148             (!status && line[1].type == LINE_STAT_NONE)) {
4149                 report("No file to diff");
4150                 return REQ_NONE;
4151         }
4153         if (status) {
4154                 if (sq_quote(oldpath, 0, status->old.name) >= sizeof(oldpath))
4155                         return REQ_QUIT;
4156                 /* Diffs for unmerged entries are empty when pasing the
4157                  * new path, so leave it empty. */
4158                 if (status->status != 'U' &&
4159                     sq_quote(newpath, 0, status->new.name) >= sizeof(newpath))
4160                         return REQ_QUIT;
4161         }
4163         if (opt_cdup[0] &&
4164             line->type != LINE_STAT_UNTRACKED &&
4165             !string_format_from(opt_cmd, &cmdsize, "cd %s;", opt_cdup))
4166                 return REQ_QUIT;
4168         switch (line->type) {
4169         case LINE_STAT_STAGED:
4170                 if (is_initial_commit()) {
4171                         if (!string_format_from(opt_cmd, &cmdsize,
4172                                                 STATUS_DIFF_NO_HEAD_SHOW_CMD,
4173                                                 newpath))
4174                                 return REQ_QUIT;
4175                 } else {
4176                         if (!string_format_from(opt_cmd, &cmdsize,
4177                                                 STATUS_DIFF_INDEX_SHOW_CMD,
4178                                                 oldpath, newpath))
4179                                 return REQ_QUIT;
4180                 }
4182                 if (status)
4183                         info = "Staged changes to %s";
4184                 else
4185                         info = "Staged changes";
4186                 break;
4188         case LINE_STAT_UNSTAGED:
4189                 if (!string_format_from(opt_cmd, &cmdsize,
4190                                         STATUS_DIFF_FILES_SHOW_CMD, oldpath, newpath))
4191                         return REQ_QUIT;
4192                 if (status)
4193                         info = "Unstaged changes to %s";
4194                 else
4195                         info = "Unstaged changes";
4196                 break;
4198         case LINE_STAT_UNTRACKED:
4199                 if (opt_pipe)
4200                         return REQ_QUIT;
4202                 if (!status) {
4203                         report("No file to show");
4204                         return REQ_NONE;
4205                 }
4207                 if (!suffixcmp(status->new.name, -1, "/")) {
4208                         report("Cannot display a directory");
4209                         return REQ_NONE;
4210                 }
4212                 opt_pipe = fopen(status->new.name, "r");
4213                 info = "Untracked file %s";
4214                 break;
4216         case LINE_STAT_HEAD:
4217                 return REQ_NONE;
4219         default:
4220                 die("line type %d not handled in switch", line->type);
4221         }
4223         split = view_is_displayed(view) ? OPEN_SPLIT : 0;
4224         open_view(view, REQ_VIEW_STAGE, OPEN_RELOAD | split);
4225         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
4226                 if (status) {
4227                         stage_status = *status;
4228                 } else {
4229                         memset(&stage_status, 0, sizeof(stage_status));
4230                 }
4232                 stage_line_type = line->type;
4233                 stage_chunks = 0;
4234                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
4235         }
4237         return REQ_NONE;
4240 static bool
4241 status_exists(struct status *status, enum line_type type)
4243         struct view *view = VIEW(REQ_VIEW_STATUS);
4244         struct line *line;
4246         for (line = view->line; line < view->line + view->lines; line++) {
4247                 struct status *pos = line->data;
4249                 if (line->type == type && pos &&
4250                     !strcmp(status->new.name, pos->new.name))
4251                         return TRUE;
4252         }
4254         return FALSE;
4258 static FILE *
4259 status_update_prepare(enum line_type type)
4261         char cmd[SIZEOF_STR];
4262         size_t cmdsize = 0;
4264         if (opt_cdup[0] &&
4265             type != LINE_STAT_UNTRACKED &&
4266             !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
4267                 return NULL;
4269         switch (type) {
4270         case LINE_STAT_STAGED:
4271                 string_add(cmd, cmdsize, "git update-index -z --index-info");
4272                 break;
4274         case LINE_STAT_UNSTAGED:
4275         case LINE_STAT_UNTRACKED:
4276                 string_add(cmd, cmdsize, "git update-index -z --add --remove --stdin");
4277                 break;
4279         default:
4280                 die("line type %d not handled in switch", type);
4281         }
4283         return popen(cmd, "w");
4286 static bool
4287 status_update_write(FILE *pipe, struct status *status, enum line_type type)
4289         char buf[SIZEOF_STR];
4290         size_t bufsize = 0;
4291         size_t written = 0;
4293         switch (type) {
4294         case LINE_STAT_STAGED:
4295                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
4296                                         status->old.mode,
4297                                         status->old.rev,
4298                                         status->old.name, 0))
4299                         return FALSE;
4300                 break;
4302         case LINE_STAT_UNSTAGED:
4303         case LINE_STAT_UNTRACKED:
4304                 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
4305                         return FALSE;
4306                 break;
4308         default:
4309                 die("line type %d not handled in switch", type);
4310         }
4312         while (!ferror(pipe) && written < bufsize) {
4313                 written += fwrite(buf + written, 1, bufsize - written, pipe);
4314         }
4316         return written == bufsize;
4319 static bool
4320 status_update_file(struct status *status, enum line_type type)
4322         FILE *pipe = status_update_prepare(type);
4323         bool result;
4325         if (!pipe)
4326                 return FALSE;
4328         result = status_update_write(pipe, status, type);
4329         pclose(pipe);
4330         return result;
4333 static bool
4334 status_update_files(struct view *view, struct line *line)
4336         FILE *pipe = status_update_prepare(line->type);
4337         bool result = TRUE;
4338         struct line *pos = view->line + view->lines;
4339         int files = 0;
4340         int file, done;
4342         if (!pipe)
4343                 return FALSE;
4345         for (pos = line; pos < view->line + view->lines && pos->data; pos++)
4346                 files++;
4348         for (file = 0, done = 0; result && file < files; line++, file++) {
4349                 int almost_done = file * 100 / files;
4351                 if (almost_done > done) {
4352                         done = almost_done;
4353                         string_format(view->ref, "updating file %u of %u (%d%% done)",
4354                                       file, files, done);
4355                         update_view_title(view);
4356                 }
4357                 result = status_update_write(pipe, line->data, line->type);
4358         }
4360         pclose(pipe);
4361         return result;
4364 static bool
4365 status_update(struct view *view)
4367         struct line *line = &view->line[view->lineno];
4369         assert(view->lines);
4371         if (!line->data) {
4372                 /* This should work even for the "On branch" line. */
4373                 if (line < view->line + view->lines && !line[1].data) {
4374                         report("Nothing to update");
4375                         return FALSE;
4376                 }
4378                 if (!status_update_files(view, line + 1)) {
4379                         report("Failed to update file status");
4380                         return FALSE;
4381                 }
4383         } else if (!status_update_file(line->data, line->type)) {
4384                 report("Failed to update file status");
4385                 return FALSE;
4386         }
4388         return TRUE;
4391 static bool
4392 status_revert(struct status *status, enum line_type type, bool has_none)
4394         if (!status || type != LINE_STAT_UNSTAGED) {
4395                 if (type == LINE_STAT_STAGED) {
4396                         report("Cannot revert changes to staged files");
4397                 } else if (type == LINE_STAT_UNTRACKED) {
4398                         report("Cannot revert changes to untracked files");
4399                 } else if (has_none) {
4400                         report("Nothing to revert");
4401                 } else {
4402                         report("Cannot revert changes to multiple files");
4403                 }
4404                 return FALSE;
4406         } else {
4407                 char cmd[SIZEOF_STR];
4408                 char file_sq[SIZEOF_STR];
4410                 if (sq_quote(file_sq, 0, status->old.name) >= sizeof(file_sq) ||
4411                     !string_format(cmd, "git checkout -- %s%s", opt_cdup, file_sq))
4412                         return FALSE;
4414                 return run_confirm(cmd, "Are you sure you want to overwrite any changes?");
4415         }
4418 static enum request
4419 status_request(struct view *view, enum request request, struct line *line)
4421         struct status *status = line->data;
4423         switch (request) {
4424         case REQ_STATUS_UPDATE:
4425                 if (!status_update(view))
4426                         return REQ_NONE;
4427                 break;
4429         case REQ_STATUS_REVERT:
4430                 if (!status_revert(status, line->type, status_has_none(view, line)))
4431                         return REQ_NONE;
4432                 break;
4434         case REQ_STATUS_MERGE:
4435                 if (!status || status->status != 'U') {
4436                         report("Merging only possible for files with unmerged status ('U').");
4437                         return REQ_NONE;
4438                 }
4439                 open_mergetool(status->new.name);
4440                 break;
4442         case REQ_EDIT:
4443                 if (!status)
4444                         return request;
4445                 if (status->status == 'D') {
4446                         report("File has been deleted.");
4447                         return REQ_NONE;
4448                 }
4450                 open_editor(status->status != '?', status->new.name);
4451                 break;
4453         case REQ_VIEW_BLAME:
4454                 if (status) {
4455                         string_copy(opt_file, status->new.name);
4456                         opt_ref[0] = 0;
4457                 }
4458                 return request;
4460         case REQ_ENTER:
4461                 /* After returning the status view has been split to
4462                  * show the stage view. No further reloading is
4463                  * necessary. */
4464                 status_enter(view, line);
4465                 return REQ_NONE;
4467         case REQ_REFRESH:
4468                 /* Simply reload the view. */
4469                 break;
4471         default:
4472                 return request;
4473         }
4475         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
4477         return REQ_NONE;
4480 static void
4481 status_select(struct view *view, struct line *line)
4483         struct status *status = line->data;
4484         char file[SIZEOF_STR] = "all files";
4485         const char *text;
4486         const char *key;
4488         if (status && !string_format(file, "'%s'", status->new.name))
4489                 return;
4491         if (!status && line[1].type == LINE_STAT_NONE)
4492                 line++;
4494         switch (line->type) {
4495         case LINE_STAT_STAGED:
4496                 text = "Press %s to unstage %s for commit";
4497                 break;
4499         case LINE_STAT_UNSTAGED:
4500                 text = "Press %s to stage %s for commit";
4501                 break;
4503         case LINE_STAT_UNTRACKED:
4504                 text = "Press %s to stage %s for addition";
4505                 break;
4507         case LINE_STAT_HEAD:
4508         case LINE_STAT_NONE:
4509                 text = "Nothing to update";
4510                 break;
4512         default:
4513                 die("line type %d not handled in switch", line->type);
4514         }
4516         if (status && status->status == 'U') {
4517                 text = "Press %s to resolve conflict in %s";
4518                 key = get_key(REQ_STATUS_MERGE);
4520         } else {
4521                 key = get_key(REQ_STATUS_UPDATE);
4522         }
4524         string_format(view->ref, text, key, file);
4527 static bool
4528 status_grep(struct view *view, struct line *line)
4530         struct status *status = line->data;
4531         enum { S_STATUS, S_NAME, S_END } state;
4532         char buf[2] = "?";
4533         regmatch_t pmatch;
4535         if (!status)
4536                 return FALSE;
4538         for (state = S_STATUS; state < S_END; state++) {
4539                 const char *text;
4541                 switch (state) {
4542                 case S_NAME:    text = status->new.name;        break;
4543                 case S_STATUS:
4544                         buf[0] = status->status;
4545                         text = buf;
4546                         break;
4548                 default:
4549                         return FALSE;
4550                 }
4552                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4553                         return TRUE;
4554         }
4556         return FALSE;
4559 static struct view_ops status_ops = {
4560         "file",
4561         status_open,
4562         NULL,
4563         status_draw,
4564         status_request,
4565         status_grep,
4566         status_select,
4567 };
4570 static bool
4571 stage_diff_line(FILE *pipe, struct line *line)
4573         const char *buf = line->data;
4574         size_t bufsize = strlen(buf);
4575         size_t written = 0;
4577         while (!ferror(pipe) && written < bufsize) {
4578                 written += fwrite(buf + written, 1, bufsize - written, pipe);
4579         }
4581         fputc('\n', pipe);
4583         return written == bufsize;
4586 static bool
4587 stage_diff_write(FILE *pipe, struct line *line, struct line *end)
4589         while (line < end) {
4590                 if (!stage_diff_line(pipe, line++))
4591                         return FALSE;
4592                 if (line->type == LINE_DIFF_CHUNK ||
4593                     line->type == LINE_DIFF_HEADER)
4594                         break;
4595         }
4597         return TRUE;
4600 static struct line *
4601 stage_diff_find(struct view *view, struct line *line, enum line_type type)
4603         for (; view->line < line; line--)
4604                 if (line->type == type)
4605                         return line;
4607         return NULL;
4610 static bool
4611 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
4613         char cmd[SIZEOF_STR];
4614         size_t cmdsize = 0;
4615         struct line *diff_hdr;
4616         FILE *pipe;
4618         diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
4619         if (!diff_hdr)
4620                 return FALSE;
4622         if (opt_cdup[0] &&
4623             !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
4624                 return FALSE;
4626         if (!string_format_from(cmd, &cmdsize,
4627                                 "git apply --whitespace=nowarn %s %s - && "
4628                                 "git update-index -q --unmerged --refresh 2>/dev/null",
4629                                 revert ? "" : "--cached",
4630                                 revert || stage_line_type == LINE_STAT_STAGED ? "-R" : ""))
4631                 return FALSE;
4633         pipe = popen(cmd, "w");
4634         if (!pipe)
4635                 return FALSE;
4637         if (!stage_diff_write(pipe, diff_hdr, chunk) ||
4638             !stage_diff_write(pipe, chunk, view->line + view->lines))
4639                 chunk = NULL;
4641         pclose(pipe);
4643         return chunk ? TRUE : FALSE;
4646 static bool
4647 stage_update(struct view *view, struct line *line)
4649         struct line *chunk = NULL;
4651         if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
4652                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
4654         if (chunk) {
4655                 if (!stage_apply_chunk(view, chunk, FALSE)) {
4656                         report("Failed to apply chunk");
4657                         return FALSE;
4658                 }
4660         } else if (!stage_status.status) {
4661                 view = VIEW(REQ_VIEW_STATUS);
4663                 for (line = view->line; line < view->line + view->lines; line++)
4664                         if (line->type == stage_line_type)
4665                                 break;
4667                 if (!status_update_files(view, line + 1)) {
4668                         report("Failed to update files");
4669                         return FALSE;
4670                 }
4672         } else if (!status_update_file(&stage_status, stage_line_type)) {
4673                 report("Failed to update file");
4674                 return FALSE;
4675         }
4677         return TRUE;
4680 static bool
4681 stage_revert(struct view *view, struct line *line)
4683         struct line *chunk = NULL;
4685         if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
4686                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
4688         if (chunk) {
4689                 if (!prompt_yesno("Are you sure you want to revert changes?"))
4690                         return FALSE;
4692                 if (!stage_apply_chunk(view, chunk, TRUE)) {
4693                         report("Failed to revert chunk");
4694                         return FALSE;
4695                 }
4696                 return TRUE;
4698         } else {
4699                 return status_revert(stage_status.status ? &stage_status : NULL,
4700                                      stage_line_type, FALSE);
4701         }
4705 static void
4706 stage_next(struct view *view, struct line *line)
4708         int i;
4710         if (!stage_chunks) {
4711                 static size_t alloc = 0;
4712                 int *tmp;
4714                 for (line = view->line; line < view->line + view->lines; line++) {
4715                         if (line->type != LINE_DIFF_CHUNK)
4716                                 continue;
4718                         tmp = realloc_items(stage_chunk, &alloc,
4719                                             stage_chunks, sizeof(*tmp));
4720                         if (!tmp) {
4721                                 report("Allocation failure");
4722                                 return;
4723                         }
4725                         stage_chunk = tmp;
4726                         stage_chunk[stage_chunks++] = line - view->line;
4727                 }
4728         }
4730         for (i = 0; i < stage_chunks; i++) {
4731                 if (stage_chunk[i] > view->lineno) {
4732                         do_scroll_view(view, stage_chunk[i] - view->lineno);
4733                         report("Chunk %d of %d", i + 1, stage_chunks);
4734                         return;
4735                 }
4736         }
4738         report("No next chunk found");
4741 static enum request
4742 stage_request(struct view *view, enum request request, struct line *line)
4744         switch (request) {
4745         case REQ_STATUS_UPDATE:
4746                 if (!stage_update(view, line))
4747                         return REQ_NONE;
4748                 break;
4750         case REQ_STATUS_REVERT:
4751                 if (!stage_revert(view, line))
4752                         return REQ_NONE;
4753                 break;
4755         case REQ_STAGE_NEXT:
4756                 if (stage_line_type == LINE_STAT_UNTRACKED) {
4757                         report("File is untracked; press %s to add",
4758                                get_key(REQ_STATUS_UPDATE));
4759                         return REQ_NONE;
4760                 }
4761                 stage_next(view, line);
4762                 return REQ_NONE;
4764         case REQ_EDIT:
4765                 if (!stage_status.new.name[0])
4766                         return request;
4767                 if (stage_status.status == 'D') {
4768                         report("File has been deleted.");
4769                         return REQ_NONE;
4770                 }
4772                 open_editor(stage_status.status != '?', stage_status.new.name);
4773                 break;
4775         case REQ_REFRESH:
4776                 /* Reload everything ... */
4777                 break;
4779         case REQ_VIEW_BLAME:
4780                 if (stage_status.new.name[0]) {
4781                         string_copy(opt_file, stage_status.new.name);
4782                         opt_ref[0] = 0;
4783                 }
4784                 return request;
4786         case REQ_ENTER:
4787                 return pager_request(view, request, line);
4789         default:
4790                 return request;
4791         }
4793         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD | OPEN_NOMAXIMIZE);
4795         /* Check whether the staged entry still exists, and close the
4796          * stage view if it doesn't. */
4797         if (!status_exists(&stage_status, stage_line_type))
4798                 return REQ_VIEW_CLOSE;
4800         if (stage_line_type == LINE_STAT_UNTRACKED) {
4801                 if (!suffixcmp(stage_status.new.name, -1, "/")) {
4802                         report("Cannot display a directory");
4803                         return REQ_NONE;
4804                 }
4806                 opt_pipe = fopen(stage_status.new.name, "r");
4807         }
4808         open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
4810         return REQ_NONE;
4813 static struct view_ops stage_ops = {
4814         "line",
4815         NULL,
4816         pager_read,
4817         pager_draw,
4818         stage_request,
4819         pager_grep,
4820         pager_select,
4821 };
4824 /*
4825  * Revision graph
4826  */
4828 struct commit {
4829         char id[SIZEOF_REV];            /* SHA1 ID. */
4830         char title[128];                /* First line of the commit message. */
4831         char author[75];                /* Author of the commit. */
4832         struct tm time;                 /* Date from the author ident. */
4833         struct ref **refs;              /* Repository references. */
4834         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
4835         size_t graph_size;              /* The width of the graph array. */
4836         bool has_parents;               /* Rewritten --parents seen. */
4837 };
4839 /* Size of rev graph with no  "padding" columns */
4840 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
4842 struct rev_graph {
4843         struct rev_graph *prev, *next, *parents;
4844         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
4845         size_t size;
4846         struct commit *commit;
4847         size_t pos;
4848         unsigned int boundary:1;
4849 };
4851 /* Parents of the commit being visualized. */
4852 static struct rev_graph graph_parents[4];
4854 /* The current stack of revisions on the graph. */
4855 static struct rev_graph graph_stacks[4] = {
4856         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
4857         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
4858         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
4859         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
4860 };
4862 static inline bool
4863 graph_parent_is_merge(struct rev_graph *graph)
4865         return graph->parents->size > 1;
4868 static inline void
4869 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
4871         struct commit *commit = graph->commit;
4873         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
4874                 commit->graph[commit->graph_size++] = symbol;
4877 static void
4878 clear_rev_graph(struct rev_graph *graph)
4880         graph->boundary = 0;
4881         graph->size = graph->pos = 0;
4882         graph->commit = NULL;
4883         memset(graph->parents, 0, sizeof(*graph->parents));
4886 static void
4887 done_rev_graph(struct rev_graph *graph)
4889         if (graph_parent_is_merge(graph) &&
4890             graph->pos < graph->size - 1 &&
4891             graph->next->size == graph->size + graph->parents->size - 1) {
4892                 size_t i = graph->pos + graph->parents->size - 1;
4894                 graph->commit->graph_size = i * 2;
4895                 while (i < graph->next->size - 1) {
4896                         append_to_rev_graph(graph, ' ');
4897                         append_to_rev_graph(graph, '\\');
4898                         i++;
4899                 }
4900         }
4902         clear_rev_graph(graph);
4905 static void
4906 push_rev_graph(struct rev_graph *graph, const char *parent)
4908         int i;
4910         /* "Collapse" duplicate parents lines.
4911          *
4912          * FIXME: This needs to also update update the drawn graph but
4913          * for now it just serves as a method for pruning graph lines. */
4914         for (i = 0; i < graph->size; i++)
4915                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
4916                         return;
4918         if (graph->size < SIZEOF_REVITEMS) {
4919                 string_copy_rev(graph->rev[graph->size++], parent);
4920         }
4923 static chtype
4924 get_rev_graph_symbol(struct rev_graph *graph)
4926         chtype symbol;
4928         if (graph->boundary)
4929                 symbol = REVGRAPH_BOUND;
4930         else if (graph->parents->size == 0)
4931                 symbol = REVGRAPH_INIT;
4932         else if (graph_parent_is_merge(graph))
4933                 symbol = REVGRAPH_MERGE;
4934         else if (graph->pos >= graph->size)
4935                 symbol = REVGRAPH_BRANCH;
4936         else
4937                 symbol = REVGRAPH_COMMIT;
4939         return symbol;
4942 static void
4943 draw_rev_graph(struct rev_graph *graph)
4945         struct rev_filler {
4946                 chtype separator, line;
4947         };
4948         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
4949         static struct rev_filler fillers[] = {
4950                 { ' ',  '|' },
4951                 { '`',  '.' },
4952                 { '\'', ' ' },
4953                 { '/',  ' ' },
4954         };
4955         chtype symbol = get_rev_graph_symbol(graph);
4956         struct rev_filler *filler;
4957         size_t i;
4959         if (opt_line_graphics)
4960                 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
4962         filler = &fillers[DEFAULT];
4964         for (i = 0; i < graph->pos; i++) {
4965                 append_to_rev_graph(graph, filler->line);
4966                 if (graph_parent_is_merge(graph->prev) &&
4967                     graph->prev->pos == i)
4968                         filler = &fillers[RSHARP];
4970                 append_to_rev_graph(graph, filler->separator);
4971         }
4973         /* Place the symbol for this revision. */
4974         append_to_rev_graph(graph, symbol);
4976         if (graph->prev->size > graph->size)
4977                 filler = &fillers[RDIAG];
4978         else
4979                 filler = &fillers[DEFAULT];
4981         i++;
4983         for (; i < graph->size; i++) {
4984                 append_to_rev_graph(graph, filler->separator);
4985                 append_to_rev_graph(graph, filler->line);
4986                 if (graph_parent_is_merge(graph->prev) &&
4987                     i < graph->prev->pos + graph->parents->size)
4988                         filler = &fillers[RSHARP];
4989                 if (graph->prev->size > graph->size)
4990                         filler = &fillers[LDIAG];
4991         }
4993         if (graph->prev->size > graph->size) {
4994                 append_to_rev_graph(graph, filler->separator);
4995                 if (filler->line != ' ')
4996                         append_to_rev_graph(graph, filler->line);
4997         }
5000 /* Prepare the next rev graph */
5001 static void
5002 prepare_rev_graph(struct rev_graph *graph)
5004         size_t i;
5006         /* First, traverse all lines of revisions up to the active one. */
5007         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
5008                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
5009                         break;
5011                 push_rev_graph(graph->next, graph->rev[graph->pos]);
5012         }
5014         /* Interleave the new revision parent(s). */
5015         for (i = 0; !graph->boundary && i < graph->parents->size; i++)
5016                 push_rev_graph(graph->next, graph->parents->rev[i]);
5018         /* Lastly, put any remaining revisions. */
5019         for (i = graph->pos + 1; i < graph->size; i++)
5020                 push_rev_graph(graph->next, graph->rev[i]);
5023 static void
5024 update_rev_graph(struct rev_graph *graph)
5026         /* If this is the finalizing update ... */
5027         if (graph->commit)
5028                 prepare_rev_graph(graph);
5030         /* Graph visualization needs a one rev look-ahead,
5031          * so the first update doesn't visualize anything. */
5032         if (!graph->prev->commit)
5033                 return;
5035         draw_rev_graph(graph->prev);
5036         done_rev_graph(graph->prev->prev);
5040 /*
5041  * Main view backend
5042  */
5044 static bool
5045 main_draw(struct view *view, struct line *line, unsigned int lineno)
5047         struct commit *commit = line->data;
5049         if (!*commit->author)
5050                 return FALSE;
5052         if (opt_date && draw_date(view, &commit->time))
5053                 return TRUE;
5055         if (opt_author &&
5056             draw_field(view, LINE_MAIN_AUTHOR, commit->author, opt_author_cols, TRUE))
5057                 return TRUE;
5059         if (opt_rev_graph && commit->graph_size &&
5060             draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
5061                 return TRUE;
5063         if (opt_show_refs && commit->refs) {
5064                 size_t i = 0;
5066                 do {
5067                         enum line_type type;
5069                         if (commit->refs[i]->head)
5070                                 type = LINE_MAIN_HEAD;
5071                         else if (commit->refs[i]->ltag)
5072                                 type = LINE_MAIN_LOCAL_TAG;
5073                         else if (commit->refs[i]->tag)
5074                                 type = LINE_MAIN_TAG;
5075                         else if (commit->refs[i]->tracked)
5076                                 type = LINE_MAIN_TRACKED;
5077                         else if (commit->refs[i]->remote)
5078                                 type = LINE_MAIN_REMOTE;
5079                         else
5080                                 type = LINE_MAIN_REF;
5082                         if (draw_text(view, type, "[", TRUE) ||
5083                             draw_text(view, type, commit->refs[i]->name, TRUE) ||
5084                             draw_text(view, type, "]", TRUE))
5085                                 return TRUE;
5087                         if (draw_text(view, LINE_DEFAULT, " ", TRUE))
5088                                 return TRUE;
5089                 } while (commit->refs[i++]->next);
5090         }
5092         draw_text(view, LINE_DEFAULT, commit->title, TRUE);
5093         return TRUE;
5096 /* Reads git log --pretty=raw output and parses it into the commit struct. */
5097 static bool
5098 main_read(struct view *view, char *line)
5100         static struct rev_graph *graph = graph_stacks;
5101         enum line_type type;
5102         struct commit *commit;
5104         if (!line) {
5105                 int i;
5107                 if (!view->lines && !view->parent)
5108                         die("No revisions match the given arguments.");
5109                 if (view->lines > 0) {
5110                         commit = view->line[view->lines - 1].data;
5111                         if (!*commit->author) {
5112                                 view->lines--;
5113                                 free(commit);
5114                                 graph->commit = NULL;
5115                         }
5116                 }
5117                 update_rev_graph(graph);
5119                 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
5120                         clear_rev_graph(&graph_stacks[i]);
5121                 return TRUE;
5122         }
5124         type = get_line_type(line);
5125         if (type == LINE_COMMIT) {
5126                 commit = calloc(1, sizeof(struct commit));
5127                 if (!commit)
5128                         return FALSE;
5130                 line += STRING_SIZE("commit ");
5131                 if (*line == '-') {
5132                         graph->boundary = 1;
5133                         line++;
5134                 }
5136                 string_copy_rev(commit->id, line);
5137                 commit->refs = get_refs(commit->id);
5138                 graph->commit = commit;
5139                 add_line_data(view, commit, LINE_MAIN_COMMIT);
5141                 while ((line = strchr(line, ' '))) {
5142                         line++;
5143                         push_rev_graph(graph->parents, line);
5144                         commit->has_parents = TRUE;
5145                 }
5146                 return TRUE;
5147         }
5149         if (!view->lines)
5150                 return TRUE;
5151         commit = view->line[view->lines - 1].data;
5153         switch (type) {
5154         case LINE_PARENT:
5155                 if (commit->has_parents)
5156                         break;
5157                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
5158                 break;
5160         case LINE_AUTHOR:
5161         {
5162                 /* Parse author lines where the name may be empty:
5163                  *      author  <email@address.tld> 1138474660 +0100
5164                  */
5165                 char *ident = line + STRING_SIZE("author ");
5166                 char *nameend = strchr(ident, '<');
5167                 char *emailend = strchr(ident, '>');
5169                 if (!nameend || !emailend)
5170                         break;
5172                 update_rev_graph(graph);
5173                 graph = graph->next;
5175                 *nameend = *emailend = 0;
5176                 ident = chomp_string(ident);
5177                 if (!*ident) {
5178                         ident = chomp_string(nameend + 1);
5179                         if (!*ident)
5180                                 ident = "Unknown";
5181                 }
5183                 string_ncopy(commit->author, ident, strlen(ident));
5185                 /* Parse epoch and timezone */
5186                 if (emailend[1] == ' ') {
5187                         char *secs = emailend + 2;
5188                         char *zone = strchr(secs, ' ');
5189                         time_t time = (time_t) atol(secs);
5191                         if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
5192                                 long tz;
5194                                 zone++;
5195                                 tz  = ('0' - zone[1]) * 60 * 60 * 10;
5196                                 tz += ('0' - zone[2]) * 60 * 60;
5197                                 tz += ('0' - zone[3]) * 60;
5198                                 tz += ('0' - zone[4]) * 60;
5200                                 if (zone[0] == '-')
5201                                         tz = -tz;
5203                                 time -= tz;
5204                         }
5206                         gmtime_r(&time, &commit->time);
5207                 }
5208                 break;
5209         }
5210         default:
5211                 /* Fill in the commit title if it has not already been set. */
5212                 if (commit->title[0])
5213                         break;
5215                 /* Require titles to start with a non-space character at the
5216                  * offset used by git log. */
5217                 if (strncmp(line, "    ", 4))
5218                         break;
5219                 line += 4;
5220                 /* Well, if the title starts with a whitespace character,
5221                  * try to be forgiving.  Otherwise we end up with no title. */
5222                 while (isspace(*line))
5223                         line++;
5224                 if (*line == '\0')
5225                         break;
5226                 /* FIXME: More graceful handling of titles; append "..." to
5227                  * shortened titles, etc. */
5229                 string_ncopy(commit->title, line, strlen(line));
5230         }
5232         return TRUE;
5235 static enum request
5236 main_request(struct view *view, enum request request, struct line *line)
5238         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
5240         switch (request) {
5241         case REQ_ENTER:
5242                 open_view(view, REQ_VIEW_DIFF, flags);
5243                 break;
5244         case REQ_REFRESH:
5245                 load_refs();
5246                 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
5247                 break;
5248         default:
5249                 return request;
5250         }
5252         return REQ_NONE;
5255 static bool
5256 grep_refs(struct ref **refs, regex_t *regex)
5258         regmatch_t pmatch;
5259         size_t i = 0;
5261         if (!refs)
5262                 return FALSE;
5263         do {
5264                 if (regexec(regex, refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
5265                         return TRUE;
5266         } while (refs[i++]->next);
5268         return FALSE;
5271 static bool
5272 main_grep(struct view *view, struct line *line)
5274         struct commit *commit = line->data;
5275         enum { S_TITLE, S_AUTHOR, S_DATE, S_REFS, S_END } state;
5276         char buf[DATE_COLS + 1];
5277         regmatch_t pmatch;
5279         for (state = S_TITLE; state < S_END; state++) {
5280                 char *text;
5282                 switch (state) {
5283                 case S_TITLE:   text = commit->title;   break;
5284                 case S_AUTHOR:
5285                         if (!opt_author)
5286                                 continue;
5287                         text = commit->author;
5288                         break;
5289                 case S_DATE:
5290                         if (!opt_date)
5291                                 continue;
5292                         if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
5293                                 continue;
5294                         text = buf;
5295                         break;
5296                 case S_REFS:
5297                         if (!opt_show_refs)
5298                                 continue;
5299                         if (grep_refs(commit->refs, view->regex) == TRUE)
5300                                 return TRUE;
5301                         continue;
5302                 default:
5303                         return FALSE;
5304                 }
5306                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
5307                         return TRUE;
5308         }
5310         return FALSE;
5313 static void
5314 main_select(struct view *view, struct line *line)
5316         struct commit *commit = line->data;
5318         string_copy_rev(view->ref, commit->id);
5319         string_copy_rev(ref_commit, view->ref);
5322 static struct view_ops main_ops = {
5323         "commit",
5324         NULL,
5325         main_read,
5326         main_draw,
5327         main_request,
5328         main_grep,
5329         main_select,
5330 };
5333 /*
5334  * Unicode / UTF-8 handling
5335  *
5336  * NOTE: Much of the following code for dealing with unicode is derived from
5337  * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
5338  * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
5339  */
5341 /* I've (over)annotated a lot of code snippets because I am not entirely
5342  * confident that the approach taken by this small UTF-8 interface is correct.
5343  * --jonas */
5345 static inline int
5346 unicode_width(unsigned long c)
5348         if (c >= 0x1100 &&
5349            (c <= 0x115f                         /* Hangul Jamo */
5350             || c == 0x2329
5351             || c == 0x232a
5352             || (c >= 0x2e80  && c <= 0xa4cf && c != 0x303f)
5353                                                 /* CJK ... Yi */
5354             || (c >= 0xac00  && c <= 0xd7a3)    /* Hangul Syllables */
5355             || (c >= 0xf900  && c <= 0xfaff)    /* CJK Compatibility Ideographs */
5356             || (c >= 0xfe30  && c <= 0xfe6f)    /* CJK Compatibility Forms */
5357             || (c >= 0xff00  && c <= 0xff60)    /* Fullwidth Forms */
5358             || (c >= 0xffe0  && c <= 0xffe6)
5359             || (c >= 0x20000 && c <= 0x2fffd)
5360             || (c >= 0x30000 && c <= 0x3fffd)))
5361                 return 2;
5363         if (c == '\t')
5364                 return opt_tab_size;
5366         return 1;
5369 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
5370  * Illegal bytes are set one. */
5371 static const unsigned char utf8_bytes[256] = {
5372         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,
5373         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,
5374         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,
5375         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,
5376         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,
5377         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,
5378         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,
5379         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,
5380 };
5382 /* Decode UTF-8 multi-byte representation into a unicode character. */
5383 static inline unsigned long
5384 utf8_to_unicode(const char *string, size_t length)
5386         unsigned long unicode;
5388         switch (length) {
5389         case 1:
5390                 unicode  =   string[0];
5391                 break;
5392         case 2:
5393                 unicode  =  (string[0] & 0x1f) << 6;
5394                 unicode +=  (string[1] & 0x3f);
5395                 break;
5396         case 3:
5397                 unicode  =  (string[0] & 0x0f) << 12;
5398                 unicode += ((string[1] & 0x3f) << 6);
5399                 unicode +=  (string[2] & 0x3f);
5400                 break;
5401         case 4:
5402                 unicode  =  (string[0] & 0x0f) << 18;
5403                 unicode += ((string[1] & 0x3f) << 12);
5404                 unicode += ((string[2] & 0x3f) << 6);
5405                 unicode +=  (string[3] & 0x3f);
5406                 break;
5407         case 5:
5408                 unicode  =  (string[0] & 0x0f) << 24;
5409                 unicode += ((string[1] & 0x3f) << 18);
5410                 unicode += ((string[2] & 0x3f) << 12);
5411                 unicode += ((string[3] & 0x3f) << 6);
5412                 unicode +=  (string[4] & 0x3f);
5413                 break;
5414         case 6:
5415                 unicode  =  (string[0] & 0x01) << 30;
5416                 unicode += ((string[1] & 0x3f) << 24);
5417                 unicode += ((string[2] & 0x3f) << 18);
5418                 unicode += ((string[3] & 0x3f) << 12);
5419                 unicode += ((string[4] & 0x3f) << 6);
5420                 unicode +=  (string[5] & 0x3f);
5421                 break;
5422         default:
5423                 die("Invalid unicode length");
5424         }
5426         /* Invalid characters could return the special 0xfffd value but NUL
5427          * should be just as good. */
5428         return unicode > 0xffff ? 0 : unicode;
5431 /* Calculates how much of string can be shown within the given maximum width
5432  * and sets trimmed parameter to non-zero value if all of string could not be
5433  * shown. If the reserve flag is TRUE, it will reserve at least one
5434  * trailing character, which can be useful when drawing a delimiter.
5435  *
5436  * Returns the number of bytes to output from string to satisfy max_width. */
5437 static size_t
5438 utf8_length(const char *string, int *width, size_t max_width, int *trimmed, bool reserve)
5440         const char *start = string;
5441         const char *end = strchr(string, '\0');
5442         unsigned char last_bytes = 0;
5443         size_t last_ucwidth = 0;
5445         *width = 0;
5446         *trimmed = 0;
5448         while (string < end) {
5449                 int c = *(unsigned char *) string;
5450                 unsigned char bytes = utf8_bytes[c];
5451                 size_t ucwidth;
5452                 unsigned long unicode;
5454                 if (string + bytes > end)
5455                         break;
5457                 /* Change representation to figure out whether
5458                  * it is a single- or double-width character. */
5460                 unicode = utf8_to_unicode(string, bytes);
5461                 /* FIXME: Graceful handling of invalid unicode character. */
5462                 if (!unicode)
5463                         break;
5465                 ucwidth = unicode_width(unicode);
5466                 *width  += ucwidth;
5467                 if (*width > max_width) {
5468                         *trimmed = 1;
5469                         *width -= ucwidth;
5470                         if (reserve && *width == max_width) {
5471                                 string -= last_bytes;
5472                                 *width -= last_ucwidth;
5473                         }
5474                         break;
5475                 }
5477                 string  += bytes;
5478                 last_bytes = bytes;
5479                 last_ucwidth = ucwidth;
5480         }
5482         return string - start;
5486 /*
5487  * Status management
5488  */
5490 /* Whether or not the curses interface has been initialized. */
5491 static bool cursed = FALSE;
5493 /* The status window is used for polling keystrokes. */
5494 static WINDOW *status_win;
5496 static bool status_empty = TRUE;
5498 /* Update status and title window. */
5499 static void
5500 report(const char *msg, ...)
5502         struct view *view = display[current_view];
5504         if (input_mode)
5505                 return;
5507         if (!view) {
5508                 char buf[SIZEOF_STR];
5509                 va_list args;
5511                 va_start(args, msg);
5512                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
5513                         buf[sizeof(buf) - 1] = 0;
5514                         buf[sizeof(buf) - 2] = '.';
5515                         buf[sizeof(buf) - 3] = '.';
5516                         buf[sizeof(buf) - 4] = '.';
5517                 }
5518                 va_end(args);
5519                 die("%s", buf);
5520         }
5522         if (!status_empty || *msg) {
5523                 va_list args;
5525                 va_start(args, msg);
5527                 wmove(status_win, 0, 0);
5528                 if (*msg) {
5529                         vwprintw(status_win, msg, args);
5530                         status_empty = FALSE;
5531                 } else {
5532                         status_empty = TRUE;
5533                 }
5534                 wclrtoeol(status_win);
5535                 wrefresh(status_win);
5537                 va_end(args);
5538         }
5540         update_view_title(view);
5541         update_display_cursor(view);
5544 /* Controls when nodelay should be in effect when polling user input. */
5545 static void
5546 set_nonblocking_input(bool loading)
5548         static unsigned int loading_views;
5550         if ((loading == FALSE && loading_views-- == 1) ||
5551             (loading == TRUE  && loading_views++ == 0))
5552                 nodelay(status_win, loading);
5555 static void
5556 init_display(void)
5558         int x, y;
5560         /* Initialize the curses library */
5561         if (isatty(STDIN_FILENO)) {
5562                 cursed = !!initscr();
5563                 opt_tty = stdin;
5564         } else {
5565                 /* Leave stdin and stdout alone when acting as a pager. */
5566                 opt_tty = fopen("/dev/tty", "r+");
5567                 if (!opt_tty)
5568                         die("Failed to open /dev/tty");
5569                 cursed = !!newterm(NULL, opt_tty, opt_tty);
5570         }
5572         if (!cursed)
5573                 die("Failed to initialize curses");
5575         nonl();         /* Tell curses not to do NL->CR/NL on output */
5576         cbreak();       /* Take input chars one at a time, no wait for \n */
5577         noecho();       /* Don't echo input */
5578         leaveok(stdscr, TRUE);
5580         if (has_colors())
5581                 init_colors();
5583         getmaxyx(stdscr, y, x);
5584         status_win = newwin(1, 0, y - 1, 0);
5585         if (!status_win)
5586                 die("Failed to create status window");
5588         /* Enable keyboard mapping */
5589         keypad(status_win, TRUE);
5590         wbkgdset(status_win, get_line_attr(LINE_STATUS));
5592         TABSIZE = opt_tab_size;
5593         if (opt_line_graphics) {
5594                 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
5595         }
5598 static bool
5599 prompt_yesno(const char *prompt)
5601         enum { WAIT, STOP, CANCEL  } status = WAIT;
5602         bool answer = FALSE;
5604         while (status == WAIT) {
5605                 struct view *view;
5606                 int i, key;
5608                 input_mode = TRUE;
5610                 foreach_view (view, i)
5611                         update_view(view);
5613                 input_mode = FALSE;
5615                 mvwprintw(status_win, 0, 0, "%s [Yy]/[Nn]", prompt);
5616                 wclrtoeol(status_win);
5618                 /* Refresh, accept single keystroke of input */
5619                 key = wgetch(status_win);
5620                 switch (key) {
5621                 case ERR:
5622                         break;
5624                 case 'y':
5625                 case 'Y':
5626                         answer = TRUE;
5627                         status = STOP;
5628                         break;
5630                 case KEY_ESC:
5631                 case KEY_RETURN:
5632                 case KEY_ENTER:
5633                 case KEY_BACKSPACE:
5634                 case 'n':
5635                 case 'N':
5636                 case '\n':
5637                 default:
5638                         answer = FALSE;
5639                         status = CANCEL;
5640                 }
5641         }
5643         /* Clear the status window */
5644         status_empty = FALSE;
5645         report("");
5647         return answer;
5650 static char *
5651 read_prompt(const char *prompt)
5653         enum { READING, STOP, CANCEL } status = READING;
5654         static char buf[sizeof(opt_cmd) - STRING_SIZE("git \0")];
5655         int pos = 0;
5657         while (status == READING) {
5658                 struct view *view;
5659                 int i, key;
5661                 input_mode = TRUE;
5663                 foreach_view (view, i)
5664                         update_view(view);
5666                 input_mode = FALSE;
5668                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
5669                 wclrtoeol(status_win);
5671                 /* Refresh, accept single keystroke of input */
5672                 key = wgetch(status_win);
5673                 switch (key) {
5674                 case KEY_RETURN:
5675                 case KEY_ENTER:
5676                 case '\n':
5677                         status = pos ? STOP : CANCEL;
5678                         break;
5680                 case KEY_BACKSPACE:
5681                         if (pos > 0)
5682                                 pos--;
5683                         else
5684                                 status = CANCEL;
5685                         break;
5687                 case KEY_ESC:
5688                         status = CANCEL;
5689                         break;
5691                 case ERR:
5692                         break;
5694                 default:
5695                         if (pos >= sizeof(buf)) {
5696                                 report("Input string too long");
5697                                 return NULL;
5698                         }
5700                         if (isprint(key))
5701                                 buf[pos++] = (char) key;
5702                 }
5703         }
5705         /* Clear the status window */
5706         status_empty = FALSE;
5707         report("");
5709         if (status == CANCEL)
5710                 return NULL;
5712         buf[pos++] = 0;
5714         return buf;
5717 /*
5718  * Repository references
5719  */
5721 static struct ref *refs = NULL;
5722 static size_t refs_alloc = 0;
5723 static size_t refs_size = 0;
5725 /* Id <-> ref store */
5726 static struct ref ***id_refs = NULL;
5727 static size_t id_refs_alloc = 0;
5728 static size_t id_refs_size = 0;
5730 static int
5731 compare_refs(const void *ref1_, const void *ref2_)
5733         const struct ref *ref1 = *(const struct ref **)ref1_;
5734         const struct ref *ref2 = *(const struct ref **)ref2_;
5736         if (ref1->tag != ref2->tag)
5737                 return ref2->tag - ref1->tag;
5738         if (ref1->ltag != ref2->ltag)
5739                 return ref2->ltag - ref2->ltag;
5740         if (ref1->head != ref2->head)
5741                 return ref2->head - ref1->head;
5742         if (ref1->tracked != ref2->tracked)
5743                 return ref2->tracked - ref1->tracked;
5744         if (ref1->remote != ref2->remote)
5745                 return ref2->remote - ref1->remote;
5746         return strcmp(ref1->name, ref2->name);
5749 static struct ref **
5750 get_refs(const char *id)
5752         struct ref ***tmp_id_refs;
5753         struct ref **ref_list = NULL;
5754         size_t ref_list_alloc = 0;
5755         size_t ref_list_size = 0;
5756         size_t i;
5758         for (i = 0; i < id_refs_size; i++)
5759                 if (!strcmp(id, id_refs[i][0]->id))
5760                         return id_refs[i];
5762         tmp_id_refs = realloc_items(id_refs, &id_refs_alloc, id_refs_size + 1,
5763                                     sizeof(*id_refs));
5764         if (!tmp_id_refs)
5765                 return NULL;
5767         id_refs = tmp_id_refs;
5769         for (i = 0; i < refs_size; i++) {
5770                 struct ref **tmp;
5772                 if (strcmp(id, refs[i].id))
5773                         continue;
5775                 tmp = realloc_items(ref_list, &ref_list_alloc,
5776                                     ref_list_size + 1, sizeof(*ref_list));
5777                 if (!tmp) {
5778                         if (ref_list)
5779                                 free(ref_list);
5780                         return NULL;
5781                 }
5783                 ref_list = tmp;
5784                 ref_list[ref_list_size] = &refs[i];
5785                 /* XXX: The properties of the commit chains ensures that we can
5786                  * safely modify the shared ref. The repo references will
5787                  * always be similar for the same id. */
5788                 ref_list[ref_list_size]->next = 1;
5790                 ref_list_size++;
5791         }
5793         if (ref_list) {
5794                 qsort(ref_list, ref_list_size, sizeof(*ref_list), compare_refs);
5795                 ref_list[ref_list_size - 1]->next = 0;
5796                 id_refs[id_refs_size++] = ref_list;
5797         }
5799         return ref_list;
5802 static int
5803 read_ref(char *id, size_t idlen, char *name, size_t namelen)
5805         struct ref *ref;
5806         bool tag = FALSE;
5807         bool ltag = FALSE;
5808         bool remote = FALSE;
5809         bool tracked = FALSE;
5810         bool check_replace = FALSE;
5811         bool head = FALSE;
5813         if (!prefixcmp(name, "refs/tags/")) {
5814                 if (!suffixcmp(name, namelen, "^{}")) {
5815                         namelen -= 3;
5816                         name[namelen] = 0;
5817                         if (refs_size > 0 && refs[refs_size - 1].ltag == TRUE)
5818                                 check_replace = TRUE;
5819                 } else {
5820                         ltag = TRUE;
5821                 }
5823                 tag = TRUE;
5824                 namelen -= STRING_SIZE("refs/tags/");
5825                 name    += STRING_SIZE("refs/tags/");
5827         } else if (!prefixcmp(name, "refs/remotes/")) {
5828                 remote = TRUE;
5829                 namelen -= STRING_SIZE("refs/remotes/");
5830                 name    += STRING_SIZE("refs/remotes/");
5831                 tracked  = !strcmp(opt_remote, name);
5833         } else if (!prefixcmp(name, "refs/heads/")) {
5834                 namelen -= STRING_SIZE("refs/heads/");
5835                 name    += STRING_SIZE("refs/heads/");
5836                 head     = !strncmp(opt_head, name, namelen);
5838         } else if (!strcmp(name, "HEAD")) {
5839                 string_ncopy(opt_head_rev, id, idlen);
5840                 return OK;
5841         }
5843         if (check_replace && !strcmp(name, refs[refs_size - 1].name)) {
5844                 /* it's an annotated tag, replace the previous sha1 with the
5845                  * resolved commit id; relies on the fact git-ls-remote lists
5846                  * the commit id of an annotated tag right before the commit id
5847                  * it points to. */
5848                 refs[refs_size - 1].ltag = ltag;
5849                 string_copy_rev(refs[refs_size - 1].id, id);
5851                 return OK;
5852         }
5853         refs = realloc_items(refs, &refs_alloc, refs_size + 1, sizeof(*refs));
5854         if (!refs)
5855                 return ERR;
5857         ref = &refs[refs_size++];
5858         ref->name = malloc(namelen + 1);
5859         if (!ref->name)
5860                 return ERR;
5862         strncpy(ref->name, name, namelen);
5863         ref->name[namelen] = 0;
5864         ref->head = head;
5865         ref->tag = tag;
5866         ref->ltag = ltag;
5867         ref->remote = remote;
5868         ref->tracked = tracked;
5869         string_copy_rev(ref->id, id);
5871         return OK;
5874 static int
5875 load_refs(void)
5877         const char *cmd_env = getenv("TIG_LS_REMOTE");
5878         const char *cmd = cmd_env && *cmd_env ? cmd_env : TIG_LS_REMOTE;
5880         if (!*opt_git_dir)
5881                 return OK;
5883         while (refs_size > 0)
5884                 free(refs[--refs_size].name);
5885         while (id_refs_size > 0)
5886                 free(id_refs[--id_refs_size]);
5888         return read_properties(popen(cmd, "r"), "\t", read_ref);
5891 static int
5892 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
5894         if (!strcmp(name, "i18n.commitencoding"))
5895                 string_ncopy(opt_encoding, value, valuelen);
5897         if (!strcmp(name, "core.editor"))
5898                 string_ncopy(opt_editor, value, valuelen);
5900         /* branch.<head>.remote */
5901         if (*opt_head &&
5902             !strncmp(name, "branch.", 7) &&
5903             !strncmp(name + 7, opt_head, strlen(opt_head)) &&
5904             !strcmp(name + 7 + strlen(opt_head), ".remote"))
5905                 string_ncopy(opt_remote, value, valuelen);
5907         if (*opt_head && *opt_remote &&
5908             !strncmp(name, "branch.", 7) &&
5909             !strncmp(name + 7, opt_head, strlen(opt_head)) &&
5910             !strcmp(name + 7 + strlen(opt_head), ".merge")) {
5911                 size_t from = strlen(opt_remote);
5913                 if (!prefixcmp(value, "refs/heads/")) {
5914                         value += STRING_SIZE("refs/heads/");
5915                         valuelen -= STRING_SIZE("refs/heads/");
5916                 }
5918                 if (!string_format_from(opt_remote, &from, "/%s", value))
5919                         opt_remote[0] = 0;
5920         }
5922         return OK;
5925 static int
5926 load_git_config(void)
5928         return read_properties(popen("git " GIT_CONFIG " --list", "r"),
5929                                "=", read_repo_config_option);
5932 static int
5933 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
5935         if (!opt_git_dir[0]) {
5936                 string_ncopy(opt_git_dir, name, namelen);
5938         } else if (opt_is_inside_work_tree == -1) {
5939                 /* This can be 3 different values depending on the
5940                  * version of git being used. If git-rev-parse does not
5941                  * understand --is-inside-work-tree it will simply echo
5942                  * the option else either "true" or "false" is printed.
5943                  * Default to true for the unknown case. */
5944                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
5946         } else if (opt_cdup[0] == ' ') {
5947                 string_ncopy(opt_cdup, name, namelen);
5948         } else {
5949                 if (!prefixcmp(name, "refs/heads/")) {
5950                         namelen -= STRING_SIZE("refs/heads/");
5951                         name    += STRING_SIZE("refs/heads/");
5952                         string_ncopy(opt_head, name, namelen);
5953                 }
5954         }
5956         return OK;
5959 static int
5960 load_repo_info(void)
5962         int result;
5963         FILE *pipe = popen("(git rev-parse --git-dir --is-inside-work-tree "
5964                            " --show-cdup; git symbolic-ref HEAD) 2>/dev/null", "r");
5966         /* XXX: The line outputted by "--show-cdup" can be empty so
5967          * initialize it to something invalid to make it possible to
5968          * detect whether it has been set or not. */
5969         opt_cdup[0] = ' ';
5971         result = read_properties(pipe, "=", read_repo_info);
5972         if (opt_cdup[0] == ' ')
5973                 opt_cdup[0] = 0;
5975         return result;
5978 static int
5979 read_properties(FILE *pipe, const char *separators,
5980                 int (*read_property)(char *, size_t, char *, size_t))
5982         char buffer[BUFSIZ];
5983         char *name;
5984         int state = OK;
5986         if (!pipe)
5987                 return ERR;
5989         while (state == OK && (name = fgets(buffer, sizeof(buffer), pipe))) {
5990                 char *value;
5991                 size_t namelen;
5992                 size_t valuelen;
5994                 name = chomp_string(name);
5995                 namelen = strcspn(name, separators);
5997                 if (name[namelen]) {
5998                         name[namelen] = 0;
5999                         value = chomp_string(name + namelen + 1);
6000                         valuelen = strlen(value);
6002                 } else {
6003                         value = "";
6004                         valuelen = 0;
6005                 }
6007                 state = read_property(name, namelen, value, valuelen);
6008         }
6010         if (state != ERR && ferror(pipe))
6011                 state = ERR;
6013         pclose(pipe);
6015         return state;
6019 /*
6020  * Main
6021  */
6023 static void __NORETURN
6024 quit(int sig)
6026         /* XXX: Restore tty modes and let the OS cleanup the rest! */
6027         if (cursed)
6028                 endwin();
6029         exit(0);
6032 static void __NORETURN
6033 die(const char *err, ...)
6035         va_list args;
6037         endwin();
6039         va_start(args, err);
6040         fputs("tig: ", stderr);
6041         vfprintf(stderr, err, args);
6042         fputs("\n", stderr);
6043         va_end(args);
6045         exit(1);
6048 static void
6049 warn(const char *msg, ...)
6051         va_list args;
6053         va_start(args, msg);
6054         fputs("tig warning: ", stderr);
6055         vfprintf(stderr, msg, args);
6056         fputs("\n", stderr);
6057         va_end(args);
6060 int
6061 main(int argc, const char *argv[])
6063         struct view *view;
6064         enum request request;
6065         size_t i;
6067         signal(SIGINT, quit);
6069         if (setlocale(LC_ALL, "")) {
6070                 char *codeset = nl_langinfo(CODESET);
6072                 string_ncopy(opt_codeset, codeset, strlen(codeset));
6073         }
6075         if (load_repo_info() == ERR)
6076                 die("Failed to load repo info.");
6078         if (load_options() == ERR)
6079                 die("Failed to load user config.");
6081         if (load_git_config() == ERR)
6082                 die("Failed to load repo config.");
6084         request = parse_options(argc, argv);
6085         if (request == REQ_NONE)
6086                 return 0;
6088         /* Require a git repository unless when running in pager mode. */
6089         if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
6090                 die("Not a git repository");
6092         if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
6093                 opt_utf8 = FALSE;
6095         if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
6096                 opt_iconv = iconv_open(opt_codeset, opt_encoding);
6097                 if (opt_iconv == ICONV_NONE)
6098                         die("Failed to initialize character set conversion");
6099         }
6101         if (load_refs() == ERR)
6102                 die("Failed to load refs.");
6104         foreach_view (view, i)
6105                 view->cmd_env = getenv(view->cmd_env);
6107         init_display();
6109         while (view_driver(display[current_view], request)) {
6110                 int key;
6111                 int i;
6113                 foreach_view (view, i)
6114                         update_view(view);
6115                 view = display[current_view];
6117                 /* Refresh, accept single keystroke of input */
6118                 key = wgetch(status_win);
6120                 /* wgetch() with nodelay() enabled returns ERR when there's no
6121                  * input. */
6122                 if (key == ERR) {
6123                         request = REQ_NONE;
6124                         continue;
6125                 }
6127                 request = get_keybinding(view->keymap, key);
6129                 /* Some low-level request handling. This keeps access to
6130                  * status_win restricted. */
6131                 switch (request) {
6132                 case REQ_PROMPT:
6133                 {
6134                         char *cmd = read_prompt(":");
6136                         if (cmd && string_format(opt_cmd, "git %s", cmd)) {
6137                                 if (strncmp(cmd, "show", 4) && isspace(cmd[4])) {
6138                                         request = REQ_VIEW_DIFF;
6139                                 } else {
6140                                         request = REQ_VIEW_PAGER;
6141                                 }
6143                                 /* Always reload^Wrerun commands from the prompt. */
6144                                 open_view(view, request, OPEN_RELOAD);
6145                         }
6147                         request = REQ_NONE;
6148                         break;
6149                 }
6150                 case REQ_SEARCH:
6151                 case REQ_SEARCH_BACK:
6152                 {
6153                         const char *prompt = request == REQ_SEARCH ? "/" : "?";
6154                         char *search = read_prompt(prompt);
6156                         if (search)
6157                                 string_ncopy(opt_search, search, strlen(search));
6158                         else
6159                                 request = REQ_NONE;
6160                         break;
6161                 }
6162                 case REQ_SCREEN_RESIZE:
6163                 {
6164                         int height, width;
6166                         getmaxyx(stdscr, height, width);
6168                         /* Resize the status view and let the view driver take
6169                          * care of resizing the displayed views. */
6170                         wresize(status_win, 1, width);
6171                         mvwin(status_win, height - 1, 0);
6172                         wrefresh(status_win);
6173                         break;
6174                 }
6175                 default:
6176                         break;
6177                 }
6178         }
6180         quit(0);
6182         return 0;