Code

Output extra \n on tig -h
[tig.git] / tig.c
1 /* Copyright (c) 2006-2008 Jonas Fonseca <fonseca@diku.dk>
2  *
3  * This program is free software; you can redistribute it and/or
4  * modify it under the terms of the GNU General Public License as
5  * published by the Free Software Foundation; either version 2 of
6  * the License, or (at your option) any later version.
7  *
8  * This program is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11  * GNU General Public License for more details.
12  */
14 #ifdef HAVE_CONFIG_H
15 #include "config.h"
16 #endif
18 #ifndef TIG_VERSION
19 #define TIG_VERSION "unknown-version"
20 #endif
22 #ifndef DEBUG
23 #define NDEBUG
24 #endif
26 #include <assert.h>
27 #include <errno.h>
28 #include <ctype.h>
29 #include <signal.h>
30 #include <stdarg.h>
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <sys/types.h>
35 #include <sys/stat.h>
36 #include <unistd.h>
37 #include <time.h>
39 #include <regex.h>
41 #include <locale.h>
42 #include <langinfo.h>
43 #include <iconv.h>
45 /* ncurses(3): Must be defined to have extended wide-character functions. */
46 #define _XOPEN_SOURCE_EXTENDED
48 #include <curses.h>
50 #if __GNUC__ >= 3
51 #define __NORETURN __attribute__((__noreturn__))
52 #else
53 #define __NORETURN
54 #endif
56 static void __NORETURN die(const char *err, ...);
57 static void warn(const char *msg, ...);
58 static void report(const char *msg, ...);
59 static int read_properties(FILE *pipe, const char *separators, int (*read)(char *, size_t, char *, size_t));
60 static void set_nonblocking_input(bool loading);
61 static size_t utf8_length(const char *string, size_t max_width, int *trimmed, bool reserve);
63 #define ABS(x)          ((x) >= 0  ? (x) : -(x))
64 #define MIN(x, y)       ((x) < (y) ? (x) :  (y))
66 #define ARRAY_SIZE(x)   (sizeof(x) / sizeof(x[0]))
67 #define STRING_SIZE(x)  (sizeof(x) - 1)
69 #define SIZEOF_STR      1024    /* Default string size. */
70 #define SIZEOF_REF      256     /* Size of symbolic or SHA1 ID. */
71 #define SIZEOF_REV      41      /* Holds a SHA-1 and an ending NUL */
73 /* Revision graph */
75 #define REVGRAPH_INIT   'I'
76 #define REVGRAPH_MERGE  'M'
77 #define REVGRAPH_BRANCH '+'
78 #define REVGRAPH_COMMIT '*'
79 #define REVGRAPH_BOUND  '^'
80 #define REVGRAPH_LINE   '|'
82 #define SIZEOF_REVGRAPH 19      /* Size of revision ancestry graphics. */
84 /* This color name can be used to refer to the default term colors. */
85 #define COLOR_DEFAULT   (-1)
87 #define ICONV_NONE      ((iconv_t) -1)
88 #ifndef ICONV_CONST
89 #define ICONV_CONST     /* nothing */
90 #endif
92 /* The format and size of the date column in the main view. */
93 #define DATE_FORMAT     "%Y-%m-%d %H:%M"
94 #define DATE_COLS       STRING_SIZE("2006-04-29 14:21 ")
96 #define AUTHOR_COLS     20
97 #define ID_COLS         8
99 /* The default interval between line numbers. */
100 #define NUMBER_INTERVAL 5
102 #define TABSIZE         8
104 #define SCALE_SPLIT_VIEW(height)        ((height) * 2 / 3)
106 #define NULL_ID         "0000000000000000000000000000000000000000"
108 #ifndef GIT_CONFIG
109 #define GIT_CONFIG "git config"
110 #endif
112 #define TIG_LS_REMOTE \
113         "git ls-remote $(git rev-parse --git-dir) 2>/dev/null"
115 #define TIG_DIFF_CMD \
116         "git show --pretty=fuller --no-color --root --patch-with-stat --find-copies-harder -C %s 2>/dev/null"
118 #define TIG_LOG_CMD     \
119         "git log --no-color --cc --stat -n100 %s 2>/dev/null"
121 #define TIG_MAIN_CMD \
122         "git log --no-color --topo-order --parents --boundary --pretty=raw %s 2>/dev/null"
124 #define TIG_TREE_CMD    \
125         "git ls-tree %s %s"
127 #define TIG_BLOB_CMD    \
128         "git cat-file blob %s"
130 /* XXX: Needs to be defined to the empty string. */
131 #define TIG_HELP_CMD    ""
132 #define TIG_PAGER_CMD   ""
133 #define TIG_STATUS_CMD  ""
134 #define TIG_STAGE_CMD   ""
135 #define TIG_BLAME_CMD   ""
137 /* Some ascii-shorthands fitted into the ncurses namespace. */
138 #define KEY_TAB         '\t'
139 #define KEY_RETURN      '\r'
140 #define KEY_ESC         27
143 struct ref {
144         char *name;             /* Ref name; tag or head names are shortened. */
145         char id[SIZEOF_REV];    /* Commit SHA1 ID */
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 next:1;    /* For ref lists: are there more refs? */
150         unsigned int head:1;    /* Is it the current HEAD? */
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_(VIEW_CLOSE,        "Close the current view"), \
335         REQ_(QUIT,              "Close all views and quit"), \
336         \
337         REQ_GROUP("Cursor navigation") \
338         REQ_(MOVE_UP,           "Move cursor one line up"), \
339         REQ_(MOVE_DOWN,         "Move cursor one line down"), \
340         REQ_(MOVE_PAGE_DOWN,    "Move cursor one page down"), \
341         REQ_(MOVE_PAGE_UP,      "Move cursor one page up"), \
342         REQ_(MOVE_FIRST_LINE,   "Move cursor to first line"), \
343         REQ_(MOVE_LAST_LINE,    "Move cursor to last line"), \
344         \
345         REQ_GROUP("Scrolling") \
346         REQ_(SCROLL_LINE_UP,    "Scroll one line up"), \
347         REQ_(SCROLL_LINE_DOWN,  "Scroll one line down"), \
348         REQ_(SCROLL_PAGE_UP,    "Scroll one page up"), \
349         REQ_(SCROLL_PAGE_DOWN,  "Scroll one page down"), \
350         \
351         REQ_GROUP("Searching") \
352         REQ_(SEARCH,            "Search the view"), \
353         REQ_(SEARCH_BACK,       "Search backwards in the view"), \
354         REQ_(FIND_NEXT,         "Find next search match"), \
355         REQ_(FIND_PREV,         "Find previous search match"), \
356         \
357         REQ_GROUP("Misc") \
358         REQ_(PROMPT,            "Bring up the prompt"), \
359         REQ_(SCREEN_REDRAW,     "Redraw the screen"), \
360         REQ_(SCREEN_RESIZE,     "Resize the screen"), \
361         REQ_(SHOW_VERSION,      "Show version information"), \
362         REQ_(STOP_LOADING,      "Stop all loading views"), \
363         REQ_(TOGGLE_LINENO,     "Toggle line numbers"), \
364         REQ_(TOGGLE_DATE,       "Toggle date display"), \
365         REQ_(TOGGLE_AUTHOR,     "Toggle author display"), \
366         REQ_(TOGGLE_REV_GRAPH,  "Toggle revision graph visualization"), \
367         REQ_(TOGGLE_REFS,       "Toggle reference display (tags/branches)"), \
368         REQ_(STATUS_UPDATE,     "Update file status"), \
369         REQ_(STATUS_MERGE,      "Merge file using external tool"), \
370         REQ_(TREE_PARENT,       "Switch to parent directory in tree view"), \
371         REQ_(EDIT,              "Open in editor"), \
372         REQ_(NONE,              "Do nothing")
375 /* User action requests. */
376 enum request {
377 #define REQ_GROUP(help)
378 #define REQ_(req, help) REQ_##req
380         /* Offset all requests to avoid conflicts with ncurses getch values. */
381         REQ_OFFSET = KEY_MAX + 1,
382         REQ_INFO
384 #undef  REQ_GROUP
385 #undef  REQ_
386 };
388 struct request_info {
389         enum request request;
390         char *name;
391         int namelen;
392         char *help;
393 };
395 static struct request_info req_info[] = {
396 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
397 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
398         REQ_INFO
399 #undef  REQ_GROUP
400 #undef  REQ_
401 };
403 static enum request
404 get_request(const char *name)
406         int namelen = strlen(name);
407         int i;
409         for (i = 0; i < ARRAY_SIZE(req_info); i++)
410                 if (req_info[i].namelen == namelen &&
411                     !string_enum_compare(req_info[i].name, name, namelen))
412                         return req_info[i].request;
414         return REQ_NONE;
418 /*
419  * Options
420  */
422 static const char usage[] =
423 "tig " TIG_VERSION " (" __DATE__ ")\n"
424 "\n"
425 "Usage: tig        [options] [revs] [--] [paths]\n"
426 "   or: tig show   [options] [revs] [--] [paths]\n"
427 "   or: tig blame  [rev] path\n"
428 "   or: tig status\n"
429 "   or: tig <      [git command output]\n"
430 "\n"
431 "Options:\n"
432 "  -v, --version   Show version and exit\n"
433 "  -h, --help      Show help message and exit";
435 /* Option and state variables. */
436 static bool opt_date                    = TRUE;
437 static bool opt_author                  = TRUE;
438 static bool opt_line_number             = FALSE;
439 static bool opt_rev_graph               = FALSE;
440 static bool opt_show_refs               = TRUE;
441 static int opt_num_interval             = NUMBER_INTERVAL;
442 static int opt_tab_size                 = TABSIZE;
443 static enum request opt_request         = REQ_VIEW_MAIN;
444 static char opt_cmd[SIZEOF_STR]         = "";
445 static char opt_path[SIZEOF_STR]        = "";
446 static char opt_file[SIZEOF_STR]        = "";
447 static char opt_ref[SIZEOF_REF]         = "";
448 static char opt_head[SIZEOF_REF]        = "";
449 static bool opt_no_head                 = TRUE;
450 static FILE *opt_pipe                   = NULL;
451 static char opt_encoding[20]            = "UTF-8";
452 static bool opt_utf8                    = TRUE;
453 static char opt_codeset[20]             = "UTF-8";
454 static iconv_t opt_iconv                = ICONV_NONE;
455 static char opt_search[SIZEOF_STR]      = "";
456 static char opt_cdup[SIZEOF_STR]        = "";
457 static char opt_git_dir[SIZEOF_STR]     = "";
458 static signed char opt_is_inside_work_tree      = -1; /* set to TRUE or FALSE */
459 static char opt_editor[SIZEOF_STR]      = "";
461 static bool
462 parse_options(int argc, char *argv[])
464         size_t buf_size;
465         char *subcommand;
466         bool seen_dashdash = FALSE;
467         int i;
469         if (argc <= 1)
470                 return TRUE;
472         subcommand = argv[1];
473         if (!strcmp(subcommand, "status") || !strcmp(subcommand, "-S")) {
474                 opt_request = REQ_VIEW_STATUS;
475                 if (!strcmp(subcommand, "-S"))
476                         warn("`-S' has been deprecated; use `tig status' instead");
477                 if (argc > 2)
478                         warn("ignoring arguments after `%s'", subcommand);
479                 return TRUE;
481         } else if (!strcmp(subcommand, "blame")) {
482                 opt_request = REQ_VIEW_BLAME;
483                 if (argc <= 2 || argc > 4)
484                         die("invalid number of options to blame\n\n%s", usage);
486                 i = 2;
487                 if (argc == 4) {
488                         string_ncopy(opt_ref, argv[i], strlen(argv[i]));
489                         i++;
490                 }
492                 string_ncopy(opt_file, argv[i], strlen(argv[i]));
493                 return TRUE;
495         } else if (!strcmp(subcommand, "show")) {
496                 opt_request = REQ_VIEW_DIFF;
498         } else if (!strcmp(subcommand, "log") || !strcmp(subcommand, "diff")) {
499                 opt_request = subcommand[0] == 'l'
500                             ? REQ_VIEW_LOG : REQ_VIEW_DIFF;
501                 warn("`tig %s' has been deprecated", subcommand);
503         } else {
504                 subcommand = NULL;
505         }
507         if (!subcommand)
508                 /* XXX: This is vulnerable to the user overriding
509                  * options required for the main view parser. */
510                 string_copy(opt_cmd, "git log --no-color --pretty=raw --boundary --parents");
511         else
512                 string_format(opt_cmd, "git %s", subcommand);
514         buf_size = strlen(opt_cmd);
516         for (i = 1 + !!subcommand; i < argc; i++) {
517                 char *opt = argv[i];
519                 if (seen_dashdash || !strcmp(opt, "--")) {
520                         seen_dashdash = TRUE;
522                 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
523                         printf("tig version %s\n", TIG_VERSION);
524                         return FALSE;
526                 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
527                         printf("%s\n", usage);
528                         return FALSE;
529                 }
531                 opt_cmd[buf_size++] = ' ';
532                 buf_size = sq_quote(opt_cmd, buf_size, opt);
533                 if (buf_size >= sizeof(opt_cmd))
534                         die("command too long");
535         }
537         if (!isatty(STDIN_FILENO)) {
538                 opt_request = REQ_VIEW_PAGER;
539                 opt_pipe = stdin;
540                 buf_size = 0;
541         }
543         opt_cmd[buf_size] = 0;
545         return TRUE;
549 /*
550  * Line-oriented content detection.
551  */
553 #define LINE_INFO \
554 LINE(DIFF_HEADER,  "diff --git ",       COLOR_YELLOW,   COLOR_DEFAULT,  0), \
555 LINE(DIFF_CHUNK,   "@@",                COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
556 LINE(DIFF_ADD,     "+",                 COLOR_GREEN,    COLOR_DEFAULT,  0), \
557 LINE(DIFF_DEL,     "-",                 COLOR_RED,      COLOR_DEFAULT,  0), \
558 LINE(DIFF_INDEX,        "index ",         COLOR_BLUE,   COLOR_DEFAULT,  0), \
559 LINE(DIFF_OLDMODE,      "old file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
560 LINE(DIFF_NEWMODE,      "new file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
561 LINE(DIFF_COPY_FROM,    "copy from",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
562 LINE(DIFF_COPY_TO,      "copy to",        COLOR_YELLOW, COLOR_DEFAULT,  0), \
563 LINE(DIFF_RENAME_FROM,  "rename from",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
564 LINE(DIFF_RENAME_TO,    "rename to",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
565 LINE(DIFF_SIMILARITY,   "similarity ",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
566 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
567 LINE(DIFF_TREE,         "diff-tree ",     COLOR_BLUE,   COLOR_DEFAULT,  0), \
568 LINE(PP_AUTHOR,    "Author: ",          COLOR_CYAN,     COLOR_DEFAULT,  0), \
569 LINE(PP_COMMIT,    "Commit: ",          COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
570 LINE(PP_MERGE,     "Merge: ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
571 LINE(PP_DATE,      "Date:   ",          COLOR_YELLOW,   COLOR_DEFAULT,  0), \
572 LINE(PP_ADATE,     "AuthorDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
573 LINE(PP_CDATE,     "CommitDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
574 LINE(PP_REFS,      "Refs: ",            COLOR_RED,      COLOR_DEFAULT,  0), \
575 LINE(COMMIT,       "commit ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
576 LINE(PARENT,       "parent ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
577 LINE(TREE,         "tree ",             COLOR_BLUE,     COLOR_DEFAULT,  0), \
578 LINE(AUTHOR,       "author ",           COLOR_CYAN,     COLOR_DEFAULT,  0), \
579 LINE(COMMITTER,    "committer ",        COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
580 LINE(SIGNOFF,      "    Signed-off-by", COLOR_YELLOW,   COLOR_DEFAULT,  0), \
581 LINE(ACKED,        "    Acked-by",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
582 LINE(DEFAULT,      "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
583 LINE(CURSOR,       "",                  COLOR_WHITE,    COLOR_GREEN,    A_BOLD), \
584 LINE(STATUS,       "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
585 LINE(DELIMITER,    "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
586 LINE(TITLE_BLUR,   "",                  COLOR_WHITE,    COLOR_BLUE,     0), \
587 LINE(TITLE_FOCUS,  "",                  COLOR_WHITE,    COLOR_BLUE,     A_BOLD), \
588 LINE(MAIN_DATE,    "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
589 LINE(MAIN_AUTHOR,  "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
590 LINE(MAIN_COMMIT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
591 LINE(MAIN_TAG,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
592 LINE(MAIN_LOCAL_TAG,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
593 LINE(MAIN_REMOTE,  "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_BOLD), \
594 LINE(MAIN_REF,     "",                  COLOR_CYAN,     COLOR_DEFAULT,  A_BOLD), \
595 LINE(MAIN_HEAD,    "",                  COLOR_RED,      COLOR_DEFAULT,  A_BOLD), \
596 LINE(MAIN_REVGRAPH,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
597 LINE(TREE_DIR,     "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
598 LINE(TREE_FILE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
599 LINE(STAT_HEAD,    "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
600 LINE(STAT_SECTION, "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
601 LINE(STAT_NONE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
602 LINE(STAT_STAGED,  "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
603 LINE(STAT_UNSTAGED,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
604 LINE(STAT_UNTRACKED,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
605 LINE(BLAME_DATE,    "",                 COLOR_BLUE,     COLOR_DEFAULT,  0), \
606 LINE(BLAME_AUTHOR,  "",                 COLOR_GREEN,    COLOR_DEFAULT,  0), \
607 LINE(BLAME_COMMIT, "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
608 LINE(BLAME_ID,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
609 LINE(BLAME_LINENO, "",                  COLOR_CYAN,     COLOR_DEFAULT,  0)
611 enum line_type {
612 #define LINE(type, line, fg, bg, attr) \
613         LINE_##type
614         LINE_INFO
615 #undef  LINE
616 };
618 struct line_info {
619         const char *name;       /* Option name. */
620         int namelen;            /* Size of option name. */
621         const char *line;       /* The start of line to match. */
622         int linelen;            /* Size of string to match. */
623         int fg, bg, attr;       /* Color and text attributes for the lines. */
624 };
626 static struct line_info line_info[] = {
627 #define LINE(type, line, fg, bg, attr) \
628         { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
629         LINE_INFO
630 #undef  LINE
631 };
633 static enum line_type
634 get_line_type(char *line)
636         int linelen = strlen(line);
637         enum line_type type;
639         for (type = 0; type < ARRAY_SIZE(line_info); type++)
640                 /* Case insensitive search matches Signed-off-by lines better. */
641                 if (linelen >= line_info[type].linelen &&
642                     !strncasecmp(line_info[type].line, line, line_info[type].linelen))
643                         return type;
645         return LINE_DEFAULT;
648 static inline int
649 get_line_attr(enum line_type type)
651         assert(type < ARRAY_SIZE(line_info));
652         return COLOR_PAIR(type) | line_info[type].attr;
655 static struct line_info *
656 get_line_info(char *name)
658         size_t namelen = strlen(name);
659         enum line_type type;
661         for (type = 0; type < ARRAY_SIZE(line_info); type++)
662                 if (namelen == line_info[type].namelen &&
663                     !string_enum_compare(line_info[type].name, name, namelen))
664                         return &line_info[type];
666         return NULL;
669 static void
670 init_colors(void)
672         int default_bg = line_info[LINE_DEFAULT].bg;
673         int default_fg = line_info[LINE_DEFAULT].fg;
674         enum line_type type;
676         start_color();
678         if (assume_default_colors(default_fg, default_bg) == ERR) {
679                 default_bg = COLOR_BLACK;
680                 default_fg = COLOR_WHITE;
681         }
683         for (type = 0; type < ARRAY_SIZE(line_info); type++) {
684                 struct line_info *info = &line_info[type];
685                 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
686                 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
688                 init_pair(type, fg, bg);
689         }
692 struct line {
693         enum line_type type;
695         /* State flags */
696         unsigned int selected:1;
697         unsigned int dirty:1;
699         void *data;             /* User data */
700 };
703 /*
704  * Keys
705  */
707 struct keybinding {
708         int alias;
709         enum request request;
710         struct keybinding *next;
711 };
713 static struct keybinding default_keybindings[] = {
714         /* View switching */
715         { 'm',          REQ_VIEW_MAIN },
716         { 'd',          REQ_VIEW_DIFF },
717         { 'l',          REQ_VIEW_LOG },
718         { 't',          REQ_VIEW_TREE },
719         { 'f',          REQ_VIEW_BLOB },
720         { 'B',          REQ_VIEW_BLAME },
721         { 'p',          REQ_VIEW_PAGER },
722         { 'h',          REQ_VIEW_HELP },
723         { 'S',          REQ_VIEW_STATUS },
724         { 'c',          REQ_VIEW_STAGE },
726         /* View manipulation */
727         { 'q',          REQ_VIEW_CLOSE },
728         { KEY_TAB,      REQ_VIEW_NEXT },
729         { KEY_RETURN,   REQ_ENTER },
730         { KEY_UP,       REQ_PREVIOUS },
731         { KEY_DOWN,     REQ_NEXT },
732         { 'R',          REQ_REFRESH },
734         /* Cursor navigation */
735         { 'k',          REQ_MOVE_UP },
736         { 'j',          REQ_MOVE_DOWN },
737         { KEY_HOME,     REQ_MOVE_FIRST_LINE },
738         { KEY_END,      REQ_MOVE_LAST_LINE },
739         { KEY_NPAGE,    REQ_MOVE_PAGE_DOWN },
740         { ' ',          REQ_MOVE_PAGE_DOWN },
741         { KEY_PPAGE,    REQ_MOVE_PAGE_UP },
742         { 'b',          REQ_MOVE_PAGE_UP },
743         { '-',          REQ_MOVE_PAGE_UP },
745         /* Scrolling */
746         { KEY_IC,       REQ_SCROLL_LINE_UP },
747         { KEY_DC,       REQ_SCROLL_LINE_DOWN },
748         { 'w',          REQ_SCROLL_PAGE_UP },
749         { 's',          REQ_SCROLL_PAGE_DOWN },
751         /* Searching */
752         { '/',          REQ_SEARCH },
753         { '?',          REQ_SEARCH_BACK },
754         { 'n',          REQ_FIND_NEXT },
755         { 'N',          REQ_FIND_PREV },
757         /* Misc */
758         { 'Q',          REQ_QUIT },
759         { 'z',          REQ_STOP_LOADING },
760         { 'v',          REQ_SHOW_VERSION },
761         { 'r',          REQ_SCREEN_REDRAW },
762         { '.',          REQ_TOGGLE_LINENO },
763         { 'D',          REQ_TOGGLE_DATE },
764         { 'A',          REQ_TOGGLE_AUTHOR },
765         { 'g',          REQ_TOGGLE_REV_GRAPH },
766         { 'F',          REQ_TOGGLE_REFS },
767         { ':',          REQ_PROMPT },
768         { 'u',          REQ_STATUS_UPDATE },
769         { 'M',          REQ_STATUS_MERGE },
770         { ',',          REQ_TREE_PARENT },
771         { 'e',          REQ_EDIT },
773         /* Using the ncurses SIGWINCH handler. */
774         { KEY_RESIZE,   REQ_SCREEN_RESIZE },
775 };
777 #define KEYMAP_INFO \
778         KEYMAP_(GENERIC), \
779         KEYMAP_(MAIN), \
780         KEYMAP_(DIFF), \
781         KEYMAP_(LOG), \
782         KEYMAP_(TREE), \
783         KEYMAP_(BLOB), \
784         KEYMAP_(BLAME), \
785         KEYMAP_(PAGER), \
786         KEYMAP_(HELP), \
787         KEYMAP_(STATUS), \
788         KEYMAP_(STAGE)
790 enum keymap {
791 #define KEYMAP_(name) KEYMAP_##name
792         KEYMAP_INFO
793 #undef  KEYMAP_
794 };
796 static struct int_map keymap_table[] = {
797 #define KEYMAP_(name) { #name, STRING_SIZE(#name), KEYMAP_##name }
798         KEYMAP_INFO
799 #undef  KEYMAP_
800 };
802 #define set_keymap(map, name) \
803         set_from_int_map(keymap_table, ARRAY_SIZE(keymap_table), map, name, strlen(name))
805 static struct keybinding *keybindings[ARRAY_SIZE(keymap_table)];
807 static void
808 add_keybinding(enum keymap keymap, enum request request, int key)
810         struct keybinding *keybinding;
812         keybinding = calloc(1, sizeof(*keybinding));
813         if (!keybinding)
814                 die("Failed to allocate keybinding");
816         keybinding->alias = key;
817         keybinding->request = request;
818         keybinding->next = keybindings[keymap];
819         keybindings[keymap] = keybinding;
822 /* Looks for a key binding first in the given map, then in the generic map, and
823  * lastly in the default keybindings. */
824 static enum request
825 get_keybinding(enum keymap keymap, int key)
827         struct keybinding *kbd;
828         int i;
830         for (kbd = keybindings[keymap]; kbd; kbd = kbd->next)
831                 if (kbd->alias == key)
832                         return kbd->request;
834         for (kbd = keybindings[KEYMAP_GENERIC]; kbd; kbd = kbd->next)
835                 if (kbd->alias == key)
836                         return kbd->request;
838         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
839                 if (default_keybindings[i].alias == key)
840                         return default_keybindings[i].request;
842         return (enum request) key;
846 struct key {
847         char *name;
848         int value;
849 };
851 static struct key key_table[] = {
852         { "Enter",      KEY_RETURN },
853         { "Space",      ' ' },
854         { "Backspace",  KEY_BACKSPACE },
855         { "Tab",        KEY_TAB },
856         { "Escape",     KEY_ESC },
857         { "Left",       KEY_LEFT },
858         { "Right",      KEY_RIGHT },
859         { "Up",         KEY_UP },
860         { "Down",       KEY_DOWN },
861         { "Insert",     KEY_IC },
862         { "Delete",     KEY_DC },
863         { "Hash",       '#' },
864         { "Home",       KEY_HOME },
865         { "End",        KEY_END },
866         { "PageUp",     KEY_PPAGE },
867         { "PageDown",   KEY_NPAGE },
868         { "F1",         KEY_F(1) },
869         { "F2",         KEY_F(2) },
870         { "F3",         KEY_F(3) },
871         { "F4",         KEY_F(4) },
872         { "F5",         KEY_F(5) },
873         { "F6",         KEY_F(6) },
874         { "F7",         KEY_F(7) },
875         { "F8",         KEY_F(8) },
876         { "F9",         KEY_F(9) },
877         { "F10",        KEY_F(10) },
878         { "F11",        KEY_F(11) },
879         { "F12",        KEY_F(12) },
880 };
882 static int
883 get_key_value(const char *name)
885         int i;
887         for (i = 0; i < ARRAY_SIZE(key_table); i++)
888                 if (!strcasecmp(key_table[i].name, name))
889                         return key_table[i].value;
891         if (strlen(name) == 1 && isprint(*name))
892                 return (int) *name;
894         return ERR;
897 static char *
898 get_key_name(int key_value)
900         static char key_char[] = "'X'";
901         char *seq = NULL;
902         int key;
904         for (key = 0; key < ARRAY_SIZE(key_table); key++)
905                 if (key_table[key].value == key_value)
906                         seq = key_table[key].name;
908         if (seq == NULL &&
909             key_value < 127 &&
910             isprint(key_value)) {
911                 key_char[1] = (char) key_value;
912                 seq = key_char;
913         }
915         return seq ? seq : "'?'";
918 static char *
919 get_key(enum request request)
921         static char buf[BUFSIZ];
922         size_t pos = 0;
923         char *sep = "";
924         int i;
926         buf[pos] = 0;
928         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
929                 struct keybinding *keybinding = &default_keybindings[i];
931                 if (keybinding->request != request)
932                         continue;
934                 if (!string_format_from(buf, &pos, "%s%s", sep,
935                                         get_key_name(keybinding->alias)))
936                         return "Too many keybindings!";
937                 sep = ", ";
938         }
940         return buf;
943 struct run_request {
944         enum keymap keymap;
945         int key;
946         char cmd[SIZEOF_STR];
947 };
949 static struct run_request *run_request;
950 static size_t run_requests;
952 static enum request
953 add_run_request(enum keymap keymap, int key, int argc, char **argv)
955         struct run_request *tmp;
956         struct run_request req = { keymap, key };
957         size_t bufpos;
959         for (bufpos = 0; argc > 0; argc--, argv++)
960                 if (!string_format_from(req.cmd, &bufpos, "%s ", *argv))
961                         return REQ_NONE;
963         req.cmd[bufpos - 1] = 0;
965         tmp = realloc(run_request, (run_requests + 1) * sizeof(*run_request));
966         if (!tmp)
967                 return REQ_NONE;
969         run_request = tmp;
970         run_request[run_requests++] = req;
972         return REQ_NONE + run_requests;
975 static struct run_request *
976 get_run_request(enum request request)
978         if (request <= REQ_NONE)
979                 return NULL;
980         return &run_request[request - REQ_NONE - 1];
983 static void
984 add_builtin_run_requests(void)
986         struct {
987                 enum keymap keymap;
988                 int key;
989                 char *argv[1];
990         } reqs[] = {
991                 { KEYMAP_MAIN,    'C', { "git cherry-pick %(commit)" } },
992                 { KEYMAP_GENERIC, 'G', { "git gc" } },
993         };
994         int i;
996         for (i = 0; i < ARRAY_SIZE(reqs); i++) {
997                 enum request req;
999                 req = add_run_request(reqs[i].keymap, reqs[i].key, 1, reqs[i].argv);
1000                 if (req != REQ_NONE)
1001                         add_keybinding(reqs[i].keymap, req, reqs[i].key);
1002         }
1005 /*
1006  * User config file handling.
1007  */
1009 static struct int_map color_map[] = {
1010 #define COLOR_MAP(name) { #name, STRING_SIZE(#name), COLOR_##name }
1011         COLOR_MAP(DEFAULT),
1012         COLOR_MAP(BLACK),
1013         COLOR_MAP(BLUE),
1014         COLOR_MAP(CYAN),
1015         COLOR_MAP(GREEN),
1016         COLOR_MAP(MAGENTA),
1017         COLOR_MAP(RED),
1018         COLOR_MAP(WHITE),
1019         COLOR_MAP(YELLOW),
1020 };
1022 #define set_color(color, name) \
1023         set_from_int_map(color_map, ARRAY_SIZE(color_map), color, name, strlen(name))
1025 static struct int_map attr_map[] = {
1026 #define ATTR_MAP(name) { #name, STRING_SIZE(#name), A_##name }
1027         ATTR_MAP(NORMAL),
1028         ATTR_MAP(BLINK),
1029         ATTR_MAP(BOLD),
1030         ATTR_MAP(DIM),
1031         ATTR_MAP(REVERSE),
1032         ATTR_MAP(STANDOUT),
1033         ATTR_MAP(UNDERLINE),
1034 };
1036 #define set_attribute(attr, name) \
1037         set_from_int_map(attr_map, ARRAY_SIZE(attr_map), attr, name, strlen(name))
1039 static int   config_lineno;
1040 static bool  config_errors;
1041 static char *config_msg;
1043 /* Wants: object fgcolor bgcolor [attr] */
1044 static int
1045 option_color_command(int argc, char *argv[])
1047         struct line_info *info;
1049         if (argc != 3 && argc != 4) {
1050                 config_msg = "Wrong number of arguments given to color command";
1051                 return ERR;
1052         }
1054         info = get_line_info(argv[0]);
1055         if (!info) {
1056                 if (!string_enum_compare(argv[0], "main-delim", strlen("main-delim"))) {
1057                         info = get_line_info("delimiter");
1059                 } else {
1060                         config_msg = "Unknown color name";
1061                         return ERR;
1062                 }
1063         }
1065         if (set_color(&info->fg, argv[1]) == ERR ||
1066             set_color(&info->bg, argv[2]) == ERR) {
1067                 config_msg = "Unknown color";
1068                 return ERR;
1069         }
1071         if (argc == 4 && set_attribute(&info->attr, argv[3]) == ERR) {
1072                 config_msg = "Unknown attribute";
1073                 return ERR;
1074         }
1076         return OK;
1079 static bool parse_bool(const char *s)
1081         return (!strcmp(s, "1") || !strcmp(s, "true") ||
1082                 !strcmp(s, "yes")) ? TRUE : FALSE;
1085 /* Wants: name = value */
1086 static int
1087 option_set_command(int argc, char *argv[])
1089         if (argc != 3) {
1090                 config_msg = "Wrong number of arguments given to set command";
1091                 return ERR;
1092         }
1094         if (strcmp(argv[1], "=")) {
1095                 config_msg = "No value assigned";
1096                 return ERR;
1097         }
1099         if (!strcmp(argv[0], "show-author")) {
1100                 opt_author = parse_bool(argv[2]);
1101                 return OK;
1102         }
1104         if (!strcmp(argv[0], "show-date")) {
1105                 opt_date = parse_bool(argv[2]);
1106                 return OK;
1107         }
1109         if (!strcmp(argv[0], "show-rev-graph")) {
1110                 opt_rev_graph = parse_bool(argv[2]);
1111                 return OK;
1112         }
1114         if (!strcmp(argv[0], "show-refs")) {
1115                 opt_show_refs = parse_bool(argv[2]);
1116                 return OK;
1117         }
1119         if (!strcmp(argv[0], "show-line-numbers")) {
1120                 opt_line_number = parse_bool(argv[2]);
1121                 return OK;
1122         }
1124         if (!strcmp(argv[0], "line-number-interval")) {
1125                 opt_num_interval = atoi(argv[2]);
1126                 return OK;
1127         }
1129         if (!strcmp(argv[0], "tab-size")) {
1130                 opt_tab_size = atoi(argv[2]);
1131                 return OK;
1132         }
1134         if (!strcmp(argv[0], "commit-encoding")) {
1135                 char *arg = argv[2];
1136                 int delimiter = *arg;
1137                 int i;
1139                 switch (delimiter) {
1140                 case '"':
1141                 case '\'':
1142                         for (arg++, i = 0; arg[i]; i++)
1143                                 if (arg[i] == delimiter) {
1144                                         arg[i] = 0;
1145                                         break;
1146                                 }
1147                 default:
1148                         string_ncopy(opt_encoding, arg, strlen(arg));
1149                         return OK;
1150                 }
1151         }
1153         config_msg = "Unknown variable name";
1154         return ERR;
1157 /* Wants: mode request key */
1158 static int
1159 option_bind_command(int argc, char *argv[])
1161         enum request request;
1162         int keymap;
1163         int key;
1165         if (argc < 3) {
1166                 config_msg = "Wrong number of arguments given to bind command";
1167                 return ERR;
1168         }
1170         if (set_keymap(&keymap, argv[0]) == ERR) {
1171                 config_msg = "Unknown key map";
1172                 return ERR;
1173         }
1175         key = get_key_value(argv[1]);
1176         if (key == ERR) {
1177                 config_msg = "Unknown key";
1178                 return ERR;
1179         }
1181         request = get_request(argv[2]);
1182         if (request == REQ_NONE) {
1183                 const char *obsolete[] = { "cherry-pick" };
1184                 size_t namelen = strlen(argv[2]);
1185                 int i;
1187                 for (i = 0; i < ARRAY_SIZE(obsolete); i++) {
1188                         if (namelen == strlen(obsolete[i]) &&
1189                             !string_enum_compare(obsolete[i], argv[2], namelen)) {
1190                                 config_msg = "Obsolete request name";
1191                                 return ERR;
1192                         }
1193                 }
1194         }
1195         if (request == REQ_NONE && *argv[2]++ == '!')
1196                 request = add_run_request(keymap, key, argc - 2, argv + 2);
1197         if (request == REQ_NONE) {
1198                 config_msg = "Unknown request name";
1199                 return ERR;
1200         }
1202         add_keybinding(keymap, request, key);
1204         return OK;
1207 static int
1208 set_option(char *opt, char *value)
1210         char *argv[16];
1211         int valuelen;
1212         int argc = 0;
1214         /* Tokenize */
1215         while (argc < ARRAY_SIZE(argv) && (valuelen = strcspn(value, " \t"))) {
1216                 argv[argc++] = value;
1217                 value += valuelen;
1219                 /* Nothing more to tokenize or last available token. */
1220                 if (!*value || argc >= ARRAY_SIZE(argv))
1221                         break;
1223                 *value++ = 0;
1224                 while (isspace(*value))
1225                         value++;
1226         }
1228         if (!strcmp(opt, "color"))
1229                 return option_color_command(argc, argv);
1231         if (!strcmp(opt, "set"))
1232                 return option_set_command(argc, argv);
1234         if (!strcmp(opt, "bind"))
1235                 return option_bind_command(argc, argv);
1237         config_msg = "Unknown option command";
1238         return ERR;
1241 static int
1242 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1244         int status = OK;
1246         config_lineno++;
1247         config_msg = "Internal error";
1249         /* Check for comment markers, since read_properties() will
1250          * only ensure opt and value are split at first " \t". */
1251         optlen = strcspn(opt, "#");
1252         if (optlen == 0)
1253                 return OK;
1255         if (opt[optlen] != 0) {
1256                 config_msg = "No option value";
1257                 status = ERR;
1259         }  else {
1260                 /* Look for comment endings in the value. */
1261                 size_t len = strcspn(value, "#");
1263                 if (len < valuelen) {
1264                         valuelen = len;
1265                         value[valuelen] = 0;
1266                 }
1268                 status = set_option(opt, value);
1269         }
1271         if (status == ERR) {
1272                 fprintf(stderr, "Error on line %d, near '%.*s': %s\n",
1273                         config_lineno, (int) optlen, opt, config_msg);
1274                 config_errors = TRUE;
1275         }
1277         /* Always keep going if errors are encountered. */
1278         return OK;
1281 static void
1282 load_option_file(const char *path)
1284         FILE *file;
1286         /* It's ok that the file doesn't exist. */
1287         file = fopen(path, "r");
1288         if (!file)
1289                 return;
1291         config_lineno = 0;
1292         config_errors = FALSE;
1294         if (read_properties(file, " \t", read_option) == ERR ||
1295             config_errors == TRUE)
1296                 fprintf(stderr, "Errors while loading %s.\n", path);
1299 static int
1300 load_options(void)
1302         char *home = getenv("HOME");
1303         char *tigrc_user = getenv("TIGRC_USER");
1304         char *tigrc_system = getenv("TIGRC_SYSTEM");
1305         char buf[SIZEOF_STR];
1307         add_builtin_run_requests();
1309         if (!tigrc_system) {
1310                 if (!string_format(buf, "%s/tigrc", SYSCONFDIR))
1311                         return ERR;
1312                 tigrc_system = buf;
1313         }
1314         load_option_file(tigrc_system);
1316         if (!tigrc_user) {
1317                 if (!home || !string_format(buf, "%s/.tigrc", home))
1318                         return ERR;
1319                 tigrc_user = buf;
1320         }
1321         load_option_file(tigrc_user);
1323         return OK;
1327 /*
1328  * The viewer
1329  */
1331 struct view;
1332 struct view_ops;
1334 /* The display array of active views and the index of the current view. */
1335 static struct view *display[2];
1336 static unsigned int current_view;
1338 /* Reading from the prompt? */
1339 static bool input_mode = FALSE;
1341 #define foreach_displayed_view(view, i) \
1342         for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1344 #define displayed_views()       (display[1] != NULL ? 2 : 1)
1346 /* Current head and commit ID */
1347 static char ref_blob[SIZEOF_REF]        = "";
1348 static char ref_commit[SIZEOF_REF]      = "HEAD";
1349 static char ref_head[SIZEOF_REF]        = "HEAD";
1351 struct view {
1352         const char *name;       /* View name */
1353         const char *cmd_fmt;    /* Default command line format */
1354         const char *cmd_env;    /* Command line set via environment */
1355         const char *id;         /* Points to either of ref_{head,commit,blob} */
1357         struct view_ops *ops;   /* View operations */
1359         enum keymap keymap;     /* What keymap does this view have */
1361         char cmd[SIZEOF_STR];   /* Command buffer */
1362         char ref[SIZEOF_REF];   /* Hovered commit reference */
1363         char vid[SIZEOF_REF];   /* View ID. Set to id member when updating. */
1365         int height, width;      /* The width and height of the main window */
1366         WINDOW *win;            /* The main window */
1367         WINDOW *title;          /* The title window living below the main window */
1369         /* Navigation */
1370         unsigned long offset;   /* Offset of the window top */
1371         unsigned long lineno;   /* Current line number */
1373         /* Searching */
1374         char grep[SIZEOF_STR];  /* Search string */
1375         regex_t *regex;         /* Pre-compiled regex */
1377         /* If non-NULL, points to the view that opened this view. If this view
1378          * is closed tig will switch back to the parent view. */
1379         struct view *parent;
1381         /* Buffering */
1382         size_t lines;           /* Total number of lines */
1383         struct line *line;      /* Line index */
1384         size_t line_alloc;      /* Total number of allocated lines */
1385         size_t line_size;       /* Total number of used lines */
1386         unsigned int digits;    /* Number of digits in the lines member. */
1388         /* Loading */
1389         FILE *pipe;
1390         time_t start_time;
1391 };
1393 struct view_ops {
1394         /* What type of content being displayed. Used in the title bar. */
1395         const char *type;
1396         /* Open and reads in all view content. */
1397         bool (*open)(struct view *view);
1398         /* Read one line; updates view->line. */
1399         bool (*read)(struct view *view, char *data);
1400         /* Draw one line; @lineno must be < view->height. */
1401         bool (*draw)(struct view *view, struct line *line, unsigned int lineno, bool selected);
1402         /* Depending on view handle a special requests. */
1403         enum request (*request)(struct view *view, enum request request, struct line *line);
1404         /* Search for regex in a line. */
1405         bool (*grep)(struct view *view, struct line *line);
1406         /* Select line */
1407         void (*select)(struct view *view, struct line *line);
1408 };
1410 static struct view_ops pager_ops;
1411 static struct view_ops main_ops;
1412 static struct view_ops tree_ops;
1413 static struct view_ops blob_ops;
1414 static struct view_ops blame_ops;
1415 static struct view_ops help_ops;
1416 static struct view_ops status_ops;
1417 static struct view_ops stage_ops;
1419 #define VIEW_STR(name, cmd, env, ref, ops, map) \
1420         { name, cmd, #env, ref, ops, map}
1422 #define VIEW_(id, name, ops, ref) \
1423         VIEW_STR(name, TIG_##id##_CMD,  TIG_##id##_CMD, ref, ops, KEYMAP_##id)
1426 static struct view views[] = {
1427         VIEW_(MAIN,   "main",   &main_ops,   ref_head),
1428         VIEW_(DIFF,   "diff",   &pager_ops,  ref_commit),
1429         VIEW_(LOG,    "log",    &pager_ops,  ref_head),
1430         VIEW_(TREE,   "tree",   &tree_ops,   ref_commit),
1431         VIEW_(BLOB,   "blob",   &blob_ops,   ref_blob),
1432         VIEW_(BLAME,  "blame",  &blame_ops,  ref_commit),
1433         VIEW_(HELP,   "help",   &help_ops,   ""),
1434         VIEW_(PAGER,  "pager",  &pager_ops,  "stdin"),
1435         VIEW_(STATUS, "status", &status_ops, ""),
1436         VIEW_(STAGE,  "stage",  &stage_ops,  ""),
1437 };
1439 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
1441 #define foreach_view(view, i) \
1442         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1444 #define view_is_displayed(view) \
1445         (view == display[0] || view == display[1])
1447 static int
1448 draw_text(struct view *view, const char *string, int max_len,
1449           bool use_tilde, bool selected)
1451         int len = 0;
1452         int trimmed = FALSE;
1454         if (max_len <= 0)
1455                 return 0;
1457         if (opt_utf8) {
1458                 len = utf8_length(string, max_len, &trimmed, use_tilde);
1459         } else {
1460                 len = strlen(string);
1461                 if (len > max_len) {
1462                         if (use_tilde) {
1463                                 max_len -= 1;
1464                         }
1465                         len = max_len;
1466                         trimmed = TRUE;
1467                 }
1468         }
1470         waddnstr(view->win, string, len);
1471         if (trimmed && use_tilde) {
1472                 if (!selected)
1473                         wattrset(view->win, get_line_attr(LINE_DELIMITER));
1474                 waddch(view->win, '~');
1475                 len++;
1476         }
1478         return len;
1481 static bool
1482 draw_view_line(struct view *view, unsigned int lineno)
1484         struct line *line;
1485         bool selected = (view->offset + lineno == view->lineno);
1486         bool draw_ok;
1488         assert(view_is_displayed(view));
1490         if (view->offset + lineno >= view->lines)
1491                 return FALSE;
1493         line = &view->line[view->offset + lineno];
1495         if (selected) {
1496                 line->selected = TRUE;
1497                 view->ops->select(view, line);
1498         } else if (line->selected) {
1499                 line->selected = FALSE;
1500                 wmove(view->win, lineno, 0);
1501                 wclrtoeol(view->win);
1502         }
1504         scrollok(view->win, FALSE);
1505         draw_ok = view->ops->draw(view, line, lineno, selected);
1506         scrollok(view->win, TRUE);
1508         return draw_ok;
1511 static void
1512 redraw_view_dirty(struct view *view)
1514         bool dirty = FALSE;
1515         int lineno;
1517         for (lineno = 0; lineno < view->height; lineno++) {
1518                 struct line *line = &view->line[view->offset + lineno];
1520                 if (!line->dirty)
1521                         continue;
1522                 line->dirty = 0;
1523                 dirty = TRUE;
1524                 if (!draw_view_line(view, lineno))
1525                         break;
1526         }
1528         if (!dirty)
1529                 return;
1530         redrawwin(view->win);
1531         if (input_mode)
1532                 wnoutrefresh(view->win);
1533         else
1534                 wrefresh(view->win);
1537 static void
1538 redraw_view_from(struct view *view, int lineno)
1540         assert(0 <= lineno && lineno < view->height);
1542         for (; lineno < view->height; lineno++) {
1543                 if (!draw_view_line(view, lineno))
1544                         break;
1545         }
1547         redrawwin(view->win);
1548         if (input_mode)
1549                 wnoutrefresh(view->win);
1550         else
1551                 wrefresh(view->win);
1554 static void
1555 redraw_view(struct view *view)
1557         wclear(view->win);
1558         redraw_view_from(view, 0);
1562 static void
1563 update_view_title(struct view *view)
1565         char buf[SIZEOF_STR];
1566         char state[SIZEOF_STR];
1567         size_t bufpos = 0, statelen = 0;
1569         assert(view_is_displayed(view));
1571         if (view != VIEW(REQ_VIEW_STATUS) && (view->lines || view->pipe)) {
1572                 unsigned int view_lines = view->offset + view->height;
1573                 unsigned int lines = view->lines
1574                                    ? MIN(view_lines, view->lines) * 100 / view->lines
1575                                    : 0;
1577                 string_format_from(state, &statelen, "- %s %d of %d (%d%%)",
1578                                    view->ops->type,
1579                                    view->lineno + 1,
1580                                    view->lines,
1581                                    lines);
1583                 if (view->pipe) {
1584                         time_t secs = time(NULL) - view->start_time;
1586                         /* Three git seconds are a long time ... */
1587                         if (secs > 2)
1588                                 string_format_from(state, &statelen, " %lds", secs);
1589                 }
1590         }
1592         string_format_from(buf, &bufpos, "[%s]", view->name);
1593         if (*view->ref && bufpos < view->width) {
1594                 size_t refsize = strlen(view->ref);
1595                 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
1597                 if (minsize < view->width)
1598                         refsize = view->width - minsize + 7;
1599                 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
1600         }
1602         if (statelen && bufpos < view->width) {
1603                 string_format_from(buf, &bufpos, " %s", state);
1604         }
1606         if (view == display[current_view])
1607                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
1608         else
1609                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
1611         mvwaddnstr(view->title, 0, 0, buf, bufpos);
1612         wclrtoeol(view->title);
1613         wmove(view->title, 0, view->width - 1);
1615         if (input_mode)
1616                 wnoutrefresh(view->title);
1617         else
1618                 wrefresh(view->title);
1621 static void
1622 resize_display(void)
1624         int offset, i;
1625         struct view *base = display[0];
1626         struct view *view = display[1] ? display[1] : display[0];
1628         /* Setup window dimensions */
1630         getmaxyx(stdscr, base->height, base->width);
1632         /* Make room for the status window. */
1633         base->height -= 1;
1635         if (view != base) {
1636                 /* Horizontal split. */
1637                 view->width   = base->width;
1638                 view->height  = SCALE_SPLIT_VIEW(base->height);
1639                 base->height -= view->height;
1641                 /* Make room for the title bar. */
1642                 view->height -= 1;
1643         }
1645         /* Make room for the title bar. */
1646         base->height -= 1;
1648         offset = 0;
1650         foreach_displayed_view (view, i) {
1651                 if (!view->win) {
1652                         view->win = newwin(view->height, 0, offset, 0);
1653                         if (!view->win)
1654                                 die("Failed to create %s view", view->name);
1656                         scrollok(view->win, TRUE);
1658                         view->title = newwin(1, 0, offset + view->height, 0);
1659                         if (!view->title)
1660                                 die("Failed to create title window");
1662                 } else {
1663                         wresize(view->win, view->height, view->width);
1664                         mvwin(view->win,   offset, 0);
1665                         mvwin(view->title, offset + view->height, 0);
1666                 }
1668                 offset += view->height + 1;
1669         }
1672 static void
1673 redraw_display(void)
1675         struct view *view;
1676         int i;
1678         foreach_displayed_view (view, i) {
1679                 redraw_view(view);
1680                 update_view_title(view);
1681         }
1684 static void
1685 update_display_cursor(struct view *view)
1687         /* Move the cursor to the right-most column of the cursor line.
1688          *
1689          * XXX: This could turn out to be a bit expensive, but it ensures that
1690          * the cursor does not jump around. */
1691         if (view->lines) {
1692                 wmove(view->win, view->lineno - view->offset, view->width - 1);
1693                 wrefresh(view->win);
1694         }
1697 /*
1698  * Navigation
1699  */
1701 /* Scrolling backend */
1702 static void
1703 do_scroll_view(struct view *view, int lines)
1705         bool redraw_current_line = FALSE;
1707         /* The rendering expects the new offset. */
1708         view->offset += lines;
1710         assert(0 <= view->offset && view->offset < view->lines);
1711         assert(lines);
1713         /* Move current line into the view. */
1714         if (view->lineno < view->offset) {
1715                 view->lineno = view->offset;
1716                 redraw_current_line = TRUE;
1717         } else if (view->lineno >= view->offset + view->height) {
1718                 view->lineno = view->offset + view->height - 1;
1719                 redraw_current_line = TRUE;
1720         }
1722         assert(view->offset <= view->lineno && view->lineno < view->lines);
1724         /* Redraw the whole screen if scrolling is pointless. */
1725         if (view->height < ABS(lines)) {
1726                 redraw_view(view);
1728         } else {
1729                 int line = lines > 0 ? view->height - lines : 0;
1730                 int end = line + ABS(lines);
1732                 wscrl(view->win, lines);
1734                 for (; line < end; line++) {
1735                         if (!draw_view_line(view, line))
1736                                 break;
1737                 }
1739                 if (redraw_current_line)
1740                         draw_view_line(view, view->lineno - view->offset);
1741         }
1743         redrawwin(view->win);
1744         wrefresh(view->win);
1745         report("");
1748 /* Scroll frontend */
1749 static void
1750 scroll_view(struct view *view, enum request request)
1752         int lines = 1;
1754         assert(view_is_displayed(view));
1756         switch (request) {
1757         case REQ_SCROLL_PAGE_DOWN:
1758                 lines = view->height;
1759         case REQ_SCROLL_LINE_DOWN:
1760                 if (view->offset + lines > view->lines)
1761                         lines = view->lines - view->offset;
1763                 if (lines == 0 || view->offset + view->height >= view->lines) {
1764                         report("Cannot scroll beyond the last line");
1765                         return;
1766                 }
1767                 break;
1769         case REQ_SCROLL_PAGE_UP:
1770                 lines = view->height;
1771         case REQ_SCROLL_LINE_UP:
1772                 if (lines > view->offset)
1773                         lines = view->offset;
1775                 if (lines == 0) {
1776                         report("Cannot scroll beyond the first line");
1777                         return;
1778                 }
1780                 lines = -lines;
1781                 break;
1783         default:
1784                 die("request %d not handled in switch", request);
1785         }
1787         do_scroll_view(view, lines);
1790 /* Cursor moving */
1791 static void
1792 move_view(struct view *view, enum request request)
1794         int scroll_steps = 0;
1795         int steps;
1797         switch (request) {
1798         case REQ_MOVE_FIRST_LINE:
1799                 steps = -view->lineno;
1800                 break;
1802         case REQ_MOVE_LAST_LINE:
1803                 steps = view->lines - view->lineno - 1;
1804                 break;
1806         case REQ_MOVE_PAGE_UP:
1807                 steps = view->height > view->lineno
1808                       ? -view->lineno : -view->height;
1809                 break;
1811         case REQ_MOVE_PAGE_DOWN:
1812                 steps = view->lineno + view->height >= view->lines
1813                       ? view->lines - view->lineno - 1 : view->height;
1814                 break;
1816         case REQ_MOVE_UP:
1817                 steps = -1;
1818                 break;
1820         case REQ_MOVE_DOWN:
1821                 steps = 1;
1822                 break;
1824         default:
1825                 die("request %d not handled in switch", request);
1826         }
1828         if (steps <= 0 && view->lineno == 0) {
1829                 report("Cannot move beyond the first line");
1830                 return;
1832         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
1833                 report("Cannot move beyond the last line");
1834                 return;
1835         }
1837         /* Move the current line */
1838         view->lineno += steps;
1839         assert(0 <= view->lineno && view->lineno < view->lines);
1841         /* Check whether the view needs to be scrolled */
1842         if (view->lineno < view->offset ||
1843             view->lineno >= view->offset + view->height) {
1844                 scroll_steps = steps;
1845                 if (steps < 0 && -steps > view->offset) {
1846                         scroll_steps = -view->offset;
1848                 } else if (steps > 0) {
1849                         if (view->lineno == view->lines - 1 &&
1850                             view->lines > view->height) {
1851                                 scroll_steps = view->lines - view->offset - 1;
1852                                 if (scroll_steps >= view->height)
1853                                         scroll_steps -= view->height - 1;
1854                         }
1855                 }
1856         }
1858         if (!view_is_displayed(view)) {
1859                 view->offset += scroll_steps;
1860                 assert(0 <= view->offset && view->offset < view->lines);
1861                 view->ops->select(view, &view->line[view->lineno]);
1862                 return;
1863         }
1865         /* Repaint the old "current" line if we be scrolling */
1866         if (ABS(steps) < view->height)
1867                 draw_view_line(view, view->lineno - steps - view->offset);
1869         if (scroll_steps) {
1870                 do_scroll_view(view, scroll_steps);
1871                 return;
1872         }
1874         /* Draw the current line */
1875         draw_view_line(view, view->lineno - view->offset);
1877         redrawwin(view->win);
1878         wrefresh(view->win);
1879         report("");
1883 /*
1884  * Searching
1885  */
1887 static void search_view(struct view *view, enum request request);
1889 static bool
1890 find_next_line(struct view *view, unsigned long lineno, struct line *line)
1892         assert(view_is_displayed(view));
1894         if (!view->ops->grep(view, line))
1895                 return FALSE;
1897         if (lineno - view->offset >= view->height) {
1898                 view->offset = lineno;
1899                 view->lineno = lineno;
1900                 redraw_view(view);
1902         } else {
1903                 unsigned long old_lineno = view->lineno - view->offset;
1905                 view->lineno = lineno;
1906                 draw_view_line(view, old_lineno);
1908                 draw_view_line(view, view->lineno - view->offset);
1909                 redrawwin(view->win);
1910                 wrefresh(view->win);
1911         }
1913         report("Line %ld matches '%s'", lineno + 1, view->grep);
1914         return TRUE;
1917 static void
1918 find_next(struct view *view, enum request request)
1920         unsigned long lineno = view->lineno;
1921         int direction;
1923         if (!*view->grep) {
1924                 if (!*opt_search)
1925                         report("No previous search");
1926                 else
1927                         search_view(view, request);
1928                 return;
1929         }
1931         switch (request) {
1932         case REQ_SEARCH:
1933         case REQ_FIND_NEXT:
1934                 direction = 1;
1935                 break;
1937         case REQ_SEARCH_BACK:
1938         case REQ_FIND_PREV:
1939                 direction = -1;
1940                 break;
1942         default:
1943                 return;
1944         }
1946         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
1947                 lineno += direction;
1949         /* Note, lineno is unsigned long so will wrap around in which case it
1950          * will become bigger than view->lines. */
1951         for (; lineno < view->lines; lineno += direction) {
1952                 struct line *line = &view->line[lineno];
1954                 if (find_next_line(view, lineno, line))
1955                         return;
1956         }
1958         report("No match found for '%s'", view->grep);
1961 static void
1962 search_view(struct view *view, enum request request)
1964         int regex_err;
1966         if (view->regex) {
1967                 regfree(view->regex);
1968                 *view->grep = 0;
1969         } else {
1970                 view->regex = calloc(1, sizeof(*view->regex));
1971                 if (!view->regex)
1972                         return;
1973         }
1975         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
1976         if (regex_err != 0) {
1977                 char buf[SIZEOF_STR] = "unknown error";
1979                 regerror(regex_err, view->regex, buf, sizeof(buf));
1980                 report("Search failed: %s", buf);
1981                 return;
1982         }
1984         string_copy(view->grep, opt_search);
1986         find_next(view, request);
1989 /*
1990  * Incremental updating
1991  */
1993 static void
1994 end_update(struct view *view)
1996         if (!view->pipe)
1997                 return;
1998         set_nonblocking_input(FALSE);
1999         if (view->pipe == stdin)
2000                 fclose(view->pipe);
2001         else
2002                 pclose(view->pipe);
2003         view->pipe = NULL;
2006 static bool
2007 begin_update(struct view *view)
2009         if (view->pipe)
2010                 end_update(view);
2012         if (opt_cmd[0]) {
2013                 string_copy(view->cmd, opt_cmd);
2014                 opt_cmd[0] = 0;
2015                 /* When running random commands, initially show the
2016                  * command in the title. However, it maybe later be
2017                  * overwritten if a commit line is selected. */
2018                 if (view == VIEW(REQ_VIEW_PAGER))
2019                         string_copy(view->ref, view->cmd);
2020                 else
2021                         view->ref[0] = 0;
2023         } else if (view == VIEW(REQ_VIEW_TREE)) {
2024                 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
2025                 char path[SIZEOF_STR];
2027                 if (strcmp(view->vid, view->id))
2028                         opt_path[0] = path[0] = 0;
2029                 else if (sq_quote(path, 0, opt_path) >= sizeof(path))
2030                         return FALSE;
2032                 if (!string_format(view->cmd, format, view->id, path))
2033                         return FALSE;
2035         } else {
2036                 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
2037                 const char *id = view->id;
2039                 if (!string_format(view->cmd, format, id, id, id, id, id))
2040                         return FALSE;
2042                 /* Put the current ref_* value to the view title ref
2043                  * member. This is needed by the blob view. Most other
2044                  * views sets it automatically after loading because the
2045                  * first line is a commit line. */
2046                 string_copy_rev(view->ref, view->id);
2047         }
2049         /* Special case for the pager view. */
2050         if (opt_pipe) {
2051                 view->pipe = opt_pipe;
2052                 opt_pipe = NULL;
2053         } else {
2054                 view->pipe = popen(view->cmd, "r");
2055         }
2057         if (!view->pipe)
2058                 return FALSE;
2060         set_nonblocking_input(TRUE);
2062         view->offset = 0;
2063         view->lines  = 0;
2064         view->lineno = 0;
2065         string_copy_rev(view->vid, view->id);
2067         if (view->line) {
2068                 int i;
2070                 for (i = 0; i < view->lines; i++)
2071                         if (view->line[i].data)
2072                                 free(view->line[i].data);
2074                 free(view->line);
2075                 view->line = NULL;
2076         }
2078         view->start_time = time(NULL);
2080         return TRUE;
2083 #define ITEM_CHUNK_SIZE 256
2084 static void *
2085 realloc_items(void *mem, size_t *size, size_t new_size, size_t item_size)
2087         size_t num_chunks = *size / ITEM_CHUNK_SIZE;
2088         size_t num_chunks_new = (new_size + ITEM_CHUNK_SIZE - 1) / ITEM_CHUNK_SIZE;
2090         if (mem == NULL || num_chunks != num_chunks_new) {
2091                 *size = num_chunks_new * ITEM_CHUNK_SIZE;
2092                 mem = realloc(mem, *size * item_size);
2093         }
2095         return mem;
2098 static struct line *
2099 realloc_lines(struct view *view, size_t line_size)
2101         size_t alloc = view->line_alloc;
2102         struct line *tmp = realloc_items(view->line, &alloc, line_size,
2103                                          sizeof(*view->line));
2105         if (!tmp)
2106                 return NULL;
2108         view->line = tmp;
2109         view->line_alloc = alloc;
2110         view->line_size = line_size;
2111         return view->line;
2114 static bool
2115 update_view(struct view *view)
2117         char in_buffer[BUFSIZ];
2118         char out_buffer[BUFSIZ * 2];
2119         char *line;
2120         /* The number of lines to read. If too low it will cause too much
2121          * redrawing (and possible flickering), if too high responsiveness
2122          * will suffer. */
2123         unsigned long lines = view->height;
2124         int redraw_from = -1;
2126         if (!view->pipe)
2127                 return TRUE;
2129         /* Only redraw if lines are visible. */
2130         if (view->offset + view->height >= view->lines)
2131                 redraw_from = view->lines - view->offset;
2133         /* FIXME: This is probably not perfect for backgrounded views. */
2134         if (!realloc_lines(view, view->lines + lines))
2135                 goto alloc_error;
2137         while ((line = fgets(in_buffer, sizeof(in_buffer), view->pipe))) {
2138                 size_t linelen = strlen(line);
2140                 if (linelen)
2141                         line[linelen - 1] = 0;
2143                 if (opt_iconv != ICONV_NONE) {
2144                         ICONV_CONST char *inbuf = line;
2145                         size_t inlen = linelen;
2147                         char *outbuf = out_buffer;
2148                         size_t outlen = sizeof(out_buffer);
2150                         size_t ret;
2152                         ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2153                         if (ret != (size_t) -1) {
2154                                 line = out_buffer;
2155                                 linelen = strlen(out_buffer);
2156                         }
2157                 }
2159                 if (!view->ops->read(view, line))
2160                         goto alloc_error;
2162                 if (lines-- == 1)
2163                         break;
2164         }
2166         {
2167                 int digits;
2169                 lines = view->lines;
2170                 for (digits = 0; lines; digits++)
2171                         lines /= 10;
2173                 /* Keep the displayed view in sync with line number scaling. */
2174                 if (digits != view->digits) {
2175                         view->digits = digits;
2176                         redraw_from = 0;
2177                 }
2178         }
2180         if (!view_is_displayed(view))
2181                 goto check_pipe;
2183         if (view == VIEW(REQ_VIEW_TREE)) {
2184                 /* Clear the view and redraw everything since the tree sorting
2185                  * might have rearranged things. */
2186                 redraw_view(view);
2188         } else if (redraw_from >= 0) {
2189                 /* If this is an incremental update, redraw the previous line
2190                  * since for commits some members could have changed when
2191                  * loading the main view. */
2192                 if (redraw_from > 0)
2193                         redraw_from--;
2195                 /* Since revision graph visualization requires knowledge
2196                  * about the parent commit, it causes a further one-off
2197                  * needed to be redrawn for incremental updates. */
2198                 if (redraw_from > 0 && opt_rev_graph)
2199                         redraw_from--;
2201                 /* Incrementally draw avoids flickering. */
2202                 redraw_view_from(view, redraw_from);
2203         }
2205         if (view == VIEW(REQ_VIEW_BLAME))
2206                 redraw_view_dirty(view);
2208         /* Update the title _after_ the redraw so that if the redraw picks up a
2209          * commit reference in view->ref it'll be available here. */
2210         update_view_title(view);
2212 check_pipe:
2213         if (ferror(view->pipe)) {
2214                 report("Failed to read: %s", strerror(errno));
2215                 goto end;
2217         } else if (feof(view->pipe)) {
2218                 report("");
2219                 goto end;
2220         }
2222         return TRUE;
2224 alloc_error:
2225         report("Allocation failure");
2227 end:
2228         if (view->ops->read(view, NULL))
2229                 end_update(view);
2230         return FALSE;
2233 static struct line *
2234 add_line_data(struct view *view, void *data, enum line_type type)
2236         struct line *line = &view->line[view->lines++];
2238         memset(line, 0, sizeof(*line));
2239         line->type = type;
2240         line->data = data;
2242         return line;
2245 static struct line *
2246 add_line_text(struct view *view, char *data, enum line_type type)
2248         if (data)
2249                 data = strdup(data);
2251         return data ? add_line_data(view, data, type) : NULL;
2255 /*
2256  * View opening
2257  */
2259 enum open_flags {
2260         OPEN_DEFAULT = 0,       /* Use default view switching. */
2261         OPEN_SPLIT = 1,         /* Split current view. */
2262         OPEN_BACKGROUNDED = 2,  /* Backgrounded. */
2263         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
2264 };
2266 static void
2267 open_view(struct view *prev, enum request request, enum open_flags flags)
2269         bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
2270         bool split = !!(flags & OPEN_SPLIT);
2271         bool reload = !!(flags & OPEN_RELOAD);
2272         struct view *view = VIEW(request);
2273         int nviews = displayed_views();
2274         struct view *base_view = display[0];
2276         if (view == prev && nviews == 1 && !reload) {
2277                 report("Already in %s view", view->name);
2278                 return;
2279         }
2281         if (view->ops->open) {
2282                 if (!view->ops->open(view)) {
2283                         report("Failed to load %s view", view->name);
2284                         return;
2285                 }
2287         } else if ((reload || strcmp(view->vid, view->id)) &&
2288                    !begin_update(view)) {
2289                 report("Failed to load %s view", view->name);
2290                 return;
2291         }
2293         if (split) {
2294                 display[1] = view;
2295                 if (!backgrounded)
2296                         current_view = 1;
2297         } else {
2298                 /* Maximize the current view. */
2299                 memset(display, 0, sizeof(display));
2300                 current_view = 0;
2301                 display[current_view] = view;
2302         }
2304         /* Resize the view when switching between split- and full-screen,
2305          * or when switching between two different full-screen views. */
2306         if (nviews != displayed_views() ||
2307             (nviews == 1 && base_view != display[0]))
2308                 resize_display();
2310         if (split && prev->lineno - prev->offset >= prev->height) {
2311                 /* Take the title line into account. */
2312                 int lines = prev->lineno - prev->offset - prev->height + 1;
2314                 /* Scroll the view that was split if the current line is
2315                  * outside the new limited view. */
2316                 do_scroll_view(prev, lines);
2317         }
2319         if (prev && view != prev) {
2320                 if (split && !backgrounded) {
2321                         /* "Blur" the previous view. */
2322                         update_view_title(prev);
2323                 }
2325                 view->parent = prev;
2326         }
2328         if (view->pipe && view->lines == 0) {
2329                 /* Clear the old view and let the incremental updating refill
2330                  * the screen. */
2331                 wclear(view->win);
2332                 report("");
2333         } else {
2334                 redraw_view(view);
2335                 report("");
2336         }
2338         /* If the view is backgrounded the above calls to report()
2339          * won't redraw the view title. */
2340         if (backgrounded)
2341                 update_view_title(view);
2344 static void
2345 open_external_viewer(const char *cmd)
2347         def_prog_mode();           /* save current tty modes */
2348         endwin();                  /* restore original tty modes */
2349         system(cmd);
2350         fprintf(stderr, "Press Enter to continue");
2351         getc(stdin);
2352         reset_prog_mode();
2353         redraw_display();
2356 static void
2357 open_mergetool(const char *file)
2359         char cmd[SIZEOF_STR];
2360         char file_sq[SIZEOF_STR];
2362         if (sq_quote(file_sq, 0, file) < sizeof(file_sq) &&
2363             string_format(cmd, "git mergetool %s", file_sq)) {
2364                 open_external_viewer(cmd);
2365         }
2368 static void
2369 open_editor(bool from_root, const char *file)
2371         char cmd[SIZEOF_STR];
2372         char file_sq[SIZEOF_STR];
2373         char *editor;
2374         char *prefix = from_root ? opt_cdup : "";
2376         editor = getenv("GIT_EDITOR");
2377         if (!editor && *opt_editor)
2378                 editor = opt_editor;
2379         if (!editor)
2380                 editor = getenv("VISUAL");
2381         if (!editor)
2382                 editor = getenv("EDITOR");
2383         if (!editor)
2384                 editor = "vi";
2386         if (sq_quote(file_sq, 0, file) < sizeof(file_sq) &&
2387             string_format(cmd, "%s %s%s", editor, prefix, file_sq)) {
2388                 open_external_viewer(cmd);
2389         }
2392 static void
2393 open_run_request(enum request request)
2395         struct run_request *req = get_run_request(request);
2396         char buf[SIZEOF_STR * 2];
2397         size_t bufpos;
2398         char *cmd;
2400         if (!req) {
2401                 report("Unknown run request");
2402                 return;
2403         }
2405         bufpos = 0;
2406         cmd = req->cmd;
2408         while (cmd) {
2409                 char *next = strstr(cmd, "%(");
2410                 int len = next - cmd;
2411                 char *value;
2413                 if (!next) {
2414                         len = strlen(cmd);
2415                         value = "";
2417                 } else if (!strncmp(next, "%(head)", 7)) {
2418                         value = ref_head;
2420                 } else if (!strncmp(next, "%(commit)", 9)) {
2421                         value = ref_commit;
2423                 } else if (!strncmp(next, "%(blob)", 7)) {
2424                         value = ref_blob;
2426                 } else {
2427                         report("Unknown replacement in run request: `%s`", req->cmd);
2428                         return;
2429                 }
2431                 if (!string_format_from(buf, &bufpos, "%.*s%s", len, cmd, value))
2432                         return;
2434                 if (next)
2435                         next = strchr(next, ')') + 1;
2436                 cmd = next;
2437         }
2439         open_external_viewer(buf);
2442 /*
2443  * User request switch noodle
2444  */
2446 static int
2447 view_driver(struct view *view, enum request request)
2449         int i;
2451         if (request == REQ_NONE) {
2452                 doupdate();
2453                 return TRUE;
2454         }
2456         if (request > REQ_NONE) {
2457                 open_run_request(request);
2458                 return TRUE;
2459         }
2461         if (view && view->lines) {
2462                 request = view->ops->request(view, request, &view->line[view->lineno]);
2463                 if (request == REQ_NONE)
2464                         return TRUE;
2465         }
2467         switch (request) {
2468         case REQ_MOVE_UP:
2469         case REQ_MOVE_DOWN:
2470         case REQ_MOVE_PAGE_UP:
2471         case REQ_MOVE_PAGE_DOWN:
2472         case REQ_MOVE_FIRST_LINE:
2473         case REQ_MOVE_LAST_LINE:
2474                 move_view(view, request);
2475                 break;
2477         case REQ_SCROLL_LINE_DOWN:
2478         case REQ_SCROLL_LINE_UP:
2479         case REQ_SCROLL_PAGE_DOWN:
2480         case REQ_SCROLL_PAGE_UP:
2481                 scroll_view(view, request);
2482                 break;
2484         case REQ_VIEW_BLAME:
2485                 if (!opt_file[0]) {
2486                         report("No file chosen, press %s to open tree view",
2487                                get_key(REQ_VIEW_TREE));
2488                         break;
2489                 }
2490                 open_view(view, request, OPEN_DEFAULT);
2491                 break;
2493         case REQ_VIEW_BLOB:
2494                 if (!ref_blob[0]) {
2495                         report("No file chosen, press %s to open tree view",
2496                                get_key(REQ_VIEW_TREE));
2497                         break;
2498                 }
2499                 open_view(view, request, OPEN_DEFAULT);
2500                 break;
2502         case REQ_VIEW_PAGER:
2503                 if (!opt_pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
2504                         report("No pager content, press %s to run command from prompt",
2505                                get_key(REQ_PROMPT));
2506                         break;
2507                 }
2508                 open_view(view, request, OPEN_DEFAULT);
2509                 break;
2511         case REQ_VIEW_STAGE:
2512                 if (!VIEW(REQ_VIEW_STAGE)->lines) {
2513                         report("No stage content, press %s to open the status view and choose file",
2514                                get_key(REQ_VIEW_STATUS));
2515                         break;
2516                 }
2517                 open_view(view, request, OPEN_DEFAULT);
2518                 break;
2520         case REQ_VIEW_STATUS:
2521                 if (opt_is_inside_work_tree == FALSE) {
2522                         report("The status view requires a working tree");
2523                         break;
2524                 }
2525                 open_view(view, request, OPEN_DEFAULT);
2526                 break;
2528         case REQ_VIEW_MAIN:
2529         case REQ_VIEW_DIFF:
2530         case REQ_VIEW_LOG:
2531         case REQ_VIEW_TREE:
2532         case REQ_VIEW_HELP:
2533                 open_view(view, request, OPEN_DEFAULT);
2534                 break;
2536         case REQ_NEXT:
2537         case REQ_PREVIOUS:
2538                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
2540                 if ((view == VIEW(REQ_VIEW_DIFF) &&
2541                      view->parent == VIEW(REQ_VIEW_MAIN)) ||
2542                    (view == VIEW(REQ_VIEW_DIFF) &&
2543                      view->parent == VIEW(REQ_VIEW_BLAME)) ||
2544                    (view == VIEW(REQ_VIEW_STAGE) &&
2545                      view->parent == VIEW(REQ_VIEW_STATUS)) ||
2546                    (view == VIEW(REQ_VIEW_BLOB) &&
2547                      view->parent == VIEW(REQ_VIEW_TREE))) {
2548                         int line;
2550                         view = view->parent;
2551                         line = view->lineno;
2552                         move_view(view, request);
2553                         if (view_is_displayed(view))
2554                                 update_view_title(view);
2555                         if (line != view->lineno)
2556                                 view->ops->request(view, REQ_ENTER,
2557                                                    &view->line[view->lineno]);
2559                 } else {
2560                         move_view(view, request);
2561                 }
2562                 break;
2564         case REQ_VIEW_NEXT:
2565         {
2566                 int nviews = displayed_views();
2567                 int next_view = (current_view + 1) % nviews;
2569                 if (next_view == current_view) {
2570                         report("Only one view is displayed");
2571                         break;
2572                 }
2574                 current_view = next_view;
2575                 /* Blur out the title of the previous view. */
2576                 update_view_title(view);
2577                 report("");
2578                 break;
2579         }
2580         case REQ_REFRESH:
2581                 report("Refreshing is not yet supported for the %s view", view->name);
2582                 break;
2584         case REQ_TOGGLE_LINENO:
2585                 opt_line_number = !opt_line_number;
2586                 redraw_display();
2587                 break;
2589         case REQ_TOGGLE_DATE:
2590                 opt_date = !opt_date;
2591                 redraw_display();
2592                 break;
2594         case REQ_TOGGLE_AUTHOR:
2595                 opt_author = !opt_author;
2596                 redraw_display();
2597                 break;
2599         case REQ_TOGGLE_REV_GRAPH:
2600                 opt_rev_graph = !opt_rev_graph;
2601                 redraw_display();
2602                 break;
2604         case REQ_TOGGLE_REFS:
2605                 opt_show_refs = !opt_show_refs;
2606                 redraw_display();
2607                 break;
2609         case REQ_PROMPT:
2610                 /* Always reload^Wrerun commands from the prompt. */
2611                 open_view(view, opt_request, OPEN_RELOAD);
2612                 break;
2614         case REQ_SEARCH:
2615         case REQ_SEARCH_BACK:
2616                 search_view(view, request);
2617                 break;
2619         case REQ_FIND_NEXT:
2620         case REQ_FIND_PREV:
2621                 find_next(view, request);
2622                 break;
2624         case REQ_STOP_LOADING:
2625                 for (i = 0; i < ARRAY_SIZE(views); i++) {
2626                         view = &views[i];
2627                         if (view->pipe)
2628                                 report("Stopped loading the %s view", view->name),
2629                         end_update(view);
2630                 }
2631                 break;
2633         case REQ_SHOW_VERSION:
2634                 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
2635                 return TRUE;
2637         case REQ_SCREEN_RESIZE:
2638                 resize_display();
2639                 /* Fall-through */
2640         case REQ_SCREEN_REDRAW:
2641                 redraw_display();
2642                 break;
2644         case REQ_EDIT:
2645                 report("Nothing to edit");
2646                 break;
2649         case REQ_ENTER:
2650                 report("Nothing to enter");
2651                 break;
2654         case REQ_VIEW_CLOSE:
2655                 /* XXX: Mark closed views by letting view->parent point to the
2656                  * view itself. Parents to closed view should never be
2657                  * followed. */
2658                 if (view->parent &&
2659                     view->parent->parent != view->parent) {
2660                         memset(display, 0, sizeof(display));
2661                         current_view = 0;
2662                         display[current_view] = view->parent;
2663                         view->parent = view;
2664                         resize_display();
2665                         redraw_display();
2666                         break;
2667                 }
2668                 /* Fall-through */
2669         case REQ_QUIT:
2670                 return FALSE;
2672         default:
2673                 /* An unknown key will show most commonly used commands. */
2674                 report("Unknown key, press 'h' for help");
2675                 return TRUE;
2676         }
2678         return TRUE;
2682 /*
2683  * Pager backend
2684  */
2686 static bool
2687 pager_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
2689         char *text = line->data;
2690         enum line_type type = line->type;
2691         int attr;
2693         wmove(view->win, lineno, 0);
2695         if (selected) {
2696                 type = LINE_CURSOR;
2697                 wchgat(view->win, -1, 0, type, NULL);
2698         }
2700         attr = get_line_attr(type);
2701         wattrset(view->win, attr);
2703         if (opt_line_number || opt_tab_size < TABSIZE) {
2704                 static char spaces[] = "                    ";
2705                 int col_offset = 0, col = 0;
2707                 if (opt_line_number) {
2708                         unsigned long real_lineno = view->offset + lineno + 1;
2710                         if (real_lineno == 1 ||
2711                             (real_lineno % opt_num_interval) == 0) {
2712                                 wprintw(view->win, "%.*d", view->digits, real_lineno);
2714                         } else {
2715                                 waddnstr(view->win, spaces,
2716                                          MIN(view->digits, STRING_SIZE(spaces)));
2717                         }
2718                         waddstr(view->win, ": ");
2719                         col_offset = view->digits + 2;
2720                 }
2722                 while (text && col_offset + col < view->width) {
2723                         int cols_max = view->width - col_offset - col;
2724                         char *pos = text;
2725                         int cols;
2727                         if (*text == '\t') {
2728                                 text++;
2729                                 assert(sizeof(spaces) > TABSIZE);
2730                                 pos = spaces;
2731                                 cols = opt_tab_size - (col % opt_tab_size);
2733                         } else {
2734                                 text = strchr(text, '\t');
2735                                 cols = line ? text - pos : strlen(pos);
2736                         }
2738                         waddnstr(view->win, pos, MIN(cols, cols_max));
2739                         col += cols;
2740                 }
2742         } else {
2743                 draw_text(view, text, view->width, TRUE, selected);
2744         }
2746         return TRUE;
2749 static bool
2750 add_describe_ref(char *buf, size_t *bufpos, char *commit_id, const char *sep)
2752         char refbuf[SIZEOF_STR];
2753         char *ref = NULL;
2754         FILE *pipe;
2756         if (!string_format(refbuf, "git describe %s 2>/dev/null", commit_id))
2757                 return TRUE;
2759         pipe = popen(refbuf, "r");
2760         if (!pipe)
2761                 return TRUE;
2763         if ((ref = fgets(refbuf, sizeof(refbuf), pipe)))
2764                 ref = chomp_string(ref);
2765         pclose(pipe);
2767         if (!ref || !*ref)
2768                 return TRUE;
2770         /* This is the only fatal call, since it can "corrupt" the buffer. */
2771         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
2772                 return FALSE;
2774         return TRUE;
2777 static void
2778 add_pager_refs(struct view *view, struct line *line)
2780         char buf[SIZEOF_STR];
2781         char *commit_id = (char *)line->data + STRING_SIZE("commit ");
2782         struct ref **refs;
2783         size_t bufpos = 0, refpos = 0;
2784         const char *sep = "Refs: ";
2785         bool is_tag = FALSE;
2787         assert(line->type == LINE_COMMIT);
2789         refs = get_refs(commit_id);
2790         if (!refs) {
2791                 if (view == VIEW(REQ_VIEW_DIFF))
2792                         goto try_add_describe_ref;
2793                 return;
2794         }
2796         do {
2797                 struct ref *ref = refs[refpos];
2798                 char *fmt = ref->tag    ? "%s[%s]" :
2799                             ref->remote ? "%s<%s>" : "%s%s";
2801                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
2802                         return;
2803                 sep = ", ";
2804                 if (ref->tag)
2805                         is_tag = TRUE;
2806         } while (refs[refpos++]->next);
2808         if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
2809 try_add_describe_ref:
2810                 /* Add <tag>-g<commit_id> "fake" reference. */
2811                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
2812                         return;
2813         }
2815         if (bufpos == 0)
2816                 return;
2818         if (!realloc_lines(view, view->line_size + 1))
2819                 return;
2821         add_line_text(view, buf, LINE_PP_REFS);
2824 static bool
2825 pager_read(struct view *view, char *data)
2827         struct line *line;
2829         if (!data)
2830                 return TRUE;
2832         line = add_line_text(view, data, get_line_type(data));
2833         if (!line)
2834                 return FALSE;
2836         if (line->type == LINE_COMMIT &&
2837             (view == VIEW(REQ_VIEW_DIFF) ||
2838              view == VIEW(REQ_VIEW_LOG)))
2839                 add_pager_refs(view, line);
2841         return TRUE;
2844 static enum request
2845 pager_request(struct view *view, enum request request, struct line *line)
2847         int split = 0;
2849         if (request != REQ_ENTER)
2850                 return request;
2852         if (line->type == LINE_COMMIT &&
2853            (view == VIEW(REQ_VIEW_LOG) ||
2854             view == VIEW(REQ_VIEW_PAGER))) {
2855                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
2856                 split = 1;
2857         }
2859         /* Always scroll the view even if it was split. That way
2860          * you can use Enter to scroll through the log view and
2861          * split open each commit diff. */
2862         scroll_view(view, REQ_SCROLL_LINE_DOWN);
2864         /* FIXME: A minor workaround. Scrolling the view will call report("")
2865          * but if we are scrolling a non-current view this won't properly
2866          * update the view title. */
2867         if (split)
2868                 update_view_title(view);
2870         return REQ_NONE;
2873 static bool
2874 pager_grep(struct view *view, struct line *line)
2876         regmatch_t pmatch;
2877         char *text = line->data;
2879         if (!*text)
2880                 return FALSE;
2882         if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
2883                 return FALSE;
2885         return TRUE;
2888 static void
2889 pager_select(struct view *view, struct line *line)
2891         if (line->type == LINE_COMMIT) {
2892                 char *text = (char *)line->data + STRING_SIZE("commit ");
2894                 if (view != VIEW(REQ_VIEW_PAGER))
2895                         string_copy_rev(view->ref, text);
2896                 string_copy_rev(ref_commit, text);
2897         }
2900 static struct view_ops pager_ops = {
2901         "line",
2902         NULL,
2903         pager_read,
2904         pager_draw,
2905         pager_request,
2906         pager_grep,
2907         pager_select,
2908 };
2911 /*
2912  * Help backend
2913  */
2915 static bool
2916 help_open(struct view *view)
2918         char buf[BUFSIZ];
2919         int lines = ARRAY_SIZE(req_info) + 2;
2920         int i;
2922         if (view->lines > 0)
2923                 return TRUE;
2925         for (i = 0; i < ARRAY_SIZE(req_info); i++)
2926                 if (!req_info[i].request)
2927                         lines++;
2929         lines += run_requests + 1;
2931         view->line = calloc(lines, sizeof(*view->line));
2932         if (!view->line)
2933                 return FALSE;
2935         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
2937         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
2938                 char *key;
2940                 if (req_info[i].request == REQ_NONE)
2941                         continue;
2943                 if (!req_info[i].request) {
2944                         add_line_text(view, "", LINE_DEFAULT);
2945                         add_line_text(view, req_info[i].help, LINE_DEFAULT);
2946                         continue;
2947                 }
2949                 key = get_key(req_info[i].request);
2950                 if (!*key)
2951                         key = "(no key defined)";
2953                 if (!string_format(buf, "    %-25s %s", key, req_info[i].help))
2954                         continue;
2956                 add_line_text(view, buf, LINE_DEFAULT);
2957         }
2959         if (run_requests) {
2960                 add_line_text(view, "", LINE_DEFAULT);
2961                 add_line_text(view, "External commands:", LINE_DEFAULT);
2962         }
2964         for (i = 0; i < run_requests; i++) {
2965                 struct run_request *req = get_run_request(REQ_NONE + i + 1);
2966                 char *key;
2968                 if (!req)
2969                         continue;
2971                 key = get_key_name(req->key);
2972                 if (!*key)
2973                         key = "(no key defined)";
2975                 if (!string_format(buf, "    %-10s %-14s `%s`",
2976                                    keymap_table[req->keymap].name,
2977                                    key, req->cmd))
2978                         continue;
2980                 add_line_text(view, buf, LINE_DEFAULT);
2981         }
2983         return TRUE;
2986 static struct view_ops help_ops = {
2987         "line",
2988         help_open,
2989         NULL,
2990         pager_draw,
2991         pager_request,
2992         pager_grep,
2993         pager_select,
2994 };
2997 /*
2998  * Tree backend
2999  */
3001 struct tree_stack_entry {
3002         struct tree_stack_entry *prev;  /* Entry below this in the stack */
3003         unsigned long lineno;           /* Line number to restore */
3004         char *name;                     /* Position of name in opt_path */
3005 };
3007 /* The top of the path stack. */
3008 static struct tree_stack_entry *tree_stack = NULL;
3009 unsigned long tree_lineno = 0;
3011 static void
3012 pop_tree_stack_entry(void)
3014         struct tree_stack_entry *entry = tree_stack;
3016         tree_lineno = entry->lineno;
3017         entry->name[0] = 0;
3018         tree_stack = entry->prev;
3019         free(entry);
3022 static void
3023 push_tree_stack_entry(char *name, unsigned long lineno)
3025         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
3026         size_t pathlen = strlen(opt_path);
3028         if (!entry)
3029                 return;
3031         entry->prev = tree_stack;
3032         entry->name = opt_path + pathlen;
3033         tree_stack = entry;
3035         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
3036                 pop_tree_stack_entry();
3037                 return;
3038         }
3040         /* Move the current line to the first tree entry. */
3041         tree_lineno = 1;
3042         entry->lineno = lineno;
3045 /* Parse output from git-ls-tree(1):
3046  *
3047  * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile
3048  * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README
3049  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
3050  * 100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38 web.conf
3051  */
3053 #define SIZEOF_TREE_ATTR \
3054         STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t")
3056 #define TREE_UP_FORMAT "040000 tree %s\t.."
3058 static int
3059 tree_compare_entry(enum line_type type1, char *name1,
3060                    enum line_type type2, char *name2)
3062         if (type1 != type2) {
3063                 if (type1 == LINE_TREE_DIR)
3064                         return -1;
3065                 return 1;
3066         }
3068         return strcmp(name1, name2);
3071 static char *
3072 tree_path(struct line *line)
3074         char *path = line->data;
3076         return path + SIZEOF_TREE_ATTR;
3079 static bool
3080 tree_read(struct view *view, char *text)
3082         size_t textlen = text ? strlen(text) : 0;
3083         char buf[SIZEOF_STR];
3084         unsigned long pos;
3085         enum line_type type;
3086         bool first_read = view->lines == 0;
3088         if (!text)
3089                 return TRUE;
3090         if (textlen <= SIZEOF_TREE_ATTR)
3091                 return FALSE;
3093         type = text[STRING_SIZE("100644 ")] == 't'
3094              ? LINE_TREE_DIR : LINE_TREE_FILE;
3096         if (first_read) {
3097                 /* Add path info line */
3098                 if (!string_format(buf, "Directory path /%s", opt_path) ||
3099                     !realloc_lines(view, view->line_size + 1) ||
3100                     !add_line_text(view, buf, LINE_DEFAULT))
3101                         return FALSE;
3103                 /* Insert "link" to parent directory. */
3104                 if (*opt_path) {
3105                         if (!string_format(buf, TREE_UP_FORMAT, view->ref) ||
3106                             !realloc_lines(view, view->line_size + 1) ||
3107                             !add_line_text(view, buf, LINE_TREE_DIR))
3108                                 return FALSE;
3109                 }
3110         }
3112         /* Strip the path part ... */
3113         if (*opt_path) {
3114                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
3115                 size_t striplen = strlen(opt_path);
3116                 char *path = text + SIZEOF_TREE_ATTR;
3118                 if (pathlen > striplen)
3119                         memmove(path, path + striplen,
3120                                 pathlen - striplen + 1);
3121         }
3123         /* Skip "Directory ..." and ".." line. */
3124         for (pos = 1 + !!*opt_path; pos < view->lines; pos++) {
3125                 struct line *line = &view->line[pos];
3126                 char *path1 = tree_path(line);
3127                 char *path2 = text + SIZEOF_TREE_ATTR;
3128                 int cmp = tree_compare_entry(line->type, path1, type, path2);
3130                 if (cmp <= 0)
3131                         continue;
3133                 text = strdup(text);
3134                 if (!text)
3135                         return FALSE;
3137                 if (view->lines > pos)
3138                         memmove(&view->line[pos + 1], &view->line[pos],
3139                                 (view->lines - pos) * sizeof(*line));
3141                 line = &view->line[pos];
3142                 line->data = text;
3143                 line->type = type;
3144                 view->lines++;
3145                 return TRUE;
3146         }
3148         if (!add_line_text(view, text, type))
3149                 return FALSE;
3151         if (tree_lineno > view->lineno) {
3152                 view->lineno = tree_lineno;
3153                 tree_lineno = 0;
3154         }
3156         return TRUE;
3159 static enum request
3160 tree_request(struct view *view, enum request request, struct line *line)
3162         enum open_flags flags;
3164         if (request == REQ_VIEW_BLAME) {
3165                 char *filename = tree_path(line);
3167                 if (line->type == LINE_TREE_DIR) {
3168                         report("Cannot show blame for directory %s", opt_path);
3169                         return REQ_NONE;
3170                 }
3172                 string_copy(opt_ref, view->vid);
3173                 string_format(opt_file, "%s%s", opt_path, filename);
3174                 return request;
3175         }
3176         if (request == REQ_TREE_PARENT) {
3177                 if (*opt_path) {
3178                         /* fake 'cd  ..' */
3179                         request = REQ_ENTER;
3180                         line = &view->line[1];
3181                 } else {
3182                         /* quit view if at top of tree */
3183                         return REQ_VIEW_CLOSE;
3184                 }
3185         }
3186         if (request != REQ_ENTER)
3187                 return request;
3189         /* Cleanup the stack if the tree view is at a different tree. */
3190         while (!*opt_path && tree_stack)
3191                 pop_tree_stack_entry();
3193         switch (line->type) {
3194         case LINE_TREE_DIR:
3195                 /* Depending on whether it is a subdir or parent (updir?) link
3196                  * mangle the path buffer. */
3197                 if (line == &view->line[1] && *opt_path) {
3198                         pop_tree_stack_entry();
3200                 } else {
3201                         char *basename = tree_path(line);
3203                         push_tree_stack_entry(basename, view->lineno);
3204                 }
3206                 /* Trees and subtrees share the same ID, so they are not not
3207                  * unique like blobs. */
3208                 flags = OPEN_RELOAD;
3209                 request = REQ_VIEW_TREE;
3210                 break;
3212         case LINE_TREE_FILE:
3213                 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
3214                 request = REQ_VIEW_BLOB;
3215                 break;
3217         default:
3218                 return TRUE;
3219         }
3221         open_view(view, request, flags);
3222         if (request == REQ_VIEW_TREE) {
3223                 view->lineno = tree_lineno;
3224         }
3226         return REQ_NONE;
3229 static void
3230 tree_select(struct view *view, struct line *line)
3232         char *text = (char *)line->data + STRING_SIZE("100644 blob ");
3234         if (line->type == LINE_TREE_FILE) {
3235                 string_copy_rev(ref_blob, text);
3237         } else if (line->type != LINE_TREE_DIR) {
3238                 return;
3239         }
3241         string_copy_rev(view->ref, text);
3244 static struct view_ops tree_ops = {
3245         "file",
3246         NULL,
3247         tree_read,
3248         pager_draw,
3249         tree_request,
3250         pager_grep,
3251         tree_select,
3252 };
3254 static bool
3255 blob_read(struct view *view, char *line)
3257         if (!line)
3258                 return TRUE;
3259         return add_line_text(view, line, LINE_DEFAULT) != NULL;
3262 static struct view_ops blob_ops = {
3263         "line",
3264         NULL,
3265         blob_read,
3266         pager_draw,
3267         pager_request,
3268         pager_grep,
3269         pager_select,
3270 };
3272 /*
3273  * Blame backend
3274  *
3275  * Loading the blame view is a two phase job:
3276  *
3277  *  1. File content is read either using opt_file from the
3278  *     filesystem or using git-cat-file.
3279  *  2. Then blame information is incrementally added by
3280  *     reading output from git-blame.
3281  */
3283 struct blame_commit {
3284         char id[SIZEOF_REV];            /* SHA1 ID. */
3285         char title[128];                /* First line of the commit message. */
3286         char author[75];                /* Author of the commit. */
3287         struct tm time;                 /* Date from the author ident. */
3288         char filename[128];             /* Name of file. */
3289 };
3291 struct blame {
3292         struct blame_commit *commit;
3293         unsigned int header:1;
3294         char text[1];
3295 };
3297 #define BLAME_CAT_FILE_CMD "git cat-file blob %s:%s"
3298 #define BLAME_INCREMENTAL_CMD "git blame --incremental %s %s"
3300 static bool
3301 blame_open(struct view *view)
3303         char path[SIZEOF_STR];
3304         char ref[SIZEOF_STR] = "";
3306         if (sq_quote(path, 0, opt_file) >= sizeof(path))
3307                 return FALSE;
3309         if (*opt_ref && sq_quote(ref, 0, opt_ref) >= sizeof(ref))
3310                 return FALSE;
3312         if (*opt_ref) {
3313                 if (!string_format(view->cmd, BLAME_CAT_FILE_CMD, ref, path))
3314                         return FALSE;
3315         } else {
3316                 view->pipe = fopen(opt_file, "r");
3317                 if (!view->pipe &&
3318                     !string_format(view->cmd, BLAME_CAT_FILE_CMD, "HEAD", path))
3319                         return FALSE;
3320         }
3322         if (!view->pipe)
3323                 view->pipe = popen(view->cmd, "r");
3324         if (!view->pipe)
3325                 return FALSE;
3327         if (!string_format(view->cmd, BLAME_INCREMENTAL_CMD, ref, path))
3328                 return FALSE;
3330         string_format(view->ref, "%s ...", opt_file);
3331         string_copy_rev(view->vid, opt_file);
3332         set_nonblocking_input(TRUE);
3334         if (view->line) {
3335                 int i;
3337                 for (i = 0; i < view->lines; i++)
3338                         free(view->line[i].data);
3339                 free(view->line);
3340         }
3342         view->lines = view->line_alloc = view->line_size = view->lineno = 0;
3343         view->offset = view->lines  = view->lineno = 0;
3344         view->line = NULL;
3345         view->start_time = time(NULL);
3347         return TRUE;
3350 static struct blame_commit *
3351 get_blame_commit(struct view *view, const char *id)
3353         size_t i;
3355         for (i = 0; i < view->lines; i++) {
3356                 struct blame *blame = view->line[i].data;
3358                 if (!blame->commit)
3359                         continue;
3361                 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
3362                         return blame->commit;
3363         }
3365         {
3366                 struct blame_commit *commit = calloc(1, sizeof(*commit));
3368                 if (commit)
3369                         string_ncopy(commit->id, id, SIZEOF_REV);
3370                 return commit;
3371         }
3374 static bool
3375 parse_number(char **posref, size_t *number, size_t min, size_t max)
3377         char *pos = *posref;
3379         *posref = NULL;
3380         pos = strchr(pos + 1, ' ');
3381         if (!pos || !isdigit(pos[1]))
3382                 return FALSE;
3383         *number = atoi(pos + 1);
3384         if (*number < min || *number > max)
3385                 return FALSE;
3387         *posref = pos;
3388         return TRUE;
3391 static struct blame_commit *
3392 parse_blame_commit(struct view *view, char *text, int *blamed)
3394         struct blame_commit *commit;
3395         struct blame *blame;
3396         char *pos = text + SIZEOF_REV - 1;
3397         size_t lineno;
3398         size_t group;
3400         if (strlen(text) <= SIZEOF_REV || *pos != ' ')
3401                 return NULL;
3403         if (!parse_number(&pos, &lineno, 1, view->lines) ||
3404             !parse_number(&pos, &group, 1, view->lines - lineno + 1))
3405                 return NULL;
3407         commit = get_blame_commit(view, text);
3408         if (!commit)
3409                 return NULL;
3411         *blamed += group;
3412         while (group--) {
3413                 struct line *line = &view->line[lineno + group - 1];
3415                 blame = line->data;
3416                 blame->commit = commit;
3417                 line->dirty = 1;
3418         }
3419         blame->header = 1;
3421         return commit;
3424 static bool
3425 blame_read_file(struct view *view, char *line)
3427         if (!line) {
3428                 FILE *pipe = NULL;
3430                 if (view->lines > 0)
3431                         pipe = popen(view->cmd, "r");
3432                 view->cmd[0] = 0;
3433                 if (!pipe) {
3434                         report("Failed to load blame data");
3435                         return TRUE;
3436                 }
3438                 fclose(view->pipe);
3439                 view->pipe = pipe;
3440                 return FALSE;
3442         } else {
3443                 size_t linelen = strlen(line);
3444                 struct blame *blame = malloc(sizeof(*blame) + linelen);
3446                 if (!line)
3447                         return FALSE;
3449                 blame->commit = NULL;
3450                 strncpy(blame->text, line, linelen);
3451                 blame->text[linelen] = 0;
3452                 return add_line_data(view, blame, LINE_BLAME_COMMIT) != NULL;
3453         }
3456 static bool
3457 match_blame_header(const char *name, char **line)
3459         size_t namelen = strlen(name);
3460         bool matched = !strncmp(name, *line, namelen);
3462         if (matched)
3463                 *line += namelen;
3465         return matched;
3468 static bool
3469 blame_read(struct view *view, char *line)
3471         static struct blame_commit *commit = NULL;
3472         static int blamed = 0;
3473         static time_t author_time;
3475         if (*view->cmd)
3476                 return blame_read_file(view, line);
3478         if (!line) {
3479                 /* Reset all! */
3480                 commit = NULL;
3481                 blamed = 0;
3482                 string_format(view->ref, "%s", view->vid);
3483                 if (view_is_displayed(view)) {
3484                         update_view_title(view);
3485                         redraw_view_from(view, 0);
3486                 }
3487                 return TRUE;
3488         }
3490         if (!commit) {
3491                 commit = parse_blame_commit(view, line, &blamed);
3492                 string_format(view->ref, "%s %2d%%", view->vid,
3493                               blamed * 100 / view->lines);
3495         } else if (match_blame_header("author ", &line)) {
3496                 string_ncopy(commit->author, line, strlen(line));
3498         } else if (match_blame_header("author-time ", &line)) {
3499                 author_time = (time_t) atol(line);
3501         } else if (match_blame_header("author-tz ", &line)) {
3502                 long tz;
3504                 tz  = ('0' - line[1]) * 60 * 60 * 10;
3505                 tz += ('0' - line[2]) * 60 * 60;
3506                 tz += ('0' - line[3]) * 60;
3507                 tz += ('0' - line[4]) * 60;
3509                 if (line[0] == '-')
3510                         tz = -tz;
3512                 author_time -= tz;
3513                 gmtime_r(&author_time, &commit->time);
3515         } else if (match_blame_header("summary ", &line)) {
3516                 string_ncopy(commit->title, line, strlen(line));
3518         } else if (match_blame_header("filename ", &line)) {
3519                 string_ncopy(commit->filename, line, strlen(line));
3520                 commit = NULL;
3521         }
3523         return TRUE;
3526 static bool
3527 blame_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
3529         struct blame *blame = line->data;
3530         int col = 0;
3532         wmove(view->win, lineno, 0);
3534         if (selected) {
3535                 wattrset(view->win, get_line_attr(LINE_CURSOR));
3536                 wchgat(view->win, -1, 0, LINE_CURSOR, NULL);
3537         } else {
3538                 wattrset(view->win, A_NORMAL);
3539         }
3541         if (opt_date) {
3542                 int n;
3544                 if (!selected)
3545                         wattrset(view->win, get_line_attr(LINE_MAIN_DATE));
3546                 if (blame->commit) {
3547                         char buf[DATE_COLS + 1];
3548                         int timelen;
3550                         timelen = strftime(buf, sizeof(buf), DATE_FORMAT, &blame->commit->time);
3551                         n = draw_text(view, buf, view->width - col, FALSE, selected);
3552                         draw_text(view, " ", view->width - col - n, FALSE, selected);
3553                 }
3555                 col += DATE_COLS;
3556                 wmove(view->win, lineno, col);
3557                 if (col >= view->width)
3558                         return TRUE;
3559         }
3561         if (opt_author) {
3562                 int max = MIN(AUTHOR_COLS - 1, view->width - col);
3564                 if (!selected)
3565                         wattrset(view->win, get_line_attr(LINE_MAIN_AUTHOR));
3566                 if (blame->commit)
3567                         draw_text(view, blame->commit->author, max, TRUE, selected);
3568                 col += AUTHOR_COLS;
3569                 if (col >= view->width)
3570                         return TRUE;
3571                 wmove(view->win, lineno, col);
3572         }
3574         {
3575                 int max = MIN(ID_COLS - 1, view->width - col);
3577                 if (!selected)
3578                         wattrset(view->win, get_line_attr(LINE_BLAME_ID));
3579                 if (blame->commit)
3580                         draw_text(view, blame->commit->id, max, FALSE, -1);
3581                 col += ID_COLS;
3582                 if (col >= view->width)
3583                         return TRUE;
3584                 wmove(view->win, lineno, col);
3585         }
3587         {
3588                 unsigned long real_lineno = view->offset + lineno + 1;
3589                 char number[10] = "          ";
3590                 int max = MIN(view->digits, STRING_SIZE(number));
3591                 bool showtrimmed = FALSE;
3593                 if (real_lineno == 1 ||
3594                     (real_lineno % opt_num_interval) == 0) {
3595                         char fmt[] = "%1ld";
3597                         if (view->digits <= 9)
3598                                 fmt[1] = '0' + view->digits;
3600                         if (!string_format(number, fmt, real_lineno))
3601                                 number[0] = 0;
3602                         showtrimmed = TRUE;
3603                 }
3605                 if (max > view->width - col)
3606                         max = view->width - col;
3607                 if (!selected)
3608                         wattrset(view->win, get_line_attr(LINE_BLAME_LINENO));
3609                 col += draw_text(view, number, max, showtrimmed, selected);
3610                 if (col >= view->width)
3611                         return TRUE;
3612         }
3614         if (!selected)
3615                 wattrset(view->win, A_NORMAL);
3617         if (col >= view->width)
3618                 return TRUE;
3619         waddch(view->win, ACS_VLINE);
3620         col++;
3621         if (col >= view->width)
3622                 return TRUE;
3623         waddch(view->win, ' ');
3624         col++;
3625         col += draw_text(view, blame->text, view->width - col, TRUE, selected);
3627         return TRUE;
3630 static enum request
3631 blame_request(struct view *view, enum request request, struct line *line)
3633         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
3634         struct blame *blame = line->data;
3636         switch (request) {
3637         case REQ_ENTER:
3638                 if (!blame->commit) {
3639                         report("No commit loaded yet");
3640                         break;
3641                 }
3643                 if (!strcmp(blame->commit->id, NULL_ID)) {
3644                         char path[SIZEOF_STR];
3646                         if (sq_quote(path, 0, view->vid) >= sizeof(path))
3647                                 break;
3648                         string_format(opt_cmd, "git diff-index --root --patch-with-stat -C -M --cached HEAD -- %s 2>/dev/null", path);
3649                 }
3651                 open_view(view, REQ_VIEW_DIFF, flags);
3652                 break;
3654         default:
3655                 return request;
3656         }
3658         return REQ_NONE;
3661 static bool
3662 blame_grep(struct view *view, struct line *line)
3664         struct blame *blame = line->data;
3665         struct blame_commit *commit = blame->commit;
3666         regmatch_t pmatch;
3668 #define MATCH(text) \
3669         (*text && regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
3671         if (commit) {
3672                 char buf[DATE_COLS + 1];
3674                 if (MATCH(commit->title) ||
3675                     MATCH(commit->author) ||
3676                     MATCH(commit->id))
3677                         return TRUE;
3679                 if (strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time) &&
3680                     MATCH(buf))
3681                         return TRUE;
3682         }
3684         return MATCH(blame->text);
3686 #undef MATCH
3689 static void
3690 blame_select(struct view *view, struct line *line)
3692         struct blame *blame = line->data;
3693         struct blame_commit *commit = blame->commit;
3695         if (!commit)
3696                 return;
3698         if (!strcmp(commit->id, NULL_ID))
3699                 string_ncopy(ref_commit, "HEAD", 4);
3700         else
3701                 string_copy_rev(ref_commit, commit->id);
3704 static struct view_ops blame_ops = {
3705         "line",
3706         blame_open,
3707         blame_read,
3708         blame_draw,
3709         blame_request,
3710         blame_grep,
3711         blame_select,
3712 };
3714 /*
3715  * Status backend
3716  */
3718 struct status {
3719         char status;
3720         struct {
3721                 mode_t mode;
3722                 char rev[SIZEOF_REV];
3723                 char name[SIZEOF_STR];
3724         } old;
3725         struct {
3726                 mode_t mode;
3727                 char rev[SIZEOF_REV];
3728                 char name[SIZEOF_STR];
3729         } new;
3730 };
3732 static char status_onbranch[SIZEOF_STR];
3733 static struct status stage_status;
3734 static enum line_type stage_line_type;
3736 /* Get fields from the diff line:
3737  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
3738  */
3739 static inline bool
3740 status_get_diff(struct status *file, char *buf, size_t bufsize)
3742         char *old_mode = buf +  1;
3743         char *new_mode = buf +  8;
3744         char *old_rev  = buf + 15;
3745         char *new_rev  = buf + 56;
3746         char *status   = buf + 97;
3748         if (bufsize < 99 ||
3749             old_mode[-1] != ':' ||
3750             new_mode[-1] != ' ' ||
3751             old_rev[-1]  != ' ' ||
3752             new_rev[-1]  != ' ' ||
3753             status[-1]   != ' ')
3754                 return FALSE;
3756         file->status = *status;
3758         string_copy_rev(file->old.rev, old_rev);
3759         string_copy_rev(file->new.rev, new_rev);
3761         file->old.mode = strtoul(old_mode, NULL, 8);
3762         file->new.mode = strtoul(new_mode, NULL, 8);
3764         file->old.name[0] = file->new.name[0] = 0;
3766         return TRUE;
3769 static bool
3770 status_run(struct view *view, const char cmd[], char status, enum line_type type)
3772         struct status *file = NULL;
3773         struct status *unmerged = NULL;
3774         char buf[SIZEOF_STR * 4];
3775         size_t bufsize = 0;
3776         FILE *pipe;
3778         pipe = popen(cmd, "r");
3779         if (!pipe)
3780                 return FALSE;
3782         add_line_data(view, NULL, type);
3784         while (!feof(pipe) && !ferror(pipe)) {
3785                 char *sep;
3786                 size_t readsize;
3788                 readsize = fread(buf + bufsize, 1, sizeof(buf) - bufsize, pipe);
3789                 if (!readsize)
3790                         break;
3791                 bufsize += readsize;
3793                 /* Process while we have NUL chars. */
3794                 while ((sep = memchr(buf, 0, bufsize))) {
3795                         size_t sepsize = sep - buf + 1;
3797                         if (!file) {
3798                                 if (!realloc_lines(view, view->line_size + 1))
3799                                         goto error_out;
3801                                 file = calloc(1, sizeof(*file));
3802                                 if (!file)
3803                                         goto error_out;
3805                                 add_line_data(view, file, type);
3806                         }
3808                         /* Parse diff info part. */
3809                         if (status) {
3810                                 file->status = status;
3811                                 if (status == 'A')
3812                                         string_copy(file->old.rev, NULL_ID);
3814                         } else if (!file->status) {
3815                                 if (!status_get_diff(file, buf, sepsize))
3816                                         goto error_out;
3818                                 bufsize -= sepsize;
3819                                 memmove(buf, sep + 1, bufsize);
3821                                 sep = memchr(buf, 0, bufsize);
3822                                 if (!sep)
3823                                         break;
3824                                 sepsize = sep - buf + 1;
3826                                 /* Collapse all 'M'odified entries that
3827                                  * follow a associated 'U'nmerged entry.
3828                                  */
3829                                 if (file->status == 'U') {
3830                                         unmerged = file;
3832                                 } else if (unmerged) {
3833                                         int collapse = !strcmp(buf, unmerged->new.name);
3835                                         unmerged = NULL;
3836                                         if (collapse) {
3837                                                 free(file);
3838                                                 view->lines--;
3839                                                 continue;
3840                                         }
3841                                 }
3842                         }
3844                         /* Grab the old name for rename/copy. */
3845                         if (!*file->old.name &&
3846                             (file->status == 'R' || file->status == 'C')) {
3847                                 sepsize = sep - buf + 1;
3848                                 string_ncopy(file->old.name, buf, sepsize);
3849                                 bufsize -= sepsize;
3850                                 memmove(buf, sep + 1, bufsize);
3852                                 sep = memchr(buf, 0, bufsize);
3853                                 if (!sep)
3854                                         break;
3855                                 sepsize = sep - buf + 1;
3856                         }
3858                         /* git-ls-files just delivers a NUL separated
3859                          * list of file names similar to the second half
3860                          * of the git-diff-* output. */
3861                         string_ncopy(file->new.name, buf, sepsize);
3862                         if (!*file->old.name)
3863                                 string_copy(file->old.name, file->new.name);
3864                         bufsize -= sepsize;
3865                         memmove(buf, sep + 1, bufsize);
3866                         file = NULL;
3867                 }
3868         }
3870         if (ferror(pipe)) {
3871 error_out:
3872                 pclose(pipe);
3873                 return FALSE;
3874         }
3876         if (!view->line[view->lines - 1].data)
3877                 add_line_data(view, NULL, LINE_STAT_NONE);
3879         pclose(pipe);
3880         return TRUE;
3883 /* Don't show unmerged entries in the staged section. */
3884 #define STATUS_DIFF_INDEX_CMD "git diff-index -z --diff-filter=ACDMRTXB --cached -M HEAD"
3885 #define STATUS_DIFF_FILES_CMD "git diff-files -z"
3886 #define STATUS_LIST_OTHER_CMD \
3887         "git ls-files -z --others --exclude-per-directory=.gitignore"
3888 #define STATUS_LIST_NO_HEAD_CMD \
3889         "git ls-files -z --cached --exclude-per-directory=.gitignore"
3891 #define STATUS_DIFF_INDEX_SHOW_CMD \
3892         "git diff-index --root --patch-with-stat -C -M --cached HEAD -- %s %s 2>/dev/null"
3894 #define STATUS_DIFF_FILES_SHOW_CMD \
3895         "git diff-files --root --patch-with-stat -C -M -- %s %s 2>/dev/null"
3897 #define STATUS_DIFF_NO_HEAD_SHOW_CMD \
3898         "git diff --no-color --patch-with-stat /dev/null %s 2>/dev/null"
3900 /* First parse staged info using git-diff-index(1), then parse unstaged
3901  * info using git-diff-files(1), and finally untracked files using
3902  * git-ls-files(1). */
3903 static bool
3904 status_open(struct view *view)
3906         struct stat statbuf;
3907         char exclude[SIZEOF_STR];
3908         char indexcmd[SIZEOF_STR] = STATUS_DIFF_INDEX_CMD;
3909         char othercmd[SIZEOF_STR] = STATUS_LIST_OTHER_CMD;
3910         unsigned long prev_lineno = view->lineno;
3911         char indexstatus = 0;
3912         size_t i;
3914         for (i = 0; i < view->lines; i++)
3915                 free(view->line[i].data);
3916         free(view->line);
3917         view->lines = view->line_alloc = view->line_size = view->lineno = 0;
3918         view->line = NULL;
3920         if (!realloc_lines(view, view->line_size + 7))
3921                 return FALSE;
3923         add_line_data(view, NULL, LINE_STAT_HEAD);
3924         if (opt_no_head)
3925                 string_copy(status_onbranch, "Initial commit");
3926         else if (!*opt_head)
3927                 string_copy(status_onbranch, "Not currently on any branch");
3928         else if (!string_format(status_onbranch, "On branch %s", opt_head))
3929                 return FALSE;
3931         if (opt_no_head) {
3932                 string_copy(indexcmd, STATUS_LIST_NO_HEAD_CMD);
3933                 indexstatus = 'A';
3934         }
3936         if (!string_format(exclude, "%s/info/exclude", opt_git_dir))
3937                 return FALSE;
3939         if (stat(exclude, &statbuf) >= 0) {
3940                 size_t cmdsize = strlen(othercmd);
3942                 if (!string_format_from(othercmd, &cmdsize, " %s", "--exclude-from=") ||
3943                     sq_quote(othercmd, cmdsize, exclude) >= sizeof(othercmd))
3944                         return FALSE;
3946                 cmdsize = strlen(indexcmd);
3947                 if (opt_no_head &&
3948                     (!string_format_from(indexcmd, &cmdsize, " %s", "--exclude-from=") ||
3949                      sq_quote(indexcmd, cmdsize, exclude) >= sizeof(indexcmd)))
3950                         return FALSE;
3951         }
3953         system("git update-index -q --refresh");
3955         if (!status_run(view, indexcmd, indexstatus, LINE_STAT_STAGED) ||
3956             !status_run(view, STATUS_DIFF_FILES_CMD, 0, LINE_STAT_UNSTAGED) ||
3957             !status_run(view, othercmd, '?', LINE_STAT_UNTRACKED))
3958                 return FALSE;
3960         /* If all went well restore the previous line number to stay in
3961          * the context or select a line with something that can be
3962          * updated. */
3963         if (prev_lineno >= view->lines)
3964                 prev_lineno = view->lines - 1;
3965         while (prev_lineno < view->lines && !view->line[prev_lineno].data)
3966                 prev_lineno++;
3968         /* If the above fails, always skip the "On branch" line. */
3969         if (prev_lineno < view->lines)
3970                 view->lineno = prev_lineno;
3971         else
3972                 view->lineno = 1;
3974         return TRUE;
3977 static bool
3978 status_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
3980         struct status *status = line->data;
3982         wmove(view->win, lineno, 0);
3984         if (selected) {
3985                 wattrset(view->win, get_line_attr(LINE_CURSOR));
3986                 wchgat(view->win, -1, 0, LINE_CURSOR, NULL);
3988         } else if (line->type == LINE_STAT_HEAD) {
3989                 wattrset(view->win, get_line_attr(LINE_STAT_HEAD));
3990                 wchgat(view->win, -1, 0, LINE_STAT_HEAD, NULL);
3992         } else if (!status && line->type != LINE_STAT_NONE) {
3993                 wattrset(view->win, get_line_attr(LINE_STAT_SECTION));
3994                 wchgat(view->win, -1, 0, LINE_STAT_SECTION, NULL);
3996         } else {
3997                 wattrset(view->win, get_line_attr(line->type));
3998         }
4000         if (!status) {
4001                 char *text;
4003                 switch (line->type) {
4004                 case LINE_STAT_STAGED:
4005                         text = "Changes to be committed:";
4006                         break;
4008                 case LINE_STAT_UNSTAGED:
4009                         text = "Changed but not updated:";
4010                         break;
4012                 case LINE_STAT_UNTRACKED:
4013                         text = "Untracked files:";
4014                         break;
4016                 case LINE_STAT_NONE:
4017                         text = "    (no files)";
4018                         break;
4020                 case LINE_STAT_HEAD:
4021                         text = status_onbranch;
4022                         break;
4024                 default:
4025                         return FALSE;
4026                 }
4028                 draw_text(view, text, view->width, TRUE, selected);
4029                 return TRUE;
4030         }
4032         waddch(view->win, status->status);
4033         if (!selected)
4034                 wattrset(view->win, A_NORMAL);
4035         wmove(view->win, lineno, 4);
4036         if (view->width < 5)
4037                 return TRUE;
4039         draw_text(view, status->new.name, view->width - 5, TRUE, selected);
4040         return TRUE;
4043 static enum request
4044 status_enter(struct view *view, struct line *line)
4046         struct status *status = line->data;
4047         char oldpath[SIZEOF_STR] = "";
4048         char newpath[SIZEOF_STR] = "";
4049         char *info;
4050         size_t cmdsize = 0;
4052         if (line->type == LINE_STAT_NONE ||
4053             (!status && line[1].type == LINE_STAT_NONE)) {
4054                 report("No file to diff");
4055                 return REQ_NONE;
4056         }
4058         if (status) {
4059                 if (sq_quote(oldpath, 0, status->old.name) >= sizeof(oldpath))
4060                         return REQ_QUIT;
4061                 /* Diffs for unmerged entries are empty when pasing the
4062                  * new path, so leave it empty. */
4063                 if (status->status != 'U' &&
4064                     sq_quote(newpath, 0, status->new.name) >= sizeof(newpath))
4065                         return REQ_QUIT;
4066         }
4068         if (opt_cdup[0] &&
4069             line->type != LINE_STAT_UNTRACKED &&
4070             !string_format_from(opt_cmd, &cmdsize, "cd %s;", opt_cdup))
4071                 return REQ_QUIT;
4073         switch (line->type) {
4074         case LINE_STAT_STAGED:
4075                 if (opt_no_head) {
4076                         if (!string_format_from(opt_cmd, &cmdsize,
4077                                                 STATUS_DIFF_NO_HEAD_SHOW_CMD,
4078                                                 newpath))
4079                                 return REQ_QUIT;
4080                 } else {
4081                         if (!string_format_from(opt_cmd, &cmdsize,
4082                                                 STATUS_DIFF_INDEX_SHOW_CMD,
4083                                                 oldpath, newpath))
4084                                 return REQ_QUIT;
4085                 }
4087                 if (status)
4088                         info = "Staged changes to %s";
4089                 else
4090                         info = "Staged changes";
4091                 break;
4093         case LINE_STAT_UNSTAGED:
4094                 if (!string_format_from(opt_cmd, &cmdsize,
4095                                         STATUS_DIFF_FILES_SHOW_CMD, oldpath, newpath))
4096                         return REQ_QUIT;
4097                 if (status)
4098                         info = "Unstaged changes to %s";
4099                 else
4100                         info = "Unstaged changes";
4101                 break;
4103         case LINE_STAT_UNTRACKED:
4104                 if (opt_pipe)
4105                         return REQ_QUIT;
4107                 if (!status) {
4108                         report("No file to show");
4109                         return REQ_NONE;
4110                 }
4112                 opt_pipe = fopen(status->new.name, "r");
4113                 info = "Untracked file %s";
4114                 break;
4116         case LINE_STAT_HEAD:
4117                 return REQ_NONE;
4119         default:
4120                 die("line type %d not handled in switch", line->type);
4121         }
4123         open_view(view, REQ_VIEW_STAGE, OPEN_RELOAD | OPEN_SPLIT);
4124         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
4125                 if (status) {
4126                         stage_status = *status;
4127                 } else {
4128                         memset(&stage_status, 0, sizeof(stage_status));
4129                 }
4131                 stage_line_type = line->type;
4132                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
4133         }
4135         return REQ_NONE;
4139 static bool
4140 status_update_file(struct view *view, struct status *status, enum line_type type)
4142         char cmd[SIZEOF_STR];
4143         char buf[SIZEOF_STR];
4144         size_t cmdsize = 0;
4145         size_t bufsize = 0;
4146         size_t written = 0;
4147         FILE *pipe;
4149         if (opt_cdup[0] &&
4150             type != LINE_STAT_UNTRACKED &&
4151             !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
4152                 return FALSE;
4154         switch (type) {
4155         case LINE_STAT_STAGED:
4156                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
4157                                         status->old.mode,
4158                                         status->old.rev,
4159                                         status->old.name, 0))
4160                         return FALSE;
4162                 string_add(cmd, cmdsize, "git update-index -z --index-info");
4163                 break;
4165         case LINE_STAT_UNSTAGED:
4166         case LINE_STAT_UNTRACKED:
4167                 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
4168                         return FALSE;
4170                 string_add(cmd, cmdsize, "git update-index -z --add --remove --stdin");
4171                 break;
4173         case LINE_STAT_HEAD:
4174                 return TRUE;
4176         default:
4177                 die("line type %d not handled in switch", type);
4178         }
4180         pipe = popen(cmd, "w");
4181         if (!pipe)
4182                 return FALSE;
4184         while (!ferror(pipe) && written < bufsize) {
4185                 written += fwrite(buf + written, 1, bufsize - written, pipe);
4186         }
4188         pclose(pipe);
4190         if (written != bufsize)
4191                 return FALSE;
4193         return TRUE;
4196 static bool
4197 status_update(struct view *view)
4199         struct line *line = &view->line[view->lineno];
4201         assert(view->lines);
4203         if (!line->data) {
4204                 while (++line < view->line + view->lines && line->data) {
4205                         if (!status_update_file(view, line->data, line->type))
4206                                 report("Failed to update file status");
4207                 }
4209                 if (!line[-1].data) {
4210                         report("Nothing to update");
4211                         return FALSE;
4212                 }
4214         } else if (!status_update_file(view, line->data, line->type)) {
4215                 report("Failed to update file status");
4216         }
4218         return TRUE;
4221 static enum request
4222 status_request(struct view *view, enum request request, struct line *line)
4224         struct status *status = line->data;
4226         switch (request) {
4227         case REQ_STATUS_UPDATE:
4228                 if (!status_update(view))
4229                         return REQ_NONE;
4230                 break;
4232         case REQ_STATUS_MERGE:
4233                 if (!status || status->status != 'U') {
4234                         report("Merging only possible for files with unmerged status ('U').");
4235                         return REQ_NONE;
4236                 }
4237                 open_mergetool(status->new.name);
4238                 break;
4240         case REQ_EDIT:
4241                 if (!status)
4242                         return request;
4244                 open_editor(status->status != '?', status->new.name);
4245                 break;
4247         case REQ_VIEW_BLAME:
4248                 if (status) {
4249                         string_copy(opt_file, status->new.name);
4250                         opt_ref[0] = 0;
4251                 }
4252                 return request;
4254         case REQ_ENTER:
4255                 /* After returning the status view has been split to
4256                  * show the stage view. No further reloading is
4257                  * necessary. */
4258                 status_enter(view, line);
4259                 return REQ_NONE;
4261         case REQ_REFRESH:
4262                 /* Simply reload the view. */
4263                 break;
4265         default:
4266                 return request;
4267         }
4269         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
4271         return REQ_NONE;
4274 static void
4275 status_select(struct view *view, struct line *line)
4277         struct status *status = line->data;
4278         char file[SIZEOF_STR] = "all files";
4279         char *text;
4280         char *key;
4282         if (status && !string_format(file, "'%s'", status->new.name))
4283                 return;
4285         if (!status && line[1].type == LINE_STAT_NONE)
4286                 line++;
4288         switch (line->type) {
4289         case LINE_STAT_STAGED:
4290                 text = "Press %s to unstage %s for commit";
4291                 break;
4293         case LINE_STAT_UNSTAGED:
4294                 text = "Press %s to stage %s for commit";
4295                 break;
4297         case LINE_STAT_UNTRACKED:
4298                 text = "Press %s to stage %s for addition";
4299                 break;
4301         case LINE_STAT_HEAD:
4302         case LINE_STAT_NONE:
4303                 text = "Nothing to update";
4304                 break;
4306         default:
4307                 die("line type %d not handled in switch", line->type);
4308         }
4310         if (status && status->status == 'U') {
4311                 text = "Press %s to resolve conflict in %s";
4312                 key = get_key(REQ_STATUS_MERGE);
4314         } else {
4315                 key = get_key(REQ_STATUS_UPDATE);
4316         }
4318         string_format(view->ref, text, key, file);
4321 static bool
4322 status_grep(struct view *view, struct line *line)
4324         struct status *status = line->data;
4325         enum { S_STATUS, S_NAME, S_END } state;
4326         char buf[2] = "?";
4327         regmatch_t pmatch;
4329         if (!status)
4330                 return FALSE;
4332         for (state = S_STATUS; state < S_END; state++) {
4333                 char *text;
4335                 switch (state) {
4336                 case S_NAME:    text = status->new.name;        break;
4337                 case S_STATUS:
4338                         buf[0] = status->status;
4339                         text = buf;
4340                         break;
4342                 default:
4343                         return FALSE;
4344                 }
4346                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4347                         return TRUE;
4348         }
4350         return FALSE;
4353 static struct view_ops status_ops = {
4354         "file",
4355         status_open,
4356         NULL,
4357         status_draw,
4358         status_request,
4359         status_grep,
4360         status_select,
4361 };
4364 static bool
4365 stage_diff_line(FILE *pipe, struct line *line)
4367         char *buf = line->data;
4368         size_t bufsize = strlen(buf);
4369         size_t written = 0;
4371         while (!ferror(pipe) && written < bufsize) {
4372                 written += fwrite(buf + written, 1, bufsize - written, pipe);
4373         }
4375         fputc('\n', pipe);
4377         return written == bufsize;
4380 static struct line *
4381 stage_diff_hdr(struct view *view, struct line *line)
4383         int diff_hdr_dir = line->type == LINE_DIFF_CHUNK ? -1 : 1;
4384         struct line *diff_hdr;
4386         if (line->type == LINE_DIFF_CHUNK)
4387                 diff_hdr = line - 1;
4388         else
4389                 diff_hdr = view->line + 1;
4391         while (diff_hdr > view->line && diff_hdr < view->line + view->lines) {
4392                 if (diff_hdr->type == LINE_DIFF_HEADER)
4393                         return diff_hdr;
4395                 diff_hdr += diff_hdr_dir;
4396         }
4398         return NULL;
4401 static bool
4402 stage_update_chunk(struct view *view, struct line *line)
4404         char cmd[SIZEOF_STR];
4405         size_t cmdsize = 0;
4406         struct line *diff_hdr, *diff_chunk, *diff_end;
4407         FILE *pipe;
4409         diff_hdr = stage_diff_hdr(view, line);
4410         if (!diff_hdr)
4411                 return FALSE;
4413         if (opt_cdup[0] &&
4414             !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
4415                 return FALSE;
4417         if (!string_format_from(cmd, &cmdsize,
4418                                 "git apply --cached %s - && "
4419                                 "git update-index -q --unmerged --refresh 2>/dev/null",
4420                                 stage_line_type == LINE_STAT_STAGED ? "-R" : ""))
4421                 return FALSE;
4423         pipe = popen(cmd, "w");
4424         if (!pipe)
4425                 return FALSE;
4427         diff_end = view->line + view->lines;
4428         if (line->type != LINE_DIFF_CHUNK) {
4429                 diff_chunk = diff_hdr;
4431         } else {
4432                 for (diff_chunk = line + 1; diff_chunk < diff_end; diff_chunk++)
4433                         if (diff_chunk->type == LINE_DIFF_CHUNK ||
4434                             diff_chunk->type == LINE_DIFF_HEADER)
4435                                 diff_end = diff_chunk;
4437                 diff_chunk = line;
4439                 while (diff_hdr->type != LINE_DIFF_CHUNK) {
4440                         switch (diff_hdr->type) {
4441                         case LINE_DIFF_HEADER:
4442                         case LINE_DIFF_INDEX:
4443                         case LINE_DIFF_ADD:
4444                         case LINE_DIFF_DEL:
4445                                 break;
4447                         default:
4448                                 diff_hdr++;
4449                                 continue;
4450                         }
4452                         if (!stage_diff_line(pipe, diff_hdr++)) {
4453                                 pclose(pipe);
4454                                 return FALSE;
4455                         }
4456                 }
4457         }
4459         while (diff_chunk < diff_end && stage_diff_line(pipe, diff_chunk))
4460                 diff_chunk++;
4462         pclose(pipe);
4464         if (diff_chunk != diff_end)
4465                 return FALSE;
4467         return TRUE;
4470 static void
4471 stage_update(struct view *view, struct line *line)
4473         if (!opt_no_head && stage_line_type != LINE_STAT_UNTRACKED &&
4474             (line->type == LINE_DIFF_CHUNK || !stage_status.status)) {
4475                 if (!stage_update_chunk(view, line)) {
4476                         report("Failed to apply chunk");
4477                         return;
4478                 }
4480         } else if (!status_update_file(view, &stage_status, stage_line_type)) {
4481                 report("Failed to update file");
4482                 return;
4483         }
4485         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
4487         view = VIEW(REQ_VIEW_STATUS);
4488         if (view_is_displayed(view))
4489                 status_enter(view, &view->line[view->lineno]);
4492 static enum request
4493 stage_request(struct view *view, enum request request, struct line *line)
4495         switch (request) {
4496         case REQ_STATUS_UPDATE:
4497                 stage_update(view, line);
4498                 break;
4500         case REQ_EDIT:
4501                 if (!stage_status.new.name[0])
4502                         return request;
4504                 open_editor(stage_status.status != '?', stage_status.new.name);
4505                 break;
4507         case REQ_VIEW_BLAME:
4508                 if (stage_status.new.name[0]) {
4509                         string_copy(opt_file, stage_status.new.name);
4510                         opt_ref[0] = 0;
4511                 }
4512                 return request;
4514         case REQ_ENTER:
4515                 pager_request(view, request, line);
4516                 break;
4518         default:
4519                 return request;
4520         }
4522         return REQ_NONE;
4525 static struct view_ops stage_ops = {
4526         "line",
4527         NULL,
4528         pager_read,
4529         pager_draw,
4530         stage_request,
4531         pager_grep,
4532         pager_select,
4533 };
4536 /*
4537  * Revision graph
4538  */
4540 struct commit {
4541         char id[SIZEOF_REV];            /* SHA1 ID. */
4542         char title[128];                /* First line of the commit message. */
4543         char author[75];                /* Author of the commit. */
4544         struct tm time;                 /* Date from the author ident. */
4545         struct ref **refs;              /* Repository references. */
4546         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
4547         size_t graph_size;              /* The width of the graph array. */
4548         bool has_parents;               /* Rewritten --parents seen. */
4549 };
4551 /* Size of rev graph with no  "padding" columns */
4552 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
4554 struct rev_graph {
4555         struct rev_graph *prev, *next, *parents;
4556         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
4557         size_t size;
4558         struct commit *commit;
4559         size_t pos;
4560         unsigned int boundary:1;
4561 };
4563 /* Parents of the commit being visualized. */
4564 static struct rev_graph graph_parents[4];
4566 /* The current stack of revisions on the graph. */
4567 static struct rev_graph graph_stacks[4] = {
4568         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
4569         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
4570         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
4571         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
4572 };
4574 static inline bool
4575 graph_parent_is_merge(struct rev_graph *graph)
4577         return graph->parents->size > 1;
4580 static inline void
4581 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
4583         struct commit *commit = graph->commit;
4585         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
4586                 commit->graph[commit->graph_size++] = symbol;
4589 static void
4590 done_rev_graph(struct rev_graph *graph)
4592         if (graph_parent_is_merge(graph) &&
4593             graph->pos < graph->size - 1 &&
4594             graph->next->size == graph->size + graph->parents->size - 1) {
4595                 size_t i = graph->pos + graph->parents->size - 1;
4597                 graph->commit->graph_size = i * 2;
4598                 while (i < graph->next->size - 1) {
4599                         append_to_rev_graph(graph, ' ');
4600                         append_to_rev_graph(graph, '\\');
4601                         i++;
4602                 }
4603         }
4605         graph->size = graph->pos = 0;
4606         graph->commit = NULL;
4607         memset(graph->parents, 0, sizeof(*graph->parents));
4610 static void
4611 push_rev_graph(struct rev_graph *graph, char *parent)
4613         int i;
4615         /* "Collapse" duplicate parents lines.
4616          *
4617          * FIXME: This needs to also update update the drawn graph but
4618          * for now it just serves as a method for pruning graph lines. */
4619         for (i = 0; i < graph->size; i++)
4620                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
4621                         return;
4623         if (graph->size < SIZEOF_REVITEMS) {
4624                 string_copy_rev(graph->rev[graph->size++], parent);
4625         }
4628 static chtype
4629 get_rev_graph_symbol(struct rev_graph *graph)
4631         chtype symbol;
4633         if (graph->boundary)
4634                 symbol = REVGRAPH_BOUND;
4635         else if (graph->parents->size == 0)
4636                 symbol = REVGRAPH_INIT;
4637         else if (graph_parent_is_merge(graph))
4638                 symbol = REVGRAPH_MERGE;
4639         else if (graph->pos >= graph->size)
4640                 symbol = REVGRAPH_BRANCH;
4641         else
4642                 symbol = REVGRAPH_COMMIT;
4644         return symbol;
4647 static void
4648 draw_rev_graph(struct rev_graph *graph)
4650         struct rev_filler {
4651                 chtype separator, line;
4652         };
4653         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
4654         static struct rev_filler fillers[] = {
4655                 { ' ',  REVGRAPH_LINE },
4656                 { '`',  '.' },
4657                 { '\'', ' ' },
4658                 { '/',  ' ' },
4659         };
4660         chtype symbol = get_rev_graph_symbol(graph);
4661         struct rev_filler *filler;
4662         size_t i;
4664         filler = &fillers[DEFAULT];
4666         for (i = 0; i < graph->pos; i++) {
4667                 append_to_rev_graph(graph, filler->line);
4668                 if (graph_parent_is_merge(graph->prev) &&
4669                     graph->prev->pos == i)
4670                         filler = &fillers[RSHARP];
4672                 append_to_rev_graph(graph, filler->separator);
4673         }
4675         /* Place the symbol for this revision. */
4676         append_to_rev_graph(graph, symbol);
4678         if (graph->prev->size > graph->size)
4679                 filler = &fillers[RDIAG];
4680         else
4681                 filler = &fillers[DEFAULT];
4683         i++;
4685         for (; i < graph->size; i++) {
4686                 append_to_rev_graph(graph, filler->separator);
4687                 append_to_rev_graph(graph, filler->line);
4688                 if (graph_parent_is_merge(graph->prev) &&
4689                     i < graph->prev->pos + graph->parents->size)
4690                         filler = &fillers[RSHARP];
4691                 if (graph->prev->size > graph->size)
4692                         filler = &fillers[LDIAG];
4693         }
4695         if (graph->prev->size > graph->size) {
4696                 append_to_rev_graph(graph, filler->separator);
4697                 if (filler->line != ' ')
4698                         append_to_rev_graph(graph, filler->line);
4699         }
4702 /* Prepare the next rev graph */
4703 static void
4704 prepare_rev_graph(struct rev_graph *graph)
4706         size_t i;
4708         /* First, traverse all lines of revisions up to the active one. */
4709         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
4710                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
4711                         break;
4713                 push_rev_graph(graph->next, graph->rev[graph->pos]);
4714         }
4716         /* Interleave the new revision parent(s). */
4717         for (i = 0; !graph->boundary && i < graph->parents->size; i++)
4718                 push_rev_graph(graph->next, graph->parents->rev[i]);
4720         /* Lastly, put any remaining revisions. */
4721         for (i = graph->pos + 1; i < graph->size; i++)
4722                 push_rev_graph(graph->next, graph->rev[i]);
4725 static void
4726 update_rev_graph(struct rev_graph *graph)
4728         /* If this is the finalizing update ... */
4729         if (graph->commit)
4730                 prepare_rev_graph(graph);
4732         /* Graph visualization needs a one rev look-ahead,
4733          * so the first update doesn't visualize anything. */
4734         if (!graph->prev->commit)
4735                 return;
4737         draw_rev_graph(graph->prev);
4738         done_rev_graph(graph->prev->prev);
4742 /*
4743  * Main view backend
4744  */
4746 static bool
4747 main_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
4749         char buf[DATE_COLS + 1];
4750         struct commit *commit = line->data;
4751         enum line_type type;
4752         int col = 0;
4753         size_t timelen;
4754         int space;
4756         if (!*commit->author)
4757                 return FALSE;
4759         space = view->width;
4760         wmove(view->win, lineno, col);
4762         if (selected) {
4763                 type = LINE_CURSOR;
4764                 wattrset(view->win, get_line_attr(type));
4765                 wchgat(view->win, -1, 0, type, NULL);
4766         } else {
4767                 type = LINE_MAIN_COMMIT;
4768                 wattrset(view->win, get_line_attr(LINE_MAIN_DATE));
4769         }
4771         if (opt_date) {
4772                 int n;
4774                 timelen = strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time);
4775                 n = draw_text(view, buf, view->width - col, FALSE, selected);
4776                 draw_text(view, " ", view->width - col - n, FALSE, selected);
4778                 col += DATE_COLS;
4779                 wmove(view->win, lineno, col);
4780                 if (col >= view->width)
4781                         return TRUE;
4782         }
4783         if (type != LINE_CURSOR)
4784                 wattrset(view->win, get_line_attr(LINE_MAIN_AUTHOR));
4786         if (opt_author) {
4787                 int max_len;
4789                 max_len = view->width - col;
4790                 if (max_len > AUTHOR_COLS - 1)
4791                         max_len = AUTHOR_COLS - 1;
4792                 draw_text(view, commit->author, max_len, TRUE, selected);
4793                 col += AUTHOR_COLS;
4794                 if (col >= view->width)
4795                         return TRUE;
4796         }
4798         if (opt_rev_graph && commit->graph_size) {
4799                 size_t graph_size = view->width - col;
4800                 size_t i;
4802                 if (type != LINE_CURSOR)
4803                         wattrset(view->win, get_line_attr(LINE_MAIN_REVGRAPH));
4804                 wmove(view->win, lineno, col);
4805                 if (graph_size > commit->graph_size)
4806                         graph_size = commit->graph_size;
4807                 /* Using waddch() instead of waddnstr() ensures that
4808                  * they'll be rendered correctly for the cursor line. */
4809                 for (i = 0; i < graph_size; i++)
4810                         waddch(view->win, commit->graph[i]);
4812                 col += commit->graph_size + 1;
4813                 if (col >= view->width)
4814                         return TRUE;
4815                 waddch(view->win, ' ');
4816         }
4817         if (type != LINE_CURSOR)
4818                 wattrset(view->win, A_NORMAL);
4820         wmove(view->win, lineno, col);
4822         if (opt_show_refs && commit->refs) {
4823                 size_t i = 0;
4825                 do {
4826                         if (type == LINE_CURSOR)
4827                                 ;
4828                         else if (commit->refs[i]->head)
4829                                 wattrset(view->win, get_line_attr(LINE_MAIN_HEAD));
4830                         else if (commit->refs[i]->ltag)
4831                                 wattrset(view->win, get_line_attr(LINE_MAIN_LOCAL_TAG));
4832                         else if (commit->refs[i]->tag)
4833                                 wattrset(view->win, get_line_attr(LINE_MAIN_TAG));
4834                         else if (commit->refs[i]->remote)
4835                                 wattrset(view->win, get_line_attr(LINE_MAIN_REMOTE));
4836                         else
4837                                 wattrset(view->win, get_line_attr(LINE_MAIN_REF));
4839                         col += draw_text(view, "[", view->width - col, TRUE, selected);
4840                         col += draw_text(view, commit->refs[i]->name, view->width - col,
4841                                          TRUE, selected);
4842                         col += draw_text(view, "]", view->width - col, TRUE, selected);
4843                         if (type != LINE_CURSOR)
4844                                 wattrset(view->win, A_NORMAL);
4845                         col += draw_text(view, " ", view->width - col, TRUE, selected);
4846                         if (col >= view->width)
4847                                 return TRUE;
4848                 } while (commit->refs[i++]->next);
4849         }
4851         if (type != LINE_CURSOR)
4852                 wattrset(view->win, get_line_attr(type));
4854         draw_text(view, commit->title, view->width - col, TRUE, selected);
4855         return TRUE;
4858 /* Reads git log --pretty=raw output and parses it into the commit struct. */
4859 static bool
4860 main_read(struct view *view, char *line)
4862         static struct rev_graph *graph = graph_stacks;
4863         enum line_type type;
4864         struct commit *commit;
4866         if (!line) {
4867                 update_rev_graph(graph);
4868                 return TRUE;
4869         }
4871         type = get_line_type(line);
4872         if (type == LINE_COMMIT) {
4873                 commit = calloc(1, sizeof(struct commit));
4874                 if (!commit)
4875                         return FALSE;
4877                 line += STRING_SIZE("commit ");
4878                 if (*line == '-') {
4879                         graph->boundary = 1;
4880                         line++;
4881                 }
4883                 string_copy_rev(commit->id, line);
4884                 commit->refs = get_refs(commit->id);
4885                 graph->commit = commit;
4886                 add_line_data(view, commit, LINE_MAIN_COMMIT);
4888                 while ((line = strchr(line, ' '))) {
4889                         line++;
4890                         push_rev_graph(graph->parents, line);
4891                         commit->has_parents = TRUE;
4892                 }
4893                 return TRUE;
4894         }
4896         if (!view->lines)
4897                 return TRUE;
4898         commit = view->line[view->lines - 1].data;
4900         switch (type) {
4901         case LINE_PARENT:
4902                 if (commit->has_parents)
4903                         break;
4904                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
4905                 break;
4907         case LINE_AUTHOR:
4908         {
4909                 /* Parse author lines where the name may be empty:
4910                  *      author  <email@address.tld> 1138474660 +0100
4911                  */
4912                 char *ident = line + STRING_SIZE("author ");
4913                 char *nameend = strchr(ident, '<');
4914                 char *emailend = strchr(ident, '>');
4916                 if (!nameend || !emailend)
4917                         break;
4919                 update_rev_graph(graph);
4920                 graph = graph->next;
4922                 *nameend = *emailend = 0;
4923                 ident = chomp_string(ident);
4924                 if (!*ident) {
4925                         ident = chomp_string(nameend + 1);
4926                         if (!*ident)
4927                                 ident = "Unknown";
4928                 }
4930                 string_ncopy(commit->author, ident, strlen(ident));
4932                 /* Parse epoch and timezone */
4933                 if (emailend[1] == ' ') {
4934                         char *secs = emailend + 2;
4935                         char *zone = strchr(secs, ' ');
4936                         time_t time = (time_t) atol(secs);
4938                         if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
4939                                 long tz;
4941                                 zone++;
4942                                 tz  = ('0' - zone[1]) * 60 * 60 * 10;
4943                                 tz += ('0' - zone[2]) * 60 * 60;
4944                                 tz += ('0' - zone[3]) * 60;
4945                                 tz += ('0' - zone[4]) * 60;
4947                                 if (zone[0] == '-')
4948                                         tz = -tz;
4950                                 time -= tz;
4951                         }
4953                         gmtime_r(&time, &commit->time);
4954                 }
4955                 break;
4956         }
4957         default:
4958                 /* Fill in the commit title if it has not already been set. */
4959                 if (commit->title[0])
4960                         break;
4962                 /* Require titles to start with a non-space character at the
4963                  * offset used by git log. */
4964                 if (strncmp(line, "    ", 4))
4965                         break;
4966                 line += 4;
4967                 /* Well, if the title starts with a whitespace character,
4968                  * try to be forgiving.  Otherwise we end up with no title. */
4969                 while (isspace(*line))
4970                         line++;
4971                 if (*line == '\0')
4972                         break;
4973                 /* FIXME: More graceful handling of titles; append "..." to
4974                  * shortened titles, etc. */
4976                 string_ncopy(commit->title, line, strlen(line));
4977         }
4979         return TRUE;
4982 static enum request
4983 main_request(struct view *view, enum request request, struct line *line)
4985         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4987         if (request == REQ_ENTER)
4988                 open_view(view, REQ_VIEW_DIFF, flags);
4989         else
4990                 return request;
4992         return REQ_NONE;
4995 static bool
4996 main_grep(struct view *view, struct line *line)
4998         struct commit *commit = line->data;
4999         enum { S_TITLE, S_AUTHOR, S_DATE, S_END } state;
5000         char buf[DATE_COLS + 1];
5001         regmatch_t pmatch;
5003         for (state = S_TITLE; state < S_END; state++) {
5004                 char *text;
5006                 switch (state) {
5007                 case S_TITLE:   text = commit->title;   break;
5008                 case S_AUTHOR:  text = commit->author;  break;
5009                 case S_DATE:
5010                         if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
5011                                 continue;
5012                         text = buf;
5013                         break;
5015                 default:
5016                         return FALSE;
5017                 }
5019                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
5020                         return TRUE;
5021         }
5023         return FALSE;
5026 static void
5027 main_select(struct view *view, struct line *line)
5029         struct commit *commit = line->data;
5031         string_copy_rev(view->ref, commit->id);
5032         string_copy_rev(ref_commit, view->ref);
5035 static struct view_ops main_ops = {
5036         "commit",
5037         NULL,
5038         main_read,
5039         main_draw,
5040         main_request,
5041         main_grep,
5042         main_select,
5043 };
5046 /*
5047  * Unicode / UTF-8 handling
5048  *
5049  * NOTE: Much of the following code for dealing with unicode is derived from
5050  * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
5051  * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
5052  */
5054 /* I've (over)annotated a lot of code snippets because I am not entirely
5055  * confident that the approach taken by this small UTF-8 interface is correct.
5056  * --jonas */
5058 static inline int
5059 unicode_width(unsigned long c)
5061         if (c >= 0x1100 &&
5062            (c <= 0x115f                         /* Hangul Jamo */
5063             || c == 0x2329
5064             || c == 0x232a
5065             || (c >= 0x2e80  && c <= 0xa4cf && c != 0x303f)
5066                                                 /* CJK ... Yi */
5067             || (c >= 0xac00  && c <= 0xd7a3)    /* Hangul Syllables */
5068             || (c >= 0xf900  && c <= 0xfaff)    /* CJK Compatibility Ideographs */
5069             || (c >= 0xfe30  && c <= 0xfe6f)    /* CJK Compatibility Forms */
5070             || (c >= 0xff00  && c <= 0xff60)    /* Fullwidth Forms */
5071             || (c >= 0xffe0  && c <= 0xffe6)
5072             || (c >= 0x20000 && c <= 0x2fffd)
5073             || (c >= 0x30000 && c <= 0x3fffd)))
5074                 return 2;
5076         if (c == '\t')
5077                 return opt_tab_size;
5079         return 1;
5082 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
5083  * Illegal bytes are set one. */
5084 static const unsigned char utf8_bytes[256] = {
5085         1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
5086         1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
5087         1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
5088         1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
5089         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,
5090         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,
5091         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,
5092         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,
5093 };
5095 /* Decode UTF-8 multi-byte representation into a unicode character. */
5096 static inline unsigned long
5097 utf8_to_unicode(const char *string, size_t length)
5099         unsigned long unicode;
5101         switch (length) {
5102         case 1:
5103                 unicode  =   string[0];
5104                 break;
5105         case 2:
5106                 unicode  =  (string[0] & 0x1f) << 6;
5107                 unicode +=  (string[1] & 0x3f);
5108                 break;
5109         case 3:
5110                 unicode  =  (string[0] & 0x0f) << 12;
5111                 unicode += ((string[1] & 0x3f) << 6);
5112                 unicode +=  (string[2] & 0x3f);
5113                 break;
5114         case 4:
5115                 unicode  =  (string[0] & 0x0f) << 18;
5116                 unicode += ((string[1] & 0x3f) << 12);
5117                 unicode += ((string[2] & 0x3f) << 6);
5118                 unicode +=  (string[3] & 0x3f);
5119                 break;
5120         case 5:
5121                 unicode  =  (string[0] & 0x0f) << 24;
5122                 unicode += ((string[1] & 0x3f) << 18);
5123                 unicode += ((string[2] & 0x3f) << 12);
5124                 unicode += ((string[3] & 0x3f) << 6);
5125                 unicode +=  (string[4] & 0x3f);
5126                 break;
5127         case 6:
5128                 unicode  =  (string[0] & 0x01) << 30;
5129                 unicode += ((string[1] & 0x3f) << 24);
5130                 unicode += ((string[2] & 0x3f) << 18);
5131                 unicode += ((string[3] & 0x3f) << 12);
5132                 unicode += ((string[4] & 0x3f) << 6);
5133                 unicode +=  (string[5] & 0x3f);
5134                 break;
5135         default:
5136                 die("Invalid unicode length");
5137         }
5139         /* Invalid characters could return the special 0xfffd value but NUL
5140          * should be just as good. */
5141         return unicode > 0xffff ? 0 : unicode;
5144 /* Calculates how much of string can be shown within the given maximum width
5145  * and sets trimmed parameter to non-zero value if all of string could not be
5146  * shown. If the reserve flag is TRUE, it will reserve at least one
5147  * trailing character, which can be useful when drawing a delimiter.
5148  *
5149  * Returns the number of bytes to output from string to satisfy max_width. */
5150 static size_t
5151 utf8_length(const char *string, size_t max_width, int *trimmed, bool reserve)
5153         const char *start = string;
5154         const char *end = strchr(string, '\0');
5155         unsigned char last_bytes = 0;
5156         size_t width = 0;
5158         *trimmed = 0;
5160         while (string < end) {
5161                 int c = *(unsigned char *) string;
5162                 unsigned char bytes = utf8_bytes[c];
5163                 size_t ucwidth;
5164                 unsigned long unicode;
5166                 if (string + bytes > end)
5167                         break;
5169                 /* Change representation to figure out whether
5170                  * it is a single- or double-width character. */
5172                 unicode = utf8_to_unicode(string, bytes);
5173                 /* FIXME: Graceful handling of invalid unicode character. */
5174                 if (!unicode)
5175                         break;
5177                 ucwidth = unicode_width(unicode);
5178                 width  += ucwidth;
5179                 if (width > max_width) {
5180                         *trimmed = 1;
5181                         if (reserve && width - ucwidth == max_width) {
5182                                 string -= last_bytes;
5183                         }
5184                         break;
5185                 }
5187                 string  += bytes;
5188                 last_bytes = bytes;
5189         }
5191         return string - start;
5195 /*
5196  * Status management
5197  */
5199 /* Whether or not the curses interface has been initialized. */
5200 static bool cursed = FALSE;
5202 /* The status window is used for polling keystrokes. */
5203 static WINDOW *status_win;
5205 static bool status_empty = TRUE;
5207 /* Update status and title window. */
5208 static void
5209 report(const char *msg, ...)
5211         struct view *view = display[current_view];
5213         if (input_mode)
5214                 return;
5216         if (!view) {
5217                 char buf[SIZEOF_STR];
5218                 va_list args;
5220                 va_start(args, msg);
5221                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
5222                         buf[sizeof(buf) - 1] = 0;
5223                         buf[sizeof(buf) - 2] = '.';
5224                         buf[sizeof(buf) - 3] = '.';
5225                         buf[sizeof(buf) - 4] = '.';
5226                 }
5227                 va_end(args);
5228                 die("%s", buf);
5229         }
5231         if (!status_empty || *msg) {
5232                 va_list args;
5234                 va_start(args, msg);
5236                 wmove(status_win, 0, 0);
5237                 if (*msg) {
5238                         vwprintw(status_win, msg, args);
5239                         status_empty = FALSE;
5240                 } else {
5241                         status_empty = TRUE;
5242                 }
5243                 wclrtoeol(status_win);
5244                 wrefresh(status_win);
5246                 va_end(args);
5247         }
5249         update_view_title(view);
5250         update_display_cursor(view);
5253 /* Controls when nodelay should be in effect when polling user input. */
5254 static void
5255 set_nonblocking_input(bool loading)
5257         static unsigned int loading_views;
5259         if ((loading == FALSE && loading_views-- == 1) ||
5260             (loading == TRUE  && loading_views++ == 0))
5261                 nodelay(status_win, loading);
5264 static void
5265 init_display(void)
5267         int x, y;
5269         /* Initialize the curses library */
5270         if (isatty(STDIN_FILENO)) {
5271                 cursed = !!initscr();
5272         } else {
5273                 /* Leave stdin and stdout alone when acting as a pager. */
5274                 FILE *io = fopen("/dev/tty", "r+");
5276                 if (!io)
5277                         die("Failed to open /dev/tty");
5278                 cursed = !!newterm(NULL, io, io);
5279         }
5281         if (!cursed)
5282                 die("Failed to initialize curses");
5284         nonl();         /* Tell curses not to do NL->CR/NL on output */
5285         cbreak();       /* Take input chars one at a time, no wait for \n */
5286         noecho();       /* Don't echo input */
5287         leaveok(stdscr, TRUE);
5289         if (has_colors())
5290                 init_colors();
5292         getmaxyx(stdscr, y, x);
5293         status_win = newwin(1, 0, y - 1, 0);
5294         if (!status_win)
5295                 die("Failed to create status window");
5297         /* Enable keyboard mapping */
5298         keypad(status_win, TRUE);
5299         wbkgdset(status_win, get_line_attr(LINE_STATUS));
5302 static char *
5303 read_prompt(const char *prompt)
5305         enum { READING, STOP, CANCEL } status = READING;
5306         static char buf[sizeof(opt_cmd) - STRING_SIZE("git \0")];
5307         int pos = 0;
5309         while (status == READING) {
5310                 struct view *view;
5311                 int i, key;
5313                 input_mode = TRUE;
5315                 foreach_view (view, i)
5316                         update_view(view);
5318                 input_mode = FALSE;
5320                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
5321                 wclrtoeol(status_win);
5323                 /* Refresh, accept single keystroke of input */
5324                 key = wgetch(status_win);
5325                 switch (key) {
5326                 case KEY_RETURN:
5327                 case KEY_ENTER:
5328                 case '\n':
5329                         status = pos ? STOP : CANCEL;
5330                         break;
5332                 case KEY_BACKSPACE:
5333                         if (pos > 0)
5334                                 pos--;
5335                         else
5336                                 status = CANCEL;
5337                         break;
5339                 case KEY_ESC:
5340                         status = CANCEL;
5341                         break;
5343                 case ERR:
5344                         break;
5346                 default:
5347                         if (pos >= sizeof(buf)) {
5348                                 report("Input string too long");
5349                                 return NULL;
5350                         }
5352                         if (isprint(key))
5353                                 buf[pos++] = (char) key;
5354                 }
5355         }
5357         /* Clear the status window */
5358         status_empty = FALSE;
5359         report("");
5361         if (status == CANCEL)
5362                 return NULL;
5364         buf[pos++] = 0;
5366         return buf;
5369 /*
5370  * Repository references
5371  */
5373 static struct ref *refs = NULL;
5374 static size_t refs_alloc = 0;
5375 static size_t refs_size = 0;
5377 /* Id <-> ref store */
5378 static struct ref ***id_refs = NULL;
5379 static size_t id_refs_alloc = 0;
5380 static size_t id_refs_size = 0;
5382 static struct ref **
5383 get_refs(char *id)
5385         struct ref ***tmp_id_refs;
5386         struct ref **ref_list = NULL;
5387         size_t ref_list_alloc = 0;
5388         size_t ref_list_size = 0;
5389         size_t i;
5391         for (i = 0; i < id_refs_size; i++)
5392                 if (!strcmp(id, id_refs[i][0]->id))
5393                         return id_refs[i];
5395         tmp_id_refs = realloc_items(id_refs, &id_refs_alloc, id_refs_size + 1,
5396                                     sizeof(*id_refs));
5397         if (!tmp_id_refs)
5398                 return NULL;
5400         id_refs = tmp_id_refs;
5402         for (i = 0; i < refs_size; i++) {
5403                 struct ref **tmp;
5405                 if (strcmp(id, refs[i].id))
5406                         continue;
5408                 tmp = realloc_items(ref_list, &ref_list_alloc,
5409                                     ref_list_size + 1, sizeof(*ref_list));
5410                 if (!tmp) {
5411                         if (ref_list)
5412                                 free(ref_list);
5413                         return NULL;
5414                 }
5416                 ref_list = tmp;
5417                 if (ref_list_size > 0)
5418                         ref_list[ref_list_size - 1]->next = 1;
5419                 ref_list[ref_list_size] = &refs[i];
5421                 /* XXX: The properties of the commit chains ensures that we can
5422                  * safely modify the shared ref. The repo references will
5423                  * always be similar for the same id. */
5424                 ref_list[ref_list_size]->next = 0;
5425                 ref_list_size++;
5426         }
5428         if (ref_list)
5429                 id_refs[id_refs_size++] = ref_list;
5431         return ref_list;
5434 static int
5435 read_ref(char *id, size_t idlen, char *name, size_t namelen)
5437         struct ref *ref;
5438         bool tag = FALSE;
5439         bool ltag = FALSE;
5440         bool remote = FALSE;
5441         bool check_replace = FALSE;
5442         bool head = FALSE;
5444         if (!strncmp(name, "refs/tags/", STRING_SIZE("refs/tags/"))) {
5445                 if (!strcmp(name + namelen - 3, "^{}")) {
5446                         namelen -= 3;
5447                         name[namelen] = 0;
5448                         if (refs_size > 0 && refs[refs_size - 1].ltag == TRUE)
5449                                 check_replace = TRUE;
5450                 } else {
5451                         ltag = TRUE;
5452                 }
5454                 tag = TRUE;
5455                 namelen -= STRING_SIZE("refs/tags/");
5456                 name    += STRING_SIZE("refs/tags/");
5458         } else if (!strncmp(name, "refs/remotes/", STRING_SIZE("refs/remotes/"))) {
5459                 remote = TRUE;
5460                 namelen -= STRING_SIZE("refs/remotes/");
5461                 name    += STRING_SIZE("refs/remotes/");
5463         } else if (!strncmp(name, "refs/heads/", STRING_SIZE("refs/heads/"))) {
5464                 namelen -= STRING_SIZE("refs/heads/");
5465                 name    += STRING_SIZE("refs/heads/");
5466                 head     = !strncmp(opt_head, name, namelen);
5468         } else if (!strcmp(name, "HEAD")) {
5469                 opt_no_head = FALSE;
5470                 return OK;
5471         }
5473         if (check_replace && !strcmp(name, refs[refs_size - 1].name)) {
5474                 /* it's an annotated tag, replace the previous sha1 with the
5475                  * resolved commit id; relies on the fact git-ls-remote lists
5476                  * the commit id of an annotated tag right beofre the commit id
5477                  * it points to. */
5478                 refs[refs_size - 1].ltag = ltag;
5479                 string_copy_rev(refs[refs_size - 1].id, id);
5481                 return OK;
5482         }
5483         refs = realloc_items(refs, &refs_alloc, refs_size + 1, sizeof(*refs));
5484         if (!refs)
5485                 return ERR;
5487         ref = &refs[refs_size++];
5488         ref->name = malloc(namelen + 1);
5489         if (!ref->name)
5490                 return ERR;
5492         strncpy(ref->name, name, namelen);
5493         ref->name[namelen] = 0;
5494         ref->tag = tag;
5495         ref->ltag = ltag;
5496         ref->remote = remote;
5497         ref->head = head;
5498         string_copy_rev(ref->id, id);
5500         return OK;
5503 static int
5504 load_refs(void)
5506         const char *cmd_env = getenv("TIG_LS_REMOTE");
5507         const char *cmd = cmd_env && *cmd_env ? cmd_env : TIG_LS_REMOTE;
5509         return read_properties(popen(cmd, "r"), "\t", read_ref);
5512 static int
5513 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
5515         if (!strcmp(name, "i18n.commitencoding"))
5516                 string_ncopy(opt_encoding, value, valuelen);
5518         if (!strcmp(name, "core.editor"))
5519                 string_ncopy(opt_editor, value, valuelen);
5521         return OK;
5524 static int
5525 load_repo_config(void)
5527         return read_properties(popen(GIT_CONFIG " --list", "r"),
5528                                "=", read_repo_config_option);
5531 static int
5532 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
5534         if (!opt_git_dir[0]) {
5535                 string_ncopy(opt_git_dir, name, namelen);
5537         } else if (opt_is_inside_work_tree == -1) {
5538                 /* This can be 3 different values depending on the
5539                  * version of git being used. If git-rev-parse does not
5540                  * understand --is-inside-work-tree it will simply echo
5541                  * the option else either "true" or "false" is printed.
5542                  * Default to true for the unknown case. */
5543                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
5545         } else if (opt_cdup[0] == ' ') {
5546                 string_ncopy(opt_cdup, name, namelen);
5547         } else {
5548                 if (!strncmp(name, "refs/heads/", STRING_SIZE("refs/heads/"))) {
5549                         namelen -= STRING_SIZE("refs/heads/");
5550                         name    += STRING_SIZE("refs/heads/");
5551                         string_ncopy(opt_head, name, namelen);
5552                 }
5553         }
5555         return OK;
5558 static int
5559 load_repo_info(void)
5561         int result;
5562         FILE *pipe = popen("git rev-parse --git-dir --is-inside-work-tree "
5563                            " --show-cdup --symbolic-full-name HEAD 2>/dev/null", "r");
5565         /* XXX: The line outputted by "--show-cdup" can be empty so
5566          * initialize it to something invalid to make it possible to
5567          * detect whether it has been set or not. */
5568         opt_cdup[0] = ' ';
5570         result = read_properties(pipe, "=", read_repo_info);
5571         if (opt_cdup[0] == ' ')
5572                 opt_cdup[0] = 0;
5574         return result;
5577 static int
5578 read_properties(FILE *pipe, const char *separators,
5579                 int (*read_property)(char *, size_t, char *, size_t))
5581         char buffer[BUFSIZ];
5582         char *name;
5583         int state = OK;
5585         if (!pipe)
5586                 return ERR;
5588         while (state == OK && (name = fgets(buffer, sizeof(buffer), pipe))) {
5589                 char *value;
5590                 size_t namelen;
5591                 size_t valuelen;
5593                 name = chomp_string(name);
5594                 namelen = strcspn(name, separators);
5596                 if (name[namelen]) {
5597                         name[namelen] = 0;
5598                         value = chomp_string(name + namelen + 1);
5599                         valuelen = strlen(value);
5601                 } else {
5602                         value = "";
5603                         valuelen = 0;
5604                 }
5606                 state = read_property(name, namelen, value, valuelen);
5607         }
5609         if (state != ERR && ferror(pipe))
5610                 state = ERR;
5612         pclose(pipe);
5614         return state;
5618 /*
5619  * Main
5620  */
5622 static void __NORETURN
5623 quit(int sig)
5625         /* XXX: Restore tty modes and let the OS cleanup the rest! */
5626         if (cursed)
5627                 endwin();
5628         exit(0);
5631 static void __NORETURN
5632 die(const char *err, ...)
5634         va_list args;
5636         endwin();
5638         va_start(args, err);
5639         fputs("tig: ", stderr);
5640         vfprintf(stderr, err, args);
5641         fputs("\n", stderr);
5642         va_end(args);
5644         exit(1);
5647 static void
5648 warn(const char *msg, ...)
5650         va_list args;
5652         va_start(args, msg);
5653         fputs("tig warning: ", stderr);
5654         vfprintf(stderr, msg, args);
5655         fputs("\n", stderr);
5656         va_end(args);
5659 int
5660 main(int argc, char *argv[])
5662         struct view *view;
5663         enum request request;
5664         size_t i;
5666         signal(SIGINT, quit);
5668         if (setlocale(LC_ALL, "")) {
5669                 char *codeset = nl_langinfo(CODESET);
5671                 string_ncopy(opt_codeset, codeset, strlen(codeset));
5672         }
5674         if (load_repo_info() == ERR)
5675                 die("Failed to load repo info.");
5677         if (load_options() == ERR)
5678                 die("Failed to load user config.");
5680         /* Load the repo config file so options can be overwritten from
5681          * the command line. */
5682         if (load_repo_config() == ERR)
5683                 die("Failed to load repo config.");
5685         if (!parse_options(argc, argv))
5686                 return 0;
5688         /* Require a git repository unless when running in pager mode. */
5689         if (!opt_git_dir[0])
5690                 die("Not a git repository");
5692         if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
5693                 opt_utf8 = FALSE;
5695         if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
5696                 opt_iconv = iconv_open(opt_codeset, opt_encoding);
5697                 if (opt_iconv == ICONV_NONE)
5698                         die("Failed to initialize character set conversion");
5699         }
5701         if (load_refs() == ERR)
5702                 die("Failed to load refs.");
5704         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
5705                 view->cmd_env = getenv(view->cmd_env);
5707         request = opt_request;
5709         init_display();
5711         while (view_driver(display[current_view], request)) {
5712                 int key;
5713                 int i;
5715                 foreach_view (view, i)
5716                         update_view(view);
5718                 /* Refresh, accept single keystroke of input */
5719                 key = wgetch(status_win);
5721                 /* wgetch() with nodelay() enabled returns ERR when there's no
5722                  * input. */
5723                 if (key == ERR) {
5724                         request = REQ_NONE;
5725                         continue;
5726                 }
5728                 request = get_keybinding(display[current_view]->keymap, key);
5730                 /* Some low-level request handling. This keeps access to
5731                  * status_win restricted. */
5732                 switch (request) {
5733                 case REQ_PROMPT:
5734                 {
5735                         char *cmd = read_prompt(":");
5737                         if (cmd && string_format(opt_cmd, "git %s", cmd)) {
5738                                 if (strncmp(cmd, "show", 4) && isspace(cmd[4])) {
5739                                         opt_request = REQ_VIEW_DIFF;
5740                                 } else {
5741                                         opt_request = REQ_VIEW_PAGER;
5742                                 }
5743                                 break;
5744                         }
5746                         request = REQ_NONE;
5747                         break;
5748                 }
5749                 case REQ_SEARCH:
5750                 case REQ_SEARCH_BACK:
5751                 {
5752                         const char *prompt = request == REQ_SEARCH
5753                                            ? "/" : "?";
5754                         char *search = read_prompt(prompt);
5756                         if (search)
5757                                 string_ncopy(opt_search, search, strlen(search));
5758                         else
5759                                 request = REQ_NONE;
5760                         break;
5761                 }
5762                 case REQ_SCREEN_RESIZE:
5763                 {
5764                         int height, width;
5766                         getmaxyx(stdscr, height, width);
5768                         /* Resize the status view and let the view driver take
5769                          * care of resizing the displayed views. */
5770                         wresize(status_win, 1, width);
5771                         mvwin(status_win, height - 1, 0);
5772                         wrefresh(status_win);
5773                         break;
5774                 }
5775                 default:
5776                         break;
5777                 }
5778         }
5780         quit(0);
5782         return 0;