Code

Added action tree-parent and bound it to backspace by default.
[tig.git] / tig.c
1 /* Copyright (c) 2006-2007 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 #include <curses.h>
47 #if __GNUC__ >= 3
48 #define __NORETURN __attribute__((__noreturn__))
49 #else
50 #define __NORETURN
51 #endif
53 static void __NORETURN die(const char *err, ...);
54 static void report(const char *msg, ...);
55 static int read_properties(FILE *pipe, const char *separators, int (*read)(char *, size_t, char *, size_t));
56 static void set_nonblocking_input(bool loading);
57 static size_t utf8_length(const char *string, size_t max_width, int *coloffset, int *trimmed);
59 #define ABS(x)          ((x) >= 0  ? (x) : -(x))
60 #define MIN(x, y)       ((x) < (y) ? (x) :  (y))
62 #define ARRAY_SIZE(x)   (sizeof(x) / sizeof(x[0]))
63 #define STRING_SIZE(x)  (sizeof(x) - 1)
65 #define SIZEOF_STR      1024    /* Default string size. */
66 #define SIZEOF_REF      256     /* Size of symbolic or SHA1 ID. */
67 #define SIZEOF_REV      41      /* Holds a SHA-1 and an ending NUL */
69 /* Revision graph */
71 #define REVGRAPH_INIT   'I'
72 #define REVGRAPH_MERGE  'M'
73 #define REVGRAPH_BRANCH '+'
74 #define REVGRAPH_COMMIT '*'
75 #define REVGRAPH_LINE   '|'
77 #define SIZEOF_REVGRAPH 19      /* Size of revision ancestry graphics. */
79 /* This color name can be used to refer to the default term colors. */
80 #define COLOR_DEFAULT   (-1)
82 #define ICONV_NONE      ((iconv_t) -1)
83 #ifndef ICONV_CONST
84 #define ICONV_CONST     /* nothing */
85 #endif
87 /* The format and size of the date column in the main view. */
88 #define DATE_FORMAT     "%Y-%m-%d %H:%M"
89 #define DATE_COLS       STRING_SIZE("2006-04-29 14:21 ")
91 #define AUTHOR_COLS     20
93 /* The default interval between line numbers. */
94 #define NUMBER_INTERVAL 1
96 #define TABSIZE         8
98 #define SCALE_SPLIT_VIEW(height)        ((height) * 2 / 3)
100 #ifndef GIT_CONFIG
101 #define GIT_CONFIG "git config"
102 #endif
104 #define TIG_LS_REMOTE \
105         "git ls-remote $(git rev-parse --git-dir) 2>/dev/null"
107 #define TIG_DIFF_CMD \
108         "git show --no-color --root --patch-with-stat --find-copies-harder -B -C %s 2>/dev/null"
110 #define TIG_LOG_CMD     \
111         "git log --no-color --cc --stat -n100 %s 2>/dev/null"
113 #define TIG_MAIN_CMD \
114         "git log --no-color --topo-order --pretty=raw %s 2>/dev/null"
116 #define TIG_TREE_CMD    \
117         "git ls-tree %s %s"
119 #define TIG_BLOB_CMD    \
120         "git cat-file blob %s"
122 /* XXX: Needs to be defined to the empty string. */
123 #define TIG_HELP_CMD    ""
124 #define TIG_PAGER_CMD   ""
125 #define TIG_STATUS_CMD  ""
126 #define TIG_STAGE_CMD   ""
128 /* Some ascii-shorthands fitted into the ncurses namespace. */
129 #define KEY_TAB         '\t'
130 #define KEY_RETURN      '\r'
131 #define KEY_ESC         27
134 struct ref {
135         char *name;             /* Ref name; tag or head names are shortened. */
136         char id[SIZEOF_REV];    /* Commit SHA1 ID */
137         unsigned int tag:1;     /* Is it a tag? */
138         unsigned int remote:1;  /* Is it a remote ref? */
139         unsigned int next:1;    /* For ref lists: are there more refs? */
140 };
142 static struct ref **get_refs(char *id);
144 struct int_map {
145         const char *name;
146         int namelen;
147         int value;
148 };
150 static int
151 set_from_int_map(struct int_map *map, size_t map_size,
152                  int *value, const char *name, int namelen)
155         int i;
157         for (i = 0; i < map_size; i++)
158                 if (namelen == map[i].namelen &&
159                     !strncasecmp(name, map[i].name, namelen)) {
160                         *value = map[i].value;
161                         return OK;
162                 }
164         return ERR;
168 /*
169  * String helpers
170  */
172 static inline void
173 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
175         if (srclen > dstlen - 1)
176                 srclen = dstlen - 1;
178         strncpy(dst, src, srclen);
179         dst[srclen] = 0;
182 /* Shorthands for safely copying into a fixed buffer. */
184 #define string_copy(dst, src) \
185         string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
187 #define string_ncopy(dst, src, srclen) \
188         string_ncopy_do(dst, sizeof(dst), src, srclen)
190 #define string_copy_rev(dst, src) \
191         string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
193 #define string_add(dst, from, src) \
194         string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
196 static char *
197 chomp_string(char *name)
199         int namelen;
201         while (isspace(*name))
202                 name++;
204         namelen = strlen(name) - 1;
205         while (namelen > 0 && isspace(name[namelen]))
206                 name[namelen--] = 0;
208         return name;
211 static bool
212 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
214         va_list args;
215         size_t pos = bufpos ? *bufpos : 0;
217         va_start(args, fmt);
218         pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
219         va_end(args);
221         if (bufpos)
222                 *bufpos = pos;
224         return pos >= bufsize ? FALSE : TRUE;
227 #define string_format(buf, fmt, args...) \
228         string_nformat(buf, sizeof(buf), NULL, fmt, args)
230 #define string_format_from(buf, from, fmt, args...) \
231         string_nformat(buf, sizeof(buf), from, fmt, args)
233 static int
234 string_enum_compare(const char *str1, const char *str2, int len)
236         size_t i;
238 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
240         /* Diff-Header == DIFF_HEADER */
241         for (i = 0; i < len; i++) {
242                 if (toupper(str1[i]) == toupper(str2[i]))
243                         continue;
245                 if (string_enum_sep(str1[i]) &&
246                     string_enum_sep(str2[i]))
247                         continue;
249                 return str1[i] - str2[i];
250         }
252         return 0;
255 /* Shell quoting
256  *
257  * NOTE: The following is a slightly modified copy of the git project's shell
258  * quoting routines found in the quote.c file.
259  *
260  * Help to copy the thing properly quoted for the shell safety.  any single
261  * quote is replaced with '\'', any exclamation point is replaced with '\!',
262  * and the whole thing is enclosed in a
263  *
264  * E.g.
265  *  original     sq_quote     result
266  *  name     ==> name      ==> 'name'
267  *  a b      ==> a b       ==> 'a b'
268  *  a'b      ==> a'\''b    ==> 'a'\''b'
269  *  a!b      ==> a'\!'b    ==> 'a'\!'b'
270  */
272 static size_t
273 sq_quote(char buf[SIZEOF_STR], size_t bufsize, const char *src)
275         char c;
277 #define BUFPUT(x) do { if (bufsize < SIZEOF_STR) buf[bufsize++] = (x); } while (0)
279         BUFPUT('\'');
280         while ((c = *src++)) {
281                 if (c == '\'' || c == '!') {
282                         BUFPUT('\'');
283                         BUFPUT('\\');
284                         BUFPUT(c);
285                         BUFPUT('\'');
286                 } else {
287                         BUFPUT(c);
288                 }
289         }
290         BUFPUT('\'');
292         if (bufsize < SIZEOF_STR)
293                 buf[bufsize] = 0;
295         return bufsize;
299 /*
300  * User requests
301  */
303 #define REQ_INFO \
304         /* XXX: Keep the view request first and in sync with views[]. */ \
305         REQ_GROUP("View switching") \
306         REQ_(VIEW_MAIN,         "Show main view"), \
307         REQ_(VIEW_DIFF,         "Show diff view"), \
308         REQ_(VIEW_LOG,          "Show log view"), \
309         REQ_(VIEW_TREE,         "Show tree view"), \
310         REQ_(VIEW_BLOB,         "Show blob view"), \
311         REQ_(VIEW_HELP,         "Show help page"), \
312         REQ_(VIEW_PAGER,        "Show pager view"), \
313         REQ_(VIEW_STATUS,       "Show status view"), \
314         REQ_(VIEW_STAGE,        "Show stage view"), \
315         \
316         REQ_GROUP("View manipulation") \
317         REQ_(ENTER,             "Enter current line and scroll"), \
318         REQ_(NEXT,              "Move to next"), \
319         REQ_(PREVIOUS,          "Move to previous"), \
320         REQ_(VIEW_NEXT,         "Move focus to next view"), \
321         REQ_(REFRESH,           "Reload and refresh"), \
322         REQ_(VIEW_CLOSE,        "Close the current view"), \
323         REQ_(QUIT,              "Close all views and quit"), \
324         \
325         REQ_GROUP("Cursor navigation") \
326         REQ_(MOVE_UP,           "Move cursor one line up"), \
327         REQ_(MOVE_DOWN,         "Move cursor one line down"), \
328         REQ_(MOVE_PAGE_DOWN,    "Move cursor one page down"), \
329         REQ_(MOVE_PAGE_UP,      "Move cursor one page up"), \
330         REQ_(MOVE_FIRST_LINE,   "Move cursor to first line"), \
331         REQ_(MOVE_LAST_LINE,    "Move cursor to last line"), \
332         \
333         REQ_GROUP("Scrolling") \
334         REQ_(SCROLL_LINE_UP,    "Scroll one line up"), \
335         REQ_(SCROLL_LINE_DOWN,  "Scroll one line down"), \
336         REQ_(SCROLL_PAGE_UP,    "Scroll one page up"), \
337         REQ_(SCROLL_PAGE_DOWN,  "Scroll one page down"), \
338         \
339         REQ_GROUP("Searching") \
340         REQ_(SEARCH,            "Search the view"), \
341         REQ_(SEARCH_BACK,       "Search backwards in the view"), \
342         REQ_(FIND_NEXT,         "Find next search match"), \
343         REQ_(FIND_PREV,         "Find previous search match"), \
344         \
345         REQ_GROUP("Misc") \
346         REQ_(PROMPT,            "Bring up the prompt"), \
347         REQ_(SCREEN_REDRAW,     "Redraw the screen"), \
348         REQ_(SCREEN_RESIZE,     "Resize the screen"), \
349         REQ_(SHOW_VERSION,      "Show version information"), \
350         REQ_(STOP_LOADING,      "Stop all loading views"), \
351         REQ_(TOGGLE_LINENO,     "Toggle line numbers"), \
352         REQ_(TOGGLE_REV_GRAPH,  "Toggle revision graph visualization"), \
353         REQ_(STATUS_UPDATE,     "Update file status"), \
354         REQ_(STATUS_MERGE,      "Merge file using external tool"), \
355         REQ_(TREE_PARENT,       "Switch to parent directory in tree view"), \
356         REQ_(EDIT,              "Open in editor"), \
357         REQ_(NONE,              "Do nothing")
360 /* User action requests. */
361 enum request {
362 #define REQ_GROUP(help)
363 #define REQ_(req, help) REQ_##req
365         /* Offset all requests to avoid conflicts with ncurses getch values. */
366         REQ_OFFSET = KEY_MAX + 1,
367         REQ_INFO
369 #undef  REQ_GROUP
370 #undef  REQ_
371 };
373 struct request_info {
374         enum request request;
375         char *name;
376         int namelen;
377         char *help;
378 };
380 static struct request_info req_info[] = {
381 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
382 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
383         REQ_INFO
384 #undef  REQ_GROUP
385 #undef  REQ_
386 };
388 static enum request
389 get_request(const char *name)
391         int namelen = strlen(name);
392         int i;
394         for (i = 0; i < ARRAY_SIZE(req_info); i++)
395                 if (req_info[i].namelen == namelen &&
396                     !string_enum_compare(req_info[i].name, name, namelen))
397                         return req_info[i].request;
399         return REQ_NONE;
403 /*
404  * Options
405  */
407 static const char usage[] =
408 "tig " TIG_VERSION " (" __DATE__ ")\n"
409 "\n"
410 "Usage: tig [options]\n"
411 "   or: tig [options] [--] [git log options]\n"
412 "   or: tig [options] log  [git log options]\n"
413 "   or: tig [options] diff [git diff options]\n"
414 "   or: tig [options] show [git show options]\n"
415 "   or: tig [options] <    [git command output]\n"
416 "\n"
417 "Options:\n"
418 "  -l                          Start up in log view\n"
419 "  -d                          Start up in diff view\n"
420 "  -S                          Start up in status view\n"
421 "  -n[I], --line-number[=I]    Show line numbers with given interval\n"
422 "  -b[N], --tab-size[=N]       Set number of spaces for tab expansion\n"
423 "  --                          Mark end of tig options\n"
424 "  -v, --version               Show version and exit\n"
425 "  -h, --help                  Show help message and exit\n";
427 /* Option and state variables. */
428 static bool opt_line_number             = FALSE;
429 static bool opt_rev_graph               = FALSE;
430 static int opt_num_interval             = NUMBER_INTERVAL;
431 static int opt_tab_size                 = TABSIZE;
432 static enum request opt_request         = REQ_VIEW_MAIN;
433 static char opt_cmd[SIZEOF_STR]         = "";
434 static char opt_path[SIZEOF_STR]        = "";
435 static FILE *opt_pipe                   = NULL;
436 static char opt_encoding[20]            = "UTF-8";
437 static bool opt_utf8                    = TRUE;
438 static char opt_codeset[20]             = "UTF-8";
439 static iconv_t opt_iconv                = ICONV_NONE;
440 static char opt_search[SIZEOF_STR]      = "";
441 static char opt_cdup[SIZEOF_STR]        = "";
442 static char opt_git_dir[SIZEOF_STR]     = "";
443 static char opt_is_inside_work_tree     = -1; /* set to TRUE or FALSE */
444 static char opt_editor[SIZEOF_STR]      = "";
446 enum option_type {
447         OPT_NONE,
448         OPT_INT,
449 };
451 static bool
452 check_option(char *opt, char short_name, char *name, enum option_type type, ...)
454         va_list args;
455         char *value = "";
456         int *number;
458         if (opt[0] != '-')
459                 return FALSE;
461         if (opt[1] == '-') {
462                 int namelen = strlen(name);
464                 opt += 2;
466                 if (strncmp(opt, name, namelen))
467                         return FALSE;
469                 if (opt[namelen] == '=')
470                         value = opt + namelen + 1;
472         } else {
473                 if (!short_name || opt[1] != short_name)
474                         return FALSE;
475                 value = opt + 2;
476         }
478         va_start(args, type);
479         if (type == OPT_INT) {
480                 number = va_arg(args, int *);
481                 if (isdigit(*value))
482                         *number = atoi(value);
483         }
484         va_end(args);
486         return TRUE;
489 /* Returns the index of log or diff command or -1 to exit. */
490 static bool
491 parse_options(int argc, char *argv[])
493         int i;
495         for (i = 1; i < argc; i++) {
496                 char *opt = argv[i];
498                 if (!strcmp(opt, "log") ||
499                     !strcmp(opt, "diff") ||
500                     !strcmp(opt, "show")) {
501                         opt_request = opt[0] == 'l'
502                                     ? REQ_VIEW_LOG : REQ_VIEW_DIFF;
503                         break;
504                 }
506                 if (opt[0] && opt[0] != '-')
507                         break;
509                 if (!strcmp(opt, "--")) {
510                         i++;
511                         break;
512                 }
514                 if (check_option(opt, 'v', "version", OPT_NONE)) {
515                         printf("tig version %s\n", TIG_VERSION);
516                         return FALSE;
517                 }
519                 if (check_option(opt, 'h', "help", OPT_NONE)) {
520                         printf(usage);
521                         return FALSE;
522                 }
524                 if (!strcmp(opt, "-S")) {
525                         opt_request = REQ_VIEW_STATUS;
526                         continue;
527                 }
529                 if (!strcmp(opt, "-l")) {
530                         opt_request = REQ_VIEW_LOG;
531                         continue;
532                 }
534                 if (!strcmp(opt, "-d")) {
535                         opt_request = REQ_VIEW_DIFF;
536                         continue;
537                 }
539                 if (check_option(opt, 'n', "line-number", OPT_INT, &opt_num_interval)) {
540                         opt_line_number = TRUE;
541                         continue;
542                 }
544                 if (check_option(opt, 'b', "tab-size", OPT_INT, &opt_tab_size)) {
545                         opt_tab_size = MIN(opt_tab_size, TABSIZE);
546                         continue;
547                 }
549                 die("unknown option '%s'\n\n%s", opt, usage);
550         }
552         if (!isatty(STDIN_FILENO)) {
553                 opt_request = REQ_VIEW_PAGER;
554                 opt_pipe = stdin;
556         } else if (i < argc) {
557                 size_t buf_size;
559                 if (opt_request == REQ_VIEW_MAIN)
560                         /* XXX: This is vulnerable to the user overriding
561                          * options required for the main view parser. */
562                         string_copy(opt_cmd, "git log --no-color --pretty=raw");
563                 else
564                         string_copy(opt_cmd, "git");
565                 buf_size = strlen(opt_cmd);
567                 while (buf_size < sizeof(opt_cmd) && i < argc) {
568                         opt_cmd[buf_size++] = ' ';
569                         buf_size = sq_quote(opt_cmd, buf_size, argv[i++]);
570                 }
572                 if (buf_size >= sizeof(opt_cmd))
573                         die("command too long");
575                 opt_cmd[buf_size] = 0;
576         }
578         if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
579                 opt_utf8 = FALSE;
581         return TRUE;
585 /*
586  * Line-oriented content detection.
587  */
589 #define LINE_INFO \
590 LINE(DIFF_HEADER,  "diff --git ",       COLOR_YELLOW,   COLOR_DEFAULT,  0), \
591 LINE(DIFF_CHUNK,   "@@",                COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
592 LINE(DIFF_ADD,     "+",                 COLOR_GREEN,    COLOR_DEFAULT,  0), \
593 LINE(DIFF_DEL,     "-",                 COLOR_RED,      COLOR_DEFAULT,  0), \
594 LINE(DIFF_INDEX,        "index ",         COLOR_BLUE,   COLOR_DEFAULT,  0), \
595 LINE(DIFF_OLDMODE,      "old file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
596 LINE(DIFF_NEWMODE,      "new file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
597 LINE(DIFF_COPY_FROM,    "copy from",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
598 LINE(DIFF_COPY_TO,      "copy to",        COLOR_YELLOW, COLOR_DEFAULT,  0), \
599 LINE(DIFF_RENAME_FROM,  "rename from",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
600 LINE(DIFF_RENAME_TO,    "rename to",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
601 LINE(DIFF_SIMILARITY,   "similarity ",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
602 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
603 LINE(DIFF_TREE,         "diff-tree ",     COLOR_BLUE,   COLOR_DEFAULT,  0), \
604 LINE(PP_AUTHOR,    "Author: ",          COLOR_CYAN,     COLOR_DEFAULT,  0), \
605 LINE(PP_COMMIT,    "Commit: ",          COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
606 LINE(PP_MERGE,     "Merge: ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
607 LINE(PP_DATE,      "Date:   ",          COLOR_YELLOW,   COLOR_DEFAULT,  0), \
608 LINE(PP_ADATE,     "AuthorDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
609 LINE(PP_CDATE,     "CommitDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
610 LINE(PP_REFS,      "Refs: ",            COLOR_RED,      COLOR_DEFAULT,  0), \
611 LINE(COMMIT,       "commit ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
612 LINE(PARENT,       "parent ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
613 LINE(TREE,         "tree ",             COLOR_BLUE,     COLOR_DEFAULT,  0), \
614 LINE(AUTHOR,       "author ",           COLOR_CYAN,     COLOR_DEFAULT,  0), \
615 LINE(COMMITTER,    "committer ",        COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
616 LINE(SIGNOFF,      "    Signed-off-by", COLOR_YELLOW,   COLOR_DEFAULT,  0), \
617 LINE(ACKED,        "    Acked-by",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
618 LINE(DEFAULT,      "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
619 LINE(CURSOR,       "",                  COLOR_WHITE,    COLOR_GREEN,    A_BOLD), \
620 LINE(STATUS,       "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
621 LINE(TITLE_BLUR,   "",                  COLOR_WHITE,    COLOR_BLUE,     0), \
622 LINE(TITLE_FOCUS,  "",                  COLOR_WHITE,    COLOR_BLUE,     A_BOLD), \
623 LINE(MAIN_DATE,    "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
624 LINE(MAIN_AUTHOR,  "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
625 LINE(MAIN_COMMIT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
626 LINE(MAIN_DELIM,   "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
627 LINE(MAIN_TAG,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
628 LINE(MAIN_REMOTE,  "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_BOLD), \
629 LINE(MAIN_REF,     "",                  COLOR_CYAN,     COLOR_DEFAULT,  A_BOLD), \
630 LINE(MAIN_REVGRAPH,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
631 LINE(TREE_DIR,     "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
632 LINE(TREE_FILE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
633 LINE(STAT_SECTION, "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
634 LINE(STAT_NONE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
635 LINE(STAT_STAGED,  "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
636 LINE(STAT_UNSTAGED,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
637 LINE(STAT_UNTRACKED,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0)
639 enum line_type {
640 #define LINE(type, line, fg, bg, attr) \
641         LINE_##type
642         LINE_INFO
643 #undef  LINE
644 };
646 struct line_info {
647         const char *name;       /* Option name. */
648         int namelen;            /* Size of option name. */
649         const char *line;       /* The start of line to match. */
650         int linelen;            /* Size of string to match. */
651         int fg, bg, attr;       /* Color and text attributes for the lines. */
652 };
654 static struct line_info line_info[] = {
655 #define LINE(type, line, fg, bg, attr) \
656         { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
657         LINE_INFO
658 #undef  LINE
659 };
661 static enum line_type
662 get_line_type(char *line)
664         int linelen = strlen(line);
665         enum line_type type;
667         for (type = 0; type < ARRAY_SIZE(line_info); type++)
668                 /* Case insensitive search matches Signed-off-by lines better. */
669                 if (linelen >= line_info[type].linelen &&
670                     !strncasecmp(line_info[type].line, line, line_info[type].linelen))
671                         return type;
673         return LINE_DEFAULT;
676 static inline int
677 get_line_attr(enum line_type type)
679         assert(type < ARRAY_SIZE(line_info));
680         return COLOR_PAIR(type) | line_info[type].attr;
683 static struct line_info *
684 get_line_info(char *name, int namelen)
686         enum line_type type;
688         for (type = 0; type < ARRAY_SIZE(line_info); type++)
689                 if (namelen == line_info[type].namelen &&
690                     !string_enum_compare(line_info[type].name, name, namelen))
691                         return &line_info[type];
693         return NULL;
696 static void
697 init_colors(void)
699         int default_bg = COLOR_BLACK;
700         int default_fg = COLOR_WHITE;
701         enum line_type type;
703         start_color();
705         if (use_default_colors() != ERR) {
706                 default_bg = -1;
707                 default_fg = -1;
708         }
710         for (type = 0; type < ARRAY_SIZE(line_info); type++) {
711                 struct line_info *info = &line_info[type];
712                 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
713                 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
715                 init_pair(type, fg, bg);
716         }
719 struct line {
720         enum line_type type;
722         /* State flags */
723         unsigned int selected:1;
725         void *data;             /* User data */
726 };
729 /*
730  * Keys
731  */
733 struct keybinding {
734         int alias;
735         enum request request;
736         struct keybinding *next;
737 };
739 static struct keybinding default_keybindings[] = {
740         /* View switching */
741         { 'm',          REQ_VIEW_MAIN },
742         { 'd',          REQ_VIEW_DIFF },
743         { 'l',          REQ_VIEW_LOG },
744         { 't',          REQ_VIEW_TREE },
745         { 'f',          REQ_VIEW_BLOB },
746         { 'p',          REQ_VIEW_PAGER },
747         { 'h',          REQ_VIEW_HELP },
748         { 'S',          REQ_VIEW_STATUS },
749         { 'c',          REQ_VIEW_STAGE },
751         /* View manipulation */
752         { 'q',          REQ_VIEW_CLOSE },
753         { KEY_TAB,      REQ_VIEW_NEXT },
754         { KEY_RETURN,   REQ_ENTER },
755         { KEY_UP,       REQ_PREVIOUS },
756         { KEY_DOWN,     REQ_NEXT },
757         { 'R',          REQ_REFRESH },
759         /* Cursor navigation */
760         { 'k',          REQ_MOVE_UP },
761         { 'j',          REQ_MOVE_DOWN },
762         { KEY_HOME,     REQ_MOVE_FIRST_LINE },
763         { KEY_END,      REQ_MOVE_LAST_LINE },
764         { KEY_NPAGE,    REQ_MOVE_PAGE_DOWN },
765         { ' ',          REQ_MOVE_PAGE_DOWN },
766         { KEY_PPAGE,    REQ_MOVE_PAGE_UP },
767         { 'b',          REQ_MOVE_PAGE_UP },
768         { '-',          REQ_MOVE_PAGE_UP },
770         /* Scrolling */
771         { KEY_IC,       REQ_SCROLL_LINE_UP },
772         { KEY_DC,       REQ_SCROLL_LINE_DOWN },
773         { 'w',          REQ_SCROLL_PAGE_UP },
774         { 's',          REQ_SCROLL_PAGE_DOWN },
776         /* Searching */
777         { '/',          REQ_SEARCH },
778         { '?',          REQ_SEARCH_BACK },
779         { 'n',          REQ_FIND_NEXT },
780         { 'N',          REQ_FIND_PREV },
782         /* Misc */
783         { 'Q',          REQ_QUIT },
784         { 'z',          REQ_STOP_LOADING },
785         { 'v',          REQ_SHOW_VERSION },
786         { 'r',          REQ_SCREEN_REDRAW },
787         { '.',          REQ_TOGGLE_LINENO },
788         { 'g',          REQ_TOGGLE_REV_GRAPH },
789         { ':',          REQ_PROMPT },
790         { 'u',          REQ_STATUS_UPDATE },
791         { 'M',          REQ_STATUS_MERGE },
792         { ',',          REQ_TREE_PARENT },
793         { 'e',          REQ_EDIT },
795         /* Using the ncurses SIGWINCH handler. */
796         { KEY_RESIZE,   REQ_SCREEN_RESIZE },
797 };
799 #define KEYMAP_INFO \
800         KEYMAP_(GENERIC), \
801         KEYMAP_(MAIN), \
802         KEYMAP_(DIFF), \
803         KEYMAP_(LOG), \
804         KEYMAP_(TREE), \
805         KEYMAP_(BLOB), \
806         KEYMAP_(PAGER), \
807         KEYMAP_(HELP), \
808         KEYMAP_(STATUS), \
809         KEYMAP_(STAGE)
811 enum keymap {
812 #define KEYMAP_(name) KEYMAP_##name
813         KEYMAP_INFO
814 #undef  KEYMAP_
815 };
817 static struct int_map keymap_table[] = {
818 #define KEYMAP_(name) { #name, STRING_SIZE(#name), KEYMAP_##name }
819         KEYMAP_INFO
820 #undef  KEYMAP_
821 };
823 #define set_keymap(map, name) \
824         set_from_int_map(keymap_table, ARRAY_SIZE(keymap_table), map, name, strlen(name))
826 static struct keybinding *keybindings[ARRAY_SIZE(keymap_table)];
828 static void
829 add_keybinding(enum keymap keymap, enum request request, int key)
831         struct keybinding *keybinding;
833         keybinding = calloc(1, sizeof(*keybinding));
834         if (!keybinding)
835                 die("Failed to allocate keybinding");
837         keybinding->alias = key;
838         keybinding->request = request;
839         keybinding->next = keybindings[keymap];
840         keybindings[keymap] = keybinding;
843 /* Looks for a key binding first in the given map, then in the generic map, and
844  * lastly in the default keybindings. */
845 static enum request
846 get_keybinding(enum keymap keymap, int key)
848         struct keybinding *kbd;
849         int i;
851         for (kbd = keybindings[keymap]; kbd; kbd = kbd->next)
852                 if (kbd->alias == key)
853                         return kbd->request;
855         for (kbd = keybindings[KEYMAP_GENERIC]; kbd; kbd = kbd->next)
856                 if (kbd->alias == key)
857                         return kbd->request;
859         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
860                 if (default_keybindings[i].alias == key)
861                         return default_keybindings[i].request;
863         return (enum request) key;
867 struct key {
868         char *name;
869         int value;
870 };
872 static struct key key_table[] = {
873         { "Enter",      KEY_RETURN },
874         { "Space",      ' ' },
875         { "Backspace",  KEY_BACKSPACE },
876         { "Tab",        KEY_TAB },
877         { "Escape",     KEY_ESC },
878         { "Left",       KEY_LEFT },
879         { "Right",      KEY_RIGHT },
880         { "Up",         KEY_UP },
881         { "Down",       KEY_DOWN },
882         { "Insert",     KEY_IC },
883         { "Delete",     KEY_DC },
884         { "Hash",       '#' },
885         { "Home",       KEY_HOME },
886         { "End",        KEY_END },
887         { "PageUp",     KEY_PPAGE },
888         { "PageDown",   KEY_NPAGE },
889         { "F1",         KEY_F(1) },
890         { "F2",         KEY_F(2) },
891         { "F3",         KEY_F(3) },
892         { "F4",         KEY_F(4) },
893         { "F5",         KEY_F(5) },
894         { "F6",         KEY_F(6) },
895         { "F7",         KEY_F(7) },
896         { "F8",         KEY_F(8) },
897         { "F9",         KEY_F(9) },
898         { "F10",        KEY_F(10) },
899         { "F11",        KEY_F(11) },
900         { "F12",        KEY_F(12) },
901 };
903 static int
904 get_key_value(const char *name)
906         int i;
908         for (i = 0; i < ARRAY_SIZE(key_table); i++)
909                 if (!strcasecmp(key_table[i].name, name))
910                         return key_table[i].value;
912         if (strlen(name) == 1 && isprint(*name))
913                 return (int) *name;
915         return ERR;
918 static char *
919 get_key_name(int key_value)
921         static char key_char[] = "'X'";
922         char *seq = NULL;
923         int key;
925         for (key = 0; key < ARRAY_SIZE(key_table); key++)
926                 if (key_table[key].value == key_value)
927                         seq = key_table[key].name;
929         if (seq == NULL &&
930             key_value < 127 &&
931             isprint(key_value)) {
932                 key_char[1] = (char) key_value;
933                 seq = key_char;
934         }
936         return seq ? seq : "'?'";
939 static char *
940 get_key(enum request request)
942         static char buf[BUFSIZ];
943         size_t pos = 0;
944         char *sep = "";
945         int i;
947         buf[pos] = 0;
949         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
950                 struct keybinding *keybinding = &default_keybindings[i];
952                 if (keybinding->request != request)
953                         continue;
955                 if (!string_format_from(buf, &pos, "%s%s", sep,
956                                         get_key_name(keybinding->alias)))
957                         return "Too many keybindings!";
958                 sep = ", ";
959         }
961         return buf;
964 struct run_request {
965         enum keymap keymap;
966         int key;
967         char cmd[SIZEOF_STR];
968 };
970 static struct run_request *run_request;
971 static size_t run_requests;
973 static enum request
974 add_run_request(enum keymap keymap, int key, int argc, char **argv)
976         struct run_request *tmp;
977         struct run_request req = { keymap, key };
978         size_t bufpos;
980         for (bufpos = 0; argc > 0; argc--, argv++)
981                 if (!string_format_from(req.cmd, &bufpos, "%s ", *argv))
982                         return REQ_NONE;
984         req.cmd[bufpos - 1] = 0;
986         tmp = realloc(run_request, (run_requests + 1) * sizeof(*run_request));
987         if (!tmp)
988                 return REQ_NONE;
990         run_request = tmp;
991         run_request[run_requests++] = req;
993         return REQ_NONE + run_requests;
996 static struct run_request *
997 get_run_request(enum request request)
999         if (request <= REQ_NONE)
1000                 return NULL;
1001         return &run_request[request - REQ_NONE - 1];
1004 static void
1005 add_builtin_run_requests(void)
1007         struct {
1008                 enum keymap keymap;
1009                 int key;
1010                 char *argv[1];
1011         } reqs[] = {
1012                 { KEYMAP_MAIN,    'C', { "git cherry-pick %(commit)" } },
1013                 { KEYMAP_GENERIC, 'G', { "git gc" } },
1014         };
1015         int i;
1017         for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1018                 enum request req;
1020                 req = add_run_request(reqs[i].keymap, reqs[i].key, 1, reqs[i].argv);
1021                 if (req != REQ_NONE)
1022                         add_keybinding(reqs[i].keymap, req, reqs[i].key);
1023         }
1026 /*
1027  * User config file handling.
1028  */
1030 static struct int_map color_map[] = {
1031 #define COLOR_MAP(name) { #name, STRING_SIZE(#name), COLOR_##name }
1032         COLOR_MAP(DEFAULT),
1033         COLOR_MAP(BLACK),
1034         COLOR_MAP(BLUE),
1035         COLOR_MAP(CYAN),
1036         COLOR_MAP(GREEN),
1037         COLOR_MAP(MAGENTA),
1038         COLOR_MAP(RED),
1039         COLOR_MAP(WHITE),
1040         COLOR_MAP(YELLOW),
1041 };
1043 #define set_color(color, name) \
1044         set_from_int_map(color_map, ARRAY_SIZE(color_map), color, name, strlen(name))
1046 static struct int_map attr_map[] = {
1047 #define ATTR_MAP(name) { #name, STRING_SIZE(#name), A_##name }
1048         ATTR_MAP(NORMAL),
1049         ATTR_MAP(BLINK),
1050         ATTR_MAP(BOLD),
1051         ATTR_MAP(DIM),
1052         ATTR_MAP(REVERSE),
1053         ATTR_MAP(STANDOUT),
1054         ATTR_MAP(UNDERLINE),
1055 };
1057 #define set_attribute(attr, name) \
1058         set_from_int_map(attr_map, ARRAY_SIZE(attr_map), attr, name, strlen(name))
1060 static int   config_lineno;
1061 static bool  config_errors;
1062 static char *config_msg;
1064 /* Wants: object fgcolor bgcolor [attr] */
1065 static int
1066 option_color_command(int argc, char *argv[])
1068         struct line_info *info;
1070         if (argc != 3 && argc != 4) {
1071                 config_msg = "Wrong number of arguments given to color command";
1072                 return ERR;
1073         }
1075         info = get_line_info(argv[0], strlen(argv[0]));
1076         if (!info) {
1077                 config_msg = "Unknown color name";
1078                 return ERR;
1079         }
1081         if (set_color(&info->fg, argv[1]) == ERR ||
1082             set_color(&info->bg, argv[2]) == ERR) {
1083                 config_msg = "Unknown color";
1084                 return ERR;
1085         }
1087         if (argc == 4 && set_attribute(&info->attr, argv[3]) == ERR) {
1088                 config_msg = "Unknown attribute";
1089                 return ERR;
1090         }
1092         return OK;
1095 /* Wants: name = value */
1096 static int
1097 option_set_command(int argc, char *argv[])
1099         if (argc != 3) {
1100                 config_msg = "Wrong number of arguments given to set command";
1101                 return ERR;
1102         }
1104         if (strcmp(argv[1], "=")) {
1105                 config_msg = "No value assigned";
1106                 return ERR;
1107         }
1109         if (!strcmp(argv[0], "show-rev-graph")) {
1110                 opt_rev_graph = (!strcmp(argv[2], "1") ||
1111                                  !strcmp(argv[2], "true") ||
1112                                  !strcmp(argv[2], "yes"));
1113                 return OK;
1114         }
1116         if (!strcmp(argv[0], "line-number-interval")) {
1117                 opt_num_interval = atoi(argv[2]);
1118                 return OK;
1119         }
1121         if (!strcmp(argv[0], "tab-size")) {
1122                 opt_tab_size = atoi(argv[2]);
1123                 return OK;
1124         }
1126         if (!strcmp(argv[0], "commit-encoding")) {
1127                 char *arg = argv[2];
1128                 int delimiter = *arg;
1129                 int i;
1131                 switch (delimiter) {
1132                 case '"':
1133                 case '\'':
1134                         for (arg++, i = 0; arg[i]; i++)
1135                                 if (arg[i] == delimiter) {
1136                                         arg[i] = 0;
1137                                         break;
1138                                 }
1139                 default:
1140                         string_ncopy(opt_encoding, arg, strlen(arg));
1141                         return OK;
1142                 }
1143         }
1145         config_msg = "Unknown variable name";
1146         return ERR;
1149 /* Wants: mode request key */
1150 static int
1151 option_bind_command(int argc, char *argv[])
1153         enum request request;
1154         int keymap;
1155         int key;
1157         if (argc < 3) {
1158                 config_msg = "Wrong number of arguments given to bind command";
1159                 return ERR;
1160         }
1162         if (set_keymap(&keymap, argv[0]) == ERR) {
1163                 config_msg = "Unknown key map";
1164                 return ERR;
1165         }
1167         key = get_key_value(argv[1]);
1168         if (key == ERR) {
1169                 config_msg = "Unknown key";
1170                 return ERR;
1171         }
1173         request = get_request(argv[2]);
1174         if (request == REQ_NONE) {
1175                 const char *obsolete[] = { "cherry-pick" };
1176                 size_t namelen = strlen(argv[2]);
1177                 int i;
1179                 for (i = 0; i < ARRAY_SIZE(obsolete); i++) {
1180                         if (namelen == strlen(obsolete[i]) &&
1181                             !string_enum_compare(obsolete[i], argv[2], namelen)) {
1182                                 config_msg = "Obsolete request name";
1183                                 return ERR;
1184                         }
1185                 }
1186         }
1187         if (request == REQ_NONE && *argv[2]++ == '!')
1188                 request = add_run_request(keymap, key, argc - 2, argv + 2);
1189         if (request == REQ_NONE) {
1190                 config_msg = "Unknown request name";
1191                 return ERR;
1192         }
1194         add_keybinding(keymap, request, key);
1196         return OK;
1199 static int
1200 set_option(char *opt, char *value)
1202         char *argv[16];
1203         int valuelen;
1204         int argc = 0;
1206         /* Tokenize */
1207         while (argc < ARRAY_SIZE(argv) && (valuelen = strcspn(value, " \t"))) {
1208                 argv[argc++] = value;
1209                 value += valuelen;
1211                 /* Nothing more to tokenize or last available token. */
1212                 if (!*value || argc >= ARRAY_SIZE(argv))
1213                         break;
1215                 *value++ = 0;
1216                 while (isspace(*value))
1217                         value++;
1218         }
1220         if (!strcmp(opt, "color"))
1221                 return option_color_command(argc, argv);
1223         if (!strcmp(opt, "set"))
1224                 return option_set_command(argc, argv);
1226         if (!strcmp(opt, "bind"))
1227                 return option_bind_command(argc, argv);
1229         config_msg = "Unknown option command";
1230         return ERR;
1233 static int
1234 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1236         int status = OK;
1238         config_lineno++;
1239         config_msg = "Internal error";
1241         /* Check for comment markers, since read_properties() will
1242          * only ensure opt and value are split at first " \t". */
1243         optlen = strcspn(opt, "#");
1244         if (optlen == 0)
1245                 return OK;
1247         if (opt[optlen] != 0) {
1248                 config_msg = "No option value";
1249                 status = ERR;
1251         }  else {
1252                 /* Look for comment endings in the value. */
1253                 size_t len = strcspn(value, "#");
1255                 if (len < valuelen) {
1256                         valuelen = len;
1257                         value[valuelen] = 0;
1258                 }
1260                 status = set_option(opt, value);
1261         }
1263         if (status == ERR) {
1264                 fprintf(stderr, "Error on line %d, near '%.*s': %s\n",
1265                         config_lineno, (int) optlen, opt, config_msg);
1266                 config_errors = TRUE;
1267         }
1269         /* Always keep going if errors are encountered. */
1270         return OK;
1273 static int
1274 load_options(void)
1276         char *home = getenv("HOME");
1277         char buf[SIZEOF_STR];
1278         FILE *file;
1280         config_lineno = 0;
1281         config_errors = FALSE;
1283         add_builtin_run_requests();
1285         if (!home || !string_format(buf, "%s/.tigrc", home))
1286                 return ERR;
1288         /* It's ok that the file doesn't exist. */
1289         file = fopen(buf, "r");
1290         if (!file)
1291                 return OK;
1293         if (read_properties(file, " \t", read_option) == ERR ||
1294             config_errors == TRUE)
1295                 fprintf(stderr, "Errors while loading %s.\n", buf);
1297         return OK;
1301 /*
1302  * The viewer
1303  */
1305 struct view;
1306 struct view_ops;
1308 /* The display array of active views and the index of the current view. */
1309 static struct view *display[2];
1310 static unsigned int current_view;
1312 /* Reading from the prompt? */
1313 static bool input_mode = FALSE;
1315 #define foreach_displayed_view(view, i) \
1316         for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1318 #define displayed_views()       (display[1] != NULL ? 2 : 1)
1320 /* Current head and commit ID */
1321 static char ref_blob[SIZEOF_REF]        = "";
1322 static char ref_commit[SIZEOF_REF]      = "HEAD";
1323 static char ref_head[SIZEOF_REF]        = "HEAD";
1325 struct view {
1326         const char *name;       /* View name */
1327         const char *cmd_fmt;    /* Default command line format */
1328         const char *cmd_env;    /* Command line set via environment */
1329         const char *id;         /* Points to either of ref_{head,commit,blob} */
1331         struct view_ops *ops;   /* View operations */
1333         enum keymap keymap;     /* What keymap does this view have */
1335         char cmd[SIZEOF_STR];   /* Command buffer */
1336         char ref[SIZEOF_REF];   /* Hovered commit reference */
1337         char vid[SIZEOF_REF];   /* View ID. Set to id member when updating. */
1339         int height, width;      /* The width and height of the main window */
1340         WINDOW *win;            /* The main window */
1341         WINDOW *title;          /* The title window living below the main window */
1343         /* Navigation */
1344         unsigned long offset;   /* Offset of the window top */
1345         unsigned long lineno;   /* Current line number */
1347         /* Searching */
1348         char grep[SIZEOF_STR];  /* Search string */
1349         regex_t *regex;         /* Pre-compiled regex */
1351         /* If non-NULL, points to the view that opened this view. If this view
1352          * is closed tig will switch back to the parent view. */
1353         struct view *parent;
1355         /* Buffering */
1356         unsigned long lines;    /* Total number of lines */
1357         struct line *line;      /* Line index */
1358         unsigned long line_size;/* Total number of allocated lines */
1359         unsigned int digits;    /* Number of digits in the lines member. */
1361         /* Loading */
1362         FILE *pipe;
1363         time_t start_time;
1364 };
1366 struct view_ops {
1367         /* What type of content being displayed. Used in the title bar. */
1368         const char *type;
1369         /* Open and reads in all view content. */
1370         bool (*open)(struct view *view);
1371         /* Read one line; updates view->line. */
1372         bool (*read)(struct view *view, char *data);
1373         /* Draw one line; @lineno must be < view->height. */
1374         bool (*draw)(struct view *view, struct line *line, unsigned int lineno, bool selected);
1375         /* Depending on view handle a special requests. */
1376         enum request (*request)(struct view *view, enum request request, struct line *line);
1377         /* Search for regex in a line. */
1378         bool (*grep)(struct view *view, struct line *line);
1379         /* Select line */
1380         void (*select)(struct view *view, struct line *line);
1381 };
1383 static struct view_ops pager_ops;
1384 static struct view_ops main_ops;
1385 static struct view_ops tree_ops;
1386 static struct view_ops blob_ops;
1387 static struct view_ops help_ops;
1388 static struct view_ops status_ops;
1389 static struct view_ops stage_ops;
1391 #define VIEW_STR(name, cmd, env, ref, ops, map) \
1392         { name, cmd, #env, ref, ops, map}
1394 #define VIEW_(id, name, ops, ref) \
1395         VIEW_STR(name, TIG_##id##_CMD,  TIG_##id##_CMD, ref, ops, KEYMAP_##id)
1398 static struct view views[] = {
1399         VIEW_(MAIN,   "main",   &main_ops,   ref_head),
1400         VIEW_(DIFF,   "diff",   &pager_ops,  ref_commit),
1401         VIEW_(LOG,    "log",    &pager_ops,  ref_head),
1402         VIEW_(TREE,   "tree",   &tree_ops,   ref_commit),
1403         VIEW_(BLOB,   "blob",   &blob_ops,   ref_blob),
1404         VIEW_(HELP,   "help",   &help_ops,   ""),
1405         VIEW_(PAGER,  "pager",  &pager_ops,  "stdin"),
1406         VIEW_(STATUS, "status", &status_ops, ""),
1407         VIEW_(STAGE,  "stage",  &stage_ops,  ""),
1408 };
1410 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
1412 #define foreach_view(view, i) \
1413         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1415 #define view_is_displayed(view) \
1416         (view == display[0] || view == display[1])
1418 static bool
1419 draw_view_line(struct view *view, unsigned int lineno)
1421         struct line *line;
1422         bool selected = (view->offset + lineno == view->lineno);
1423         bool draw_ok;
1425         assert(view_is_displayed(view));
1427         if (view->offset + lineno >= view->lines)
1428                 return FALSE;
1430         line = &view->line[view->offset + lineno];
1432         if (selected) {
1433                 line->selected = TRUE;
1434                 view->ops->select(view, line);
1435         } else if (line->selected) {
1436                 line->selected = FALSE;
1437                 wmove(view->win, lineno, 0);
1438                 wclrtoeol(view->win);
1439         }
1441         scrollok(view->win, FALSE);
1442         draw_ok = view->ops->draw(view, line, lineno, selected);
1443         scrollok(view->win, TRUE);
1445         return draw_ok;
1448 static void
1449 redraw_view_from(struct view *view, int lineno)
1451         assert(0 <= lineno && lineno < view->height);
1453         for (; lineno < view->height; lineno++) {
1454                 if (!draw_view_line(view, lineno))
1455                         break;
1456         }
1458         redrawwin(view->win);
1459         if (input_mode)
1460                 wnoutrefresh(view->win);
1461         else
1462                 wrefresh(view->win);
1465 static void
1466 redraw_view(struct view *view)
1468         wclear(view->win);
1469         redraw_view_from(view, 0);
1473 static void
1474 update_view_title(struct view *view)
1476         char buf[SIZEOF_STR];
1477         char state[SIZEOF_STR];
1478         size_t bufpos = 0, statelen = 0;
1480         assert(view_is_displayed(view));
1482         if (view != VIEW(REQ_VIEW_STATUS) && (view->lines || view->pipe)) {
1483                 unsigned int view_lines = view->offset + view->height;
1484                 unsigned int lines = view->lines
1485                                    ? MIN(view_lines, view->lines) * 100 / view->lines
1486                                    : 0;
1488                 string_format_from(state, &statelen, "- %s %d of %d (%d%%)",
1489                                    view->ops->type,
1490                                    view->lineno + 1,
1491                                    view->lines,
1492                                    lines);
1494                 if (view->pipe) {
1495                         time_t secs = time(NULL) - view->start_time;
1497                         /* Three git seconds are a long time ... */
1498                         if (secs > 2)
1499                                 string_format_from(state, &statelen, " %lds", secs);
1500                 }
1501         }
1503         string_format_from(buf, &bufpos, "[%s]", view->name);
1504         if (*view->ref && bufpos < view->width) {
1505                 size_t refsize = strlen(view->ref);
1506                 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
1508                 if (minsize < view->width)
1509                         refsize = view->width - minsize + 7;
1510                 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
1511         }
1513         if (statelen && bufpos < view->width) {
1514                 string_format_from(buf, &bufpos, " %s", state);
1515         }
1517         if (view == display[current_view])
1518                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
1519         else
1520                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
1522         mvwaddnstr(view->title, 0, 0, buf, bufpos);
1523         wclrtoeol(view->title);
1524         wmove(view->title, 0, view->width - 1);
1526         if (input_mode)
1527                 wnoutrefresh(view->title);
1528         else
1529                 wrefresh(view->title);
1532 static void
1533 resize_display(void)
1535         int offset, i;
1536         struct view *base = display[0];
1537         struct view *view = display[1] ? display[1] : display[0];
1539         /* Setup window dimensions */
1541         getmaxyx(stdscr, base->height, base->width);
1543         /* Make room for the status window. */
1544         base->height -= 1;
1546         if (view != base) {
1547                 /* Horizontal split. */
1548                 view->width   = base->width;
1549                 view->height  = SCALE_SPLIT_VIEW(base->height);
1550                 base->height -= view->height;
1552                 /* Make room for the title bar. */
1553                 view->height -= 1;
1554         }
1556         /* Make room for the title bar. */
1557         base->height -= 1;
1559         offset = 0;
1561         foreach_displayed_view (view, i) {
1562                 if (!view->win) {
1563                         view->win = newwin(view->height, 0, offset, 0);
1564                         if (!view->win)
1565                                 die("Failed to create %s view", view->name);
1567                         scrollok(view->win, TRUE);
1569                         view->title = newwin(1, 0, offset + view->height, 0);
1570                         if (!view->title)
1571                                 die("Failed to create title window");
1573                 } else {
1574                         wresize(view->win, view->height, view->width);
1575                         mvwin(view->win,   offset, 0);
1576                         mvwin(view->title, offset + view->height, 0);
1577                 }
1579                 offset += view->height + 1;
1580         }
1583 static void
1584 redraw_display(void)
1586         struct view *view;
1587         int i;
1589         foreach_displayed_view (view, i) {
1590                 redraw_view(view);
1591                 update_view_title(view);
1592         }
1595 static void
1596 update_display_cursor(struct view *view)
1598         /* Move the cursor to the right-most column of the cursor line.
1599          *
1600          * XXX: This could turn out to be a bit expensive, but it ensures that
1601          * the cursor does not jump around. */
1602         if (view->lines) {
1603                 wmove(view->win, view->lineno - view->offset, view->width - 1);
1604                 wrefresh(view->win);
1605         }
1608 /*
1609  * Navigation
1610  */
1612 /* Scrolling backend */
1613 static void
1614 do_scroll_view(struct view *view, int lines)
1616         bool redraw_current_line = FALSE;
1618         /* The rendering expects the new offset. */
1619         view->offset += lines;
1621         assert(0 <= view->offset && view->offset < view->lines);
1622         assert(lines);
1624         /* Move current line into the view. */
1625         if (view->lineno < view->offset) {
1626                 view->lineno = view->offset;
1627                 redraw_current_line = TRUE;
1628         } else if (view->lineno >= view->offset + view->height) {
1629                 view->lineno = view->offset + view->height - 1;
1630                 redraw_current_line = TRUE;
1631         }
1633         assert(view->offset <= view->lineno && view->lineno < view->lines);
1635         /* Redraw the whole screen if scrolling is pointless. */
1636         if (view->height < ABS(lines)) {
1637                 redraw_view(view);
1639         } else {
1640                 int line = lines > 0 ? view->height - lines : 0;
1641                 int end = line + ABS(lines);
1643                 wscrl(view->win, lines);
1645                 for (; line < end; line++) {
1646                         if (!draw_view_line(view, line))
1647                                 break;
1648                 }
1650                 if (redraw_current_line)
1651                         draw_view_line(view, view->lineno - view->offset);
1652         }
1654         redrawwin(view->win);
1655         wrefresh(view->win);
1656         report("");
1659 /* Scroll frontend */
1660 static void
1661 scroll_view(struct view *view, enum request request)
1663         int lines = 1;
1665         assert(view_is_displayed(view));
1667         switch (request) {
1668         case REQ_SCROLL_PAGE_DOWN:
1669                 lines = view->height;
1670         case REQ_SCROLL_LINE_DOWN:
1671                 if (view->offset + lines > view->lines)
1672                         lines = view->lines - view->offset;
1674                 if (lines == 0 || view->offset + view->height >= view->lines) {
1675                         report("Cannot scroll beyond the last line");
1676                         return;
1677                 }
1678                 break;
1680         case REQ_SCROLL_PAGE_UP:
1681                 lines = view->height;
1682         case REQ_SCROLL_LINE_UP:
1683                 if (lines > view->offset)
1684                         lines = view->offset;
1686                 if (lines == 0) {
1687                         report("Cannot scroll beyond the first line");
1688                         return;
1689                 }
1691                 lines = -lines;
1692                 break;
1694         default:
1695                 die("request %d not handled in switch", request);
1696         }
1698         do_scroll_view(view, lines);
1701 /* Cursor moving */
1702 static void
1703 move_view(struct view *view, enum request request)
1705         int scroll_steps = 0;
1706         int steps;
1708         switch (request) {
1709         case REQ_MOVE_FIRST_LINE:
1710                 steps = -view->lineno;
1711                 break;
1713         case REQ_MOVE_LAST_LINE:
1714                 steps = view->lines - view->lineno - 1;
1715                 break;
1717         case REQ_MOVE_PAGE_UP:
1718                 steps = view->height > view->lineno
1719                       ? -view->lineno : -view->height;
1720                 break;
1722         case REQ_MOVE_PAGE_DOWN:
1723                 steps = view->lineno + view->height >= view->lines
1724                       ? view->lines - view->lineno - 1 : view->height;
1725                 break;
1727         case REQ_MOVE_UP:
1728                 steps = -1;
1729                 break;
1731         case REQ_MOVE_DOWN:
1732                 steps = 1;
1733                 break;
1735         default:
1736                 die("request %d not handled in switch", request);
1737         }
1739         if (steps <= 0 && view->lineno == 0) {
1740                 report("Cannot move beyond the first line");
1741                 return;
1743         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
1744                 report("Cannot move beyond the last line");
1745                 return;
1746         }
1748         /* Move the current line */
1749         view->lineno += steps;
1750         assert(0 <= view->lineno && view->lineno < view->lines);
1752         /* Check whether the view needs to be scrolled */
1753         if (view->lineno < view->offset ||
1754             view->lineno >= view->offset + view->height) {
1755                 scroll_steps = steps;
1756                 if (steps < 0 && -steps > view->offset) {
1757                         scroll_steps = -view->offset;
1759                 } else if (steps > 0) {
1760                         if (view->lineno == view->lines - 1 &&
1761                             view->lines > view->height) {
1762                                 scroll_steps = view->lines - view->offset - 1;
1763                                 if (scroll_steps >= view->height)
1764                                         scroll_steps -= view->height - 1;
1765                         }
1766                 }
1767         }
1769         if (!view_is_displayed(view)) {
1770                 view->offset += scroll_steps;
1771                 assert(0 <= view->offset && view->offset < view->lines);
1772                 view->ops->select(view, &view->line[view->lineno]);
1773                 return;
1774         }
1776         /* Repaint the old "current" line if we be scrolling */
1777         if (ABS(steps) < view->height)
1778                 draw_view_line(view, view->lineno - steps - view->offset);
1780         if (scroll_steps) {
1781                 do_scroll_view(view, scroll_steps);
1782                 return;
1783         }
1785         /* Draw the current line */
1786         draw_view_line(view, view->lineno - view->offset);
1788         redrawwin(view->win);
1789         wrefresh(view->win);
1790         report("");
1794 /*
1795  * Searching
1796  */
1798 static void search_view(struct view *view, enum request request);
1800 static bool
1801 find_next_line(struct view *view, unsigned long lineno, struct line *line)
1803         assert(view_is_displayed(view));
1805         if (!view->ops->grep(view, line))
1806                 return FALSE;
1808         if (lineno - view->offset >= view->height) {
1809                 view->offset = lineno;
1810                 view->lineno = lineno;
1811                 redraw_view(view);
1813         } else {
1814                 unsigned long old_lineno = view->lineno - view->offset;
1816                 view->lineno = lineno;
1817                 draw_view_line(view, old_lineno);
1819                 draw_view_line(view, view->lineno - view->offset);
1820                 redrawwin(view->win);
1821                 wrefresh(view->win);
1822         }
1824         report("Line %ld matches '%s'", lineno + 1, view->grep);
1825         return TRUE;
1828 static void
1829 find_next(struct view *view, enum request request)
1831         unsigned long lineno = view->lineno;
1832         int direction;
1834         if (!*view->grep) {
1835                 if (!*opt_search)
1836                         report("No previous search");
1837                 else
1838                         search_view(view, request);
1839                 return;
1840         }
1842         switch (request) {
1843         case REQ_SEARCH:
1844         case REQ_FIND_NEXT:
1845                 direction = 1;
1846                 break;
1848         case REQ_SEARCH_BACK:
1849         case REQ_FIND_PREV:
1850                 direction = -1;
1851                 break;
1853         default:
1854                 return;
1855         }
1857         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
1858                 lineno += direction;
1860         /* Note, lineno is unsigned long so will wrap around in which case it
1861          * will become bigger than view->lines. */
1862         for (; lineno < view->lines; lineno += direction) {
1863                 struct line *line = &view->line[lineno];
1865                 if (find_next_line(view, lineno, line))
1866                         return;
1867         }
1869         report("No match found for '%s'", view->grep);
1872 static void
1873 search_view(struct view *view, enum request request)
1875         int regex_err;
1877         if (view->regex) {
1878                 regfree(view->regex);
1879                 *view->grep = 0;
1880         } else {
1881                 view->regex = calloc(1, sizeof(*view->regex));
1882                 if (!view->regex)
1883                         return;
1884         }
1886         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
1887         if (regex_err != 0) {
1888                 char buf[SIZEOF_STR] = "unknown error";
1890                 regerror(regex_err, view->regex, buf, sizeof(buf));
1891                 report("Search failed: %s", buf);
1892                 return;
1893         }
1895         string_copy(view->grep, opt_search);
1897         find_next(view, request);
1900 /*
1901  * Incremental updating
1902  */
1904 static void
1905 end_update(struct view *view)
1907         if (!view->pipe)
1908                 return;
1909         set_nonblocking_input(FALSE);
1910         if (view->pipe == stdin)
1911                 fclose(view->pipe);
1912         else
1913                 pclose(view->pipe);
1914         view->pipe = NULL;
1917 static bool
1918 begin_update(struct view *view)
1920         if (view->pipe)
1921                 end_update(view);
1923         if (opt_cmd[0]) {
1924                 string_copy(view->cmd, opt_cmd);
1925                 opt_cmd[0] = 0;
1926                 /* When running random commands, initially show the
1927                  * command in the title. However, it maybe later be
1928                  * overwritten if a commit line is selected. */
1929                 if (view == VIEW(REQ_VIEW_PAGER))
1930                         string_copy(view->ref, view->cmd);
1931                 else
1932                         view->ref[0] = 0;
1934         } else if (view == VIEW(REQ_VIEW_TREE)) {
1935                 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
1936                 char path[SIZEOF_STR];
1938                 if (strcmp(view->vid, view->id))
1939                         opt_path[0] = path[0] = 0;
1940                 else if (sq_quote(path, 0, opt_path) >= sizeof(path))
1941                         return FALSE;
1943                 if (!string_format(view->cmd, format, view->id, path))
1944                         return FALSE;
1946         } else {
1947                 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
1948                 const char *id = view->id;
1950                 if (!string_format(view->cmd, format, id, id, id, id, id))
1951                         return FALSE;
1953                 /* Put the current ref_* value to the view title ref
1954                  * member. This is needed by the blob view. Most other
1955                  * views sets it automatically after loading because the
1956                  * first line is a commit line. */
1957                 string_copy_rev(view->ref, view->id);
1958         }
1960         /* Special case for the pager view. */
1961         if (opt_pipe) {
1962                 view->pipe = opt_pipe;
1963                 opt_pipe = NULL;
1964         } else {
1965                 view->pipe = popen(view->cmd, "r");
1966         }
1968         if (!view->pipe)
1969                 return FALSE;
1971         set_nonblocking_input(TRUE);
1973         view->offset = 0;
1974         view->lines  = 0;
1975         view->lineno = 0;
1976         string_copy_rev(view->vid, view->id);
1978         if (view->line) {
1979                 int i;
1981                 for (i = 0; i < view->lines; i++)
1982                         if (view->line[i].data)
1983                                 free(view->line[i].data);
1985                 free(view->line);
1986                 view->line = NULL;
1987         }
1989         view->start_time = time(NULL);
1991         return TRUE;
1994 static struct line *
1995 realloc_lines(struct view *view, size_t line_size)
1997         struct line *tmp = realloc(view->line, sizeof(*view->line) * line_size);
1999         if (!tmp)
2000                 return NULL;
2002         view->line = tmp;
2003         view->line_size = line_size;
2004         return view->line;
2007 static bool
2008 update_view(struct view *view)
2010         char in_buffer[BUFSIZ];
2011         char out_buffer[BUFSIZ * 2];
2012         char *line;
2013         /* The number of lines to read. If too low it will cause too much
2014          * redrawing (and possible flickering), if too high responsiveness
2015          * will suffer. */
2016         unsigned long lines = view->height;
2017         int redraw_from = -1;
2019         if (!view->pipe)
2020                 return TRUE;
2022         /* Only redraw if lines are visible. */
2023         if (view->offset + view->height >= view->lines)
2024                 redraw_from = view->lines - view->offset;
2026         /* FIXME: This is probably not perfect for backgrounded views. */
2027         if (!realloc_lines(view, view->lines + lines))
2028                 goto alloc_error;
2030         while ((line = fgets(in_buffer, sizeof(in_buffer), view->pipe))) {
2031                 size_t linelen = strlen(line);
2033                 if (linelen)
2034                         line[linelen - 1] = 0;
2036                 if (opt_iconv != ICONV_NONE) {
2037                         ICONV_CONST char *inbuf = line;
2038                         size_t inlen = linelen;
2040                         char *outbuf = out_buffer;
2041                         size_t outlen = sizeof(out_buffer);
2043                         size_t ret;
2045                         ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2046                         if (ret != (size_t) -1) {
2047                                 line = out_buffer;
2048                                 linelen = strlen(out_buffer);
2049                         }
2050                 }
2052                 if (!view->ops->read(view, line))
2053                         goto alloc_error;
2055                 if (lines-- == 1)
2056                         break;
2057         }
2059         {
2060                 int digits;
2062                 lines = view->lines;
2063                 for (digits = 0; lines; digits++)
2064                         lines /= 10;
2066                 /* Keep the displayed view in sync with line number scaling. */
2067                 if (digits != view->digits) {
2068                         view->digits = digits;
2069                         redraw_from = 0;
2070                 }
2071         }
2073         if (!view_is_displayed(view))
2074                 goto check_pipe;
2076         if (view == VIEW(REQ_VIEW_TREE)) {
2077                 /* Clear the view and redraw everything since the tree sorting
2078                  * might have rearranged things. */
2079                 redraw_view(view);
2081         } else if (redraw_from >= 0) {
2082                 /* If this is an incremental update, redraw the previous line
2083                  * since for commits some members could have changed when
2084                  * loading the main view. */
2085                 if (redraw_from > 0)
2086                         redraw_from--;
2088                 /* Since revision graph visualization requires knowledge
2089                  * about the parent commit, it causes a further one-off
2090                  * needed to be redrawn for incremental updates. */
2091                 if (redraw_from > 0 && opt_rev_graph)
2092                         redraw_from--;
2094                 /* Incrementally draw avoids flickering. */
2095                 redraw_view_from(view, redraw_from);
2096         }
2098         /* Update the title _after_ the redraw so that if the redraw picks up a
2099          * commit reference in view->ref it'll be available here. */
2100         update_view_title(view);
2102 check_pipe:
2103         if (ferror(view->pipe)) {
2104                 report("Failed to read: %s", strerror(errno));
2105                 goto end;
2107         } else if (feof(view->pipe)) {
2108                 report("");
2109                 goto end;
2110         }
2112         return TRUE;
2114 alloc_error:
2115         report("Allocation failure");
2117 end:
2118         view->ops->read(view, NULL);
2119         end_update(view);
2120         return FALSE;
2123 static struct line *
2124 add_line_data(struct view *view, void *data, enum line_type type)
2126         struct line *line = &view->line[view->lines++];
2128         memset(line, 0, sizeof(*line));
2129         line->type = type;
2130         line->data = data;
2132         return line;
2135 static struct line *
2136 add_line_text(struct view *view, char *data, enum line_type type)
2138         if (data)
2139                 data = strdup(data);
2141         return data ? add_line_data(view, data, type) : NULL;
2145 /*
2146  * View opening
2147  */
2149 enum open_flags {
2150         OPEN_DEFAULT = 0,       /* Use default view switching. */
2151         OPEN_SPLIT = 1,         /* Split current view. */
2152         OPEN_BACKGROUNDED = 2,  /* Backgrounded. */
2153         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
2154 };
2156 static void
2157 open_view(struct view *prev, enum request request, enum open_flags flags)
2159         bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
2160         bool split = !!(flags & OPEN_SPLIT);
2161         bool reload = !!(flags & OPEN_RELOAD);
2162         struct view *view = VIEW(request);
2163         int nviews = displayed_views();
2164         struct view *base_view = display[0];
2166         if (view == prev && nviews == 1 && !reload) {
2167                 report("Already in %s view", view->name);
2168                 return;
2169         }
2171         if (view->ops->open) {
2172                 if (!view->ops->open(view)) {
2173                         report("Failed to load %s view", view->name);
2174                         return;
2175                 }
2177         } else if ((reload || strcmp(view->vid, view->id)) &&
2178                    !begin_update(view)) {
2179                 report("Failed to load %s view", view->name);
2180                 return;
2181         }
2183         if (split) {
2184                 display[1] = view;
2185                 if (!backgrounded)
2186                         current_view = 1;
2187         } else {
2188                 /* Maximize the current view. */
2189                 memset(display, 0, sizeof(display));
2190                 current_view = 0;
2191                 display[current_view] = view;
2192         }
2194         /* Resize the view when switching between split- and full-screen,
2195          * or when switching between two different full-screen views. */
2196         if (nviews != displayed_views() ||
2197             (nviews == 1 && base_view != display[0]))
2198                 resize_display();
2200         if (split && prev->lineno - prev->offset >= prev->height) {
2201                 /* Take the title line into account. */
2202                 int lines = prev->lineno - prev->offset - prev->height + 1;
2204                 /* Scroll the view that was split if the current line is
2205                  * outside the new limited view. */
2206                 do_scroll_view(prev, lines);
2207         }
2209         if (prev && view != prev) {
2210                 if (split && !backgrounded) {
2211                         /* "Blur" the previous view. */
2212                         update_view_title(prev);
2213                 }
2215                 view->parent = prev;
2216         }
2218         if (view->pipe && view->lines == 0) {
2219                 /* Clear the old view and let the incremental updating refill
2220                  * the screen. */
2221                 wclear(view->win);
2222                 report("");
2223         } else {
2224                 redraw_view(view);
2225                 report("");
2226         }
2228         /* If the view is backgrounded the above calls to report()
2229          * won't redraw the view title. */
2230         if (backgrounded)
2231                 update_view_title(view);
2234 static void
2235 open_external_viewer(const char *cmd)
2237         def_prog_mode();           /* save current tty modes */
2238         endwin();                  /* restore original tty modes */
2239         system(cmd);
2240         fprintf(stderr, "Press Enter to continue");
2241         getc(stdin);
2242         reset_prog_mode();
2243         redraw_display();
2246 static void
2247 open_mergetool(const char *file)
2249         char cmd[SIZEOF_STR];
2250         char file_sq[SIZEOF_STR];
2252         if (sq_quote(file_sq, 0, file) < sizeof(file_sq) &&
2253             string_format(cmd, "git mergetool %s", file_sq)) {
2254                 open_external_viewer(cmd);
2255         }
2258 static void
2259 open_editor(bool from_root, const char *file)
2261         char cmd[SIZEOF_STR];
2262         char file_sq[SIZEOF_STR];
2263         char *editor;
2264         char *prefix = from_root ? opt_cdup : "";
2266         editor = getenv("GIT_EDITOR");
2267         if (!editor && *opt_editor)
2268                 editor = opt_editor;
2269         if (!editor)
2270                 editor = getenv("VISUAL");
2271         if (!editor)
2272                 editor = getenv("EDITOR");
2273         if (!editor)
2274                 editor = "vi";
2276         if (sq_quote(file_sq, 0, file) < sizeof(file_sq) &&
2277             string_format(cmd, "%s %s%s", editor, prefix, file_sq)) {
2278                 open_external_viewer(cmd);
2279         }
2282 static void
2283 open_run_request(enum request request)
2285         struct run_request *req = get_run_request(request);
2286         char buf[SIZEOF_STR * 2];
2287         size_t bufpos;
2288         char *cmd;
2290         if (!req) {
2291                 report("Unknown run request");
2292                 return;
2293         }
2295         bufpos = 0;
2296         cmd = req->cmd;
2298         while (cmd) {
2299                 char *next = strstr(cmd, "%(");
2300                 int len = next - cmd;
2301                 char *value;
2303                 if (!next) {
2304                         len = strlen(cmd);
2305                         value = "";
2307                 } else if (!strncmp(next, "%(head)", 7)) {
2308                         value = ref_head;
2310                 } else if (!strncmp(next, "%(commit)", 9)) {
2311                         value = ref_commit;
2313                 } else if (!strncmp(next, "%(blob)", 7)) {
2314                         value = ref_blob;
2316                 } else {
2317                         report("Unknown replacement in run request: `%s`", req->cmd);
2318                         return;
2319                 }
2321                 if (!string_format_from(buf, &bufpos, "%.*s%s", len, cmd, value))
2322                         return;
2324                 if (next)
2325                         next = strchr(next, ')') + 1;
2326                 cmd = next;
2327         }
2329         open_external_viewer(buf);
2332 /*
2333  * User request switch noodle
2334  */
2336 static int
2337 view_driver(struct view *view, enum request request)
2339         int i;
2341         if (request == REQ_NONE) {
2342                 doupdate();
2343                 return TRUE;
2344         }
2346         if (request > REQ_NONE) {
2347                 open_run_request(request);
2348                 return TRUE;
2349         }
2351         if (view && view->lines) {
2352                 request = view->ops->request(view, request, &view->line[view->lineno]);
2353                 if (request == REQ_NONE)
2354                         return TRUE;
2355         }
2357         switch (request) {
2358         case REQ_MOVE_UP:
2359         case REQ_MOVE_DOWN:
2360         case REQ_MOVE_PAGE_UP:
2361         case REQ_MOVE_PAGE_DOWN:
2362         case REQ_MOVE_FIRST_LINE:
2363         case REQ_MOVE_LAST_LINE:
2364                 move_view(view, request);
2365                 break;
2367         case REQ_SCROLL_LINE_DOWN:
2368         case REQ_SCROLL_LINE_UP:
2369         case REQ_SCROLL_PAGE_DOWN:
2370         case REQ_SCROLL_PAGE_UP:
2371                 scroll_view(view, request);
2372                 break;
2374         case REQ_VIEW_BLOB:
2375                 if (!ref_blob[0]) {
2376                         report("No file chosen, press %s to open tree view",
2377                                get_key(REQ_VIEW_TREE));
2378                         break;
2379                 }
2380                 open_view(view, request, OPEN_DEFAULT);
2381                 break;
2383         case REQ_VIEW_PAGER:
2384                 if (!opt_pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
2385                         report("No pager content, press %s to run command from prompt",
2386                                get_key(REQ_PROMPT));
2387                         break;
2388                 }
2389                 open_view(view, request, OPEN_DEFAULT);
2390                 break;
2392         case REQ_VIEW_STAGE:
2393                 if (!VIEW(REQ_VIEW_STAGE)->lines) {
2394                         report("No stage content, press %s to open the status view and choose file",
2395                                get_key(REQ_VIEW_STATUS));
2396                         break;
2397                 }
2398                 open_view(view, request, OPEN_DEFAULT);
2399                 break;
2401         case REQ_VIEW_STATUS:
2402                 if (opt_is_inside_work_tree == FALSE) {
2403                         report("The status view requires a working tree");
2404                         break;
2405                 }
2406                 open_view(view, request, OPEN_DEFAULT);
2407                 break;
2409         case REQ_VIEW_MAIN:
2410         case REQ_VIEW_DIFF:
2411         case REQ_VIEW_LOG:
2412         case REQ_VIEW_TREE:
2413         case REQ_VIEW_HELP:
2414                 open_view(view, request, OPEN_DEFAULT);
2415                 break;
2417         case REQ_NEXT:
2418         case REQ_PREVIOUS:
2419                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
2421                 if ((view == VIEW(REQ_VIEW_DIFF) &&
2422                      view->parent == VIEW(REQ_VIEW_MAIN)) ||
2423                    (view == VIEW(REQ_VIEW_STAGE) &&
2424                      view->parent == VIEW(REQ_VIEW_STATUS)) ||
2425                    (view == VIEW(REQ_VIEW_BLOB) &&
2426                      view->parent == VIEW(REQ_VIEW_TREE))) {
2427                         int line;
2429                         view = view->parent;
2430                         line = view->lineno;
2431                         move_view(view, request);
2432                         if (view_is_displayed(view))
2433                                 update_view_title(view);
2434                         if (line != view->lineno)
2435                                 view->ops->request(view, REQ_ENTER,
2436                                                    &view->line[view->lineno]);
2438                 } else {
2439                         move_view(view, request);
2440                 }
2441                 break;
2443         case REQ_VIEW_NEXT:
2444         {
2445                 int nviews = displayed_views();
2446                 int next_view = (current_view + 1) % nviews;
2448                 if (next_view == current_view) {
2449                         report("Only one view is displayed");
2450                         break;
2451                 }
2453                 current_view = next_view;
2454                 /* Blur out the title of the previous view. */
2455                 update_view_title(view);
2456                 report("");
2457                 break;
2458         }
2459         case REQ_REFRESH:
2460                 report("Refreshing is not yet supported for the %s view", view->name);
2461                 break;
2463         case REQ_TOGGLE_LINENO:
2464                 opt_line_number = !opt_line_number;
2465                 redraw_display();
2466                 break;
2468         case REQ_TOGGLE_REV_GRAPH:
2469                 opt_rev_graph = !opt_rev_graph;
2470                 redraw_display();
2471                 break;
2473         case REQ_PROMPT:
2474                 /* Always reload^Wrerun commands from the prompt. */
2475                 open_view(view, opt_request, OPEN_RELOAD);
2476                 break;
2478         case REQ_SEARCH:
2479         case REQ_SEARCH_BACK:
2480                 search_view(view, request);
2481                 break;
2483         case REQ_FIND_NEXT:
2484         case REQ_FIND_PREV:
2485                 find_next(view, request);
2486                 break;
2488         case REQ_STOP_LOADING:
2489                 for (i = 0; i < ARRAY_SIZE(views); i++) {
2490                         view = &views[i];
2491                         if (view->pipe)
2492                                 report("Stopped loading the %s view", view->name),
2493                         end_update(view);
2494                 }
2495                 break;
2497         case REQ_SHOW_VERSION:
2498                 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
2499                 return TRUE;
2501         case REQ_SCREEN_RESIZE:
2502                 resize_display();
2503                 /* Fall-through */
2504         case REQ_SCREEN_REDRAW:
2505                 redraw_display();
2506                 break;
2508         case REQ_EDIT:
2509                 report("Nothing to edit");
2510                 break;
2513         case REQ_ENTER:
2514                 report("Nothing to enter");
2515                 break;
2518         case REQ_VIEW_CLOSE:
2519                 /* XXX: Mark closed views by letting view->parent point to the
2520                  * view itself. Parents to closed view should never be
2521                  * followed. */
2522                 if (view->parent &&
2523                     view->parent->parent != view->parent) {
2524                         memset(display, 0, sizeof(display));
2525                         current_view = 0;
2526                         display[current_view] = view->parent;
2527                         view->parent = view;
2528                         resize_display();
2529                         redraw_display();
2530                         break;
2531                 }
2532                 /* Fall-through */
2533         case REQ_QUIT:
2534                 return FALSE;
2536         default:
2537                 /* An unknown key will show most commonly used commands. */
2538                 report("Unknown key, press 'h' for help");
2539                 return TRUE;
2540         }
2542         return TRUE;
2546 /*
2547  * Pager backend
2548  */
2550 static bool
2551 pager_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
2553         char *text = line->data;
2554         enum line_type type = line->type;
2555         int textlen = strlen(text);
2556         int attr;
2558         wmove(view->win, lineno, 0);
2560         if (selected) {
2561                 type = LINE_CURSOR;
2562                 wchgat(view->win, -1, 0, type, NULL);
2563         }
2565         attr = get_line_attr(type);
2566         wattrset(view->win, attr);
2568         if (opt_line_number || opt_tab_size < TABSIZE) {
2569                 static char spaces[] = "                    ";
2570                 int col_offset = 0, col = 0;
2572                 if (opt_line_number) {
2573                         unsigned long real_lineno = view->offset + lineno + 1;
2575                         if (real_lineno == 1 ||
2576                             (real_lineno % opt_num_interval) == 0) {
2577                                 wprintw(view->win, "%.*d", view->digits, real_lineno);
2579                         } else {
2580                                 waddnstr(view->win, spaces,
2581                                          MIN(view->digits, STRING_SIZE(spaces)));
2582                         }
2583                         waddstr(view->win, ": ");
2584                         col_offset = view->digits + 2;
2585                 }
2587                 while (text && col_offset + col < view->width) {
2588                         int cols_max = view->width - col_offset - col;
2589                         char *pos = text;
2590                         int cols;
2592                         if (*text == '\t') {
2593                                 text++;
2594                                 assert(sizeof(spaces) > TABSIZE);
2595                                 pos = spaces;
2596                                 cols = opt_tab_size - (col % opt_tab_size);
2598                         } else {
2599                                 text = strchr(text, '\t');
2600                                 cols = line ? text - pos : strlen(pos);
2601                         }
2603                         waddnstr(view->win, pos, MIN(cols, cols_max));
2604                         col += cols;
2605                 }
2607         } else {
2608                 int col = 0, pos = 0;
2610                 for (; pos < textlen && col < view->width; pos++, col++)
2611                         if (text[pos] == '\t')
2612                                 col += TABSIZE - (col % TABSIZE) - 1;
2614                 waddnstr(view->win, text, pos);
2615         }
2617         return TRUE;
2620 static bool
2621 add_describe_ref(char *buf, size_t *bufpos, char *commit_id, const char *sep)
2623         char refbuf[SIZEOF_STR];
2624         char *ref = NULL;
2625         FILE *pipe;
2627         if (!string_format(refbuf, "git describe %s 2>/dev/null", commit_id))
2628                 return TRUE;
2630         pipe = popen(refbuf, "r");
2631         if (!pipe)
2632                 return TRUE;
2634         if ((ref = fgets(refbuf, sizeof(refbuf), pipe)))
2635                 ref = chomp_string(ref);
2636         pclose(pipe);
2638         if (!ref || !*ref)
2639                 return TRUE;
2641         /* This is the only fatal call, since it can "corrupt" the buffer. */
2642         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
2643                 return FALSE;
2645         return TRUE;
2648 static void
2649 add_pager_refs(struct view *view, struct line *line)
2651         char buf[SIZEOF_STR];
2652         char *commit_id = line->data + STRING_SIZE("commit ");
2653         struct ref **refs;
2654         size_t bufpos = 0, refpos = 0;
2655         const char *sep = "Refs: ";
2656         bool is_tag = FALSE;
2658         assert(line->type == LINE_COMMIT);
2660         refs = get_refs(commit_id);
2661         if (!refs) {
2662                 if (view == VIEW(REQ_VIEW_DIFF))
2663                         goto try_add_describe_ref;
2664                 return;
2665         }
2667         do {
2668                 struct ref *ref = refs[refpos];
2669                 char *fmt = ref->tag    ? "%s[%s]" :
2670                             ref->remote ? "%s<%s>" : "%s%s";
2672                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
2673                         return;
2674                 sep = ", ";
2675                 if (ref->tag)
2676                         is_tag = TRUE;
2677         } while (refs[refpos++]->next);
2679         if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
2680 try_add_describe_ref:
2681                 /* Add <tag>-g<commit_id> "fake" reference. */
2682                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
2683                         return;
2684         }
2686         if (bufpos == 0)
2687                 return;
2689         if (!realloc_lines(view, view->line_size + 1))
2690                 return;
2692         add_line_text(view, buf, LINE_PP_REFS);
2695 static bool
2696 pager_read(struct view *view, char *data)
2698         struct line *line;
2700         if (!data)
2701                 return TRUE;
2703         line = add_line_text(view, data, get_line_type(data));
2704         if (!line)
2705                 return FALSE;
2707         if (line->type == LINE_COMMIT &&
2708             (view == VIEW(REQ_VIEW_DIFF) ||
2709              view == VIEW(REQ_VIEW_LOG)))
2710                 add_pager_refs(view, line);
2712         return TRUE;
2715 static enum request
2716 pager_request(struct view *view, enum request request, struct line *line)
2718         int split = 0;
2720         if (request != REQ_ENTER)
2721                 return request;
2723         if (line->type == LINE_COMMIT &&
2724            (view == VIEW(REQ_VIEW_LOG) ||
2725             view == VIEW(REQ_VIEW_PAGER))) {
2726                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
2727                 split = 1;
2728         }
2730         /* Always scroll the view even if it was split. That way
2731          * you can use Enter to scroll through the log view and
2732          * split open each commit diff. */
2733         scroll_view(view, REQ_SCROLL_LINE_DOWN);
2735         /* FIXME: A minor workaround. Scrolling the view will call report("")
2736          * but if we are scrolling a non-current view this won't properly
2737          * update the view title. */
2738         if (split)
2739                 update_view_title(view);
2741         return REQ_NONE;
2744 static bool
2745 pager_grep(struct view *view, struct line *line)
2747         regmatch_t pmatch;
2748         char *text = line->data;
2750         if (!*text)
2751                 return FALSE;
2753         if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
2754                 return FALSE;
2756         return TRUE;
2759 static void
2760 pager_select(struct view *view, struct line *line)
2762         if (line->type == LINE_COMMIT) {
2763                 char *text = line->data + STRING_SIZE("commit ");
2765                 if (view != VIEW(REQ_VIEW_PAGER))
2766                         string_copy_rev(view->ref, text);
2767                 string_copy_rev(ref_commit, text);
2768         }
2771 static struct view_ops pager_ops = {
2772         "line",
2773         NULL,
2774         pager_read,
2775         pager_draw,
2776         pager_request,
2777         pager_grep,
2778         pager_select,
2779 };
2782 /*
2783  * Help backend
2784  */
2786 static bool
2787 help_open(struct view *view)
2789         char buf[BUFSIZ];
2790         int lines = ARRAY_SIZE(req_info) + 2;
2791         int i;
2793         if (view->lines > 0)
2794                 return TRUE;
2796         for (i = 0; i < ARRAY_SIZE(req_info); i++)
2797                 if (!req_info[i].request)
2798                         lines++;
2800         lines += run_requests + 1;
2802         view->line = calloc(lines, sizeof(*view->line));
2803         if (!view->line)
2804                 return FALSE;
2806         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
2808         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
2809                 char *key;
2811                 if (req_info[i].request == REQ_NONE)
2812                         continue;
2814                 if (!req_info[i].request) {
2815                         add_line_text(view, "", LINE_DEFAULT);
2816                         add_line_text(view, req_info[i].help, LINE_DEFAULT);
2817                         continue;
2818                 }
2820                 key = get_key(req_info[i].request);
2821                 if (!*key)
2822                         key = "(no key defined)";
2824                 if (!string_format(buf, "    %-25s %s", key, req_info[i].help))
2825                         continue;
2827                 add_line_text(view, buf, LINE_DEFAULT);
2828         }
2830         if (run_requests) {
2831                 add_line_text(view, "", LINE_DEFAULT);
2832                 add_line_text(view, "External commands:", LINE_DEFAULT);
2833         }
2835         for (i = 0; i < run_requests; i++) {
2836                 struct run_request *req = get_run_request(REQ_NONE + i + 1);
2837                 char *key;
2839                 if (!req)
2840                         continue;
2842                 key = get_key_name(req->key);
2843                 if (!*key)
2844                         key = "(no key defined)";
2846                 if (!string_format(buf, "    %-10s %-14s `%s`",
2847                                    keymap_table[req->keymap].name,
2848                                    key, req->cmd))
2849                         continue;
2851                 add_line_text(view, buf, LINE_DEFAULT);
2852         }
2854         return TRUE;
2857 static struct view_ops help_ops = {
2858         "line",
2859         help_open,
2860         NULL,
2861         pager_draw,
2862         pager_request,
2863         pager_grep,
2864         pager_select,
2865 };
2868 /*
2869  * Tree backend
2870  */
2872 struct tree_stack_entry {
2873         struct tree_stack_entry *prev;  /* Entry below this in the stack */
2874         unsigned long lineno;           /* Line number to restore */
2875         char *name;                     /* Position of name in opt_path */
2876 };
2878 /* The top of the path stack. */
2879 static struct tree_stack_entry *tree_stack = NULL;
2880 unsigned long tree_lineno = 0;
2882 static void
2883 pop_tree_stack_entry(void)
2885         struct tree_stack_entry *entry = tree_stack;
2887         tree_lineno = entry->lineno;
2888         entry->name[0] = 0;
2889         tree_stack = entry->prev;
2890         free(entry);
2893 static void
2894 push_tree_stack_entry(char *name, unsigned long lineno)
2896         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
2897         size_t pathlen = strlen(opt_path);
2899         if (!entry)
2900                 return;
2902         entry->prev = tree_stack;
2903         entry->name = opt_path + pathlen;
2904         tree_stack = entry;
2906         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
2907                 pop_tree_stack_entry();
2908                 return;
2909         }
2911         /* Move the current line to the first tree entry. */
2912         tree_lineno = 1;
2913         entry->lineno = lineno;
2916 /* Parse output from git-ls-tree(1):
2917  *
2918  * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile
2919  * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README
2920  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
2921  * 100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38 web.conf
2922  */
2924 #define SIZEOF_TREE_ATTR \
2925         STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t")
2927 #define TREE_UP_FORMAT "040000 tree %s\t.."
2929 static int
2930 tree_compare_entry(enum line_type type1, char *name1,
2931                    enum line_type type2, char *name2)
2933         if (type1 != type2) {
2934                 if (type1 == LINE_TREE_DIR)
2935                         return -1;
2936                 return 1;
2937         }
2939         return strcmp(name1, name2);
2942 static bool
2943 tree_read(struct view *view, char *text)
2945         size_t textlen = text ? strlen(text) : 0;
2946         char buf[SIZEOF_STR];
2947         unsigned long pos;
2948         enum line_type type;
2949         bool first_read = view->lines == 0;
2951         if (textlen <= SIZEOF_TREE_ATTR)
2952                 return FALSE;
2954         type = text[STRING_SIZE("100644 ")] == 't'
2955              ? LINE_TREE_DIR : LINE_TREE_FILE;
2957         if (first_read) {
2958                 /* Add path info line */
2959                 if (!string_format(buf, "Directory path /%s", opt_path) ||
2960                     !realloc_lines(view, view->line_size + 1) ||
2961                     !add_line_text(view, buf, LINE_DEFAULT))
2962                         return FALSE;
2964                 /* Insert "link" to parent directory. */
2965                 if (*opt_path) {
2966                         if (!string_format(buf, TREE_UP_FORMAT, view->ref) ||
2967                             !realloc_lines(view, view->line_size + 1) ||
2968                             !add_line_text(view, buf, LINE_TREE_DIR))
2969                                 return FALSE;
2970                 }
2971         }
2973         /* Strip the path part ... */
2974         if (*opt_path) {
2975                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
2976                 size_t striplen = strlen(opt_path);
2977                 char *path = text + SIZEOF_TREE_ATTR;
2979                 if (pathlen > striplen)
2980                         memmove(path, path + striplen,
2981                                 pathlen - striplen + 1);
2982         }
2984         /* Skip "Directory ..." and ".." line. */
2985         for (pos = 1 + !!*opt_path; pos < view->lines; pos++) {
2986                 struct line *line = &view->line[pos];
2987                 char *path1 = ((char *) line->data) + SIZEOF_TREE_ATTR;
2988                 char *path2 = text + SIZEOF_TREE_ATTR;
2989                 int cmp = tree_compare_entry(line->type, path1, type, path2);
2991                 if (cmp <= 0)
2992                         continue;
2994                 text = strdup(text);
2995                 if (!text)
2996                         return FALSE;
2998                 if (view->lines > pos)
2999                         memmove(&view->line[pos + 1], &view->line[pos],
3000                                 (view->lines - pos) * sizeof(*line));
3002                 line = &view->line[pos];
3003                 line->data = text;
3004                 line->type = type;
3005                 view->lines++;
3006                 return TRUE;
3007         }
3009         if (!add_line_text(view, text, type))
3010                 return FALSE;
3012         if (tree_lineno > view->lineno) {
3013                 view->lineno = tree_lineno;
3014                 tree_lineno = 0;
3015         }
3017         return TRUE;
3020 static enum request
3021 tree_request(struct view *view, enum request request, struct line *line)
3023         enum open_flags flags;
3025         if (request == REQ_TREE_PARENT) {
3026                 if (*opt_path) {
3027                         /* fake 'cd  ..' */
3028                         request = REQ_ENTER;
3029                         line = &view->line[1];
3030                 } else {
3031                         /* quit view if at top of tree */
3032                         return REQ_VIEW_CLOSE;
3033                 }
3034         }
3035         if (request != REQ_ENTER)
3036                 return request;
3038         /* Cleanup the stack if the tree view is at a different tree. */
3039         while (!*opt_path && tree_stack)
3040                 pop_tree_stack_entry();
3042         switch (line->type) {
3043         case LINE_TREE_DIR:
3044                 /* Depending on whether it is a subdir or parent (updir?) link
3045                  * mangle the path buffer. */
3046                 if (line == &view->line[1] && *opt_path) {
3047                         pop_tree_stack_entry();
3049                 } else {
3050                         char *data = line->data;
3051                         char *basename = data + SIZEOF_TREE_ATTR;
3053                         push_tree_stack_entry(basename, view->lineno);
3054                 }
3056                 /* Trees and subtrees share the same ID, so they are not not
3057                  * unique like blobs. */
3058                 flags = OPEN_RELOAD;
3059                 request = REQ_VIEW_TREE;
3060                 break;
3062         case LINE_TREE_FILE:
3063                 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
3064                 request = REQ_VIEW_BLOB;
3065                 break;
3067         default:
3068                 return TRUE;
3069         }
3071         open_view(view, request, flags);
3072         if (request == REQ_VIEW_TREE) {
3073                 view->lineno = tree_lineno;
3074         }
3076         return REQ_NONE;
3079 static void
3080 tree_select(struct view *view, struct line *line)
3082         char *text = line->data + STRING_SIZE("100644 blob ");
3084         if (line->type == LINE_TREE_FILE) {
3085                 string_copy_rev(ref_blob, text);
3087         } else if (line->type != LINE_TREE_DIR) {
3088                 return;
3089         }
3091         string_copy_rev(view->ref, text);
3094 static struct view_ops tree_ops = {
3095         "file",
3096         NULL,
3097         tree_read,
3098         pager_draw,
3099         tree_request,
3100         pager_grep,
3101         tree_select,
3102 };
3104 static bool
3105 blob_read(struct view *view, char *line)
3107         return add_line_text(view, line, LINE_DEFAULT) != NULL;
3110 static struct view_ops blob_ops = {
3111         "line",
3112         NULL,
3113         blob_read,
3114         pager_draw,
3115         pager_request,
3116         pager_grep,
3117         pager_select,
3118 };
3121 /*
3122  * Status backend
3123  */
3125 struct status {
3126         char status;
3127         struct {
3128                 mode_t mode;
3129                 char rev[SIZEOF_REV];
3130         } old;
3131         struct {
3132                 mode_t mode;
3133                 char rev[SIZEOF_REV];
3134         } new;
3135         char name[SIZEOF_STR];
3136 };
3138 static struct status stage_status;
3139 static enum line_type stage_line_type;
3141 /* Get fields from the diff line:
3142  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
3143  */
3144 static inline bool
3145 status_get_diff(struct status *file, char *buf, size_t bufsize)
3147         char *old_mode = buf +  1;
3148         char *new_mode = buf +  8;
3149         char *old_rev  = buf + 15;
3150         char *new_rev  = buf + 56;
3151         char *status   = buf + 97;
3153         if (bufsize != 99 ||
3154             old_mode[-1] != ':' ||
3155             new_mode[-1] != ' ' ||
3156             old_rev[-1]  != ' ' ||
3157             new_rev[-1]  != ' ' ||
3158             status[-1]   != ' ')
3159                 return FALSE;
3161         file->status = *status;
3163         string_copy_rev(file->old.rev, old_rev);
3164         string_copy_rev(file->new.rev, new_rev);
3166         file->old.mode = strtoul(old_mode, NULL, 8);
3167         file->new.mode = strtoul(new_mode, NULL, 8);
3169         file->name[0] = 0;
3171         return TRUE;
3174 static bool
3175 status_run(struct view *view, const char cmd[], bool diff, enum line_type type)
3177         struct status *file = NULL;
3178         struct status *unmerged = NULL;
3179         char buf[SIZEOF_STR * 4];
3180         size_t bufsize = 0;
3181         FILE *pipe;
3183         pipe = popen(cmd, "r");
3184         if (!pipe)
3185                 return FALSE;
3187         add_line_data(view, NULL, type);
3189         while (!feof(pipe) && !ferror(pipe)) {
3190                 char *sep;
3191                 size_t readsize;
3193                 readsize = fread(buf + bufsize, 1, sizeof(buf) - bufsize, pipe);
3194                 if (!readsize)
3195                         break;
3196                 bufsize += readsize;
3198                 /* Process while we have NUL chars. */
3199                 while ((sep = memchr(buf, 0, bufsize))) {
3200                         size_t sepsize = sep - buf + 1;
3202                         if (!file) {
3203                                 if (!realloc_lines(view, view->line_size + 1))
3204                                         goto error_out;
3206                                 file = calloc(1, sizeof(*file));
3207                                 if (!file)
3208                                         goto error_out;
3210                                 add_line_data(view, file, type);
3211                         }
3213                         /* Parse diff info part. */
3214                         if (!diff) {
3215                                 file->status = '?';
3217                         } else if (!file->status) {
3218                                 if (!status_get_diff(file, buf, sepsize))
3219                                         goto error_out;
3221                                 bufsize -= sepsize;
3222                                 memmove(buf, sep + 1, bufsize);
3224                                 sep = memchr(buf, 0, bufsize);
3225                                 if (!sep)
3226                                         break;
3227                                 sepsize = sep - buf + 1;
3229                                 /* Collapse all 'M'odified entries that
3230                                  * follow a associated 'U'nmerged entry.
3231                                  */
3232                                 if (file->status == 'U') {
3233                                         unmerged = file;
3235                                 } else if (unmerged) {
3236                                         int collapse = !strcmp(buf, unmerged->name);
3238                                         unmerged = NULL;
3239                                         if (collapse) {
3240                                                 free(file);
3241                                                 view->lines--;
3242                                                 continue;
3243                                         }
3244                                 }
3245                         }
3247                         /* git-ls-files just delivers a NUL separated
3248                          * list of file names similar to the second half
3249                          * of the git-diff-* output. */
3250                         string_ncopy(file->name, buf, sepsize);
3251                         bufsize -= sepsize;
3252                         memmove(buf, sep + 1, bufsize);
3253                         file = NULL;
3254                 }
3255         }
3257         if (ferror(pipe)) {
3258 error_out:
3259                 pclose(pipe);
3260                 return FALSE;
3261         }
3263         if (!view->line[view->lines - 1].data)
3264                 add_line_data(view, NULL, LINE_STAT_NONE);
3266         pclose(pipe);
3267         return TRUE;
3270 /* Don't show unmerged entries in the staged section. */
3271 #define STATUS_DIFF_INDEX_CMD "git diff-index -z --diff-filter=ACDMRTXB --cached HEAD"
3272 #define STATUS_DIFF_FILES_CMD "git diff-files -z"
3273 #define STATUS_LIST_OTHER_CMD \
3274         "git ls-files -z --others --exclude-per-directory=.gitignore"
3276 #define STATUS_DIFF_INDEX_SHOW_CMD \
3277         "git diff-index --root --patch-with-stat --find-copies-harder -B -C --cached HEAD -- %s 2>/dev/null"
3279 #define STATUS_DIFF_FILES_SHOW_CMD \
3280         "git diff-files --root --patch-with-stat --find-copies-harder -B -C -- %s 2>/dev/null"
3282 /* First parse staged info using git-diff-index(1), then parse unstaged
3283  * info using git-diff-files(1), and finally untracked files using
3284  * git-ls-files(1). */
3285 static bool
3286 status_open(struct view *view)
3288         struct stat statbuf;
3289         char exclude[SIZEOF_STR];
3290         char cmd[SIZEOF_STR];
3291         unsigned long prev_lineno = view->lineno;
3292         size_t i;
3294         for (i = 0; i < view->lines; i++)
3295                 free(view->line[i].data);
3296         free(view->line);
3297         view->lines = view->line_size = view->lineno = 0;
3298         view->line = NULL;
3300         if (!realloc_lines(view, view->line_size + 6))
3301                 return FALSE;
3303         if (!string_format(exclude, "%s/info/exclude", opt_git_dir))
3304                 return FALSE;
3306         string_copy(cmd, STATUS_LIST_OTHER_CMD);
3308         if (stat(exclude, &statbuf) >= 0) {
3309                 size_t cmdsize = strlen(cmd);
3311                 if (!string_format_from(cmd, &cmdsize, " %s", "--exclude-from=") ||
3312                     sq_quote(cmd, cmdsize, exclude) >= sizeof(cmd))
3313                         return FALSE;
3314         }
3316         if (!status_run(view, STATUS_DIFF_INDEX_CMD, TRUE, LINE_STAT_STAGED) ||
3317             !status_run(view, STATUS_DIFF_FILES_CMD, TRUE, LINE_STAT_UNSTAGED) ||
3318             !status_run(view, cmd, FALSE, LINE_STAT_UNTRACKED))
3319                 return FALSE;
3321         /* If all went well restore the previous line number to stay in
3322          * the context. */
3323         if (prev_lineno < view->lines)
3324                 view->lineno = prev_lineno;
3325         else
3326                 view->lineno = view->lines - 1;
3328         return TRUE;
3331 static bool
3332 status_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
3334         struct status *status = line->data;
3336         wmove(view->win, lineno, 0);
3338         if (selected) {
3339                 wattrset(view->win, get_line_attr(LINE_CURSOR));
3340                 wchgat(view->win, -1, 0, LINE_CURSOR, NULL);
3342         } else if (!status && line->type != LINE_STAT_NONE) {
3343                 wattrset(view->win, get_line_attr(LINE_STAT_SECTION));
3344                 wchgat(view->win, -1, 0, LINE_STAT_SECTION, NULL);
3346         } else {
3347                 wattrset(view->win, get_line_attr(line->type));
3348         }
3350         if (!status) {
3351                 char *text;
3353                 switch (line->type) {
3354                 case LINE_STAT_STAGED:
3355                         text = "Changes to be committed:";
3356                         break;
3358                 case LINE_STAT_UNSTAGED:
3359                         text = "Changed but not updated:";
3360                         break;
3362                 case LINE_STAT_UNTRACKED:
3363                         text = "Untracked files:";
3364                         break;
3366                 case LINE_STAT_NONE:
3367                         text = "    (no files)";
3368                         break;
3370                 default:
3371                         return FALSE;
3372                 }
3374                 waddstr(view->win, text);
3375                 return TRUE;
3376         }
3378         waddch(view->win, status->status);
3379         if (!selected)
3380                 wattrset(view->win, A_NORMAL);
3381         wmove(view->win, lineno, 4);
3382         waddstr(view->win, status->name);
3384         return TRUE;
3387 static enum request
3388 status_enter(struct view *view, struct line *line)
3390         struct status *status = line->data;
3391         char path[SIZEOF_STR] = "";
3392         char *info;
3393         size_t cmdsize = 0;
3395         if (line->type == LINE_STAT_NONE ||
3396             (!status && line[1].type == LINE_STAT_NONE)) {
3397                 report("No file to diff");
3398                 return REQ_NONE;
3399         }
3401         if (status && sq_quote(path, 0, status->name) >= sizeof(path))
3402                 return REQ_QUIT;
3404         if (opt_cdup[0] &&
3405             line->type != LINE_STAT_UNTRACKED &&
3406             !string_format_from(opt_cmd, &cmdsize, "cd %s;", opt_cdup))
3407                 return REQ_QUIT;
3409         switch (line->type) {
3410         case LINE_STAT_STAGED:
3411                 if (!string_format_from(opt_cmd, &cmdsize,
3412                                         STATUS_DIFF_INDEX_SHOW_CMD, path))
3413                         return REQ_QUIT;
3414                 if (status)
3415                         info = "Staged changes to %s";
3416                 else
3417                         info = "Staged changes";
3418                 break;
3420         case LINE_STAT_UNSTAGED:
3421                 if (!string_format_from(opt_cmd, &cmdsize,
3422                                         STATUS_DIFF_FILES_SHOW_CMD, path))
3423                         return REQ_QUIT;
3424                 if (status)
3425                         info = "Unstaged changes to %s";
3426                 else
3427                         info = "Unstaged changes";
3428                 break;
3430         case LINE_STAT_UNTRACKED:
3431                 if (opt_pipe)
3432                         return REQ_QUIT;
3435                 if (!status) {
3436                         report("No file to show");
3437                         return REQ_NONE;
3438                 }
3440                 opt_pipe = fopen(status->name, "r");
3441                 info = "Untracked file %s";
3442                 break;
3444         default:
3445                 die("line type %d not handled in switch", line->type);
3446         }
3448         open_view(view, REQ_VIEW_STAGE, OPEN_RELOAD | OPEN_SPLIT);
3449         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
3450                 if (status) {
3451                         stage_status = *status;
3452                 } else {
3453                         memset(&stage_status, 0, sizeof(stage_status));
3454                 }
3456                 stage_line_type = line->type;
3457                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.name);
3458         }
3460         return REQ_NONE;
3464 static bool
3465 status_update_file(struct view *view, struct status *status, enum line_type type)
3467         char cmd[SIZEOF_STR];
3468         char buf[SIZEOF_STR];
3469         size_t cmdsize = 0;
3470         size_t bufsize = 0;
3471         size_t written = 0;
3472         FILE *pipe;
3474         if (opt_cdup[0] &&
3475             type != LINE_STAT_UNTRACKED &&
3476             !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
3477                 return FALSE;
3479         switch (type) {
3480         case LINE_STAT_STAGED:
3481                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
3482                                         status->old.mode,
3483                                         status->old.rev,
3484                                         status->name, 0))
3485                         return FALSE;
3487                 string_add(cmd, cmdsize, "git update-index -z --index-info");
3488                 break;
3490         case LINE_STAT_UNSTAGED:
3491         case LINE_STAT_UNTRACKED:
3492                 if (!string_format_from(buf, &bufsize, "%s%c", status->name, 0))
3493                         return FALSE;
3495                 string_add(cmd, cmdsize, "git update-index -z --add --remove --stdin");
3496                 break;
3498         default:
3499                 die("line type %d not handled in switch", type);
3500         }
3502         pipe = popen(cmd, "w");
3503         if (!pipe)
3504                 return FALSE;
3506         while (!ferror(pipe) && written < bufsize) {
3507                 written += fwrite(buf + written, 1, bufsize - written, pipe);
3508         }
3510         pclose(pipe);
3512         if (written != bufsize)
3513                 return FALSE;
3515         return TRUE;
3518 static void
3519 status_update(struct view *view)
3521         struct line *line = &view->line[view->lineno];
3523         assert(view->lines);
3525         if (!line->data) {
3526                 while (++line < view->line + view->lines && line->data) {
3527                         if (!status_update_file(view, line->data, line->type))
3528                                 report("Failed to update file status");
3529                 }
3531                 if (!line[-1].data) {
3532                         report("Nothing to update");
3533                         return;
3534                 }
3536         } else if (!status_update_file(view, line->data, line->type)) {
3537                 report("Failed to update file status");
3538         }
3541 static enum request
3542 status_request(struct view *view, enum request request, struct line *line)
3544         struct status *status = line->data;
3546         switch (request) {
3547         case REQ_STATUS_UPDATE:
3548                 status_update(view);
3549                 break;
3551         case REQ_STATUS_MERGE:
3552                 if (!status || status->status != 'U') {
3553                         report("Merging only possible for files with unmerged status ('U').");
3554                         return REQ_NONE;
3555                 }
3556                 open_mergetool(status->name);
3557                 break;
3559         case REQ_EDIT:
3560                 if (!status)
3561                         return request;
3563                 open_editor(status->status != '?', status->name);
3564                 break;
3566         case REQ_ENTER:
3567                 /* After returning the status view has been split to
3568                  * show the stage view. No further reloading is
3569                  * necessary. */
3570                 status_enter(view, line);
3571                 return REQ_NONE;
3573         case REQ_REFRESH:
3574                 /* Simply reload the view. */
3575                 break;
3577         default:
3578                 return request;
3579         }
3581         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
3583         return REQ_NONE;
3586 static void
3587 status_select(struct view *view, struct line *line)
3589         struct status *status = line->data;
3590         char file[SIZEOF_STR] = "all files";
3591         char *text;
3592         char *key;
3594         if (status && !string_format(file, "'%s'", status->name))
3595                 return;
3597         if (!status && line[1].type == LINE_STAT_NONE)
3598                 line++;
3600         switch (line->type) {
3601         case LINE_STAT_STAGED:
3602                 text = "Press %s to unstage %s for commit";
3603                 break;
3605         case LINE_STAT_UNSTAGED:
3606                 text = "Press %s to stage %s for commit";
3607                 break;
3609         case LINE_STAT_UNTRACKED:
3610                 text = "Press %s to stage %s for addition";
3611                 break;
3613         case LINE_STAT_NONE:
3614                 text = "Nothing to update";
3615                 break;
3617         default:
3618                 die("line type %d not handled in switch", line->type);
3619         }
3621         if (status && status->status == 'U') {
3622                 text = "Press %s to resolve conflict in %s";
3623                 key = get_key(REQ_STATUS_MERGE);
3625         } else {
3626                 key = get_key(REQ_STATUS_UPDATE);
3627         }
3629         string_format(view->ref, text, key, file);
3632 static bool
3633 status_grep(struct view *view, struct line *line)
3635         struct status *status = line->data;
3636         enum { S_STATUS, S_NAME, S_END } state;
3637         char buf[2] = "?";
3638         regmatch_t pmatch;
3640         if (!status)
3641                 return FALSE;
3643         for (state = S_STATUS; state < S_END; state++) {
3644                 char *text;
3646                 switch (state) {
3647                 case S_NAME:    text = status->name;    break;
3648                 case S_STATUS:
3649                         buf[0] = status->status;
3650                         text = buf;
3651                         break;
3653                 default:
3654                         return FALSE;
3655                 }
3657                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
3658                         return TRUE;
3659         }
3661         return FALSE;
3664 static struct view_ops status_ops = {
3665         "file",
3666         status_open,
3667         NULL,
3668         status_draw,
3669         status_request,
3670         status_grep,
3671         status_select,
3672 };
3675 static bool
3676 stage_diff_line(FILE *pipe, struct line *line)
3678         char *buf = line->data;
3679         size_t bufsize = strlen(buf);
3680         size_t written = 0;
3682         while (!ferror(pipe) && written < bufsize) {
3683                 written += fwrite(buf + written, 1, bufsize - written, pipe);
3684         }
3686         fputc('\n', pipe);
3688         return written == bufsize;
3691 static struct line *
3692 stage_diff_hdr(struct view *view, struct line *line)
3694         int diff_hdr_dir = line->type == LINE_DIFF_CHUNK ? -1 : 1;
3695         struct line *diff_hdr;
3697         if (line->type == LINE_DIFF_CHUNK)
3698                 diff_hdr = line - 1;
3699         else
3700                 diff_hdr = view->line + 1;
3702         while (diff_hdr > view->line && diff_hdr < view->line + view->lines) {
3703                 if (diff_hdr->type == LINE_DIFF_HEADER)
3704                         return diff_hdr;
3706                 diff_hdr += diff_hdr_dir;
3707         }
3709         return NULL;
3712 static bool
3713 stage_update_chunk(struct view *view, struct line *line)
3715         char cmd[SIZEOF_STR];
3716         size_t cmdsize = 0;
3717         struct line *diff_hdr, *diff_chunk, *diff_end;
3718         FILE *pipe;
3720         diff_hdr = stage_diff_hdr(view, line);
3721         if (!diff_hdr)
3722                 return FALSE;
3724         if (opt_cdup[0] &&
3725             !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
3726                 return FALSE;
3728         if (!string_format_from(cmd, &cmdsize,
3729                                 "git apply --cached %s - && "
3730                                 "git update-index -q --unmerged --refresh 2>/dev/null",
3731                                 stage_line_type == LINE_STAT_STAGED ? "-R" : ""))
3732                 return FALSE;
3734         pipe = popen(cmd, "w");
3735         if (!pipe)
3736                 return FALSE;
3738         diff_end = view->line + view->lines;
3739         if (line->type != LINE_DIFF_CHUNK) {
3740                 diff_chunk = diff_hdr;
3742         } else {
3743                 for (diff_chunk = line + 1; diff_chunk < diff_end; diff_chunk++)
3744                         if (diff_chunk->type == LINE_DIFF_CHUNK ||
3745                             diff_chunk->type == LINE_DIFF_HEADER)
3746                                 diff_end = diff_chunk;
3748                 diff_chunk = line;
3750                 while (diff_hdr->type != LINE_DIFF_CHUNK) {
3751                         switch (diff_hdr->type) {
3752                         case LINE_DIFF_HEADER:
3753                         case LINE_DIFF_INDEX:
3754                         case LINE_DIFF_ADD:
3755                         case LINE_DIFF_DEL:
3756                                 break;
3758                         default:
3759                                 diff_hdr++;
3760                                 continue;
3761                         }
3763                         if (!stage_diff_line(pipe, diff_hdr++)) {
3764                                 pclose(pipe);
3765                                 return FALSE;
3766                         }
3767                 }
3768         }
3770         while (diff_chunk < diff_end && stage_diff_line(pipe, diff_chunk))
3771                 diff_chunk++;
3773         pclose(pipe);
3775         if (diff_chunk != diff_end)
3776                 return FALSE;
3778         return TRUE;
3781 static void
3782 stage_update(struct view *view, struct line *line)
3784         if (stage_line_type != LINE_STAT_UNTRACKED &&
3785             (line->type == LINE_DIFF_CHUNK || !stage_status.status)) {
3786                 if (!stage_update_chunk(view, line)) {
3787                         report("Failed to apply chunk");
3788                         return;
3789                 }
3791         } else if (!status_update_file(view, &stage_status, stage_line_type)) {
3792                 report("Failed to update file");
3793                 return;
3794         }
3796         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
3798         view = VIEW(REQ_VIEW_STATUS);
3799         if (view_is_displayed(view))
3800                 status_enter(view, &view->line[view->lineno]);
3803 static enum request
3804 stage_request(struct view *view, enum request request, struct line *line)
3806         switch (request) {
3807         case REQ_STATUS_UPDATE:
3808                 stage_update(view, line);
3809                 break;
3811         case REQ_EDIT:
3812                 if (!stage_status.name[0])
3813                         return request;
3815                 open_editor(stage_status.status != '?', stage_status.name);
3816                 break;
3818         case REQ_ENTER:
3819                 pager_request(view, request, line);
3820                 break;
3822         default:
3823                 return request;
3824         }
3826         return REQ_NONE;
3829 static struct view_ops stage_ops = {
3830         "line",
3831         NULL,
3832         pager_read,
3833         pager_draw,
3834         stage_request,
3835         pager_grep,
3836         pager_select,
3837 };
3840 /*
3841  * Revision graph
3842  */
3844 struct commit {
3845         char id[SIZEOF_REV];            /* SHA1 ID. */
3846         char title[128];                /* First line of the commit message. */
3847         char author[75];                /* Author of the commit. */
3848         struct tm time;                 /* Date from the author ident. */
3849         struct ref **refs;              /* Repository references. */
3850         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
3851         size_t graph_size;              /* The width of the graph array. */
3852 };
3854 /* Size of rev graph with no  "padding" columns */
3855 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
3857 struct rev_graph {
3858         struct rev_graph *prev, *next, *parents;
3859         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
3860         size_t size;
3861         struct commit *commit;
3862         size_t pos;
3863 };
3865 /* Parents of the commit being visualized. */
3866 static struct rev_graph graph_parents[4];
3868 /* The current stack of revisions on the graph. */
3869 static struct rev_graph graph_stacks[4] = {
3870         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
3871         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
3872         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
3873         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
3874 };
3876 static inline bool
3877 graph_parent_is_merge(struct rev_graph *graph)
3879         return graph->parents->size > 1;
3882 static inline void
3883 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
3885         struct commit *commit = graph->commit;
3887         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
3888                 commit->graph[commit->graph_size++] = symbol;
3891 static void
3892 done_rev_graph(struct rev_graph *graph)
3894         if (graph_parent_is_merge(graph) &&
3895             graph->pos < graph->size - 1 &&
3896             graph->next->size == graph->size + graph->parents->size - 1) {
3897                 size_t i = graph->pos + graph->parents->size - 1;
3899                 graph->commit->graph_size = i * 2;
3900                 while (i < graph->next->size - 1) {
3901                         append_to_rev_graph(graph, ' ');
3902                         append_to_rev_graph(graph, '\\');
3903                         i++;
3904                 }
3905         }
3907         graph->size = graph->pos = 0;
3908         graph->commit = NULL;
3909         memset(graph->parents, 0, sizeof(*graph->parents));
3912 static void
3913 push_rev_graph(struct rev_graph *graph, char *parent)
3915         int i;
3917         /* "Collapse" duplicate parents lines.
3918          *
3919          * FIXME: This needs to also update update the drawn graph but
3920          * for now it just serves as a method for pruning graph lines. */
3921         for (i = 0; i < graph->size; i++)
3922                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
3923                         return;
3925         if (graph->size < SIZEOF_REVITEMS) {
3926                 string_copy_rev(graph->rev[graph->size++], parent);
3927         }
3930 static chtype
3931 get_rev_graph_symbol(struct rev_graph *graph)
3933         chtype symbol;
3935         if (graph->parents->size == 0)
3936                 symbol = REVGRAPH_INIT;
3937         else if (graph_parent_is_merge(graph))
3938                 symbol = REVGRAPH_MERGE;
3939         else if (graph->pos >= graph->size)
3940                 symbol = REVGRAPH_BRANCH;
3941         else
3942                 symbol = REVGRAPH_COMMIT;
3944         return symbol;
3947 static void
3948 draw_rev_graph(struct rev_graph *graph)
3950         struct rev_filler {
3951                 chtype separator, line;
3952         };
3953         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
3954         static struct rev_filler fillers[] = {
3955                 { ' ',  REVGRAPH_LINE },
3956                 { '`',  '.' },
3957                 { '\'', ' ' },
3958                 { '/',  ' ' },
3959         };
3960         chtype symbol = get_rev_graph_symbol(graph);
3961         struct rev_filler *filler;
3962         size_t i;
3964         filler = &fillers[DEFAULT];
3966         for (i = 0; i < graph->pos; i++) {
3967                 append_to_rev_graph(graph, filler->line);
3968                 if (graph_parent_is_merge(graph->prev) &&
3969                     graph->prev->pos == i)
3970                         filler = &fillers[RSHARP];
3972                 append_to_rev_graph(graph, filler->separator);
3973         }
3975         /* Place the symbol for this revision. */
3976         append_to_rev_graph(graph, symbol);
3978         if (graph->prev->size > graph->size)
3979                 filler = &fillers[RDIAG];
3980         else
3981                 filler = &fillers[DEFAULT];
3983         i++;
3985         for (; i < graph->size; i++) {
3986                 append_to_rev_graph(graph, filler->separator);
3987                 append_to_rev_graph(graph, filler->line);
3988                 if (graph_parent_is_merge(graph->prev) &&
3989                     i < graph->prev->pos + graph->parents->size)
3990                         filler = &fillers[RSHARP];
3991                 if (graph->prev->size > graph->size)
3992                         filler = &fillers[LDIAG];
3993         }
3995         if (graph->prev->size > graph->size) {
3996                 append_to_rev_graph(graph, filler->separator);
3997                 if (filler->line != ' ')
3998                         append_to_rev_graph(graph, filler->line);
3999         }
4002 /* Prepare the next rev graph */
4003 static void
4004 prepare_rev_graph(struct rev_graph *graph)
4006         size_t i;
4008         /* First, traverse all lines of revisions up to the active one. */
4009         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
4010                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
4011                         break;
4013                 push_rev_graph(graph->next, graph->rev[graph->pos]);
4014         }
4016         /* Interleave the new revision parent(s). */
4017         for (i = 0; i < graph->parents->size; i++)
4018                 push_rev_graph(graph->next, graph->parents->rev[i]);
4020         /* Lastly, put any remaining revisions. */
4021         for (i = graph->pos + 1; i < graph->size; i++)
4022                 push_rev_graph(graph->next, graph->rev[i]);
4025 static void
4026 update_rev_graph(struct rev_graph *graph)
4028         /* If this is the finalizing update ... */
4029         if (graph->commit)
4030                 prepare_rev_graph(graph);
4032         /* Graph visualization needs a one rev look-ahead,
4033          * so the first update doesn't visualize anything. */
4034         if (!graph->prev->commit)
4035                 return;
4037         draw_rev_graph(graph->prev);
4038         done_rev_graph(graph->prev->prev);
4042 /*
4043  * Main view backend
4044  */
4046 static bool
4047 main_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
4049         char buf[DATE_COLS + 1];
4050         struct commit *commit = line->data;
4051         enum line_type type;
4052         int col = 0;
4053         size_t timelen;
4054         size_t authorlen;
4055         int trimmed = 1;
4057         if (!*commit->author)
4058                 return FALSE;
4060         wmove(view->win, lineno, col);
4062         if (selected) {
4063                 type = LINE_CURSOR;
4064                 wattrset(view->win, get_line_attr(type));
4065                 wchgat(view->win, -1, 0, type, NULL);
4067         } else {
4068                 type = LINE_MAIN_COMMIT;
4069                 wattrset(view->win, get_line_attr(LINE_MAIN_DATE));
4070         }
4072         timelen = strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time);
4073         waddnstr(view->win, buf, timelen);
4074         waddstr(view->win, " ");
4076         col += DATE_COLS;
4077         wmove(view->win, lineno, col);
4078         if (type != LINE_CURSOR)
4079                 wattrset(view->win, get_line_attr(LINE_MAIN_AUTHOR));
4081         if (opt_utf8) {
4082                 authorlen = utf8_length(commit->author, AUTHOR_COLS - 2, &col, &trimmed);
4083         } else {
4084                 authorlen = strlen(commit->author);
4085                 if (authorlen > AUTHOR_COLS - 2) {
4086                         authorlen = AUTHOR_COLS - 2;
4087                         trimmed = 1;
4088                 }
4089         }
4091         if (trimmed) {
4092                 waddnstr(view->win, commit->author, authorlen);
4093                 if (type != LINE_CURSOR)
4094                         wattrset(view->win, get_line_attr(LINE_MAIN_DELIM));
4095                 waddch(view->win, '~');
4096         } else {
4097                 waddstr(view->win, commit->author);
4098         }
4100         col += AUTHOR_COLS;
4102         if (opt_rev_graph && commit->graph_size) {
4103                 size_t i;
4105                 if (type != LINE_CURSOR)
4106                         wattrset(view->win, get_line_attr(LINE_MAIN_REVGRAPH));
4107                 wmove(view->win, lineno, col);
4108                 /* Using waddch() instead of waddnstr() ensures that
4109                  * they'll be rendered correctly for the cursor line. */
4110                 for (i = 0; i < commit->graph_size; i++)
4111                         waddch(view->win, commit->graph[i]);
4113                 waddch(view->win, ' ');
4114                 col += commit->graph_size + 1;
4115         }
4116         if (type != LINE_CURSOR)
4117                 wattrset(view->win, A_NORMAL);
4119         wmove(view->win, lineno, col);
4121         if (commit->refs) {
4122                 size_t i = 0;
4124                 do {
4125                         if (type == LINE_CURSOR)
4126                                 ;
4127                         else if (commit->refs[i]->tag)
4128                                 wattrset(view->win, get_line_attr(LINE_MAIN_TAG));
4129                         else if (commit->refs[i]->remote)
4130                                 wattrset(view->win, get_line_attr(LINE_MAIN_REMOTE));
4131                         else
4132                                 wattrset(view->win, get_line_attr(LINE_MAIN_REF));
4133                         waddstr(view->win, "[");
4134                         waddstr(view->win, commit->refs[i]->name);
4135                         waddstr(view->win, "]");
4136                         if (type != LINE_CURSOR)
4137                                 wattrset(view->win, A_NORMAL);
4138                         waddstr(view->win, " ");
4139                         col += strlen(commit->refs[i]->name) + STRING_SIZE("[] ");
4140                 } while (commit->refs[i++]->next);
4141         }
4143         if (type != LINE_CURSOR)
4144                 wattrset(view->win, get_line_attr(type));
4146         {
4147                 int titlelen = strlen(commit->title);
4149                 if (col + titlelen > view->width)
4150                         titlelen = view->width - col;
4152                 waddnstr(view->win, commit->title, titlelen);
4153         }
4155         return TRUE;
4158 /* Reads git log --pretty=raw output and parses it into the commit struct. */
4159 static bool
4160 main_read(struct view *view, char *line)
4162         static struct rev_graph *graph = graph_stacks;
4163         enum line_type type;
4164         struct commit *commit;
4166         if (!line) {
4167                 update_rev_graph(graph);
4168                 return TRUE;
4169         }
4171         type = get_line_type(line);
4172         if (type == LINE_COMMIT) {
4173                 commit = calloc(1, sizeof(struct commit));
4174                 if (!commit)
4175                         return FALSE;
4177                 string_copy_rev(commit->id, line + STRING_SIZE("commit "));
4178                 commit->refs = get_refs(commit->id);
4179                 graph->commit = commit;
4180                 add_line_data(view, commit, LINE_MAIN_COMMIT);
4181                 return TRUE;
4182         }
4184         if (!view->lines)
4185                 return TRUE;
4186         commit = view->line[view->lines - 1].data;
4188         switch (type) {
4189         case LINE_PARENT:
4190                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
4191                 break;
4193         case LINE_AUTHOR:
4194         {
4195                 /* Parse author lines where the name may be empty:
4196                  *      author  <email@address.tld> 1138474660 +0100
4197                  */
4198                 char *ident = line + STRING_SIZE("author ");
4199                 char *nameend = strchr(ident, '<');
4200                 char *emailend = strchr(ident, '>');
4202                 if (!nameend || !emailend)
4203                         break;
4205                 update_rev_graph(graph);
4206                 graph = graph->next;
4208                 *nameend = *emailend = 0;
4209                 ident = chomp_string(ident);
4210                 if (!*ident) {
4211                         ident = chomp_string(nameend + 1);
4212                         if (!*ident)
4213                                 ident = "Unknown";
4214                 }
4216                 string_ncopy(commit->author, ident, strlen(ident));
4218                 /* Parse epoch and timezone */
4219                 if (emailend[1] == ' ') {
4220                         char *secs = emailend + 2;
4221                         char *zone = strchr(secs, ' ');
4222                         time_t time = (time_t) atol(secs);
4224                         if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
4225                                 long tz;
4227                                 zone++;
4228                                 tz  = ('0' - zone[1]) * 60 * 60 * 10;
4229                                 tz += ('0' - zone[2]) * 60 * 60;
4230                                 tz += ('0' - zone[3]) * 60;
4231                                 tz += ('0' - zone[4]) * 60;
4233                                 if (zone[0] == '-')
4234                                         tz = -tz;
4236                                 time -= tz;
4237                         }
4239                         gmtime_r(&time, &commit->time);
4240                 }
4241                 break;
4242         }
4243         default:
4244                 /* Fill in the commit title if it has not already been set. */
4245                 if (commit->title[0])
4246                         break;
4248                 /* Require titles to start with a non-space character at the
4249                  * offset used by git log. */
4250                 if (strncmp(line, "    ", 4))
4251                         break;
4252                 line += 4;
4253                 /* Well, if the title starts with a whitespace character,
4254                  * try to be forgiving.  Otherwise we end up with no title. */
4255                 while (isspace(*line))
4256                         line++;
4257                 if (*line == '\0')
4258                         break;
4259                 /* FIXME: More graceful handling of titles; append "..." to
4260                  * shortened titles, etc. */
4262                 string_ncopy(commit->title, line, strlen(line));
4263         }
4265         return TRUE;
4268 static enum request
4269 main_request(struct view *view, enum request request, struct line *line)
4271         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4273         if (request == REQ_ENTER)
4274                 open_view(view, REQ_VIEW_DIFF, flags);
4275         else
4276                 return request;
4278         return REQ_NONE;
4281 static bool
4282 main_grep(struct view *view, struct line *line)
4284         struct commit *commit = line->data;
4285         enum { S_TITLE, S_AUTHOR, S_DATE, S_END } state;
4286         char buf[DATE_COLS + 1];
4287         regmatch_t pmatch;
4289         for (state = S_TITLE; state < S_END; state++) {
4290                 char *text;
4292                 switch (state) {
4293                 case S_TITLE:   text = commit->title;   break;
4294                 case S_AUTHOR:  text = commit->author;  break;
4295                 case S_DATE:
4296                         if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
4297                                 continue;
4298                         text = buf;
4299                         break;
4301                 default:
4302                         return FALSE;
4303                 }
4305                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4306                         return TRUE;
4307         }
4309         return FALSE;
4312 static void
4313 main_select(struct view *view, struct line *line)
4315         struct commit *commit = line->data;
4317         string_copy_rev(view->ref, commit->id);
4318         string_copy_rev(ref_commit, view->ref);
4321 static struct view_ops main_ops = {
4322         "commit",
4323         NULL,
4324         main_read,
4325         main_draw,
4326         main_request,
4327         main_grep,
4328         main_select,
4329 };
4332 /*
4333  * Unicode / UTF-8 handling
4334  *
4335  * NOTE: Much of the following code for dealing with unicode is derived from
4336  * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
4337  * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
4338  */
4340 /* I've (over)annotated a lot of code snippets because I am not entirely
4341  * confident that the approach taken by this small UTF-8 interface is correct.
4342  * --jonas */
4344 static inline int
4345 unicode_width(unsigned long c)
4347         if (c >= 0x1100 &&
4348            (c <= 0x115f                         /* Hangul Jamo */
4349             || c == 0x2329
4350             || c == 0x232a
4351             || (c >= 0x2e80  && c <= 0xa4cf && c != 0x303f)
4352                                                 /* CJK ... Yi */
4353             || (c >= 0xac00  && c <= 0xd7a3)    /* Hangul Syllables */
4354             || (c >= 0xf900  && c <= 0xfaff)    /* CJK Compatibility Ideographs */
4355             || (c >= 0xfe30  && c <= 0xfe6f)    /* CJK Compatibility Forms */
4356             || (c >= 0xff00  && c <= 0xff60)    /* Fullwidth Forms */
4357             || (c >= 0xffe0  && c <= 0xffe6)
4358             || (c >= 0x20000 && c <= 0x2fffd)
4359             || (c >= 0x30000 && c <= 0x3fffd)))
4360                 return 2;
4362         return 1;
4365 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
4366  * Illegal bytes are set one. */
4367 static const unsigned char utf8_bytes[256] = {
4368         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,
4369         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,
4370         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,
4371         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,
4372         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,
4373         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,
4374         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,
4375         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,
4376 };
4378 /* Decode UTF-8 multi-byte representation into a unicode character. */
4379 static inline unsigned long
4380 utf8_to_unicode(const char *string, size_t length)
4382         unsigned long unicode;
4384         switch (length) {
4385         case 1:
4386                 unicode  =   string[0];
4387                 break;
4388         case 2:
4389                 unicode  =  (string[0] & 0x1f) << 6;
4390                 unicode +=  (string[1] & 0x3f);
4391                 break;
4392         case 3:
4393                 unicode  =  (string[0] & 0x0f) << 12;
4394                 unicode += ((string[1] & 0x3f) << 6);
4395                 unicode +=  (string[2] & 0x3f);
4396                 break;
4397         case 4:
4398                 unicode  =  (string[0] & 0x0f) << 18;
4399                 unicode += ((string[1] & 0x3f) << 12);
4400                 unicode += ((string[2] & 0x3f) << 6);
4401                 unicode +=  (string[3] & 0x3f);
4402                 break;
4403         case 5:
4404                 unicode  =  (string[0] & 0x0f) << 24;
4405                 unicode += ((string[1] & 0x3f) << 18);
4406                 unicode += ((string[2] & 0x3f) << 12);
4407                 unicode += ((string[3] & 0x3f) << 6);
4408                 unicode +=  (string[4] & 0x3f);
4409                 break;
4410         case 6:
4411                 unicode  =  (string[0] & 0x01) << 30;
4412                 unicode += ((string[1] & 0x3f) << 24);
4413                 unicode += ((string[2] & 0x3f) << 18);
4414                 unicode += ((string[3] & 0x3f) << 12);
4415                 unicode += ((string[4] & 0x3f) << 6);
4416                 unicode +=  (string[5] & 0x3f);
4417                 break;
4418         default:
4419                 die("Invalid unicode length");
4420         }
4422         /* Invalid characters could return the special 0xfffd value but NUL
4423          * should be just as good. */
4424         return unicode > 0xffff ? 0 : unicode;
4427 /* Calculates how much of string can be shown within the given maximum width
4428  * and sets trimmed parameter to non-zero value if all of string could not be
4429  * shown.
4430  *
4431  * Additionally, adds to coloffset how many many columns to move to align with
4432  * the expected position. Takes into account how multi-byte and double-width
4433  * characters will effect the cursor position.
4434  *
4435  * Returns the number of bytes to output from string to satisfy max_width. */
4436 static size_t
4437 utf8_length(const char *string, size_t max_width, int *coloffset, int *trimmed)
4439         const char *start = string;
4440         const char *end = strchr(string, '\0');
4441         size_t mbwidth = 0;
4442         size_t width = 0;
4444         *trimmed = 0;
4446         while (string < end) {
4447                 int c = *(unsigned char *) string;
4448                 unsigned char bytes = utf8_bytes[c];
4449                 size_t ucwidth;
4450                 unsigned long unicode;
4452                 if (string + bytes > end)
4453                         break;
4455                 /* Change representation to figure out whether
4456                  * it is a single- or double-width character. */
4458                 unicode = utf8_to_unicode(string, bytes);
4459                 /* FIXME: Graceful handling of invalid unicode character. */
4460                 if (!unicode)
4461                         break;
4463                 ucwidth = unicode_width(unicode);
4464                 width  += ucwidth;
4465                 if (width > max_width) {
4466                         *trimmed = 1;
4467                         break;
4468                 }
4470                 /* The column offset collects the differences between the
4471                  * number of bytes encoding a character and the number of
4472                  * columns will be used for rendering said character.
4473                  *
4474                  * So if some character A is encoded in 2 bytes, but will be
4475                  * represented on the screen using only 1 byte this will and up
4476                  * adding 1 to the multi-byte column offset.
4477                  *
4478                  * Assumes that no double-width character can be encoding in
4479                  * less than two bytes. */
4480                 if (bytes > ucwidth)
4481                         mbwidth += bytes - ucwidth;
4483                 string  += bytes;
4484         }
4486         *coloffset += mbwidth;
4488         return string - start;
4492 /*
4493  * Status management
4494  */
4496 /* Whether or not the curses interface has been initialized. */
4497 static bool cursed = FALSE;
4499 /* The status window is used for polling keystrokes. */
4500 static WINDOW *status_win;
4502 static bool status_empty = TRUE;
4504 /* Update status and title window. */
4505 static void
4506 report(const char *msg, ...)
4508         struct view *view = display[current_view];
4510         if (input_mode)
4511                 return;
4513         if (!view) {
4514                 char buf[SIZEOF_STR];
4515                 va_list args;
4517                 va_start(args, msg);
4518                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
4519                         buf[sizeof(buf) - 1] = 0;
4520                         buf[sizeof(buf) - 2] = '.';
4521                         buf[sizeof(buf) - 3] = '.';
4522                         buf[sizeof(buf) - 4] = '.';
4523                 }
4524                 va_end(args);
4525                 die("%s", buf);
4526         }
4528         if (!status_empty || *msg) {
4529                 va_list args;
4531                 va_start(args, msg);
4533                 wmove(status_win, 0, 0);
4534                 if (*msg) {
4535                         vwprintw(status_win, msg, args);
4536                         status_empty = FALSE;
4537                 } else {
4538                         status_empty = TRUE;
4539                 }
4540                 wclrtoeol(status_win);
4541                 wrefresh(status_win);
4543                 va_end(args);
4544         }
4546         update_view_title(view);
4547         update_display_cursor(view);
4550 /* Controls when nodelay should be in effect when polling user input. */
4551 static void
4552 set_nonblocking_input(bool loading)
4554         static unsigned int loading_views;
4556         if ((loading == FALSE && loading_views-- == 1) ||
4557             (loading == TRUE  && loading_views++ == 0))
4558                 nodelay(status_win, loading);
4561 static void
4562 init_display(void)
4564         int x, y;
4566         /* Initialize the curses library */
4567         if (isatty(STDIN_FILENO)) {
4568                 cursed = !!initscr();
4569         } else {
4570                 /* Leave stdin and stdout alone when acting as a pager. */
4571                 FILE *io = fopen("/dev/tty", "r+");
4573                 if (!io)
4574                         die("Failed to open /dev/tty");
4575                 cursed = !!newterm(NULL, io, io);
4576         }
4578         if (!cursed)
4579                 die("Failed to initialize curses");
4581         nonl();         /* Tell curses not to do NL->CR/NL on output */
4582         cbreak();       /* Take input chars one at a time, no wait for \n */
4583         noecho();       /* Don't echo input */
4584         leaveok(stdscr, TRUE);
4586         if (has_colors())
4587                 init_colors();
4589         getmaxyx(stdscr, y, x);
4590         status_win = newwin(1, 0, y - 1, 0);
4591         if (!status_win)
4592                 die("Failed to create status window");
4594         /* Enable keyboard mapping */
4595         keypad(status_win, TRUE);
4596         wbkgdset(status_win, get_line_attr(LINE_STATUS));
4599 static char *
4600 read_prompt(const char *prompt)
4602         enum { READING, STOP, CANCEL } status = READING;
4603         static char buf[sizeof(opt_cmd) - STRING_SIZE("git \0")];
4604         int pos = 0;
4606         while (status == READING) {
4607                 struct view *view;
4608                 int i, key;
4610                 input_mode = TRUE;
4612                 foreach_view (view, i)
4613                         update_view(view);
4615                 input_mode = FALSE;
4617                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
4618                 wclrtoeol(status_win);
4620                 /* Refresh, accept single keystroke of input */
4621                 key = wgetch(status_win);
4622                 switch (key) {
4623                 case KEY_RETURN:
4624                 case KEY_ENTER:
4625                 case '\n':
4626                         status = pos ? STOP : CANCEL;
4627                         break;
4629                 case KEY_BACKSPACE:
4630                         if (pos > 0)
4631                                 pos--;
4632                         else
4633                                 status = CANCEL;
4634                         break;
4636                 case KEY_ESC:
4637                         status = CANCEL;
4638                         break;
4640                 case ERR:
4641                         break;
4643                 default:
4644                         if (pos >= sizeof(buf)) {
4645                                 report("Input string too long");
4646                                 return NULL;
4647                         }
4649                         if (isprint(key))
4650                                 buf[pos++] = (char) key;
4651                 }
4652         }
4654         /* Clear the status window */
4655         status_empty = FALSE;
4656         report("");
4658         if (status == CANCEL)
4659                 return NULL;
4661         buf[pos++] = 0;
4663         return buf;
4666 /*
4667  * Repository references
4668  */
4670 static struct ref *refs;
4671 static size_t refs_size;
4673 /* Id <-> ref store */
4674 static struct ref ***id_refs;
4675 static size_t id_refs_size;
4677 static struct ref **
4678 get_refs(char *id)
4680         struct ref ***tmp_id_refs;
4681         struct ref **ref_list = NULL;
4682         size_t ref_list_size = 0;
4683         size_t i;
4685         for (i = 0; i < id_refs_size; i++)
4686                 if (!strcmp(id, id_refs[i][0]->id))
4687                         return id_refs[i];
4689         tmp_id_refs = realloc(id_refs, (id_refs_size + 1) * sizeof(*id_refs));
4690         if (!tmp_id_refs)
4691                 return NULL;
4693         id_refs = tmp_id_refs;
4695         for (i = 0; i < refs_size; i++) {
4696                 struct ref **tmp;
4698                 if (strcmp(id, refs[i].id))
4699                         continue;
4701                 tmp = realloc(ref_list, (ref_list_size + 1) * sizeof(*ref_list));
4702                 if (!tmp) {
4703                         if (ref_list)
4704                                 free(ref_list);
4705                         return NULL;
4706                 }
4708                 ref_list = tmp;
4709                 if (ref_list_size > 0)
4710                         ref_list[ref_list_size - 1]->next = 1;
4711                 ref_list[ref_list_size] = &refs[i];
4713                 /* XXX: The properties of the commit chains ensures that we can
4714                  * safely modify the shared ref. The repo references will
4715                  * always be similar for the same id. */
4716                 ref_list[ref_list_size]->next = 0;
4717                 ref_list_size++;
4718         }
4720         if (ref_list)
4721                 id_refs[id_refs_size++] = ref_list;
4723         return ref_list;
4726 static int
4727 read_ref(char *id, size_t idlen, char *name, size_t namelen)
4729         struct ref *ref;
4730         bool tag = FALSE;
4731         bool remote = FALSE;
4733         if (!strncmp(name, "refs/tags/", STRING_SIZE("refs/tags/"))) {
4734                 /* Commits referenced by tags has "^{}" appended. */
4735                 if (name[namelen - 1] != '}')
4736                         return OK;
4738                 while (namelen > 0 && name[namelen] != '^')
4739                         namelen--;
4741                 tag = TRUE;
4742                 namelen -= STRING_SIZE("refs/tags/");
4743                 name    += STRING_SIZE("refs/tags/");
4745         } else if (!strncmp(name, "refs/remotes/", STRING_SIZE("refs/remotes/"))) {
4746                 remote = TRUE;
4747                 namelen -= STRING_SIZE("refs/remotes/");
4748                 name    += STRING_SIZE("refs/remotes/");
4750         } else if (!strncmp(name, "refs/heads/", STRING_SIZE("refs/heads/"))) {
4751                 namelen -= STRING_SIZE("refs/heads/");
4752                 name    += STRING_SIZE("refs/heads/");
4754         } else if (!strcmp(name, "HEAD")) {
4755                 return OK;
4756         }
4758         refs = realloc(refs, sizeof(*refs) * (refs_size + 1));
4759         if (!refs)
4760                 return ERR;
4762         ref = &refs[refs_size++];
4763         ref->name = malloc(namelen + 1);
4764         if (!ref->name)
4765                 return ERR;
4767         strncpy(ref->name, name, namelen);
4768         ref->name[namelen] = 0;
4769         ref->tag = tag;
4770         ref->remote = remote;
4771         string_copy_rev(ref->id, id);
4773         return OK;
4776 static int
4777 load_refs(void)
4779         const char *cmd_env = getenv("TIG_LS_REMOTE");
4780         const char *cmd = cmd_env && *cmd_env ? cmd_env : TIG_LS_REMOTE;
4782         return read_properties(popen(cmd, "r"), "\t", read_ref);
4785 static int
4786 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
4788         if (!strcmp(name, "i18n.commitencoding"))
4789                 string_ncopy(opt_encoding, value, valuelen);
4791         if (!strcmp(name, "core.editor"))
4792                 string_ncopy(opt_editor, value, valuelen);
4794         return OK;
4797 static int
4798 load_repo_config(void)
4800         return read_properties(popen(GIT_CONFIG " --list", "r"),
4801                                "=", read_repo_config_option);
4804 static int
4805 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
4807         if (!opt_git_dir[0]) {
4808                 string_ncopy(opt_git_dir, name, namelen);
4810         } else if (opt_is_inside_work_tree == -1) {
4811                 /* This can be 3 different values depending on the
4812                  * version of git being used. If git-rev-parse does not
4813                  * understand --is-inside-work-tree it will simply echo
4814                  * the option else either "true" or "false" is printed.
4815                  * Default to true for the unknown case. */
4816                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
4818         } else {
4819                 string_ncopy(opt_cdup, name, namelen);
4820         }
4822         return OK;
4825 /* XXX: The line outputted by "--show-cdup" can be empty so the option
4826  * must be the last one! */
4827 static int
4828 load_repo_info(void)
4830         return read_properties(popen("git rev-parse --git-dir --is-inside-work-tree --show-cdup 2>/dev/null", "r"),
4831                                "=", read_repo_info);
4834 static int
4835 read_properties(FILE *pipe, const char *separators,
4836                 int (*read_property)(char *, size_t, char *, size_t))
4838         char buffer[BUFSIZ];
4839         char *name;
4840         int state = OK;
4842         if (!pipe)
4843                 return ERR;
4845         while (state == OK && (name = fgets(buffer, sizeof(buffer), pipe))) {
4846                 char *value;
4847                 size_t namelen;
4848                 size_t valuelen;
4850                 name = chomp_string(name);
4851                 namelen = strcspn(name, separators);
4853                 if (name[namelen]) {
4854                         name[namelen] = 0;
4855                         value = chomp_string(name + namelen + 1);
4856                         valuelen = strlen(value);
4858                 } else {
4859                         value = "";
4860                         valuelen = 0;
4861                 }
4863                 state = read_property(name, namelen, value, valuelen);
4864         }
4866         if (state != ERR && ferror(pipe))
4867                 state = ERR;
4869         pclose(pipe);
4871         return state;
4875 /*
4876  * Main
4877  */
4879 static void __NORETURN
4880 quit(int sig)
4882         /* XXX: Restore tty modes and let the OS cleanup the rest! */
4883         if (cursed)
4884                 endwin();
4885         exit(0);
4888 static void __NORETURN
4889 die(const char *err, ...)
4891         va_list args;
4893         endwin();
4895         va_start(args, err);
4896         fputs("tig: ", stderr);
4897         vfprintf(stderr, err, args);
4898         fputs("\n", stderr);
4899         va_end(args);
4901         exit(1);
4904 int
4905 main(int argc, char *argv[])
4907         struct view *view;
4908         enum request request;
4909         size_t i;
4911         signal(SIGINT, quit);
4913         if (setlocale(LC_ALL, "")) {
4914                 char *codeset = nl_langinfo(CODESET);
4916                 string_ncopy(opt_codeset, codeset, strlen(codeset));
4917         }
4919         if (load_repo_info() == ERR)
4920                 die("Failed to load repo info.");
4922         if (load_options() == ERR)
4923                 die("Failed to load user config.");
4925         /* Load the repo config file so options can be overwritten from
4926          * the command line. */
4927         if (load_repo_config() == ERR)
4928                 die("Failed to load repo config.");
4930         if (!parse_options(argc, argv))
4931                 return 0;
4933         /* Require a git repository unless when running in pager mode. */
4934         if (!opt_git_dir[0])
4935                 die("Not a git repository");
4937         if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
4938                 opt_iconv = iconv_open(opt_codeset, opt_encoding);
4939                 if (opt_iconv == ICONV_NONE)
4940                         die("Failed to initialize character set conversion");
4941         }
4943         if (load_refs() == ERR)
4944                 die("Failed to load refs.");
4946         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
4947                 view->cmd_env = getenv(view->cmd_env);
4949         request = opt_request;
4951         init_display();
4953         while (view_driver(display[current_view], request)) {
4954                 int key;
4955                 int i;
4957                 foreach_view (view, i)
4958                         update_view(view);
4960                 /* Refresh, accept single keystroke of input */
4961                 key = wgetch(status_win);
4963                 /* wgetch() with nodelay() enabled returns ERR when there's no
4964                  * input. */
4965                 if (key == ERR) {
4966                         request = REQ_NONE;
4967                         continue;
4968                 }
4970                 request = get_keybinding(display[current_view]->keymap, key);
4972                 /* Some low-level request handling. This keeps access to
4973                  * status_win restricted. */
4974                 switch (request) {
4975                 case REQ_PROMPT:
4976                 {
4977                         char *cmd = read_prompt(":");
4979                         if (cmd && string_format(opt_cmd, "git %s", cmd)) {
4980                                 if (strncmp(cmd, "show", 4) && isspace(cmd[4])) {
4981                                         opt_request = REQ_VIEW_DIFF;
4982                                 } else {
4983                                         opt_request = REQ_VIEW_PAGER;
4984                                 }
4985                                 break;
4986                         }
4988                         request = REQ_NONE;
4989                         break;
4990                 }
4991                 case REQ_SEARCH:
4992                 case REQ_SEARCH_BACK:
4993                 {
4994                         const char *prompt = request == REQ_SEARCH
4995                                            ? "/" : "?";
4996                         char *search = read_prompt(prompt);
4998                         if (search)
4999                                 string_ncopy(opt_search, search, strlen(search));
5000                         else
5001                                 request = REQ_NONE;
5002                         break;
5003                 }
5004                 case REQ_SCREEN_RESIZE:
5005                 {
5006                         int height, width;
5008                         getmaxyx(stdscr, height, width);
5010                         /* Resize the status view and let the view driver take
5011                          * care of resizing the displayed views. */
5012                         wresize(status_win, 1, width);
5013                         mvwin(status_win, height - 1, 0);
5014                         wrefresh(status_win);
5015                         break;
5016                 }
5017                 default:
5018                         break;
5019                 }
5020         }
5022         quit(0);
5024         return 0;