Code

blame: simplify handling of incomplete commit information
[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  '^'
81 #define SIZEOF_REVGRAPH 19      /* Size of revision ancestry graphics. */
83 /* This color name can be used to refer to the default term colors. */
84 #define COLOR_DEFAULT   (-1)
86 #define ICONV_NONE      ((iconv_t) -1)
87 #ifndef ICONV_CONST
88 #define ICONV_CONST     /* nothing */
89 #endif
91 /* The format and size of the date column in the main view. */
92 #define DATE_FORMAT     "%Y-%m-%d %H:%M"
93 #define DATE_COLS       STRING_SIZE("2006-04-29 14:21 ")
95 #define AUTHOR_COLS     20
96 #define ID_COLS         8
98 /* The default interval between line numbers. */
99 #define NUMBER_INTERVAL 5
101 #define TAB_SIZE        8
103 #define SCALE_SPLIT_VIEW(height)        ((height) * 2 / 3)
105 #define NULL_ID         "0000000000000000000000000000000000000000"
107 #ifndef GIT_CONFIG
108 #define GIT_CONFIG "git config"
109 #endif
111 #define TIG_LS_REMOTE \
112         "git ls-remote $(git rev-parse --git-dir) 2>/dev/null"
114 #define TIG_DIFF_CMD \
115         "git show --pretty=fuller --no-color --root --patch-with-stat --find-copies-harder -C %s 2>/dev/null"
117 #define TIG_LOG_CMD     \
118         "git log --no-color --cc --stat -n100 %s 2>/dev/null"
120 #define TIG_MAIN_CMD \
121         "git log --no-color --topo-order --parents --boundary --pretty=raw %s 2>/dev/null"
123 #define TIG_TREE_CMD    \
124         "git ls-tree %s %s"
126 #define TIG_BLOB_CMD    \
127         "git cat-file blob %s"
129 /* XXX: Needs to be defined to the empty string. */
130 #define TIG_HELP_CMD    ""
131 #define TIG_PAGER_CMD   ""
132 #define TIG_STATUS_CMD  ""
133 #define TIG_STAGE_CMD   ""
134 #define TIG_BLAME_CMD   ""
136 /* Some ascii-shorthands fitted into the ncurses namespace. */
137 #define KEY_TAB         '\t'
138 #define KEY_RETURN      '\r'
139 #define KEY_ESC         27
142 struct ref {
143         char *name;             /* Ref name; tag or head names are shortened. */
144         char id[SIZEOF_REV];    /* Commit SHA1 ID */
145         unsigned int head:1;    /* Is it the current HEAD? */
146         unsigned int tag:1;     /* Is it a tag? */
147         unsigned int ltag:1;    /* If so, is the tag local? */
148         unsigned int remote:1;  /* Is it a remote ref? */
149         unsigned int tracked:1; /* Is it the remote for the current HEAD? */
150         unsigned int next:1;    /* For ref lists: are there more refs? */
151 };
153 static struct ref **get_refs(char *id);
155 struct int_map {
156         const char *name;
157         int namelen;
158         int value;
159 };
161 static int
162 set_from_int_map(struct int_map *map, size_t map_size,
163                  int *value, const char *name, int namelen)
166         int i;
168         for (i = 0; i < map_size; i++)
169                 if (namelen == map[i].namelen &&
170                     !strncasecmp(name, map[i].name, namelen)) {
171                         *value = map[i].value;
172                         return OK;
173                 }
175         return ERR;
179 /*
180  * String helpers
181  */
183 static inline void
184 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
186         if (srclen > dstlen - 1)
187                 srclen = dstlen - 1;
189         strncpy(dst, src, srclen);
190         dst[srclen] = 0;
193 /* Shorthands for safely copying into a fixed buffer. */
195 #define string_copy(dst, src) \
196         string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
198 #define string_ncopy(dst, src, srclen) \
199         string_ncopy_do(dst, sizeof(dst), src, srclen)
201 #define string_copy_rev(dst, src) \
202         string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
204 #define string_add(dst, from, src) \
205         string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
207 static char *
208 chomp_string(char *name)
210         int namelen;
212         while (isspace(*name))
213                 name++;
215         namelen = strlen(name) - 1;
216         while (namelen > 0 && isspace(name[namelen]))
217                 name[namelen--] = 0;
219         return name;
222 static bool
223 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
225         va_list args;
226         size_t pos = bufpos ? *bufpos : 0;
228         va_start(args, fmt);
229         pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
230         va_end(args);
232         if (bufpos)
233                 *bufpos = pos;
235         return pos >= bufsize ? FALSE : TRUE;
238 #define string_format(buf, fmt, args...) \
239         string_nformat(buf, sizeof(buf), NULL, fmt, args)
241 #define string_format_from(buf, from, fmt, args...) \
242         string_nformat(buf, sizeof(buf), from, fmt, args)
244 static int
245 string_enum_compare(const char *str1, const char *str2, int len)
247         size_t i;
249 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
251         /* Diff-Header == DIFF_HEADER */
252         for (i = 0; i < len; i++) {
253                 if (toupper(str1[i]) == toupper(str2[i]))
254                         continue;
256                 if (string_enum_sep(str1[i]) &&
257                     string_enum_sep(str2[i]))
258                         continue;
260                 return str1[i] - str2[i];
261         }
263         return 0;
266 /* Shell quoting
267  *
268  * NOTE: The following is a slightly modified copy of the git project's shell
269  * quoting routines found in the quote.c file.
270  *
271  * Help to copy the thing properly quoted for the shell safety.  any single
272  * quote is replaced with '\'', any exclamation point is replaced with '\!',
273  * and the whole thing is enclosed in a
274  *
275  * E.g.
276  *  original     sq_quote     result
277  *  name     ==> name      ==> 'name'
278  *  a b      ==> a b       ==> 'a b'
279  *  a'b      ==> a'\''b    ==> 'a'\''b'
280  *  a!b      ==> a'\!'b    ==> 'a'\!'b'
281  */
283 static size_t
284 sq_quote(char buf[SIZEOF_STR], size_t bufsize, const char *src)
286         char c;
288 #define BUFPUT(x) do { if (bufsize < SIZEOF_STR) buf[bufsize++] = (x); } while (0)
290         BUFPUT('\'');
291         while ((c = *src++)) {
292                 if (c == '\'' || c == '!') {
293                         BUFPUT('\'');
294                         BUFPUT('\\');
295                         BUFPUT(c);
296                         BUFPUT('\'');
297                 } else {
298                         BUFPUT(c);
299                 }
300         }
301         BUFPUT('\'');
303         if (bufsize < SIZEOF_STR)
304                 buf[bufsize] = 0;
306         return bufsize;
310 /*
311  * User requests
312  */
314 #define REQ_INFO \
315         /* XXX: Keep the view request first and in sync with views[]. */ \
316         REQ_GROUP("View switching") \
317         REQ_(VIEW_MAIN,         "Show main view"), \
318         REQ_(VIEW_DIFF,         "Show diff view"), \
319         REQ_(VIEW_LOG,          "Show log view"), \
320         REQ_(VIEW_TREE,         "Show tree view"), \
321         REQ_(VIEW_BLOB,         "Show blob view"), \
322         REQ_(VIEW_BLAME,        "Show blame view"), \
323         REQ_(VIEW_HELP,         "Show help page"), \
324         REQ_(VIEW_PAGER,        "Show pager view"), \
325         REQ_(VIEW_STATUS,       "Show status view"), \
326         REQ_(VIEW_STAGE,        "Show stage view"), \
327         \
328         REQ_GROUP("View manipulation") \
329         REQ_(ENTER,             "Enter current line and scroll"), \
330         REQ_(NEXT,              "Move to next"), \
331         REQ_(PREVIOUS,          "Move to previous"), \
332         REQ_(VIEW_NEXT,         "Move focus to next view"), \
333         REQ_(REFRESH,           "Reload and refresh"), \
334         REQ_(MAXIMIZE,          "Maximize the current view"), \
335         REQ_(VIEW_CLOSE,        "Close the current view"), \
336         REQ_(QUIT,              "Close all views and quit"), \
337         \
338         REQ_GROUP("Cursor navigation") \
339         REQ_(MOVE_UP,           "Move cursor one line up"), \
340         REQ_(MOVE_DOWN,         "Move cursor one line down"), \
341         REQ_(MOVE_PAGE_DOWN,    "Move cursor one page down"), \
342         REQ_(MOVE_PAGE_UP,      "Move cursor one page up"), \
343         REQ_(MOVE_FIRST_LINE,   "Move cursor to first line"), \
344         REQ_(MOVE_LAST_LINE,    "Move cursor to last line"), \
345         \
346         REQ_GROUP("Scrolling") \
347         REQ_(SCROLL_LINE_UP,    "Scroll one line up"), \
348         REQ_(SCROLL_LINE_DOWN,  "Scroll one line down"), \
349         REQ_(SCROLL_PAGE_UP,    "Scroll one page up"), \
350         REQ_(SCROLL_PAGE_DOWN,  "Scroll one page down"), \
351         \
352         REQ_GROUP("Searching") \
353         REQ_(SEARCH,            "Search the view"), \
354         REQ_(SEARCH_BACK,       "Search backwards in the view"), \
355         REQ_(FIND_NEXT,         "Find next search match"), \
356         REQ_(FIND_PREV,         "Find previous search match"), \
357         \
358         REQ_GROUP("Misc") \
359         REQ_(PROMPT,            "Bring up the prompt"), \
360         REQ_(SCREEN_REDRAW,     "Redraw the screen"), \
361         REQ_(SCREEN_RESIZE,     "Resize the screen"), \
362         REQ_(SHOW_VERSION,      "Show version information"), \
363         REQ_(STOP_LOADING,      "Stop all loading views"), \
364         REQ_(TOGGLE_LINENO,     "Toggle line numbers"), \
365         REQ_(TOGGLE_DATE,       "Toggle date display"), \
366         REQ_(TOGGLE_AUTHOR,     "Toggle author display"), \
367         REQ_(TOGGLE_REV_GRAPH,  "Toggle revision graph visualization"), \
368         REQ_(TOGGLE_REFS,       "Toggle reference display (tags/branches)"), \
369         REQ_(STATUS_UPDATE,     "Update file status"), \
370         REQ_(STATUS_MERGE,      "Merge file using external tool"), \
371         REQ_(TREE_PARENT,       "Switch to parent directory in tree view"), \
372         REQ_(EDIT,              "Open in editor"), \
373         REQ_(NONE,              "Do nothing")
376 /* User action requests. */
377 enum request {
378 #define REQ_GROUP(help)
379 #define REQ_(req, help) REQ_##req
381         /* Offset all requests to avoid conflicts with ncurses getch values. */
382         REQ_OFFSET = KEY_MAX + 1,
383         REQ_INFO
385 #undef  REQ_GROUP
386 #undef  REQ_
387 };
389 struct request_info {
390         enum request request;
391         char *name;
392         int namelen;
393         char *help;
394 };
396 static struct request_info req_info[] = {
397 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
398 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
399         REQ_INFO
400 #undef  REQ_GROUP
401 #undef  REQ_
402 };
404 static enum request
405 get_request(const char *name)
407         int namelen = strlen(name);
408         int i;
410         for (i = 0; i < ARRAY_SIZE(req_info); i++)
411                 if (req_info[i].namelen == namelen &&
412                     !string_enum_compare(req_info[i].name, name, namelen))
413                         return req_info[i].request;
415         return REQ_NONE;
419 /*
420  * Options
421  */
423 static const char usage[] =
424 "tig " TIG_VERSION " (" __DATE__ ")\n"
425 "\n"
426 "Usage: tig        [options] [revs] [--] [paths]\n"
427 "   or: tig show   [options] [revs] [--] [paths]\n"
428 "   or: tig blame  [rev] path\n"
429 "   or: tig status\n"
430 "   or: tig <      [git command output]\n"
431 "\n"
432 "Options:\n"
433 "  -v, --version   Show version and exit\n"
434 "  -h, --help      Show help message and exit";
436 /* Option and state variables. */
437 static bool opt_date                    = TRUE;
438 static bool opt_author                  = TRUE;
439 static bool opt_line_number             = FALSE;
440 static bool opt_line_graphics           = TRUE;
441 static bool opt_rev_graph               = FALSE;
442 static bool opt_show_refs               = TRUE;
443 static int opt_num_interval             = NUMBER_INTERVAL;
444 static int opt_tab_size                 = TAB_SIZE;
445 static enum request opt_request         = REQ_VIEW_MAIN;
446 static char opt_cmd[SIZEOF_STR]         = "";
447 static char opt_path[SIZEOF_STR]        = "";
448 static char opt_file[SIZEOF_STR]        = "";
449 static char opt_ref[SIZEOF_REF]         = "";
450 static char opt_head[SIZEOF_REF]        = "";
451 static char opt_remote[SIZEOF_REF]      = "";
452 static bool opt_no_head                 = TRUE;
453 static FILE *opt_pipe                   = NULL;
454 static char opt_encoding[20]            = "UTF-8";
455 static bool opt_utf8                    = TRUE;
456 static char opt_codeset[20]             = "UTF-8";
457 static iconv_t opt_iconv                = ICONV_NONE;
458 static char opt_search[SIZEOF_STR]      = "";
459 static char opt_cdup[SIZEOF_STR]        = "";
460 static char opt_git_dir[SIZEOF_STR]     = "";
461 static signed char opt_is_inside_work_tree      = -1; /* set to TRUE or FALSE */
462 static char opt_editor[SIZEOF_STR]      = "";
464 static bool
465 parse_options(int argc, char *argv[])
467         size_t buf_size;
468         char *subcommand;
469         bool seen_dashdash = FALSE;
470         int i;
472         if (!isatty(STDIN_FILENO)) {
473                 opt_request = REQ_VIEW_PAGER;
474                 opt_pipe = stdin;
475                 return TRUE;
476         }
478         if (argc <= 1)
479                 return TRUE;
481         subcommand = argv[1];
482         if (!strcmp(subcommand, "status") || !strcmp(subcommand, "-S")) {
483                 opt_request = REQ_VIEW_STATUS;
484                 if (!strcmp(subcommand, "-S"))
485                         warn("`-S' has been deprecated; use `tig status' instead");
486                 if (argc > 2)
487                         warn("ignoring arguments after `%s'", subcommand);
488                 return TRUE;
490         } else if (!strcmp(subcommand, "blame")) {
491                 opt_request = REQ_VIEW_BLAME;
492                 if (argc <= 2 || argc > 4)
493                         die("invalid number of options to blame\n\n%s", usage);
495                 i = 2;
496                 if (argc == 4) {
497                         string_ncopy(opt_ref, argv[i], strlen(argv[i]));
498                         i++;
499                 }
501                 string_ncopy(opt_file, argv[i], strlen(argv[i]));
502                 return TRUE;
504         } else if (!strcmp(subcommand, "show")) {
505                 opt_request = REQ_VIEW_DIFF;
507         } else if (!strcmp(subcommand, "log") || !strcmp(subcommand, "diff")) {
508                 opt_request = subcommand[0] == 'l'
509                             ? REQ_VIEW_LOG : REQ_VIEW_DIFF;
510                 warn("`tig %s' has been deprecated", subcommand);
512         } else {
513                 subcommand = NULL;
514         }
516         if (!subcommand)
517                 /* XXX: This is vulnerable to the user overriding
518                  * options required for the main view parser. */
519                 string_copy(opt_cmd, "git log --no-color --pretty=raw --boundary --parents");
520         else
521                 string_format(opt_cmd, "git %s", subcommand);
523         buf_size = strlen(opt_cmd);
525         for (i = 1 + !!subcommand; i < argc; i++) {
526                 char *opt = argv[i];
528                 if (seen_dashdash || !strcmp(opt, "--")) {
529                         seen_dashdash = TRUE;
531                 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
532                         printf("tig version %s\n", TIG_VERSION);
533                         return FALSE;
535                 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
536                         printf("%s\n", usage);
537                         return FALSE;
538                 }
540                 opt_cmd[buf_size++] = ' ';
541                 buf_size = sq_quote(opt_cmd, buf_size, opt);
542                 if (buf_size >= sizeof(opt_cmd))
543                         die("command too long");
544         }
546         opt_cmd[buf_size] = 0;
548         return TRUE;
552 /*
553  * Line-oriented content detection.
554  */
556 #define LINE_INFO \
557 LINE(DIFF_HEADER,  "diff --git ",       COLOR_YELLOW,   COLOR_DEFAULT,  0), \
558 LINE(DIFF_CHUNK,   "@@",                COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
559 LINE(DIFF_ADD,     "+",                 COLOR_GREEN,    COLOR_DEFAULT,  0), \
560 LINE(DIFF_DEL,     "-",                 COLOR_RED,      COLOR_DEFAULT,  0), \
561 LINE(DIFF_INDEX,        "index ",         COLOR_BLUE,   COLOR_DEFAULT,  0), \
562 LINE(DIFF_OLDMODE,      "old file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
563 LINE(DIFF_NEWMODE,      "new file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
564 LINE(DIFF_COPY_FROM,    "copy from",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
565 LINE(DIFF_COPY_TO,      "copy to",        COLOR_YELLOW, COLOR_DEFAULT,  0), \
566 LINE(DIFF_RENAME_FROM,  "rename from",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
567 LINE(DIFF_RENAME_TO,    "rename to",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
568 LINE(DIFF_SIMILARITY,   "similarity ",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
569 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
570 LINE(DIFF_TREE,         "diff-tree ",     COLOR_BLUE,   COLOR_DEFAULT,  0), \
571 LINE(PP_AUTHOR,    "Author: ",          COLOR_CYAN,     COLOR_DEFAULT,  0), \
572 LINE(PP_COMMIT,    "Commit: ",          COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
573 LINE(PP_MERGE,     "Merge: ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
574 LINE(PP_DATE,      "Date:   ",          COLOR_YELLOW,   COLOR_DEFAULT,  0), \
575 LINE(PP_ADATE,     "AuthorDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
576 LINE(PP_CDATE,     "CommitDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
577 LINE(PP_REFS,      "Refs: ",            COLOR_RED,      COLOR_DEFAULT,  0), \
578 LINE(COMMIT,       "commit ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
579 LINE(PARENT,       "parent ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
580 LINE(TREE,         "tree ",             COLOR_BLUE,     COLOR_DEFAULT,  0), \
581 LINE(AUTHOR,       "author ",           COLOR_CYAN,     COLOR_DEFAULT,  0), \
582 LINE(COMMITTER,    "committer ",        COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
583 LINE(SIGNOFF,      "    Signed-off-by", COLOR_YELLOW,   COLOR_DEFAULT,  0), \
584 LINE(ACKED,        "    Acked-by",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
585 LINE(DEFAULT,      "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
586 LINE(CURSOR,       "",                  COLOR_WHITE,    COLOR_GREEN,    A_BOLD), \
587 LINE(STATUS,       "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
588 LINE(DELIMITER,    "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
589 LINE(DATE,         "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
590 LINE(LINE_NUMBER,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
591 LINE(TITLE_BLUR,   "",                  COLOR_WHITE,    COLOR_BLUE,     0), \
592 LINE(TITLE_FOCUS,  "",                  COLOR_WHITE,    COLOR_BLUE,     A_BOLD), \
593 LINE(MAIN_AUTHOR,  "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
594 LINE(MAIN_COMMIT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
595 LINE(MAIN_TAG,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
596 LINE(MAIN_LOCAL_TAG,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
597 LINE(MAIN_REMOTE,  "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
598 LINE(MAIN_TRACKED, "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_BOLD), \
599 LINE(MAIN_REF,     "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
600 LINE(MAIN_HEAD,    "",                  COLOR_CYAN,     COLOR_DEFAULT,  A_BOLD), \
601 LINE(MAIN_REVGRAPH,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
602 LINE(TREE_DIR,     "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
603 LINE(TREE_FILE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
604 LINE(STAT_HEAD,    "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
605 LINE(STAT_SECTION, "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
606 LINE(STAT_NONE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
607 LINE(STAT_STAGED,  "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
608 LINE(STAT_UNSTAGED,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
609 LINE(STAT_UNTRACKED,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
610 LINE(BLAME_ID,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0)
612 enum line_type {
613 #define LINE(type, line, fg, bg, attr) \
614         LINE_##type
615         LINE_INFO,
616         LINE_NONE
617 #undef  LINE
618 };
620 struct line_info {
621         const char *name;       /* Option name. */
622         int namelen;            /* Size of option name. */
623         const char *line;       /* The start of line to match. */
624         int linelen;            /* Size of string to match. */
625         int fg, bg, attr;       /* Color and text attributes for the lines. */
626 };
628 static struct line_info line_info[] = {
629 #define LINE(type, line, fg, bg, attr) \
630         { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
631         LINE_INFO
632 #undef  LINE
633 };
635 static enum line_type
636 get_line_type(char *line)
638         int linelen = strlen(line);
639         enum line_type type;
641         for (type = 0; type < ARRAY_SIZE(line_info); type++)
642                 /* Case insensitive search matches Signed-off-by lines better. */
643                 if (linelen >= line_info[type].linelen &&
644                     !strncasecmp(line_info[type].line, line, line_info[type].linelen))
645                         return type;
647         return LINE_DEFAULT;
650 static inline int
651 get_line_attr(enum line_type type)
653         assert(type < ARRAY_SIZE(line_info));
654         return COLOR_PAIR(type) | line_info[type].attr;
657 static struct line_info *
658 get_line_info(char *name)
660         size_t namelen = strlen(name);
661         enum line_type type;
663         for (type = 0; type < ARRAY_SIZE(line_info); type++)
664                 if (namelen == line_info[type].namelen &&
665                     !string_enum_compare(line_info[type].name, name, namelen))
666                         return &line_info[type];
668         return NULL;
671 static void
672 init_colors(void)
674         int default_bg = line_info[LINE_DEFAULT].bg;
675         int default_fg = line_info[LINE_DEFAULT].fg;
676         enum line_type type;
678         start_color();
680         if (assume_default_colors(default_fg, default_bg) == ERR) {
681                 default_bg = COLOR_BLACK;
682                 default_fg = COLOR_WHITE;
683         }
685         for (type = 0; type < ARRAY_SIZE(line_info); type++) {
686                 struct line_info *info = &line_info[type];
687                 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
688                 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
690                 init_pair(type, fg, bg);
691         }
694 struct line {
695         enum line_type type;
697         /* State flags */
698         unsigned int selected:1;
699         unsigned int dirty:1;
701         void *data;             /* User data */
702 };
705 /*
706  * Keys
707  */
709 struct keybinding {
710         int alias;
711         enum request request;
712         struct keybinding *next;
713 };
715 static struct keybinding default_keybindings[] = {
716         /* View switching */
717         { 'm',          REQ_VIEW_MAIN },
718         { 'd',          REQ_VIEW_DIFF },
719         { 'l',          REQ_VIEW_LOG },
720         { 't',          REQ_VIEW_TREE },
721         { 'f',          REQ_VIEW_BLOB },
722         { 'B',          REQ_VIEW_BLAME },
723         { 'p',          REQ_VIEW_PAGER },
724         { 'h',          REQ_VIEW_HELP },
725         { 'S',          REQ_VIEW_STATUS },
726         { 'c',          REQ_VIEW_STAGE },
728         /* View manipulation */
729         { 'q',          REQ_VIEW_CLOSE },
730         { KEY_TAB,      REQ_VIEW_NEXT },
731         { KEY_RETURN,   REQ_ENTER },
732         { KEY_UP,       REQ_PREVIOUS },
733         { KEY_DOWN,     REQ_NEXT },
734         { 'R',          REQ_REFRESH },
735         { KEY_F(5),     REQ_REFRESH },
736         { 'O',          REQ_MAXIMIZE },
738         /* Cursor navigation */
739         { 'k',          REQ_MOVE_UP },
740         { 'j',          REQ_MOVE_DOWN },
741         { KEY_HOME,     REQ_MOVE_FIRST_LINE },
742         { KEY_END,      REQ_MOVE_LAST_LINE },
743         { KEY_NPAGE,    REQ_MOVE_PAGE_DOWN },
744         { ' ',          REQ_MOVE_PAGE_DOWN },
745         { KEY_PPAGE,    REQ_MOVE_PAGE_UP },
746         { 'b',          REQ_MOVE_PAGE_UP },
747         { '-',          REQ_MOVE_PAGE_UP },
749         /* Scrolling */
750         { KEY_IC,       REQ_SCROLL_LINE_UP },
751         { KEY_DC,       REQ_SCROLL_LINE_DOWN },
752         { 'w',          REQ_SCROLL_PAGE_UP },
753         { 's',          REQ_SCROLL_PAGE_DOWN },
755         /* Searching */
756         { '/',          REQ_SEARCH },
757         { '?',          REQ_SEARCH_BACK },
758         { 'n',          REQ_FIND_NEXT },
759         { 'N',          REQ_FIND_PREV },
761         /* Misc */
762         { 'Q',          REQ_QUIT },
763         { 'z',          REQ_STOP_LOADING },
764         { 'v',          REQ_SHOW_VERSION },
765         { 'r',          REQ_SCREEN_REDRAW },
766         { '.',          REQ_TOGGLE_LINENO },
767         { 'D',          REQ_TOGGLE_DATE },
768         { 'A',          REQ_TOGGLE_AUTHOR },
769         { 'g',          REQ_TOGGLE_REV_GRAPH },
770         { 'F',          REQ_TOGGLE_REFS },
771         { ':',          REQ_PROMPT },
772         { 'u',          REQ_STATUS_UPDATE },
773         { 'M',          REQ_STATUS_MERGE },
774         { ',',          REQ_TREE_PARENT },
775         { 'e',          REQ_EDIT },
777         /* Using the ncurses SIGWINCH handler. */
778         { KEY_RESIZE,   REQ_SCREEN_RESIZE },
779 };
781 #define KEYMAP_INFO \
782         KEYMAP_(GENERIC), \
783         KEYMAP_(MAIN), \
784         KEYMAP_(DIFF), \
785         KEYMAP_(LOG), \
786         KEYMAP_(TREE), \
787         KEYMAP_(BLOB), \
788         KEYMAP_(BLAME), \
789         KEYMAP_(PAGER), \
790         KEYMAP_(HELP), \
791         KEYMAP_(STATUS), \
792         KEYMAP_(STAGE)
794 enum keymap {
795 #define KEYMAP_(name) KEYMAP_##name
796         KEYMAP_INFO
797 #undef  KEYMAP_
798 };
800 static struct int_map keymap_table[] = {
801 #define KEYMAP_(name) { #name, STRING_SIZE(#name), KEYMAP_##name }
802         KEYMAP_INFO
803 #undef  KEYMAP_
804 };
806 #define set_keymap(map, name) \
807         set_from_int_map(keymap_table, ARRAY_SIZE(keymap_table), map, name, strlen(name))
809 static struct keybinding *keybindings[ARRAY_SIZE(keymap_table)];
811 static void
812 add_keybinding(enum keymap keymap, enum request request, int key)
814         struct keybinding *keybinding;
816         keybinding = calloc(1, sizeof(*keybinding));
817         if (!keybinding)
818                 die("Failed to allocate keybinding");
820         keybinding->alias = key;
821         keybinding->request = request;
822         keybinding->next = keybindings[keymap];
823         keybindings[keymap] = keybinding;
826 /* Looks for a key binding first in the given map, then in the generic map, and
827  * lastly in the default keybindings. */
828 static enum request
829 get_keybinding(enum keymap keymap, int key)
831         struct keybinding *kbd;
832         int i;
834         for (kbd = keybindings[keymap]; kbd; kbd = kbd->next)
835                 if (kbd->alias == key)
836                         return kbd->request;
838         for (kbd = keybindings[KEYMAP_GENERIC]; kbd; kbd = kbd->next)
839                 if (kbd->alias == key)
840                         return kbd->request;
842         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
843                 if (default_keybindings[i].alias == key)
844                         return default_keybindings[i].request;
846         return (enum request) key;
850 struct key {
851         char *name;
852         int value;
853 };
855 static struct key key_table[] = {
856         { "Enter",      KEY_RETURN },
857         { "Space",      ' ' },
858         { "Backspace",  KEY_BACKSPACE },
859         { "Tab",        KEY_TAB },
860         { "Escape",     KEY_ESC },
861         { "Left",       KEY_LEFT },
862         { "Right",      KEY_RIGHT },
863         { "Up",         KEY_UP },
864         { "Down",       KEY_DOWN },
865         { "Insert",     KEY_IC },
866         { "Delete",     KEY_DC },
867         { "Hash",       '#' },
868         { "Home",       KEY_HOME },
869         { "End",        KEY_END },
870         { "PageUp",     KEY_PPAGE },
871         { "PageDown",   KEY_NPAGE },
872         { "F1",         KEY_F(1) },
873         { "F2",         KEY_F(2) },
874         { "F3",         KEY_F(3) },
875         { "F4",         KEY_F(4) },
876         { "F5",         KEY_F(5) },
877         { "F6",         KEY_F(6) },
878         { "F7",         KEY_F(7) },
879         { "F8",         KEY_F(8) },
880         { "F9",         KEY_F(9) },
881         { "F10",        KEY_F(10) },
882         { "F11",        KEY_F(11) },
883         { "F12",        KEY_F(12) },
884 };
886 static int
887 get_key_value(const char *name)
889         int i;
891         for (i = 0; i < ARRAY_SIZE(key_table); i++)
892                 if (!strcasecmp(key_table[i].name, name))
893                         return key_table[i].value;
895         if (strlen(name) == 1 && isprint(*name))
896                 return (int) *name;
898         return ERR;
901 static char *
902 get_key_name(int key_value)
904         static char key_char[] = "'X'";
905         char *seq = NULL;
906         int key;
908         for (key = 0; key < ARRAY_SIZE(key_table); key++)
909                 if (key_table[key].value == key_value)
910                         seq = key_table[key].name;
912         if (seq == NULL &&
913             key_value < 127 &&
914             isprint(key_value)) {
915                 key_char[1] = (char) key_value;
916                 seq = key_char;
917         }
919         return seq ? seq : "'?'";
922 static char *
923 get_key(enum request request)
925         static char buf[BUFSIZ];
926         size_t pos = 0;
927         char *sep = "";
928         int i;
930         buf[pos] = 0;
932         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
933                 struct keybinding *keybinding = &default_keybindings[i];
935                 if (keybinding->request != request)
936                         continue;
938                 if (!string_format_from(buf, &pos, "%s%s", sep,
939                                         get_key_name(keybinding->alias)))
940                         return "Too many keybindings!";
941                 sep = ", ";
942         }
944         return buf;
947 struct run_request {
948         enum keymap keymap;
949         int key;
950         char cmd[SIZEOF_STR];
951 };
953 static struct run_request *run_request;
954 static size_t run_requests;
956 static enum request
957 add_run_request(enum keymap keymap, int key, int argc, char **argv)
959         struct run_request *tmp;
960         struct run_request req = { keymap, key };
961         size_t bufpos;
963         for (bufpos = 0; argc > 0; argc--, argv++)
964                 if (!string_format_from(req.cmd, &bufpos, "%s ", *argv))
965                         return REQ_NONE;
967         req.cmd[bufpos - 1] = 0;
969         tmp = realloc(run_request, (run_requests + 1) * sizeof(*run_request));
970         if (!tmp)
971                 return REQ_NONE;
973         run_request = tmp;
974         run_request[run_requests++] = req;
976         return REQ_NONE + run_requests;
979 static struct run_request *
980 get_run_request(enum request request)
982         if (request <= REQ_NONE)
983                 return NULL;
984         return &run_request[request - REQ_NONE - 1];
987 static void
988 add_builtin_run_requests(void)
990         struct {
991                 enum keymap keymap;
992                 int key;
993                 char *argv[1];
994         } reqs[] = {
995                 { KEYMAP_MAIN,    'C', { "git cherry-pick %(commit)" } },
996                 { KEYMAP_GENERIC, 'G', { "git gc" } },
997         };
998         int i;
1000         for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1001                 enum request req;
1003                 req = add_run_request(reqs[i].keymap, reqs[i].key, 1, reqs[i].argv);
1004                 if (req != REQ_NONE)
1005                         add_keybinding(reqs[i].keymap, req, reqs[i].key);
1006         }
1009 /*
1010  * User config file handling.
1011  */
1013 static struct int_map color_map[] = {
1014 #define COLOR_MAP(name) { #name, STRING_SIZE(#name), COLOR_##name }
1015         COLOR_MAP(DEFAULT),
1016         COLOR_MAP(BLACK),
1017         COLOR_MAP(BLUE),
1018         COLOR_MAP(CYAN),
1019         COLOR_MAP(GREEN),
1020         COLOR_MAP(MAGENTA),
1021         COLOR_MAP(RED),
1022         COLOR_MAP(WHITE),
1023         COLOR_MAP(YELLOW),
1024 };
1026 #define set_color(color, name) \
1027         set_from_int_map(color_map, ARRAY_SIZE(color_map), color, name, strlen(name))
1029 static struct int_map attr_map[] = {
1030 #define ATTR_MAP(name) { #name, STRING_SIZE(#name), A_##name }
1031         ATTR_MAP(NORMAL),
1032         ATTR_MAP(BLINK),
1033         ATTR_MAP(BOLD),
1034         ATTR_MAP(DIM),
1035         ATTR_MAP(REVERSE),
1036         ATTR_MAP(STANDOUT),
1037         ATTR_MAP(UNDERLINE),
1038 };
1040 #define set_attribute(attr, name) \
1041         set_from_int_map(attr_map, ARRAY_SIZE(attr_map), attr, name, strlen(name))
1043 static int   config_lineno;
1044 static bool  config_errors;
1045 static char *config_msg;
1047 /* Wants: object fgcolor bgcolor [attr] */
1048 static int
1049 option_color_command(int argc, char *argv[])
1051         struct line_info *info;
1053         if (argc != 3 && argc != 4) {
1054                 config_msg = "Wrong number of arguments given to color command";
1055                 return ERR;
1056         }
1058         info = get_line_info(argv[0]);
1059         if (!info) {
1060                 if (!string_enum_compare(argv[0], "main-delim", strlen("main-delim"))) {
1061                         info = get_line_info("delimiter");
1063                 } else if (!string_enum_compare(argv[0], "main-date", strlen("main-date"))) {
1064                         info = get_line_info("date");
1066                 } else {
1067                         config_msg = "Unknown color name";
1068                         return ERR;
1069                 }
1070         }
1072         if (set_color(&info->fg, argv[1]) == ERR ||
1073             set_color(&info->bg, argv[2]) == ERR) {
1074                 config_msg = "Unknown color";
1075                 return ERR;
1076         }
1078         if (argc == 4 && set_attribute(&info->attr, argv[3]) == ERR) {
1079                 config_msg = "Unknown attribute";
1080                 return ERR;
1081         }
1083         return OK;
1086 static bool parse_bool(const char *s)
1088         return (!strcmp(s, "1") || !strcmp(s, "true") ||
1089                 !strcmp(s, "yes")) ? TRUE : FALSE;
1092 /* Wants: name = value */
1093 static int
1094 option_set_command(int argc, char *argv[])
1096         if (argc != 3) {
1097                 config_msg = "Wrong number of arguments given to set command";
1098                 return ERR;
1099         }
1101         if (strcmp(argv[1], "=")) {
1102                 config_msg = "No value assigned";
1103                 return ERR;
1104         }
1106         if (!strcmp(argv[0], "show-author")) {
1107                 opt_author = parse_bool(argv[2]);
1108                 return OK;
1109         }
1111         if (!strcmp(argv[0], "show-date")) {
1112                 opt_date = parse_bool(argv[2]);
1113                 return OK;
1114         }
1116         if (!strcmp(argv[0], "show-rev-graph")) {
1117                 opt_rev_graph = parse_bool(argv[2]);
1118                 return OK;
1119         }
1121         if (!strcmp(argv[0], "show-refs")) {
1122                 opt_show_refs = parse_bool(argv[2]);
1123                 return OK;
1124         }
1126         if (!strcmp(argv[0], "show-line-numbers")) {
1127                 opt_line_number = parse_bool(argv[2]);
1128                 return OK;
1129         }
1131         if (!strcmp(argv[0], "line-graphics")) {
1132                 opt_line_graphics = parse_bool(argv[2]);
1133                 return OK;
1134         }
1136         if (!strcmp(argv[0], "line-number-interval")) {
1137                 opt_num_interval = atoi(argv[2]);
1138                 return OK;
1139         }
1141         if (!strcmp(argv[0], "tab-size")) {
1142                 opt_tab_size = atoi(argv[2]);
1143                 return OK;
1144         }
1146         if (!strcmp(argv[0], "commit-encoding")) {
1147                 char *arg = argv[2];
1148                 int delimiter = *arg;
1149                 int i;
1151                 switch (delimiter) {
1152                 case '"':
1153                 case '\'':
1154                         for (arg++, i = 0; arg[i]; i++)
1155                                 if (arg[i] == delimiter) {
1156                                         arg[i] = 0;
1157                                         break;
1158                                 }
1159                 default:
1160                         string_ncopy(opt_encoding, arg, strlen(arg));
1161                         return OK;
1162                 }
1163         }
1165         config_msg = "Unknown variable name";
1166         return ERR;
1169 /* Wants: mode request key */
1170 static int
1171 option_bind_command(int argc, char *argv[])
1173         enum request request;
1174         int keymap;
1175         int key;
1177         if (argc < 3) {
1178                 config_msg = "Wrong number of arguments given to bind command";
1179                 return ERR;
1180         }
1182         if (set_keymap(&keymap, argv[0]) == ERR) {
1183                 config_msg = "Unknown key map";
1184                 return ERR;
1185         }
1187         key = get_key_value(argv[1]);
1188         if (key == ERR) {
1189                 config_msg = "Unknown key";
1190                 return ERR;
1191         }
1193         request = get_request(argv[2]);
1194         if (request == REQ_NONE) {
1195                 const char *obsolete[] = { "cherry-pick" };
1196                 size_t namelen = strlen(argv[2]);
1197                 int i;
1199                 for (i = 0; i < ARRAY_SIZE(obsolete); i++) {
1200                         if (namelen == strlen(obsolete[i]) &&
1201                             !string_enum_compare(obsolete[i], argv[2], namelen)) {
1202                                 config_msg = "Obsolete request name";
1203                                 return ERR;
1204                         }
1205                 }
1206         }
1207         if (request == REQ_NONE && *argv[2]++ == '!')
1208                 request = add_run_request(keymap, key, argc - 2, argv + 2);
1209         if (request == REQ_NONE) {
1210                 config_msg = "Unknown request name";
1211                 return ERR;
1212         }
1214         add_keybinding(keymap, request, key);
1216         return OK;
1219 static int
1220 set_option(char *opt, char *value)
1222         char *argv[16];
1223         int valuelen;
1224         int argc = 0;
1226         /* Tokenize */
1227         while (argc < ARRAY_SIZE(argv) && (valuelen = strcspn(value, " \t"))) {
1228                 argv[argc++] = value;
1229                 value += valuelen;
1231                 /* Nothing more to tokenize or last available token. */
1232                 if (!*value || argc >= ARRAY_SIZE(argv))
1233                         break;
1235                 *value++ = 0;
1236                 while (isspace(*value))
1237                         value++;
1238         }
1240         if (!strcmp(opt, "color"))
1241                 return option_color_command(argc, argv);
1243         if (!strcmp(opt, "set"))
1244                 return option_set_command(argc, argv);
1246         if (!strcmp(opt, "bind"))
1247                 return option_bind_command(argc, argv);
1249         config_msg = "Unknown option command";
1250         return ERR;
1253 static int
1254 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1256         int status = OK;
1258         config_lineno++;
1259         config_msg = "Internal error";
1261         /* Check for comment markers, since read_properties() will
1262          * only ensure opt and value are split at first " \t". */
1263         optlen = strcspn(opt, "#");
1264         if (optlen == 0)
1265                 return OK;
1267         if (opt[optlen] != 0) {
1268                 config_msg = "No option value";
1269                 status = ERR;
1271         }  else {
1272                 /* Look for comment endings in the value. */
1273                 size_t len = strcspn(value, "#");
1275                 if (len < valuelen) {
1276                         valuelen = len;
1277                         value[valuelen] = 0;
1278                 }
1280                 status = set_option(opt, value);
1281         }
1283         if (status == ERR) {
1284                 fprintf(stderr, "Error on line %d, near '%.*s': %s\n",
1285                         config_lineno, (int) optlen, opt, config_msg);
1286                 config_errors = TRUE;
1287         }
1289         /* Always keep going if errors are encountered. */
1290         return OK;
1293 static void
1294 load_option_file(const char *path)
1296         FILE *file;
1298         /* It's ok that the file doesn't exist. */
1299         file = fopen(path, "r");
1300         if (!file)
1301                 return;
1303         config_lineno = 0;
1304         config_errors = FALSE;
1306         if (read_properties(file, " \t", read_option) == ERR ||
1307             config_errors == TRUE)
1308                 fprintf(stderr, "Errors while loading %s.\n", path);
1311 static int
1312 load_options(void)
1314         char *home = getenv("HOME");
1315         char *tigrc_user = getenv("TIGRC_USER");
1316         char *tigrc_system = getenv("TIGRC_SYSTEM");
1317         char buf[SIZEOF_STR];
1319         add_builtin_run_requests();
1321         if (!tigrc_system) {
1322                 if (!string_format(buf, "%s/tigrc", SYSCONFDIR))
1323                         return ERR;
1324                 tigrc_system = buf;
1325         }
1326         load_option_file(tigrc_system);
1328         if (!tigrc_user) {
1329                 if (!home || !string_format(buf, "%s/.tigrc", home))
1330                         return ERR;
1331                 tigrc_user = buf;
1332         }
1333         load_option_file(tigrc_user);
1335         return OK;
1339 /*
1340  * The viewer
1341  */
1343 struct view;
1344 struct view_ops;
1346 /* The display array of active views and the index of the current view. */
1347 static struct view *display[2];
1348 static unsigned int current_view;
1350 /* Reading from the prompt? */
1351 static bool input_mode = FALSE;
1353 #define foreach_displayed_view(view, i) \
1354         for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1356 #define displayed_views()       (display[1] != NULL ? 2 : 1)
1358 /* Current head and commit ID */
1359 static char ref_blob[SIZEOF_REF]        = "";
1360 static char ref_commit[SIZEOF_REF]      = "HEAD";
1361 static char ref_head[SIZEOF_REF]        = "HEAD";
1363 struct view {
1364         const char *name;       /* View name */
1365         const char *cmd_fmt;    /* Default command line format */
1366         const char *cmd_env;    /* Command line set via environment */
1367         const char *id;         /* Points to either of ref_{head,commit,blob} */
1369         struct view_ops *ops;   /* View operations */
1371         enum keymap keymap;     /* What keymap does this view have */
1372         bool git_dir;           /* Whether the view requires a git directory. */
1374         char cmd[SIZEOF_STR];   /* Command buffer */
1375         char ref[SIZEOF_REF];   /* Hovered commit reference */
1376         char vid[SIZEOF_REF];   /* View ID. Set to id member when updating. */
1378         int height, width;      /* The width and height of the main window */
1379         WINDOW *win;            /* The main window */
1380         WINDOW *title;          /* The title window living below the main window */
1382         /* Navigation */
1383         unsigned long offset;   /* Offset of the window top */
1384         unsigned long lineno;   /* Current line number */
1386         /* Searching */
1387         char grep[SIZEOF_STR];  /* Search string */
1388         regex_t *regex;         /* Pre-compiled regex */
1390         /* If non-NULL, points to the view that opened this view. If this view
1391          * is closed tig will switch back to the parent view. */
1392         struct view *parent;
1394         /* Buffering */
1395         size_t lines;           /* Total number of lines */
1396         struct line *line;      /* Line index */
1397         size_t line_alloc;      /* Total number of allocated lines */
1398         size_t line_size;       /* Total number of used lines */
1399         unsigned int digits;    /* Number of digits in the lines member. */
1401         /* Drawing */
1402         struct line *curline;   /* Line currently being drawn. */
1403         enum line_type curtype; /* Attribute currently used for drawing. */
1405         /* Loading */
1406         FILE *pipe;
1407         time_t start_time;
1408 };
1410 struct view_ops {
1411         /* What type of content being displayed. Used in the title bar. */
1412         const char *type;
1413         /* Open and reads in all view content. */
1414         bool (*open)(struct view *view);
1415         /* Read one line; updates view->line. */
1416         bool (*read)(struct view *view, char *data);
1417         /* Draw one line; @lineno must be < view->height. */
1418         bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
1419         /* Depending on view handle a special requests. */
1420         enum request (*request)(struct view *view, enum request request, struct line *line);
1421         /* Search for regex in a line. */
1422         bool (*grep)(struct view *view, struct line *line);
1423         /* Select line */
1424         void (*select)(struct view *view, struct line *line);
1425 };
1427 static struct view_ops pager_ops;
1428 static struct view_ops main_ops;
1429 static struct view_ops tree_ops;
1430 static struct view_ops blob_ops;
1431 static struct view_ops blame_ops;
1432 static struct view_ops help_ops;
1433 static struct view_ops status_ops;
1434 static struct view_ops stage_ops;
1436 #define VIEW_STR(name, cmd, env, ref, ops, map, git) \
1437         { name, cmd, #env, ref, ops, map, git }
1439 #define VIEW_(id, name, ops, git, ref) \
1440         VIEW_STR(name, TIG_##id##_CMD,  TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
1443 static struct view views[] = {
1444         VIEW_(MAIN,   "main",   &main_ops,   TRUE,  ref_head),
1445         VIEW_(DIFF,   "diff",   &pager_ops,  TRUE,  ref_commit),
1446         VIEW_(LOG,    "log",    &pager_ops,  TRUE,  ref_head),
1447         VIEW_(TREE,   "tree",   &tree_ops,   TRUE,  ref_commit),
1448         VIEW_(BLOB,   "blob",   &blob_ops,   TRUE,  ref_blob),
1449         VIEW_(BLAME,  "blame",  &blame_ops,  TRUE,  ref_commit),
1450         VIEW_(HELP,   "help",   &help_ops,   FALSE, ""),
1451         VIEW_(PAGER,  "pager",  &pager_ops,  FALSE, "stdin"),
1452         VIEW_(STATUS, "status", &status_ops, TRUE,  ""),
1453         VIEW_(STAGE,  "stage",  &stage_ops,  TRUE,  ""),
1454 };
1456 #define VIEW(req)       (&views[(req) - REQ_OFFSET - 1])
1457 #define VIEW_REQ(view)  ((view) - views + REQ_OFFSET + 1)
1459 #define foreach_view(view, i) \
1460         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1462 #define view_is_displayed(view) \
1463         (view == display[0] || view == display[1])
1466 enum line_graphic {
1467         LINE_GRAPHIC_VLINE,
1468 };
1470 static int line_graphics[] = {
1471         /* LINE_GRAPHIC_VLINE: */ '|'
1472 };
1474 static inline void
1475 set_view_attr(struct view *view, enum line_type type)
1477         if (!view->curline->selected && view->curtype != type) {
1478                 wattrset(view->win, get_line_attr(type));
1479                 wchgat(view->win, -1, 0, type, NULL);
1480                 view->curtype = type;
1481         }
1484 static int
1485 draw_text(struct view *view, enum line_type type, const char *string,
1486           int max_len, bool use_tilde)
1488         int len = 0;
1489         int trimmed = FALSE;
1491         if (max_len <= 0)
1492                 return 0;
1494         if (opt_utf8) {
1495                 len = utf8_length(string, max_len, &trimmed, use_tilde);
1496         } else {
1497                 len = strlen(string);
1498                 if (len > max_len) {
1499                         if (use_tilde) {
1500                                 max_len -= 1;
1501                         }
1502                         len = max_len;
1503                         trimmed = TRUE;
1504                 }
1505         }
1507         set_view_attr(view, type);
1508         waddnstr(view->win, string, len);
1509         if (trimmed && use_tilde) {
1510                 set_view_attr(view, LINE_DELIMITER);
1511                 waddch(view->win, '~');
1512                 len++;
1513         }
1515         return len;
1518 static int
1519 draw_lineno(struct view *view, unsigned int lineno, int max)
1521         static char fmt[] = "%1ld";
1522         char number[10] = "          ";
1523         int digits3 = view->digits < 3 ? 3 : view->digits;
1524         int max_number = MIN(digits3, STRING_SIZE(number));
1525         bool showtrimmed = FALSE;
1526         int col;
1528         lineno += view->offset + 1;
1529         if (lineno == 1 || (lineno % opt_num_interval) == 0) {
1530                 if (view->digits <= 9)
1531                         fmt[1] = '0' + digits3;
1533                 if (!string_format(number, fmt, lineno))
1534                         number[0] = 0;
1535                 showtrimmed = TRUE;
1536         }
1538         if (max < max_number)
1539                 max_number = max;
1541         col = draw_text(view, LINE_LINE_NUMBER, number, max_number, showtrimmed);
1542         if (col < max) {
1543                 set_view_attr(view, LINE_DEFAULT);
1544                 waddch(view->win, line_graphics[LINE_GRAPHIC_VLINE]);
1545                 col++;
1546         }
1547         if (col < max) {
1548                 waddch(view->win, ' ');
1549                 col++;
1550         }
1552         return col;
1555 static int
1556 draw_date(struct view *view, struct tm *time, int max)
1558         char buf[DATE_COLS];
1559         int col;
1560         int timelen = 0;
1562         if (max > DATE_COLS)
1563                 max = DATE_COLS;
1564         if (time)
1565                 timelen = strftime(buf, sizeof(buf), DATE_FORMAT, time);
1566         if (!timelen) {
1567                 memset(buf, ' ', sizeof(buf) - 1);
1568                 buf[sizeof(buf) - 1] = 0;
1569         }
1571         col = draw_text(view, LINE_DATE, buf, max, FALSE);
1572         if (col < max) {
1573                 set_view_attr(view, LINE_DEFAULT);
1574                 waddch(view->win, ' ');
1575                 col++;
1576         }
1578         return col;
1581 static bool
1582 draw_view_line(struct view *view, unsigned int lineno)
1584         struct line *line;
1585         bool selected = (view->offset + lineno == view->lineno);
1586         bool draw_ok;
1588         assert(view_is_displayed(view));
1590         if (view->offset + lineno >= view->lines)
1591                 return FALSE;
1593         line = &view->line[view->offset + lineno];
1595         wmove(view->win, lineno, 0);
1596         view->curline = line;
1597         view->curtype = LINE_NONE;
1598         line->selected = FALSE;
1600         if (selected) {
1601                 set_view_attr(view, LINE_CURSOR);
1602                 line->selected = TRUE;
1603                 view->ops->select(view, line);
1604         } else if (line->selected) {
1605                 wclrtoeol(view->win);
1606         }
1608         scrollok(view->win, FALSE);
1609         draw_ok = view->ops->draw(view, line, lineno);
1610         scrollok(view->win, TRUE);
1612         return draw_ok;
1615 static void
1616 redraw_view_dirty(struct view *view)
1618         bool dirty = FALSE;
1619         int lineno;
1621         for (lineno = 0; lineno < view->height; lineno++) {
1622                 struct line *line = &view->line[view->offset + lineno];
1624                 if (!line->dirty)
1625                         continue;
1626                 line->dirty = 0;
1627                 dirty = TRUE;
1628                 if (!draw_view_line(view, lineno))
1629                         break;
1630         }
1632         if (!dirty)
1633                 return;
1634         redrawwin(view->win);
1635         if (input_mode)
1636                 wnoutrefresh(view->win);
1637         else
1638                 wrefresh(view->win);
1641 static void
1642 redraw_view_from(struct view *view, int lineno)
1644         assert(0 <= lineno && lineno < view->height);
1646         for (; lineno < view->height; lineno++) {
1647                 if (!draw_view_line(view, lineno))
1648                         break;
1649         }
1651         redrawwin(view->win);
1652         if (input_mode)
1653                 wnoutrefresh(view->win);
1654         else
1655                 wrefresh(view->win);
1658 static void
1659 redraw_view(struct view *view)
1661         wclear(view->win);
1662         redraw_view_from(view, 0);
1666 static void
1667 update_view_title(struct view *view)
1669         char buf[SIZEOF_STR];
1670         char state[SIZEOF_STR];
1671         size_t bufpos = 0, statelen = 0;
1673         assert(view_is_displayed(view));
1675         if (view != VIEW(REQ_VIEW_STATUS) && (view->lines || view->pipe)) {
1676                 unsigned int view_lines = view->offset + view->height;
1677                 unsigned int lines = view->lines
1678                                    ? MIN(view_lines, view->lines) * 100 / view->lines
1679                                    : 0;
1681                 string_format_from(state, &statelen, "- %s %d of %d (%d%%)",
1682                                    view->ops->type,
1683                                    view->lineno + 1,
1684                                    view->lines,
1685                                    lines);
1687                 if (view->pipe) {
1688                         time_t secs = time(NULL) - view->start_time;
1690                         /* Three git seconds are a long time ... */
1691                         if (secs > 2)
1692                                 string_format_from(state, &statelen, " %lds", secs);
1693                 }
1694         }
1696         string_format_from(buf, &bufpos, "[%s]", view->name);
1697         if (*view->ref && bufpos < view->width) {
1698                 size_t refsize = strlen(view->ref);
1699                 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
1701                 if (minsize < view->width)
1702                         refsize = view->width - minsize + 7;
1703                 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
1704         }
1706         if (statelen && bufpos < view->width) {
1707                 string_format_from(buf, &bufpos, " %s", state);
1708         }
1710         if (view == display[current_view])
1711                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
1712         else
1713                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
1715         mvwaddnstr(view->title, 0, 0, buf, bufpos);
1716         wclrtoeol(view->title);
1717         wmove(view->title, 0, view->width - 1);
1719         if (input_mode)
1720                 wnoutrefresh(view->title);
1721         else
1722                 wrefresh(view->title);
1725 static void
1726 resize_display(void)
1728         int offset, i;
1729         struct view *base = display[0];
1730         struct view *view = display[1] ? display[1] : display[0];
1732         /* Setup window dimensions */
1734         getmaxyx(stdscr, base->height, base->width);
1736         /* Make room for the status window. */
1737         base->height -= 1;
1739         if (view != base) {
1740                 /* Horizontal split. */
1741                 view->width   = base->width;
1742                 view->height  = SCALE_SPLIT_VIEW(base->height);
1743                 base->height -= view->height;
1745                 /* Make room for the title bar. */
1746                 view->height -= 1;
1747         }
1749         /* Make room for the title bar. */
1750         base->height -= 1;
1752         offset = 0;
1754         foreach_displayed_view (view, i) {
1755                 if (!view->win) {
1756                         view->win = newwin(view->height, 0, offset, 0);
1757                         if (!view->win)
1758                                 die("Failed to create %s view", view->name);
1760                         scrollok(view->win, TRUE);
1762                         view->title = newwin(1, 0, offset + view->height, 0);
1763                         if (!view->title)
1764                                 die("Failed to create title window");
1766                 } else {
1767                         wresize(view->win, view->height, view->width);
1768                         mvwin(view->win,   offset, 0);
1769                         mvwin(view->title, offset + view->height, 0);
1770                 }
1772                 offset += view->height + 1;
1773         }
1776 static void
1777 redraw_display(void)
1779         struct view *view;
1780         int i;
1782         foreach_displayed_view (view, i) {
1783                 redraw_view(view);
1784                 update_view_title(view);
1785         }
1788 static void
1789 update_display_cursor(struct view *view)
1791         /* Move the cursor to the right-most column of the cursor line.
1792          *
1793          * XXX: This could turn out to be a bit expensive, but it ensures that
1794          * the cursor does not jump around. */
1795         if (view->lines) {
1796                 wmove(view->win, view->lineno - view->offset, view->width - 1);
1797                 wrefresh(view->win);
1798         }
1801 /*
1802  * Navigation
1803  */
1805 /* Scrolling backend */
1806 static void
1807 do_scroll_view(struct view *view, int lines)
1809         bool redraw_current_line = FALSE;
1811         /* The rendering expects the new offset. */
1812         view->offset += lines;
1814         assert(0 <= view->offset && view->offset < view->lines);
1815         assert(lines);
1817         /* Move current line into the view. */
1818         if (view->lineno < view->offset) {
1819                 view->lineno = view->offset;
1820                 redraw_current_line = TRUE;
1821         } else if (view->lineno >= view->offset + view->height) {
1822                 view->lineno = view->offset + view->height - 1;
1823                 redraw_current_line = TRUE;
1824         }
1826         assert(view->offset <= view->lineno && view->lineno < view->lines);
1828         /* Redraw the whole screen if scrolling is pointless. */
1829         if (view->height < ABS(lines)) {
1830                 redraw_view(view);
1832         } else {
1833                 int line = lines > 0 ? view->height - lines : 0;
1834                 int end = line + ABS(lines);
1836                 wscrl(view->win, lines);
1838                 for (; line < end; line++) {
1839                         if (!draw_view_line(view, line))
1840                                 break;
1841                 }
1843                 if (redraw_current_line)
1844                         draw_view_line(view, view->lineno - view->offset);
1845         }
1847         redrawwin(view->win);
1848         wrefresh(view->win);
1849         report("");
1852 /* Scroll frontend */
1853 static void
1854 scroll_view(struct view *view, enum request request)
1856         int lines = 1;
1858         assert(view_is_displayed(view));
1860         switch (request) {
1861         case REQ_SCROLL_PAGE_DOWN:
1862                 lines = view->height;
1863         case REQ_SCROLL_LINE_DOWN:
1864                 if (view->offset + lines > view->lines)
1865                         lines = view->lines - view->offset;
1867                 if (lines == 0 || view->offset + view->height >= view->lines) {
1868                         report("Cannot scroll beyond the last line");
1869                         return;
1870                 }
1871                 break;
1873         case REQ_SCROLL_PAGE_UP:
1874                 lines = view->height;
1875         case REQ_SCROLL_LINE_UP:
1876                 if (lines > view->offset)
1877                         lines = view->offset;
1879                 if (lines == 0) {
1880                         report("Cannot scroll beyond the first line");
1881                         return;
1882                 }
1884                 lines = -lines;
1885                 break;
1887         default:
1888                 die("request %d not handled in switch", request);
1889         }
1891         do_scroll_view(view, lines);
1894 /* Cursor moving */
1895 static void
1896 move_view(struct view *view, enum request request)
1898         int scroll_steps = 0;
1899         int steps;
1901         switch (request) {
1902         case REQ_MOVE_FIRST_LINE:
1903                 steps = -view->lineno;
1904                 break;
1906         case REQ_MOVE_LAST_LINE:
1907                 steps = view->lines - view->lineno - 1;
1908                 break;
1910         case REQ_MOVE_PAGE_UP:
1911                 steps = view->height > view->lineno
1912                       ? -view->lineno : -view->height;
1913                 break;
1915         case REQ_MOVE_PAGE_DOWN:
1916                 steps = view->lineno + view->height >= view->lines
1917                       ? view->lines - view->lineno - 1 : view->height;
1918                 break;
1920         case REQ_MOVE_UP:
1921                 steps = -1;
1922                 break;
1924         case REQ_MOVE_DOWN:
1925                 steps = 1;
1926                 break;
1928         default:
1929                 die("request %d not handled in switch", request);
1930         }
1932         if (steps <= 0 && view->lineno == 0) {
1933                 report("Cannot move beyond the first line");
1934                 return;
1936         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
1937                 report("Cannot move beyond the last line");
1938                 return;
1939         }
1941         /* Move the current line */
1942         view->lineno += steps;
1943         assert(0 <= view->lineno && view->lineno < view->lines);
1945         /* Check whether the view needs to be scrolled */
1946         if (view->lineno < view->offset ||
1947             view->lineno >= view->offset + view->height) {
1948                 scroll_steps = steps;
1949                 if (steps < 0 && -steps > view->offset) {
1950                         scroll_steps = -view->offset;
1952                 } else if (steps > 0) {
1953                         if (view->lineno == view->lines - 1 &&
1954                             view->lines > view->height) {
1955                                 scroll_steps = view->lines - view->offset - 1;
1956                                 if (scroll_steps >= view->height)
1957                                         scroll_steps -= view->height - 1;
1958                         }
1959                 }
1960         }
1962         if (!view_is_displayed(view)) {
1963                 view->offset += scroll_steps;
1964                 assert(0 <= view->offset && view->offset < view->lines);
1965                 view->ops->select(view, &view->line[view->lineno]);
1966                 return;
1967         }
1969         /* Repaint the old "current" line if we be scrolling */
1970         if (ABS(steps) < view->height)
1971                 draw_view_line(view, view->lineno - steps - view->offset);
1973         if (scroll_steps) {
1974                 do_scroll_view(view, scroll_steps);
1975                 return;
1976         }
1978         /* Draw the current line */
1979         draw_view_line(view, view->lineno - view->offset);
1981         redrawwin(view->win);
1982         wrefresh(view->win);
1983         report("");
1987 /*
1988  * Searching
1989  */
1991 static void search_view(struct view *view, enum request request);
1993 static bool
1994 find_next_line(struct view *view, unsigned long lineno, struct line *line)
1996         assert(view_is_displayed(view));
1998         if (!view->ops->grep(view, line))
1999                 return FALSE;
2001         if (lineno - view->offset >= view->height) {
2002                 view->offset = lineno;
2003                 view->lineno = lineno;
2004                 redraw_view(view);
2006         } else {
2007                 unsigned long old_lineno = view->lineno - view->offset;
2009                 view->lineno = lineno;
2010                 draw_view_line(view, old_lineno);
2012                 draw_view_line(view, view->lineno - view->offset);
2013                 redrawwin(view->win);
2014                 wrefresh(view->win);
2015         }
2017         report("Line %ld matches '%s'", lineno + 1, view->grep);
2018         return TRUE;
2021 static void
2022 find_next(struct view *view, enum request request)
2024         unsigned long lineno = view->lineno;
2025         int direction;
2027         if (!*view->grep) {
2028                 if (!*opt_search)
2029                         report("No previous search");
2030                 else
2031                         search_view(view, request);
2032                 return;
2033         }
2035         switch (request) {
2036         case REQ_SEARCH:
2037         case REQ_FIND_NEXT:
2038                 direction = 1;
2039                 break;
2041         case REQ_SEARCH_BACK:
2042         case REQ_FIND_PREV:
2043                 direction = -1;
2044                 break;
2046         default:
2047                 return;
2048         }
2050         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2051                 lineno += direction;
2053         /* Note, lineno is unsigned long so will wrap around in which case it
2054          * will become bigger than view->lines. */
2055         for (; lineno < view->lines; lineno += direction) {
2056                 struct line *line = &view->line[lineno];
2058                 if (find_next_line(view, lineno, line))
2059                         return;
2060         }
2062         report("No match found for '%s'", view->grep);
2065 static void
2066 search_view(struct view *view, enum request request)
2068         int regex_err;
2070         if (view->regex) {
2071                 regfree(view->regex);
2072                 *view->grep = 0;
2073         } else {
2074                 view->regex = calloc(1, sizeof(*view->regex));
2075                 if (!view->regex)
2076                         return;
2077         }
2079         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2080         if (regex_err != 0) {
2081                 char buf[SIZEOF_STR] = "unknown error";
2083                 regerror(regex_err, view->regex, buf, sizeof(buf));
2084                 report("Search failed: %s", buf);
2085                 return;
2086         }
2088         string_copy(view->grep, opt_search);
2090         find_next(view, request);
2093 /*
2094  * Incremental updating
2095  */
2097 static void
2098 end_update(struct view *view)
2100         if (!view->pipe)
2101                 return;
2102         set_nonblocking_input(FALSE);
2103         if (view->pipe == stdin)
2104                 fclose(view->pipe);
2105         else
2106                 pclose(view->pipe);
2107         view->pipe = NULL;
2110 static bool
2111 begin_update(struct view *view)
2113         if (view->pipe)
2114                 end_update(view);
2116         if (opt_cmd[0]) {
2117                 string_copy(view->cmd, opt_cmd);
2118                 opt_cmd[0] = 0;
2119                 /* When running random commands, initially show the
2120                  * command in the title. However, it maybe later be
2121                  * overwritten if a commit line is selected. */
2122                 if (view == VIEW(REQ_VIEW_PAGER))
2123                         string_copy(view->ref, view->cmd);
2124                 else
2125                         view->ref[0] = 0;
2127         } else if (view == VIEW(REQ_VIEW_TREE)) {
2128                 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
2129                 char path[SIZEOF_STR];
2131                 if (strcmp(view->vid, view->id))
2132                         opt_path[0] = path[0] = 0;
2133                 else if (sq_quote(path, 0, opt_path) >= sizeof(path))
2134                         return FALSE;
2136                 if (!string_format(view->cmd, format, view->id, path))
2137                         return FALSE;
2139         } else {
2140                 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
2141                 const char *id = view->id;
2143                 if (!string_format(view->cmd, format, id, id, id, id, id))
2144                         return FALSE;
2146                 /* Put the current ref_* value to the view title ref
2147                  * member. This is needed by the blob view. Most other
2148                  * views sets it automatically after loading because the
2149                  * first line is a commit line. */
2150                 string_copy_rev(view->ref, view->id);
2151         }
2153         /* Special case for the pager view. */
2154         if (opt_pipe) {
2155                 view->pipe = opt_pipe;
2156                 opt_pipe = NULL;
2157         } else {
2158                 view->pipe = popen(view->cmd, "r");
2159         }
2161         if (!view->pipe)
2162                 return FALSE;
2164         set_nonblocking_input(TRUE);
2166         view->offset = 0;
2167         view->lines  = 0;
2168         view->lineno = 0;
2169         string_copy_rev(view->vid, view->id);
2171         if (view->line) {
2172                 int i;
2174                 for (i = 0; i < view->lines; i++)
2175                         if (view->line[i].data)
2176                                 free(view->line[i].data);
2178                 free(view->line);
2179                 view->line = NULL;
2180         }
2182         view->start_time = time(NULL);
2184         return TRUE;
2187 #define ITEM_CHUNK_SIZE 256
2188 static void *
2189 realloc_items(void *mem, size_t *size, size_t new_size, size_t item_size)
2191         size_t num_chunks = *size / ITEM_CHUNK_SIZE;
2192         size_t num_chunks_new = (new_size + ITEM_CHUNK_SIZE - 1) / ITEM_CHUNK_SIZE;
2194         if (mem == NULL || num_chunks != num_chunks_new) {
2195                 *size = num_chunks_new * ITEM_CHUNK_SIZE;
2196                 mem = realloc(mem, *size * item_size);
2197         }
2199         return mem;
2202 static struct line *
2203 realloc_lines(struct view *view, size_t line_size)
2205         size_t alloc = view->line_alloc;
2206         struct line *tmp = realloc_items(view->line, &alloc, line_size,
2207                                          sizeof(*view->line));
2209         if (!tmp)
2210                 return NULL;
2212         view->line = tmp;
2213         view->line_alloc = alloc;
2214         view->line_size = line_size;
2215         return view->line;
2218 static bool
2219 update_view(struct view *view)
2221         char in_buffer[BUFSIZ];
2222         char out_buffer[BUFSIZ * 2];
2223         char *line;
2224         /* The number of lines to read. If too low it will cause too much
2225          * redrawing (and possible flickering), if too high responsiveness
2226          * will suffer. */
2227         unsigned long lines = view->height;
2228         int redraw_from = -1;
2230         if (!view->pipe)
2231                 return TRUE;
2233         /* Only redraw if lines are visible. */
2234         if (view->offset + view->height >= view->lines)
2235                 redraw_from = view->lines - view->offset;
2237         /* FIXME: This is probably not perfect for backgrounded views. */
2238         if (!realloc_lines(view, view->lines + lines))
2239                 goto alloc_error;
2241         while ((line = fgets(in_buffer, sizeof(in_buffer), view->pipe))) {
2242                 size_t linelen = strlen(line);
2244                 if (linelen)
2245                         line[linelen - 1] = 0;
2247                 if (opt_iconv != ICONV_NONE) {
2248                         ICONV_CONST char *inbuf = line;
2249                         size_t inlen = linelen;
2251                         char *outbuf = out_buffer;
2252                         size_t outlen = sizeof(out_buffer);
2254                         size_t ret;
2256                         ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2257                         if (ret != (size_t) -1) {
2258                                 line = out_buffer;
2259                                 linelen = strlen(out_buffer);
2260                         }
2261                 }
2263                 if (!view->ops->read(view, line))
2264                         goto alloc_error;
2266                 if (lines-- == 1)
2267                         break;
2268         }
2270         {
2271                 int digits;
2273                 lines = view->lines;
2274                 for (digits = 0; lines; digits++)
2275                         lines /= 10;
2277                 /* Keep the displayed view in sync with line number scaling. */
2278                 if (digits != view->digits) {
2279                         view->digits = digits;
2280                         redraw_from = 0;
2281                 }
2282         }
2284         if (!view_is_displayed(view))
2285                 goto check_pipe;
2287         if (view == VIEW(REQ_VIEW_TREE)) {
2288                 /* Clear the view and redraw everything since the tree sorting
2289                  * might have rearranged things. */
2290                 redraw_view(view);
2292         } else if (redraw_from >= 0) {
2293                 /* If this is an incremental update, redraw the previous line
2294                  * since for commits some members could have changed when
2295                  * loading the main view. */
2296                 if (redraw_from > 0)
2297                         redraw_from--;
2299                 /* Since revision graph visualization requires knowledge
2300                  * about the parent commit, it causes a further one-off
2301                  * needed to be redrawn for incremental updates. */
2302                 if (redraw_from > 0 && opt_rev_graph)
2303                         redraw_from--;
2305                 /* Incrementally draw avoids flickering. */
2306                 redraw_view_from(view, redraw_from);
2307         }
2309         if (view == VIEW(REQ_VIEW_BLAME))
2310                 redraw_view_dirty(view);
2312         /* Update the title _after_ the redraw so that if the redraw picks up a
2313          * commit reference in view->ref it'll be available here. */
2314         update_view_title(view);
2316 check_pipe:
2317         if (ferror(view->pipe)) {
2318                 report("Failed to read: %s", strerror(errno));
2319                 goto end;
2321         } else if (feof(view->pipe)) {
2322                 report("");
2323                 goto end;
2324         }
2326         return TRUE;
2328 alloc_error:
2329         report("Allocation failure");
2331 end:
2332         if (view->ops->read(view, NULL))
2333                 end_update(view);
2334         return FALSE;
2337 static struct line *
2338 add_line_data(struct view *view, void *data, enum line_type type)
2340         struct line *line = &view->line[view->lines++];
2342         memset(line, 0, sizeof(*line));
2343         line->type = type;
2344         line->data = data;
2346         return line;
2349 static struct line *
2350 add_line_text(struct view *view, char *data, enum line_type type)
2352         if (data)
2353                 data = strdup(data);
2355         return data ? add_line_data(view, data, type) : NULL;
2359 /*
2360  * View opening
2361  */
2363 enum open_flags {
2364         OPEN_DEFAULT = 0,       /* Use default view switching. */
2365         OPEN_SPLIT = 1,         /* Split current view. */
2366         OPEN_BACKGROUNDED = 2,  /* Backgrounded. */
2367         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
2368         OPEN_NOMAXIMIZE = 8,    /* Do not maximize the current view. */
2369 };
2371 static void
2372 open_view(struct view *prev, enum request request, enum open_flags flags)
2374         bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
2375         bool split = !!(flags & OPEN_SPLIT);
2376         bool reload = !!(flags & OPEN_RELOAD);
2377         bool nomaximize = !!(flags & OPEN_NOMAXIMIZE);
2378         struct view *view = VIEW(request);
2379         int nviews = displayed_views();
2380         struct view *base_view = display[0];
2382         if (view == prev && nviews == 1 && !reload) {
2383                 report("Already in %s view", view->name);
2384                 return;
2385         }
2387         if (view->git_dir && !opt_git_dir[0]) {
2388                 report("The %s view is disabled in pager view", view->name);
2389                 return;
2390         }
2392         if (split) {
2393                 display[1] = view;
2394                 if (!backgrounded)
2395                         current_view = 1;
2396         } else if (!nomaximize) {
2397                 /* Maximize the current view. */
2398                 memset(display, 0, sizeof(display));
2399                 current_view = 0;
2400                 display[current_view] = view;
2401         }
2403         /* Resize the view when switching between split- and full-screen,
2404          * or when switching between two different full-screen views. */
2405         if (nviews != displayed_views() ||
2406             (nviews == 1 && base_view != display[0]))
2407                 resize_display();
2409         if (view->ops->open) {
2410                 if (!view->ops->open(view)) {
2411                         report("Failed to load %s view", view->name);
2412                         return;
2413                 }
2415         } else if ((reload || strcmp(view->vid, view->id)) &&
2416                    !begin_update(view)) {
2417                 report("Failed to load %s view", view->name);
2418                 return;
2419         }
2421         if (split && prev->lineno - prev->offset >= prev->height) {
2422                 /* Take the title line into account. */
2423                 int lines = prev->lineno - prev->offset - prev->height + 1;
2425                 /* Scroll the view that was split if the current line is
2426                  * outside the new limited view. */
2427                 do_scroll_view(prev, lines);
2428         }
2430         if (prev && view != prev) {
2431                 if (split && !backgrounded) {
2432                         /* "Blur" the previous view. */
2433                         update_view_title(prev);
2434                 }
2436                 view->parent = prev;
2437         }
2439         if (view->pipe && view->lines == 0) {
2440                 /* Clear the old view and let the incremental updating refill
2441                  * the screen. */
2442                 werase(view->win);
2443                 report("");
2444         } else {
2445                 redraw_view(view);
2446                 report("");
2447         }
2449         /* If the view is backgrounded the above calls to report()
2450          * won't redraw the view title. */
2451         if (backgrounded)
2452                 update_view_title(view);
2455 static void
2456 open_external_viewer(const char *cmd)
2458         def_prog_mode();           /* save current tty modes */
2459         endwin();                  /* restore original tty modes */
2460         system(cmd);
2461         fprintf(stderr, "Press Enter to continue");
2462         getc(stdin);
2463         reset_prog_mode();
2464         redraw_display();
2467 static void
2468 open_mergetool(const char *file)
2470         char cmd[SIZEOF_STR];
2471         char file_sq[SIZEOF_STR];
2473         if (sq_quote(file_sq, 0, file) < sizeof(file_sq) &&
2474             string_format(cmd, "git mergetool %s", file_sq)) {
2475                 open_external_viewer(cmd);
2476         }
2479 static void
2480 open_editor(bool from_root, const char *file)
2482         char cmd[SIZEOF_STR];
2483         char file_sq[SIZEOF_STR];
2484         char *editor;
2485         char *prefix = from_root ? opt_cdup : "";
2487         editor = getenv("GIT_EDITOR");
2488         if (!editor && *opt_editor)
2489                 editor = opt_editor;
2490         if (!editor)
2491                 editor = getenv("VISUAL");
2492         if (!editor)
2493                 editor = getenv("EDITOR");
2494         if (!editor)
2495                 editor = "vi";
2497         if (sq_quote(file_sq, 0, file) < sizeof(file_sq) &&
2498             string_format(cmd, "%s %s%s", editor, prefix, file_sq)) {
2499                 open_external_viewer(cmd);
2500         }
2503 static void
2504 open_run_request(enum request request)
2506         struct run_request *req = get_run_request(request);
2507         char buf[SIZEOF_STR * 2];
2508         size_t bufpos;
2509         char *cmd;
2511         if (!req) {
2512                 report("Unknown run request");
2513                 return;
2514         }
2516         bufpos = 0;
2517         cmd = req->cmd;
2519         while (cmd) {
2520                 char *next = strstr(cmd, "%(");
2521                 int len = next - cmd;
2522                 char *value;
2524                 if (!next) {
2525                         len = strlen(cmd);
2526                         value = "";
2528                 } else if (!strncmp(next, "%(head)", 7)) {
2529                         value = ref_head;
2531                 } else if (!strncmp(next, "%(commit)", 9)) {
2532                         value = ref_commit;
2534                 } else if (!strncmp(next, "%(blob)", 7)) {
2535                         value = ref_blob;
2537                 } else {
2538                         report("Unknown replacement in run request: `%s`", req->cmd);
2539                         return;
2540                 }
2542                 if (!string_format_from(buf, &bufpos, "%.*s%s", len, cmd, value))
2543                         return;
2545                 if (next)
2546                         next = strchr(next, ')') + 1;
2547                 cmd = next;
2548         }
2550         open_external_viewer(buf);
2553 /*
2554  * User request switch noodle
2555  */
2557 static int
2558 view_driver(struct view *view, enum request request)
2560         int i;
2562         if (request == REQ_NONE) {
2563                 doupdate();
2564                 return TRUE;
2565         }
2567         if (request > REQ_NONE) {
2568                 open_run_request(request);
2569                 /* FIXME: When all views can refresh always do this. */
2570                 if (view == VIEW(REQ_VIEW_STATUS) ||
2571                     view == VIEW(REQ_VIEW_STAGE))
2572                         request = REQ_REFRESH;
2573                 else
2574                         return TRUE;
2575         }
2577         if (view && view->lines) {
2578                 request = view->ops->request(view, request, &view->line[view->lineno]);
2579                 if (request == REQ_NONE)
2580                         return TRUE;
2581         }
2583         switch (request) {
2584         case REQ_MOVE_UP:
2585         case REQ_MOVE_DOWN:
2586         case REQ_MOVE_PAGE_UP:
2587         case REQ_MOVE_PAGE_DOWN:
2588         case REQ_MOVE_FIRST_LINE:
2589         case REQ_MOVE_LAST_LINE:
2590                 move_view(view, request);
2591                 break;
2593         case REQ_SCROLL_LINE_DOWN:
2594         case REQ_SCROLL_LINE_UP:
2595         case REQ_SCROLL_PAGE_DOWN:
2596         case REQ_SCROLL_PAGE_UP:
2597                 scroll_view(view, request);
2598                 break;
2600         case REQ_VIEW_BLAME:
2601                 if (!opt_file[0]) {
2602                         report("No file chosen, press %s to open tree view",
2603                                get_key(REQ_VIEW_TREE));
2604                         break;
2605                 }
2606                 open_view(view, request, OPEN_DEFAULT);
2607                 break;
2609         case REQ_VIEW_BLOB:
2610                 if (!ref_blob[0]) {
2611                         report("No file chosen, press %s to open tree view",
2612                                get_key(REQ_VIEW_TREE));
2613                         break;
2614                 }
2615                 open_view(view, request, OPEN_DEFAULT);
2616                 break;
2618         case REQ_VIEW_PAGER:
2619                 if (!opt_pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
2620                         report("No pager content, press %s to run command from prompt",
2621                                get_key(REQ_PROMPT));
2622                         break;
2623                 }
2624                 open_view(view, request, OPEN_DEFAULT);
2625                 break;
2627         case REQ_VIEW_STAGE:
2628                 if (!VIEW(REQ_VIEW_STAGE)->lines) {
2629                         report("No stage content, press %s to open the status view and choose file",
2630                                get_key(REQ_VIEW_STATUS));
2631                         break;
2632                 }
2633                 open_view(view, request, OPEN_DEFAULT);
2634                 break;
2636         case REQ_VIEW_STATUS:
2637                 if (opt_is_inside_work_tree == FALSE) {
2638                         report("The status view requires a working tree");
2639                         break;
2640                 }
2641                 open_view(view, request, OPEN_DEFAULT);
2642                 break;
2644         case REQ_VIEW_MAIN:
2645         case REQ_VIEW_DIFF:
2646         case REQ_VIEW_LOG:
2647         case REQ_VIEW_TREE:
2648         case REQ_VIEW_HELP:
2649                 open_view(view, request, OPEN_DEFAULT);
2650                 break;
2652         case REQ_NEXT:
2653         case REQ_PREVIOUS:
2654                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
2656                 if ((view == VIEW(REQ_VIEW_DIFF) &&
2657                      view->parent == VIEW(REQ_VIEW_MAIN)) ||
2658                    (view == VIEW(REQ_VIEW_DIFF) &&
2659                      view->parent == VIEW(REQ_VIEW_BLAME)) ||
2660                    (view == VIEW(REQ_VIEW_STAGE) &&
2661                      view->parent == VIEW(REQ_VIEW_STATUS)) ||
2662                    (view == VIEW(REQ_VIEW_BLOB) &&
2663                      view->parent == VIEW(REQ_VIEW_TREE))) {
2664                         int line;
2666                         view = view->parent;
2667                         line = view->lineno;
2668                         move_view(view, request);
2669                         if (view_is_displayed(view))
2670                                 update_view_title(view);
2671                         if (line != view->lineno)
2672                                 view->ops->request(view, REQ_ENTER,
2673                                                    &view->line[view->lineno]);
2675                 } else {
2676                         move_view(view, request);
2677                 }
2678                 break;
2680         case REQ_VIEW_NEXT:
2681         {
2682                 int nviews = displayed_views();
2683                 int next_view = (current_view + 1) % nviews;
2685                 if (next_view == current_view) {
2686                         report("Only one view is displayed");
2687                         break;
2688                 }
2690                 current_view = next_view;
2691                 /* Blur out the title of the previous view. */
2692                 update_view_title(view);
2693                 report("");
2694                 break;
2695         }
2696         case REQ_REFRESH:
2697                 report("Refreshing is not yet supported for the %s view", view->name);
2698                 break;
2700         case REQ_MAXIMIZE:
2701                 if (displayed_views() == 2)
2702                         open_view(view, VIEW_REQ(view), OPEN_DEFAULT);
2703                 break;
2705         case REQ_TOGGLE_LINENO:
2706                 opt_line_number = !opt_line_number;
2707                 redraw_display();
2708                 break;
2710         case REQ_TOGGLE_DATE:
2711                 opt_date = !opt_date;
2712                 redraw_display();
2713                 break;
2715         case REQ_TOGGLE_AUTHOR:
2716                 opt_author = !opt_author;
2717                 redraw_display();
2718                 break;
2720         case REQ_TOGGLE_REV_GRAPH:
2721                 opt_rev_graph = !opt_rev_graph;
2722                 redraw_display();
2723                 break;
2725         case REQ_TOGGLE_REFS:
2726                 opt_show_refs = !opt_show_refs;
2727                 redraw_display();
2728                 break;
2730         case REQ_PROMPT:
2731                 /* Always reload^Wrerun commands from the prompt. */
2732                 open_view(view, opt_request, OPEN_RELOAD);
2733                 break;
2735         case REQ_SEARCH:
2736         case REQ_SEARCH_BACK:
2737                 search_view(view, request);
2738                 break;
2740         case REQ_FIND_NEXT:
2741         case REQ_FIND_PREV:
2742                 find_next(view, request);
2743                 break;
2745         case REQ_STOP_LOADING:
2746                 for (i = 0; i < ARRAY_SIZE(views); i++) {
2747                         view = &views[i];
2748                         if (view->pipe)
2749                                 report("Stopped loading the %s view", view->name),
2750                         end_update(view);
2751                 }
2752                 break;
2754         case REQ_SHOW_VERSION:
2755                 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
2756                 return TRUE;
2758         case REQ_SCREEN_RESIZE:
2759                 resize_display();
2760                 /* Fall-through */
2761         case REQ_SCREEN_REDRAW:
2762                 redraw_display();
2763                 break;
2765         case REQ_EDIT:
2766                 report("Nothing to edit");
2767                 break;
2770         case REQ_ENTER:
2771                 report("Nothing to enter");
2772                 break;
2775         case REQ_VIEW_CLOSE:
2776                 /* XXX: Mark closed views by letting view->parent point to the
2777                  * view itself. Parents to closed view should never be
2778                  * followed. */
2779                 if (view->parent &&
2780                     view->parent->parent != view->parent) {
2781                         memset(display, 0, sizeof(display));
2782                         current_view = 0;
2783                         display[current_view] = view->parent;
2784                         view->parent = view;
2785                         resize_display();
2786                         redraw_display();
2787                         break;
2788                 }
2789                 /* Fall-through */
2790         case REQ_QUIT:
2791                 return FALSE;
2793         default:
2794                 /* An unknown key will show most commonly used commands. */
2795                 report("Unknown key, press 'h' for help");
2796                 return TRUE;
2797         }
2799         return TRUE;
2803 /*
2804  * Pager backend
2805  */
2807 static bool
2808 pager_draw(struct view *view, struct line *line, unsigned int lineno)
2810         char *text = line->data;
2811         int col = 0;
2813         if (opt_line_number) {
2814                 col += draw_lineno(view, lineno, view->width);
2815                 if (col >= view->width)
2816                         return TRUE;
2817         }
2819         draw_text(view, line->type, text, view->width - col, TRUE);
2820         return TRUE;
2823 static bool
2824 add_describe_ref(char *buf, size_t *bufpos, char *commit_id, const char *sep)
2826         char refbuf[SIZEOF_STR];
2827         char *ref = NULL;
2828         FILE *pipe;
2830         if (!string_format(refbuf, "git describe %s 2>/dev/null", commit_id))
2831                 return TRUE;
2833         pipe = popen(refbuf, "r");
2834         if (!pipe)
2835                 return TRUE;
2837         if ((ref = fgets(refbuf, sizeof(refbuf), pipe)))
2838                 ref = chomp_string(ref);
2839         pclose(pipe);
2841         if (!ref || !*ref)
2842                 return TRUE;
2844         /* This is the only fatal call, since it can "corrupt" the buffer. */
2845         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
2846                 return FALSE;
2848         return TRUE;
2851 static void
2852 add_pager_refs(struct view *view, struct line *line)
2854         char buf[SIZEOF_STR];
2855         char *commit_id = (char *)line->data + STRING_SIZE("commit ");
2856         struct ref **refs;
2857         size_t bufpos = 0, refpos = 0;
2858         const char *sep = "Refs: ";
2859         bool is_tag = FALSE;
2861         assert(line->type == LINE_COMMIT);
2863         refs = get_refs(commit_id);
2864         if (!refs) {
2865                 if (view == VIEW(REQ_VIEW_DIFF))
2866                         goto try_add_describe_ref;
2867                 return;
2868         }
2870         do {
2871                 struct ref *ref = refs[refpos];
2872                 char *fmt = ref->tag    ? "%s[%s]" :
2873                             ref->remote ? "%s<%s>" : "%s%s";
2875                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
2876                         return;
2877                 sep = ", ";
2878                 if (ref->tag)
2879                         is_tag = TRUE;
2880         } while (refs[refpos++]->next);
2882         if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
2883 try_add_describe_ref:
2884                 /* Add <tag>-g<commit_id> "fake" reference. */
2885                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
2886                         return;
2887         }
2889         if (bufpos == 0)
2890                 return;
2892         if (!realloc_lines(view, view->line_size + 1))
2893                 return;
2895         add_line_text(view, buf, LINE_PP_REFS);
2898 static bool
2899 pager_read(struct view *view, char *data)
2901         struct line *line;
2903         if (!data)
2904                 return TRUE;
2906         line = add_line_text(view, data, get_line_type(data));
2907         if (!line)
2908                 return FALSE;
2910         if (line->type == LINE_COMMIT &&
2911             (view == VIEW(REQ_VIEW_DIFF) ||
2912              view == VIEW(REQ_VIEW_LOG)))
2913                 add_pager_refs(view, line);
2915         return TRUE;
2918 static enum request
2919 pager_request(struct view *view, enum request request, struct line *line)
2921         int split = 0;
2923         if (request != REQ_ENTER)
2924                 return request;
2926         if (line->type == LINE_COMMIT &&
2927            (view == VIEW(REQ_VIEW_LOG) ||
2928             view == VIEW(REQ_VIEW_PAGER))) {
2929                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
2930                 split = 1;
2931         }
2933         /* Always scroll the view even if it was split. That way
2934          * you can use Enter to scroll through the log view and
2935          * split open each commit diff. */
2936         scroll_view(view, REQ_SCROLL_LINE_DOWN);
2938         /* FIXME: A minor workaround. Scrolling the view will call report("")
2939          * but if we are scrolling a non-current view this won't properly
2940          * update the view title. */
2941         if (split)
2942                 update_view_title(view);
2944         return REQ_NONE;
2947 static bool
2948 pager_grep(struct view *view, struct line *line)
2950         regmatch_t pmatch;
2951         char *text = line->data;
2953         if (!*text)
2954                 return FALSE;
2956         if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
2957                 return FALSE;
2959         return TRUE;
2962 static void
2963 pager_select(struct view *view, struct line *line)
2965         if (line->type == LINE_COMMIT) {
2966                 char *text = (char *)line->data + STRING_SIZE("commit ");
2968                 if (view != VIEW(REQ_VIEW_PAGER))
2969                         string_copy_rev(view->ref, text);
2970                 string_copy_rev(ref_commit, text);
2971         }
2974 static struct view_ops pager_ops = {
2975         "line",
2976         NULL,
2977         pager_read,
2978         pager_draw,
2979         pager_request,
2980         pager_grep,
2981         pager_select,
2982 };
2985 /*
2986  * Help backend
2987  */
2989 static bool
2990 help_open(struct view *view)
2992         char buf[BUFSIZ];
2993         int lines = ARRAY_SIZE(req_info) + 2;
2994         int i;
2996         if (view->lines > 0)
2997                 return TRUE;
2999         for (i = 0; i < ARRAY_SIZE(req_info); i++)
3000                 if (!req_info[i].request)
3001                         lines++;
3003         lines += run_requests + 1;
3005         view->line = calloc(lines, sizeof(*view->line));
3006         if (!view->line)
3007                 return FALSE;
3009         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
3011         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
3012                 char *key;
3014                 if (req_info[i].request == REQ_NONE)
3015                         continue;
3017                 if (!req_info[i].request) {
3018                         add_line_text(view, "", LINE_DEFAULT);
3019                         add_line_text(view, req_info[i].help, LINE_DEFAULT);
3020                         continue;
3021                 }
3023                 key = get_key(req_info[i].request);
3024                 if (!*key)
3025                         key = "(no key defined)";
3027                 if (!string_format(buf, "    %-25s %s", key, req_info[i].help))
3028                         continue;
3030                 add_line_text(view, buf, LINE_DEFAULT);
3031         }
3033         if (run_requests) {
3034                 add_line_text(view, "", LINE_DEFAULT);
3035                 add_line_text(view, "External commands:", LINE_DEFAULT);
3036         }
3038         for (i = 0; i < run_requests; i++) {
3039                 struct run_request *req = get_run_request(REQ_NONE + i + 1);
3040                 char *key;
3042                 if (!req)
3043                         continue;
3045                 key = get_key_name(req->key);
3046                 if (!*key)
3047                         key = "(no key defined)";
3049                 if (!string_format(buf, "    %-10s %-14s `%s`",
3050                                    keymap_table[req->keymap].name,
3051                                    key, req->cmd))
3052                         continue;
3054                 add_line_text(view, buf, LINE_DEFAULT);
3055         }
3057         return TRUE;
3060 static struct view_ops help_ops = {
3061         "line",
3062         help_open,
3063         NULL,
3064         pager_draw,
3065         pager_request,
3066         pager_grep,
3067         pager_select,
3068 };
3071 /*
3072  * Tree backend
3073  */
3075 struct tree_stack_entry {
3076         struct tree_stack_entry *prev;  /* Entry below this in the stack */
3077         unsigned long lineno;           /* Line number to restore */
3078         char *name;                     /* Position of name in opt_path */
3079 };
3081 /* The top of the path stack. */
3082 static struct tree_stack_entry *tree_stack = NULL;
3083 unsigned long tree_lineno = 0;
3085 static void
3086 pop_tree_stack_entry(void)
3088         struct tree_stack_entry *entry = tree_stack;
3090         tree_lineno = entry->lineno;
3091         entry->name[0] = 0;
3092         tree_stack = entry->prev;
3093         free(entry);
3096 static void
3097 push_tree_stack_entry(char *name, unsigned long lineno)
3099         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
3100         size_t pathlen = strlen(opt_path);
3102         if (!entry)
3103                 return;
3105         entry->prev = tree_stack;
3106         entry->name = opt_path + pathlen;
3107         tree_stack = entry;
3109         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
3110                 pop_tree_stack_entry();
3111                 return;
3112         }
3114         /* Move the current line to the first tree entry. */
3115         tree_lineno = 1;
3116         entry->lineno = lineno;
3119 /* Parse output from git-ls-tree(1):
3120  *
3121  * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile
3122  * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README
3123  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
3124  * 100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38 web.conf
3125  */
3127 #define SIZEOF_TREE_ATTR \
3128         STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t")
3130 #define TREE_UP_FORMAT "040000 tree %s\t.."
3132 static int
3133 tree_compare_entry(enum line_type type1, char *name1,
3134                    enum line_type type2, char *name2)
3136         if (type1 != type2) {
3137                 if (type1 == LINE_TREE_DIR)
3138                         return -1;
3139                 return 1;
3140         }
3142         return strcmp(name1, name2);
3145 static char *
3146 tree_path(struct line *line)
3148         char *path = line->data;
3150         return path + SIZEOF_TREE_ATTR;
3153 static bool
3154 tree_read(struct view *view, char *text)
3156         size_t textlen = text ? strlen(text) : 0;
3157         char buf[SIZEOF_STR];
3158         unsigned long pos;
3159         enum line_type type;
3160         bool first_read = view->lines == 0;
3162         if (!text)
3163                 return TRUE;
3164         if (textlen <= SIZEOF_TREE_ATTR)
3165                 return FALSE;
3167         type = text[STRING_SIZE("100644 ")] == 't'
3168              ? LINE_TREE_DIR : LINE_TREE_FILE;
3170         if (first_read) {
3171                 /* Add path info line */
3172                 if (!string_format(buf, "Directory path /%s", opt_path) ||
3173                     !realloc_lines(view, view->line_size + 1) ||
3174                     !add_line_text(view, buf, LINE_DEFAULT))
3175                         return FALSE;
3177                 /* Insert "link" to parent directory. */
3178                 if (*opt_path) {
3179                         if (!string_format(buf, TREE_UP_FORMAT, view->ref) ||
3180                             !realloc_lines(view, view->line_size + 1) ||
3181                             !add_line_text(view, buf, LINE_TREE_DIR))
3182                                 return FALSE;
3183                 }
3184         }
3186         /* Strip the path part ... */
3187         if (*opt_path) {
3188                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
3189                 size_t striplen = strlen(opt_path);
3190                 char *path = text + SIZEOF_TREE_ATTR;
3192                 if (pathlen > striplen)
3193                         memmove(path, path + striplen,
3194                                 pathlen - striplen + 1);
3195         }
3197         /* Skip "Directory ..." and ".." line. */
3198         for (pos = 1 + !!*opt_path; pos < view->lines; pos++) {
3199                 struct line *line = &view->line[pos];
3200                 char *path1 = tree_path(line);
3201                 char *path2 = text + SIZEOF_TREE_ATTR;
3202                 int cmp = tree_compare_entry(line->type, path1, type, path2);
3204                 if (cmp <= 0)
3205                         continue;
3207                 text = strdup(text);
3208                 if (!text)
3209                         return FALSE;
3211                 if (view->lines > pos)
3212                         memmove(&view->line[pos + 1], &view->line[pos],
3213                                 (view->lines - pos) * sizeof(*line));
3215                 line = &view->line[pos];
3216                 line->data = text;
3217                 line->type = type;
3218                 view->lines++;
3219                 return TRUE;
3220         }
3222         if (!add_line_text(view, text, type))
3223                 return FALSE;
3225         if (tree_lineno > view->lineno) {
3226                 view->lineno = tree_lineno;
3227                 tree_lineno = 0;
3228         }
3230         return TRUE;
3233 static enum request
3234 tree_request(struct view *view, enum request request, struct line *line)
3236         enum open_flags flags;
3238         if (request == REQ_VIEW_BLAME) {
3239                 char *filename = tree_path(line);
3241                 if (line->type == LINE_TREE_DIR) {
3242                         report("Cannot show blame for directory %s", opt_path);
3243                         return REQ_NONE;
3244                 }
3246                 string_copy(opt_ref, view->vid);
3247                 string_format(opt_file, "%s%s", opt_path, filename);
3248                 return request;
3249         }
3250         if (request == REQ_TREE_PARENT) {
3251                 if (*opt_path) {
3252                         /* fake 'cd  ..' */
3253                         request = REQ_ENTER;
3254                         line = &view->line[1];
3255                 } else {
3256                         /* quit view if at top of tree */
3257                         return REQ_VIEW_CLOSE;
3258                 }
3259         }
3260         if (request != REQ_ENTER)
3261                 return request;
3263         /* Cleanup the stack if the tree view is at a different tree. */
3264         while (!*opt_path && tree_stack)
3265                 pop_tree_stack_entry();
3267         switch (line->type) {
3268         case LINE_TREE_DIR:
3269                 /* Depending on whether it is a subdir or parent (updir?) link
3270                  * mangle the path buffer. */
3271                 if (line == &view->line[1] && *opt_path) {
3272                         pop_tree_stack_entry();
3274                 } else {
3275                         char *basename = tree_path(line);
3277                         push_tree_stack_entry(basename, view->lineno);
3278                 }
3280                 /* Trees and subtrees share the same ID, so they are not not
3281                  * unique like blobs. */
3282                 flags = OPEN_RELOAD;
3283                 request = REQ_VIEW_TREE;
3284                 break;
3286         case LINE_TREE_FILE:
3287                 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
3288                 request = REQ_VIEW_BLOB;
3289                 break;
3291         default:
3292                 return TRUE;
3293         }
3295         open_view(view, request, flags);
3296         if (request == REQ_VIEW_TREE) {
3297                 view->lineno = tree_lineno;
3298         }
3300         return REQ_NONE;
3303 static void
3304 tree_select(struct view *view, struct line *line)
3306         char *text = (char *)line->data + STRING_SIZE("100644 blob ");
3308         if (line->type == LINE_TREE_FILE) {
3309                 string_copy_rev(ref_blob, text);
3311         } else if (line->type != LINE_TREE_DIR) {
3312                 return;
3313         }
3315         string_copy_rev(view->ref, text);
3318 static struct view_ops tree_ops = {
3319         "file",
3320         NULL,
3321         tree_read,
3322         pager_draw,
3323         tree_request,
3324         pager_grep,
3325         tree_select,
3326 };
3328 static bool
3329 blob_read(struct view *view, char *line)
3331         if (!line)
3332                 return TRUE;
3333         return add_line_text(view, line, LINE_DEFAULT) != NULL;
3336 static struct view_ops blob_ops = {
3337         "line",
3338         NULL,
3339         blob_read,
3340         pager_draw,
3341         pager_request,
3342         pager_grep,
3343         pager_select,
3344 };
3346 /*
3347  * Blame backend
3348  *
3349  * Loading the blame view is a two phase job:
3350  *
3351  *  1. File content is read either using opt_file from the
3352  *     filesystem or using git-cat-file.
3353  *  2. Then blame information is incrementally added by
3354  *     reading output from git-blame.
3355  */
3357 struct blame_commit {
3358         char id[SIZEOF_REV];            /* SHA1 ID. */
3359         char title[128];                /* First line of the commit message. */
3360         char author[75];                /* Author of the commit. */
3361         struct tm time;                 /* Date from the author ident. */
3362         char filename[128];             /* Name of file. */
3363 };
3365 struct blame {
3366         struct blame_commit *commit;
3367         unsigned int header:1;
3368         char text[1];
3369 };
3371 #define BLAME_CAT_FILE_CMD "git cat-file blob %s:%s"
3372 #define BLAME_INCREMENTAL_CMD "git blame --incremental %s %s"
3374 static bool
3375 blame_open(struct view *view)
3377         char path[SIZEOF_STR];
3378         char ref[SIZEOF_STR] = "";
3380         if (sq_quote(path, 0, opt_file) >= sizeof(path))
3381                 return FALSE;
3383         if (*opt_ref && sq_quote(ref, 0, opt_ref) >= sizeof(ref))
3384                 return FALSE;
3386         if (*opt_ref) {
3387                 if (!string_format(view->cmd, BLAME_CAT_FILE_CMD, ref, path))
3388                         return FALSE;
3389         } else {
3390                 view->pipe = fopen(opt_file, "r");
3391                 if (!view->pipe &&
3392                     !string_format(view->cmd, BLAME_CAT_FILE_CMD, "HEAD", path))
3393                         return FALSE;
3394         }
3396         if (!view->pipe)
3397                 view->pipe = popen(view->cmd, "r");
3398         if (!view->pipe)
3399                 return FALSE;
3401         if (!string_format(view->cmd, BLAME_INCREMENTAL_CMD, ref, path))
3402                 return FALSE;
3404         string_format(view->ref, "%s ...", opt_file);
3405         string_copy_rev(view->vid, opt_file);
3406         set_nonblocking_input(TRUE);
3408         if (view->line) {
3409                 int i;
3411                 for (i = 0; i < view->lines; i++)
3412                         free(view->line[i].data);
3413                 free(view->line);
3414         }
3416         view->lines = view->line_alloc = view->line_size = view->lineno = 0;
3417         view->offset = view->lines  = view->lineno = 0;
3418         view->line = NULL;
3419         view->start_time = time(NULL);
3421         return TRUE;
3424 static struct blame_commit *
3425 get_blame_commit(struct view *view, const char *id)
3427         size_t i;
3429         for (i = 0; i < view->lines; i++) {
3430                 struct blame *blame = view->line[i].data;
3432                 if (!blame->commit)
3433                         continue;
3435                 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
3436                         return blame->commit;
3437         }
3439         {
3440                 struct blame_commit *commit = calloc(1, sizeof(*commit));
3442                 if (commit)
3443                         string_ncopy(commit->id, id, SIZEOF_REV);
3444                 return commit;
3445         }
3448 static bool
3449 parse_number(char **posref, size_t *number, size_t min, size_t max)
3451         char *pos = *posref;
3453         *posref = NULL;
3454         pos = strchr(pos + 1, ' ');
3455         if (!pos || !isdigit(pos[1]))
3456                 return FALSE;
3457         *number = atoi(pos + 1);
3458         if (*number < min || *number > max)
3459                 return FALSE;
3461         *posref = pos;
3462         return TRUE;
3465 static struct blame_commit *
3466 parse_blame_commit(struct view *view, char *text, int *blamed)
3468         struct blame_commit *commit;
3469         struct blame *blame;
3470         char *pos = text + SIZEOF_REV - 1;
3471         size_t lineno;
3472         size_t group;
3474         if (strlen(text) <= SIZEOF_REV || *pos != ' ')
3475                 return NULL;
3477         if (!parse_number(&pos, &lineno, 1, view->lines) ||
3478             !parse_number(&pos, &group, 1, view->lines - lineno + 1))
3479                 return NULL;
3481         commit = get_blame_commit(view, text);
3482         if (!commit)
3483                 return NULL;
3485         *blamed += group;
3486         while (group--) {
3487                 struct line *line = &view->line[lineno + group - 1];
3489                 blame = line->data;
3490                 blame->commit = commit;
3491                 blame->header = !group;
3492                 line->dirty = 1;
3493         }
3495         return commit;
3498 static bool
3499 blame_read_file(struct view *view, char *line)
3501         if (!line) {
3502                 FILE *pipe = NULL;
3504                 if (view->lines > 0)
3505                         pipe = popen(view->cmd, "r");
3506                 else if (!view->parent)
3507                         die("No blame exist for %s", view->vid);
3508                 view->cmd[0] = 0;
3509                 if (!pipe) {
3510                         report("Failed to load blame data");
3511                         return TRUE;
3512                 }
3514                 fclose(view->pipe);
3515                 view->pipe = pipe;
3516                 return FALSE;
3518         } else {
3519                 size_t linelen = strlen(line);
3520                 struct blame *blame = malloc(sizeof(*blame) + linelen);
3522                 if (!line)
3523                         return FALSE;
3525                 blame->commit = NULL;
3526                 strncpy(blame->text, line, linelen);
3527                 blame->text[linelen] = 0;
3528                 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
3529         }
3532 static bool
3533 match_blame_header(const char *name, char **line)
3535         size_t namelen = strlen(name);
3536         bool matched = !strncmp(name, *line, namelen);
3538         if (matched)
3539                 *line += namelen;
3541         return matched;
3544 static bool
3545 blame_read(struct view *view, char *line)
3547         static struct blame_commit *commit = NULL;
3548         static int blamed = 0;
3549         static time_t author_time;
3551         if (*view->cmd)
3552                 return blame_read_file(view, line);
3554         if (!line) {
3555                 /* Reset all! */
3556                 commit = NULL;
3557                 blamed = 0;
3558                 string_format(view->ref, "%s", view->vid);
3559                 if (view_is_displayed(view)) {
3560                         update_view_title(view);
3561                         redraw_view_from(view, 0);
3562                 }
3563                 return TRUE;
3564         }
3566         if (!commit) {
3567                 commit = parse_blame_commit(view, line, &blamed);
3568                 string_format(view->ref, "%s %2d%%", view->vid,
3569                               blamed * 100 / view->lines);
3571         } else if (match_blame_header("author ", &line)) {
3572                 string_ncopy(commit->author, line, strlen(line));
3574         } else if (match_blame_header("author-time ", &line)) {
3575                 author_time = (time_t) atol(line);
3577         } else if (match_blame_header("author-tz ", &line)) {
3578                 long tz;
3580                 tz  = ('0' - line[1]) * 60 * 60 * 10;
3581                 tz += ('0' - line[2]) * 60 * 60;
3582                 tz += ('0' - line[3]) * 60;
3583                 tz += ('0' - line[4]) * 60;
3585                 if (line[0] == '-')
3586                         tz = -tz;
3588                 author_time -= tz;
3589                 gmtime_r(&author_time, &commit->time);
3591         } else if (match_blame_header("summary ", &line)) {
3592                 string_ncopy(commit->title, line, strlen(line));
3594         } else if (match_blame_header("filename ", &line)) {
3595                 string_ncopy(commit->filename, line, strlen(line));
3596                 commit = NULL;
3597         }
3599         return TRUE;
3602 static bool
3603 blame_draw(struct view *view, struct line *line, unsigned int lineno)
3605         struct blame *blame = line->data;
3606         struct tm *time = NULL;
3607         char *id = NULL, *author = NULL;
3608         int col = 0;
3610         if (blame->commit && *blame->commit->filename) {
3611                 id = blame->commit->id;
3612                 author = blame->commit->author;
3613                 time = &blame->commit->time;
3614         }
3616         if (opt_date) {
3617                 col += draw_date(view, time, view->width);
3618                 if (col >= view->width)
3619                         return TRUE;
3620         }
3622         if (opt_author) {
3623                 int max = MIN(AUTHOR_COLS - 1, view->width - col);
3625                 set_view_attr(view, LINE_MAIN_AUTHOR);
3626                 if (author)
3627                         draw_text(view, LINE_MAIN_AUTHOR, author, max, TRUE);
3628                 col += AUTHOR_COLS;
3629                 if (col >= view->width)
3630                         return TRUE;
3631                 wmove(view->win, lineno, col);
3632         }
3634         {
3635                 int max = MIN(ID_COLS - 1, view->width - col);
3637                 set_view_attr(view, LINE_BLAME_ID);
3638                 if (id)
3639                         draw_text(view, LINE_BLAME_ID, id, max, FALSE);
3640                 col += ID_COLS;
3641                 if (col >= view->width)
3642                         return TRUE;
3643                 wmove(view->win, lineno, col);
3644         }
3646         col += draw_lineno(view, lineno, view->width - col);
3647         if (col >= view->width)
3648                 return TRUE;
3650         col += draw_text(view, LINE_DEFAULT, blame->text, view->width - col, TRUE);
3651         return TRUE;
3654 static enum request
3655 blame_request(struct view *view, enum request request, struct line *line)
3657         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
3658         struct blame *blame = line->data;
3660         switch (request) {
3661         case REQ_ENTER:
3662                 if (!blame->commit) {
3663                         report("No commit loaded yet");
3664                         break;
3665                 }
3667                 if (!strcmp(blame->commit->id, NULL_ID)) {
3668                         char path[SIZEOF_STR];
3670                         if (sq_quote(path, 0, view->vid) >= sizeof(path))
3671                                 break;
3672                         string_format(opt_cmd, "git diff-index --root --patch-with-stat -C -M --cached HEAD -- %s 2>/dev/null", path);
3673                 }
3675                 open_view(view, REQ_VIEW_DIFF, flags);
3676                 break;
3678         default:
3679                 return request;
3680         }
3682         return REQ_NONE;
3685 static bool
3686 blame_grep(struct view *view, struct line *line)
3688         struct blame *blame = line->data;
3689         struct blame_commit *commit = blame->commit;
3690         regmatch_t pmatch;
3692 #define MATCH(text, on)                                                 \
3693         (on && *text && regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
3695         if (commit) {
3696                 char buf[DATE_COLS + 1];
3698                 if (MATCH(commit->title, 1) ||
3699                     MATCH(commit->author, opt_author) ||
3700                     MATCH(commit->id, opt_date))
3701                         return TRUE;
3703                 if (strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time) &&
3704                     MATCH(buf, 1))
3705                         return TRUE;
3706         }
3708         return MATCH(blame->text, 1);
3710 #undef MATCH
3713 static void
3714 blame_select(struct view *view, struct line *line)
3716         struct blame *blame = line->data;
3717         struct blame_commit *commit = blame->commit;
3719         if (!commit)
3720                 return;
3722         if (!strcmp(commit->id, NULL_ID))
3723                 string_ncopy(ref_commit, "HEAD", 4);
3724         else
3725                 string_copy_rev(ref_commit, commit->id);
3728 static struct view_ops blame_ops = {
3729         "line",
3730         blame_open,
3731         blame_read,
3732         blame_draw,
3733         blame_request,
3734         blame_grep,
3735         blame_select,
3736 };
3738 /*
3739  * Status backend
3740  */
3742 struct status {
3743         char status;
3744         struct {
3745                 mode_t mode;
3746                 char rev[SIZEOF_REV];
3747                 char name[SIZEOF_STR];
3748         } old;
3749         struct {
3750                 mode_t mode;
3751                 char rev[SIZEOF_REV];
3752                 char name[SIZEOF_STR];
3753         } new;
3754 };
3756 static char status_onbranch[SIZEOF_STR];
3757 static struct status stage_status;
3758 static enum line_type stage_line_type;
3760 /* Get fields from the diff line:
3761  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
3762  */
3763 static inline bool
3764 status_get_diff(struct status *file, char *buf, size_t bufsize)
3766         char *old_mode = buf +  1;
3767         char *new_mode = buf +  8;
3768         char *old_rev  = buf + 15;
3769         char *new_rev  = buf + 56;
3770         char *status   = buf + 97;
3772         if (bufsize < 99 ||
3773             old_mode[-1] != ':' ||
3774             new_mode[-1] != ' ' ||
3775             old_rev[-1]  != ' ' ||
3776             new_rev[-1]  != ' ' ||
3777             status[-1]   != ' ')
3778                 return FALSE;
3780         file->status = *status;
3782         string_copy_rev(file->old.rev, old_rev);
3783         string_copy_rev(file->new.rev, new_rev);
3785         file->old.mode = strtoul(old_mode, NULL, 8);
3786         file->new.mode = strtoul(new_mode, NULL, 8);
3788         file->old.name[0] = file->new.name[0] = 0;
3790         return TRUE;
3793 static bool
3794 status_run(struct view *view, const char cmd[], char status, enum line_type type)
3796         struct status *file = NULL;
3797         struct status *unmerged = NULL;
3798         char buf[SIZEOF_STR * 4];
3799         size_t bufsize = 0;
3800         FILE *pipe;
3802         pipe = popen(cmd, "r");
3803         if (!pipe)
3804                 return FALSE;
3806         add_line_data(view, NULL, type);
3808         while (!feof(pipe) && !ferror(pipe)) {
3809                 char *sep;
3810                 size_t readsize;
3812                 readsize = fread(buf + bufsize, 1, sizeof(buf) - bufsize, pipe);
3813                 if (!readsize)
3814                         break;
3815                 bufsize += readsize;
3817                 /* Process while we have NUL chars. */
3818                 while ((sep = memchr(buf, 0, bufsize))) {
3819                         size_t sepsize = sep - buf + 1;
3821                         if (!file) {
3822                                 if (!realloc_lines(view, view->line_size + 1))
3823                                         goto error_out;
3825                                 file = calloc(1, sizeof(*file));
3826                                 if (!file)
3827                                         goto error_out;
3829                                 add_line_data(view, file, type);
3830                         }
3832                         /* Parse diff info part. */
3833                         if (status) {
3834                                 file->status = status;
3835                                 if (status == 'A')
3836                                         string_copy(file->old.rev, NULL_ID);
3838                         } else if (!file->status) {
3839                                 if (!status_get_diff(file, buf, sepsize))
3840                                         goto error_out;
3842                                 bufsize -= sepsize;
3843                                 memmove(buf, sep + 1, bufsize);
3845                                 sep = memchr(buf, 0, bufsize);
3846                                 if (!sep)
3847                                         break;
3848                                 sepsize = sep - buf + 1;
3850                                 /* Collapse all 'M'odified entries that
3851                                  * follow a associated 'U'nmerged entry.
3852                                  */
3853                                 if (file->status == 'U') {
3854                                         unmerged = file;
3856                                 } else if (unmerged) {
3857                                         int collapse = !strcmp(buf, unmerged->new.name);
3859                                         unmerged = NULL;
3860                                         if (collapse) {
3861                                                 free(file);
3862                                                 view->lines--;
3863                                                 continue;
3864                                         }
3865                                 }
3866                         }
3868                         /* Grab the old name for rename/copy. */
3869                         if (!*file->old.name &&
3870                             (file->status == 'R' || file->status == 'C')) {
3871                                 sepsize = sep - buf + 1;
3872                                 string_ncopy(file->old.name, buf, sepsize);
3873                                 bufsize -= sepsize;
3874                                 memmove(buf, sep + 1, bufsize);
3876                                 sep = memchr(buf, 0, bufsize);
3877                                 if (!sep)
3878                                         break;
3879                                 sepsize = sep - buf + 1;
3880                         }
3882                         /* git-ls-files just delivers a NUL separated
3883                          * list of file names similar to the second half
3884                          * of the git-diff-* output. */
3885                         string_ncopy(file->new.name, buf, sepsize);
3886                         if (!*file->old.name)
3887                                 string_copy(file->old.name, file->new.name);
3888                         bufsize -= sepsize;
3889                         memmove(buf, sep + 1, bufsize);
3890                         file = NULL;
3891                 }
3892         }
3894         if (ferror(pipe)) {
3895 error_out:
3896                 pclose(pipe);
3897                 return FALSE;
3898         }
3900         if (!view->line[view->lines - 1].data)
3901                 add_line_data(view, NULL, LINE_STAT_NONE);
3903         pclose(pipe);
3904         return TRUE;
3907 /* Don't show unmerged entries in the staged section. */
3908 #define STATUS_DIFF_INDEX_CMD "git diff-index -z --diff-filter=ACDMRTXB --cached -M HEAD"
3909 #define STATUS_DIFF_FILES_CMD "git diff-files -z"
3910 #define STATUS_LIST_OTHER_CMD \
3911         "git ls-files -z --others --exclude-per-directory=.gitignore"
3912 #define STATUS_LIST_NO_HEAD_CMD \
3913         "git ls-files -z --cached --exclude-per-directory=.gitignore"
3915 #define STATUS_DIFF_INDEX_SHOW_CMD \
3916         "git diff-index --root --patch-with-stat -C -M --cached HEAD -- %s %s 2>/dev/null"
3918 #define STATUS_DIFF_FILES_SHOW_CMD \
3919         "git diff-files --root --patch-with-stat -C -M -- %s %s 2>/dev/null"
3921 #define STATUS_DIFF_NO_HEAD_SHOW_CMD \
3922         "git diff --no-color --patch-with-stat /dev/null %s 2>/dev/null"
3924 /* First parse staged info using git-diff-index(1), then parse unstaged
3925  * info using git-diff-files(1), and finally untracked files using
3926  * git-ls-files(1). */
3927 static bool
3928 status_open(struct view *view)
3930         struct stat statbuf;
3931         char exclude[SIZEOF_STR];
3932         char indexcmd[SIZEOF_STR] = STATUS_DIFF_INDEX_CMD;
3933         char othercmd[SIZEOF_STR] = STATUS_LIST_OTHER_CMD;
3934         unsigned long prev_lineno = view->lineno;
3935         char indexstatus = 0;
3936         size_t i;
3938         for (i = 0; i < view->lines; i++)
3939                 free(view->line[i].data);
3940         free(view->line);
3941         view->lines = view->line_alloc = view->line_size = view->lineno = 0;
3942         view->line = NULL;
3944         if (!realloc_lines(view, view->line_size + 7))
3945                 return FALSE;
3947         add_line_data(view, NULL, LINE_STAT_HEAD);
3948         if (opt_no_head)
3949                 string_copy(status_onbranch, "Initial commit");
3950         else if (!*opt_head)
3951                 string_copy(status_onbranch, "Not currently on any branch");
3952         else if (!string_format(status_onbranch, "On branch %s", opt_head))
3953                 return FALSE;
3955         if (opt_no_head) {
3956                 string_copy(indexcmd, STATUS_LIST_NO_HEAD_CMD);
3957                 indexstatus = 'A';
3958         }
3960         if (!string_format(exclude, "%s/info/exclude", opt_git_dir))
3961                 return FALSE;
3963         if (stat(exclude, &statbuf) >= 0) {
3964                 size_t cmdsize = strlen(othercmd);
3966                 if (!string_format_from(othercmd, &cmdsize, " %s", "--exclude-from=") ||
3967                     sq_quote(othercmd, cmdsize, exclude) >= sizeof(othercmd))
3968                         return FALSE;
3970                 cmdsize = strlen(indexcmd);
3971                 if (opt_no_head &&
3972                     (!string_format_from(indexcmd, &cmdsize, " %s", "--exclude-from=") ||
3973                      sq_quote(indexcmd, cmdsize, exclude) >= sizeof(indexcmd)))
3974                         return FALSE;
3975         }
3977         system("git update-index -q --refresh >/dev/null 2>/dev/null");
3979         if (!status_run(view, indexcmd, indexstatus, LINE_STAT_STAGED) ||
3980             !status_run(view, STATUS_DIFF_FILES_CMD, 0, LINE_STAT_UNSTAGED) ||
3981             !status_run(view, othercmd, '?', LINE_STAT_UNTRACKED))
3982                 return FALSE;
3984         /* If all went well restore the previous line number to stay in
3985          * the context or select a line with something that can be
3986          * updated. */
3987         if (prev_lineno >= view->lines)
3988                 prev_lineno = view->lines - 1;
3989         while (prev_lineno < view->lines && !view->line[prev_lineno].data)
3990                 prev_lineno++;
3991         while (prev_lineno > 0 && !view->line[prev_lineno].data)
3992                 prev_lineno--;
3994         /* If the above fails, always skip the "On branch" line. */
3995         if (prev_lineno < view->lines)
3996                 view->lineno = prev_lineno;
3997         else
3998                 view->lineno = 1;
4000         if (view->lineno < view->offset)
4001                 view->offset = view->lineno;
4002         else if (view->offset + view->height <= view->lineno)
4003                 view->offset = view->lineno - view->height + 1;
4005         return TRUE;
4008 static bool
4009 status_draw(struct view *view, struct line *line, unsigned int lineno)
4011         struct status *status = line->data;
4012         enum line_type type;
4013         char *text;
4014         int col = 0;
4016         if (!status) {
4017                 switch (line->type) {
4018                 case LINE_STAT_STAGED:
4019                         type = LINE_STAT_SECTION;
4020                         text = "Changes to be committed:";
4021                         break;
4023                 case LINE_STAT_UNSTAGED:
4024                         type = LINE_STAT_SECTION;
4025                         text = "Changed but not updated:";
4026                         break;
4028                 case LINE_STAT_UNTRACKED:
4029                         type = LINE_STAT_SECTION;
4030                         text = "Untracked files:";
4031                         break;
4033                 case LINE_STAT_NONE:
4034                         type = LINE_DEFAULT;
4035                         text = "    (no files)";
4036                         break;
4038                 case LINE_STAT_HEAD:
4039                         type = LINE_STAT_HEAD;
4040                         text = status_onbranch;
4041                         break;
4043                 default:
4044                         return FALSE;
4045                 }
4046         } else {
4047                 char buf[] = { status->status, ' ', ' ', ' ', 0 };
4049                 col += draw_text(view, line->type, buf, view->width, TRUE);
4050                 type = LINE_DEFAULT;
4051                 text = status->new.name;
4052         }
4054         draw_text(view, type, text, view->width - col, TRUE);
4055         return TRUE;
4058 static enum request
4059 status_enter(struct view *view, struct line *line)
4061         struct status *status = line->data;
4062         char oldpath[SIZEOF_STR] = "";
4063         char newpath[SIZEOF_STR] = "";
4064         char *info;
4065         size_t cmdsize = 0;
4066         enum open_flags split;
4068         if (line->type == LINE_STAT_NONE ||
4069             (!status && line[1].type == LINE_STAT_NONE)) {
4070                 report("No file to diff");
4071                 return REQ_NONE;
4072         }
4074         if (status) {
4075                 if (sq_quote(oldpath, 0, status->old.name) >= sizeof(oldpath))
4076                         return REQ_QUIT;
4077                 /* Diffs for unmerged entries are empty when pasing the
4078                  * new path, so leave it empty. */
4079                 if (status->status != 'U' &&
4080                     sq_quote(newpath, 0, status->new.name) >= sizeof(newpath))
4081                         return REQ_QUIT;
4082         }
4084         if (opt_cdup[0] &&
4085             line->type != LINE_STAT_UNTRACKED &&
4086             !string_format_from(opt_cmd, &cmdsize, "cd %s;", opt_cdup))
4087                 return REQ_QUIT;
4089         switch (line->type) {
4090         case LINE_STAT_STAGED:
4091                 if (opt_no_head) {
4092                         if (!string_format_from(opt_cmd, &cmdsize,
4093                                                 STATUS_DIFF_NO_HEAD_SHOW_CMD,
4094                                                 newpath))
4095                                 return REQ_QUIT;
4096                 } else {
4097                         if (!string_format_from(opt_cmd, &cmdsize,
4098                                                 STATUS_DIFF_INDEX_SHOW_CMD,
4099                                                 oldpath, newpath))
4100                                 return REQ_QUIT;
4101                 }
4103                 if (status)
4104                         info = "Staged changes to %s";
4105                 else
4106                         info = "Staged changes";
4107                 break;
4109         case LINE_STAT_UNSTAGED:
4110                 if (!string_format_from(opt_cmd, &cmdsize,
4111                                         STATUS_DIFF_FILES_SHOW_CMD, oldpath, newpath))
4112                         return REQ_QUIT;
4113                 if (status)
4114                         info = "Unstaged changes to %s";
4115                 else
4116                         info = "Unstaged changes";
4117                 break;
4119         case LINE_STAT_UNTRACKED:
4120                 if (opt_pipe)
4121                         return REQ_QUIT;
4123                 if (!status) {
4124                         report("No file to show");
4125                         return REQ_NONE;
4126                 }
4128                 opt_pipe = fopen(status->new.name, "r");
4129                 info = "Untracked file %s";
4130                 break;
4132         case LINE_STAT_HEAD:
4133                 return REQ_NONE;
4135         default:
4136                 die("line type %d not handled in switch", line->type);
4137         }
4139         split = view_is_displayed(view) ? OPEN_SPLIT : 0;
4140         open_view(view, REQ_VIEW_STAGE, OPEN_RELOAD | split);
4141         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
4142                 if (status) {
4143                         stage_status = *status;
4144                 } else {
4145                         memset(&stage_status, 0, sizeof(stage_status));
4146                 }
4148                 stage_line_type = line->type;
4149                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
4150         }
4152         return REQ_NONE;
4155 static bool
4156 status_exists(struct status *status, enum line_type type)
4158         struct view *view = VIEW(REQ_VIEW_STATUS);
4159         struct line *line;
4161         for (line = view->line; line < view->line + view->lines; line++) {
4162                 struct status *pos = line->data;
4164                 if (line->type == type && pos &&
4165                     !strcmp(status->new.name, pos->new.name))
4166                         return TRUE;
4167         }
4169         return FALSE;
4173 static FILE *
4174 status_update_prepare(enum line_type type)
4176         char cmd[SIZEOF_STR];
4177         size_t cmdsize = 0;
4179         if (opt_cdup[0] &&
4180             type != LINE_STAT_UNTRACKED &&
4181             !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
4182                 return NULL;
4184         switch (type) {
4185         case LINE_STAT_STAGED:
4186                 string_add(cmd, cmdsize, "git update-index -z --index-info");
4187                 break;
4189         case LINE_STAT_UNSTAGED:
4190         case LINE_STAT_UNTRACKED:
4191                 string_add(cmd, cmdsize, "git update-index -z --add --remove --stdin");
4192                 break;
4194         default:
4195                 die("line type %d not handled in switch", type);
4196         }
4198         return popen(cmd, "w");
4201 static bool
4202 status_update_write(FILE *pipe, struct status *status, enum line_type type)
4204         char buf[SIZEOF_STR];
4205         size_t bufsize = 0;
4206         size_t written = 0;
4208         switch (type) {
4209         case LINE_STAT_STAGED:
4210                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
4211                                         status->old.mode,
4212                                         status->old.rev,
4213                                         status->old.name, 0))
4214                         return FALSE;
4215                 break;
4217         case LINE_STAT_UNSTAGED:
4218         case LINE_STAT_UNTRACKED:
4219                 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
4220                         return FALSE;
4221                 break;
4223         default:
4224                 die("line type %d not handled in switch", type);
4225         }
4227         while (!ferror(pipe) && written < bufsize) {
4228                 written += fwrite(buf + written, 1, bufsize - written, pipe);
4229         }
4231         return written == bufsize;
4234 static bool
4235 status_update_file(struct status *status, enum line_type type)
4237         FILE *pipe = status_update_prepare(type);
4238         bool result;
4240         if (!pipe)
4241                 return FALSE;
4243         result = status_update_write(pipe, status, type);
4244         pclose(pipe);
4245         return result;
4248 static bool
4249 status_update_files(struct view *view, struct line *line)
4251         FILE *pipe = status_update_prepare(line->type);
4252         bool result = TRUE;
4253         struct line *pos = view->line + view->lines;
4254         int files = 0;
4255         int file, done;
4257         if (!pipe)
4258                 return FALSE;
4260         for (pos = line; pos < view->line + view->lines && pos->data; pos++)
4261                 files++;
4263         for (file = 0, done = 0; result && file < files; line++, file++) {
4264                 int almost_done = file * 100 / files;
4266                 if (almost_done > done) {
4267                         done = almost_done;
4268                         string_format(view->ref, "updating file %u of %u (%d%% done)",
4269                                       file, files, done);
4270                         update_view_title(view);
4271                 }
4272                 result = status_update_write(pipe, line->data, line->type);
4273         }
4275         pclose(pipe);
4276         return result;
4279 static bool
4280 status_update(struct view *view)
4282         struct line *line = &view->line[view->lineno];
4284         assert(view->lines);
4286         if (!line->data) {
4287                 /* This should work even for the "On branch" line. */
4288                 if (line < view->line + view->lines && !line[1].data) {
4289                         report("Nothing to update");
4290                         return FALSE;
4291                 }
4293                 if (!status_update_files(view, line + 1)) {
4294                         report("Failed to update file status");
4295                         return FALSE;
4296                 }
4298         } else if (!status_update_file(line->data, line->type)) {
4299                 report("Failed to update file status");
4300                 return FALSE;
4301         }
4303         return TRUE;
4306 static enum request
4307 status_request(struct view *view, enum request request, struct line *line)
4309         struct status *status = line->data;
4311         switch (request) {
4312         case REQ_STATUS_UPDATE:
4313                 if (!status_update(view))
4314                         return REQ_NONE;
4315                 break;
4317         case REQ_STATUS_MERGE:
4318                 if (!status || status->status != 'U') {
4319                         report("Merging only possible for files with unmerged status ('U').");
4320                         return REQ_NONE;
4321                 }
4322                 open_mergetool(status->new.name);
4323                 break;
4325         case REQ_EDIT:
4326                 if (!status)
4327                         return request;
4329                 open_editor(status->status != '?', status->new.name);
4330                 break;
4332         case REQ_VIEW_BLAME:
4333                 if (status) {
4334                         string_copy(opt_file, status->new.name);
4335                         opt_ref[0] = 0;
4336                 }
4337                 return request;
4339         case REQ_ENTER:
4340                 /* After returning the status view has been split to
4341                  * show the stage view. No further reloading is
4342                  * necessary. */
4343                 status_enter(view, line);
4344                 return REQ_NONE;
4346         case REQ_REFRESH:
4347                 /* Simply reload the view. */
4348                 break;
4350         default:
4351                 return request;
4352         }
4354         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
4356         return REQ_NONE;
4359 static void
4360 status_select(struct view *view, struct line *line)
4362         struct status *status = line->data;
4363         char file[SIZEOF_STR] = "all files";
4364         char *text;
4365         char *key;
4367         if (status && !string_format(file, "'%s'", status->new.name))
4368                 return;
4370         if (!status && line[1].type == LINE_STAT_NONE)
4371                 line++;
4373         switch (line->type) {
4374         case LINE_STAT_STAGED:
4375                 text = "Press %s to unstage %s for commit";
4376                 break;
4378         case LINE_STAT_UNSTAGED:
4379                 text = "Press %s to stage %s for commit";
4380                 break;
4382         case LINE_STAT_UNTRACKED:
4383                 text = "Press %s to stage %s for addition";
4384                 break;
4386         case LINE_STAT_HEAD:
4387         case LINE_STAT_NONE:
4388                 text = "Nothing to update";
4389                 break;
4391         default:
4392                 die("line type %d not handled in switch", line->type);
4393         }
4395         if (status && status->status == 'U') {
4396                 text = "Press %s to resolve conflict in %s";
4397                 key = get_key(REQ_STATUS_MERGE);
4399         } else {
4400                 key = get_key(REQ_STATUS_UPDATE);
4401         }
4403         string_format(view->ref, text, key, file);
4406 static bool
4407 status_grep(struct view *view, struct line *line)
4409         struct status *status = line->data;
4410         enum { S_STATUS, S_NAME, S_END } state;
4411         char buf[2] = "?";
4412         regmatch_t pmatch;
4414         if (!status)
4415                 return FALSE;
4417         for (state = S_STATUS; state < S_END; state++) {
4418                 char *text;
4420                 switch (state) {
4421                 case S_NAME:    text = status->new.name;        break;
4422                 case S_STATUS:
4423                         buf[0] = status->status;
4424                         text = buf;
4425                         break;
4427                 default:
4428                         return FALSE;
4429                 }
4431                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4432                         return TRUE;
4433         }
4435         return FALSE;
4438 static struct view_ops status_ops = {
4439         "file",
4440         status_open,
4441         NULL,
4442         status_draw,
4443         status_request,
4444         status_grep,
4445         status_select,
4446 };
4449 static bool
4450 stage_diff_line(FILE *pipe, struct line *line)
4452         char *buf = line->data;
4453         size_t bufsize = strlen(buf);
4454         size_t written = 0;
4456         while (!ferror(pipe) && written < bufsize) {
4457                 written += fwrite(buf + written, 1, bufsize - written, pipe);
4458         }
4460         fputc('\n', pipe);
4462         return written == bufsize;
4465 static bool
4466 stage_diff_write(FILE *pipe, struct line *line, struct line *end)
4468         while (line < end) {
4469                 if (!stage_diff_line(pipe, line++))
4470                         return FALSE;
4471                 if (line->type == LINE_DIFF_CHUNK ||
4472                     line->type == LINE_DIFF_HEADER)
4473                         break;
4474         }
4476         return TRUE;
4479 static struct line *
4480 stage_diff_find(struct view *view, struct line *line, enum line_type type)
4482         for (; view->line < line; line--)
4483                 if (line->type == type)
4484                         return line;
4486         return NULL;
4489 static bool
4490 stage_update_chunk(struct view *view, struct line *chunk)
4492         char cmd[SIZEOF_STR];
4493         size_t cmdsize = 0;
4494         struct line *diff_hdr;
4495         FILE *pipe;
4497         diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
4498         if (!diff_hdr)
4499                 return FALSE;
4501         if (opt_cdup[0] &&
4502             !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
4503                 return FALSE;
4505         if (!string_format_from(cmd, &cmdsize,
4506                                 "git apply --whitespace=nowarn --cached %s - && "
4507                                 "git update-index -q --unmerged --refresh 2>/dev/null",
4508                                 stage_line_type == LINE_STAT_STAGED ? "-R" : ""))
4509                 return FALSE;
4511         pipe = popen(cmd, "w");
4512         if (!pipe)
4513                 return FALSE;
4515         if (!stage_diff_write(pipe, diff_hdr, chunk) ||
4516             !stage_diff_write(pipe, chunk, view->line + view->lines))
4517                 chunk = NULL;
4519         pclose(pipe);
4521         return chunk ? TRUE : FALSE;
4524 static bool
4525 stage_update(struct view *view, struct line *line)
4527         struct line *chunk = NULL;
4529         if (!opt_no_head && stage_line_type != LINE_STAT_UNTRACKED)
4530                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
4532         if (chunk) {
4533                 if (!stage_update_chunk(view, chunk)) {
4534                         report("Failed to apply chunk");
4535                         return FALSE;
4536                 }
4538         } else if (!stage_status.status) {
4539                 view = VIEW(REQ_VIEW_STATUS);
4541                 for (line = view->line; line < view->line + view->lines; line++)
4542                         if (line->type == stage_line_type)
4543                                 break;
4545                 if (!status_update_files(view, line + 1)) {
4546                         report("Failed to update files");
4547                         return FALSE;
4548                 }
4550         } else if (!status_update_file(&stage_status, stage_line_type)) {
4551                 report("Failed to update file");
4552                 return FALSE;
4553         }
4555         return TRUE;
4558 static enum request
4559 stage_request(struct view *view, enum request request, struct line *line)
4561         switch (request) {
4562         case REQ_STATUS_UPDATE:
4563                 if (!stage_update(view, line))
4564                         return REQ_NONE;
4565                 break;
4567         case REQ_EDIT:
4568                 if (!stage_status.new.name[0])
4569                         return request;
4571                 open_editor(stage_status.status != '?', stage_status.new.name);
4572                 break;
4574         case REQ_REFRESH:
4575                 /* Reload everything ... */
4576                 break;
4578         case REQ_VIEW_BLAME:
4579                 if (stage_status.new.name[0]) {
4580                         string_copy(opt_file, stage_status.new.name);
4581                         opt_ref[0] = 0;
4582                 }
4583                 return request;
4585         case REQ_ENTER:
4586                 return pager_request(view, request, line);
4588         default:
4589                 return request;
4590         }
4592         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD | OPEN_NOMAXIMIZE);
4594         /* Check whether the staged entry still exists, and close the
4595          * stage view if it doesn't. */
4596         if (!status_exists(&stage_status, stage_line_type))
4597                 return REQ_VIEW_CLOSE;
4599         if (stage_line_type == LINE_STAT_UNTRACKED)
4600                 opt_pipe = fopen(stage_status.new.name, "r");
4601         else
4602                 string_copy(opt_cmd, view->cmd);
4603         open_view(view, REQ_VIEW_STAGE, OPEN_RELOAD | OPEN_NOMAXIMIZE);
4605         return REQ_NONE;
4608 static struct view_ops stage_ops = {
4609         "line",
4610         NULL,
4611         pager_read,
4612         pager_draw,
4613         stage_request,
4614         pager_grep,
4615         pager_select,
4616 };
4619 /*
4620  * Revision graph
4621  */
4623 struct commit {
4624         char id[SIZEOF_REV];            /* SHA1 ID. */
4625         char title[128];                /* First line of the commit message. */
4626         char author[75];                /* Author of the commit. */
4627         struct tm time;                 /* Date from the author ident. */
4628         struct ref **refs;              /* Repository references. */
4629         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
4630         size_t graph_size;              /* The width of the graph array. */
4631         bool has_parents;               /* Rewritten --parents seen. */
4632 };
4634 /* Size of rev graph with no  "padding" columns */
4635 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
4637 struct rev_graph {
4638         struct rev_graph *prev, *next, *parents;
4639         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
4640         size_t size;
4641         struct commit *commit;
4642         size_t pos;
4643         unsigned int boundary:1;
4644 };
4646 /* Parents of the commit being visualized. */
4647 static struct rev_graph graph_parents[4];
4649 /* The current stack of revisions on the graph. */
4650 static struct rev_graph graph_stacks[4] = {
4651         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
4652         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
4653         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
4654         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
4655 };
4657 static inline bool
4658 graph_parent_is_merge(struct rev_graph *graph)
4660         return graph->parents->size > 1;
4663 static inline void
4664 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
4666         struct commit *commit = graph->commit;
4668         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
4669                 commit->graph[commit->graph_size++] = symbol;
4672 static void
4673 done_rev_graph(struct rev_graph *graph)
4675         if (graph_parent_is_merge(graph) &&
4676             graph->pos < graph->size - 1 &&
4677             graph->next->size == graph->size + graph->parents->size - 1) {
4678                 size_t i = graph->pos + graph->parents->size - 1;
4680                 graph->commit->graph_size = i * 2;
4681                 while (i < graph->next->size - 1) {
4682                         append_to_rev_graph(graph, ' ');
4683                         append_to_rev_graph(graph, '\\');
4684                         i++;
4685                 }
4686         }
4688         graph->size = graph->pos = 0;
4689         graph->commit = NULL;
4690         memset(graph->parents, 0, sizeof(*graph->parents));
4693 static void
4694 push_rev_graph(struct rev_graph *graph, char *parent)
4696         int i;
4698         /* "Collapse" duplicate parents lines.
4699          *
4700          * FIXME: This needs to also update update the drawn graph but
4701          * for now it just serves as a method for pruning graph lines. */
4702         for (i = 0; i < graph->size; i++)
4703                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
4704                         return;
4706         if (graph->size < SIZEOF_REVITEMS) {
4707                 string_copy_rev(graph->rev[graph->size++], parent);
4708         }
4711 static chtype
4712 get_rev_graph_symbol(struct rev_graph *graph)
4714         chtype symbol;
4716         if (graph->boundary)
4717                 symbol = REVGRAPH_BOUND;
4718         else if (graph->parents->size == 0)
4719                 symbol = REVGRAPH_INIT;
4720         else if (graph_parent_is_merge(graph))
4721                 symbol = REVGRAPH_MERGE;
4722         else if (graph->pos >= graph->size)
4723                 symbol = REVGRAPH_BRANCH;
4724         else
4725                 symbol = REVGRAPH_COMMIT;
4727         return symbol;
4730 static void
4731 draw_rev_graph(struct rev_graph *graph)
4733         struct rev_filler {
4734                 chtype separator, line;
4735         };
4736         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
4737         static struct rev_filler fillers[] = {
4738                 { ' ',  '|' },
4739                 { '`',  '.' },
4740                 { '\'', ' ' },
4741                 { '/',  ' ' },
4742         };
4743         chtype symbol = get_rev_graph_symbol(graph);
4744         struct rev_filler *filler;
4745         size_t i;
4747         if (opt_line_graphics)
4748                 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
4750         filler = &fillers[DEFAULT];
4752         for (i = 0; i < graph->pos; i++) {
4753                 append_to_rev_graph(graph, filler->line);
4754                 if (graph_parent_is_merge(graph->prev) &&
4755                     graph->prev->pos == i)
4756                         filler = &fillers[RSHARP];
4758                 append_to_rev_graph(graph, filler->separator);
4759         }
4761         /* Place the symbol for this revision. */
4762         append_to_rev_graph(graph, symbol);
4764         if (graph->prev->size > graph->size)
4765                 filler = &fillers[RDIAG];
4766         else
4767                 filler = &fillers[DEFAULT];
4769         i++;
4771         for (; i < graph->size; i++) {
4772                 append_to_rev_graph(graph, filler->separator);
4773                 append_to_rev_graph(graph, filler->line);
4774                 if (graph_parent_is_merge(graph->prev) &&
4775                     i < graph->prev->pos + graph->parents->size)
4776                         filler = &fillers[RSHARP];
4777                 if (graph->prev->size > graph->size)
4778                         filler = &fillers[LDIAG];
4779         }
4781         if (graph->prev->size > graph->size) {
4782                 append_to_rev_graph(graph, filler->separator);
4783                 if (filler->line != ' ')
4784                         append_to_rev_graph(graph, filler->line);
4785         }
4788 /* Prepare the next rev graph */
4789 static void
4790 prepare_rev_graph(struct rev_graph *graph)
4792         size_t i;
4794         /* First, traverse all lines of revisions up to the active one. */
4795         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
4796                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
4797                         break;
4799                 push_rev_graph(graph->next, graph->rev[graph->pos]);
4800         }
4802         /* Interleave the new revision parent(s). */
4803         for (i = 0; !graph->boundary && i < graph->parents->size; i++)
4804                 push_rev_graph(graph->next, graph->parents->rev[i]);
4806         /* Lastly, put any remaining revisions. */
4807         for (i = graph->pos + 1; i < graph->size; i++)
4808                 push_rev_graph(graph->next, graph->rev[i]);
4811 static void
4812 update_rev_graph(struct rev_graph *graph)
4814         /* If this is the finalizing update ... */
4815         if (graph->commit)
4816                 prepare_rev_graph(graph);
4818         /* Graph visualization needs a one rev look-ahead,
4819          * so the first update doesn't visualize anything. */
4820         if (!graph->prev->commit)
4821                 return;
4823         draw_rev_graph(graph->prev);
4824         done_rev_graph(graph->prev->prev);
4828 /*
4829  * Main view backend
4830  */
4832 static bool
4833 main_draw(struct view *view, struct line *line, unsigned int lineno)
4835         struct commit *commit = line->data;
4836         int col = 0;
4838         if (!*commit->author)
4839                 return FALSE;
4841         if (opt_date) {
4842                 col += draw_date(view, &commit->time, view->width);
4843                 if (col >= view->width)
4844                         return TRUE;
4845         }
4847         if (opt_author) {
4848                 int max_len;
4850                 max_len = view->width - col;
4851                 if (max_len > AUTHOR_COLS - 1)
4852                         max_len = AUTHOR_COLS - 1;
4853                 draw_text(view, LINE_MAIN_AUTHOR, commit->author, max_len, TRUE);
4854                 col += AUTHOR_COLS;
4855                 if (col >= view->width)
4856                         return TRUE;
4857         }
4859         if (opt_rev_graph && commit->graph_size) {
4860                 size_t graph_size = view->width - col;
4861                 size_t i;
4863                 set_view_attr(view, LINE_MAIN_REVGRAPH);
4864                 wmove(view->win, lineno, col);
4865                 if (graph_size > commit->graph_size)
4866                         graph_size = commit->graph_size;
4867                 /* Using waddch() instead of waddnstr() ensures that
4868                  * they'll be rendered correctly for the cursor line. */
4869                 for (i = 0; i < graph_size; i++)
4870                         waddch(view->win, commit->graph[i]);
4872                 col += commit->graph_size + 1;
4873                 if (col >= view->width)
4874                         return TRUE;
4875                 waddch(view->win, ' ');
4876         }
4878         set_view_attr(view, LINE_DEFAULT);
4879         wmove(view->win, lineno, col);
4881         if (opt_show_refs && commit->refs) {
4882                 size_t i = 0;
4884                 do {
4885                         enum line_type type;
4887                         if (commit->refs[i]->head)
4888                                 type = LINE_MAIN_HEAD;
4889                         else if (commit->refs[i]->ltag)
4890                                 type = LINE_MAIN_LOCAL_TAG;
4891                         else if (commit->refs[i]->tag)
4892                                 type = LINE_MAIN_TAG;
4893                         else if (commit->refs[i]->tracked)
4894                                 type = LINE_MAIN_TRACKED;
4895                         else if (commit->refs[i]->remote)
4896                                 type = LINE_MAIN_REMOTE;
4897                         else
4898                                 type = LINE_MAIN_REF;
4900                         col += draw_text(view, type, "[", view->width - col, TRUE);
4901                         col += draw_text(view, type, commit->refs[i]->name, view->width - col, TRUE);
4902                         col += draw_text(view, type, "]", view->width - col, TRUE);
4904                         col += draw_text(view, LINE_DEFAULT, " ", view->width - col, TRUE);
4905                         if (col >= view->width)
4906                                 return TRUE;
4907                 } while (commit->refs[i++]->next);
4908         }
4910         draw_text(view, LINE_DEFAULT, commit->title, view->width - col, TRUE);
4911         return TRUE;
4914 /* Reads git log --pretty=raw output and parses it into the commit struct. */
4915 static bool
4916 main_read(struct view *view, char *line)
4918         static struct rev_graph *graph = graph_stacks;
4919         enum line_type type;
4920         struct commit *commit;
4922         if (!line) {
4923                 if (!view->lines && !view->parent)
4924                         die("No revisions match the given arguments.");
4925                 update_rev_graph(graph);
4926                 return TRUE;
4927         }
4929         type = get_line_type(line);
4930         if (type == LINE_COMMIT) {
4931                 commit = calloc(1, sizeof(struct commit));
4932                 if (!commit)
4933                         return FALSE;
4935                 line += STRING_SIZE("commit ");
4936                 if (*line == '-') {
4937                         graph->boundary = 1;
4938                         line++;
4939                 }
4941                 string_copy_rev(commit->id, line);
4942                 commit->refs = get_refs(commit->id);
4943                 graph->commit = commit;
4944                 add_line_data(view, commit, LINE_MAIN_COMMIT);
4946                 while ((line = strchr(line, ' '))) {
4947                         line++;
4948                         push_rev_graph(graph->parents, line);
4949                         commit->has_parents = TRUE;
4950                 }
4951                 return TRUE;
4952         }
4954         if (!view->lines)
4955                 return TRUE;
4956         commit = view->line[view->lines - 1].data;
4958         switch (type) {
4959         case LINE_PARENT:
4960                 if (commit->has_parents)
4961                         break;
4962                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
4963                 break;
4965         case LINE_AUTHOR:
4966         {
4967                 /* Parse author lines where the name may be empty:
4968                  *      author  <email@address.tld> 1138474660 +0100
4969                  */
4970                 char *ident = line + STRING_SIZE("author ");
4971                 char *nameend = strchr(ident, '<');
4972                 char *emailend = strchr(ident, '>');
4974                 if (!nameend || !emailend)
4975                         break;
4977                 update_rev_graph(graph);
4978                 graph = graph->next;
4980                 *nameend = *emailend = 0;
4981                 ident = chomp_string(ident);
4982                 if (!*ident) {
4983                         ident = chomp_string(nameend + 1);
4984                         if (!*ident)
4985                                 ident = "Unknown";
4986                 }
4988                 string_ncopy(commit->author, ident, strlen(ident));
4990                 /* Parse epoch and timezone */
4991                 if (emailend[1] == ' ') {
4992                         char *secs = emailend + 2;
4993                         char *zone = strchr(secs, ' ');
4994                         time_t time = (time_t) atol(secs);
4996                         if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
4997                                 long tz;
4999                                 zone++;
5000                                 tz  = ('0' - zone[1]) * 60 * 60 * 10;
5001                                 tz += ('0' - zone[2]) * 60 * 60;
5002                                 tz += ('0' - zone[3]) * 60;
5003                                 tz += ('0' - zone[4]) * 60;
5005                                 if (zone[0] == '-')
5006                                         tz = -tz;
5008                                 time -= tz;
5009                         }
5011                         gmtime_r(&time, &commit->time);
5012                 }
5013                 break;
5014         }
5015         default:
5016                 /* Fill in the commit title if it has not already been set. */
5017                 if (commit->title[0])
5018                         break;
5020                 /* Require titles to start with a non-space character at the
5021                  * offset used by git log. */
5022                 if (strncmp(line, "    ", 4))
5023                         break;
5024                 line += 4;
5025                 /* Well, if the title starts with a whitespace character,
5026                  * try to be forgiving.  Otherwise we end up with no title. */
5027                 while (isspace(*line))
5028                         line++;
5029                 if (*line == '\0')
5030                         break;
5031                 /* FIXME: More graceful handling of titles; append "..." to
5032                  * shortened titles, etc. */
5034                 string_ncopy(commit->title, line, strlen(line));
5035         }
5037         return TRUE;
5040 static enum request
5041 main_request(struct view *view, enum request request, struct line *line)
5043         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
5045         if (request == REQ_ENTER)
5046                 open_view(view, REQ_VIEW_DIFF, flags);
5047         else
5048                 return request;
5050         return REQ_NONE;
5053 static bool
5054 grep_refs(struct ref **refs, regex_t *regex)
5056         regmatch_t pmatch;
5057         size_t i = 0;
5059         if (!refs)
5060                 return FALSE;
5061         do {
5062                 if (regexec(regex, refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
5063                         return TRUE;
5064         } while (refs[i++]->next);
5066         return FALSE;
5069 static bool
5070 main_grep(struct view *view, struct line *line)
5072         struct commit *commit = line->data;
5073         enum { S_TITLE, S_AUTHOR, S_DATE, S_REFS, S_END } state;
5074         char buf[DATE_COLS + 1];
5075         regmatch_t pmatch;
5077         for (state = S_TITLE; state < S_END; state++) {
5078                 char *text;
5080                 switch (state) {
5081                 case S_TITLE:   text = commit->title;   break;
5082                 case S_AUTHOR:
5083                         if (!opt_author)
5084                                 continue;
5085                         text = commit->author;
5086                         break;
5087                 case S_DATE:
5088                         if (!opt_date)
5089                                 continue;
5090                         if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
5091                                 continue;
5092                         text = buf;
5093                         break;
5094                 case S_REFS:
5095                         if (!opt_show_refs)
5096                                 continue;
5097                         if (grep_refs(commit->refs, view->regex) == TRUE)
5098                                 return TRUE;
5099                         continue;
5100                 default:
5101                         return FALSE;
5102                 }
5104                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
5105                         return TRUE;
5106         }
5108         return FALSE;
5111 static void
5112 main_select(struct view *view, struct line *line)
5114         struct commit *commit = line->data;
5116         string_copy_rev(view->ref, commit->id);
5117         string_copy_rev(ref_commit, view->ref);
5120 static struct view_ops main_ops = {
5121         "commit",
5122         NULL,
5123         main_read,
5124         main_draw,
5125         main_request,
5126         main_grep,
5127         main_select,
5128 };
5131 /*
5132  * Unicode / UTF-8 handling
5133  *
5134  * NOTE: Much of the following code for dealing with unicode is derived from
5135  * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
5136  * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
5137  */
5139 /* I've (over)annotated a lot of code snippets because I am not entirely
5140  * confident that the approach taken by this small UTF-8 interface is correct.
5141  * --jonas */
5143 static inline int
5144 unicode_width(unsigned long c)
5146         if (c >= 0x1100 &&
5147            (c <= 0x115f                         /* Hangul Jamo */
5148             || c == 0x2329
5149             || c == 0x232a
5150             || (c >= 0x2e80  && c <= 0xa4cf && c != 0x303f)
5151                                                 /* CJK ... Yi */
5152             || (c >= 0xac00  && c <= 0xd7a3)    /* Hangul Syllables */
5153             || (c >= 0xf900  && c <= 0xfaff)    /* CJK Compatibility Ideographs */
5154             || (c >= 0xfe30  && c <= 0xfe6f)    /* CJK Compatibility Forms */
5155             || (c >= 0xff00  && c <= 0xff60)    /* Fullwidth Forms */
5156             || (c >= 0xffe0  && c <= 0xffe6)
5157             || (c >= 0x20000 && c <= 0x2fffd)
5158             || (c >= 0x30000 && c <= 0x3fffd)))
5159                 return 2;
5161         if (c == '\t')
5162                 return opt_tab_size;
5164         return 1;
5167 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
5168  * Illegal bytes are set one. */
5169 static const unsigned char utf8_bytes[256] = {
5170         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,
5171         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,
5172         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,
5173         1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
5174         1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
5175         1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
5176         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,
5177         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,
5178 };
5180 /* Decode UTF-8 multi-byte representation into a unicode character. */
5181 static inline unsigned long
5182 utf8_to_unicode(const char *string, size_t length)
5184         unsigned long unicode;
5186         switch (length) {
5187         case 1:
5188                 unicode  =   string[0];
5189                 break;
5190         case 2:
5191                 unicode  =  (string[0] & 0x1f) << 6;
5192                 unicode +=  (string[1] & 0x3f);
5193                 break;
5194         case 3:
5195                 unicode  =  (string[0] & 0x0f) << 12;
5196                 unicode += ((string[1] & 0x3f) << 6);
5197                 unicode +=  (string[2] & 0x3f);
5198                 break;
5199         case 4:
5200                 unicode  =  (string[0] & 0x0f) << 18;
5201                 unicode += ((string[1] & 0x3f) << 12);
5202                 unicode += ((string[2] & 0x3f) << 6);
5203                 unicode +=  (string[3] & 0x3f);
5204                 break;
5205         case 5:
5206                 unicode  =  (string[0] & 0x0f) << 24;
5207                 unicode += ((string[1] & 0x3f) << 18);
5208                 unicode += ((string[2] & 0x3f) << 12);
5209                 unicode += ((string[3] & 0x3f) << 6);
5210                 unicode +=  (string[4] & 0x3f);
5211                 break;
5212         case 6:
5213                 unicode  =  (string[0] & 0x01) << 30;
5214                 unicode += ((string[1] & 0x3f) << 24);
5215                 unicode += ((string[2] & 0x3f) << 18);
5216                 unicode += ((string[3] & 0x3f) << 12);
5217                 unicode += ((string[4] & 0x3f) << 6);
5218                 unicode +=  (string[5] & 0x3f);
5219                 break;
5220         default:
5221                 die("Invalid unicode length");
5222         }
5224         /* Invalid characters could return the special 0xfffd value but NUL
5225          * should be just as good. */
5226         return unicode > 0xffff ? 0 : unicode;
5229 /* Calculates how much of string can be shown within the given maximum width
5230  * and sets trimmed parameter to non-zero value if all of string could not be
5231  * shown. If the reserve flag is TRUE, it will reserve at least one
5232  * trailing character, which can be useful when drawing a delimiter.
5233  *
5234  * Returns the number of bytes to output from string to satisfy max_width. */
5235 static size_t
5236 utf8_length(const char *string, size_t max_width, int *trimmed, bool reserve)
5238         const char *start = string;
5239         const char *end = strchr(string, '\0');
5240         unsigned char last_bytes = 0;
5241         size_t width = 0;
5243         *trimmed = 0;
5245         while (string < end) {
5246                 int c = *(unsigned char *) string;
5247                 unsigned char bytes = utf8_bytes[c];
5248                 size_t ucwidth;
5249                 unsigned long unicode;
5251                 if (string + bytes > end)
5252                         break;
5254                 /* Change representation to figure out whether
5255                  * it is a single- or double-width character. */
5257                 unicode = utf8_to_unicode(string, bytes);
5258                 /* FIXME: Graceful handling of invalid unicode character. */
5259                 if (!unicode)
5260                         break;
5262                 ucwidth = unicode_width(unicode);
5263                 width  += ucwidth;
5264                 if (width > max_width) {
5265                         *trimmed = 1;
5266                         if (reserve && width - ucwidth == max_width) {
5267                                 string -= last_bytes;
5268                         }
5269                         break;
5270                 }
5272                 string  += bytes;
5273                 last_bytes = bytes;
5274         }
5276         return string - start;
5280 /*
5281  * Status management
5282  */
5284 /* Whether or not the curses interface has been initialized. */
5285 static bool cursed = FALSE;
5287 /* The status window is used for polling keystrokes. */
5288 static WINDOW *status_win;
5290 static bool status_empty = TRUE;
5292 /* Update status and title window. */
5293 static void
5294 report(const char *msg, ...)
5296         struct view *view = display[current_view];
5298         if (input_mode)
5299                 return;
5301         if (!view) {
5302                 char buf[SIZEOF_STR];
5303                 va_list args;
5305                 va_start(args, msg);
5306                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
5307                         buf[sizeof(buf) - 1] = 0;
5308                         buf[sizeof(buf) - 2] = '.';
5309                         buf[sizeof(buf) - 3] = '.';
5310                         buf[sizeof(buf) - 4] = '.';
5311                 }
5312                 va_end(args);
5313                 die("%s", buf);
5314         }
5316         if (!status_empty || *msg) {
5317                 va_list args;
5319                 va_start(args, msg);
5321                 wmove(status_win, 0, 0);
5322                 if (*msg) {
5323                         vwprintw(status_win, msg, args);
5324                         status_empty = FALSE;
5325                 } else {
5326                         status_empty = TRUE;
5327                 }
5328                 wclrtoeol(status_win);
5329                 wrefresh(status_win);
5331                 va_end(args);
5332         }
5334         update_view_title(view);
5335         update_display_cursor(view);
5338 /* Controls when nodelay should be in effect when polling user input. */
5339 static void
5340 set_nonblocking_input(bool loading)
5342         static unsigned int loading_views;
5344         if ((loading == FALSE && loading_views-- == 1) ||
5345             (loading == TRUE  && loading_views++ == 0))
5346                 nodelay(status_win, loading);
5349 static void
5350 init_display(void)
5352         int x, y;
5354         /* Initialize the curses library */
5355         if (isatty(STDIN_FILENO)) {
5356                 cursed = !!initscr();
5357         } else {
5358                 /* Leave stdin and stdout alone when acting as a pager. */
5359                 FILE *io = fopen("/dev/tty", "r+");
5361                 if (!io)
5362                         die("Failed to open /dev/tty");
5363                 cursed = !!newterm(NULL, io, io);
5364         }
5366         if (!cursed)
5367                 die("Failed to initialize curses");
5369         nonl();         /* Tell curses not to do NL->CR/NL on output */
5370         cbreak();       /* Take input chars one at a time, no wait for \n */
5371         noecho();       /* Don't echo input */
5372         leaveok(stdscr, TRUE);
5374         if (has_colors())
5375                 init_colors();
5377         getmaxyx(stdscr, y, x);
5378         status_win = newwin(1, 0, y - 1, 0);
5379         if (!status_win)
5380                 die("Failed to create status window");
5382         /* Enable keyboard mapping */
5383         keypad(status_win, TRUE);
5384         wbkgdset(status_win, get_line_attr(LINE_STATUS));
5386         TABSIZE = opt_tab_size;
5387         if (opt_line_graphics) {
5388                 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
5389         }
5392 static char *
5393 read_prompt(const char *prompt)
5395         enum { READING, STOP, CANCEL } status = READING;
5396         static char buf[sizeof(opt_cmd) - STRING_SIZE("git \0")];
5397         int pos = 0;
5399         while (status == READING) {
5400                 struct view *view;
5401                 int i, key;
5403                 input_mode = TRUE;
5405                 foreach_view (view, i)
5406                         update_view(view);
5408                 input_mode = FALSE;
5410                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
5411                 wclrtoeol(status_win);
5413                 /* Refresh, accept single keystroke of input */
5414                 key = wgetch(status_win);
5415                 switch (key) {
5416                 case KEY_RETURN:
5417                 case KEY_ENTER:
5418                 case '\n':
5419                         status = pos ? STOP : CANCEL;
5420                         break;
5422                 case KEY_BACKSPACE:
5423                         if (pos > 0)
5424                                 pos--;
5425                         else
5426                                 status = CANCEL;
5427                         break;
5429                 case KEY_ESC:
5430                         status = CANCEL;
5431                         break;
5433                 case ERR:
5434                         break;
5436                 default:
5437                         if (pos >= sizeof(buf)) {
5438                                 report("Input string too long");
5439                                 return NULL;
5440                         }
5442                         if (isprint(key))
5443                                 buf[pos++] = (char) key;
5444                 }
5445         }
5447         /* Clear the status window */
5448         status_empty = FALSE;
5449         report("");
5451         if (status == CANCEL)
5452                 return NULL;
5454         buf[pos++] = 0;
5456         return buf;
5459 /*
5460  * Repository references
5461  */
5463 static struct ref *refs = NULL;
5464 static size_t refs_alloc = 0;
5465 static size_t refs_size = 0;
5467 /* Id <-> ref store */
5468 static struct ref ***id_refs = NULL;
5469 static size_t id_refs_alloc = 0;
5470 static size_t id_refs_size = 0;
5472 static struct ref **
5473 get_refs(char *id)
5475         struct ref ***tmp_id_refs;
5476         struct ref **ref_list = NULL;
5477         size_t ref_list_alloc = 0;
5478         size_t ref_list_size = 0;
5479         size_t i;
5481         for (i = 0; i < id_refs_size; i++)
5482                 if (!strcmp(id, id_refs[i][0]->id))
5483                         return id_refs[i];
5485         tmp_id_refs = realloc_items(id_refs, &id_refs_alloc, id_refs_size + 1,
5486                                     sizeof(*id_refs));
5487         if (!tmp_id_refs)
5488                 return NULL;
5490         id_refs = tmp_id_refs;
5492         for (i = 0; i < refs_size; i++) {
5493                 struct ref **tmp;
5495                 if (strcmp(id, refs[i].id))
5496                         continue;
5498                 tmp = realloc_items(ref_list, &ref_list_alloc,
5499                                     ref_list_size + 1, sizeof(*ref_list));
5500                 if (!tmp) {
5501                         if (ref_list)
5502                                 free(ref_list);
5503                         return NULL;
5504                 }
5506                 ref_list = tmp;
5507                 if (ref_list_size > 0)
5508                         ref_list[ref_list_size - 1]->next = 1;
5509                 ref_list[ref_list_size] = &refs[i];
5511                 /* XXX: The properties of the commit chains ensures that we can
5512                  * safely modify the shared ref. The repo references will
5513                  * always be similar for the same id. */
5514                 ref_list[ref_list_size]->next = 0;
5515                 ref_list_size++;
5516         }
5518         if (ref_list)
5519                 id_refs[id_refs_size++] = ref_list;
5521         return ref_list;
5524 static int
5525 read_ref(char *id, size_t idlen, char *name, size_t namelen)
5527         struct ref *ref;
5528         bool tag = FALSE;
5529         bool ltag = FALSE;
5530         bool remote = FALSE;
5531         bool tracked = FALSE;
5532         bool check_replace = FALSE;
5533         bool head = FALSE;
5535         if (!strncmp(name, "refs/tags/", STRING_SIZE("refs/tags/"))) {
5536                 if (!strcmp(name + namelen - 3, "^{}")) {
5537                         namelen -= 3;
5538                         name[namelen] = 0;
5539                         if (refs_size > 0 && refs[refs_size - 1].ltag == TRUE)
5540                                 check_replace = TRUE;
5541                 } else {
5542                         ltag = TRUE;
5543                 }
5545                 tag = TRUE;
5546                 namelen -= STRING_SIZE("refs/tags/");
5547                 name    += STRING_SIZE("refs/tags/");
5549         } else if (!strncmp(name, "refs/remotes/", STRING_SIZE("refs/remotes/"))) {
5550                 remote = TRUE;
5551                 namelen -= STRING_SIZE("refs/remotes/");
5552                 name    += STRING_SIZE("refs/remotes/");
5553                 tracked  = !strcmp(opt_remote, name);
5555         } else if (!strncmp(name, "refs/heads/", STRING_SIZE("refs/heads/"))) {
5556                 namelen -= STRING_SIZE("refs/heads/");
5557                 name    += STRING_SIZE("refs/heads/");
5558                 head     = !strncmp(opt_head, name, namelen);
5560         } else if (!strcmp(name, "HEAD")) {
5561                 opt_no_head = FALSE;
5562                 return OK;
5563         }
5565         if (check_replace && !strcmp(name, refs[refs_size - 1].name)) {
5566                 /* it's an annotated tag, replace the previous sha1 with the
5567                  * resolved commit id; relies on the fact git-ls-remote lists
5568                  * the commit id of an annotated tag right beofre the commit id
5569                  * it points to. */
5570                 refs[refs_size - 1].ltag = ltag;
5571                 string_copy_rev(refs[refs_size - 1].id, id);
5573                 return OK;
5574         }
5575         refs = realloc_items(refs, &refs_alloc, refs_size + 1, sizeof(*refs));
5576         if (!refs)
5577                 return ERR;
5579         ref = &refs[refs_size++];
5580         ref->name = malloc(namelen + 1);
5581         if (!ref->name)
5582                 return ERR;
5584         strncpy(ref->name, name, namelen);
5585         ref->name[namelen] = 0;
5586         ref->head = head;
5587         ref->tag = tag;
5588         ref->ltag = ltag;
5589         ref->remote = remote;
5590         ref->tracked = tracked;
5591         string_copy_rev(ref->id, id);
5593         return OK;
5596 static int
5597 load_refs(void)
5599         const char *cmd_env = getenv("TIG_LS_REMOTE");
5600         const char *cmd = cmd_env && *cmd_env ? cmd_env : TIG_LS_REMOTE;
5602         return read_properties(popen(cmd, "r"), "\t", read_ref);
5605 static int
5606 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
5608         if (!strcmp(name, "i18n.commitencoding"))
5609                 string_ncopy(opt_encoding, value, valuelen);
5611         if (!strcmp(name, "core.editor"))
5612                 string_ncopy(opt_editor, value, valuelen);
5614         /* branch.<head>.remote */
5615         if (*opt_head &&
5616             !strncmp(name, "branch.", 7) &&
5617             !strncmp(name + 7, opt_head, strlen(opt_head)) &&
5618             !strcmp(name + 7 + strlen(opt_head), ".remote"))
5619                 string_ncopy(opt_remote, value, valuelen);
5621         if (*opt_head && *opt_remote &&
5622             !strncmp(name, "branch.", 7) &&
5623             !strncmp(name + 7, opt_head, strlen(opt_head)) &&
5624             !strcmp(name + 7 + strlen(opt_head), ".merge")) {
5625                 size_t from = strlen(opt_remote);
5627                 if (!strncmp(value, "refs/heads/", STRING_SIZE("refs/heads/"))) {
5628                         value += STRING_SIZE("refs/heads/");
5629                         valuelen -= STRING_SIZE("refs/heads/");
5630                 }
5632                 if (!string_format_from(opt_remote, &from, "/%s", value))
5633                         opt_remote[0] = 0;
5634         }
5636         return OK;
5639 static int
5640 load_git_config(void)
5642         return read_properties(popen(GIT_CONFIG " --list", "r"),
5643                                "=", read_repo_config_option);
5646 static int
5647 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
5649         if (!opt_git_dir[0]) {
5650                 string_ncopy(opt_git_dir, name, namelen);
5652         } else if (opt_is_inside_work_tree == -1) {
5653                 /* This can be 3 different values depending on the
5654                  * version of git being used. If git-rev-parse does not
5655                  * understand --is-inside-work-tree it will simply echo
5656                  * the option else either "true" or "false" is printed.
5657                  * Default to true for the unknown case. */
5658                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
5660         } else if (opt_cdup[0] == ' ') {
5661                 string_ncopy(opt_cdup, name, namelen);
5662         } else {
5663                 if (!strncmp(name, "refs/heads/", STRING_SIZE("refs/heads/"))) {
5664                         namelen -= STRING_SIZE("refs/heads/");
5665                         name    += STRING_SIZE("refs/heads/");
5666                         string_ncopy(opt_head, name, namelen);
5667                 }
5668         }
5670         return OK;
5673 static int
5674 load_repo_info(void)
5676         int result;
5677         FILE *pipe = popen("(git rev-parse --git-dir --is-inside-work-tree "
5678                            " --show-cdup; git symbolic-ref HEAD) 2>/dev/null", "r");
5680         /* XXX: The line outputted by "--show-cdup" can be empty so
5681          * initialize it to something invalid to make it possible to
5682          * detect whether it has been set or not. */
5683         opt_cdup[0] = ' ';
5685         result = read_properties(pipe, "=", read_repo_info);
5686         if (opt_cdup[0] == ' ')
5687                 opt_cdup[0] = 0;
5689         return result;
5692 static int
5693 read_properties(FILE *pipe, const char *separators,
5694                 int (*read_property)(char *, size_t, char *, size_t))
5696         char buffer[BUFSIZ];
5697         char *name;
5698         int state = OK;
5700         if (!pipe)
5701                 return ERR;
5703         while (state == OK && (name = fgets(buffer, sizeof(buffer), pipe))) {
5704                 char *value;
5705                 size_t namelen;
5706                 size_t valuelen;
5708                 name = chomp_string(name);
5709                 namelen = strcspn(name, separators);
5711                 if (name[namelen]) {
5712                         name[namelen] = 0;
5713                         value = chomp_string(name + namelen + 1);
5714                         valuelen = strlen(value);
5716                 } else {
5717                         value = "";
5718                         valuelen = 0;
5719                 }
5721                 state = read_property(name, namelen, value, valuelen);
5722         }
5724         if (state != ERR && ferror(pipe))
5725                 state = ERR;
5727         pclose(pipe);
5729         return state;
5733 /*
5734  * Main
5735  */
5737 static void __NORETURN
5738 quit(int sig)
5740         /* XXX: Restore tty modes and let the OS cleanup the rest! */
5741         if (cursed)
5742                 endwin();
5743         exit(0);
5746 static void __NORETURN
5747 die(const char *err, ...)
5749         va_list args;
5751         endwin();
5753         va_start(args, err);
5754         fputs("tig: ", stderr);
5755         vfprintf(stderr, err, args);
5756         fputs("\n", stderr);
5757         va_end(args);
5759         exit(1);
5762 static void
5763 warn(const char *msg, ...)
5765         va_list args;
5767         va_start(args, msg);
5768         fputs("tig warning: ", stderr);
5769         vfprintf(stderr, msg, args);
5770         fputs("\n", stderr);
5771         va_end(args);
5774 int
5775 main(int argc, char *argv[])
5777         struct view *view;
5778         enum request request;
5779         size_t i;
5781         signal(SIGINT, quit);
5783         if (setlocale(LC_ALL, "")) {
5784                 char *codeset = nl_langinfo(CODESET);
5786                 string_ncopy(opt_codeset, codeset, strlen(codeset));
5787         }
5789         if (load_repo_info() == ERR)
5790                 die("Failed to load repo info.");
5792         if (load_options() == ERR)
5793                 die("Failed to load user config.");
5795         if (load_git_config() == ERR)
5796                 die("Failed to load repo config.");
5798         if (!parse_options(argc, argv))
5799                 return 0;
5801         /* Require a git repository unless when running in pager mode. */
5802         if (!opt_git_dir[0] && opt_request != REQ_VIEW_PAGER)
5803                 die("Not a git repository");
5805         if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
5806                 opt_utf8 = FALSE;
5808         if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
5809                 opt_iconv = iconv_open(opt_codeset, opt_encoding);
5810                 if (opt_iconv == ICONV_NONE)
5811                         die("Failed to initialize character set conversion");
5812         }
5814         if (*opt_git_dir && load_refs() == ERR)
5815                 die("Failed to load refs.");
5817         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
5818                 view->cmd_env = getenv(view->cmd_env);
5820         request = opt_request;
5822         init_display();
5824         while (view_driver(display[current_view], request)) {
5825                 int key;
5826                 int i;
5828                 foreach_view (view, i)
5829                         update_view(view);
5831                 /* Refresh, accept single keystroke of input */
5832                 key = wgetch(status_win);
5834                 /* wgetch() with nodelay() enabled returns ERR when there's no
5835                  * input. */
5836                 if (key == ERR) {
5837                         request = REQ_NONE;
5838                         continue;
5839                 }
5841                 request = get_keybinding(display[current_view]->keymap, key);
5843                 /* Some low-level request handling. This keeps access to
5844                  * status_win restricted. */
5845                 switch (request) {
5846                 case REQ_PROMPT:
5847                 {
5848                         char *cmd = read_prompt(":");
5850                         if (cmd && string_format(opt_cmd, "git %s", cmd)) {
5851                                 if (strncmp(cmd, "show", 4) && isspace(cmd[4])) {
5852                                         opt_request = REQ_VIEW_DIFF;
5853                                 } else {
5854                                         opt_request = REQ_VIEW_PAGER;
5855                                 }
5856                                 break;
5857                         }
5859                         request = REQ_NONE;
5860                         break;
5861                 }
5862                 case REQ_SEARCH:
5863                 case REQ_SEARCH_BACK:
5864                 {
5865                         const char *prompt = request == REQ_SEARCH
5866                                            ? "/" : "?";
5867                         char *search = read_prompt(prompt);
5869                         if (search)
5870                                 string_ncopy(opt_search, search, strlen(search));
5871                         else
5872                                 request = REQ_NONE;
5873                         break;
5874                 }
5875                 case REQ_SCREEN_RESIZE:
5876                 {
5877                         int height, width;
5879                         getmaxyx(stdscr, height, width);
5881                         /* Resize the status view and let the view driver take
5882                          * care of resizing the displayed views. */
5883                         wresize(status_win, 1, width);
5884                         mvwin(status_win, height - 1, 0);
5885                         wrefresh(status_win);
5886                         break;
5887                 }
5888                 default:
5889                         break;
5890                 }
5891         }
5893         quit(0);
5895         return 0;