Code

Drop use of $(...) for popen() and system() calls
[tig.git] / tig.c
1 /* Copyright (c) 2006-2008 Jonas Fonseca <fonseca@diku.dk>
2  *
3  * This program is free software; you can redistribute it and/or
4  * modify it under the terms of the GNU General Public License as
5  * published by the Free Software Foundation; either version 2 of
6  * the License, or (at your option) any later version.
7  *
8  * This program is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11  * GNU General Public License for more details.
12  */
14 #ifdef HAVE_CONFIG_H
15 #include "config.h"
16 #endif
18 #ifndef TIG_VERSION
19 #define TIG_VERSION "unknown-version"
20 #endif
22 #ifndef DEBUG
23 #define NDEBUG
24 #endif
26 #include <assert.h>
27 #include <errno.h>
28 #include <ctype.h>
29 #include <signal.h>
30 #include <stdarg.h>
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <sys/types.h>
35 #include <sys/stat.h>
36 #include <unistd.h>
37 #include <time.h>
39 #include <regex.h>
41 #include <locale.h>
42 #include <langinfo.h>
43 #include <iconv.h>
45 /* ncurses(3): Must be defined to have extended wide-character functions. */
46 #define _XOPEN_SOURCE_EXTENDED
48 #include <curses.h>
50 #if __GNUC__ >= 3
51 #define __NORETURN __attribute__((__noreturn__))
52 #else
53 #define __NORETURN
54 #endif
56 static void __NORETURN die(const char *err, ...);
57 static void warn(const char *msg, ...);
58 static void report(const char *msg, ...);
59 static int read_properties(FILE *pipe, const char *separators, int (*read)(char *, size_t, char *, size_t));
60 static void set_nonblocking_input(bool loading);
61 static size_t utf8_length(const char *string, int *width, size_t max_width, int *trimmed, bool reserve);
63 #define ABS(x)          ((x) >= 0  ? (x) : -(x))
64 #define MIN(x, y)       ((x) < (y) ? (x) :  (y))
66 #define ARRAY_SIZE(x)   (sizeof(x) / sizeof(x[0]))
67 #define STRING_SIZE(x)  (sizeof(x) - 1)
69 #define SIZEOF_STR      1024    /* Default string size. */
70 #define SIZEOF_REF      256     /* Size of symbolic or SHA1 ID. */
71 #define SIZEOF_REV      41      /* Holds a SHA-1 and an ending NUL */
73 /* Revision graph */
75 #define REVGRAPH_INIT   'I'
76 #define REVGRAPH_MERGE  'M'
77 #define REVGRAPH_BRANCH '+'
78 #define REVGRAPH_COMMIT '*'
79 #define REVGRAPH_BOUND  '^'
81 #define SIZEOF_REVGRAPH 19      /* Size of revision ancestry graphics. */
83 /* This color name can be used to refer to the default term colors. */
84 #define COLOR_DEFAULT   (-1)
86 #define ICONV_NONE      ((iconv_t) -1)
87 #ifndef ICONV_CONST
88 #define ICONV_CONST     /* nothing */
89 #endif
91 /* The format and size of the date column in the main view. */
92 #define DATE_FORMAT     "%Y-%m-%d %H:%M"
93 #define DATE_COLS       STRING_SIZE("2006-04-29 14:21 ")
95 #define AUTHOR_COLS     20
96 #define ID_COLS         8
98 /* The default interval between line numbers. */
99 #define NUMBER_INTERVAL 5
101 #define TAB_SIZE        8
103 #define SCALE_SPLIT_VIEW(height)        ((height) * 2 / 3)
105 #define NULL_ID         "0000000000000000000000000000000000000000"
107 #ifndef GIT_CONFIG
108 #define GIT_CONFIG "git config"
109 #endif
111 #define TIG_LS_REMOTE \
112         "git ls-remote . 2>/dev/null"
114 #define TIG_DIFF_CMD \
115         "git show --pretty=fuller --no-color --root --patch-with-stat --find-copies-harder -C %s 2>/dev/null"
117 #define TIG_LOG_CMD     \
118         "git log --no-color --cc --stat -n100 %s 2>/dev/null"
120 #define TIG_MAIN_CMD \
121         "git log --no-color --topo-order --parents --boundary --pretty=raw %s 2>/dev/null"
123 #define TIG_TREE_CMD    \
124         "git ls-tree %s %s"
126 #define TIG_BLOB_CMD    \
127         "git cat-file blob %s"
129 /* XXX: Needs to be defined to the empty string. */
130 #define TIG_HELP_CMD    ""
131 #define TIG_PAGER_CMD   ""
132 #define TIG_STATUS_CMD  ""
133 #define TIG_STAGE_CMD   ""
134 #define TIG_BLAME_CMD   ""
136 /* Some ascii-shorthands fitted into the ncurses namespace. */
137 #define KEY_TAB         '\t'
138 #define KEY_RETURN      '\r'
139 #define KEY_ESC         27
142 struct ref {
143         char *name;             /* Ref name; tag or head names are shortened. */
144         char id[SIZEOF_REV];    /* Commit SHA1 ID */
145         unsigned int head:1;    /* Is it the current HEAD? */
146         unsigned int tag:1;     /* Is it a tag? */
147         unsigned int ltag:1;    /* If so, is the tag local? */
148         unsigned int remote:1;  /* Is it a remote ref? */
149         unsigned int tracked:1; /* Is it the remote for the current HEAD? */
150         unsigned int next:1;    /* For ref lists: are there more refs? */
151 };
153 static struct ref **get_refs(char *id);
155 struct int_map {
156         const char *name;
157         int namelen;
158         int value;
159 };
161 static int
162 set_from_int_map(struct int_map *map, size_t map_size,
163                  int *value, const char *name, int namelen)
166         int i;
168         for (i = 0; i < map_size; i++)
169                 if (namelen == map[i].namelen &&
170                     !strncasecmp(name, map[i].name, namelen)) {
171                         *value = map[i].value;
172                         return OK;
173                 }
175         return ERR;
179 /*
180  * String helpers
181  */
183 static inline void
184 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
186         if (srclen > dstlen - 1)
187                 srclen = dstlen - 1;
189         strncpy(dst, src, srclen);
190         dst[srclen] = 0;
193 /* Shorthands for safely copying into a fixed buffer. */
195 #define string_copy(dst, src) \
196         string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
198 #define string_ncopy(dst, src, srclen) \
199         string_ncopy_do(dst, sizeof(dst), src, srclen)
201 #define string_copy_rev(dst, src) \
202         string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
204 #define string_add(dst, from, src) \
205         string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
207 static char *
208 chomp_string(char *name)
210         int namelen;
212         while (isspace(*name))
213                 name++;
215         namelen = strlen(name) - 1;
216         while (namelen > 0 && isspace(name[namelen]))
217                 name[namelen--] = 0;
219         return name;
222 static bool
223 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
225         va_list args;
226         size_t pos = bufpos ? *bufpos : 0;
228         va_start(args, fmt);
229         pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
230         va_end(args);
232         if (bufpos)
233                 *bufpos = pos;
235         return pos >= bufsize ? FALSE : TRUE;
238 #define string_format(buf, fmt, args...) \
239         string_nformat(buf, sizeof(buf), NULL, fmt, args)
241 #define string_format_from(buf, from, fmt, args...) \
242         string_nformat(buf, sizeof(buf), from, fmt, args)
244 static int
245 string_enum_compare(const char *str1, const char *str2, int len)
247         size_t i;
249 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
251         /* Diff-Header == DIFF_HEADER */
252         for (i = 0; i < len; i++) {
253                 if (toupper(str1[i]) == toupper(str2[i]))
254                         continue;
256                 if (string_enum_sep(str1[i]) &&
257                     string_enum_sep(str2[i]))
258                         continue;
260                 return str1[i] - str2[i];
261         }
263         return 0;
266 /* Shell quoting
267  *
268  * NOTE: The following is a slightly modified copy of the git project's shell
269  * quoting routines found in the quote.c file.
270  *
271  * Help to copy the thing properly quoted for the shell safety.  any single
272  * quote is replaced with '\'', any exclamation point is replaced with '\!',
273  * and the whole thing is enclosed in a
274  *
275  * E.g.
276  *  original     sq_quote     result
277  *  name     ==> name      ==> 'name'
278  *  a b      ==> a b       ==> 'a b'
279  *  a'b      ==> a'\''b    ==> 'a'\''b'
280  *  a!b      ==> a'\!'b    ==> 'a'\!'b'
281  */
283 static size_t
284 sq_quote(char buf[SIZEOF_STR], size_t bufsize, const char *src)
286         char c;
288 #define BUFPUT(x) do { if (bufsize < SIZEOF_STR) buf[bufsize++] = (x); } while (0)
290         BUFPUT('\'');
291         while ((c = *src++)) {
292                 if (c == '\'' || c == '!') {
293                         BUFPUT('\'');
294                         BUFPUT('\\');
295                         BUFPUT(c);
296                         BUFPUT('\'');
297                 } else {
298                         BUFPUT(c);
299                 }
300         }
301         BUFPUT('\'');
303         if (bufsize < SIZEOF_STR)
304                 buf[bufsize] = 0;
306         return bufsize;
310 /*
311  * User requests
312  */
314 #define REQ_INFO \
315         /* XXX: Keep the view request first and in sync with views[]. */ \
316         REQ_GROUP("View switching") \
317         REQ_(VIEW_MAIN,         "Show main view"), \
318         REQ_(VIEW_DIFF,         "Show diff view"), \
319         REQ_(VIEW_LOG,          "Show log view"), \
320         REQ_(VIEW_TREE,         "Show tree view"), \
321         REQ_(VIEW_BLOB,         "Show blob view"), \
322         REQ_(VIEW_BLAME,        "Show blame view"), \
323         REQ_(VIEW_HELP,         "Show help page"), \
324         REQ_(VIEW_PAGER,        "Show pager view"), \
325         REQ_(VIEW_STATUS,       "Show status view"), \
326         REQ_(VIEW_STAGE,        "Show stage view"), \
327         \
328         REQ_GROUP("View manipulation") \
329         REQ_(ENTER,             "Enter current line and scroll"), \
330         REQ_(NEXT,              "Move to next"), \
331         REQ_(PREVIOUS,          "Move to previous"), \
332         REQ_(VIEW_NEXT,         "Move focus to next view"), \
333         REQ_(REFRESH,           "Reload and refresh"), \
334         REQ_(MAXIMIZE,          "Maximize the current view"), \
335         REQ_(VIEW_CLOSE,        "Close the current view"), \
336         REQ_(QUIT,              "Close all views and quit"), \
337         \
338         REQ_GROUP("Cursor navigation") \
339         REQ_(MOVE_UP,           "Move cursor one line up"), \
340         REQ_(MOVE_DOWN,         "Move cursor one line down"), \
341         REQ_(MOVE_PAGE_DOWN,    "Move cursor one page down"), \
342         REQ_(MOVE_PAGE_UP,      "Move cursor one page up"), \
343         REQ_(MOVE_FIRST_LINE,   "Move cursor to first line"), \
344         REQ_(MOVE_LAST_LINE,    "Move cursor to last line"), \
345         \
346         REQ_GROUP("Scrolling") \
347         REQ_(SCROLL_LINE_UP,    "Scroll one line up"), \
348         REQ_(SCROLL_LINE_DOWN,  "Scroll one line down"), \
349         REQ_(SCROLL_PAGE_UP,    "Scroll one page up"), \
350         REQ_(SCROLL_PAGE_DOWN,  "Scroll one page down"), \
351         \
352         REQ_GROUP("Searching") \
353         REQ_(SEARCH,            "Search the view"), \
354         REQ_(SEARCH_BACK,       "Search backwards in the view"), \
355         REQ_(FIND_NEXT,         "Find next search match"), \
356         REQ_(FIND_PREV,         "Find previous search match"), \
357         \
358         REQ_GROUP("Misc") \
359         REQ_(PROMPT,            "Bring up the prompt"), \
360         REQ_(SCREEN_REDRAW,     "Redraw the screen"), \
361         REQ_(SCREEN_RESIZE,     "Resize the screen"), \
362         REQ_(SHOW_VERSION,      "Show version information"), \
363         REQ_(STOP_LOADING,      "Stop all loading views"), \
364         REQ_(TOGGLE_LINENO,     "Toggle line numbers"), \
365         REQ_(TOGGLE_DATE,       "Toggle date display"), \
366         REQ_(TOGGLE_AUTHOR,     "Toggle author display"), \
367         REQ_(TOGGLE_REV_GRAPH,  "Toggle revision graph visualization"), \
368         REQ_(TOGGLE_REFS,       "Toggle reference display (tags/branches)"), \
369         REQ_(STATUS_UPDATE,     "Update file status"), \
370         REQ_(STATUS_MERGE,      "Merge file using external tool"), \
371         REQ_(TREE_PARENT,       "Switch to parent directory in tree view"), \
372         REQ_(EDIT,              "Open in editor"), \
373         REQ_(NONE,              "Do nothing")
376 /* User action requests. */
377 enum request {
378 #define REQ_GROUP(help)
379 #define REQ_(req, help) REQ_##req
381         /* Offset all requests to avoid conflicts with ncurses getch values. */
382         REQ_OFFSET = KEY_MAX + 1,
383         REQ_INFO
385 #undef  REQ_GROUP
386 #undef  REQ_
387 };
389 struct request_info {
390         enum request request;
391         char *name;
392         int namelen;
393         char *help;
394 };
396 static struct request_info req_info[] = {
397 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
398 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
399         REQ_INFO
400 #undef  REQ_GROUP
401 #undef  REQ_
402 };
404 static enum request
405 get_request(const char *name)
407         int namelen = strlen(name);
408         int i;
410         for (i = 0; i < ARRAY_SIZE(req_info); i++)
411                 if (req_info[i].namelen == namelen &&
412                     !string_enum_compare(req_info[i].name, name, namelen))
413                         return req_info[i].request;
415         return REQ_NONE;
419 /*
420  * Options
421  */
423 static const char usage[] =
424 "tig " TIG_VERSION " (" __DATE__ ")\n"
425 "\n"
426 "Usage: tig        [options] [revs] [--] [paths]\n"
427 "   or: tig show   [options] [revs] [--] [paths]\n"
428 "   or: tig blame  [rev] path\n"
429 "   or: tig status\n"
430 "   or: tig <      [git command output]\n"
431 "\n"
432 "Options:\n"
433 "  -v, --version   Show version and exit\n"
434 "  -h, --help      Show help message and exit";
436 /* Option and state variables. */
437 static bool opt_date                    = TRUE;
438 static bool opt_author                  = TRUE;
439 static bool opt_line_number             = FALSE;
440 static bool opt_line_graphics           = TRUE;
441 static bool opt_rev_graph               = FALSE;
442 static bool opt_show_refs               = TRUE;
443 static int opt_num_interval             = NUMBER_INTERVAL;
444 static int opt_tab_size                 = TAB_SIZE;
445 static enum request opt_request         = REQ_VIEW_MAIN;
446 static char opt_cmd[SIZEOF_STR]         = "";
447 static char opt_path[SIZEOF_STR]        = "";
448 static char opt_file[SIZEOF_STR]        = "";
449 static char opt_ref[SIZEOF_REF]         = "";
450 static char opt_head[SIZEOF_REF]        = "";
451 static char opt_remote[SIZEOF_REF]      = "";
452 static bool opt_no_head                 = TRUE;
453 static FILE *opt_pipe                   = NULL;
454 static char opt_encoding[20]            = "UTF-8";
455 static bool opt_utf8                    = TRUE;
456 static char opt_codeset[20]             = "UTF-8";
457 static iconv_t opt_iconv                = ICONV_NONE;
458 static char opt_search[SIZEOF_STR]      = "";
459 static char opt_cdup[SIZEOF_STR]        = "";
460 static char opt_git_dir[SIZEOF_STR]     = "";
461 static signed char opt_is_inside_work_tree      = -1; /* set to TRUE or FALSE */
462 static char opt_editor[SIZEOF_STR]      = "";
464 static bool
465 parse_options(int argc, char *argv[])
467         size_t buf_size;
468         char *subcommand;
469         bool seen_dashdash = FALSE;
470         int i;
472         if (!isatty(STDIN_FILENO)) {
473                 opt_request = REQ_VIEW_PAGER;
474                 opt_pipe = stdin;
475                 return TRUE;
476         }
478         if (argc <= 1)
479                 return TRUE;
481         subcommand = argv[1];
482         if (!strcmp(subcommand, "status") || !strcmp(subcommand, "-S")) {
483                 opt_request = REQ_VIEW_STATUS;
484                 if (!strcmp(subcommand, "-S"))
485                         warn("`-S' has been deprecated; use `tig status' instead");
486                 if (argc > 2)
487                         warn("ignoring arguments after `%s'", subcommand);
488                 return TRUE;
490         } else if (!strcmp(subcommand, "blame")) {
491                 opt_request = REQ_VIEW_BLAME;
492                 if (argc <= 2 || argc > 4)
493                         die("invalid number of options to blame\n\n%s", usage);
495                 i = 2;
496                 if (argc == 4) {
497                         string_ncopy(opt_ref, argv[i], strlen(argv[i]));
498                         i++;
499                 }
501                 string_ncopy(opt_file, argv[i], strlen(argv[i]));
502                 return TRUE;
504         } else if (!strcmp(subcommand, "show")) {
505                 opt_request = REQ_VIEW_DIFF;
507         } else if (!strcmp(subcommand, "log") || !strcmp(subcommand, "diff")) {
508                 opt_request = subcommand[0] == 'l'
509                             ? REQ_VIEW_LOG : REQ_VIEW_DIFF;
510                 warn("`tig %s' has been deprecated", subcommand);
512         } else {
513                 subcommand = NULL;
514         }
516         if (!subcommand)
517                 /* XXX: This is vulnerable to the user overriding
518                  * options required for the main view parser. */
519                 string_copy(opt_cmd, "git log --no-color --pretty=raw --boundary --parents");
520         else
521                 string_format(opt_cmd, "git %s", subcommand);
523         buf_size = strlen(opt_cmd);
525         for (i = 1 + !!subcommand; i < argc; i++) {
526                 char *opt = argv[i];
528                 if (seen_dashdash || !strcmp(opt, "--")) {
529                         seen_dashdash = TRUE;
531                 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
532                         printf("tig version %s\n", TIG_VERSION);
533                         return FALSE;
535                 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
536                         printf("%s\n", usage);
537                         return FALSE;
538                 }
540                 opt_cmd[buf_size++] = ' ';
541                 buf_size = sq_quote(opt_cmd, buf_size, opt);
542                 if (buf_size >= sizeof(opt_cmd))
543                         die("command too long");
544         }
546         opt_cmd[buf_size] = 0;
548         return TRUE;
552 /*
553  * Line-oriented content detection.
554  */
556 #define LINE_INFO \
557 LINE(DIFF_HEADER,  "diff --git ",       COLOR_YELLOW,   COLOR_DEFAULT,  0), \
558 LINE(DIFF_CHUNK,   "@@",                COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
559 LINE(DIFF_ADD,     "+",                 COLOR_GREEN,    COLOR_DEFAULT,  0), \
560 LINE(DIFF_DEL,     "-",                 COLOR_RED,      COLOR_DEFAULT,  0), \
561 LINE(DIFF_INDEX,        "index ",         COLOR_BLUE,   COLOR_DEFAULT,  0), \
562 LINE(DIFF_OLDMODE,      "old file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
563 LINE(DIFF_NEWMODE,      "new file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
564 LINE(DIFF_COPY_FROM,    "copy from",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
565 LINE(DIFF_COPY_TO,      "copy to",        COLOR_YELLOW, COLOR_DEFAULT,  0), \
566 LINE(DIFF_RENAME_FROM,  "rename from",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
567 LINE(DIFF_RENAME_TO,    "rename to",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
568 LINE(DIFF_SIMILARITY,   "similarity ",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
569 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
570 LINE(DIFF_TREE,         "diff-tree ",     COLOR_BLUE,   COLOR_DEFAULT,  0), \
571 LINE(PP_AUTHOR,    "Author: ",          COLOR_CYAN,     COLOR_DEFAULT,  0), \
572 LINE(PP_COMMIT,    "Commit: ",          COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
573 LINE(PP_MERGE,     "Merge: ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
574 LINE(PP_DATE,      "Date:   ",          COLOR_YELLOW,   COLOR_DEFAULT,  0), \
575 LINE(PP_ADATE,     "AuthorDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
576 LINE(PP_CDATE,     "CommitDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
577 LINE(PP_REFS,      "Refs: ",            COLOR_RED,      COLOR_DEFAULT,  0), \
578 LINE(COMMIT,       "commit ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
579 LINE(PARENT,       "parent ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
580 LINE(TREE,         "tree ",             COLOR_BLUE,     COLOR_DEFAULT,  0), \
581 LINE(AUTHOR,       "author ",           COLOR_CYAN,     COLOR_DEFAULT,  0), \
582 LINE(COMMITTER,    "committer ",        COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
583 LINE(SIGNOFF,      "    Signed-off-by", COLOR_YELLOW,   COLOR_DEFAULT,  0), \
584 LINE(ACKED,        "    Acked-by",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
585 LINE(DEFAULT,      "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
586 LINE(CURSOR,       "",                  COLOR_WHITE,    COLOR_GREEN,    A_BOLD), \
587 LINE(STATUS,       "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
588 LINE(DELIMITER,    "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
589 LINE(DATE,         "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
590 LINE(LINE_NUMBER,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
591 LINE(TITLE_BLUR,   "",                  COLOR_WHITE,    COLOR_BLUE,     0), \
592 LINE(TITLE_FOCUS,  "",                  COLOR_WHITE,    COLOR_BLUE,     A_BOLD), \
593 LINE(MAIN_AUTHOR,  "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
594 LINE(MAIN_COMMIT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
595 LINE(MAIN_TAG,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
596 LINE(MAIN_LOCAL_TAG,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
597 LINE(MAIN_REMOTE,  "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
598 LINE(MAIN_TRACKED, "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_BOLD), \
599 LINE(MAIN_REF,     "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
600 LINE(MAIN_HEAD,    "",                  COLOR_CYAN,     COLOR_DEFAULT,  A_BOLD), \
601 LINE(MAIN_REVGRAPH,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
602 LINE(TREE_DIR,     "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
603 LINE(TREE_FILE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
604 LINE(STAT_HEAD,    "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
605 LINE(STAT_SECTION, "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
606 LINE(STAT_NONE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
607 LINE(STAT_STAGED,  "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
608 LINE(STAT_UNSTAGED,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
609 LINE(STAT_UNTRACKED,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
610 LINE(BLAME_ID,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0)
612 enum line_type {
613 #define LINE(type, line, fg, bg, attr) \
614         LINE_##type
615         LINE_INFO,
616         LINE_NONE
617 #undef  LINE
618 };
620 struct line_info {
621         const char *name;       /* Option name. */
622         int namelen;            /* Size of option name. */
623         const char *line;       /* The start of line to match. */
624         int linelen;            /* Size of string to match. */
625         int fg, bg, attr;       /* Color and text attributes for the lines. */
626 };
628 static struct line_info line_info[] = {
629 #define LINE(type, line, fg, bg, attr) \
630         { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
631         LINE_INFO
632 #undef  LINE
633 };
635 static enum line_type
636 get_line_type(char *line)
638         int linelen = strlen(line);
639         enum line_type type;
641         for (type = 0; type < ARRAY_SIZE(line_info); type++)
642                 /* Case insensitive search matches Signed-off-by lines better. */
643                 if (linelen >= line_info[type].linelen &&
644                     !strncasecmp(line_info[type].line, line, line_info[type].linelen))
645                         return type;
647         return LINE_DEFAULT;
650 static inline int
651 get_line_attr(enum line_type type)
653         assert(type < ARRAY_SIZE(line_info));
654         return COLOR_PAIR(type) | line_info[type].attr;
657 static struct line_info *
658 get_line_info(char *name)
660         size_t namelen = strlen(name);
661         enum line_type type;
663         for (type = 0; type < ARRAY_SIZE(line_info); type++)
664                 if (namelen == line_info[type].namelen &&
665                     !string_enum_compare(line_info[type].name, name, namelen))
666                         return &line_info[type];
668         return NULL;
671 static void
672 init_colors(void)
674         int default_bg = line_info[LINE_DEFAULT].bg;
675         int default_fg = line_info[LINE_DEFAULT].fg;
676         enum line_type type;
678         start_color();
680         if (assume_default_colors(default_fg, default_bg) == ERR) {
681                 default_bg = COLOR_BLACK;
682                 default_fg = COLOR_WHITE;
683         }
685         for (type = 0; type < ARRAY_SIZE(line_info); type++) {
686                 struct line_info *info = &line_info[type];
687                 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
688                 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
690                 init_pair(type, fg, bg);
691         }
694 struct line {
695         enum line_type type;
697         /* State flags */
698         unsigned int selected:1;
699         unsigned int dirty:1;
701         void *data;             /* User data */
702 };
705 /*
706  * Keys
707  */
709 struct keybinding {
710         int alias;
711         enum request request;
712         struct keybinding *next;
713 };
715 static struct keybinding default_keybindings[] = {
716         /* View switching */
717         { 'm',          REQ_VIEW_MAIN },
718         { 'd',          REQ_VIEW_DIFF },
719         { 'l',          REQ_VIEW_LOG },
720         { 't',          REQ_VIEW_TREE },
721         { 'f',          REQ_VIEW_BLOB },
722         { 'B',          REQ_VIEW_BLAME },
723         { 'p',          REQ_VIEW_PAGER },
724         { 'h',          REQ_VIEW_HELP },
725         { 'S',          REQ_VIEW_STATUS },
726         { 'c',          REQ_VIEW_STAGE },
728         /* View manipulation */
729         { 'q',          REQ_VIEW_CLOSE },
730         { KEY_TAB,      REQ_VIEW_NEXT },
731         { KEY_RETURN,   REQ_ENTER },
732         { KEY_UP,       REQ_PREVIOUS },
733         { KEY_DOWN,     REQ_NEXT },
734         { 'R',          REQ_REFRESH },
735         { KEY_F(5),     REQ_REFRESH },
736         { 'O',          REQ_MAXIMIZE },
738         /* Cursor navigation */
739         { 'k',          REQ_MOVE_UP },
740         { 'j',          REQ_MOVE_DOWN },
741         { KEY_HOME,     REQ_MOVE_FIRST_LINE },
742         { KEY_END,      REQ_MOVE_LAST_LINE },
743         { KEY_NPAGE,    REQ_MOVE_PAGE_DOWN },
744         { ' ',          REQ_MOVE_PAGE_DOWN },
745         { KEY_PPAGE,    REQ_MOVE_PAGE_UP },
746         { 'b',          REQ_MOVE_PAGE_UP },
747         { '-',          REQ_MOVE_PAGE_UP },
749         /* Scrolling */
750         { KEY_IC,       REQ_SCROLL_LINE_UP },
751         { KEY_DC,       REQ_SCROLL_LINE_DOWN },
752         { 'w',          REQ_SCROLL_PAGE_UP },
753         { 's',          REQ_SCROLL_PAGE_DOWN },
755         /* Searching */
756         { '/',          REQ_SEARCH },
757         { '?',          REQ_SEARCH_BACK },
758         { 'n',          REQ_FIND_NEXT },
759         { 'N',          REQ_FIND_PREV },
761         /* Misc */
762         { 'Q',          REQ_QUIT },
763         { 'z',          REQ_STOP_LOADING },
764         { 'v',          REQ_SHOW_VERSION },
765         { 'r',          REQ_SCREEN_REDRAW },
766         { '.',          REQ_TOGGLE_LINENO },
767         { 'D',          REQ_TOGGLE_DATE },
768         { 'A',          REQ_TOGGLE_AUTHOR },
769         { 'g',          REQ_TOGGLE_REV_GRAPH },
770         { 'F',          REQ_TOGGLE_REFS },
771         { ':',          REQ_PROMPT },
772         { 'u',          REQ_STATUS_UPDATE },
773         { 'M',          REQ_STATUS_MERGE },
774         { ',',          REQ_TREE_PARENT },
775         { 'e',          REQ_EDIT },
777         /* Using the ncurses SIGWINCH handler. */
778         { KEY_RESIZE,   REQ_SCREEN_RESIZE },
779 };
781 #define KEYMAP_INFO \
782         KEYMAP_(GENERIC), \
783         KEYMAP_(MAIN), \
784         KEYMAP_(DIFF), \
785         KEYMAP_(LOG), \
786         KEYMAP_(TREE), \
787         KEYMAP_(BLOB), \
788         KEYMAP_(BLAME), \
789         KEYMAP_(PAGER), \
790         KEYMAP_(HELP), \
791         KEYMAP_(STATUS), \
792         KEYMAP_(STAGE)
794 enum keymap {
795 #define KEYMAP_(name) KEYMAP_##name
796         KEYMAP_INFO
797 #undef  KEYMAP_
798 };
800 static struct int_map keymap_table[] = {
801 #define KEYMAP_(name) { #name, STRING_SIZE(#name), KEYMAP_##name }
802         KEYMAP_INFO
803 #undef  KEYMAP_
804 };
806 #define set_keymap(map, name) \
807         set_from_int_map(keymap_table, ARRAY_SIZE(keymap_table), map, name, strlen(name))
809 static struct keybinding *keybindings[ARRAY_SIZE(keymap_table)];
811 static void
812 add_keybinding(enum keymap keymap, enum request request, int key)
814         struct keybinding *keybinding;
816         keybinding = calloc(1, sizeof(*keybinding));
817         if (!keybinding)
818                 die("Failed to allocate keybinding");
820         keybinding->alias = key;
821         keybinding->request = request;
822         keybinding->next = keybindings[keymap];
823         keybindings[keymap] = keybinding;
826 /* Looks for a key binding first in the given map, then in the generic map, and
827  * lastly in the default keybindings. */
828 static enum request
829 get_keybinding(enum keymap keymap, int key)
831         struct keybinding *kbd;
832         int i;
834         for (kbd = keybindings[keymap]; kbd; kbd = kbd->next)
835                 if (kbd->alias == key)
836                         return kbd->request;
838         for (kbd = keybindings[KEYMAP_GENERIC]; kbd; kbd = kbd->next)
839                 if (kbd->alias == key)
840                         return kbd->request;
842         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
843                 if (default_keybindings[i].alias == key)
844                         return default_keybindings[i].request;
846         return (enum request) key;
850 struct key {
851         char *name;
852         int value;
853 };
855 static struct key key_table[] = {
856         { "Enter",      KEY_RETURN },
857         { "Space",      ' ' },
858         { "Backspace",  KEY_BACKSPACE },
859         { "Tab",        KEY_TAB },
860         { "Escape",     KEY_ESC },
861         { "Left",       KEY_LEFT },
862         { "Right",      KEY_RIGHT },
863         { "Up",         KEY_UP },
864         { "Down",       KEY_DOWN },
865         { "Insert",     KEY_IC },
866         { "Delete",     KEY_DC },
867         { "Hash",       '#' },
868         { "Home",       KEY_HOME },
869         { "End",        KEY_END },
870         { "PageUp",     KEY_PPAGE },
871         { "PageDown",   KEY_NPAGE },
872         { "F1",         KEY_F(1) },
873         { "F2",         KEY_F(2) },
874         { "F3",         KEY_F(3) },
875         { "F4",         KEY_F(4) },
876         { "F5",         KEY_F(5) },
877         { "F6",         KEY_F(6) },
878         { "F7",         KEY_F(7) },
879         { "F8",         KEY_F(8) },
880         { "F9",         KEY_F(9) },
881         { "F10",        KEY_F(10) },
882         { "F11",        KEY_F(11) },
883         { "F12",        KEY_F(12) },
884 };
886 static int
887 get_key_value(const char *name)
889         int i;
891         for (i = 0; i < ARRAY_SIZE(key_table); i++)
892                 if (!strcasecmp(key_table[i].name, name))
893                         return key_table[i].value;
895         if (strlen(name) == 1 && isprint(*name))
896                 return (int) *name;
898         return ERR;
901 static char *
902 get_key_name(int key_value)
904         static char key_char[] = "'X'";
905         char *seq = NULL;
906         int key;
908         for (key = 0; key < ARRAY_SIZE(key_table); key++)
909                 if (key_table[key].value == key_value)
910                         seq = key_table[key].name;
912         if (seq == NULL &&
913             key_value < 127 &&
914             isprint(key_value)) {
915                 key_char[1] = (char) key_value;
916                 seq = key_char;
917         }
919         return seq ? seq : "'?'";
922 static char *
923 get_key(enum request request)
925         static char buf[BUFSIZ];
926         size_t pos = 0;
927         char *sep = "";
928         int i;
930         buf[pos] = 0;
932         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
933                 struct keybinding *keybinding = &default_keybindings[i];
935                 if (keybinding->request != request)
936                         continue;
938                 if (!string_format_from(buf, &pos, "%s%s", sep,
939                                         get_key_name(keybinding->alias)))
940                         return "Too many keybindings!";
941                 sep = ", ";
942         }
944         return buf;
947 struct run_request {
948         enum keymap keymap;
949         int key;
950         char cmd[SIZEOF_STR];
951 };
953 static struct run_request *run_request;
954 static size_t run_requests;
956 static enum request
957 add_run_request(enum keymap keymap, int key, int argc, char **argv)
959         struct run_request *req;
960         char cmd[SIZEOF_STR];
961         size_t bufpos;
963         for (bufpos = 0; argc > 0; argc--, argv++)
964                 if (!string_format_from(cmd, &bufpos, "%s ", *argv))
965                         return REQ_NONE;
967         req = realloc(run_request, (run_requests + 1) * sizeof(*run_request));
968         if (!req)
969                 return REQ_NONE;
971         run_request = req;
972         req = &run_request[run_requests++];
973         string_copy(req->cmd, cmd);
974         req->keymap = keymap;
975         req->key = key;
977         return REQ_NONE + run_requests;
980 static struct run_request *
981 get_run_request(enum request request)
983         if (request <= REQ_NONE)
984                 return NULL;
985         return &run_request[request - REQ_NONE - 1];
988 static void
989 add_builtin_run_requests(void)
991         struct {
992                 enum keymap keymap;
993                 int key;
994                 char *argv[1];
995         } reqs[] = {
996                 { KEYMAP_MAIN,    'C', { "git cherry-pick %(commit)" } },
997                 { KEYMAP_GENERIC, 'G', { "git gc" } },
998         };
999         int i;
1001         for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1002                 enum request req;
1004                 req = add_run_request(reqs[i].keymap, reqs[i].key, 1, reqs[i].argv);
1005                 if (req != REQ_NONE)
1006                         add_keybinding(reqs[i].keymap, req, reqs[i].key);
1007         }
1010 /*
1011  * User config file handling.
1012  */
1014 static struct int_map color_map[] = {
1015 #define COLOR_MAP(name) { #name, STRING_SIZE(#name), COLOR_##name }
1016         COLOR_MAP(DEFAULT),
1017         COLOR_MAP(BLACK),
1018         COLOR_MAP(BLUE),
1019         COLOR_MAP(CYAN),
1020         COLOR_MAP(GREEN),
1021         COLOR_MAP(MAGENTA),
1022         COLOR_MAP(RED),
1023         COLOR_MAP(WHITE),
1024         COLOR_MAP(YELLOW),
1025 };
1027 #define set_color(color, name) \
1028         set_from_int_map(color_map, ARRAY_SIZE(color_map), color, name, strlen(name))
1030 static struct int_map attr_map[] = {
1031 #define ATTR_MAP(name) { #name, STRING_SIZE(#name), A_##name }
1032         ATTR_MAP(NORMAL),
1033         ATTR_MAP(BLINK),
1034         ATTR_MAP(BOLD),
1035         ATTR_MAP(DIM),
1036         ATTR_MAP(REVERSE),
1037         ATTR_MAP(STANDOUT),
1038         ATTR_MAP(UNDERLINE),
1039 };
1041 #define set_attribute(attr, name) \
1042         set_from_int_map(attr_map, ARRAY_SIZE(attr_map), attr, name, strlen(name))
1044 static int   config_lineno;
1045 static bool  config_errors;
1046 static char *config_msg;
1048 /* Wants: object fgcolor bgcolor [attr] */
1049 static int
1050 option_color_command(int argc, char *argv[])
1052         struct line_info *info;
1054         if (argc != 3 && argc != 4) {
1055                 config_msg = "Wrong number of arguments given to color command";
1056                 return ERR;
1057         }
1059         info = get_line_info(argv[0]);
1060         if (!info) {
1061                 if (!string_enum_compare(argv[0], "main-delim", strlen("main-delim"))) {
1062                         info = get_line_info("delimiter");
1064                 } else if (!string_enum_compare(argv[0], "main-date", strlen("main-date"))) {
1065                         info = get_line_info("date");
1067                 } else {
1068                         config_msg = "Unknown color name";
1069                         return ERR;
1070                 }
1071         }
1073         if (set_color(&info->fg, argv[1]) == ERR ||
1074             set_color(&info->bg, argv[2]) == ERR) {
1075                 config_msg = "Unknown color";
1076                 return ERR;
1077         }
1079         if (argc == 4 && set_attribute(&info->attr, argv[3]) == ERR) {
1080                 config_msg = "Unknown attribute";
1081                 return ERR;
1082         }
1084         return OK;
1087 static bool parse_bool(const char *s)
1089         return (!strcmp(s, "1") || !strcmp(s, "true") ||
1090                 !strcmp(s, "yes")) ? TRUE : FALSE;
1093 /* Wants: name = value */
1094 static int
1095 option_set_command(int argc, char *argv[])
1097         if (argc != 3) {
1098                 config_msg = "Wrong number of arguments given to set command";
1099                 return ERR;
1100         }
1102         if (strcmp(argv[1], "=")) {
1103                 config_msg = "No value assigned";
1104                 return ERR;
1105         }
1107         if (!strcmp(argv[0], "show-author")) {
1108                 opt_author = parse_bool(argv[2]);
1109                 return OK;
1110         }
1112         if (!strcmp(argv[0], "show-date")) {
1113                 opt_date = parse_bool(argv[2]);
1114                 return OK;
1115         }
1117         if (!strcmp(argv[0], "show-rev-graph")) {
1118                 opt_rev_graph = parse_bool(argv[2]);
1119                 return OK;
1120         }
1122         if (!strcmp(argv[0], "show-refs")) {
1123                 opt_show_refs = parse_bool(argv[2]);
1124                 return OK;
1125         }
1127         if (!strcmp(argv[0], "show-line-numbers")) {
1128                 opt_line_number = parse_bool(argv[2]);
1129                 return OK;
1130         }
1132         if (!strcmp(argv[0], "line-graphics")) {
1133                 opt_line_graphics = parse_bool(argv[2]);
1134                 return OK;
1135         }
1137         if (!strcmp(argv[0], "line-number-interval")) {
1138                 opt_num_interval = atoi(argv[2]);
1139                 return OK;
1140         }
1142         if (!strcmp(argv[0], "tab-size")) {
1143                 opt_tab_size = atoi(argv[2]);
1144                 return OK;
1145         }
1147         if (!strcmp(argv[0], "commit-encoding")) {
1148                 char *arg = argv[2];
1149                 int delimiter = *arg;
1150                 int i;
1152                 switch (delimiter) {
1153                 case '"':
1154                 case '\'':
1155                         for (arg++, i = 0; arg[i]; i++)
1156                                 if (arg[i] == delimiter) {
1157                                         arg[i] = 0;
1158                                         break;
1159                                 }
1160                 default:
1161                         string_ncopy(opt_encoding, arg, strlen(arg));
1162                         return OK;
1163                 }
1164         }
1166         config_msg = "Unknown variable name";
1167         return ERR;
1170 /* Wants: mode request key */
1171 static int
1172 option_bind_command(int argc, char *argv[])
1174         enum request request;
1175         int keymap;
1176         int key;
1178         if (argc < 3) {
1179                 config_msg = "Wrong number of arguments given to bind command";
1180                 return ERR;
1181         }
1183         if (set_keymap(&keymap, argv[0]) == ERR) {
1184                 config_msg = "Unknown key map";
1185                 return ERR;
1186         }
1188         key = get_key_value(argv[1]);
1189         if (key == ERR) {
1190                 config_msg = "Unknown key";
1191                 return ERR;
1192         }
1194         request = get_request(argv[2]);
1195         if (request == REQ_NONE) {
1196                 const char *obsolete[] = { "cherry-pick" };
1197                 size_t namelen = strlen(argv[2]);
1198                 int i;
1200                 for (i = 0; i < ARRAY_SIZE(obsolete); i++) {
1201                         if (namelen == strlen(obsolete[i]) &&
1202                             !string_enum_compare(obsolete[i], argv[2], namelen)) {
1203                                 config_msg = "Obsolete request name";
1204                                 return ERR;
1205                         }
1206                 }
1207         }
1208         if (request == REQ_NONE && *argv[2]++ == '!')
1209                 request = add_run_request(keymap, key, argc - 2, argv + 2);
1210         if (request == REQ_NONE) {
1211                 config_msg = "Unknown request name";
1212                 return ERR;
1213         }
1215         add_keybinding(keymap, request, key);
1217         return OK;
1220 static int
1221 set_option(char *opt, char *value)
1223         char *argv[16];
1224         int valuelen;
1225         int argc = 0;
1227         /* Tokenize */
1228         while (argc < ARRAY_SIZE(argv) && (valuelen = strcspn(value, " \t"))) {
1229                 argv[argc++] = value;
1230                 value += valuelen;
1232                 /* Nothing more to tokenize or last available token. */
1233                 if (!*value || argc >= ARRAY_SIZE(argv))
1234                         break;
1236                 *value++ = 0;
1237                 while (isspace(*value))
1238                         value++;
1239         }
1241         if (!strcmp(opt, "color"))
1242                 return option_color_command(argc, argv);
1244         if (!strcmp(opt, "set"))
1245                 return option_set_command(argc, argv);
1247         if (!strcmp(opt, "bind"))
1248                 return option_bind_command(argc, argv);
1250         config_msg = "Unknown option command";
1251         return ERR;
1254 static int
1255 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1257         int status = OK;
1259         config_lineno++;
1260         config_msg = "Internal error";
1262         /* Check for comment markers, since read_properties() will
1263          * only ensure opt and value are split at first " \t". */
1264         optlen = strcspn(opt, "#");
1265         if (optlen == 0)
1266                 return OK;
1268         if (opt[optlen] != 0) {
1269                 config_msg = "No option value";
1270                 status = ERR;
1272         }  else {
1273                 /* Look for comment endings in the value. */
1274                 size_t len = strcspn(value, "#");
1276                 if (len < valuelen) {
1277                         valuelen = len;
1278                         value[valuelen] = 0;
1279                 }
1281                 status = set_option(opt, value);
1282         }
1284         if (status == ERR) {
1285                 fprintf(stderr, "Error on line %d, near '%.*s': %s\n",
1286                         config_lineno, (int) optlen, opt, config_msg);
1287                 config_errors = TRUE;
1288         }
1290         /* Always keep going if errors are encountered. */
1291         return OK;
1294 static void
1295 load_option_file(const char *path)
1297         FILE *file;
1299         /* It's ok that the file doesn't exist. */
1300         file = fopen(path, "r");
1301         if (!file)
1302                 return;
1304         config_lineno = 0;
1305         config_errors = FALSE;
1307         if (read_properties(file, " \t", read_option) == ERR ||
1308             config_errors == TRUE)
1309                 fprintf(stderr, "Errors while loading %s.\n", path);
1312 static int
1313 load_options(void)
1315         char *home = getenv("HOME");
1316         char *tigrc_user = getenv("TIGRC_USER");
1317         char *tigrc_system = getenv("TIGRC_SYSTEM");
1318         char buf[SIZEOF_STR];
1320         add_builtin_run_requests();
1322         if (!tigrc_system) {
1323                 if (!string_format(buf, "%s/tigrc", SYSCONFDIR))
1324                         return ERR;
1325                 tigrc_system = buf;
1326         }
1327         load_option_file(tigrc_system);
1329         if (!tigrc_user) {
1330                 if (!home || !string_format(buf, "%s/.tigrc", home))
1331                         return ERR;
1332                 tigrc_user = buf;
1333         }
1334         load_option_file(tigrc_user);
1336         return OK;
1340 /*
1341  * The viewer
1342  */
1344 struct view;
1345 struct view_ops;
1347 /* The display array of active views and the index of the current view. */
1348 static struct view *display[2];
1349 static unsigned int current_view;
1351 /* Reading from the prompt? */
1352 static bool input_mode = FALSE;
1354 #define foreach_displayed_view(view, i) \
1355         for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1357 #define displayed_views()       (display[1] != NULL ? 2 : 1)
1359 /* Current head and commit ID */
1360 static char ref_blob[SIZEOF_REF]        = "";
1361 static char ref_commit[SIZEOF_REF]      = "HEAD";
1362 static char ref_head[SIZEOF_REF]        = "HEAD";
1364 struct view {
1365         const char *name;       /* View name */
1366         const char *cmd_fmt;    /* Default command line format */
1367         const char *cmd_env;    /* Command line set via environment */
1368         const char *id;         /* Points to either of ref_{head,commit,blob} */
1370         struct view_ops *ops;   /* View operations */
1372         enum keymap keymap;     /* What keymap does this view have */
1373         bool git_dir;           /* Whether the view requires a git directory. */
1375         char cmd[SIZEOF_STR];   /* Command buffer */
1376         char ref[SIZEOF_REF];   /* Hovered commit reference */
1377         char vid[SIZEOF_REF];   /* View ID. Set to id member when updating. */
1379         int height, width;      /* The width and height of the main window */
1380         WINDOW *win;            /* The main window */
1381         WINDOW *title;          /* The title window living below the main window */
1383         /* Navigation */
1384         unsigned long offset;   /* Offset of the window top */
1385         unsigned long lineno;   /* Current line number */
1387         /* Searching */
1388         char grep[SIZEOF_STR];  /* Search string */
1389         regex_t *regex;         /* Pre-compiled regex */
1391         /* If non-NULL, points to the view that opened this view. If this view
1392          * is closed tig will switch back to the parent view. */
1393         struct view *parent;
1395         /* Buffering */
1396         size_t lines;           /* Total number of lines */
1397         struct line *line;      /* Line index */
1398         size_t line_alloc;      /* Total number of allocated lines */
1399         size_t line_size;       /* Total number of used lines */
1400         unsigned int digits;    /* Number of digits in the lines member. */
1402         /* Drawing */
1403         struct line *curline;   /* Line currently being drawn. */
1404         enum line_type curtype; /* Attribute currently used for drawing. */
1405         unsigned long col;      /* Column when drawing. */
1407         /* Loading */
1408         FILE *pipe;
1409         time_t start_time;
1410 };
1412 struct view_ops {
1413         /* What type of content being displayed. Used in the title bar. */
1414         const char *type;
1415         /* Open and reads in all view content. */
1416         bool (*open)(struct view *view);
1417         /* Read one line; updates view->line. */
1418         bool (*read)(struct view *view, char *data);
1419         /* Draw one line; @lineno must be < view->height. */
1420         bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
1421         /* Depending on view handle a special requests. */
1422         enum request (*request)(struct view *view, enum request request, struct line *line);
1423         /* Search for regex in a line. */
1424         bool (*grep)(struct view *view, struct line *line);
1425         /* Select line */
1426         void (*select)(struct view *view, struct line *line);
1427 };
1429 static struct view_ops pager_ops;
1430 static struct view_ops main_ops;
1431 static struct view_ops tree_ops;
1432 static struct view_ops blob_ops;
1433 static struct view_ops blame_ops;
1434 static struct view_ops help_ops;
1435 static struct view_ops status_ops;
1436 static struct view_ops stage_ops;
1438 #define VIEW_STR(name, cmd, env, ref, ops, map, git) \
1439         { name, cmd, #env, ref, ops, map, git }
1441 #define VIEW_(id, name, ops, git, ref) \
1442         VIEW_STR(name, TIG_##id##_CMD,  TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
1445 static struct view views[] = {
1446         VIEW_(MAIN,   "main",   &main_ops,   TRUE,  ref_head),
1447         VIEW_(DIFF,   "diff",   &pager_ops,  TRUE,  ref_commit),
1448         VIEW_(LOG,    "log",    &pager_ops,  TRUE,  ref_head),
1449         VIEW_(TREE,   "tree",   &tree_ops,   TRUE,  ref_commit),
1450         VIEW_(BLOB,   "blob",   &blob_ops,   TRUE,  ref_blob),
1451         VIEW_(BLAME,  "blame",  &blame_ops,  TRUE,  ref_commit),
1452         VIEW_(HELP,   "help",   &help_ops,   FALSE, ""),
1453         VIEW_(PAGER,  "pager",  &pager_ops,  FALSE, "stdin"),
1454         VIEW_(STATUS, "status", &status_ops, TRUE,  ""),
1455         VIEW_(STAGE,  "stage",  &stage_ops,  TRUE,  ""),
1456 };
1458 #define VIEW(req)       (&views[(req) - REQ_OFFSET - 1])
1459 #define VIEW_REQ(view)  ((view) - views + REQ_OFFSET + 1)
1461 #define foreach_view(view, i) \
1462         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1464 #define view_is_displayed(view) \
1465         (view == display[0] || view == display[1])
1468 enum line_graphic {
1469         LINE_GRAPHIC_VLINE
1470 };
1472 static int line_graphics[] = {
1473         /* LINE_GRAPHIC_VLINE: */ '|'
1474 };
1476 static inline void
1477 set_view_attr(struct view *view, enum line_type type)
1479         if (!view->curline->selected && view->curtype != type) {
1480                 wattrset(view->win, get_line_attr(type));
1481                 wchgat(view->win, -1, 0, type, NULL);
1482                 view->curtype = type;
1483         }
1486 static int
1487 draw_chars(struct view *view, enum line_type type, const char *string,
1488            int max_len, bool use_tilde)
1490         int len = 0;
1491         int col = 0;
1492         int trimmed = FALSE;
1494         if (max_len <= 0)
1495                 return 0;
1497         if (opt_utf8) {
1498                 len = utf8_length(string, &col, max_len, &trimmed, use_tilde);
1499         } else {
1500                 col = len = strlen(string);
1501                 if (len > max_len) {
1502                         if (use_tilde) {
1503                                 max_len -= 1;
1504                         }
1505                         col = len = max_len;
1506                         trimmed = TRUE;
1507                 }
1508         }
1510         set_view_attr(view, type);
1511         waddnstr(view->win, string, len);
1512         if (trimmed && use_tilde) {
1513                 set_view_attr(view, LINE_DELIMITER);
1514                 waddch(view->win, '~');
1515                 col++;
1516         }
1518         return col;
1521 static int
1522 draw_space(struct view *view, enum line_type type, int max, int spaces)
1524         static char space[] = "                    ";
1525         int col = 0;
1527         spaces = MIN(max, spaces);
1529         while (spaces > 0) {
1530                 int len = MIN(spaces, sizeof(space) - 1);
1532                 col += draw_chars(view, type, space, spaces, FALSE);
1533                 spaces -= len;
1534         }
1536         return col;
1539 static bool
1540 draw_lineno(struct view *view, unsigned int lineno)
1542         char number[10];
1543         int digits3 = view->digits < 3 ? 3 : view->digits;
1544         int max_number = MIN(digits3, STRING_SIZE(number));
1545         int max = view->width - view->col;
1546         int col;
1548         if (max < max_number)
1549                 max_number = max;
1551         lineno += view->offset + 1;
1552         if (lineno == 1 || (lineno % opt_num_interval) == 0) {
1553                 static char fmt[] = "%1ld";
1555                 if (view->digits <= 9)
1556                         fmt[1] = '0' + digits3;
1558                 if (!string_format(number, fmt, lineno))
1559                         number[0] = 0;
1560                 col = draw_chars(view, LINE_LINE_NUMBER, number, max_number, TRUE);
1561         } else {
1562                 col = draw_space(view, LINE_LINE_NUMBER, max_number, max_number);
1563         }
1565         if (col < max) {
1566                 set_view_attr(view, LINE_DEFAULT);
1567                 waddch(view->win, line_graphics[LINE_GRAPHIC_VLINE]);
1568                 col++;
1569         }
1571         if (col < max)
1572                 col += draw_space(view, LINE_DEFAULT, max - col, 1);
1573         view->col += col;
1575         return view->width - view->col <= 0;
1578 static bool
1579 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
1581         view->col += draw_chars(view, type, string, view->width - view->col, trim);
1582         return view->width - view->col <= 0;
1585 static bool
1586 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
1588         int max = view->width - view->col;
1589         int i;
1591         if (max < size)
1592                 size = max;
1594         set_view_attr(view, type);
1595         /* Using waddch() instead of waddnstr() ensures that
1596          * they'll be rendered correctly for the cursor line. */
1597         for (i = 0; i < size; i++)
1598                 waddch(view->win, graphic[i]);
1600         view->col += size;
1601         if (size < max) {
1602                 waddch(view->win, ' ');
1603                 view->col++;
1604         }
1606         return view->width - view->col <= 0;
1609 static bool
1610 draw_field(struct view *view, enum line_type type, char *text, int len, bool trim)
1612         int max = MIN(view->width - view->col, len);
1613         int col;
1615         if (text)
1616                 col = draw_chars(view, type, text, max - 1, trim);
1617         else
1618                 col = draw_space(view, type, max - 1, max - 1);
1620         view->col += col + draw_space(view, LINE_DEFAULT, max - col, max - col);
1621         return view->width - view->col <= 0;
1624 static bool
1625 draw_date(struct view *view, struct tm *time)
1627         char buf[DATE_COLS];
1628         char *date;
1629         int timelen = 0;
1631         if (time)
1632                 timelen = strftime(buf, sizeof(buf), DATE_FORMAT, time);
1633         date = timelen ? buf : NULL;
1635         return draw_field(view, LINE_DATE, date, DATE_COLS, FALSE);
1638 static bool
1639 draw_view_line(struct view *view, unsigned int lineno)
1641         struct line *line;
1642         bool selected = (view->offset + lineno == view->lineno);
1643         bool draw_ok;
1645         assert(view_is_displayed(view));
1647         if (view->offset + lineno >= view->lines)
1648                 return FALSE;
1650         line = &view->line[view->offset + lineno];
1652         wmove(view->win, lineno, 0);
1653         view->col = 0;
1654         view->curline = line;
1655         view->curtype = LINE_NONE;
1656         line->selected = FALSE;
1658         if (selected) {
1659                 set_view_attr(view, LINE_CURSOR);
1660                 line->selected = TRUE;
1661                 view->ops->select(view, line);
1662         } else if (line->selected) {
1663                 wclrtoeol(view->win);
1664         }
1666         scrollok(view->win, FALSE);
1667         draw_ok = view->ops->draw(view, line, lineno);
1668         scrollok(view->win, TRUE);
1670         return draw_ok;
1673 static void
1674 redraw_view_dirty(struct view *view)
1676         bool dirty = FALSE;
1677         int lineno;
1679         for (lineno = 0; lineno < view->height; lineno++) {
1680                 struct line *line = &view->line[view->offset + lineno];
1682                 if (!line->dirty)
1683                         continue;
1684                 line->dirty = 0;
1685                 dirty = TRUE;
1686                 if (!draw_view_line(view, lineno))
1687                         break;
1688         }
1690         if (!dirty)
1691                 return;
1692         redrawwin(view->win);
1693         if (input_mode)
1694                 wnoutrefresh(view->win);
1695         else
1696                 wrefresh(view->win);
1699 static void
1700 redraw_view_from(struct view *view, int lineno)
1702         assert(0 <= lineno && lineno < view->height);
1704         for (; lineno < view->height; lineno++) {
1705                 if (!draw_view_line(view, lineno))
1706                         break;
1707         }
1709         redrawwin(view->win);
1710         if (input_mode)
1711                 wnoutrefresh(view->win);
1712         else
1713                 wrefresh(view->win);
1716 static void
1717 redraw_view(struct view *view)
1719         wclear(view->win);
1720         redraw_view_from(view, 0);
1724 static void
1725 update_view_title(struct view *view)
1727         char buf[SIZEOF_STR];
1728         char state[SIZEOF_STR];
1729         size_t bufpos = 0, statelen = 0;
1731         assert(view_is_displayed(view));
1733         if (view != VIEW(REQ_VIEW_STATUS) && (view->lines || view->pipe)) {
1734                 unsigned int view_lines = view->offset + view->height;
1735                 unsigned int lines = view->lines
1736                                    ? MIN(view_lines, view->lines) * 100 / view->lines
1737                                    : 0;
1739                 string_format_from(state, &statelen, "- %s %d of %d (%d%%)",
1740                                    view->ops->type,
1741                                    view->lineno + 1,
1742                                    view->lines,
1743                                    lines);
1745                 if (view->pipe) {
1746                         time_t secs = time(NULL) - view->start_time;
1748                         /* Three git seconds are a long time ... */
1749                         if (secs > 2)
1750                                 string_format_from(state, &statelen, " %lds", secs);
1751                 }
1752         }
1754         string_format_from(buf, &bufpos, "[%s]", view->name);
1755         if (*view->ref && bufpos < view->width) {
1756                 size_t refsize = strlen(view->ref);
1757                 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
1759                 if (minsize < view->width)
1760                         refsize = view->width - minsize + 7;
1761                 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
1762         }
1764         if (statelen && bufpos < view->width) {
1765                 string_format_from(buf, &bufpos, " %s", state);
1766         }
1768         if (view == display[current_view])
1769                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
1770         else
1771                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
1773         mvwaddnstr(view->title, 0, 0, buf, bufpos);
1774         wclrtoeol(view->title);
1775         wmove(view->title, 0, view->width - 1);
1777         if (input_mode)
1778                 wnoutrefresh(view->title);
1779         else
1780                 wrefresh(view->title);
1783 static void
1784 resize_display(void)
1786         int offset, i;
1787         struct view *base = display[0];
1788         struct view *view = display[1] ? display[1] : display[0];
1790         /* Setup window dimensions */
1792         getmaxyx(stdscr, base->height, base->width);
1794         /* Make room for the status window. */
1795         base->height -= 1;
1797         if (view != base) {
1798                 /* Horizontal split. */
1799                 view->width   = base->width;
1800                 view->height  = SCALE_SPLIT_VIEW(base->height);
1801                 base->height -= view->height;
1803                 /* Make room for the title bar. */
1804                 view->height -= 1;
1805         }
1807         /* Make room for the title bar. */
1808         base->height -= 1;
1810         offset = 0;
1812         foreach_displayed_view (view, i) {
1813                 if (!view->win) {
1814                         view->win = newwin(view->height, 0, offset, 0);
1815                         if (!view->win)
1816                                 die("Failed to create %s view", view->name);
1818                         scrollok(view->win, TRUE);
1820                         view->title = newwin(1, 0, offset + view->height, 0);
1821                         if (!view->title)
1822                                 die("Failed to create title window");
1824                 } else {
1825                         wresize(view->win, view->height, view->width);
1826                         mvwin(view->win,   offset, 0);
1827                         mvwin(view->title, offset + view->height, 0);
1828                 }
1830                 offset += view->height + 1;
1831         }
1834 static void
1835 redraw_display(void)
1837         struct view *view;
1838         int i;
1840         foreach_displayed_view (view, i) {
1841                 redraw_view(view);
1842                 update_view_title(view);
1843         }
1846 static void
1847 update_display_cursor(struct view *view)
1849         /* Move the cursor to the right-most column of the cursor line.
1850          *
1851          * XXX: This could turn out to be a bit expensive, but it ensures that
1852          * the cursor does not jump around. */
1853         if (view->lines) {
1854                 wmove(view->win, view->lineno - view->offset, view->width - 1);
1855                 wrefresh(view->win);
1856         }
1859 /*
1860  * Navigation
1861  */
1863 /* Scrolling backend */
1864 static void
1865 do_scroll_view(struct view *view, int lines)
1867         bool redraw_current_line = FALSE;
1869         /* The rendering expects the new offset. */
1870         view->offset += lines;
1872         assert(0 <= view->offset && view->offset < view->lines);
1873         assert(lines);
1875         /* Move current line into the view. */
1876         if (view->lineno < view->offset) {
1877                 view->lineno = view->offset;
1878                 redraw_current_line = TRUE;
1879         } else if (view->lineno >= view->offset + view->height) {
1880                 view->lineno = view->offset + view->height - 1;
1881                 redraw_current_line = TRUE;
1882         }
1884         assert(view->offset <= view->lineno && view->lineno < view->lines);
1886         /* Redraw the whole screen if scrolling is pointless. */
1887         if (view->height < ABS(lines)) {
1888                 redraw_view(view);
1890         } else {
1891                 int line = lines > 0 ? view->height - lines : 0;
1892                 int end = line + ABS(lines);
1894                 wscrl(view->win, lines);
1896                 for (; line < end; line++) {
1897                         if (!draw_view_line(view, line))
1898                                 break;
1899                 }
1901                 if (redraw_current_line)
1902                         draw_view_line(view, view->lineno - view->offset);
1903         }
1905         redrawwin(view->win);
1906         wrefresh(view->win);
1907         report("");
1910 /* Scroll frontend */
1911 static void
1912 scroll_view(struct view *view, enum request request)
1914         int lines = 1;
1916         assert(view_is_displayed(view));
1918         switch (request) {
1919         case REQ_SCROLL_PAGE_DOWN:
1920                 lines = view->height;
1921         case REQ_SCROLL_LINE_DOWN:
1922                 if (view->offset + lines > view->lines)
1923                         lines = view->lines - view->offset;
1925                 if (lines == 0 || view->offset + view->height >= view->lines) {
1926                         report("Cannot scroll beyond the last line");
1927                         return;
1928                 }
1929                 break;
1931         case REQ_SCROLL_PAGE_UP:
1932                 lines = view->height;
1933         case REQ_SCROLL_LINE_UP:
1934                 if (lines > view->offset)
1935                         lines = view->offset;
1937                 if (lines == 0) {
1938                         report("Cannot scroll beyond the first line");
1939                         return;
1940                 }
1942                 lines = -lines;
1943                 break;
1945         default:
1946                 die("request %d not handled in switch", request);
1947         }
1949         do_scroll_view(view, lines);
1952 /* Cursor moving */
1953 static void
1954 move_view(struct view *view, enum request request)
1956         int scroll_steps = 0;
1957         int steps;
1959         switch (request) {
1960         case REQ_MOVE_FIRST_LINE:
1961                 steps = -view->lineno;
1962                 break;
1964         case REQ_MOVE_LAST_LINE:
1965                 steps = view->lines - view->lineno - 1;
1966                 break;
1968         case REQ_MOVE_PAGE_UP:
1969                 steps = view->height > view->lineno
1970                       ? -view->lineno : -view->height;
1971                 break;
1973         case REQ_MOVE_PAGE_DOWN:
1974                 steps = view->lineno + view->height >= view->lines
1975                       ? view->lines - view->lineno - 1 : view->height;
1976                 break;
1978         case REQ_MOVE_UP:
1979                 steps = -1;
1980                 break;
1982         case REQ_MOVE_DOWN:
1983                 steps = 1;
1984                 break;
1986         default:
1987                 die("request %d not handled in switch", request);
1988         }
1990         if (steps <= 0 && view->lineno == 0) {
1991                 report("Cannot move beyond the first line");
1992                 return;
1994         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
1995                 report("Cannot move beyond the last line");
1996                 return;
1997         }
1999         /* Move the current line */
2000         view->lineno += steps;
2001         assert(0 <= view->lineno && view->lineno < view->lines);
2003         /* Check whether the view needs to be scrolled */
2004         if (view->lineno < view->offset ||
2005             view->lineno >= view->offset + view->height) {
2006                 scroll_steps = steps;
2007                 if (steps < 0 && -steps > view->offset) {
2008                         scroll_steps = -view->offset;
2010                 } else if (steps > 0) {
2011                         if (view->lineno == view->lines - 1 &&
2012                             view->lines > view->height) {
2013                                 scroll_steps = view->lines - view->offset - 1;
2014                                 if (scroll_steps >= view->height)
2015                                         scroll_steps -= view->height - 1;
2016                         }
2017                 }
2018         }
2020         if (!view_is_displayed(view)) {
2021                 view->offset += scroll_steps;
2022                 assert(0 <= view->offset && view->offset < view->lines);
2023                 view->ops->select(view, &view->line[view->lineno]);
2024                 return;
2025         }
2027         /* Repaint the old "current" line if we be scrolling */
2028         if (ABS(steps) < view->height)
2029                 draw_view_line(view, view->lineno - steps - view->offset);
2031         if (scroll_steps) {
2032                 do_scroll_view(view, scroll_steps);
2033                 return;
2034         }
2036         /* Draw the current line */
2037         draw_view_line(view, view->lineno - view->offset);
2039         redrawwin(view->win);
2040         wrefresh(view->win);
2041         report("");
2045 /*
2046  * Searching
2047  */
2049 static void search_view(struct view *view, enum request request);
2051 static bool
2052 find_next_line(struct view *view, unsigned long lineno, struct line *line)
2054         assert(view_is_displayed(view));
2056         if (!view->ops->grep(view, line))
2057                 return FALSE;
2059         if (lineno - view->offset >= view->height) {
2060                 view->offset = lineno;
2061                 view->lineno = lineno;
2062                 redraw_view(view);
2064         } else {
2065                 unsigned long old_lineno = view->lineno - view->offset;
2067                 view->lineno = lineno;
2068                 draw_view_line(view, old_lineno);
2070                 draw_view_line(view, view->lineno - view->offset);
2071                 redrawwin(view->win);
2072                 wrefresh(view->win);
2073         }
2075         report("Line %ld matches '%s'", lineno + 1, view->grep);
2076         return TRUE;
2079 static void
2080 find_next(struct view *view, enum request request)
2082         unsigned long lineno = view->lineno;
2083         int direction;
2085         if (!*view->grep) {
2086                 if (!*opt_search)
2087                         report("No previous search");
2088                 else
2089                         search_view(view, request);
2090                 return;
2091         }
2093         switch (request) {
2094         case REQ_SEARCH:
2095         case REQ_FIND_NEXT:
2096                 direction = 1;
2097                 break;
2099         case REQ_SEARCH_BACK:
2100         case REQ_FIND_PREV:
2101                 direction = -1;
2102                 break;
2104         default:
2105                 return;
2106         }
2108         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2109                 lineno += direction;
2111         /* Note, lineno is unsigned long so will wrap around in which case it
2112          * will become bigger than view->lines. */
2113         for (; lineno < view->lines; lineno += direction) {
2114                 struct line *line = &view->line[lineno];
2116                 if (find_next_line(view, lineno, line))
2117                         return;
2118         }
2120         report("No match found for '%s'", view->grep);
2123 static void
2124 search_view(struct view *view, enum request request)
2126         int regex_err;
2128         if (view->regex) {
2129                 regfree(view->regex);
2130                 *view->grep = 0;
2131         } else {
2132                 view->regex = calloc(1, sizeof(*view->regex));
2133                 if (!view->regex)
2134                         return;
2135         }
2137         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2138         if (regex_err != 0) {
2139                 char buf[SIZEOF_STR] = "unknown error";
2141                 regerror(regex_err, view->regex, buf, sizeof(buf));
2142                 report("Search failed: %s", buf);
2143                 return;
2144         }
2146         string_copy(view->grep, opt_search);
2148         find_next(view, request);
2151 /*
2152  * Incremental updating
2153  */
2155 static void
2156 end_update(struct view *view)
2158         if (!view->pipe)
2159                 return;
2160         set_nonblocking_input(FALSE);
2161         if (view->pipe == stdin)
2162                 fclose(view->pipe);
2163         else
2164                 pclose(view->pipe);
2165         view->pipe = NULL;
2168 static bool
2169 begin_update(struct view *view)
2171         if (view->pipe)
2172                 end_update(view);
2174         if (opt_cmd[0]) {
2175                 string_copy(view->cmd, opt_cmd);
2176                 opt_cmd[0] = 0;
2177                 /* When running random commands, initially show the
2178                  * command in the title. However, it maybe later be
2179                  * overwritten if a commit line is selected. */
2180                 if (view == VIEW(REQ_VIEW_PAGER))
2181                         string_copy(view->ref, view->cmd);
2182                 else
2183                         view->ref[0] = 0;
2185         } else if (view == VIEW(REQ_VIEW_TREE)) {
2186                 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
2187                 char path[SIZEOF_STR];
2189                 if (strcmp(view->vid, view->id))
2190                         opt_path[0] = path[0] = 0;
2191                 else if (sq_quote(path, 0, opt_path) >= sizeof(path))
2192                         return FALSE;
2194                 if (!string_format(view->cmd, format, view->id, path))
2195                         return FALSE;
2197         } else {
2198                 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
2199                 const char *id = view->id;
2201                 if (!string_format(view->cmd, format, id, id, id, id, id))
2202                         return FALSE;
2204                 /* Put the current ref_* value to the view title ref
2205                  * member. This is needed by the blob view. Most other
2206                  * views sets it automatically after loading because the
2207                  * first line is a commit line. */
2208                 string_copy_rev(view->ref, view->id);
2209         }
2211         /* Special case for the pager view. */
2212         if (opt_pipe) {
2213                 view->pipe = opt_pipe;
2214                 opt_pipe = NULL;
2215         } else {
2216                 view->pipe = popen(view->cmd, "r");
2217         }
2219         if (!view->pipe)
2220                 return FALSE;
2222         set_nonblocking_input(TRUE);
2224         view->offset = 0;
2225         view->lines  = 0;
2226         view->lineno = 0;
2227         string_copy_rev(view->vid, view->id);
2229         if (view->line) {
2230                 int i;
2232                 for (i = 0; i < view->lines; i++)
2233                         if (view->line[i].data)
2234                                 free(view->line[i].data);
2236                 free(view->line);
2237                 view->line = NULL;
2238         }
2240         view->start_time = time(NULL);
2242         return TRUE;
2245 #define ITEM_CHUNK_SIZE 256
2246 static void *
2247 realloc_items(void *mem, size_t *size, size_t new_size, size_t item_size)
2249         size_t num_chunks = *size / ITEM_CHUNK_SIZE;
2250         size_t num_chunks_new = (new_size + ITEM_CHUNK_SIZE - 1) / ITEM_CHUNK_SIZE;
2252         if (mem == NULL || num_chunks != num_chunks_new) {
2253                 *size = num_chunks_new * ITEM_CHUNK_SIZE;
2254                 mem = realloc(mem, *size * item_size);
2255         }
2257         return mem;
2260 static struct line *
2261 realloc_lines(struct view *view, size_t line_size)
2263         size_t alloc = view->line_alloc;
2264         struct line *tmp = realloc_items(view->line, &alloc, line_size,
2265                                          sizeof(*view->line));
2267         if (!tmp)
2268                 return NULL;
2270         view->line = tmp;
2271         view->line_alloc = alloc;
2272         view->line_size = line_size;
2273         return view->line;
2276 static bool
2277 update_view(struct view *view)
2279         char in_buffer[BUFSIZ];
2280         char out_buffer[BUFSIZ * 2];
2281         char *line;
2282         /* The number of lines to read. If too low it will cause too much
2283          * redrawing (and possible flickering), if too high responsiveness
2284          * will suffer. */
2285         unsigned long lines = view->height;
2286         int redraw_from = -1;
2288         if (!view->pipe)
2289                 return TRUE;
2291         /* Only redraw if lines are visible. */
2292         if (view->offset + view->height >= view->lines)
2293                 redraw_from = view->lines - view->offset;
2295         /* FIXME: This is probably not perfect for backgrounded views. */
2296         if (!realloc_lines(view, view->lines + lines))
2297                 goto alloc_error;
2299         while ((line = fgets(in_buffer, sizeof(in_buffer), view->pipe))) {
2300                 size_t linelen = strlen(line);
2302                 if (linelen)
2303                         line[linelen - 1] = 0;
2305                 if (opt_iconv != ICONV_NONE) {
2306                         ICONV_CONST char *inbuf = line;
2307                         size_t inlen = linelen;
2309                         char *outbuf = out_buffer;
2310                         size_t outlen = sizeof(out_buffer);
2312                         size_t ret;
2314                         ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2315                         if (ret != (size_t) -1) {
2316                                 line = out_buffer;
2317                                 linelen = strlen(out_buffer);
2318                         }
2319                 }
2321                 if (!view->ops->read(view, line))
2322                         goto alloc_error;
2324                 if (lines-- == 1)
2325                         break;
2326         }
2328         {
2329                 int digits;
2331                 lines = view->lines;
2332                 for (digits = 0; lines; digits++)
2333                         lines /= 10;
2335                 /* Keep the displayed view in sync with line number scaling. */
2336                 if (digits != view->digits) {
2337                         view->digits = digits;
2338                         redraw_from = 0;
2339                 }
2340         }
2342         if (!view_is_displayed(view))
2343                 goto check_pipe;
2345         if (view == VIEW(REQ_VIEW_TREE)) {
2346                 /* Clear the view and redraw everything since the tree sorting
2347                  * might have rearranged things. */
2348                 redraw_view(view);
2350         } else if (redraw_from >= 0) {
2351                 /* If this is an incremental update, redraw the previous line
2352                  * since for commits some members could have changed when
2353                  * loading the main view. */
2354                 if (redraw_from > 0)
2355                         redraw_from--;
2357                 /* Since revision graph visualization requires knowledge
2358                  * about the parent commit, it causes a further one-off
2359                  * needed to be redrawn for incremental updates. */
2360                 if (redraw_from > 0 && opt_rev_graph)
2361                         redraw_from--;
2363                 /* Incrementally draw avoids flickering. */
2364                 redraw_view_from(view, redraw_from);
2365         }
2367         if (view == VIEW(REQ_VIEW_BLAME))
2368                 redraw_view_dirty(view);
2370         /* Update the title _after_ the redraw so that if the redraw picks up a
2371          * commit reference in view->ref it'll be available here. */
2372         update_view_title(view);
2374 check_pipe:
2375         if (ferror(view->pipe)) {
2376                 report("Failed to read: %s", strerror(errno));
2377                 goto end;
2379         } else if (feof(view->pipe)) {
2380                 report("");
2381                 goto end;
2382         }
2384         return TRUE;
2386 alloc_error:
2387         report("Allocation failure");
2389 end:
2390         if (view->ops->read(view, NULL))
2391                 end_update(view);
2392         return FALSE;
2395 static struct line *
2396 add_line_data(struct view *view, void *data, enum line_type type)
2398         struct line *line = &view->line[view->lines++];
2400         memset(line, 0, sizeof(*line));
2401         line->type = type;
2402         line->data = data;
2404         return line;
2407 static struct line *
2408 add_line_text(struct view *view, char *data, enum line_type type)
2410         if (data)
2411                 data = strdup(data);
2413         return data ? add_line_data(view, data, type) : NULL;
2417 /*
2418  * View opening
2419  */
2421 enum open_flags {
2422         OPEN_DEFAULT = 0,       /* Use default view switching. */
2423         OPEN_SPLIT = 1,         /* Split current view. */
2424         OPEN_BACKGROUNDED = 2,  /* Backgrounded. */
2425         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
2426         OPEN_NOMAXIMIZE = 8     /* Do not maximize the current view. */
2427 };
2429 static void
2430 open_view(struct view *prev, enum request request, enum open_flags flags)
2432         bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
2433         bool split = !!(flags & OPEN_SPLIT);
2434         bool reload = !!(flags & OPEN_RELOAD);
2435         bool nomaximize = !!(flags & OPEN_NOMAXIMIZE);
2436         struct view *view = VIEW(request);
2437         int nviews = displayed_views();
2438         struct view *base_view = display[0];
2440         if (view == prev && nviews == 1 && !reload) {
2441                 report("Already in %s view", view->name);
2442                 return;
2443         }
2445         if (view->git_dir && !opt_git_dir[0]) {
2446                 report("The %s view is disabled in pager view", view->name);
2447                 return;
2448         }
2450         if (split) {
2451                 display[1] = view;
2452                 if (!backgrounded)
2453                         current_view = 1;
2454         } else if (!nomaximize) {
2455                 /* Maximize the current view. */
2456                 memset(display, 0, sizeof(display));
2457                 current_view = 0;
2458                 display[current_view] = view;
2459         }
2461         /* Resize the view when switching between split- and full-screen,
2462          * or when switching between two different full-screen views. */
2463         if (nviews != displayed_views() ||
2464             (nviews == 1 && base_view != display[0]))
2465                 resize_display();
2467         if (view->ops->open) {
2468                 if (!view->ops->open(view)) {
2469                         report("Failed to load %s view", view->name);
2470                         return;
2471                 }
2473         } else if ((reload || strcmp(view->vid, view->id)) &&
2474                    !begin_update(view)) {
2475                 report("Failed to load %s view", view->name);
2476                 return;
2477         }
2479         if (split && prev->lineno - prev->offset >= prev->height) {
2480                 /* Take the title line into account. */
2481                 int lines = prev->lineno - prev->offset - prev->height + 1;
2483                 /* Scroll the view that was split if the current line is
2484                  * outside the new limited view. */
2485                 do_scroll_view(prev, lines);
2486         }
2488         if (prev && view != prev) {
2489                 if (split && !backgrounded) {
2490                         /* "Blur" the previous view. */
2491                         update_view_title(prev);
2492                 }
2494                 view->parent = prev;
2495         }
2497         if (view->pipe && view->lines == 0) {
2498                 /* Clear the old view and let the incremental updating refill
2499                  * the screen. */
2500                 werase(view->win);
2501                 report("");
2502         } else {
2503                 redraw_view(view);
2504                 report("");
2505         }
2507         /* If the view is backgrounded the above calls to report()
2508          * won't redraw the view title. */
2509         if (backgrounded)
2510                 update_view_title(view);
2513 static void
2514 open_external_viewer(const char *cmd)
2516         def_prog_mode();           /* save current tty modes */
2517         endwin();                  /* restore original tty modes */
2518         system(cmd);
2519         fprintf(stderr, "Press Enter to continue");
2520         getc(stdin);
2521         reset_prog_mode();
2522         redraw_display();
2525 static void
2526 open_mergetool(const char *file)
2528         char cmd[SIZEOF_STR];
2529         char file_sq[SIZEOF_STR];
2531         if (sq_quote(file_sq, 0, file) < sizeof(file_sq) &&
2532             string_format(cmd, "git mergetool %s", file_sq)) {
2533                 open_external_viewer(cmd);
2534         }
2537 static void
2538 open_editor(bool from_root, const char *file)
2540         char cmd[SIZEOF_STR];
2541         char file_sq[SIZEOF_STR];
2542         char *editor;
2543         char *prefix = from_root ? opt_cdup : "";
2545         editor = getenv("GIT_EDITOR");
2546         if (!editor && *opt_editor)
2547                 editor = opt_editor;
2548         if (!editor)
2549                 editor = getenv("VISUAL");
2550         if (!editor)
2551                 editor = getenv("EDITOR");
2552         if (!editor)
2553                 editor = "vi";
2555         if (sq_quote(file_sq, 0, file) < sizeof(file_sq) &&
2556             string_format(cmd, "%s %s%s", editor, prefix, file_sq)) {
2557                 open_external_viewer(cmd);
2558         }
2561 static void
2562 open_run_request(enum request request)
2564         struct run_request *req = get_run_request(request);
2565         char buf[SIZEOF_STR * 2];
2566         size_t bufpos;
2567         char *cmd;
2569         if (!req) {
2570                 report("Unknown run request");
2571                 return;
2572         }
2574         bufpos = 0;
2575         cmd = req->cmd;
2577         while (cmd) {
2578                 char *next = strstr(cmd, "%(");
2579                 int len = next - cmd;
2580                 char *value;
2582                 if (!next) {
2583                         len = strlen(cmd);
2584                         value = "";
2586                 } else if (!strncmp(next, "%(head)", 7)) {
2587                         value = ref_head;
2589                 } else if (!strncmp(next, "%(commit)", 9)) {
2590                         value = ref_commit;
2592                 } else if (!strncmp(next, "%(blob)", 7)) {
2593                         value = ref_blob;
2595                 } else {
2596                         report("Unknown replacement in run request: `%s`", req->cmd);
2597                         return;
2598                 }
2600                 if (!string_format_from(buf, &bufpos, "%.*s%s", len, cmd, value))
2601                         return;
2603                 if (next)
2604                         next = strchr(next, ')') + 1;
2605                 cmd = next;
2606         }
2608         open_external_viewer(buf);
2611 /*
2612  * User request switch noodle
2613  */
2615 static int
2616 view_driver(struct view *view, enum request request)
2618         int i;
2620         if (request == REQ_NONE) {
2621                 doupdate();
2622                 return TRUE;
2623         }
2625         if (request > REQ_NONE) {
2626                 open_run_request(request);
2627                 /* FIXME: When all views can refresh always do this. */
2628                 if (view == VIEW(REQ_VIEW_STATUS) ||
2629                     view == VIEW(REQ_VIEW_STAGE))
2630                         request = REQ_REFRESH;
2631                 else
2632                         return TRUE;
2633         }
2635         if (view && view->lines) {
2636                 request = view->ops->request(view, request, &view->line[view->lineno]);
2637                 if (request == REQ_NONE)
2638                         return TRUE;
2639         }
2641         switch (request) {
2642         case REQ_MOVE_UP:
2643         case REQ_MOVE_DOWN:
2644         case REQ_MOVE_PAGE_UP:
2645         case REQ_MOVE_PAGE_DOWN:
2646         case REQ_MOVE_FIRST_LINE:
2647         case REQ_MOVE_LAST_LINE:
2648                 move_view(view, request);
2649                 break;
2651         case REQ_SCROLL_LINE_DOWN:
2652         case REQ_SCROLL_LINE_UP:
2653         case REQ_SCROLL_PAGE_DOWN:
2654         case REQ_SCROLL_PAGE_UP:
2655                 scroll_view(view, request);
2656                 break;
2658         case REQ_VIEW_BLAME:
2659                 if (!opt_file[0]) {
2660                         report("No file chosen, press %s to open tree view",
2661                                get_key(REQ_VIEW_TREE));
2662                         break;
2663                 }
2664                 open_view(view, request, OPEN_DEFAULT);
2665                 break;
2667         case REQ_VIEW_BLOB:
2668                 if (!ref_blob[0]) {
2669                         report("No file chosen, press %s to open tree view",
2670                                get_key(REQ_VIEW_TREE));
2671                         break;
2672                 }
2673                 open_view(view, request, OPEN_DEFAULT);
2674                 break;
2676         case REQ_VIEW_PAGER:
2677                 if (!opt_pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
2678                         report("No pager content, press %s to run command from prompt",
2679                                get_key(REQ_PROMPT));
2680                         break;
2681                 }
2682                 open_view(view, request, OPEN_DEFAULT);
2683                 break;
2685         case REQ_VIEW_STAGE:
2686                 if (!VIEW(REQ_VIEW_STAGE)->lines) {
2687                         report("No stage content, press %s to open the status view and choose file",
2688                                get_key(REQ_VIEW_STATUS));
2689                         break;
2690                 }
2691                 open_view(view, request, OPEN_DEFAULT);
2692                 break;
2694         case REQ_VIEW_STATUS:
2695                 if (opt_is_inside_work_tree == FALSE) {
2696                         report("The status view requires a working tree");
2697                         break;
2698                 }
2699                 open_view(view, request, OPEN_DEFAULT);
2700                 break;
2702         case REQ_VIEW_MAIN:
2703         case REQ_VIEW_DIFF:
2704         case REQ_VIEW_LOG:
2705         case REQ_VIEW_TREE:
2706         case REQ_VIEW_HELP:
2707                 open_view(view, request, OPEN_DEFAULT);
2708                 break;
2710         case REQ_NEXT:
2711         case REQ_PREVIOUS:
2712                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
2714                 if ((view == VIEW(REQ_VIEW_DIFF) &&
2715                      view->parent == VIEW(REQ_VIEW_MAIN)) ||
2716                    (view == VIEW(REQ_VIEW_DIFF) &&
2717                      view->parent == VIEW(REQ_VIEW_BLAME)) ||
2718                    (view == VIEW(REQ_VIEW_STAGE) &&
2719                      view->parent == VIEW(REQ_VIEW_STATUS)) ||
2720                    (view == VIEW(REQ_VIEW_BLOB) &&
2721                      view->parent == VIEW(REQ_VIEW_TREE))) {
2722                         int line;
2724                         view = view->parent;
2725                         line = view->lineno;
2726                         move_view(view, request);
2727                         if (view_is_displayed(view))
2728                                 update_view_title(view);
2729                         if (line != view->lineno)
2730                                 view->ops->request(view, REQ_ENTER,
2731                                                    &view->line[view->lineno]);
2733                 } else {
2734                         move_view(view, request);
2735                 }
2736                 break;
2738         case REQ_VIEW_NEXT:
2739         {
2740                 int nviews = displayed_views();
2741                 int next_view = (current_view + 1) % nviews;
2743                 if (next_view == current_view) {
2744                         report("Only one view is displayed");
2745                         break;
2746                 }
2748                 current_view = next_view;
2749                 /* Blur out the title of the previous view. */
2750                 update_view_title(view);
2751                 report("");
2752                 break;
2753         }
2754         case REQ_REFRESH:
2755                 report("Refreshing is not yet supported for the %s view", view->name);
2756                 break;
2758         case REQ_MAXIMIZE:
2759                 if (displayed_views() == 2)
2760                         open_view(view, VIEW_REQ(view), OPEN_DEFAULT);
2761                 break;
2763         case REQ_TOGGLE_LINENO:
2764                 opt_line_number = !opt_line_number;
2765                 redraw_display();
2766                 break;
2768         case REQ_TOGGLE_DATE:
2769                 opt_date = !opt_date;
2770                 redraw_display();
2771                 break;
2773         case REQ_TOGGLE_AUTHOR:
2774                 opt_author = !opt_author;
2775                 redraw_display();
2776                 break;
2778         case REQ_TOGGLE_REV_GRAPH:
2779                 opt_rev_graph = !opt_rev_graph;
2780                 redraw_display();
2781                 break;
2783         case REQ_TOGGLE_REFS:
2784                 opt_show_refs = !opt_show_refs;
2785                 redraw_display();
2786                 break;
2788         case REQ_PROMPT:
2789                 /* Always reload^Wrerun commands from the prompt. */
2790                 open_view(view, opt_request, OPEN_RELOAD);
2791                 break;
2793         case REQ_SEARCH:
2794         case REQ_SEARCH_BACK:
2795                 search_view(view, request);
2796                 break;
2798         case REQ_FIND_NEXT:
2799         case REQ_FIND_PREV:
2800                 find_next(view, request);
2801                 break;
2803         case REQ_STOP_LOADING:
2804                 for (i = 0; i < ARRAY_SIZE(views); i++) {
2805                         view = &views[i];
2806                         if (view->pipe)
2807                                 report("Stopped loading the %s view", view->name),
2808                         end_update(view);
2809                 }
2810                 break;
2812         case REQ_SHOW_VERSION:
2813                 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
2814                 return TRUE;
2816         case REQ_SCREEN_RESIZE:
2817                 resize_display();
2818                 /* Fall-through */
2819         case REQ_SCREEN_REDRAW:
2820                 redraw_display();
2821                 break;
2823         case REQ_EDIT:
2824                 report("Nothing to edit");
2825                 break;
2828         case REQ_ENTER:
2829                 report("Nothing to enter");
2830                 break;
2833         case REQ_VIEW_CLOSE:
2834                 /* XXX: Mark closed views by letting view->parent point to the
2835                  * view itself. Parents to closed view should never be
2836                  * followed. */
2837                 if (view->parent &&
2838                     view->parent->parent != view->parent) {
2839                         memset(display, 0, sizeof(display));
2840                         current_view = 0;
2841                         display[current_view] = view->parent;
2842                         view->parent = view;
2843                         resize_display();
2844                         redraw_display();
2845                         break;
2846                 }
2847                 /* Fall-through */
2848         case REQ_QUIT:
2849                 return FALSE;
2851         default:
2852                 /* An unknown key will show most commonly used commands. */
2853                 report("Unknown key, press 'h' for help");
2854                 return TRUE;
2855         }
2857         return TRUE;
2861 /*
2862  * Pager backend
2863  */
2865 static bool
2866 pager_draw(struct view *view, struct line *line, unsigned int lineno)
2868         char *text = line->data;
2870         if (opt_line_number && draw_lineno(view, lineno))
2871                 return TRUE;
2873         draw_text(view, line->type, text, TRUE);
2874         return TRUE;
2877 static bool
2878 add_describe_ref(char *buf, size_t *bufpos, char *commit_id, const char *sep)
2880         char refbuf[SIZEOF_STR];
2881         char *ref = NULL;
2882         FILE *pipe;
2884         if (!string_format(refbuf, "git describe %s 2>/dev/null", commit_id))
2885                 return TRUE;
2887         pipe = popen(refbuf, "r");
2888         if (!pipe)
2889                 return TRUE;
2891         if ((ref = fgets(refbuf, sizeof(refbuf), pipe)))
2892                 ref = chomp_string(ref);
2893         pclose(pipe);
2895         if (!ref || !*ref)
2896                 return TRUE;
2898         /* This is the only fatal call, since it can "corrupt" the buffer. */
2899         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
2900                 return FALSE;
2902         return TRUE;
2905 static void
2906 add_pager_refs(struct view *view, struct line *line)
2908         char buf[SIZEOF_STR];
2909         char *commit_id = (char *)line->data + STRING_SIZE("commit ");
2910         struct ref **refs;
2911         size_t bufpos = 0, refpos = 0;
2912         const char *sep = "Refs: ";
2913         bool is_tag = FALSE;
2915         assert(line->type == LINE_COMMIT);
2917         refs = get_refs(commit_id);
2918         if (!refs) {
2919                 if (view == VIEW(REQ_VIEW_DIFF))
2920                         goto try_add_describe_ref;
2921                 return;
2922         }
2924         do {
2925                 struct ref *ref = refs[refpos];
2926                 char *fmt = ref->tag    ? "%s[%s]" :
2927                             ref->remote ? "%s<%s>" : "%s%s";
2929                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
2930                         return;
2931                 sep = ", ";
2932                 if (ref->tag)
2933                         is_tag = TRUE;
2934         } while (refs[refpos++]->next);
2936         if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
2937 try_add_describe_ref:
2938                 /* Add <tag>-g<commit_id> "fake" reference. */
2939                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
2940                         return;
2941         }
2943         if (bufpos == 0)
2944                 return;
2946         if (!realloc_lines(view, view->line_size + 1))
2947                 return;
2949         add_line_text(view, buf, LINE_PP_REFS);
2952 static bool
2953 pager_read(struct view *view, char *data)
2955         struct line *line;
2957         if (!data)
2958                 return TRUE;
2960         line = add_line_text(view, data, get_line_type(data));
2961         if (!line)
2962                 return FALSE;
2964         if (line->type == LINE_COMMIT &&
2965             (view == VIEW(REQ_VIEW_DIFF) ||
2966              view == VIEW(REQ_VIEW_LOG)))
2967                 add_pager_refs(view, line);
2969         return TRUE;
2972 static enum request
2973 pager_request(struct view *view, enum request request, struct line *line)
2975         int split = 0;
2977         if (request != REQ_ENTER)
2978                 return request;
2980         if (line->type == LINE_COMMIT &&
2981            (view == VIEW(REQ_VIEW_LOG) ||
2982             view == VIEW(REQ_VIEW_PAGER))) {
2983                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
2984                 split = 1;
2985         }
2987         /* Always scroll the view even if it was split. That way
2988          * you can use Enter to scroll through the log view and
2989          * split open each commit diff. */
2990         scroll_view(view, REQ_SCROLL_LINE_DOWN);
2992         /* FIXME: A minor workaround. Scrolling the view will call report("")
2993          * but if we are scrolling a non-current view this won't properly
2994          * update the view title. */
2995         if (split)
2996                 update_view_title(view);
2998         return REQ_NONE;
3001 static bool
3002 pager_grep(struct view *view, struct line *line)
3004         regmatch_t pmatch;
3005         char *text = line->data;
3007         if (!*text)
3008                 return FALSE;
3010         if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
3011                 return FALSE;
3013         return TRUE;
3016 static void
3017 pager_select(struct view *view, struct line *line)
3019         if (line->type == LINE_COMMIT) {
3020                 char *text = (char *)line->data + STRING_SIZE("commit ");
3022                 if (view != VIEW(REQ_VIEW_PAGER))
3023                         string_copy_rev(view->ref, text);
3024                 string_copy_rev(ref_commit, text);
3025         }
3028 static struct view_ops pager_ops = {
3029         "line",
3030         NULL,
3031         pager_read,
3032         pager_draw,
3033         pager_request,
3034         pager_grep,
3035         pager_select,
3036 };
3039 /*
3040  * Help backend
3041  */
3043 static bool
3044 help_open(struct view *view)
3046         char buf[BUFSIZ];
3047         int lines = ARRAY_SIZE(req_info) + 2;
3048         int i;
3050         if (view->lines > 0)
3051                 return TRUE;
3053         for (i = 0; i < ARRAY_SIZE(req_info); i++)
3054                 if (!req_info[i].request)
3055                         lines++;
3057         lines += run_requests + 1;
3059         view->line = calloc(lines, sizeof(*view->line));
3060         if (!view->line)
3061                 return FALSE;
3063         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
3065         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
3066                 char *key;
3068                 if (req_info[i].request == REQ_NONE)
3069                         continue;
3071                 if (!req_info[i].request) {
3072                         add_line_text(view, "", LINE_DEFAULT);
3073                         add_line_text(view, req_info[i].help, LINE_DEFAULT);
3074                         continue;
3075                 }
3077                 key = get_key(req_info[i].request);
3078                 if (!*key)
3079                         key = "(no key defined)";
3081                 if (!string_format(buf, "    %-25s %s", key, req_info[i].help))
3082                         continue;
3084                 add_line_text(view, buf, LINE_DEFAULT);
3085         }
3087         if (run_requests) {
3088                 add_line_text(view, "", LINE_DEFAULT);
3089                 add_line_text(view, "External commands:", LINE_DEFAULT);
3090         }
3092         for (i = 0; i < run_requests; i++) {
3093                 struct run_request *req = get_run_request(REQ_NONE + i + 1);
3094                 char *key;
3096                 if (!req)
3097                         continue;
3099                 key = get_key_name(req->key);
3100                 if (!*key)
3101                         key = "(no key defined)";
3103                 if (!string_format(buf, "    %-10s %-14s `%s`",
3104                                    keymap_table[req->keymap].name,
3105                                    key, req->cmd))
3106                         continue;
3108                 add_line_text(view, buf, LINE_DEFAULT);
3109         }
3111         return TRUE;
3114 static struct view_ops help_ops = {
3115         "line",
3116         help_open,
3117         NULL,
3118         pager_draw,
3119         pager_request,
3120         pager_grep,
3121         pager_select,
3122 };
3125 /*
3126  * Tree backend
3127  */
3129 struct tree_stack_entry {
3130         struct tree_stack_entry *prev;  /* Entry below this in the stack */
3131         unsigned long lineno;           /* Line number to restore */
3132         char *name;                     /* Position of name in opt_path */
3133 };
3135 /* The top of the path stack. */
3136 static struct tree_stack_entry *tree_stack = NULL;
3137 unsigned long tree_lineno = 0;
3139 static void
3140 pop_tree_stack_entry(void)
3142         struct tree_stack_entry *entry = tree_stack;
3144         tree_lineno = entry->lineno;
3145         entry->name[0] = 0;
3146         tree_stack = entry->prev;
3147         free(entry);
3150 static void
3151 push_tree_stack_entry(char *name, unsigned long lineno)
3153         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
3154         size_t pathlen = strlen(opt_path);
3156         if (!entry)
3157                 return;
3159         entry->prev = tree_stack;
3160         entry->name = opt_path + pathlen;
3161         tree_stack = entry;
3163         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
3164                 pop_tree_stack_entry();
3165                 return;
3166         }
3168         /* Move the current line to the first tree entry. */
3169         tree_lineno = 1;
3170         entry->lineno = lineno;
3173 /* Parse output from git-ls-tree(1):
3174  *
3175  * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile
3176  * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README
3177  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
3178  * 100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38 web.conf
3179  */
3181 #define SIZEOF_TREE_ATTR \
3182         STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t")
3184 #define TREE_UP_FORMAT "040000 tree %s\t.."
3186 static int
3187 tree_compare_entry(enum line_type type1, char *name1,
3188                    enum line_type type2, char *name2)
3190         if (type1 != type2) {
3191                 if (type1 == LINE_TREE_DIR)
3192                         return -1;
3193                 return 1;
3194         }
3196         return strcmp(name1, name2);
3199 static char *
3200 tree_path(struct line *line)
3202         char *path = line->data;
3204         return path + SIZEOF_TREE_ATTR;
3207 static bool
3208 tree_read(struct view *view, char *text)
3210         size_t textlen = text ? strlen(text) : 0;
3211         char buf[SIZEOF_STR];
3212         unsigned long pos;
3213         enum line_type type;
3214         bool first_read = view->lines == 0;
3216         if (!text)
3217                 return TRUE;
3218         if (textlen <= SIZEOF_TREE_ATTR)
3219                 return FALSE;
3221         type = text[STRING_SIZE("100644 ")] == 't'
3222              ? LINE_TREE_DIR : LINE_TREE_FILE;
3224         if (first_read) {
3225                 /* Add path info line */
3226                 if (!string_format(buf, "Directory path /%s", opt_path) ||
3227                     !realloc_lines(view, view->line_size + 1) ||
3228                     !add_line_text(view, buf, LINE_DEFAULT))
3229                         return FALSE;
3231                 /* Insert "link" to parent directory. */
3232                 if (*opt_path) {
3233                         if (!string_format(buf, TREE_UP_FORMAT, view->ref) ||
3234                             !realloc_lines(view, view->line_size + 1) ||
3235                             !add_line_text(view, buf, LINE_TREE_DIR))
3236                                 return FALSE;
3237                 }
3238         }
3240         /* Strip the path part ... */
3241         if (*opt_path) {
3242                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
3243                 size_t striplen = strlen(opt_path);
3244                 char *path = text + SIZEOF_TREE_ATTR;
3246                 if (pathlen > striplen)
3247                         memmove(path, path + striplen,
3248                                 pathlen - striplen + 1);
3249         }
3251         /* Skip "Directory ..." and ".." line. */
3252         for (pos = 1 + !!*opt_path; pos < view->lines; pos++) {
3253                 struct line *line = &view->line[pos];
3254                 char *path1 = tree_path(line);
3255                 char *path2 = text + SIZEOF_TREE_ATTR;
3256                 int cmp = tree_compare_entry(line->type, path1, type, path2);
3258                 if (cmp <= 0)
3259                         continue;
3261                 text = strdup(text);
3262                 if (!text)
3263                         return FALSE;
3265                 if (view->lines > pos)
3266                         memmove(&view->line[pos + 1], &view->line[pos],
3267                                 (view->lines - pos) * sizeof(*line));
3269                 line = &view->line[pos];
3270                 line->data = text;
3271                 line->type = type;
3272                 view->lines++;
3273                 return TRUE;
3274         }
3276         if (!add_line_text(view, text, type))
3277                 return FALSE;
3279         if (tree_lineno > view->lineno) {
3280                 view->lineno = tree_lineno;
3281                 tree_lineno = 0;
3282         }
3284         return TRUE;
3287 static enum request
3288 tree_request(struct view *view, enum request request, struct line *line)
3290         enum open_flags flags;
3292         if (request == REQ_VIEW_BLAME) {
3293                 char *filename = tree_path(line);
3295                 if (line->type == LINE_TREE_DIR) {
3296                         report("Cannot show blame for directory %s", opt_path);
3297                         return REQ_NONE;
3298                 }
3300                 string_copy(opt_ref, view->vid);
3301                 string_format(opt_file, "%s%s", opt_path, filename);
3302                 return request;
3303         }
3304         if (request == REQ_TREE_PARENT) {
3305                 if (*opt_path) {
3306                         /* fake 'cd  ..' */
3307                         request = REQ_ENTER;
3308                         line = &view->line[1];
3309                 } else {
3310                         /* quit view if at top of tree */
3311                         return REQ_VIEW_CLOSE;
3312                 }
3313         }
3314         if (request != REQ_ENTER)
3315                 return request;
3317         /* Cleanup the stack if the tree view is at a different tree. */
3318         while (!*opt_path && tree_stack)
3319                 pop_tree_stack_entry();
3321         switch (line->type) {
3322         case LINE_TREE_DIR:
3323                 /* Depending on whether it is a subdir or parent (updir?) link
3324                  * mangle the path buffer. */
3325                 if (line == &view->line[1] && *opt_path) {
3326                         pop_tree_stack_entry();
3328                 } else {
3329                         char *basename = tree_path(line);
3331                         push_tree_stack_entry(basename, view->lineno);
3332                 }
3334                 /* Trees and subtrees share the same ID, so they are not not
3335                  * unique like blobs. */
3336                 flags = OPEN_RELOAD;
3337                 request = REQ_VIEW_TREE;
3338                 break;
3340         case LINE_TREE_FILE:
3341                 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
3342                 request = REQ_VIEW_BLOB;
3343                 break;
3345         default:
3346                 return TRUE;
3347         }
3349         open_view(view, request, flags);
3350         if (request == REQ_VIEW_TREE) {
3351                 view->lineno = tree_lineno;
3352         }
3354         return REQ_NONE;
3357 static void
3358 tree_select(struct view *view, struct line *line)
3360         char *text = (char *)line->data + STRING_SIZE("100644 blob ");
3362         if (line->type == LINE_TREE_FILE) {
3363                 string_copy_rev(ref_blob, text);
3365         } else if (line->type != LINE_TREE_DIR) {
3366                 return;
3367         }
3369         string_copy_rev(view->ref, text);
3372 static struct view_ops tree_ops = {
3373         "file",
3374         NULL,
3375         tree_read,
3376         pager_draw,
3377         tree_request,
3378         pager_grep,
3379         tree_select,
3380 };
3382 static bool
3383 blob_read(struct view *view, char *line)
3385         if (!line)
3386                 return TRUE;
3387         return add_line_text(view, line, LINE_DEFAULT) != NULL;
3390 static struct view_ops blob_ops = {
3391         "line",
3392         NULL,
3393         blob_read,
3394         pager_draw,
3395         pager_request,
3396         pager_grep,
3397         pager_select,
3398 };
3400 /*
3401  * Blame backend
3402  *
3403  * Loading the blame view is a two phase job:
3404  *
3405  *  1. File content is read either using opt_file from the
3406  *     filesystem or using git-cat-file.
3407  *  2. Then blame information is incrementally added by
3408  *     reading output from git-blame.
3409  */
3411 struct blame_commit {
3412         char id[SIZEOF_REV];            /* SHA1 ID. */
3413         char title[128];                /* First line of the commit message. */
3414         char author[75];                /* Author of the commit. */
3415         struct tm time;                 /* Date from the author ident. */
3416         char filename[128];             /* Name of file. */
3417 };
3419 struct blame {
3420         struct blame_commit *commit;
3421         unsigned int header:1;
3422         char text[1];
3423 };
3425 #define BLAME_CAT_FILE_CMD "git cat-file blob %s:%s"
3426 #define BLAME_INCREMENTAL_CMD "git blame --incremental %s %s"
3428 static bool
3429 blame_open(struct view *view)
3431         char path[SIZEOF_STR];
3432         char ref[SIZEOF_STR] = "";
3434         if (sq_quote(path, 0, opt_file) >= sizeof(path))
3435                 return FALSE;
3437         if (*opt_ref && sq_quote(ref, 0, opt_ref) >= sizeof(ref))
3438                 return FALSE;
3440         if (*opt_ref) {
3441                 if (!string_format(view->cmd, BLAME_CAT_FILE_CMD, ref, path))
3442                         return FALSE;
3443         } else {
3444                 view->pipe = fopen(opt_file, "r");
3445                 if (!view->pipe &&
3446                     !string_format(view->cmd, BLAME_CAT_FILE_CMD, "HEAD", path))
3447                         return FALSE;
3448         }
3450         if (!view->pipe)
3451                 view->pipe = popen(view->cmd, "r");
3452         if (!view->pipe)
3453                 return FALSE;
3455         if (!string_format(view->cmd, BLAME_INCREMENTAL_CMD, ref, path))
3456                 return FALSE;
3458         string_format(view->ref, "%s ...", opt_file);
3459         string_copy_rev(view->vid, opt_file);
3460         set_nonblocking_input(TRUE);
3462         if (view->line) {
3463                 int i;
3465                 for (i = 0; i < view->lines; i++)
3466                         free(view->line[i].data);
3467                 free(view->line);
3468         }
3470         view->lines = view->line_alloc = view->line_size = view->lineno = 0;
3471         view->offset = view->lines  = view->lineno = 0;
3472         view->line = NULL;
3473         view->start_time = time(NULL);
3475         return TRUE;
3478 static struct blame_commit *
3479 get_blame_commit(struct view *view, const char *id)
3481         size_t i;
3483         for (i = 0; i < view->lines; i++) {
3484                 struct blame *blame = view->line[i].data;
3486                 if (!blame->commit)
3487                         continue;
3489                 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
3490                         return blame->commit;
3491         }
3493         {
3494                 struct blame_commit *commit = calloc(1, sizeof(*commit));
3496                 if (commit)
3497                         string_ncopy(commit->id, id, SIZEOF_REV);
3498                 return commit;
3499         }
3502 static bool
3503 parse_number(char **posref, size_t *number, size_t min, size_t max)
3505         char *pos = *posref;
3507         *posref = NULL;
3508         pos = strchr(pos + 1, ' ');
3509         if (!pos || !isdigit(pos[1]))
3510                 return FALSE;
3511         *number = atoi(pos + 1);
3512         if (*number < min || *number > max)
3513                 return FALSE;
3515         *posref = pos;
3516         return TRUE;
3519 static struct blame_commit *
3520 parse_blame_commit(struct view *view, char *text, int *blamed)
3522         struct blame_commit *commit;
3523         struct blame *blame;
3524         char *pos = text + SIZEOF_REV - 1;
3525         size_t lineno;
3526         size_t group;
3528         if (strlen(text) <= SIZEOF_REV || *pos != ' ')
3529                 return NULL;
3531         if (!parse_number(&pos, &lineno, 1, view->lines) ||
3532             !parse_number(&pos, &group, 1, view->lines - lineno + 1))
3533                 return NULL;
3535         commit = get_blame_commit(view, text);
3536         if (!commit)
3537                 return NULL;
3539         *blamed += group;
3540         while (group--) {
3541                 struct line *line = &view->line[lineno + group - 1];
3543                 blame = line->data;
3544                 blame->commit = commit;
3545                 blame->header = !group;
3546                 line->dirty = 1;
3547         }
3549         return commit;
3552 static bool
3553 blame_read_file(struct view *view, char *line)
3555         if (!line) {
3556                 FILE *pipe = NULL;
3558                 if (view->lines > 0)
3559                         pipe = popen(view->cmd, "r");
3560                 else if (!view->parent)
3561                         die("No blame exist for %s", view->vid);
3562                 view->cmd[0] = 0;
3563                 if (!pipe) {
3564                         report("Failed to load blame data");
3565                         return TRUE;
3566                 }
3568                 fclose(view->pipe);
3569                 view->pipe = pipe;
3570                 return FALSE;
3572         } else {
3573                 size_t linelen = strlen(line);
3574                 struct blame *blame = malloc(sizeof(*blame) + linelen);
3576                 if (!line)
3577                         return FALSE;
3579                 blame->commit = NULL;
3580                 strncpy(blame->text, line, linelen);
3581                 blame->text[linelen] = 0;
3582                 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
3583         }
3586 static bool
3587 match_blame_header(const char *name, char **line)
3589         size_t namelen = strlen(name);
3590         bool matched = !strncmp(name, *line, namelen);
3592         if (matched)
3593                 *line += namelen;
3595         return matched;
3598 static bool
3599 blame_read(struct view *view, char *line)
3601         static struct blame_commit *commit = NULL;
3602         static int blamed = 0;
3603         static time_t author_time;
3605         if (*view->cmd)
3606                 return blame_read_file(view, line);
3608         if (!line) {
3609                 /* Reset all! */
3610                 commit = NULL;
3611                 blamed = 0;
3612                 string_format(view->ref, "%s", view->vid);
3613                 if (view_is_displayed(view)) {
3614                         update_view_title(view);
3615                         redraw_view_from(view, 0);
3616                 }
3617                 return TRUE;
3618         }
3620         if (!commit) {
3621                 commit = parse_blame_commit(view, line, &blamed);
3622                 string_format(view->ref, "%s %2d%%", view->vid,
3623                               blamed * 100 / view->lines);
3625         } else if (match_blame_header("author ", &line)) {
3626                 string_ncopy(commit->author, line, strlen(line));
3628         } else if (match_blame_header("author-time ", &line)) {
3629                 author_time = (time_t) atol(line);
3631         } else if (match_blame_header("author-tz ", &line)) {
3632                 long tz;
3634                 tz  = ('0' - line[1]) * 60 * 60 * 10;
3635                 tz += ('0' - line[2]) * 60 * 60;
3636                 tz += ('0' - line[3]) * 60;
3637                 tz += ('0' - line[4]) * 60;
3639                 if (line[0] == '-')
3640                         tz = -tz;
3642                 author_time -= tz;
3643                 gmtime_r(&author_time, &commit->time);
3645         } else if (match_blame_header("summary ", &line)) {
3646                 string_ncopy(commit->title, line, strlen(line));
3648         } else if (match_blame_header("filename ", &line)) {
3649                 string_ncopy(commit->filename, line, strlen(line));
3650                 commit = NULL;
3651         }
3653         return TRUE;
3656 static bool
3657 blame_draw(struct view *view, struct line *line, unsigned int lineno)
3659         struct blame *blame = line->data;
3660         struct tm *time = NULL;
3661         char *id = NULL, *author = NULL;
3663         if (blame->commit && *blame->commit->filename) {
3664                 id = blame->commit->id;
3665                 author = blame->commit->author;
3666                 time = &blame->commit->time;
3667         }
3669         if (opt_date && draw_date(view, time))
3670                 return TRUE;
3672         if (opt_author &&
3673             draw_field(view, LINE_MAIN_AUTHOR, author, AUTHOR_COLS, TRUE))
3674                 return TRUE;
3676         if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
3677                 return TRUE;
3679         if (draw_lineno(view, lineno))
3680                 return TRUE;
3682         draw_text(view, LINE_DEFAULT, blame->text, TRUE);
3683         return TRUE;
3686 static enum request
3687 blame_request(struct view *view, enum request request, struct line *line)
3689         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
3690         struct blame *blame = line->data;
3692         switch (request) {
3693         case REQ_ENTER:
3694                 if (!blame->commit) {
3695                         report("No commit loaded yet");
3696                         break;
3697                 }
3699                 if (!strcmp(blame->commit->id, NULL_ID)) {
3700                         char path[SIZEOF_STR];
3702                         if (sq_quote(path, 0, view->vid) >= sizeof(path))
3703                                 break;
3704                         string_format(opt_cmd, "git diff-index --root --patch-with-stat -C -M --cached HEAD -- %s 2>/dev/null", path);
3705                 }
3707                 open_view(view, REQ_VIEW_DIFF, flags);
3708                 break;
3710         default:
3711                 return request;
3712         }
3714         return REQ_NONE;
3717 static bool
3718 blame_grep(struct view *view, struct line *line)
3720         struct blame *blame = line->data;
3721         struct blame_commit *commit = blame->commit;
3722         regmatch_t pmatch;
3724 #define MATCH(text, on)                                                 \
3725         (on && *text && regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
3727         if (commit) {
3728                 char buf[DATE_COLS + 1];
3730                 if (MATCH(commit->title, 1) ||
3731                     MATCH(commit->author, opt_author) ||
3732                     MATCH(commit->id, opt_date))
3733                         return TRUE;
3735                 if (strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time) &&
3736                     MATCH(buf, 1))
3737                         return TRUE;
3738         }
3740         return MATCH(blame->text, 1);
3742 #undef MATCH
3745 static void
3746 blame_select(struct view *view, struct line *line)
3748         struct blame *blame = line->data;
3749         struct blame_commit *commit = blame->commit;
3751         if (!commit)
3752                 return;
3754         if (!strcmp(commit->id, NULL_ID))
3755                 string_ncopy(ref_commit, "HEAD", 4);
3756         else
3757                 string_copy_rev(ref_commit, commit->id);
3760 static struct view_ops blame_ops = {
3761         "line",
3762         blame_open,
3763         blame_read,
3764         blame_draw,
3765         blame_request,
3766         blame_grep,
3767         blame_select,
3768 };
3770 /*
3771  * Status backend
3772  */
3774 struct status {
3775         char status;
3776         struct {
3777                 mode_t mode;
3778                 char rev[SIZEOF_REV];
3779                 char name[SIZEOF_STR];
3780         } old;
3781         struct {
3782                 mode_t mode;
3783                 char rev[SIZEOF_REV];
3784                 char name[SIZEOF_STR];
3785         } new;
3786 };
3788 static char status_onbranch[SIZEOF_STR];
3789 static struct status stage_status;
3790 static enum line_type stage_line_type;
3792 /* Get fields from the diff line:
3793  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
3794  */
3795 static inline bool
3796 status_get_diff(struct status *file, char *buf, size_t bufsize)
3798         char *old_mode = buf +  1;
3799         char *new_mode = buf +  8;
3800         char *old_rev  = buf + 15;
3801         char *new_rev  = buf + 56;
3802         char *status   = buf + 97;
3804         if (bufsize < 99 ||
3805             old_mode[-1] != ':' ||
3806             new_mode[-1] != ' ' ||
3807             old_rev[-1]  != ' ' ||
3808             new_rev[-1]  != ' ' ||
3809             status[-1]   != ' ')
3810                 return FALSE;
3812         file->status = *status;
3814         string_copy_rev(file->old.rev, old_rev);
3815         string_copy_rev(file->new.rev, new_rev);
3817         file->old.mode = strtoul(old_mode, NULL, 8);
3818         file->new.mode = strtoul(new_mode, NULL, 8);
3820         file->old.name[0] = file->new.name[0] = 0;
3822         return TRUE;
3825 static bool
3826 status_run(struct view *view, const char cmd[], char status, enum line_type type)
3828         struct status *file = NULL;
3829         struct status *unmerged = NULL;
3830         char buf[SIZEOF_STR * 4];
3831         size_t bufsize = 0;
3832         FILE *pipe;
3834         pipe = popen(cmd, "r");
3835         if (!pipe)
3836                 return FALSE;
3838         add_line_data(view, NULL, type);
3840         while (!feof(pipe) && !ferror(pipe)) {
3841                 char *sep;
3842                 size_t readsize;
3844                 readsize = fread(buf + bufsize, 1, sizeof(buf) - bufsize, pipe);
3845                 if (!readsize)
3846                         break;
3847                 bufsize += readsize;
3849                 /* Process while we have NUL chars. */
3850                 while ((sep = memchr(buf, 0, bufsize))) {
3851                         size_t sepsize = sep - buf + 1;
3853                         if (!file) {
3854                                 if (!realloc_lines(view, view->line_size + 1))
3855                                         goto error_out;
3857                                 file = calloc(1, sizeof(*file));
3858                                 if (!file)
3859                                         goto error_out;
3861                                 add_line_data(view, file, type);
3862                         }
3864                         /* Parse diff info part. */
3865                         if (status) {
3866                                 file->status = status;
3867                                 if (status == 'A')
3868                                         string_copy(file->old.rev, NULL_ID);
3870                         } else if (!file->status) {
3871                                 if (!status_get_diff(file, buf, sepsize))
3872                                         goto error_out;
3874                                 bufsize -= sepsize;
3875                                 memmove(buf, sep + 1, bufsize);
3877                                 sep = memchr(buf, 0, bufsize);
3878                                 if (!sep)
3879                                         break;
3880                                 sepsize = sep - buf + 1;
3882                                 /* Collapse all 'M'odified entries that
3883                                  * follow a associated 'U'nmerged entry.
3884                                  */
3885                                 if (file->status == 'U') {
3886                                         unmerged = file;
3888                                 } else if (unmerged) {
3889                                         int collapse = !strcmp(buf, unmerged->new.name);
3891                                         unmerged = NULL;
3892                                         if (collapse) {
3893                                                 free(file);
3894                                                 view->lines--;
3895                                                 continue;
3896                                         }
3897                                 }
3898                         }
3900                         /* Grab the old name for rename/copy. */
3901                         if (!*file->old.name &&
3902                             (file->status == 'R' || file->status == 'C')) {
3903                                 sepsize = sep - buf + 1;
3904                                 string_ncopy(file->old.name, buf, sepsize);
3905                                 bufsize -= sepsize;
3906                                 memmove(buf, sep + 1, bufsize);
3908                                 sep = memchr(buf, 0, bufsize);
3909                                 if (!sep)
3910                                         break;
3911                                 sepsize = sep - buf + 1;
3912                         }
3914                         /* git-ls-files just delivers a NUL separated
3915                          * list of file names similar to the second half
3916                          * of the git-diff-* output. */
3917                         string_ncopy(file->new.name, buf, sepsize);
3918                         if (!*file->old.name)
3919                                 string_copy(file->old.name, file->new.name);
3920                         bufsize -= sepsize;
3921                         memmove(buf, sep + 1, bufsize);
3922                         file = NULL;
3923                 }
3924         }
3926         if (ferror(pipe)) {
3927 error_out:
3928                 pclose(pipe);
3929                 return FALSE;
3930         }
3932         if (!view->line[view->lines - 1].data)
3933                 add_line_data(view, NULL, LINE_STAT_NONE);
3935         pclose(pipe);
3936         return TRUE;
3939 /* Don't show unmerged entries in the staged section. */
3940 #define STATUS_DIFF_INDEX_CMD "git diff-index -z --diff-filter=ACDMRTXB --cached -M HEAD"
3941 #define STATUS_DIFF_FILES_CMD "git diff-files -z"
3942 #define STATUS_LIST_OTHER_CMD \
3943         "git ls-files -z --others --exclude-per-directory=.gitignore"
3944 #define STATUS_LIST_NO_HEAD_CMD \
3945         "git ls-files -z --cached --exclude-per-directory=.gitignore"
3947 #define STATUS_DIFF_INDEX_SHOW_CMD \
3948         "git diff-index --root --patch-with-stat -C -M --cached HEAD -- %s %s 2>/dev/null"
3950 #define STATUS_DIFF_FILES_SHOW_CMD \
3951         "git diff-files --root --patch-with-stat -C -M -- %s %s 2>/dev/null"
3953 #define STATUS_DIFF_NO_HEAD_SHOW_CMD \
3954         "git diff --no-color --patch-with-stat /dev/null %s 2>/dev/null"
3956 /* First parse staged info using git-diff-index(1), then parse unstaged
3957  * info using git-diff-files(1), and finally untracked files using
3958  * git-ls-files(1). */
3959 static bool
3960 status_open(struct view *view)
3962         struct stat statbuf;
3963         char exclude[SIZEOF_STR];
3964         char indexcmd[SIZEOF_STR] = STATUS_DIFF_INDEX_CMD;
3965         char othercmd[SIZEOF_STR] = STATUS_LIST_OTHER_CMD;
3966         unsigned long prev_lineno = view->lineno;
3967         char indexstatus = 0;
3968         size_t i;
3970         for (i = 0; i < view->lines; i++)
3971                 free(view->line[i].data);
3972         free(view->line);
3973         view->lines = view->line_alloc = view->line_size = view->lineno = 0;
3974         view->line = NULL;
3976         if (!realloc_lines(view, view->line_size + 7))
3977                 return FALSE;
3979         add_line_data(view, NULL, LINE_STAT_HEAD);
3980         if (opt_no_head)
3981                 string_copy(status_onbranch, "Initial commit");
3982         else if (!*opt_head)
3983                 string_copy(status_onbranch, "Not currently on any branch");
3984         else if (!string_format(status_onbranch, "On branch %s", opt_head))
3985                 return FALSE;
3987         if (opt_no_head) {
3988                 string_copy(indexcmd, STATUS_LIST_NO_HEAD_CMD);
3989                 indexstatus = 'A';
3990         }
3992         if (!string_format(exclude, "%s/info/exclude", opt_git_dir))
3993                 return FALSE;
3995         if (stat(exclude, &statbuf) >= 0) {
3996                 size_t cmdsize = strlen(othercmd);
3998                 if (!string_format_from(othercmd, &cmdsize, " %s", "--exclude-from=") ||
3999                     sq_quote(othercmd, cmdsize, exclude) >= sizeof(othercmd))
4000                         return FALSE;
4002                 cmdsize = strlen(indexcmd);
4003                 if (opt_no_head &&
4004                     (!string_format_from(indexcmd, &cmdsize, " %s", "--exclude-from=") ||
4005                      sq_quote(indexcmd, cmdsize, exclude) >= sizeof(indexcmd)))
4006                         return FALSE;
4007         }
4009         system("git update-index -q --refresh >/dev/null 2>/dev/null");
4011         if (!status_run(view, indexcmd, indexstatus, LINE_STAT_STAGED) ||
4012             !status_run(view, STATUS_DIFF_FILES_CMD, 0, LINE_STAT_UNSTAGED) ||
4013             !status_run(view, othercmd, '?', LINE_STAT_UNTRACKED))
4014                 return FALSE;
4016         /* If all went well restore the previous line number to stay in
4017          * the context or select a line with something that can be
4018          * updated. */
4019         if (prev_lineno >= view->lines)
4020                 prev_lineno = view->lines - 1;
4021         while (prev_lineno < view->lines && !view->line[prev_lineno].data)
4022                 prev_lineno++;
4023         while (prev_lineno > 0 && !view->line[prev_lineno].data)
4024                 prev_lineno--;
4026         /* If the above fails, always skip the "On branch" line. */
4027         if (prev_lineno < view->lines)
4028                 view->lineno = prev_lineno;
4029         else
4030                 view->lineno = 1;
4032         if (view->lineno < view->offset)
4033                 view->offset = view->lineno;
4034         else if (view->offset + view->height <= view->lineno)
4035                 view->offset = view->lineno - view->height + 1;
4037         return TRUE;
4040 static bool
4041 status_draw(struct view *view, struct line *line, unsigned int lineno)
4043         struct status *status = line->data;
4044         enum line_type type;
4045         char *text;
4047         if (!status) {
4048                 switch (line->type) {
4049                 case LINE_STAT_STAGED:
4050                         type = LINE_STAT_SECTION;
4051                         text = "Changes to be committed:";
4052                         break;
4054                 case LINE_STAT_UNSTAGED:
4055                         type = LINE_STAT_SECTION;
4056                         text = "Changed but not updated:";
4057                         break;
4059                 case LINE_STAT_UNTRACKED:
4060                         type = LINE_STAT_SECTION;
4061                         text = "Untracked files:";
4062                         break;
4064                 case LINE_STAT_NONE:
4065                         type = LINE_DEFAULT;
4066                         text = "    (no files)";
4067                         break;
4069                 case LINE_STAT_HEAD:
4070                         type = LINE_STAT_HEAD;
4071                         text = status_onbranch;
4072                         break;
4074                 default:
4075                         return FALSE;
4076                 }
4077         } else {
4078                 static char buf[] = { '?', ' ', ' ', ' ', 0 };
4080                 buf[0] = status->status;
4081                 if (draw_text(view, line->type, buf, TRUE))
4082                         return TRUE;
4083                 type = LINE_DEFAULT;
4084                 text = status->new.name;
4085         }
4087         draw_text(view, type, text, TRUE);
4088         return TRUE;
4091 static enum request
4092 status_enter(struct view *view, struct line *line)
4094         struct status *status = line->data;
4095         char oldpath[SIZEOF_STR] = "";
4096         char newpath[SIZEOF_STR] = "";
4097         char *info;
4098         size_t cmdsize = 0;
4099         enum open_flags split;
4101         if (line->type == LINE_STAT_NONE ||
4102             (!status && line[1].type == LINE_STAT_NONE)) {
4103                 report("No file to diff");
4104                 return REQ_NONE;
4105         }
4107         if (status) {
4108                 if (sq_quote(oldpath, 0, status->old.name) >= sizeof(oldpath))
4109                         return REQ_QUIT;
4110                 /* Diffs for unmerged entries are empty when pasing the
4111                  * new path, so leave it empty. */
4112                 if (status->status != 'U' &&
4113                     sq_quote(newpath, 0, status->new.name) >= sizeof(newpath))
4114                         return REQ_QUIT;
4115         }
4117         if (opt_cdup[0] &&
4118             line->type != LINE_STAT_UNTRACKED &&
4119             !string_format_from(opt_cmd, &cmdsize, "cd %s;", opt_cdup))
4120                 return REQ_QUIT;
4122         switch (line->type) {
4123         case LINE_STAT_STAGED:
4124                 if (opt_no_head) {
4125                         if (!string_format_from(opt_cmd, &cmdsize,
4126                                                 STATUS_DIFF_NO_HEAD_SHOW_CMD,
4127                                                 newpath))
4128                                 return REQ_QUIT;
4129                 } else {
4130                         if (!string_format_from(opt_cmd, &cmdsize,
4131                                                 STATUS_DIFF_INDEX_SHOW_CMD,
4132                                                 oldpath, newpath))
4133                                 return REQ_QUIT;
4134                 }
4136                 if (status)
4137                         info = "Staged changes to %s";
4138                 else
4139                         info = "Staged changes";
4140                 break;
4142         case LINE_STAT_UNSTAGED:
4143                 if (!string_format_from(opt_cmd, &cmdsize,
4144                                         STATUS_DIFF_FILES_SHOW_CMD, oldpath, newpath))
4145                         return REQ_QUIT;
4146                 if (status)
4147                         info = "Unstaged changes to %s";
4148                 else
4149                         info = "Unstaged changes";
4150                 break;
4152         case LINE_STAT_UNTRACKED:
4153                 if (opt_pipe)
4154                         return REQ_QUIT;
4156                 if (!status) {
4157                         report("No file to show");
4158                         return REQ_NONE;
4159                 }
4161                 opt_pipe = fopen(status->new.name, "r");
4162                 info = "Untracked file %s";
4163                 break;
4165         case LINE_STAT_HEAD:
4166                 return REQ_NONE;
4168         default:
4169                 die("line type %d not handled in switch", line->type);
4170         }
4172         split = view_is_displayed(view) ? OPEN_SPLIT : 0;
4173         open_view(view, REQ_VIEW_STAGE, OPEN_RELOAD | split);
4174         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
4175                 if (status) {
4176                         stage_status = *status;
4177                 } else {
4178                         memset(&stage_status, 0, sizeof(stage_status));
4179                 }
4181                 stage_line_type = line->type;
4182                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
4183         }
4185         return REQ_NONE;
4188 static bool
4189 status_exists(struct status *status, enum line_type type)
4191         struct view *view = VIEW(REQ_VIEW_STATUS);
4192         struct line *line;
4194         for (line = view->line; line < view->line + view->lines; line++) {
4195                 struct status *pos = line->data;
4197                 if (line->type == type && pos &&
4198                     !strcmp(status->new.name, pos->new.name))
4199                         return TRUE;
4200         }
4202         return FALSE;
4206 static FILE *
4207 status_update_prepare(enum line_type type)
4209         char cmd[SIZEOF_STR];
4210         size_t cmdsize = 0;
4212         if (opt_cdup[0] &&
4213             type != LINE_STAT_UNTRACKED &&
4214             !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
4215                 return NULL;
4217         switch (type) {
4218         case LINE_STAT_STAGED:
4219                 string_add(cmd, cmdsize, "git update-index -z --index-info");
4220                 break;
4222         case LINE_STAT_UNSTAGED:
4223         case LINE_STAT_UNTRACKED:
4224                 string_add(cmd, cmdsize, "git update-index -z --add --remove --stdin");
4225                 break;
4227         default:
4228                 die("line type %d not handled in switch", type);
4229         }
4231         return popen(cmd, "w");
4234 static bool
4235 status_update_write(FILE *pipe, struct status *status, enum line_type type)
4237         char buf[SIZEOF_STR];
4238         size_t bufsize = 0;
4239         size_t written = 0;
4241         switch (type) {
4242         case LINE_STAT_STAGED:
4243                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
4244                                         status->old.mode,
4245                                         status->old.rev,
4246                                         status->old.name, 0))
4247                         return FALSE;
4248                 break;
4250         case LINE_STAT_UNSTAGED:
4251         case LINE_STAT_UNTRACKED:
4252                 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
4253                         return FALSE;
4254                 break;
4256         default:
4257                 die("line type %d not handled in switch", type);
4258         }
4260         while (!ferror(pipe) && written < bufsize) {
4261                 written += fwrite(buf + written, 1, bufsize - written, pipe);
4262         }
4264         return written == bufsize;
4267 static bool
4268 status_update_file(struct status *status, enum line_type type)
4270         FILE *pipe = status_update_prepare(type);
4271         bool result;
4273         if (!pipe)
4274                 return FALSE;
4276         result = status_update_write(pipe, status, type);
4277         pclose(pipe);
4278         return result;
4281 static bool
4282 status_update_files(struct view *view, struct line *line)
4284         FILE *pipe = status_update_prepare(line->type);
4285         bool result = TRUE;
4286         struct line *pos = view->line + view->lines;
4287         int files = 0;
4288         int file, done;
4290         if (!pipe)
4291                 return FALSE;
4293         for (pos = line; pos < view->line + view->lines && pos->data; pos++)
4294                 files++;
4296         for (file = 0, done = 0; result && file < files; line++, file++) {
4297                 int almost_done = file * 100 / files;
4299                 if (almost_done > done) {
4300                         done = almost_done;
4301                         string_format(view->ref, "updating file %u of %u (%d%% done)",
4302                                       file, files, done);
4303                         update_view_title(view);
4304                 }
4305                 result = status_update_write(pipe, line->data, line->type);
4306         }
4308         pclose(pipe);
4309         return result;
4312 static bool
4313 status_update(struct view *view)
4315         struct line *line = &view->line[view->lineno];
4317         assert(view->lines);
4319         if (!line->data) {
4320                 /* This should work even for the "On branch" line. */
4321                 if (line < view->line + view->lines && !line[1].data) {
4322                         report("Nothing to update");
4323                         return FALSE;
4324                 }
4326                 if (!status_update_files(view, line + 1)) {
4327                         report("Failed to update file status");
4328                         return FALSE;
4329                 }
4331         } else if (!status_update_file(line->data, line->type)) {
4332                 report("Failed to update file status");
4333                 return FALSE;
4334         }
4336         return TRUE;
4339 static enum request
4340 status_request(struct view *view, enum request request, struct line *line)
4342         struct status *status = line->data;
4344         switch (request) {
4345         case REQ_STATUS_UPDATE:
4346                 if (!status_update(view))
4347                         return REQ_NONE;
4348                 break;
4350         case REQ_STATUS_MERGE:
4351                 if (!status || status->status != 'U') {
4352                         report("Merging only possible for files with unmerged status ('U').");
4353                         return REQ_NONE;
4354                 }
4355                 open_mergetool(status->new.name);
4356                 break;
4358         case REQ_EDIT:
4359                 if (!status)
4360                         return request;
4362                 open_editor(status->status != '?', status->new.name);
4363                 break;
4365         case REQ_VIEW_BLAME:
4366                 if (status) {
4367                         string_copy(opt_file, status->new.name);
4368                         opt_ref[0] = 0;
4369                 }
4370                 return request;
4372         case REQ_ENTER:
4373                 /* After returning the status view has been split to
4374                  * show the stage view. No further reloading is
4375                  * necessary. */
4376                 status_enter(view, line);
4377                 return REQ_NONE;
4379         case REQ_REFRESH:
4380                 /* Simply reload the view. */
4381                 break;
4383         default:
4384                 return request;
4385         }
4387         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
4389         return REQ_NONE;
4392 static void
4393 status_select(struct view *view, struct line *line)
4395         struct status *status = line->data;
4396         char file[SIZEOF_STR] = "all files";
4397         char *text;
4398         char *key;
4400         if (status && !string_format(file, "'%s'", status->new.name))
4401                 return;
4403         if (!status && line[1].type == LINE_STAT_NONE)
4404                 line++;
4406         switch (line->type) {
4407         case LINE_STAT_STAGED:
4408                 text = "Press %s to unstage %s for commit";
4409                 break;
4411         case LINE_STAT_UNSTAGED:
4412                 text = "Press %s to stage %s for commit";
4413                 break;
4415         case LINE_STAT_UNTRACKED:
4416                 text = "Press %s to stage %s for addition";
4417                 break;
4419         case LINE_STAT_HEAD:
4420         case LINE_STAT_NONE:
4421                 text = "Nothing to update";
4422                 break;
4424         default:
4425                 die("line type %d not handled in switch", line->type);
4426         }
4428         if (status && status->status == 'U') {
4429                 text = "Press %s to resolve conflict in %s";
4430                 key = get_key(REQ_STATUS_MERGE);
4432         } else {
4433                 key = get_key(REQ_STATUS_UPDATE);
4434         }
4436         string_format(view->ref, text, key, file);
4439 static bool
4440 status_grep(struct view *view, struct line *line)
4442         struct status *status = line->data;
4443         enum { S_STATUS, S_NAME, S_END } state;
4444         char buf[2] = "?";
4445         regmatch_t pmatch;
4447         if (!status)
4448                 return FALSE;
4450         for (state = S_STATUS; state < S_END; state++) {
4451                 char *text;
4453                 switch (state) {
4454                 case S_NAME:    text = status->new.name;        break;
4455                 case S_STATUS:
4456                         buf[0] = status->status;
4457                         text = buf;
4458                         break;
4460                 default:
4461                         return FALSE;
4462                 }
4464                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4465                         return TRUE;
4466         }
4468         return FALSE;
4471 static struct view_ops status_ops = {
4472         "file",
4473         status_open,
4474         NULL,
4475         status_draw,
4476         status_request,
4477         status_grep,
4478         status_select,
4479 };
4482 static bool
4483 stage_diff_line(FILE *pipe, struct line *line)
4485         char *buf = line->data;
4486         size_t bufsize = strlen(buf);
4487         size_t written = 0;
4489         while (!ferror(pipe) && written < bufsize) {
4490                 written += fwrite(buf + written, 1, bufsize - written, pipe);
4491         }
4493         fputc('\n', pipe);
4495         return written == bufsize;
4498 static bool
4499 stage_diff_write(FILE *pipe, struct line *line, struct line *end)
4501         while (line < end) {
4502                 if (!stage_diff_line(pipe, line++))
4503                         return FALSE;
4504                 if (line->type == LINE_DIFF_CHUNK ||
4505                     line->type == LINE_DIFF_HEADER)
4506                         break;
4507         }
4509         return TRUE;
4512 static struct line *
4513 stage_diff_find(struct view *view, struct line *line, enum line_type type)
4515         for (; view->line < line; line--)
4516                 if (line->type == type)
4517                         return line;
4519         return NULL;
4522 static bool
4523 stage_update_chunk(struct view *view, struct line *chunk)
4525         char cmd[SIZEOF_STR];
4526         size_t cmdsize = 0;
4527         struct line *diff_hdr;
4528         FILE *pipe;
4530         diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
4531         if (!diff_hdr)
4532                 return FALSE;
4534         if (opt_cdup[0] &&
4535             !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
4536                 return FALSE;
4538         if (!string_format_from(cmd, &cmdsize,
4539                                 "git apply --whitespace=nowarn --cached %s - && "
4540                                 "git update-index -q --unmerged --refresh 2>/dev/null",
4541                                 stage_line_type == LINE_STAT_STAGED ? "-R" : ""))
4542                 return FALSE;
4544         pipe = popen(cmd, "w");
4545         if (!pipe)
4546                 return FALSE;
4548         if (!stage_diff_write(pipe, diff_hdr, chunk) ||
4549             !stage_diff_write(pipe, chunk, view->line + view->lines))
4550                 chunk = NULL;
4552         pclose(pipe);
4554         return chunk ? TRUE : FALSE;
4557 static bool
4558 stage_update(struct view *view, struct line *line)
4560         struct line *chunk = NULL;
4562         if (!opt_no_head && stage_line_type != LINE_STAT_UNTRACKED)
4563                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
4565         if (chunk) {
4566                 if (!stage_update_chunk(view, chunk)) {
4567                         report("Failed to apply chunk");
4568                         return FALSE;
4569                 }
4571         } else if (!stage_status.status) {
4572                 view = VIEW(REQ_VIEW_STATUS);
4574                 for (line = view->line; line < view->line + view->lines; line++)
4575                         if (line->type == stage_line_type)
4576                                 break;
4578                 if (!status_update_files(view, line + 1)) {
4579                         report("Failed to update files");
4580                         return FALSE;
4581                 }
4583         } else if (!status_update_file(&stage_status, stage_line_type)) {
4584                 report("Failed to update file");
4585                 return FALSE;
4586         }
4588         return TRUE;
4591 static enum request
4592 stage_request(struct view *view, enum request request, struct line *line)
4594         switch (request) {
4595         case REQ_STATUS_UPDATE:
4596                 if (!stage_update(view, line))
4597                         return REQ_NONE;
4598                 break;
4600         case REQ_EDIT:
4601                 if (!stage_status.new.name[0])
4602                         return request;
4604                 open_editor(stage_status.status != '?', stage_status.new.name);
4605                 break;
4607         case REQ_REFRESH:
4608                 /* Reload everything ... */
4609                 break;
4611         case REQ_VIEW_BLAME:
4612                 if (stage_status.new.name[0]) {
4613                         string_copy(opt_file, stage_status.new.name);
4614                         opt_ref[0] = 0;
4615                 }
4616                 return request;
4618         case REQ_ENTER:
4619                 return pager_request(view, request, line);
4621         default:
4622                 return request;
4623         }
4625         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD | OPEN_NOMAXIMIZE);
4627         /* Check whether the staged entry still exists, and close the
4628          * stage view if it doesn't. */
4629         if (!status_exists(&stage_status, stage_line_type))
4630                 return REQ_VIEW_CLOSE;
4632         if (stage_line_type == LINE_STAT_UNTRACKED)
4633                 opt_pipe = fopen(stage_status.new.name, "r");
4634         else
4635                 string_copy(opt_cmd, view->cmd);
4636         open_view(view, REQ_VIEW_STAGE, OPEN_RELOAD | OPEN_NOMAXIMIZE);
4638         return REQ_NONE;
4641 static struct view_ops stage_ops = {
4642         "line",
4643         NULL,
4644         pager_read,
4645         pager_draw,
4646         stage_request,
4647         pager_grep,
4648         pager_select,
4649 };
4652 /*
4653  * Revision graph
4654  */
4656 struct commit {
4657         char id[SIZEOF_REV];            /* SHA1 ID. */
4658         char title[128];                /* First line of the commit message. */
4659         char author[75];                /* Author of the commit. */
4660         struct tm time;                 /* Date from the author ident. */
4661         struct ref **refs;              /* Repository references. */
4662         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
4663         size_t graph_size;              /* The width of the graph array. */
4664         bool has_parents;               /* Rewritten --parents seen. */
4665 };
4667 /* Size of rev graph with no  "padding" columns */
4668 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
4670 struct rev_graph {
4671         struct rev_graph *prev, *next, *parents;
4672         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
4673         size_t size;
4674         struct commit *commit;
4675         size_t pos;
4676         unsigned int boundary:1;
4677 };
4679 /* Parents of the commit being visualized. */
4680 static struct rev_graph graph_parents[4];
4682 /* The current stack of revisions on the graph. */
4683 static struct rev_graph graph_stacks[4] = {
4684         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
4685         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
4686         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
4687         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
4688 };
4690 static inline bool
4691 graph_parent_is_merge(struct rev_graph *graph)
4693         return graph->parents->size > 1;
4696 static inline void
4697 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
4699         struct commit *commit = graph->commit;
4701         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
4702                 commit->graph[commit->graph_size++] = symbol;
4705 static void
4706 done_rev_graph(struct rev_graph *graph)
4708         if (graph_parent_is_merge(graph) &&
4709             graph->pos < graph->size - 1 &&
4710             graph->next->size == graph->size + graph->parents->size - 1) {
4711                 size_t i = graph->pos + graph->parents->size - 1;
4713                 graph->commit->graph_size = i * 2;
4714                 while (i < graph->next->size - 1) {
4715                         append_to_rev_graph(graph, ' ');
4716                         append_to_rev_graph(graph, '\\');
4717                         i++;
4718                 }
4719         }
4721         graph->size = graph->pos = 0;
4722         graph->commit = NULL;
4723         memset(graph->parents, 0, sizeof(*graph->parents));
4726 static void
4727 push_rev_graph(struct rev_graph *graph, char *parent)
4729         int i;
4731         /* "Collapse" duplicate parents lines.
4732          *
4733          * FIXME: This needs to also update update the drawn graph but
4734          * for now it just serves as a method for pruning graph lines. */
4735         for (i = 0; i < graph->size; i++)
4736                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
4737                         return;
4739         if (graph->size < SIZEOF_REVITEMS) {
4740                 string_copy_rev(graph->rev[graph->size++], parent);
4741         }
4744 static chtype
4745 get_rev_graph_symbol(struct rev_graph *graph)
4747         chtype symbol;
4749         if (graph->boundary)
4750                 symbol = REVGRAPH_BOUND;
4751         else if (graph->parents->size == 0)
4752                 symbol = REVGRAPH_INIT;
4753         else if (graph_parent_is_merge(graph))
4754                 symbol = REVGRAPH_MERGE;
4755         else if (graph->pos >= graph->size)
4756                 symbol = REVGRAPH_BRANCH;
4757         else
4758                 symbol = REVGRAPH_COMMIT;
4760         return symbol;
4763 static void
4764 draw_rev_graph(struct rev_graph *graph)
4766         struct rev_filler {
4767                 chtype separator, line;
4768         };
4769         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
4770         static struct rev_filler fillers[] = {
4771                 { ' ',  '|' },
4772                 { '`',  '.' },
4773                 { '\'', ' ' },
4774                 { '/',  ' ' },
4775         };
4776         chtype symbol = get_rev_graph_symbol(graph);
4777         struct rev_filler *filler;
4778         size_t i;
4780         if (opt_line_graphics)
4781                 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
4783         filler = &fillers[DEFAULT];
4785         for (i = 0; i < graph->pos; i++) {
4786                 append_to_rev_graph(graph, filler->line);
4787                 if (graph_parent_is_merge(graph->prev) &&
4788                     graph->prev->pos == i)
4789                         filler = &fillers[RSHARP];
4791                 append_to_rev_graph(graph, filler->separator);
4792         }
4794         /* Place the symbol for this revision. */
4795         append_to_rev_graph(graph, symbol);
4797         if (graph->prev->size > graph->size)
4798                 filler = &fillers[RDIAG];
4799         else
4800                 filler = &fillers[DEFAULT];
4802         i++;
4804         for (; i < graph->size; i++) {
4805                 append_to_rev_graph(graph, filler->separator);
4806                 append_to_rev_graph(graph, filler->line);
4807                 if (graph_parent_is_merge(graph->prev) &&
4808                     i < graph->prev->pos + graph->parents->size)
4809                         filler = &fillers[RSHARP];
4810                 if (graph->prev->size > graph->size)
4811                         filler = &fillers[LDIAG];
4812         }
4814         if (graph->prev->size > graph->size) {
4815                 append_to_rev_graph(graph, filler->separator);
4816                 if (filler->line != ' ')
4817                         append_to_rev_graph(graph, filler->line);
4818         }
4821 /* Prepare the next rev graph */
4822 static void
4823 prepare_rev_graph(struct rev_graph *graph)
4825         size_t i;
4827         /* First, traverse all lines of revisions up to the active one. */
4828         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
4829                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
4830                         break;
4832                 push_rev_graph(graph->next, graph->rev[graph->pos]);
4833         }
4835         /* Interleave the new revision parent(s). */
4836         for (i = 0; !graph->boundary && i < graph->parents->size; i++)
4837                 push_rev_graph(graph->next, graph->parents->rev[i]);
4839         /* Lastly, put any remaining revisions. */
4840         for (i = graph->pos + 1; i < graph->size; i++)
4841                 push_rev_graph(graph->next, graph->rev[i]);
4844 static void
4845 update_rev_graph(struct rev_graph *graph)
4847         /* If this is the finalizing update ... */
4848         if (graph->commit)
4849                 prepare_rev_graph(graph);
4851         /* Graph visualization needs a one rev look-ahead,
4852          * so the first update doesn't visualize anything. */
4853         if (!graph->prev->commit)
4854                 return;
4856         draw_rev_graph(graph->prev);
4857         done_rev_graph(graph->prev->prev);
4861 /*
4862  * Main view backend
4863  */
4865 static bool
4866 main_draw(struct view *view, struct line *line, unsigned int lineno)
4868         struct commit *commit = line->data;
4870         if (!*commit->author)
4871                 return FALSE;
4873         if (opt_date && draw_date(view, &commit->time))
4874                 return TRUE;
4876         if (opt_author &&
4877             draw_field(view, LINE_MAIN_AUTHOR, commit->author, AUTHOR_COLS, TRUE))
4878                 return TRUE;
4880         if (opt_rev_graph && commit->graph_size &&
4881             draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
4882                 return TRUE;
4884         if (opt_show_refs && commit->refs) {
4885                 size_t i = 0;
4887                 do {
4888                         enum line_type type;
4890                         if (commit->refs[i]->head)
4891                                 type = LINE_MAIN_HEAD;
4892                         else if (commit->refs[i]->ltag)
4893                                 type = LINE_MAIN_LOCAL_TAG;
4894                         else if (commit->refs[i]->tag)
4895                                 type = LINE_MAIN_TAG;
4896                         else if (commit->refs[i]->tracked)
4897                                 type = LINE_MAIN_TRACKED;
4898                         else if (commit->refs[i]->remote)
4899                                 type = LINE_MAIN_REMOTE;
4900                         else
4901                                 type = LINE_MAIN_REF;
4903                         if (draw_text(view, type, "[", TRUE) ||
4904                             draw_text(view, type, commit->refs[i]->name, TRUE) ||
4905                             draw_text(view, type, "]", TRUE))
4906                                 return TRUE;
4908                         if (draw_text(view, LINE_DEFAULT, " ", TRUE))
4909                                 return TRUE;
4910                 } while (commit->refs[i++]->next);
4911         }
4913         draw_text(view, LINE_DEFAULT, commit->title, TRUE);
4914         return TRUE;
4917 /* Reads git log --pretty=raw output and parses it into the commit struct. */
4918 static bool
4919 main_read(struct view *view, char *line)
4921         static struct rev_graph *graph = graph_stacks;
4922         enum line_type type;
4923         struct commit *commit;
4925         if (!line) {
4926                 if (!view->lines && !view->parent)
4927                         die("No revisions match the given arguments.");
4928                 update_rev_graph(graph);
4929                 return TRUE;
4930         }
4932         type = get_line_type(line);
4933         if (type == LINE_COMMIT) {
4934                 commit = calloc(1, sizeof(struct commit));
4935                 if (!commit)
4936                         return FALSE;
4938                 line += STRING_SIZE("commit ");
4939                 if (*line == '-') {
4940                         graph->boundary = 1;
4941                         line++;
4942                 }
4944                 string_copy_rev(commit->id, line);
4945                 commit->refs = get_refs(commit->id);
4946                 graph->commit = commit;
4947                 add_line_data(view, commit, LINE_MAIN_COMMIT);
4949                 while ((line = strchr(line, ' '))) {
4950                         line++;
4951                         push_rev_graph(graph->parents, line);
4952                         commit->has_parents = TRUE;
4953                 }
4954                 return TRUE;
4955         }
4957         if (!view->lines)
4958                 return TRUE;
4959         commit = view->line[view->lines - 1].data;
4961         switch (type) {
4962         case LINE_PARENT:
4963                 if (commit->has_parents)
4964                         break;
4965                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
4966                 break;
4968         case LINE_AUTHOR:
4969         {
4970                 /* Parse author lines where the name may be empty:
4971                  *      author  <email@address.tld> 1138474660 +0100
4972                  */
4973                 char *ident = line + STRING_SIZE("author ");
4974                 char *nameend = strchr(ident, '<');
4975                 char *emailend = strchr(ident, '>');
4977                 if (!nameend || !emailend)
4978                         break;
4980                 update_rev_graph(graph);
4981                 graph = graph->next;
4983                 *nameend = *emailend = 0;
4984                 ident = chomp_string(ident);
4985                 if (!*ident) {
4986                         ident = chomp_string(nameend + 1);
4987                         if (!*ident)
4988                                 ident = "Unknown";
4989                 }
4991                 string_ncopy(commit->author, ident, strlen(ident));
4993                 /* Parse epoch and timezone */
4994                 if (emailend[1] == ' ') {
4995                         char *secs = emailend + 2;
4996                         char *zone = strchr(secs, ' ');
4997                         time_t time = (time_t) atol(secs);
4999                         if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
5000                                 long tz;
5002                                 zone++;
5003                                 tz  = ('0' - zone[1]) * 60 * 60 * 10;
5004                                 tz += ('0' - zone[2]) * 60 * 60;
5005                                 tz += ('0' - zone[3]) * 60;
5006                                 tz += ('0' - zone[4]) * 60;
5008                                 if (zone[0] == '-')
5009                                         tz = -tz;
5011                                 time -= tz;
5012                         }
5014                         gmtime_r(&time, &commit->time);
5015                 }
5016                 break;
5017         }
5018         default:
5019                 /* Fill in the commit title if it has not already been set. */
5020                 if (commit->title[0])
5021                         break;
5023                 /* Require titles to start with a non-space character at the
5024                  * offset used by git log. */
5025                 if (strncmp(line, "    ", 4))
5026                         break;
5027                 line += 4;
5028                 /* Well, if the title starts with a whitespace character,
5029                  * try to be forgiving.  Otherwise we end up with no title. */
5030                 while (isspace(*line))
5031                         line++;
5032                 if (*line == '\0')
5033                         break;
5034                 /* FIXME: More graceful handling of titles; append "..." to
5035                  * shortened titles, etc. */
5037                 string_ncopy(commit->title, line, strlen(line));
5038         }
5040         return TRUE;
5043 static enum request
5044 main_request(struct view *view, enum request request, struct line *line)
5046         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
5048         if (request == REQ_ENTER)
5049                 open_view(view, REQ_VIEW_DIFF, flags);
5050         else
5051                 return request;
5053         return REQ_NONE;
5056 static bool
5057 grep_refs(struct ref **refs, regex_t *regex)
5059         regmatch_t pmatch;
5060         size_t i = 0;
5062         if (!refs)
5063                 return FALSE;
5064         do {
5065                 if (regexec(regex, refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
5066                         return TRUE;
5067         } while (refs[i++]->next);
5069         return FALSE;
5072 static bool
5073 main_grep(struct view *view, struct line *line)
5075         struct commit *commit = line->data;
5076         enum { S_TITLE, S_AUTHOR, S_DATE, S_REFS, S_END } state;
5077         char buf[DATE_COLS + 1];
5078         regmatch_t pmatch;
5080         for (state = S_TITLE; state < S_END; state++) {
5081                 char *text;
5083                 switch (state) {
5084                 case S_TITLE:   text = commit->title;   break;
5085                 case S_AUTHOR:
5086                         if (!opt_author)
5087                                 continue;
5088                         text = commit->author;
5089                         break;
5090                 case S_DATE:
5091                         if (!opt_date)
5092                                 continue;
5093                         if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
5094                                 continue;
5095                         text = buf;
5096                         break;
5097                 case S_REFS:
5098                         if (!opt_show_refs)
5099                                 continue;
5100                         if (grep_refs(commit->refs, view->regex) == TRUE)
5101                                 return TRUE;
5102                         continue;
5103                 default:
5104                         return FALSE;
5105                 }
5107                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
5108                         return TRUE;
5109         }
5111         return FALSE;
5114 static void
5115 main_select(struct view *view, struct line *line)
5117         struct commit *commit = line->data;
5119         string_copy_rev(view->ref, commit->id);
5120         string_copy_rev(ref_commit, view->ref);
5123 static struct view_ops main_ops = {
5124         "commit",
5125         NULL,
5126         main_read,
5127         main_draw,
5128         main_request,
5129         main_grep,
5130         main_select,
5131 };
5134 /*
5135  * Unicode / UTF-8 handling
5136  *
5137  * NOTE: Much of the following code for dealing with unicode is derived from
5138  * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
5139  * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
5140  */
5142 /* I've (over)annotated a lot of code snippets because I am not entirely
5143  * confident that the approach taken by this small UTF-8 interface is correct.
5144  * --jonas */
5146 static inline int
5147 unicode_width(unsigned long c)
5149         if (c >= 0x1100 &&
5150            (c <= 0x115f                         /* Hangul Jamo */
5151             || c == 0x2329
5152             || c == 0x232a
5153             || (c >= 0x2e80  && c <= 0xa4cf && c != 0x303f)
5154                                                 /* CJK ... Yi */
5155             || (c >= 0xac00  && c <= 0xd7a3)    /* Hangul Syllables */
5156             || (c >= 0xf900  && c <= 0xfaff)    /* CJK Compatibility Ideographs */
5157             || (c >= 0xfe30  && c <= 0xfe6f)    /* CJK Compatibility Forms */
5158             || (c >= 0xff00  && c <= 0xff60)    /* Fullwidth Forms */
5159             || (c >= 0xffe0  && c <= 0xffe6)
5160             || (c >= 0x20000 && c <= 0x2fffd)
5161             || (c >= 0x30000 && c <= 0x3fffd)))
5162                 return 2;
5164         if (c == '\t')
5165                 return opt_tab_size;
5167         return 1;
5170 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
5171  * Illegal bytes are set one. */
5172 static const unsigned char utf8_bytes[256] = {
5173         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,
5174         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,
5175         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,
5176         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,
5177         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,
5178         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,
5179         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,
5180         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,
5181 };
5183 /* Decode UTF-8 multi-byte representation into a unicode character. */
5184 static inline unsigned long
5185 utf8_to_unicode(const char *string, size_t length)
5187         unsigned long unicode;
5189         switch (length) {
5190         case 1:
5191                 unicode  =   string[0];
5192                 break;
5193         case 2:
5194                 unicode  =  (string[0] & 0x1f) << 6;
5195                 unicode +=  (string[1] & 0x3f);
5196                 break;
5197         case 3:
5198                 unicode  =  (string[0] & 0x0f) << 12;
5199                 unicode += ((string[1] & 0x3f) << 6);
5200                 unicode +=  (string[2] & 0x3f);
5201                 break;
5202         case 4:
5203                 unicode  =  (string[0] & 0x0f) << 18;
5204                 unicode += ((string[1] & 0x3f) << 12);
5205                 unicode += ((string[2] & 0x3f) << 6);
5206                 unicode +=  (string[3] & 0x3f);
5207                 break;
5208         case 5:
5209                 unicode  =  (string[0] & 0x0f) << 24;
5210                 unicode += ((string[1] & 0x3f) << 18);
5211                 unicode += ((string[2] & 0x3f) << 12);
5212                 unicode += ((string[3] & 0x3f) << 6);
5213                 unicode +=  (string[4] & 0x3f);
5214                 break;
5215         case 6:
5216                 unicode  =  (string[0] & 0x01) << 30;
5217                 unicode += ((string[1] & 0x3f) << 24);
5218                 unicode += ((string[2] & 0x3f) << 18);
5219                 unicode += ((string[3] & 0x3f) << 12);
5220                 unicode += ((string[4] & 0x3f) << 6);
5221                 unicode +=  (string[5] & 0x3f);
5222                 break;
5223         default:
5224                 die("Invalid unicode length");
5225         }
5227         /* Invalid characters could return the special 0xfffd value but NUL
5228          * should be just as good. */
5229         return unicode > 0xffff ? 0 : unicode;
5232 /* Calculates how much of string can be shown within the given maximum width
5233  * and sets trimmed parameter to non-zero value if all of string could not be
5234  * shown. If the reserve flag is TRUE, it will reserve at least one
5235  * trailing character, which can be useful when drawing a delimiter.
5236  *
5237  * Returns the number of bytes to output from string to satisfy max_width. */
5238 static size_t
5239 utf8_length(const char *string, int *width, size_t max_width, int *trimmed, bool reserve)
5241         const char *start = string;
5242         const char *end = strchr(string, '\0');
5243         unsigned char last_bytes = 0;
5244         size_t last_ucwidth = 0;
5246         *width = 0;
5247         *trimmed = 0;
5249         while (string < end) {
5250                 int c = *(unsigned char *) string;
5251                 unsigned char bytes = utf8_bytes[c];
5252                 size_t ucwidth;
5253                 unsigned long unicode;
5255                 if (string + bytes > end)
5256                         break;
5258                 /* Change representation to figure out whether
5259                  * it is a single- or double-width character. */
5261                 unicode = utf8_to_unicode(string, bytes);
5262                 /* FIXME: Graceful handling of invalid unicode character. */
5263                 if (!unicode)
5264                         break;
5266                 ucwidth = unicode_width(unicode);
5267                 *width  += ucwidth;
5268                 if (*width > max_width) {
5269                         *trimmed = 1;
5270                         *width -= ucwidth;
5271                         if (reserve && *width == max_width) {
5272                                 string -= last_bytes;
5273                                 *width -= last_ucwidth;
5274                         }
5275                         break;
5276                 }
5278                 string  += bytes;
5279                 last_bytes = bytes;
5280                 last_ucwidth = ucwidth;
5281         }
5283         return string - start;
5287 /*
5288  * Status management
5289  */
5291 /* Whether or not the curses interface has been initialized. */
5292 static bool cursed = FALSE;
5294 /* The status window is used for polling keystrokes. */
5295 static WINDOW *status_win;
5297 static bool status_empty = TRUE;
5299 /* Update status and title window. */
5300 static void
5301 report(const char *msg, ...)
5303         struct view *view = display[current_view];
5305         if (input_mode)
5306                 return;
5308         if (!view) {
5309                 char buf[SIZEOF_STR];
5310                 va_list args;
5312                 va_start(args, msg);
5313                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
5314                         buf[sizeof(buf) - 1] = 0;
5315                         buf[sizeof(buf) - 2] = '.';
5316                         buf[sizeof(buf) - 3] = '.';
5317                         buf[sizeof(buf) - 4] = '.';
5318                 }
5319                 va_end(args);
5320                 die("%s", buf);
5321         }
5323         if (!status_empty || *msg) {
5324                 va_list args;
5326                 va_start(args, msg);
5328                 wmove(status_win, 0, 0);
5329                 if (*msg) {
5330                         vwprintw(status_win, msg, args);
5331                         status_empty = FALSE;
5332                 } else {
5333                         status_empty = TRUE;
5334                 }
5335                 wclrtoeol(status_win);
5336                 wrefresh(status_win);
5338                 va_end(args);
5339         }
5341         update_view_title(view);
5342         update_display_cursor(view);
5345 /* Controls when nodelay should be in effect when polling user input. */
5346 static void
5347 set_nonblocking_input(bool loading)
5349         static unsigned int loading_views;
5351         if ((loading == FALSE && loading_views-- == 1) ||
5352             (loading == TRUE  && loading_views++ == 0))
5353                 nodelay(status_win, loading);
5356 static void
5357 init_display(void)
5359         int x, y;
5361         /* Initialize the curses library */
5362         if (isatty(STDIN_FILENO)) {
5363                 cursed = !!initscr();
5364         } else {
5365                 /* Leave stdin and stdout alone when acting as a pager. */
5366                 FILE *io = fopen("/dev/tty", "r+");
5368                 if (!io)
5369                         die("Failed to open /dev/tty");
5370                 cursed = !!newterm(NULL, io, io);
5371         }
5373         if (!cursed)
5374                 die("Failed to initialize curses");
5376         nonl();         /* Tell curses not to do NL->CR/NL on output */
5377         cbreak();       /* Take input chars one at a time, no wait for \n */
5378         noecho();       /* Don't echo input */
5379         leaveok(stdscr, TRUE);
5381         if (has_colors())
5382                 init_colors();
5384         getmaxyx(stdscr, y, x);
5385         status_win = newwin(1, 0, y - 1, 0);
5386         if (!status_win)
5387                 die("Failed to create status window");
5389         /* Enable keyboard mapping */
5390         keypad(status_win, TRUE);
5391         wbkgdset(status_win, get_line_attr(LINE_STATUS));
5393         TABSIZE = opt_tab_size;
5394         if (opt_line_graphics) {
5395                 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
5396         }
5399 static char *
5400 read_prompt(const char *prompt)
5402         enum { READING, STOP, CANCEL } status = READING;
5403         static char buf[sizeof(opt_cmd) - STRING_SIZE("git \0")];
5404         int pos = 0;
5406         while (status == READING) {
5407                 struct view *view;
5408                 int i, key;
5410                 input_mode = TRUE;
5412                 foreach_view (view, i)
5413                         update_view(view);
5415                 input_mode = FALSE;
5417                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
5418                 wclrtoeol(status_win);
5420                 /* Refresh, accept single keystroke of input */
5421                 key = wgetch(status_win);
5422                 switch (key) {
5423                 case KEY_RETURN:
5424                 case KEY_ENTER:
5425                 case '\n':
5426                         status = pos ? STOP : CANCEL;
5427                         break;
5429                 case KEY_BACKSPACE:
5430                         if (pos > 0)
5431                                 pos--;
5432                         else
5433                                 status = CANCEL;
5434                         break;
5436                 case KEY_ESC:
5437                         status = CANCEL;
5438                         break;
5440                 case ERR:
5441                         break;
5443                 default:
5444                         if (pos >= sizeof(buf)) {
5445                                 report("Input string too long");
5446                                 return NULL;
5447                         }
5449                         if (isprint(key))
5450                                 buf[pos++] = (char) key;
5451                 }
5452         }
5454         /* Clear the status window */
5455         status_empty = FALSE;
5456         report("");
5458         if (status == CANCEL)
5459                 return NULL;
5461         buf[pos++] = 0;
5463         return buf;
5466 /*
5467  * Repository references
5468  */
5470 static struct ref *refs = NULL;
5471 static size_t refs_alloc = 0;
5472 static size_t refs_size = 0;
5474 /* Id <-> ref store */
5475 static struct ref ***id_refs = NULL;
5476 static size_t id_refs_alloc = 0;
5477 static size_t id_refs_size = 0;
5479 static struct ref **
5480 get_refs(char *id)
5482         struct ref ***tmp_id_refs;
5483         struct ref **ref_list = NULL;
5484         size_t ref_list_alloc = 0;
5485         size_t ref_list_size = 0;
5486         size_t i;
5488         for (i = 0; i < id_refs_size; i++)
5489                 if (!strcmp(id, id_refs[i][0]->id))
5490                         return id_refs[i];
5492         tmp_id_refs = realloc_items(id_refs, &id_refs_alloc, id_refs_size + 1,
5493                                     sizeof(*id_refs));
5494         if (!tmp_id_refs)
5495                 return NULL;
5497         id_refs = tmp_id_refs;
5499         for (i = 0; i < refs_size; i++) {
5500                 struct ref **tmp;
5502                 if (strcmp(id, refs[i].id))
5503                         continue;
5505                 tmp = realloc_items(ref_list, &ref_list_alloc,
5506                                     ref_list_size + 1, sizeof(*ref_list));
5507                 if (!tmp) {
5508                         if (ref_list)
5509                                 free(ref_list);
5510                         return NULL;
5511                 }
5513                 ref_list = tmp;
5514                 if (ref_list_size > 0)
5515                         ref_list[ref_list_size - 1]->next = 1;
5516                 ref_list[ref_list_size] = &refs[i];
5518                 /* XXX: The properties of the commit chains ensures that we can
5519                  * safely modify the shared ref. The repo references will
5520                  * always be similar for the same id. */
5521                 ref_list[ref_list_size]->next = 0;
5522                 ref_list_size++;
5523         }
5525         if (ref_list)
5526                 id_refs[id_refs_size++] = ref_list;
5528         return ref_list;
5531 static int
5532 read_ref(char *id, size_t idlen, char *name, size_t namelen)
5534         struct ref *ref;
5535         bool tag = FALSE;
5536         bool ltag = FALSE;
5537         bool remote = FALSE;
5538         bool tracked = FALSE;
5539         bool check_replace = FALSE;
5540         bool head = FALSE;
5542         if (!strncmp(name, "refs/tags/", STRING_SIZE("refs/tags/"))) {
5543                 if (!strcmp(name + namelen - 3, "^{}")) {
5544                         namelen -= 3;
5545                         name[namelen] = 0;
5546                         if (refs_size > 0 && refs[refs_size - 1].ltag == TRUE)
5547                                 check_replace = TRUE;
5548                 } else {
5549                         ltag = TRUE;
5550                 }
5552                 tag = TRUE;
5553                 namelen -= STRING_SIZE("refs/tags/");
5554                 name    += STRING_SIZE("refs/tags/");
5556         } else if (!strncmp(name, "refs/remotes/", STRING_SIZE("refs/remotes/"))) {
5557                 remote = TRUE;
5558                 namelen -= STRING_SIZE("refs/remotes/");
5559                 name    += STRING_SIZE("refs/remotes/");
5560                 tracked  = !strcmp(opt_remote, name);
5562         } else if (!strncmp(name, "refs/heads/", STRING_SIZE("refs/heads/"))) {
5563                 namelen -= STRING_SIZE("refs/heads/");
5564                 name    += STRING_SIZE("refs/heads/");
5565                 head     = !strncmp(opt_head, name, namelen);
5567         } else if (!strcmp(name, "HEAD")) {
5568                 opt_no_head = FALSE;
5569                 return OK;
5570         }
5572         if (check_replace && !strcmp(name, refs[refs_size - 1].name)) {
5573                 /* it's an annotated tag, replace the previous sha1 with the
5574                  * resolved commit id; relies on the fact git-ls-remote lists
5575                  * the commit id of an annotated tag right beofre the commit id
5576                  * it points to. */
5577                 refs[refs_size - 1].ltag = ltag;
5578                 string_copy_rev(refs[refs_size - 1].id, id);
5580                 return OK;
5581         }
5582         refs = realloc_items(refs, &refs_alloc, refs_size + 1, sizeof(*refs));
5583         if (!refs)
5584                 return ERR;
5586         ref = &refs[refs_size++];
5587         ref->name = malloc(namelen + 1);
5588         if (!ref->name)
5589                 return ERR;
5591         strncpy(ref->name, name, namelen);
5592         ref->name[namelen] = 0;
5593         ref->head = head;
5594         ref->tag = tag;
5595         ref->ltag = ltag;
5596         ref->remote = remote;
5597         ref->tracked = tracked;
5598         string_copy_rev(ref->id, id);
5600         return OK;
5603 static int
5604 load_refs(void)
5606         const char *cmd_env = getenv("TIG_LS_REMOTE");
5607         const char *cmd = cmd_env && *cmd_env ? cmd_env : TIG_LS_REMOTE;
5609         return read_properties(popen(cmd, "r"), "\t", read_ref);
5612 static int
5613 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
5615         if (!strcmp(name, "i18n.commitencoding"))
5616                 string_ncopy(opt_encoding, value, valuelen);
5618         if (!strcmp(name, "core.editor"))
5619                 string_ncopy(opt_editor, value, valuelen);
5621         /* branch.<head>.remote */
5622         if (*opt_head &&
5623             !strncmp(name, "branch.", 7) &&
5624             !strncmp(name + 7, opt_head, strlen(opt_head)) &&
5625             !strcmp(name + 7 + strlen(opt_head), ".remote"))
5626                 string_ncopy(opt_remote, value, valuelen);
5628         if (*opt_head && *opt_remote &&
5629             !strncmp(name, "branch.", 7) &&
5630             !strncmp(name + 7, opt_head, strlen(opt_head)) &&
5631             !strcmp(name + 7 + strlen(opt_head), ".merge")) {
5632                 size_t from = strlen(opt_remote);
5634                 if (!strncmp(value, "refs/heads/", STRING_SIZE("refs/heads/"))) {
5635                         value += STRING_SIZE("refs/heads/");
5636                         valuelen -= STRING_SIZE("refs/heads/");
5637                 }
5639                 if (!string_format_from(opt_remote, &from, "/%s", value))
5640                         opt_remote[0] = 0;
5641         }
5643         return OK;
5646 static int
5647 load_git_config(void)
5649         return read_properties(popen(GIT_CONFIG " --list", "r"),
5650                                "=", read_repo_config_option);
5653 static int
5654 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
5656         if (!opt_git_dir[0]) {
5657                 string_ncopy(opt_git_dir, name, namelen);
5659         } else if (opt_is_inside_work_tree == -1) {
5660                 /* This can be 3 different values depending on the
5661                  * version of git being used. If git-rev-parse does not
5662                  * understand --is-inside-work-tree it will simply echo
5663                  * the option else either "true" or "false" is printed.
5664                  * Default to true for the unknown case. */
5665                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
5667         } else if (opt_cdup[0] == ' ') {
5668                 string_ncopy(opt_cdup, name, namelen);
5669         } else {
5670                 if (!strncmp(name, "refs/heads/", STRING_SIZE("refs/heads/"))) {
5671                         namelen -= STRING_SIZE("refs/heads/");
5672                         name    += STRING_SIZE("refs/heads/");
5673                         string_ncopy(opt_head, name, namelen);
5674                 }
5675         }
5677         return OK;
5680 static int
5681 load_repo_info(void)
5683         int result;
5684         FILE *pipe = popen("(git rev-parse --git-dir --is-inside-work-tree "
5685                            " --show-cdup; git symbolic-ref HEAD) 2>/dev/null", "r");
5687         /* XXX: The line outputted by "--show-cdup" can be empty so
5688          * initialize it to something invalid to make it possible to
5689          * detect whether it has been set or not. */
5690         opt_cdup[0] = ' ';
5692         result = read_properties(pipe, "=", read_repo_info);
5693         if (opt_cdup[0] == ' ')
5694                 opt_cdup[0] = 0;
5696         return result;
5699 static int
5700 read_properties(FILE *pipe, const char *separators,
5701                 int (*read_property)(char *, size_t, char *, size_t))
5703         char buffer[BUFSIZ];
5704         char *name;
5705         int state = OK;
5707         if (!pipe)
5708                 return ERR;
5710         while (state == OK && (name = fgets(buffer, sizeof(buffer), pipe))) {
5711                 char *value;
5712                 size_t namelen;
5713                 size_t valuelen;
5715                 name = chomp_string(name);
5716                 namelen = strcspn(name, separators);
5718                 if (name[namelen]) {
5719                         name[namelen] = 0;
5720                         value = chomp_string(name + namelen + 1);
5721                         valuelen = strlen(value);
5723                 } else {
5724                         value = "";
5725                         valuelen = 0;
5726                 }
5728                 state = read_property(name, namelen, value, valuelen);
5729         }
5731         if (state != ERR && ferror(pipe))
5732                 state = ERR;
5734         pclose(pipe);
5736         return state;
5740 /*
5741  * Main
5742  */
5744 static void __NORETURN
5745 quit(int sig)
5747         /* XXX: Restore tty modes and let the OS cleanup the rest! */
5748         if (cursed)
5749                 endwin();
5750         exit(0);
5753 static void __NORETURN
5754 die(const char *err, ...)
5756         va_list args;
5758         endwin();
5760         va_start(args, err);
5761         fputs("tig: ", stderr);
5762         vfprintf(stderr, err, args);
5763         fputs("\n", stderr);
5764         va_end(args);
5766         exit(1);
5769 static void
5770 warn(const char *msg, ...)
5772         va_list args;
5774         va_start(args, msg);
5775         fputs("tig warning: ", stderr);
5776         vfprintf(stderr, msg, args);
5777         fputs("\n", stderr);
5778         va_end(args);
5781 int
5782 main(int argc, char *argv[])
5784         struct view *view;
5785         enum request request;
5786         size_t i;
5788         signal(SIGINT, quit);
5790         if (setlocale(LC_ALL, "")) {
5791                 char *codeset = nl_langinfo(CODESET);
5793                 string_ncopy(opt_codeset, codeset, strlen(codeset));
5794         }
5796         if (load_repo_info() == ERR)
5797                 die("Failed to load repo info.");
5799         if (load_options() == ERR)
5800                 die("Failed to load user config.");
5802         if (load_git_config() == ERR)
5803                 die("Failed to load repo config.");
5805         if (!parse_options(argc, argv))
5806                 return 0;
5808         /* Require a git repository unless when running in pager mode. */
5809         if (!opt_git_dir[0] && opt_request != REQ_VIEW_PAGER)
5810                 die("Not a git repository");
5812         if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
5813                 opt_utf8 = FALSE;
5815         if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
5816                 opt_iconv = iconv_open(opt_codeset, opt_encoding);
5817                 if (opt_iconv == ICONV_NONE)
5818                         die("Failed to initialize character set conversion");
5819         }
5821         if (*opt_git_dir && load_refs() == ERR)
5822                 die("Failed to load refs.");
5824         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
5825                 view->cmd_env = getenv(view->cmd_env);
5827         request = opt_request;
5829         init_display();
5831         while (view_driver(display[current_view], request)) {
5832                 int key;
5833                 int i;
5835                 foreach_view (view, i)
5836                         update_view(view);
5838                 /* Refresh, accept single keystroke of input */
5839                 key = wgetch(status_win);
5841                 /* wgetch() with nodelay() enabled returns ERR when there's no
5842                  * input. */
5843                 if (key == ERR) {
5844                         request = REQ_NONE;
5845                         continue;
5846                 }
5848                 request = get_keybinding(display[current_view]->keymap, key);
5850                 /* Some low-level request handling. This keeps access to
5851                  * status_win restricted. */
5852                 switch (request) {
5853                 case REQ_PROMPT:
5854                 {
5855                         char *cmd = read_prompt(":");
5857                         if (cmd && string_format(opt_cmd, "git %s", cmd)) {
5858                                 if (strncmp(cmd, "show", 4) && isspace(cmd[4])) {
5859                                         opt_request = REQ_VIEW_DIFF;
5860                                 } else {
5861                                         opt_request = REQ_VIEW_PAGER;
5862                                 }
5863                                 break;
5864                         }
5866                         request = REQ_NONE;
5867                         break;
5868                 }
5869                 case REQ_SEARCH:
5870                 case REQ_SEARCH_BACK:
5871                 {
5872                         const char *prompt = request == REQ_SEARCH
5873                                            ? "/" : "?";
5874                         char *search = read_prompt(prompt);
5876                         if (search)
5877                                 string_ncopy(opt_search, search, strlen(search));
5878                         else
5879                                 request = REQ_NONE;
5880                         break;
5881                 }
5882                 case REQ_SCREEN_RESIZE:
5883                 {
5884                         int height, width;
5886                         getmaxyx(stdscr, height, width);
5888                         /* Resize the status view and let the view driver take
5889                          * care of resizing the displayed views. */
5890                         wresize(status_win, 1, width);
5891                         mvwin(status_win, height - 1, 0);
5892                         wrefresh(status_win);
5893                         break;
5894                 }
5895                 default:
5896                         break;
5897                 }
5898         }
5900         quit(0);
5902         return 0;