Code

Remove deprecated options and cleanup option parsing
[tig.git] / tig.c
1 /* Copyright (c) 2006-2008 Jonas Fonseca <fonseca@diku.dk>
2  *
3  * This program is free software; you can redistribute it and/or
4  * modify it under the terms of the GNU General Public License as
5  * published by the Free Software Foundation; either version 2 of
6  * the License, or (at your option) any later version.
7  *
8  * This program is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11  * GNU General Public License for more details.
12  */
14 #ifdef HAVE_CONFIG_H
15 #include "config.h"
16 #endif
18 #ifndef TIG_VERSION
19 #define TIG_VERSION "unknown-version"
20 #endif
22 #ifndef DEBUG
23 #define NDEBUG
24 #endif
26 #include <assert.h>
27 #include <errno.h>
28 #include <ctype.h>
29 #include <signal.h>
30 #include <stdarg.h>
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <sys/types.h>
35 #include <sys/stat.h>
36 #include <unistd.h>
37 #include <time.h>
39 #include <regex.h>
41 #include <locale.h>
42 #include <langinfo.h>
43 #include <iconv.h>
45 /* ncurses(3): Must be defined to have extended wide-character functions. */
46 #define _XOPEN_SOURCE_EXTENDED
48 #include <curses.h>
50 #if __GNUC__ >= 3
51 #define __NORETURN __attribute__((__noreturn__))
52 #else
53 #define __NORETURN
54 #endif
56 static void __NORETURN die(const char *err, ...);
57 static void warn(const char *msg, ...);
58 static void report(const char *msg, ...);
59 static int read_properties(FILE *pipe, const char *separators, int (*read)(char *, size_t, char *, size_t));
60 static void set_nonblocking_input(bool loading);
61 static size_t utf8_length(const char *string, size_t max_width, int *trimmed, bool reserve);
63 #define ABS(x)          ((x) >= 0  ? (x) : -(x))
64 #define MIN(x, y)       ((x) < (y) ? (x) :  (y))
66 #define ARRAY_SIZE(x)   (sizeof(x) / sizeof(x[0]))
67 #define STRING_SIZE(x)  (sizeof(x) - 1)
69 #define SIZEOF_STR      1024    /* Default string size. */
70 #define SIZEOF_REF      256     /* Size of symbolic or SHA1 ID. */
71 #define SIZEOF_REV      41      /* Holds a SHA-1 and an ending NUL */
73 /* Revision graph */
75 #define REVGRAPH_INIT   'I'
76 #define REVGRAPH_MERGE  'M'
77 #define REVGRAPH_BRANCH '+'
78 #define REVGRAPH_COMMIT '*'
79 #define REVGRAPH_BOUND  '^'
80 #define REVGRAPH_LINE   '|'
82 #define SIZEOF_REVGRAPH 19      /* Size of revision ancestry graphics. */
84 /* This color name can be used to refer to the default term colors. */
85 #define COLOR_DEFAULT   (-1)
87 #define ICONV_NONE      ((iconv_t) -1)
88 #ifndef ICONV_CONST
89 #define ICONV_CONST     /* nothing */
90 #endif
92 /* The format and size of the date column in the main view. */
93 #define DATE_FORMAT     "%Y-%m-%d %H:%M"
94 #define DATE_COLS       STRING_SIZE("2006-04-29 14:21 ")
96 #define AUTHOR_COLS     20
97 #define ID_COLS         8
99 /* The default interval between line numbers. */
100 #define NUMBER_INTERVAL 5
102 #define TABSIZE         8
104 #define SCALE_SPLIT_VIEW(height)        ((height) * 2 / 3)
106 #ifndef GIT_CONFIG
107 #define GIT_CONFIG "git config"
108 #endif
110 #define TIG_LS_REMOTE \
111         "git ls-remote $(git rev-parse --git-dir) 2>/dev/null"
113 #define TIG_DIFF_CMD \
114         "git show --pretty=fuller --no-color --root --patch-with-stat --find-copies-harder -C %s 2>/dev/null"
116 #define TIG_LOG_CMD     \
117         "git log --no-color --cc --stat -n100 %s 2>/dev/null"
119 #define TIG_MAIN_CMD \
120         "git log --no-color --topo-order --parents --boundary --pretty=raw %s 2>/dev/null"
122 #define TIG_TREE_CMD    \
123         "git ls-tree %s %s"
125 #define TIG_BLOB_CMD    \
126         "git cat-file blob %s"
128 /* XXX: Needs to be defined to the empty string. */
129 #define TIG_HELP_CMD    ""
130 #define TIG_PAGER_CMD   ""
131 #define TIG_STATUS_CMD  ""
132 #define TIG_STAGE_CMD   ""
133 #define TIG_BLAME_CMD   ""
135 /* Some ascii-shorthands fitted into the ncurses namespace. */
136 #define KEY_TAB         '\t'
137 #define KEY_RETURN      '\r'
138 #define KEY_ESC         27
141 struct ref {
142         char *name;             /* Ref name; tag or head names are shortened. */
143         char id[SIZEOF_REV];    /* Commit SHA1 ID */
144         unsigned int tag:1;     /* Is it a tag? */
145         unsigned int ltag:1;    /* If so, is the tag local? */
146         unsigned int remote:1;  /* Is it a remote ref? */
147         unsigned int next:1;    /* For ref lists: are there more refs? */
148 };
150 static struct ref **get_refs(char *id);
152 struct int_map {
153         const char *name;
154         int namelen;
155         int value;
156 };
158 static int
159 set_from_int_map(struct int_map *map, size_t map_size,
160                  int *value, const char *name, int namelen)
163         int i;
165         for (i = 0; i < map_size; i++)
166                 if (namelen == map[i].namelen &&
167                     !strncasecmp(name, map[i].name, namelen)) {
168                         *value = map[i].value;
169                         return OK;
170                 }
172         return ERR;
176 /*
177  * String helpers
178  */
180 static inline void
181 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
183         if (srclen > dstlen - 1)
184                 srclen = dstlen - 1;
186         strncpy(dst, src, srclen);
187         dst[srclen] = 0;
190 /* Shorthands for safely copying into a fixed buffer. */
192 #define string_copy(dst, src) \
193         string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
195 #define string_ncopy(dst, src, srclen) \
196         string_ncopy_do(dst, sizeof(dst), src, srclen)
198 #define string_copy_rev(dst, src) \
199         string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
201 #define string_add(dst, from, src) \
202         string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
204 static char *
205 chomp_string(char *name)
207         int namelen;
209         while (isspace(*name))
210                 name++;
212         namelen = strlen(name) - 1;
213         while (namelen > 0 && isspace(name[namelen]))
214                 name[namelen--] = 0;
216         return name;
219 static bool
220 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
222         va_list args;
223         size_t pos = bufpos ? *bufpos : 0;
225         va_start(args, fmt);
226         pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
227         va_end(args);
229         if (bufpos)
230                 *bufpos = pos;
232         return pos >= bufsize ? FALSE : TRUE;
235 #define string_format(buf, fmt, args...) \
236         string_nformat(buf, sizeof(buf), NULL, fmt, args)
238 #define string_format_from(buf, from, fmt, args...) \
239         string_nformat(buf, sizeof(buf), from, fmt, args)
241 static int
242 string_enum_compare(const char *str1, const char *str2, int len)
244         size_t i;
246 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
248         /* Diff-Header == DIFF_HEADER */
249         for (i = 0; i < len; i++) {
250                 if (toupper(str1[i]) == toupper(str2[i]))
251                         continue;
253                 if (string_enum_sep(str1[i]) &&
254                     string_enum_sep(str2[i]))
255                         continue;
257                 return str1[i] - str2[i];
258         }
260         return 0;
263 /* Shell quoting
264  *
265  * NOTE: The following is a slightly modified copy of the git project's shell
266  * quoting routines found in the quote.c file.
267  *
268  * Help to copy the thing properly quoted for the shell safety.  any single
269  * quote is replaced with '\'', any exclamation point is replaced with '\!',
270  * and the whole thing is enclosed in a
271  *
272  * E.g.
273  *  original     sq_quote     result
274  *  name     ==> name      ==> 'name'
275  *  a b      ==> a b       ==> 'a b'
276  *  a'b      ==> a'\''b    ==> 'a'\''b'
277  *  a!b      ==> a'\!'b    ==> 'a'\!'b'
278  */
280 static size_t
281 sq_quote(char buf[SIZEOF_STR], size_t bufsize, const char *src)
283         char c;
285 #define BUFPUT(x) do { if (bufsize < SIZEOF_STR) buf[bufsize++] = (x); } while (0)
287         BUFPUT('\'');
288         while ((c = *src++)) {
289                 if (c == '\'' || c == '!') {
290                         BUFPUT('\'');
291                         BUFPUT('\\');
292                         BUFPUT(c);
293                         BUFPUT('\'');
294                 } else {
295                         BUFPUT(c);
296                 }
297         }
298         BUFPUT('\'');
300         if (bufsize < SIZEOF_STR)
301                 buf[bufsize] = 0;
303         return bufsize;
307 /*
308  * User requests
309  */
311 #define REQ_INFO \
312         /* XXX: Keep the view request first and in sync with views[]. */ \
313         REQ_GROUP("View switching") \
314         REQ_(VIEW_MAIN,         "Show main view"), \
315         REQ_(VIEW_DIFF,         "Show diff view"), \
316         REQ_(VIEW_LOG,          "Show log view"), \
317         REQ_(VIEW_TREE,         "Show tree view"), \
318         REQ_(VIEW_BLOB,         "Show blob view"), \
319         REQ_(VIEW_BLAME,        "Show blame view"), \
320         REQ_(VIEW_HELP,         "Show help page"), \
321         REQ_(VIEW_PAGER,        "Show pager view"), \
322         REQ_(VIEW_STATUS,       "Show status view"), \
323         REQ_(VIEW_STAGE,        "Show stage view"), \
324         \
325         REQ_GROUP("View manipulation") \
326         REQ_(ENTER,             "Enter current line and scroll"), \
327         REQ_(NEXT,              "Move to next"), \
328         REQ_(PREVIOUS,          "Move to previous"), \
329         REQ_(VIEW_NEXT,         "Move focus to next view"), \
330         REQ_(REFRESH,           "Reload and refresh"), \
331         REQ_(VIEW_CLOSE,        "Close the current view"), \
332         REQ_(QUIT,              "Close all views and quit"), \
333         \
334         REQ_GROUP("Cursor navigation") \
335         REQ_(MOVE_UP,           "Move cursor one line up"), \
336         REQ_(MOVE_DOWN,         "Move cursor one line down"), \
337         REQ_(MOVE_PAGE_DOWN,    "Move cursor one page down"), \
338         REQ_(MOVE_PAGE_UP,      "Move cursor one page up"), \
339         REQ_(MOVE_FIRST_LINE,   "Move cursor to first line"), \
340         REQ_(MOVE_LAST_LINE,    "Move cursor to last line"), \
341         \
342         REQ_GROUP("Scrolling") \
343         REQ_(SCROLL_LINE_UP,    "Scroll one line up"), \
344         REQ_(SCROLL_LINE_DOWN,  "Scroll one line down"), \
345         REQ_(SCROLL_PAGE_UP,    "Scroll one page up"), \
346         REQ_(SCROLL_PAGE_DOWN,  "Scroll one page down"), \
347         \
348         REQ_GROUP("Searching") \
349         REQ_(SEARCH,            "Search the view"), \
350         REQ_(SEARCH_BACK,       "Search backwards in the view"), \
351         REQ_(FIND_NEXT,         "Find next search match"), \
352         REQ_(FIND_PREV,         "Find previous search match"), \
353         \
354         REQ_GROUP("Misc") \
355         REQ_(PROMPT,            "Bring up the prompt"), \
356         REQ_(SCREEN_REDRAW,     "Redraw the screen"), \
357         REQ_(SCREEN_RESIZE,     "Resize the screen"), \
358         REQ_(SHOW_VERSION,      "Show version information"), \
359         REQ_(STOP_LOADING,      "Stop all loading views"), \
360         REQ_(TOGGLE_LINENO,     "Toggle line numbers"), \
361         REQ_(TOGGLE_DATE,       "Toggle date display"), \
362         REQ_(TOGGLE_AUTHOR,     "Toggle author display"), \
363         REQ_(TOGGLE_REV_GRAPH,  "Toggle revision graph visualization"), \
364         REQ_(TOGGLE_REFS,       "Toggle reference display (tags/branches)"), \
365         REQ_(STATUS_UPDATE,     "Update file status"), \
366         REQ_(STATUS_MERGE,      "Merge file using external tool"), \
367         REQ_(TREE_PARENT,       "Switch to parent directory in tree view"), \
368         REQ_(EDIT,              "Open in editor"), \
369         REQ_(NONE,              "Do nothing")
372 /* User action requests. */
373 enum request {
374 #define REQ_GROUP(help)
375 #define REQ_(req, help) REQ_##req
377         /* Offset all requests to avoid conflicts with ncurses getch values. */
378         REQ_OFFSET = KEY_MAX + 1,
379         REQ_INFO
381 #undef  REQ_GROUP
382 #undef  REQ_
383 };
385 struct request_info {
386         enum request request;
387         char *name;
388         int namelen;
389         char *help;
390 };
392 static struct request_info req_info[] = {
393 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
394 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
395         REQ_INFO
396 #undef  REQ_GROUP
397 #undef  REQ_
398 };
400 static enum request
401 get_request(const char *name)
403         int namelen = strlen(name);
404         int i;
406         for (i = 0; i < ARRAY_SIZE(req_info); i++)
407                 if (req_info[i].namelen == namelen &&
408                     !string_enum_compare(req_info[i].name, name, namelen))
409                         return req_info[i].request;
411         return REQ_NONE;
415 /*
416  * Options
417  */
419 static const char usage[] =
420 "tig " TIG_VERSION " (" __DATE__ ")\n"
421 "\n"
422 "Usage: tig        [options] [revs] [--] [paths]\n"
423 "   or: tig show   [options] [revs] [--] [paths]\n"
424 "   or: tig blame  [rev] path\n"
425 "   or: tig status\n"
426 "   or: tig <      [git command output]\n"
427 "\n"
428 "Options:\n"
429 "  -v, --version   Show version and exit\n"
430 "  -h, --help      Show help message and exit";
432 /* Option and state variables. */
433 static bool opt_date                    = TRUE;
434 static bool opt_author                  = TRUE;
435 static bool opt_line_number             = FALSE;
436 static bool opt_rev_graph               = FALSE;
437 static bool opt_show_refs               = TRUE;
438 static int opt_num_interval             = NUMBER_INTERVAL;
439 static int opt_tab_size                 = TABSIZE;
440 static enum request opt_request         = REQ_VIEW_MAIN;
441 static char opt_cmd[SIZEOF_STR]         = "";
442 static char opt_path[SIZEOF_STR]        = "";
443 static char opt_file[SIZEOF_STR]        = "";
444 static char opt_ref[SIZEOF_REF]         = "";
445 static FILE *opt_pipe                   = NULL;
446 static char opt_encoding[20]            = "UTF-8";
447 static bool opt_utf8                    = TRUE;
448 static char opt_codeset[20]             = "UTF-8";
449 static iconv_t opt_iconv                = ICONV_NONE;
450 static char opt_search[SIZEOF_STR]      = "";
451 static char opt_cdup[SIZEOF_STR]        = "";
452 static char opt_git_dir[SIZEOF_STR]     = "";
453 static signed char opt_is_inside_work_tree      = -1; /* set to TRUE or FALSE */
454 static char opt_editor[SIZEOF_STR]      = "";
456 static bool
457 parse_options(int argc, char *argv[])
459         size_t buf_size;
460         char *subcommand;
461         bool seen_dashdash = FALSE;
462         int i;
464         if (argc <= 1)
465                 return TRUE;
467         subcommand = argv[1];
468         if (!strcmp(subcommand, "status") || !strcmp(subcommand, "-S")) {
469                 opt_request = REQ_VIEW_STATUS;
470                 if (!strcmp(subcommand, "-S"))
471                         warn("`-S' has been deprecated; use `tig status' instead");
472                 if (argc > 2)
473                         warn("ignoring arguments after `%s'", subcommand);
474                 return TRUE;
476         } else if (!strcmp(subcommand, "blame")) {
477                 opt_request = REQ_VIEW_BLAME;
478                 if (argc <= 2 || argc > 4)
479                         die("invalid number of options to blame\n\n%s", usage);
481                 i = 2;
482                 if (argc == 4) {
483                         string_ncopy(opt_ref, argv[i], strlen(argv[i]));
484                         i++;
485                 }
487                 string_ncopy(opt_file, argv[i], strlen(argv[i]));
488                 return TRUE;
490         } else if (!strcmp(subcommand, "show")) {
491                 opt_request = REQ_VIEW_DIFF;
493         } else if (!strcmp(subcommand, "log") || !strcmp(subcommand, "diff")) {
494                 opt_request = subcommand[0] == 'l'
495                             ? REQ_VIEW_LOG : REQ_VIEW_DIFF;
496                 warn("`tig %s' has been deprecated", subcommand);
498         } else {
499                 subcommand = NULL;
500         }
502         if (!subcommand)
503                 /* XXX: This is vulnerable to the user overriding
504                  * options required for the main view parser. */
505                 string_copy(opt_cmd, "git log --no-color --pretty=raw --boundary --parents");
506         else
507                 string_format(opt_cmd, "git %s", subcommand);
509         buf_size = strlen(opt_cmd);
511         for (i = 1 + !!subcommand; i < argc; i++) {
512                 char *opt = argv[i];
514                 if (seen_dashdash || !strcmp(opt, "--")) {
515                         seen_dashdash = TRUE;
517                 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
518                         printf("tig version %s\n", TIG_VERSION);
519                         return FALSE;
521                 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
522                         printf(usage);
523                         return FALSE;
524                 }
526                 opt_cmd[buf_size++] = ' ';
527                 buf_size = sq_quote(opt_cmd, buf_size, opt);
528                 if (buf_size >= sizeof(opt_cmd))
529                         die("command too long");
530         }
532         if (!isatty(STDIN_FILENO)) {
533                 opt_request = REQ_VIEW_PAGER;
534                 opt_pipe = stdin;
535                 buf_size = 0;
536         }
538         opt_cmd[buf_size] = 0;
540         return TRUE;
544 /*
545  * Line-oriented content detection.
546  */
548 #define LINE_INFO \
549 LINE(DIFF_HEADER,  "diff --git ",       COLOR_YELLOW,   COLOR_DEFAULT,  0), \
550 LINE(DIFF_CHUNK,   "@@",                COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
551 LINE(DIFF_ADD,     "+",                 COLOR_GREEN,    COLOR_DEFAULT,  0), \
552 LINE(DIFF_DEL,     "-",                 COLOR_RED,      COLOR_DEFAULT,  0), \
553 LINE(DIFF_INDEX,        "index ",         COLOR_BLUE,   COLOR_DEFAULT,  0), \
554 LINE(DIFF_OLDMODE,      "old file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
555 LINE(DIFF_NEWMODE,      "new file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
556 LINE(DIFF_COPY_FROM,    "copy from",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
557 LINE(DIFF_COPY_TO,      "copy to",        COLOR_YELLOW, COLOR_DEFAULT,  0), \
558 LINE(DIFF_RENAME_FROM,  "rename from",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
559 LINE(DIFF_RENAME_TO,    "rename to",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
560 LINE(DIFF_SIMILARITY,   "similarity ",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
561 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
562 LINE(DIFF_TREE,         "diff-tree ",     COLOR_BLUE,   COLOR_DEFAULT,  0), \
563 LINE(PP_AUTHOR,    "Author: ",          COLOR_CYAN,     COLOR_DEFAULT,  0), \
564 LINE(PP_COMMIT,    "Commit: ",          COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
565 LINE(PP_MERGE,     "Merge: ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
566 LINE(PP_DATE,      "Date:   ",          COLOR_YELLOW,   COLOR_DEFAULT,  0), \
567 LINE(PP_ADATE,     "AuthorDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
568 LINE(PP_CDATE,     "CommitDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
569 LINE(PP_REFS,      "Refs: ",            COLOR_RED,      COLOR_DEFAULT,  0), \
570 LINE(COMMIT,       "commit ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
571 LINE(PARENT,       "parent ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
572 LINE(TREE,         "tree ",             COLOR_BLUE,     COLOR_DEFAULT,  0), \
573 LINE(AUTHOR,       "author ",           COLOR_CYAN,     COLOR_DEFAULT,  0), \
574 LINE(COMMITTER,    "committer ",        COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
575 LINE(SIGNOFF,      "    Signed-off-by", COLOR_YELLOW,   COLOR_DEFAULT,  0), \
576 LINE(ACKED,        "    Acked-by",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
577 LINE(DEFAULT,      "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
578 LINE(CURSOR,       "",                  COLOR_WHITE,    COLOR_GREEN,    A_BOLD), \
579 LINE(STATUS,       "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
580 LINE(TITLE_BLUR,   "",                  COLOR_WHITE,    COLOR_BLUE,     0), \
581 LINE(TITLE_FOCUS,  "",                  COLOR_WHITE,    COLOR_BLUE,     A_BOLD), \
582 LINE(MAIN_DATE,    "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
583 LINE(MAIN_AUTHOR,  "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
584 LINE(MAIN_COMMIT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
585 LINE(MAIN_DELIM,   "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
586 LINE(MAIN_TAG,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
587 LINE(MAIN_LOCAL_TAG,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
588 LINE(MAIN_REMOTE,  "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_BOLD), \
589 LINE(MAIN_REF,     "",                  COLOR_CYAN,     COLOR_DEFAULT,  A_BOLD), \
590 LINE(MAIN_REVGRAPH,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
591 LINE(TREE_DIR,     "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
592 LINE(TREE_FILE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
593 LINE(STAT_SECTION, "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
594 LINE(STAT_NONE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
595 LINE(STAT_STAGED,  "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
596 LINE(STAT_UNSTAGED,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
597 LINE(STAT_UNTRACKED,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
598 LINE(BLAME_DATE,    "",                 COLOR_BLUE,     COLOR_DEFAULT,  0), \
599 LINE(BLAME_AUTHOR,  "",                 COLOR_GREEN,    COLOR_DEFAULT,  0), \
600 LINE(BLAME_COMMIT, "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
601 LINE(BLAME_ID,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
602 LINE(BLAME_LINENO, "",                  COLOR_CYAN,     COLOR_DEFAULT,  0)
604 enum line_type {
605 #define LINE(type, line, fg, bg, attr) \
606         LINE_##type
607         LINE_INFO
608 #undef  LINE
609 };
611 struct line_info {
612         const char *name;       /* Option name. */
613         int namelen;            /* Size of option name. */
614         const char *line;       /* The start of line to match. */
615         int linelen;            /* Size of string to match. */
616         int fg, bg, attr;       /* Color and text attributes for the lines. */
617 };
619 static struct line_info line_info[] = {
620 #define LINE(type, line, fg, bg, attr) \
621         { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
622         LINE_INFO
623 #undef  LINE
624 };
626 static enum line_type
627 get_line_type(char *line)
629         int linelen = strlen(line);
630         enum line_type type;
632         for (type = 0; type < ARRAY_SIZE(line_info); type++)
633                 /* Case insensitive search matches Signed-off-by lines better. */
634                 if (linelen >= line_info[type].linelen &&
635                     !strncasecmp(line_info[type].line, line, line_info[type].linelen))
636                         return type;
638         return LINE_DEFAULT;
641 static inline int
642 get_line_attr(enum line_type type)
644         assert(type < ARRAY_SIZE(line_info));
645         return COLOR_PAIR(type) | line_info[type].attr;
648 static struct line_info *
649 get_line_info(char *name, int namelen)
651         enum line_type type;
653         for (type = 0; type < ARRAY_SIZE(line_info); type++)
654                 if (namelen == line_info[type].namelen &&
655                     !string_enum_compare(line_info[type].name, name, namelen))
656                         return &line_info[type];
658         return NULL;
661 static void
662 init_colors(void)
664         int default_bg = line_info[LINE_DEFAULT].bg;
665         int default_fg = line_info[LINE_DEFAULT].fg;
666         enum line_type type;
668         start_color();
670         if (assume_default_colors(default_fg, default_bg) == ERR) {
671                 default_bg = COLOR_BLACK;
672                 default_fg = COLOR_WHITE;
673         }
675         for (type = 0; type < ARRAY_SIZE(line_info); type++) {
676                 struct line_info *info = &line_info[type];
677                 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
678                 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
680                 init_pair(type, fg, bg);
681         }
684 struct line {
685         enum line_type type;
687         /* State flags */
688         unsigned int selected:1;
689         unsigned int dirty:1;
691         void *data;             /* User data */
692 };
695 /*
696  * Keys
697  */
699 struct keybinding {
700         int alias;
701         enum request request;
702         struct keybinding *next;
703 };
705 static struct keybinding default_keybindings[] = {
706         /* View switching */
707         { 'm',          REQ_VIEW_MAIN },
708         { 'd',          REQ_VIEW_DIFF },
709         { 'l',          REQ_VIEW_LOG },
710         { 't',          REQ_VIEW_TREE },
711         { 'f',          REQ_VIEW_BLOB },
712         { 'B',          REQ_VIEW_BLAME },
713         { 'p',          REQ_VIEW_PAGER },
714         { 'h',          REQ_VIEW_HELP },
715         { 'S',          REQ_VIEW_STATUS },
716         { 'c',          REQ_VIEW_STAGE },
718         /* View manipulation */
719         { 'q',          REQ_VIEW_CLOSE },
720         { KEY_TAB,      REQ_VIEW_NEXT },
721         { KEY_RETURN,   REQ_ENTER },
722         { KEY_UP,       REQ_PREVIOUS },
723         { KEY_DOWN,     REQ_NEXT },
724         { 'R',          REQ_REFRESH },
726         /* Cursor navigation */
727         { 'k',          REQ_MOVE_UP },
728         { 'j',          REQ_MOVE_DOWN },
729         { KEY_HOME,     REQ_MOVE_FIRST_LINE },
730         { KEY_END,      REQ_MOVE_LAST_LINE },
731         { KEY_NPAGE,    REQ_MOVE_PAGE_DOWN },
732         { ' ',          REQ_MOVE_PAGE_DOWN },
733         { KEY_PPAGE,    REQ_MOVE_PAGE_UP },
734         { 'b',          REQ_MOVE_PAGE_UP },
735         { '-',          REQ_MOVE_PAGE_UP },
737         /* Scrolling */
738         { KEY_IC,       REQ_SCROLL_LINE_UP },
739         { KEY_DC,       REQ_SCROLL_LINE_DOWN },
740         { 'w',          REQ_SCROLL_PAGE_UP },
741         { 's',          REQ_SCROLL_PAGE_DOWN },
743         /* Searching */
744         { '/',          REQ_SEARCH },
745         { '?',          REQ_SEARCH_BACK },
746         { 'n',          REQ_FIND_NEXT },
747         { 'N',          REQ_FIND_PREV },
749         /* Misc */
750         { 'Q',          REQ_QUIT },
751         { 'z',          REQ_STOP_LOADING },
752         { 'v',          REQ_SHOW_VERSION },
753         { 'r',          REQ_SCREEN_REDRAW },
754         { '.',          REQ_TOGGLE_LINENO },
755         { 'D',          REQ_TOGGLE_DATE },
756         { 'A',          REQ_TOGGLE_AUTHOR },
757         { 'g',          REQ_TOGGLE_REV_GRAPH },
758         { 'F',          REQ_TOGGLE_REFS },
759         { ':',          REQ_PROMPT },
760         { 'u',          REQ_STATUS_UPDATE },
761         { 'M',          REQ_STATUS_MERGE },
762         { ',',          REQ_TREE_PARENT },
763         { 'e',          REQ_EDIT },
765         /* Using the ncurses SIGWINCH handler. */
766         { KEY_RESIZE,   REQ_SCREEN_RESIZE },
767 };
769 #define KEYMAP_INFO \
770         KEYMAP_(GENERIC), \
771         KEYMAP_(MAIN), \
772         KEYMAP_(DIFF), \
773         KEYMAP_(LOG), \
774         KEYMAP_(TREE), \
775         KEYMAP_(BLOB), \
776         KEYMAP_(BLAME), \
777         KEYMAP_(PAGER), \
778         KEYMAP_(HELP), \
779         KEYMAP_(STATUS), \
780         KEYMAP_(STAGE)
782 enum keymap {
783 #define KEYMAP_(name) KEYMAP_##name
784         KEYMAP_INFO
785 #undef  KEYMAP_
786 };
788 static struct int_map keymap_table[] = {
789 #define KEYMAP_(name) { #name, STRING_SIZE(#name), KEYMAP_##name }
790         KEYMAP_INFO
791 #undef  KEYMAP_
792 };
794 #define set_keymap(map, name) \
795         set_from_int_map(keymap_table, ARRAY_SIZE(keymap_table), map, name, strlen(name))
797 static struct keybinding *keybindings[ARRAY_SIZE(keymap_table)];
799 static void
800 add_keybinding(enum keymap keymap, enum request request, int key)
802         struct keybinding *keybinding;
804         keybinding = calloc(1, sizeof(*keybinding));
805         if (!keybinding)
806                 die("Failed to allocate keybinding");
808         keybinding->alias = key;
809         keybinding->request = request;
810         keybinding->next = keybindings[keymap];
811         keybindings[keymap] = keybinding;
814 /* Looks for a key binding first in the given map, then in the generic map, and
815  * lastly in the default keybindings. */
816 static enum request
817 get_keybinding(enum keymap keymap, int key)
819         struct keybinding *kbd;
820         int i;
822         for (kbd = keybindings[keymap]; kbd; kbd = kbd->next)
823                 if (kbd->alias == key)
824                         return kbd->request;
826         for (kbd = keybindings[KEYMAP_GENERIC]; kbd; kbd = kbd->next)
827                 if (kbd->alias == key)
828                         return kbd->request;
830         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
831                 if (default_keybindings[i].alias == key)
832                         return default_keybindings[i].request;
834         return (enum request) key;
838 struct key {
839         char *name;
840         int value;
841 };
843 static struct key key_table[] = {
844         { "Enter",      KEY_RETURN },
845         { "Space",      ' ' },
846         { "Backspace",  KEY_BACKSPACE },
847         { "Tab",        KEY_TAB },
848         { "Escape",     KEY_ESC },
849         { "Left",       KEY_LEFT },
850         { "Right",      KEY_RIGHT },
851         { "Up",         KEY_UP },
852         { "Down",       KEY_DOWN },
853         { "Insert",     KEY_IC },
854         { "Delete",     KEY_DC },
855         { "Hash",       '#' },
856         { "Home",       KEY_HOME },
857         { "End",        KEY_END },
858         { "PageUp",     KEY_PPAGE },
859         { "PageDown",   KEY_NPAGE },
860         { "F1",         KEY_F(1) },
861         { "F2",         KEY_F(2) },
862         { "F3",         KEY_F(3) },
863         { "F4",         KEY_F(4) },
864         { "F5",         KEY_F(5) },
865         { "F6",         KEY_F(6) },
866         { "F7",         KEY_F(7) },
867         { "F8",         KEY_F(8) },
868         { "F9",         KEY_F(9) },
869         { "F10",        KEY_F(10) },
870         { "F11",        KEY_F(11) },
871         { "F12",        KEY_F(12) },
872 };
874 static int
875 get_key_value(const char *name)
877         int i;
879         for (i = 0; i < ARRAY_SIZE(key_table); i++)
880                 if (!strcasecmp(key_table[i].name, name))
881                         return key_table[i].value;
883         if (strlen(name) == 1 && isprint(*name))
884                 return (int) *name;
886         return ERR;
889 static char *
890 get_key_name(int key_value)
892         static char key_char[] = "'X'";
893         char *seq = NULL;
894         int key;
896         for (key = 0; key < ARRAY_SIZE(key_table); key++)
897                 if (key_table[key].value == key_value)
898                         seq = key_table[key].name;
900         if (seq == NULL &&
901             key_value < 127 &&
902             isprint(key_value)) {
903                 key_char[1] = (char) key_value;
904                 seq = key_char;
905         }
907         return seq ? seq : "'?'";
910 static char *
911 get_key(enum request request)
913         static char buf[BUFSIZ];
914         size_t pos = 0;
915         char *sep = "";
916         int i;
918         buf[pos] = 0;
920         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
921                 struct keybinding *keybinding = &default_keybindings[i];
923                 if (keybinding->request != request)
924                         continue;
926                 if (!string_format_from(buf, &pos, "%s%s", sep,
927                                         get_key_name(keybinding->alias)))
928                         return "Too many keybindings!";
929                 sep = ", ";
930         }
932         return buf;
935 struct run_request {
936         enum keymap keymap;
937         int key;
938         char cmd[SIZEOF_STR];
939 };
941 static struct run_request *run_request;
942 static size_t run_requests;
944 static enum request
945 add_run_request(enum keymap keymap, int key, int argc, char **argv)
947         struct run_request *tmp;
948         struct run_request req = { keymap, key };
949         size_t bufpos;
951         for (bufpos = 0; argc > 0; argc--, argv++)
952                 if (!string_format_from(req.cmd, &bufpos, "%s ", *argv))
953                         return REQ_NONE;
955         req.cmd[bufpos - 1] = 0;
957         tmp = realloc(run_request, (run_requests + 1) * sizeof(*run_request));
958         if (!tmp)
959                 return REQ_NONE;
961         run_request = tmp;
962         run_request[run_requests++] = req;
964         return REQ_NONE + run_requests;
967 static struct run_request *
968 get_run_request(enum request request)
970         if (request <= REQ_NONE)
971                 return NULL;
972         return &run_request[request - REQ_NONE - 1];
975 static void
976 add_builtin_run_requests(void)
978         struct {
979                 enum keymap keymap;
980                 int key;
981                 char *argv[1];
982         } reqs[] = {
983                 { KEYMAP_MAIN,    'C', { "git cherry-pick %(commit)" } },
984                 { KEYMAP_GENERIC, 'G', { "git gc" } },
985         };
986         int i;
988         for (i = 0; i < ARRAY_SIZE(reqs); i++) {
989                 enum request req;
991                 req = add_run_request(reqs[i].keymap, reqs[i].key, 1, reqs[i].argv);
992                 if (req != REQ_NONE)
993                         add_keybinding(reqs[i].keymap, req, reqs[i].key);
994         }
997 /*
998  * User config file handling.
999  */
1001 static struct int_map color_map[] = {
1002 #define COLOR_MAP(name) { #name, STRING_SIZE(#name), COLOR_##name }
1003         COLOR_MAP(DEFAULT),
1004         COLOR_MAP(BLACK),
1005         COLOR_MAP(BLUE),
1006         COLOR_MAP(CYAN),
1007         COLOR_MAP(GREEN),
1008         COLOR_MAP(MAGENTA),
1009         COLOR_MAP(RED),
1010         COLOR_MAP(WHITE),
1011         COLOR_MAP(YELLOW),
1012 };
1014 #define set_color(color, name) \
1015         set_from_int_map(color_map, ARRAY_SIZE(color_map), color, name, strlen(name))
1017 static struct int_map attr_map[] = {
1018 #define ATTR_MAP(name) { #name, STRING_SIZE(#name), A_##name }
1019         ATTR_MAP(NORMAL),
1020         ATTR_MAP(BLINK),
1021         ATTR_MAP(BOLD),
1022         ATTR_MAP(DIM),
1023         ATTR_MAP(REVERSE),
1024         ATTR_MAP(STANDOUT),
1025         ATTR_MAP(UNDERLINE),
1026 };
1028 #define set_attribute(attr, name) \
1029         set_from_int_map(attr_map, ARRAY_SIZE(attr_map), attr, name, strlen(name))
1031 static int   config_lineno;
1032 static bool  config_errors;
1033 static char *config_msg;
1035 /* Wants: object fgcolor bgcolor [attr] */
1036 static int
1037 option_color_command(int argc, char *argv[])
1039         struct line_info *info;
1041         if (argc != 3 && argc != 4) {
1042                 config_msg = "Wrong number of arguments given to color command";
1043                 return ERR;
1044         }
1046         info = get_line_info(argv[0], strlen(argv[0]));
1047         if (!info) {
1048                 config_msg = "Unknown color name";
1049                 return ERR;
1050         }
1052         if (set_color(&info->fg, argv[1]) == ERR ||
1053             set_color(&info->bg, argv[2]) == ERR) {
1054                 config_msg = "Unknown color";
1055                 return ERR;
1056         }
1058         if (argc == 4 && set_attribute(&info->attr, argv[3]) == ERR) {
1059                 config_msg = "Unknown attribute";
1060                 return ERR;
1061         }
1063         return OK;
1066 static bool parse_bool(const char *s)
1068         return (!strcmp(s, "1") || !strcmp(s, "true") ||
1069                 !strcmp(s, "yes")) ? TRUE : FALSE;
1072 /* Wants: name = value */
1073 static int
1074 option_set_command(int argc, char *argv[])
1076         if (argc != 3) {
1077                 config_msg = "Wrong number of arguments given to set command";
1078                 return ERR;
1079         }
1081         if (strcmp(argv[1], "=")) {
1082                 config_msg = "No value assigned";
1083                 return ERR;
1084         }
1086         if (!strcmp(argv[0], "show-author")) {
1087                 opt_author = parse_bool(argv[2]);
1088                 return OK;
1089         }
1091         if (!strcmp(argv[0], "show-date")) {
1092                 opt_date = parse_bool(argv[2]);
1093                 return OK;
1094         }
1096         if (!strcmp(argv[0], "show-rev-graph")) {
1097                 opt_rev_graph = parse_bool(argv[2]);
1098                 return OK;
1099         }
1101         if (!strcmp(argv[0], "show-refs")) {
1102                 opt_show_refs = parse_bool(argv[2]);
1103                 return OK;
1104         }
1106         if (!strcmp(argv[0], "show-line-numbers")) {
1107                 opt_line_number = parse_bool(argv[2]);
1108                 return OK;
1109         }
1111         if (!strcmp(argv[0], "line-number-interval")) {
1112                 opt_num_interval = atoi(argv[2]);
1113                 return OK;
1114         }
1116         if (!strcmp(argv[0], "tab-size")) {
1117                 opt_tab_size = atoi(argv[2]);
1118                 return OK;
1119         }
1121         if (!strcmp(argv[0], "commit-encoding")) {
1122                 char *arg = argv[2];
1123                 int delimiter = *arg;
1124                 int i;
1126                 switch (delimiter) {
1127                 case '"':
1128                 case '\'':
1129                         for (arg++, i = 0; arg[i]; i++)
1130                                 if (arg[i] == delimiter) {
1131                                         arg[i] = 0;
1132                                         break;
1133                                 }
1134                 default:
1135                         string_ncopy(opt_encoding, arg, strlen(arg));
1136                         return OK;
1137                 }
1138         }
1140         config_msg = "Unknown variable name";
1141         return ERR;
1144 /* Wants: mode request key */
1145 static int
1146 option_bind_command(int argc, char *argv[])
1148         enum request request;
1149         int keymap;
1150         int key;
1152         if (argc < 3) {
1153                 config_msg = "Wrong number of arguments given to bind command";
1154                 return ERR;
1155         }
1157         if (set_keymap(&keymap, argv[0]) == ERR) {
1158                 config_msg = "Unknown key map";
1159                 return ERR;
1160         }
1162         key = get_key_value(argv[1]);
1163         if (key == ERR) {
1164                 config_msg = "Unknown key";
1165                 return ERR;
1166         }
1168         request = get_request(argv[2]);
1169         if (request == REQ_NONE) {
1170                 const char *obsolete[] = { "cherry-pick" };
1171                 size_t namelen = strlen(argv[2]);
1172                 int i;
1174                 for (i = 0; i < ARRAY_SIZE(obsolete); i++) {
1175                         if (namelen == strlen(obsolete[i]) &&
1176                             !string_enum_compare(obsolete[i], argv[2], namelen)) {
1177                                 config_msg = "Obsolete request name";
1178                                 return ERR;
1179                         }
1180                 }
1181         }
1182         if (request == REQ_NONE && *argv[2]++ == '!')
1183                 request = add_run_request(keymap, key, argc - 2, argv + 2);
1184         if (request == REQ_NONE) {
1185                 config_msg = "Unknown request name";
1186                 return ERR;
1187         }
1189         add_keybinding(keymap, request, key);
1191         return OK;
1194 static int
1195 set_option(char *opt, char *value)
1197         char *argv[16];
1198         int valuelen;
1199         int argc = 0;
1201         /* Tokenize */
1202         while (argc < ARRAY_SIZE(argv) && (valuelen = strcspn(value, " \t"))) {
1203                 argv[argc++] = value;
1204                 value += valuelen;
1206                 /* Nothing more to tokenize or last available token. */
1207                 if (!*value || argc >= ARRAY_SIZE(argv))
1208                         break;
1210                 *value++ = 0;
1211                 while (isspace(*value))
1212                         value++;
1213         }
1215         if (!strcmp(opt, "color"))
1216                 return option_color_command(argc, argv);
1218         if (!strcmp(opt, "set"))
1219                 return option_set_command(argc, argv);
1221         if (!strcmp(opt, "bind"))
1222                 return option_bind_command(argc, argv);
1224         config_msg = "Unknown option command";
1225         return ERR;
1228 static int
1229 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1231         int status = OK;
1233         config_lineno++;
1234         config_msg = "Internal error";
1236         /* Check for comment markers, since read_properties() will
1237          * only ensure opt and value are split at first " \t". */
1238         optlen = strcspn(opt, "#");
1239         if (optlen == 0)
1240                 return OK;
1242         if (opt[optlen] != 0) {
1243                 config_msg = "No option value";
1244                 status = ERR;
1246         }  else {
1247                 /* Look for comment endings in the value. */
1248                 size_t len = strcspn(value, "#");
1250                 if (len < valuelen) {
1251                         valuelen = len;
1252                         value[valuelen] = 0;
1253                 }
1255                 status = set_option(opt, value);
1256         }
1258         if (status == ERR) {
1259                 fprintf(stderr, "Error on line %d, near '%.*s': %s\n",
1260                         config_lineno, (int) optlen, opt, config_msg);
1261                 config_errors = TRUE;
1262         }
1264         /* Always keep going if errors are encountered. */
1265         return OK;
1268 static void
1269 load_option_file(const char *path)
1271         FILE *file;
1273         /* It's ok that the file doesn't exist. */
1274         file = fopen(path, "r");
1275         if (!file)
1276                 return;
1278         config_lineno = 0;
1279         config_errors = FALSE;
1281         if (read_properties(file, " \t", read_option) == ERR ||
1282             config_errors == TRUE)
1283                 fprintf(stderr, "Errors while loading %s.\n", path);
1286 static int
1287 load_options(void)
1289         char *home = getenv("HOME");
1290         char *tigrc_user = getenv("TIGRC_USER");
1291         char *tigrc_system = getenv("TIGRC_SYSTEM");
1292         char buf[SIZEOF_STR];
1294         add_builtin_run_requests();
1296         if (!tigrc_system) {
1297                 if (!string_format(buf, "%s/tigrc", SYSCONFDIR))
1298                         return ERR;
1299                 tigrc_system = buf;
1300         }
1301         load_option_file(tigrc_system);
1303         if (!tigrc_user) {
1304                 if (!home || !string_format(buf, "%s/.tigrc", home))
1305                         return ERR;
1306                 tigrc_user = buf;
1307         }
1308         load_option_file(tigrc_user);
1310         return OK;
1314 /*
1315  * The viewer
1316  */
1318 struct view;
1319 struct view_ops;
1321 /* The display array of active views and the index of the current view. */
1322 static struct view *display[2];
1323 static unsigned int current_view;
1325 /* Reading from the prompt? */
1326 static bool input_mode = FALSE;
1328 #define foreach_displayed_view(view, i) \
1329         for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1331 #define displayed_views()       (display[1] != NULL ? 2 : 1)
1333 /* Current head and commit ID */
1334 static char ref_blob[SIZEOF_REF]        = "";
1335 static char ref_commit[SIZEOF_REF]      = "HEAD";
1336 static char ref_head[SIZEOF_REF]        = "HEAD";
1338 struct view {
1339         const char *name;       /* View name */
1340         const char *cmd_fmt;    /* Default command line format */
1341         const char *cmd_env;    /* Command line set via environment */
1342         const char *id;         /* Points to either of ref_{head,commit,blob} */
1344         struct view_ops *ops;   /* View operations */
1346         enum keymap keymap;     /* What keymap does this view have */
1348         char cmd[SIZEOF_STR];   /* Command buffer */
1349         char ref[SIZEOF_REF];   /* Hovered commit reference */
1350         char vid[SIZEOF_REF];   /* View ID. Set to id member when updating. */
1352         int height, width;      /* The width and height of the main window */
1353         WINDOW *win;            /* The main window */
1354         WINDOW *title;          /* The title window living below the main window */
1356         /* Navigation */
1357         unsigned long offset;   /* Offset of the window top */
1358         unsigned long lineno;   /* Current line number */
1360         /* Searching */
1361         char grep[SIZEOF_STR];  /* Search string */
1362         regex_t *regex;         /* Pre-compiled regex */
1364         /* If non-NULL, points to the view that opened this view. If this view
1365          * is closed tig will switch back to the parent view. */
1366         struct view *parent;
1368         /* Buffering */
1369         size_t lines;           /* Total number of lines */
1370         struct line *line;      /* Line index */
1371         size_t line_alloc;      /* Total number of allocated lines */
1372         size_t line_size;       /* Total number of used lines */
1373         unsigned int digits;    /* Number of digits in the lines member. */
1375         /* Loading */
1376         FILE *pipe;
1377         time_t start_time;
1378 };
1380 struct view_ops {
1381         /* What type of content being displayed. Used in the title bar. */
1382         const char *type;
1383         /* Open and reads in all view content. */
1384         bool (*open)(struct view *view);
1385         /* Read one line; updates view->line. */
1386         bool (*read)(struct view *view, char *data);
1387         /* Draw one line; @lineno must be < view->height. */
1388         bool (*draw)(struct view *view, struct line *line, unsigned int lineno, bool selected);
1389         /* Depending on view handle a special requests. */
1390         enum request (*request)(struct view *view, enum request request, struct line *line);
1391         /* Search for regex in a line. */
1392         bool (*grep)(struct view *view, struct line *line);
1393         /* Select line */
1394         void (*select)(struct view *view, struct line *line);
1395 };
1397 static struct view_ops pager_ops;
1398 static struct view_ops main_ops;
1399 static struct view_ops tree_ops;
1400 static struct view_ops blob_ops;
1401 static struct view_ops blame_ops;
1402 static struct view_ops help_ops;
1403 static struct view_ops status_ops;
1404 static struct view_ops stage_ops;
1406 #define VIEW_STR(name, cmd, env, ref, ops, map) \
1407         { name, cmd, #env, ref, ops, map}
1409 #define VIEW_(id, name, ops, ref) \
1410         VIEW_STR(name, TIG_##id##_CMD,  TIG_##id##_CMD, ref, ops, KEYMAP_##id)
1413 static struct view views[] = {
1414         VIEW_(MAIN,   "main",   &main_ops,   ref_head),
1415         VIEW_(DIFF,   "diff",   &pager_ops,  ref_commit),
1416         VIEW_(LOG,    "log",    &pager_ops,  ref_head),
1417         VIEW_(TREE,   "tree",   &tree_ops,   ref_commit),
1418         VIEW_(BLOB,   "blob",   &blob_ops,   ref_blob),
1419         VIEW_(BLAME,  "blame",  &blame_ops,  ref_commit),
1420         VIEW_(HELP,   "help",   &help_ops,   ""),
1421         VIEW_(PAGER,  "pager",  &pager_ops,  "stdin"),
1422         VIEW_(STATUS, "status", &status_ops, ""),
1423         VIEW_(STAGE,  "stage",  &stage_ops,  ""),
1424 };
1426 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
1428 #define foreach_view(view, i) \
1429         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1431 #define view_is_displayed(view) \
1432         (view == display[0] || view == display[1])
1434 static int
1435 draw_text(struct view *view, const char *string, int max_len,
1436           bool use_tilde, int tilde_attr)
1438         int len = 0;
1439         int trimmed = FALSE;
1441         if (max_len <= 0)
1442                 return 0;
1444         if (opt_utf8) {
1445                 len = utf8_length(string, max_len, &trimmed, use_tilde);
1446         } else {
1447                 len = strlen(string);
1448                 if (len > max_len) {
1449                         if (use_tilde) {
1450                                 max_len -= 1;
1451                         }
1452                         len = max_len;
1453                         trimmed = TRUE;
1454                 }
1455         }
1457         waddnstr(view->win, string, len);
1458         if (trimmed && use_tilde) {
1459                 if (tilde_attr != -1)
1460                         wattrset(view->win, tilde_attr);
1461                 waddch(view->win, '~');
1462                 len++;
1463         }
1465         return len;
1468 static bool
1469 draw_view_line(struct view *view, unsigned int lineno)
1471         struct line *line;
1472         bool selected = (view->offset + lineno == view->lineno);
1473         bool draw_ok;
1475         assert(view_is_displayed(view));
1477         if (view->offset + lineno >= view->lines)
1478                 return FALSE;
1480         line = &view->line[view->offset + lineno];
1482         if (selected) {
1483                 line->selected = TRUE;
1484                 view->ops->select(view, line);
1485         } else if (line->selected) {
1486                 line->selected = FALSE;
1487                 wmove(view->win, lineno, 0);
1488                 wclrtoeol(view->win);
1489         }
1491         scrollok(view->win, FALSE);
1492         draw_ok = view->ops->draw(view, line, lineno, selected);
1493         scrollok(view->win, TRUE);
1495         return draw_ok;
1498 static void
1499 redraw_view_dirty(struct view *view)
1501         bool dirty = FALSE;
1502         int lineno;
1504         for (lineno = 0; lineno < view->height; lineno++) {
1505                 struct line *line = &view->line[view->offset + lineno];
1507                 if (!line->dirty)
1508                         continue;
1509                 line->dirty = 0;
1510                 dirty = TRUE;
1511                 if (!draw_view_line(view, lineno))
1512                         break;
1513         }
1515         if (!dirty)
1516                 return;
1517         redrawwin(view->win);
1518         if (input_mode)
1519                 wnoutrefresh(view->win);
1520         else
1521                 wrefresh(view->win);
1524 static void
1525 redraw_view_from(struct view *view, int lineno)
1527         assert(0 <= lineno && lineno < view->height);
1529         for (; lineno < view->height; lineno++) {
1530                 if (!draw_view_line(view, lineno))
1531                         break;
1532         }
1534         redrawwin(view->win);
1535         if (input_mode)
1536                 wnoutrefresh(view->win);
1537         else
1538                 wrefresh(view->win);
1541 static void
1542 redraw_view(struct view *view)
1544         wclear(view->win);
1545         redraw_view_from(view, 0);
1549 static void
1550 update_view_title(struct view *view)
1552         char buf[SIZEOF_STR];
1553         char state[SIZEOF_STR];
1554         size_t bufpos = 0, statelen = 0;
1556         assert(view_is_displayed(view));
1558         if (view != VIEW(REQ_VIEW_STATUS) && (view->lines || view->pipe)) {
1559                 unsigned int view_lines = view->offset + view->height;
1560                 unsigned int lines = view->lines
1561                                    ? MIN(view_lines, view->lines) * 100 / view->lines
1562                                    : 0;
1564                 string_format_from(state, &statelen, "- %s %d of %d (%d%%)",
1565                                    view->ops->type,
1566                                    view->lineno + 1,
1567                                    view->lines,
1568                                    lines);
1570                 if (view->pipe) {
1571                         time_t secs = time(NULL) - view->start_time;
1573                         /* Three git seconds are a long time ... */
1574                         if (secs > 2)
1575                                 string_format_from(state, &statelen, " %lds", secs);
1576                 }
1577         }
1579         string_format_from(buf, &bufpos, "[%s]", view->name);
1580         if (*view->ref && bufpos < view->width) {
1581                 size_t refsize = strlen(view->ref);
1582                 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
1584                 if (minsize < view->width)
1585                         refsize = view->width - minsize + 7;
1586                 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
1587         }
1589         if (statelen && bufpos < view->width) {
1590                 string_format_from(buf, &bufpos, " %s", state);
1591         }
1593         if (view == display[current_view])
1594                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
1595         else
1596                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
1598         mvwaddnstr(view->title, 0, 0, buf, bufpos);
1599         wclrtoeol(view->title);
1600         wmove(view->title, 0, view->width - 1);
1602         if (input_mode)
1603                 wnoutrefresh(view->title);
1604         else
1605                 wrefresh(view->title);
1608 static void
1609 resize_display(void)
1611         int offset, i;
1612         struct view *base = display[0];
1613         struct view *view = display[1] ? display[1] : display[0];
1615         /* Setup window dimensions */
1617         getmaxyx(stdscr, base->height, base->width);
1619         /* Make room for the status window. */
1620         base->height -= 1;
1622         if (view != base) {
1623                 /* Horizontal split. */
1624                 view->width   = base->width;
1625                 view->height  = SCALE_SPLIT_VIEW(base->height);
1626                 base->height -= view->height;
1628                 /* Make room for the title bar. */
1629                 view->height -= 1;
1630         }
1632         /* Make room for the title bar. */
1633         base->height -= 1;
1635         offset = 0;
1637         foreach_displayed_view (view, i) {
1638                 if (!view->win) {
1639                         view->win = newwin(view->height, 0, offset, 0);
1640                         if (!view->win)
1641                                 die("Failed to create %s view", view->name);
1643                         scrollok(view->win, TRUE);
1645                         view->title = newwin(1, 0, offset + view->height, 0);
1646                         if (!view->title)
1647                                 die("Failed to create title window");
1649                 } else {
1650                         wresize(view->win, view->height, view->width);
1651                         mvwin(view->win,   offset, 0);
1652                         mvwin(view->title, offset + view->height, 0);
1653                 }
1655                 offset += view->height + 1;
1656         }
1659 static void
1660 redraw_display(void)
1662         struct view *view;
1663         int i;
1665         foreach_displayed_view (view, i) {
1666                 redraw_view(view);
1667                 update_view_title(view);
1668         }
1671 static void
1672 update_display_cursor(struct view *view)
1674         /* Move the cursor to the right-most column of the cursor line.
1675          *
1676          * XXX: This could turn out to be a bit expensive, but it ensures that
1677          * the cursor does not jump around. */
1678         if (view->lines) {
1679                 wmove(view->win, view->lineno - view->offset, view->width - 1);
1680                 wrefresh(view->win);
1681         }
1684 /*
1685  * Navigation
1686  */
1688 /* Scrolling backend */
1689 static void
1690 do_scroll_view(struct view *view, int lines)
1692         bool redraw_current_line = FALSE;
1694         /* The rendering expects the new offset. */
1695         view->offset += lines;
1697         assert(0 <= view->offset && view->offset < view->lines);
1698         assert(lines);
1700         /* Move current line into the view. */
1701         if (view->lineno < view->offset) {
1702                 view->lineno = view->offset;
1703                 redraw_current_line = TRUE;
1704         } else if (view->lineno >= view->offset + view->height) {
1705                 view->lineno = view->offset + view->height - 1;
1706                 redraw_current_line = TRUE;
1707         }
1709         assert(view->offset <= view->lineno && view->lineno < view->lines);
1711         /* Redraw the whole screen if scrolling is pointless. */
1712         if (view->height < ABS(lines)) {
1713                 redraw_view(view);
1715         } else {
1716                 int line = lines > 0 ? view->height - lines : 0;
1717                 int end = line + ABS(lines);
1719                 wscrl(view->win, lines);
1721                 for (; line < end; line++) {
1722                         if (!draw_view_line(view, line))
1723                                 break;
1724                 }
1726                 if (redraw_current_line)
1727                         draw_view_line(view, view->lineno - view->offset);
1728         }
1730         redrawwin(view->win);
1731         wrefresh(view->win);
1732         report("");
1735 /* Scroll frontend */
1736 static void
1737 scroll_view(struct view *view, enum request request)
1739         int lines = 1;
1741         assert(view_is_displayed(view));
1743         switch (request) {
1744         case REQ_SCROLL_PAGE_DOWN:
1745                 lines = view->height;
1746         case REQ_SCROLL_LINE_DOWN:
1747                 if (view->offset + lines > view->lines)
1748                         lines = view->lines - view->offset;
1750                 if (lines == 0 || view->offset + view->height >= view->lines) {
1751                         report("Cannot scroll beyond the last line");
1752                         return;
1753                 }
1754                 break;
1756         case REQ_SCROLL_PAGE_UP:
1757                 lines = view->height;
1758         case REQ_SCROLL_LINE_UP:
1759                 if (lines > view->offset)
1760                         lines = view->offset;
1762                 if (lines == 0) {
1763                         report("Cannot scroll beyond the first line");
1764                         return;
1765                 }
1767                 lines = -lines;
1768                 break;
1770         default:
1771                 die("request %d not handled in switch", request);
1772         }
1774         do_scroll_view(view, lines);
1777 /* Cursor moving */
1778 static void
1779 move_view(struct view *view, enum request request)
1781         int scroll_steps = 0;
1782         int steps;
1784         switch (request) {
1785         case REQ_MOVE_FIRST_LINE:
1786                 steps = -view->lineno;
1787                 break;
1789         case REQ_MOVE_LAST_LINE:
1790                 steps = view->lines - view->lineno - 1;
1791                 break;
1793         case REQ_MOVE_PAGE_UP:
1794                 steps = view->height > view->lineno
1795                       ? -view->lineno : -view->height;
1796                 break;
1798         case REQ_MOVE_PAGE_DOWN:
1799                 steps = view->lineno + view->height >= view->lines
1800                       ? view->lines - view->lineno - 1 : view->height;
1801                 break;
1803         case REQ_MOVE_UP:
1804                 steps = -1;
1805                 break;
1807         case REQ_MOVE_DOWN:
1808                 steps = 1;
1809                 break;
1811         default:
1812                 die("request %d not handled in switch", request);
1813         }
1815         if (steps <= 0 && view->lineno == 0) {
1816                 report("Cannot move beyond the first line");
1817                 return;
1819         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
1820                 report("Cannot move beyond the last line");
1821                 return;
1822         }
1824         /* Move the current line */
1825         view->lineno += steps;
1826         assert(0 <= view->lineno && view->lineno < view->lines);
1828         /* Check whether the view needs to be scrolled */
1829         if (view->lineno < view->offset ||
1830             view->lineno >= view->offset + view->height) {
1831                 scroll_steps = steps;
1832                 if (steps < 0 && -steps > view->offset) {
1833                         scroll_steps = -view->offset;
1835                 } else if (steps > 0) {
1836                         if (view->lineno == view->lines - 1 &&
1837                             view->lines > view->height) {
1838                                 scroll_steps = view->lines - view->offset - 1;
1839                                 if (scroll_steps >= view->height)
1840                                         scroll_steps -= view->height - 1;
1841                         }
1842                 }
1843         }
1845         if (!view_is_displayed(view)) {
1846                 view->offset += scroll_steps;
1847                 assert(0 <= view->offset && view->offset < view->lines);
1848                 view->ops->select(view, &view->line[view->lineno]);
1849                 return;
1850         }
1852         /* Repaint the old "current" line if we be scrolling */
1853         if (ABS(steps) < view->height)
1854                 draw_view_line(view, view->lineno - steps - view->offset);
1856         if (scroll_steps) {
1857                 do_scroll_view(view, scroll_steps);
1858                 return;
1859         }
1861         /* Draw the current line */
1862         draw_view_line(view, view->lineno - view->offset);
1864         redrawwin(view->win);
1865         wrefresh(view->win);
1866         report("");
1870 /*
1871  * Searching
1872  */
1874 static void search_view(struct view *view, enum request request);
1876 static bool
1877 find_next_line(struct view *view, unsigned long lineno, struct line *line)
1879         assert(view_is_displayed(view));
1881         if (!view->ops->grep(view, line))
1882                 return FALSE;
1884         if (lineno - view->offset >= view->height) {
1885                 view->offset = lineno;
1886                 view->lineno = lineno;
1887                 redraw_view(view);
1889         } else {
1890                 unsigned long old_lineno = view->lineno - view->offset;
1892                 view->lineno = lineno;
1893                 draw_view_line(view, old_lineno);
1895                 draw_view_line(view, view->lineno - view->offset);
1896                 redrawwin(view->win);
1897                 wrefresh(view->win);
1898         }
1900         report("Line %ld matches '%s'", lineno + 1, view->grep);
1901         return TRUE;
1904 static void
1905 find_next(struct view *view, enum request request)
1907         unsigned long lineno = view->lineno;
1908         int direction;
1910         if (!*view->grep) {
1911                 if (!*opt_search)
1912                         report("No previous search");
1913                 else
1914                         search_view(view, request);
1915                 return;
1916         }
1918         switch (request) {
1919         case REQ_SEARCH:
1920         case REQ_FIND_NEXT:
1921                 direction = 1;
1922                 break;
1924         case REQ_SEARCH_BACK:
1925         case REQ_FIND_PREV:
1926                 direction = -1;
1927                 break;
1929         default:
1930                 return;
1931         }
1933         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
1934                 lineno += direction;
1936         /* Note, lineno is unsigned long so will wrap around in which case it
1937          * will become bigger than view->lines. */
1938         for (; lineno < view->lines; lineno += direction) {
1939                 struct line *line = &view->line[lineno];
1941                 if (find_next_line(view, lineno, line))
1942                         return;
1943         }
1945         report("No match found for '%s'", view->grep);
1948 static void
1949 search_view(struct view *view, enum request request)
1951         int regex_err;
1953         if (view->regex) {
1954                 regfree(view->regex);
1955                 *view->grep = 0;
1956         } else {
1957                 view->regex = calloc(1, sizeof(*view->regex));
1958                 if (!view->regex)
1959                         return;
1960         }
1962         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
1963         if (regex_err != 0) {
1964                 char buf[SIZEOF_STR] = "unknown error";
1966                 regerror(regex_err, view->regex, buf, sizeof(buf));
1967                 report("Search failed: %s", buf);
1968                 return;
1969         }
1971         string_copy(view->grep, opt_search);
1973         find_next(view, request);
1976 /*
1977  * Incremental updating
1978  */
1980 static void
1981 end_update(struct view *view)
1983         if (!view->pipe)
1984                 return;
1985         set_nonblocking_input(FALSE);
1986         if (view->pipe == stdin)
1987                 fclose(view->pipe);
1988         else
1989                 pclose(view->pipe);
1990         view->pipe = NULL;
1993 static bool
1994 begin_update(struct view *view)
1996         if (view->pipe)
1997                 end_update(view);
1999         if (opt_cmd[0]) {
2000                 string_copy(view->cmd, opt_cmd);
2001                 opt_cmd[0] = 0;
2002                 /* When running random commands, initially show the
2003                  * command in the title. However, it maybe later be
2004                  * overwritten if a commit line is selected. */
2005                 if (view == VIEW(REQ_VIEW_PAGER))
2006                         string_copy(view->ref, view->cmd);
2007                 else
2008                         view->ref[0] = 0;
2010         } else if (view == VIEW(REQ_VIEW_TREE)) {
2011                 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
2012                 char path[SIZEOF_STR];
2014                 if (strcmp(view->vid, view->id))
2015                         opt_path[0] = path[0] = 0;
2016                 else if (sq_quote(path, 0, opt_path) >= sizeof(path))
2017                         return FALSE;
2019                 if (!string_format(view->cmd, format, view->id, path))
2020                         return FALSE;
2022         } else {
2023                 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
2024                 const char *id = view->id;
2026                 if (!string_format(view->cmd, format, id, id, id, id, id))
2027                         return FALSE;
2029                 /* Put the current ref_* value to the view title ref
2030                  * member. This is needed by the blob view. Most other
2031                  * views sets it automatically after loading because the
2032                  * first line is a commit line. */
2033                 string_copy_rev(view->ref, view->id);
2034         }
2036         /* Special case for the pager view. */
2037         if (opt_pipe) {
2038                 view->pipe = opt_pipe;
2039                 opt_pipe = NULL;
2040         } else {
2041                 view->pipe = popen(view->cmd, "r");
2042         }
2044         if (!view->pipe)
2045                 return FALSE;
2047         set_nonblocking_input(TRUE);
2049         view->offset = 0;
2050         view->lines  = 0;
2051         view->lineno = 0;
2052         string_copy_rev(view->vid, view->id);
2054         if (view->line) {
2055                 int i;
2057                 for (i = 0; i < view->lines; i++)
2058                         if (view->line[i].data)
2059                                 free(view->line[i].data);
2061                 free(view->line);
2062                 view->line = NULL;
2063         }
2065         view->start_time = time(NULL);
2067         return TRUE;
2070 #define ITEM_CHUNK_SIZE 256
2071 static void *
2072 realloc_items(void *mem, size_t *size, size_t new_size, size_t item_size)
2074         size_t num_chunks = *size / ITEM_CHUNK_SIZE;
2075         size_t num_chunks_new = (new_size + ITEM_CHUNK_SIZE - 1) / ITEM_CHUNK_SIZE;
2077         if (mem == NULL || num_chunks != num_chunks_new) {
2078                 *size = num_chunks_new * ITEM_CHUNK_SIZE;
2079                 mem = realloc(mem, *size * item_size);
2080         }
2082         return mem;
2085 static struct line *
2086 realloc_lines(struct view *view, size_t line_size)
2088         size_t alloc = view->line_alloc;
2089         struct line *tmp = realloc_items(view->line, &alloc, line_size,
2090                                          sizeof(*view->line));
2092         if (!tmp)
2093                 return NULL;
2095         view->line = tmp;
2096         view->line_alloc = alloc;
2097         view->line_size = line_size;
2098         return view->line;
2101 static bool
2102 update_view(struct view *view)
2104         char in_buffer[BUFSIZ];
2105         char out_buffer[BUFSIZ * 2];
2106         char *line;
2107         /* The number of lines to read. If too low it will cause too much
2108          * redrawing (and possible flickering), if too high responsiveness
2109          * will suffer. */
2110         unsigned long lines = view->height;
2111         int redraw_from = -1;
2113         if (!view->pipe)
2114                 return TRUE;
2116         /* Only redraw if lines are visible. */
2117         if (view->offset + view->height >= view->lines)
2118                 redraw_from = view->lines - view->offset;
2120         /* FIXME: This is probably not perfect for backgrounded views. */
2121         if (!realloc_lines(view, view->lines + lines))
2122                 goto alloc_error;
2124         while ((line = fgets(in_buffer, sizeof(in_buffer), view->pipe))) {
2125                 size_t linelen = strlen(line);
2127                 if (linelen)
2128                         line[linelen - 1] = 0;
2130                 if (opt_iconv != ICONV_NONE) {
2131                         ICONV_CONST char *inbuf = line;
2132                         size_t inlen = linelen;
2134                         char *outbuf = out_buffer;
2135                         size_t outlen = sizeof(out_buffer);
2137                         size_t ret;
2139                         ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2140                         if (ret != (size_t) -1) {
2141                                 line = out_buffer;
2142                                 linelen = strlen(out_buffer);
2143                         }
2144                 }
2146                 if (!view->ops->read(view, line))
2147                         goto alloc_error;
2149                 if (lines-- == 1)
2150                         break;
2151         }
2153         {
2154                 int digits;
2156                 lines = view->lines;
2157                 for (digits = 0; lines; digits++)
2158                         lines /= 10;
2160                 /* Keep the displayed view in sync with line number scaling. */
2161                 if (digits != view->digits) {
2162                         view->digits = digits;
2163                         redraw_from = 0;
2164                 }
2165         }
2167         if (!view_is_displayed(view))
2168                 goto check_pipe;
2170         if (view == VIEW(REQ_VIEW_TREE)) {
2171                 /* Clear the view and redraw everything since the tree sorting
2172                  * might have rearranged things. */
2173                 redraw_view(view);
2176         } else if (redraw_from >= 0) {
2177                 /* If this is an incremental update, redraw the previous line
2178                  * since for commits some members could have changed when
2179                  * loading the main view. */
2180                 if (redraw_from > 0)
2181                         redraw_from--;
2183                 /* Since revision graph visualization requires knowledge
2184                  * about the parent commit, it causes a further one-off
2185                  * needed to be redrawn for incremental updates. */
2186                 if (redraw_from > 0 && opt_rev_graph)
2187                         redraw_from--;
2189                 /* Incrementally draw avoids flickering. */
2190                 redraw_view_from(view, redraw_from);
2191         }
2193         if (view == VIEW(REQ_VIEW_BLAME))
2194                 redraw_view_dirty(view);
2196         /* Update the title _after_ the redraw so that if the redraw picks up a
2197          * commit reference in view->ref it'll be available here. */
2198         update_view_title(view);
2200 check_pipe:
2201         if (ferror(view->pipe)) {
2202                 report("Failed to read: %s", strerror(errno));
2203                 goto end;
2205         } else if (feof(view->pipe)) {
2206                 report("");
2207                 goto end;
2208         }
2210         return TRUE;
2212 alloc_error:
2213         report("Allocation failure");
2215 end:
2216         if (view->ops->read(view, NULL))
2217                 end_update(view);
2218         return FALSE;
2221 static struct line *
2222 add_line_data(struct view *view, void *data, enum line_type type)
2224         struct line *line = &view->line[view->lines++];
2226         memset(line, 0, sizeof(*line));
2227         line->type = type;
2228         line->data = data;
2230         return line;
2233 static struct line *
2234 add_line_text(struct view *view, char *data, enum line_type type)
2236         if (data)
2237                 data = strdup(data);
2239         return data ? add_line_data(view, data, type) : NULL;
2243 /*
2244  * View opening
2245  */
2247 enum open_flags {
2248         OPEN_DEFAULT = 0,       /* Use default view switching. */
2249         OPEN_SPLIT = 1,         /* Split current view. */
2250         OPEN_BACKGROUNDED = 2,  /* Backgrounded. */
2251         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
2252 };
2254 static void
2255 open_view(struct view *prev, enum request request, enum open_flags flags)
2257         bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
2258         bool split = !!(flags & OPEN_SPLIT);
2259         bool reload = !!(flags & OPEN_RELOAD);
2260         struct view *view = VIEW(request);
2261         int nviews = displayed_views();
2262         struct view *base_view = display[0];
2264         if (view == prev && nviews == 1 && !reload) {
2265                 report("Already in %s view", view->name);
2266                 return;
2267         }
2269         if (view->ops->open) {
2270                 if (!view->ops->open(view)) {
2271                         report("Failed to load %s view", view->name);
2272                         return;
2273                 }
2275         } else if ((reload || strcmp(view->vid, view->id)) &&
2276                    !begin_update(view)) {
2277                 report("Failed to load %s view", view->name);
2278                 return;
2279         }
2281         if (split) {
2282                 display[1] = view;
2283                 if (!backgrounded)
2284                         current_view = 1;
2285         } else {
2286                 /* Maximize the current view. */
2287                 memset(display, 0, sizeof(display));
2288                 current_view = 0;
2289                 display[current_view] = view;
2290         }
2292         /* Resize the view when switching between split- and full-screen,
2293          * or when switching between two different full-screen views. */
2294         if (nviews != displayed_views() ||
2295             (nviews == 1 && base_view != display[0]))
2296                 resize_display();
2298         if (split && prev->lineno - prev->offset >= prev->height) {
2299                 /* Take the title line into account. */
2300                 int lines = prev->lineno - prev->offset - prev->height + 1;
2302                 /* Scroll the view that was split if the current line is
2303                  * outside the new limited view. */
2304                 do_scroll_view(prev, lines);
2305         }
2307         if (prev && view != prev) {
2308                 if (split && !backgrounded) {
2309                         /* "Blur" the previous view. */
2310                         update_view_title(prev);
2311                 }
2313                 view->parent = prev;
2314         }
2316         if (view->pipe && view->lines == 0) {
2317                 /* Clear the old view and let the incremental updating refill
2318                  * the screen. */
2319                 wclear(view->win);
2320                 report("");
2321         } else {
2322                 redraw_view(view);
2323                 report("");
2324         }
2326         /* If the view is backgrounded the above calls to report()
2327          * won't redraw the view title. */
2328         if (backgrounded)
2329                 update_view_title(view);
2332 static void
2333 open_external_viewer(const char *cmd)
2335         def_prog_mode();           /* save current tty modes */
2336         endwin();                  /* restore original tty modes */
2337         system(cmd);
2338         fprintf(stderr, "Press Enter to continue");
2339         getc(stdin);
2340         reset_prog_mode();
2341         redraw_display();
2344 static void
2345 open_mergetool(const char *file)
2347         char cmd[SIZEOF_STR];
2348         char file_sq[SIZEOF_STR];
2350         if (sq_quote(file_sq, 0, file) < sizeof(file_sq) &&
2351             string_format(cmd, "git mergetool %s", file_sq)) {
2352                 open_external_viewer(cmd);
2353         }
2356 static void
2357 open_editor(bool from_root, const char *file)
2359         char cmd[SIZEOF_STR];
2360         char file_sq[SIZEOF_STR];
2361         char *editor;
2362         char *prefix = from_root ? opt_cdup : "";
2364         editor = getenv("GIT_EDITOR");
2365         if (!editor && *opt_editor)
2366                 editor = opt_editor;
2367         if (!editor)
2368                 editor = getenv("VISUAL");
2369         if (!editor)
2370                 editor = getenv("EDITOR");
2371         if (!editor)
2372                 editor = "vi";
2374         if (sq_quote(file_sq, 0, file) < sizeof(file_sq) &&
2375             string_format(cmd, "%s %s%s", editor, prefix, file_sq)) {
2376                 open_external_viewer(cmd);
2377         }
2380 static void
2381 open_run_request(enum request request)
2383         struct run_request *req = get_run_request(request);
2384         char buf[SIZEOF_STR * 2];
2385         size_t bufpos;
2386         char *cmd;
2388         if (!req) {
2389                 report("Unknown run request");
2390                 return;
2391         }
2393         bufpos = 0;
2394         cmd = req->cmd;
2396         while (cmd) {
2397                 char *next = strstr(cmd, "%(");
2398                 int len = next - cmd;
2399                 char *value;
2401                 if (!next) {
2402                         len = strlen(cmd);
2403                         value = "";
2405                 } else if (!strncmp(next, "%(head)", 7)) {
2406                         value = ref_head;
2408                 } else if (!strncmp(next, "%(commit)", 9)) {
2409                         value = ref_commit;
2411                 } else if (!strncmp(next, "%(blob)", 7)) {
2412                         value = ref_blob;
2414                 } else {
2415                         report("Unknown replacement in run request: `%s`", req->cmd);
2416                         return;
2417                 }
2419                 if (!string_format_from(buf, &bufpos, "%.*s%s", len, cmd, value))
2420                         return;
2422                 if (next)
2423                         next = strchr(next, ')') + 1;
2424                 cmd = next;
2425         }
2427         open_external_viewer(buf);
2430 /*
2431  * User request switch noodle
2432  */
2434 static int
2435 view_driver(struct view *view, enum request request)
2437         int i;
2439         if (request == REQ_NONE) {
2440                 doupdate();
2441                 return TRUE;
2442         }
2444         if (request > REQ_NONE) {
2445                 open_run_request(request);
2446                 return TRUE;
2447         }
2449         if (view && view->lines) {
2450                 request = view->ops->request(view, request, &view->line[view->lineno]);
2451                 if (request == REQ_NONE)
2452                         return TRUE;
2453         }
2455         switch (request) {
2456         case REQ_MOVE_UP:
2457         case REQ_MOVE_DOWN:
2458         case REQ_MOVE_PAGE_UP:
2459         case REQ_MOVE_PAGE_DOWN:
2460         case REQ_MOVE_FIRST_LINE:
2461         case REQ_MOVE_LAST_LINE:
2462                 move_view(view, request);
2463                 break;
2465         case REQ_SCROLL_LINE_DOWN:
2466         case REQ_SCROLL_LINE_UP:
2467         case REQ_SCROLL_PAGE_DOWN:
2468         case REQ_SCROLL_PAGE_UP:
2469                 scroll_view(view, request);
2470                 break;
2472         case REQ_VIEW_BLAME:
2473                 if (!opt_file[0]) {
2474                         report("No file chosen, press %s to open tree view",
2475                                get_key(REQ_VIEW_TREE));
2476                         break;
2477                 }
2478                 open_view(view, request, OPEN_DEFAULT);
2479                 break;
2481         case REQ_VIEW_BLOB:
2482                 if (!ref_blob[0]) {
2483                         report("No file chosen, press %s to open tree view",
2484                                get_key(REQ_VIEW_TREE));
2485                         break;
2486                 }
2487                 open_view(view, request, OPEN_DEFAULT);
2488                 break;
2490         case REQ_VIEW_PAGER:
2491                 if (!opt_pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
2492                         report("No pager content, press %s to run command from prompt",
2493                                get_key(REQ_PROMPT));
2494                         break;
2495                 }
2496                 open_view(view, request, OPEN_DEFAULT);
2497                 break;
2499         case REQ_VIEW_STAGE:
2500                 if (!VIEW(REQ_VIEW_STAGE)->lines) {
2501                         report("No stage content, press %s to open the status view and choose file",
2502                                get_key(REQ_VIEW_STATUS));
2503                         break;
2504                 }
2505                 open_view(view, request, OPEN_DEFAULT);
2506                 break;
2508         case REQ_VIEW_STATUS:
2509                 if (opt_is_inside_work_tree == FALSE) {
2510                         report("The status view requires a working tree");
2511                         break;
2512                 }
2513                 open_view(view, request, OPEN_DEFAULT);
2514                 break;
2516         case REQ_VIEW_MAIN:
2517         case REQ_VIEW_DIFF:
2518         case REQ_VIEW_LOG:
2519         case REQ_VIEW_TREE:
2520         case REQ_VIEW_HELP:
2521                 open_view(view, request, OPEN_DEFAULT);
2522                 break;
2524         case REQ_NEXT:
2525         case REQ_PREVIOUS:
2526                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
2528                 if ((view == VIEW(REQ_VIEW_DIFF) &&
2529                      view->parent == VIEW(REQ_VIEW_MAIN)) ||
2530                    (view == VIEW(REQ_VIEW_DIFF) &&
2531                      view->parent == VIEW(REQ_VIEW_BLAME)) ||
2532                    (view == VIEW(REQ_VIEW_STAGE) &&
2533                      view->parent == VIEW(REQ_VIEW_STATUS)) ||
2534                    (view == VIEW(REQ_VIEW_BLOB) &&
2535                      view->parent == VIEW(REQ_VIEW_TREE))) {
2536                         int line;
2538                         view = view->parent;
2539                         line = view->lineno;
2540                         move_view(view, request);
2541                         if (view_is_displayed(view))
2542                                 update_view_title(view);
2543                         if (line != view->lineno)
2544                                 view->ops->request(view, REQ_ENTER,
2545                                                    &view->line[view->lineno]);
2547                 } else {
2548                         move_view(view, request);
2549                 }
2550                 break;
2552         case REQ_VIEW_NEXT:
2553         {
2554                 int nviews = displayed_views();
2555                 int next_view = (current_view + 1) % nviews;
2557                 if (next_view == current_view) {
2558                         report("Only one view is displayed");
2559                         break;
2560                 }
2562                 current_view = next_view;
2563                 /* Blur out the title of the previous view. */
2564                 update_view_title(view);
2565                 report("");
2566                 break;
2567         }
2568         case REQ_REFRESH:
2569                 report("Refreshing is not yet supported for the %s view", view->name);
2570                 break;
2572         case REQ_TOGGLE_LINENO:
2573                 opt_line_number = !opt_line_number;
2574                 redraw_display();
2575                 break;
2577         case REQ_TOGGLE_DATE:
2578                 opt_date = !opt_date;
2579                 redraw_display();
2580                 break;
2582         case REQ_TOGGLE_AUTHOR:
2583                 opt_author = !opt_author;
2584                 redraw_display();
2585                 break;
2587         case REQ_TOGGLE_REV_GRAPH:
2588                 opt_rev_graph = !opt_rev_graph;
2589                 redraw_display();
2590                 break;
2592         case REQ_TOGGLE_REFS:
2593                 opt_show_refs = !opt_show_refs;
2594                 redraw_display();
2595                 break;
2597         case REQ_PROMPT:
2598                 /* Always reload^Wrerun commands from the prompt. */
2599                 open_view(view, opt_request, OPEN_RELOAD);
2600                 break;
2602         case REQ_SEARCH:
2603         case REQ_SEARCH_BACK:
2604                 search_view(view, request);
2605                 break;
2607         case REQ_FIND_NEXT:
2608         case REQ_FIND_PREV:
2609                 find_next(view, request);
2610                 break;
2612         case REQ_STOP_LOADING:
2613                 for (i = 0; i < ARRAY_SIZE(views); i++) {
2614                         view = &views[i];
2615                         if (view->pipe)
2616                                 report("Stopped loading the %s view", view->name),
2617                         end_update(view);
2618                 }
2619                 break;
2621         case REQ_SHOW_VERSION:
2622                 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
2623                 return TRUE;
2625         case REQ_SCREEN_RESIZE:
2626                 resize_display();
2627                 /* Fall-through */
2628         case REQ_SCREEN_REDRAW:
2629                 redraw_display();
2630                 break;
2632         case REQ_EDIT:
2633                 report("Nothing to edit");
2634                 break;
2637         case REQ_ENTER:
2638                 report("Nothing to enter");
2639                 break;
2642         case REQ_VIEW_CLOSE:
2643                 /* XXX: Mark closed views by letting view->parent point to the
2644                  * view itself. Parents to closed view should never be
2645                  * followed. */
2646                 if (view->parent &&
2647                     view->parent->parent != view->parent) {
2648                         memset(display, 0, sizeof(display));
2649                         current_view = 0;
2650                         display[current_view] = view->parent;
2651                         view->parent = view;
2652                         resize_display();
2653                         redraw_display();
2654                         break;
2655                 }
2656                 /* Fall-through */
2657         case REQ_QUIT:
2658                 return FALSE;
2660         default:
2661                 /* An unknown key will show most commonly used commands. */
2662                 report("Unknown key, press 'h' for help");
2663                 return TRUE;
2664         }
2666         return TRUE;
2670 /*
2671  * Pager backend
2672  */
2674 static bool
2675 pager_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
2677         char *text = line->data;
2678         enum line_type type = line->type;
2679         int attr;
2681         wmove(view->win, lineno, 0);
2683         if (selected) {
2684                 type = LINE_CURSOR;
2685                 wchgat(view->win, -1, 0, type, NULL);
2686         }
2688         attr = get_line_attr(type);
2689         wattrset(view->win, attr);
2691         if (opt_line_number || opt_tab_size < TABSIZE) {
2692                 static char spaces[] = "                    ";
2693                 int col_offset = 0, col = 0;
2695                 if (opt_line_number) {
2696                         unsigned long real_lineno = view->offset + lineno + 1;
2698                         if (real_lineno == 1 ||
2699                             (real_lineno % opt_num_interval) == 0) {
2700                                 wprintw(view->win, "%.*d", view->digits, real_lineno);
2702                         } else {
2703                                 waddnstr(view->win, spaces,
2704                                          MIN(view->digits, STRING_SIZE(spaces)));
2705                         }
2706                         waddstr(view->win, ": ");
2707                         col_offset = view->digits + 2;
2708                 }
2710                 while (text && col_offset + col < view->width) {
2711                         int cols_max = view->width - col_offset - col;
2712                         char *pos = text;
2713                         int cols;
2715                         if (*text == '\t') {
2716                                 text++;
2717                                 assert(sizeof(spaces) > TABSIZE);
2718                                 pos = spaces;
2719                                 cols = opt_tab_size - (col % opt_tab_size);
2721                         } else {
2722                                 text = strchr(text, '\t');
2723                                 cols = line ? text - pos : strlen(pos);
2724                         }
2726                         waddnstr(view->win, pos, MIN(cols, cols_max));
2727                         col += cols;
2728                 }
2730         } else {
2731                 int tilde_attr = get_line_attr(LINE_MAIN_DELIM);
2733                 draw_text(view, text, view->width, TRUE, tilde_attr);
2734         }
2736         return TRUE;
2739 static bool
2740 add_describe_ref(char *buf, size_t *bufpos, char *commit_id, const char *sep)
2742         char refbuf[SIZEOF_STR];
2743         char *ref = NULL;
2744         FILE *pipe;
2746         if (!string_format(refbuf, "git describe %s 2>/dev/null", commit_id))
2747                 return TRUE;
2749         pipe = popen(refbuf, "r");
2750         if (!pipe)
2751                 return TRUE;
2753         if ((ref = fgets(refbuf, sizeof(refbuf), pipe)))
2754                 ref = chomp_string(ref);
2755         pclose(pipe);
2757         if (!ref || !*ref)
2758                 return TRUE;
2760         /* This is the only fatal call, since it can "corrupt" the buffer. */
2761         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
2762                 return FALSE;
2764         return TRUE;
2767 static void
2768 add_pager_refs(struct view *view, struct line *line)
2770         char buf[SIZEOF_STR];
2771         char *commit_id = (char *)line->data + STRING_SIZE("commit ");
2772         struct ref **refs;
2773         size_t bufpos = 0, refpos = 0;
2774         const char *sep = "Refs: ";
2775         bool is_tag = FALSE;
2777         assert(line->type == LINE_COMMIT);
2779         refs = get_refs(commit_id);
2780         if (!refs) {
2781                 if (view == VIEW(REQ_VIEW_DIFF))
2782                         goto try_add_describe_ref;
2783                 return;
2784         }
2786         do {
2787                 struct ref *ref = refs[refpos];
2788                 char *fmt = ref->tag    ? "%s[%s]" :
2789                             ref->remote ? "%s<%s>" : "%s%s";
2791                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
2792                         return;
2793                 sep = ", ";
2794                 if (ref->tag)
2795                         is_tag = TRUE;
2796         } while (refs[refpos++]->next);
2798         if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
2799 try_add_describe_ref:
2800                 /* Add <tag>-g<commit_id> "fake" reference. */
2801                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
2802                         return;
2803         }
2805         if (bufpos == 0)
2806                 return;
2808         if (!realloc_lines(view, view->line_size + 1))
2809                 return;
2811         add_line_text(view, buf, LINE_PP_REFS);
2814 static bool
2815 pager_read(struct view *view, char *data)
2817         struct line *line;
2819         if (!data)
2820                 return TRUE;
2822         line = add_line_text(view, data, get_line_type(data));
2823         if (!line)
2824                 return FALSE;
2826         if (line->type == LINE_COMMIT &&
2827             (view == VIEW(REQ_VIEW_DIFF) ||
2828              view == VIEW(REQ_VIEW_LOG)))
2829                 add_pager_refs(view, line);
2831         return TRUE;
2834 static enum request
2835 pager_request(struct view *view, enum request request, struct line *line)
2837         int split = 0;
2839         if (request != REQ_ENTER)
2840                 return request;
2842         if (line->type == LINE_COMMIT &&
2843            (view == VIEW(REQ_VIEW_LOG) ||
2844             view == VIEW(REQ_VIEW_PAGER))) {
2845                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
2846                 split = 1;
2847         }
2849         /* Always scroll the view even if it was split. That way
2850          * you can use Enter to scroll through the log view and
2851          * split open each commit diff. */
2852         scroll_view(view, REQ_SCROLL_LINE_DOWN);
2854         /* FIXME: A minor workaround. Scrolling the view will call report("")
2855          * but if we are scrolling a non-current view this won't properly
2856          * update the view title. */
2857         if (split)
2858                 update_view_title(view);
2860         return REQ_NONE;
2863 static bool
2864 pager_grep(struct view *view, struct line *line)
2866         regmatch_t pmatch;
2867         char *text = line->data;
2869         if (!*text)
2870                 return FALSE;
2872         if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
2873                 return FALSE;
2875         return TRUE;
2878 static void
2879 pager_select(struct view *view, struct line *line)
2881         if (line->type == LINE_COMMIT) {
2882                 char *text = (char *)line->data + STRING_SIZE("commit ");
2884                 if (view != VIEW(REQ_VIEW_PAGER))
2885                         string_copy_rev(view->ref, text);
2886                 string_copy_rev(ref_commit, text);
2887         }
2890 static struct view_ops pager_ops = {
2891         "line",
2892         NULL,
2893         pager_read,
2894         pager_draw,
2895         pager_request,
2896         pager_grep,
2897         pager_select,
2898 };
2901 /*
2902  * Help backend
2903  */
2905 static bool
2906 help_open(struct view *view)
2908         char buf[BUFSIZ];
2909         int lines = ARRAY_SIZE(req_info) + 2;
2910         int i;
2912         if (view->lines > 0)
2913                 return TRUE;
2915         for (i = 0; i < ARRAY_SIZE(req_info); i++)
2916                 if (!req_info[i].request)
2917                         lines++;
2919         lines += run_requests + 1;
2921         view->line = calloc(lines, sizeof(*view->line));
2922         if (!view->line)
2923                 return FALSE;
2925         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
2927         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
2928                 char *key;
2930                 if (req_info[i].request == REQ_NONE)
2931                         continue;
2933                 if (!req_info[i].request) {
2934                         add_line_text(view, "", LINE_DEFAULT);
2935                         add_line_text(view, req_info[i].help, LINE_DEFAULT);
2936                         continue;
2937                 }
2939                 key = get_key(req_info[i].request);
2940                 if (!*key)
2941                         key = "(no key defined)";
2943                 if (!string_format(buf, "    %-25s %s", key, req_info[i].help))
2944                         continue;
2946                 add_line_text(view, buf, LINE_DEFAULT);
2947         }
2949         if (run_requests) {
2950                 add_line_text(view, "", LINE_DEFAULT);
2951                 add_line_text(view, "External commands:", LINE_DEFAULT);
2952         }
2954         for (i = 0; i < run_requests; i++) {
2955                 struct run_request *req = get_run_request(REQ_NONE + i + 1);
2956                 char *key;
2958                 if (!req)
2959                         continue;
2961                 key = get_key_name(req->key);
2962                 if (!*key)
2963                         key = "(no key defined)";
2965                 if (!string_format(buf, "    %-10s %-14s `%s`",
2966                                    keymap_table[req->keymap].name,
2967                                    key, req->cmd))
2968                         continue;
2970                 add_line_text(view, buf, LINE_DEFAULT);
2971         }
2973         return TRUE;
2976 static struct view_ops help_ops = {
2977         "line",
2978         help_open,
2979         NULL,
2980         pager_draw,
2981         pager_request,
2982         pager_grep,
2983         pager_select,
2984 };
2987 /*
2988  * Tree backend
2989  */
2991 struct tree_stack_entry {
2992         struct tree_stack_entry *prev;  /* Entry below this in the stack */
2993         unsigned long lineno;           /* Line number to restore */
2994         char *name;                     /* Position of name in opt_path */
2995 };
2997 /* The top of the path stack. */
2998 static struct tree_stack_entry *tree_stack = NULL;
2999 unsigned long tree_lineno = 0;
3001 static void
3002 pop_tree_stack_entry(void)
3004         struct tree_stack_entry *entry = tree_stack;
3006         tree_lineno = entry->lineno;
3007         entry->name[0] = 0;
3008         tree_stack = entry->prev;
3009         free(entry);
3012 static void
3013 push_tree_stack_entry(char *name, unsigned long lineno)
3015         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
3016         size_t pathlen = strlen(opt_path);
3018         if (!entry)
3019                 return;
3021         entry->prev = tree_stack;
3022         entry->name = opt_path + pathlen;
3023         tree_stack = entry;
3025         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
3026                 pop_tree_stack_entry();
3027                 return;
3028         }
3030         /* Move the current line to the first tree entry. */
3031         tree_lineno = 1;
3032         entry->lineno = lineno;
3035 /* Parse output from git-ls-tree(1):
3036  *
3037  * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile
3038  * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README
3039  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
3040  * 100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38 web.conf
3041  */
3043 #define SIZEOF_TREE_ATTR \
3044         STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t")
3046 #define TREE_UP_FORMAT "040000 tree %s\t.."
3048 static int
3049 tree_compare_entry(enum line_type type1, char *name1,
3050                    enum line_type type2, char *name2)
3052         if (type1 != type2) {
3053                 if (type1 == LINE_TREE_DIR)
3054                         return -1;
3055                 return 1;
3056         }
3058         return strcmp(name1, name2);
3061 static char *
3062 tree_path(struct line *line)
3064         char *path = line->data;
3066         return path + SIZEOF_TREE_ATTR;
3069 static bool
3070 tree_read(struct view *view, char *text)
3072         size_t textlen = text ? strlen(text) : 0;
3073         char buf[SIZEOF_STR];
3074         unsigned long pos;
3075         enum line_type type;
3076         bool first_read = view->lines == 0;
3078         if (!text)
3079                 return TRUE;
3080         if (textlen <= SIZEOF_TREE_ATTR)
3081                 return FALSE;
3083         type = text[STRING_SIZE("100644 ")] == 't'
3084              ? LINE_TREE_DIR : LINE_TREE_FILE;
3086         if (first_read) {
3087                 /* Add path info line */
3088                 if (!string_format(buf, "Directory path /%s", opt_path) ||
3089                     !realloc_lines(view, view->line_size + 1) ||
3090                     !add_line_text(view, buf, LINE_DEFAULT))
3091                         return FALSE;
3093                 /* Insert "link" to parent directory. */
3094                 if (*opt_path) {
3095                         if (!string_format(buf, TREE_UP_FORMAT, view->ref) ||
3096                             !realloc_lines(view, view->line_size + 1) ||
3097                             !add_line_text(view, buf, LINE_TREE_DIR))
3098                                 return FALSE;
3099                 }
3100         }
3102         /* Strip the path part ... */
3103         if (*opt_path) {
3104                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
3105                 size_t striplen = strlen(opt_path);
3106                 char *path = text + SIZEOF_TREE_ATTR;
3108                 if (pathlen > striplen)
3109                         memmove(path, path + striplen,
3110                                 pathlen - striplen + 1);
3111         }
3113         /* Skip "Directory ..." and ".." line. */
3114         for (pos = 1 + !!*opt_path; pos < view->lines; pos++) {
3115                 struct line *line = &view->line[pos];
3116                 char *path1 = tree_path(line);
3117                 char *path2 = text + SIZEOF_TREE_ATTR;
3118                 int cmp = tree_compare_entry(line->type, path1, type, path2);
3120                 if (cmp <= 0)
3121                         continue;
3123                 text = strdup(text);
3124                 if (!text)
3125                         return FALSE;
3127                 if (view->lines > pos)
3128                         memmove(&view->line[pos + 1], &view->line[pos],
3129                                 (view->lines - pos) * sizeof(*line));
3131                 line = &view->line[pos];
3132                 line->data = text;
3133                 line->type = type;
3134                 view->lines++;
3135                 return TRUE;
3136         }
3138         if (!add_line_text(view, text, type))
3139                 return FALSE;
3141         if (tree_lineno > view->lineno) {
3142                 view->lineno = tree_lineno;
3143                 tree_lineno = 0;
3144         }
3146         return TRUE;
3149 static enum request
3150 tree_request(struct view *view, enum request request, struct line *line)
3152         enum open_flags flags;
3154         if (request == REQ_VIEW_BLAME) {
3155                 char *filename = tree_path(line);
3157                 if (line->type == LINE_TREE_DIR) {
3158                         report("Cannot show blame for directory %s", opt_path);
3159                         return REQ_NONE;
3160                 }
3162                 string_copy(opt_ref, ref_commit);
3163                 string_ncopy(opt_file, filename, strlen(filename));
3164                 return request;
3165         }
3166         if (request == REQ_TREE_PARENT) {
3167                 if (*opt_path) {
3168                         /* fake 'cd  ..' */
3169                         request = REQ_ENTER;
3170                         line = &view->line[1];
3171                 } else {
3172                         /* quit view if at top of tree */
3173                         return REQ_VIEW_CLOSE;
3174                 }
3175         }
3176         if (request != REQ_ENTER)
3177                 return request;
3179         /* Cleanup the stack if the tree view is at a different tree. */
3180         while (!*opt_path && tree_stack)
3181                 pop_tree_stack_entry();
3183         switch (line->type) {
3184         case LINE_TREE_DIR:
3185                 /* Depending on whether it is a subdir or parent (updir?) link
3186                  * mangle the path buffer. */
3187                 if (line == &view->line[1] && *opt_path) {
3188                         pop_tree_stack_entry();
3190                 } else {
3191                         char *basename = tree_path(line);
3193                         push_tree_stack_entry(basename, view->lineno);
3194                 }
3196                 /* Trees and subtrees share the same ID, so they are not not
3197                  * unique like blobs. */
3198                 flags = OPEN_RELOAD;
3199                 request = REQ_VIEW_TREE;
3200                 break;
3202         case LINE_TREE_FILE:
3203                 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
3204                 request = REQ_VIEW_BLOB;
3205                 break;
3207         default:
3208                 return TRUE;
3209         }
3211         open_view(view, request, flags);
3212         if (request == REQ_VIEW_TREE) {
3213                 view->lineno = tree_lineno;
3214         }
3216         return REQ_NONE;
3219 static void
3220 tree_select(struct view *view, struct line *line)
3222         char *text = (char *)line->data + STRING_SIZE("100644 blob ");
3224         if (line->type == LINE_TREE_FILE) {
3225                 string_copy_rev(ref_blob, text);
3227         } else if (line->type != LINE_TREE_DIR) {
3228                 return;
3229         }
3231         string_copy_rev(view->ref, text);
3234 static struct view_ops tree_ops = {
3235         "file",
3236         NULL,
3237         tree_read,
3238         pager_draw,
3239         tree_request,
3240         pager_grep,
3241         tree_select,
3242 };
3244 static bool
3245 blob_read(struct view *view, char *line)
3247         if (!line)
3248                 return TRUE;
3249         return add_line_text(view, line, LINE_DEFAULT) != NULL;
3252 static struct view_ops blob_ops = {
3253         "line",
3254         NULL,
3255         blob_read,
3256         pager_draw,
3257         pager_request,
3258         pager_grep,
3259         pager_select,
3260 };
3262 /*
3263  * Blame backend
3264  *
3265  * Loading the blame view is a two phase job:
3266  *
3267  *  1. File content is read either using opt_file from the
3268  *     filesystem or using git-cat-file.
3269  *  2. Then blame information is incrementally added by
3270  *     reading output from git-blame.
3271  */
3273 struct blame_commit {
3274         char id[SIZEOF_REV];            /* SHA1 ID. */
3275         char title[128];                /* First line of the commit message. */
3276         char author[75];                /* Author of the commit. */
3277         struct tm time;                 /* Date from the author ident. */
3278         char filename[128];             /* Name of file. */
3279 };
3281 struct blame {
3282         struct blame_commit *commit;
3283         unsigned int header:1;
3284         char text[1];
3285 };
3287 #define BLAME_CAT_FILE_CMD "git cat-file blob %s:%s"
3288 #define BLAME_INCREMENTAL_CMD "git blame --incremental %s %s"
3290 static bool
3291 blame_open(struct view *view)
3293         char path[SIZEOF_STR];
3294         char ref[SIZEOF_STR] = "";
3296         if (sq_quote(path, 0, opt_file) >= sizeof(path))
3297                 return FALSE;
3299         if (*opt_ref && sq_quote(ref, 0, opt_ref) >= sizeof(ref))
3300                 return FALSE;
3302         if (*opt_ref) {
3303                 if (!string_format(view->cmd, BLAME_CAT_FILE_CMD, ref, path))
3304                         return FALSE;
3305         } else {
3306                 view->pipe = fopen(opt_file, "r");
3307                 if (!view->pipe &&
3308                     !string_format(view->cmd, BLAME_CAT_FILE_CMD, "HEAD", path))
3309                         return FALSE;
3310         }
3312         if (!view->pipe)
3313                 view->pipe = popen(view->cmd, "r");
3314         if (!view->pipe)
3315                 return FALSE;
3317         if (!string_format(view->cmd, BLAME_INCREMENTAL_CMD, ref, path))
3318                 return FALSE;
3320         string_format(view->ref, "%s ...", opt_file);
3321         string_copy_rev(view->vid, opt_file);
3322         set_nonblocking_input(TRUE);
3324         if (view->line) {
3325                 int i;
3327                 for (i = 0; i < view->lines; i++)
3328                         free(view->line[i].data);
3329                 free(view->line);
3330         }
3332         view->lines = view->line_alloc = view->line_size = view->lineno = 0;
3333         view->offset = view->lines  = view->lineno = 0;
3334         view->line = NULL;
3335         view->start_time = time(NULL);
3337         return TRUE;
3340 static struct blame_commit *
3341 get_blame_commit(struct view *view, const char *id)
3343         size_t i;
3345         for (i = 0; i < view->lines; i++) {
3346                 struct blame *blame = view->line[i].data;
3348                 if (!blame->commit)
3349                         continue;
3351                 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
3352                         return blame->commit;
3353         }
3355         {
3356                 struct blame_commit *commit = calloc(1, sizeof(*commit));
3358                 if (commit)
3359                         string_ncopy(commit->id, id, SIZEOF_REV);
3360                 return commit;
3361         }
3364 static bool
3365 parse_number(char **posref, size_t *number, size_t min, size_t max)
3367         char *pos = *posref;
3369         *posref = NULL;
3370         pos = strchr(pos + 1, ' ');
3371         if (!pos || !isdigit(pos[1]))
3372                 return FALSE;
3373         *number = atoi(pos + 1);
3374         if (*number < min || *number > max)
3375                 return FALSE;
3377         *posref = pos;
3378         return TRUE;
3381 static struct blame_commit *
3382 parse_blame_commit(struct view *view, char *text, int *blamed)
3384         struct blame_commit *commit;
3385         struct blame *blame;
3386         char *pos = text + SIZEOF_REV - 1;
3387         size_t lineno;
3388         size_t group;
3390         if (strlen(text) <= SIZEOF_REV || *pos != ' ')
3391                 return NULL;
3393         if (!parse_number(&pos, &lineno, 1, view->lines) ||
3394             !parse_number(&pos, &group, 1, view->lines - lineno + 1))
3395                 return NULL;
3397         commit = get_blame_commit(view, text);
3398         if (!commit)
3399                 return NULL;
3401         *blamed += group;
3402         while (group--) {
3403                 struct line *line = &view->line[lineno + group - 1];
3405                 blame = line->data;
3406                 blame->commit = commit;
3407                 line->dirty = 1;
3408         }
3409         blame->header = 1;
3411         return commit;
3414 static bool
3415 blame_read_file(struct view *view, char *line)
3417         if (!line) {
3418                 FILE *pipe = NULL;
3420                 if (view->lines > 0)
3421                         pipe = popen(view->cmd, "r");
3422                 view->cmd[0] = 0;
3423                 if (!pipe) {
3424                         report("Failed to load blame data");
3425                         return TRUE;
3426                 }
3428                 fclose(view->pipe);
3429                 view->pipe = pipe;
3430                 return FALSE;
3432         } else {
3433                 size_t linelen = strlen(line);
3434                 struct blame *blame = malloc(sizeof(*blame) + linelen);
3436                 if (!line)
3437                         return FALSE;
3439                 blame->commit = NULL;
3440                 strncpy(blame->text, line, linelen);
3441                 blame->text[linelen] = 0;
3442                 return add_line_data(view, blame, LINE_BLAME_COMMIT) != NULL;
3443         }
3446 static bool
3447 match_blame_header(const char *name, char **line)
3449         size_t namelen = strlen(name);
3450         bool matched = !strncmp(name, *line, namelen);
3452         if (matched)
3453                 *line += namelen;
3455         return matched;
3458 static bool
3459 blame_read(struct view *view, char *line)
3461         static struct blame_commit *commit = NULL;
3462         static int blamed = 0;
3463         static time_t author_time;
3465         if (*view->cmd)
3466                 return blame_read_file(view, line);
3468         if (!line) {
3469                 /* Reset all! */
3470                 commit = NULL;
3471                 blamed = 0;
3472                 string_format(view->ref, "%s", view->vid);
3473                 if (view_is_displayed(view)) {
3474                         update_view_title(view);
3475                         redraw_view_from(view, 0);
3476                 }
3477                 return TRUE;
3478         }
3480         if (!commit) {
3481                 commit = parse_blame_commit(view, line, &blamed);
3482                 string_format(view->ref, "%s %2d%%", view->vid,
3483                               blamed * 100 / view->lines);
3485         } else if (match_blame_header("author ", &line)) {
3486                 string_ncopy(commit->author, line, strlen(line));
3488         } else if (match_blame_header("author-time ", &line)) {
3489                 author_time = (time_t) atol(line);
3491         } else if (match_blame_header("author-tz ", &line)) {
3492                 long tz;
3494                 tz  = ('0' - line[1]) * 60 * 60 * 10;
3495                 tz += ('0' - line[2]) * 60 * 60;
3496                 tz += ('0' - line[3]) * 60;
3497                 tz += ('0' - line[4]) * 60;
3499                 if (line[0] == '-')
3500                         tz = -tz;
3502                 author_time -= tz;
3503                 gmtime_r(&author_time, &commit->time);
3505         } else if (match_blame_header("summary ", &line)) {
3506                 string_ncopy(commit->title, line, strlen(line));
3508         } else if (match_blame_header("filename ", &line)) {
3509                 string_ncopy(commit->filename, line, strlen(line));
3510                 commit = NULL;
3511         }
3513         return TRUE;
3516 static bool
3517 blame_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
3519         int tilde_attr = -1;
3520         struct blame *blame = line->data;
3521         int col = 0;
3523         wmove(view->win, lineno, 0);
3525         if (selected) {
3526                 wattrset(view->win, get_line_attr(LINE_CURSOR));
3527                 wchgat(view->win, -1, 0, LINE_CURSOR, NULL);
3528         } else {
3529                 wattrset(view->win, A_NORMAL);
3530                 tilde_attr = get_line_attr(LINE_MAIN_DELIM);
3531         }
3533         if (opt_date) {
3534                 int n;
3536                 if (!selected)
3537                         wattrset(view->win, get_line_attr(LINE_MAIN_DATE));
3538                 if (blame->commit) {
3539                         char buf[DATE_COLS + 1];
3540                         int timelen;
3542                         timelen = strftime(buf, sizeof(buf), DATE_FORMAT, &blame->commit->time);
3543                         n = draw_text(view, buf, view->width - col, FALSE, tilde_attr);
3544                         draw_text(view, " ", view->width - col - n, FALSE, tilde_attr);
3545                 }
3547                 col += DATE_COLS;
3548                 wmove(view->win, lineno, col);
3549                 if (col >= view->width)
3550                         return TRUE;
3551         }
3553         if (opt_author) {
3554                 int max = MIN(AUTHOR_COLS - 1, view->width - col);
3556                 if (!selected)
3557                         wattrset(view->win, get_line_attr(LINE_MAIN_AUTHOR));
3558                 if (blame->commit)
3559                         draw_text(view, blame->commit->author, max, TRUE, tilde_attr);
3560                 col += AUTHOR_COLS;
3561                 if (col >= view->width)
3562                         return TRUE;
3563                 wmove(view->win, lineno, col);
3564         }
3566         {
3567                 int max = MIN(ID_COLS - 1, view->width - col);
3569                 if (!selected)
3570                         wattrset(view->win, get_line_attr(LINE_BLAME_ID));
3571                 if (blame->commit)
3572                         draw_text(view, blame->commit->id, max, FALSE, -1);
3573                 col += ID_COLS;
3574                 if (col >= view->width)
3575                         return TRUE;
3576                 wmove(view->win, lineno, col);
3577         }
3579         {
3580                 unsigned long real_lineno = view->offset + lineno + 1;
3581                 char number[10] = "          ";
3582                 int max = MIN(view->digits, STRING_SIZE(number));
3583                 bool showtrimmed = FALSE;
3585                 if (real_lineno == 1 ||
3586                     (real_lineno % opt_num_interval) == 0) {
3587                         char fmt[] = "%1ld";
3589                         if (view->digits <= 9)
3590                                 fmt[1] = '0' + view->digits;
3592                         if (!string_format(number, fmt, real_lineno))
3593                                 number[0] = 0;
3594                         showtrimmed = TRUE;
3595                 }
3597                 if (max > view->width - col)
3598                         max = view->width - col;
3599                 if (!selected)
3600                         wattrset(view->win, get_line_attr(LINE_BLAME_LINENO));
3601                 col += draw_text(view, number, max, showtrimmed, tilde_attr);
3602                 if (col >= view->width)
3603                         return TRUE;
3604         }
3606         if (!selected)
3607                 wattrset(view->win, A_NORMAL);
3609         if (col >= view->width)
3610                 return TRUE;
3611         waddch(view->win, ACS_VLINE);
3612         col++;
3613         if (col >= view->width)
3614                 return TRUE;
3615         waddch(view->win, ' ');
3616         col++;
3617         col += draw_text(view, blame->text, view->width - col, TRUE, tilde_attr);
3619         return TRUE;
3622 static enum request
3623 blame_request(struct view *view, enum request request, struct line *line)
3625         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
3626         struct blame *blame = line->data;
3628         switch (request) {
3629         case REQ_ENTER:
3630                 if (!blame->commit) {
3631                         report("No commit loaded yet");
3632                         break;
3633                 }
3635                 if (!strcmp(blame->commit->id, "0000000000000000000000000000000000000000")) {
3636                         char path[SIZEOF_STR];
3638                         if (sq_quote(path, 0, view->vid) >= sizeof(path))
3639                                 break;
3640                         string_format(opt_cmd, "git diff-index --root --patch-with-stat -C -M --cached HEAD -- %s 2>/dev/null", path);
3641                 }
3643                 open_view(view, REQ_VIEW_DIFF, flags);
3644                 break;
3646         default:
3647                 return request;
3648         }
3650         return REQ_NONE;
3653 static bool
3654 blame_grep(struct view *view, struct line *line)
3656         struct blame *blame = line->data;
3657         struct blame_commit *commit = blame->commit;
3658         regmatch_t pmatch;
3660 #define MATCH(text) \
3661         (*text && regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
3663         if (commit) {
3664                 char buf[DATE_COLS + 1];
3666                 if (MATCH(commit->title) ||
3667                     MATCH(commit->author) ||
3668                     MATCH(commit->id))
3669                         return TRUE;
3671                 if (strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time) &&
3672                     MATCH(buf))
3673                         return TRUE;
3674         }
3676         return MATCH(blame->text);
3678 #undef MATCH
3681 static void
3682 blame_select(struct view *view, struct line *line)
3684         struct blame *blame = line->data;
3685         struct blame_commit *commit = blame->commit;
3687         if (!commit)
3688                 return;
3690         if (!strcmp(commit->id, "0000000000000000000000000000000000000000"))
3691                 string_ncopy(ref_commit, "HEAD", 4);
3692         else
3693                 string_copy_rev(ref_commit, commit->id);
3696 static struct view_ops blame_ops = {
3697         "line",
3698         blame_open,
3699         blame_read,
3700         blame_draw,
3701         blame_request,
3702         blame_grep,
3703         blame_select,
3704 };
3706 /*
3707  * Status backend
3708  */
3710 struct status {
3711         char status;
3712         struct {
3713                 mode_t mode;
3714                 char rev[SIZEOF_REV];
3715                 char name[SIZEOF_STR];
3716         } old;
3717         struct {
3718                 mode_t mode;
3719                 char rev[SIZEOF_REV];
3720                 char name[SIZEOF_STR];
3721         } new;
3722 };
3724 static struct status stage_status;
3725 static enum line_type stage_line_type;
3727 /* Get fields from the diff line:
3728  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
3729  */
3730 static inline bool
3731 status_get_diff(struct status *file, char *buf, size_t bufsize)
3733         char *old_mode = buf +  1;
3734         char *new_mode = buf +  8;
3735         char *old_rev  = buf + 15;
3736         char *new_rev  = buf + 56;
3737         char *status   = buf + 97;
3739         if (bufsize < 99 ||
3740             old_mode[-1] != ':' ||
3741             new_mode[-1] != ' ' ||
3742             old_rev[-1]  != ' ' ||
3743             new_rev[-1]  != ' ' ||
3744             status[-1]   != ' ')
3745                 return FALSE;
3747         file->status = *status;
3749         string_copy_rev(file->old.rev, old_rev);
3750         string_copy_rev(file->new.rev, new_rev);
3752         file->old.mode = strtoul(old_mode, NULL, 8);
3753         file->new.mode = strtoul(new_mode, NULL, 8);
3755         file->old.name[0] = file->new.name[0] = 0;
3757         return TRUE;
3760 static bool
3761 status_run(struct view *view, const char cmd[], bool diff, enum line_type type)
3763         struct status *file = NULL;
3764         struct status *unmerged = NULL;
3765         char buf[SIZEOF_STR * 4];
3766         size_t bufsize = 0;
3767         FILE *pipe;
3769         pipe = popen(cmd, "r");
3770         if (!pipe)
3771                 return FALSE;
3773         add_line_data(view, NULL, type);
3775         while (!feof(pipe) && !ferror(pipe)) {
3776                 char *sep;
3777                 size_t readsize;
3779                 readsize = fread(buf + bufsize, 1, sizeof(buf) - bufsize, pipe);
3780                 if (!readsize)
3781                         break;
3782                 bufsize += readsize;
3784                 /* Process while we have NUL chars. */
3785                 while ((sep = memchr(buf, 0, bufsize))) {
3786                         size_t sepsize = sep - buf + 1;
3788                         if (!file) {
3789                                 if (!realloc_lines(view, view->line_size + 1))
3790                                         goto error_out;
3792                                 file = calloc(1, sizeof(*file));
3793                                 if (!file)
3794                                         goto error_out;
3796                                 add_line_data(view, file, type);
3797                         }
3799                         /* Parse diff info part. */
3800                         if (!diff) {
3801                                 file->status = '?';
3803                         } else if (!file->status) {
3804                                 if (!status_get_diff(file, buf, sepsize))
3805                                         goto error_out;
3807                                 bufsize -= sepsize;
3808                                 memmove(buf, sep + 1, bufsize);
3810                                 sep = memchr(buf, 0, bufsize);
3811                                 if (!sep)
3812                                         break;
3813                                 sepsize = sep - buf + 1;
3815                                 /* Collapse all 'M'odified entries that
3816                                  * follow a associated 'U'nmerged entry.
3817                                  */
3818                                 if (file->status == 'U') {
3819                                         unmerged = file;
3821                                 } else if (unmerged) {
3822                                         int collapse = !strcmp(buf, unmerged->new.name);
3824                                         unmerged = NULL;
3825                                         if (collapse) {
3826                                                 free(file);
3827                                                 view->lines--;
3828                                                 continue;
3829                                         }
3830                                 }
3831                         }
3833                         /* Grab the old name for rename/copy. */
3834                         if (!*file->old.name &&
3835                             (file->status == 'R' || file->status == 'C')) {
3836                                 sepsize = sep - buf + 1;
3837                                 string_ncopy(file->old.name, buf, sepsize);
3838                                 bufsize -= sepsize;
3839                                 memmove(buf, sep + 1, bufsize);
3841                                 sep = memchr(buf, 0, bufsize);
3842                                 if (!sep)
3843                                         break;
3844                                 sepsize = sep - buf + 1;
3845                         }
3847                         /* git-ls-files just delivers a NUL separated
3848                          * list of file names similar to the second half
3849                          * of the git-diff-* output. */
3850                         string_ncopy(file->new.name, buf, sepsize);
3851                         if (!*file->old.name)
3852                                 string_copy(file->old.name, file->new.name);
3853                         bufsize -= sepsize;
3854                         memmove(buf, sep + 1, bufsize);
3855                         file = NULL;
3856                 }
3857         }
3859         if (ferror(pipe)) {
3860 error_out:
3861                 pclose(pipe);
3862                 return FALSE;
3863         }
3865         if (!view->line[view->lines - 1].data)
3866                 add_line_data(view, NULL, LINE_STAT_NONE);
3868         pclose(pipe);
3869         return TRUE;
3872 /* Don't show unmerged entries in the staged section. */
3873 #define STATUS_DIFF_INDEX_CMD "git diff-index -z --diff-filter=ACDMRTXB --cached -M HEAD"
3874 #define STATUS_DIFF_FILES_CMD "git diff-files -z"
3875 #define STATUS_LIST_OTHER_CMD \
3876         "git ls-files -z --others --exclude-per-directory=.gitignore"
3878 #define STATUS_DIFF_INDEX_SHOW_CMD \
3879         "git diff-index --root --patch-with-stat -C -M --cached HEAD -- %s %s 2>/dev/null"
3881 #define STATUS_DIFF_FILES_SHOW_CMD \
3882         "git diff-files --root --patch-with-stat -C -M -- %s %s 2>/dev/null"
3884 /* First parse staged info using git-diff-index(1), then parse unstaged
3885  * info using git-diff-files(1), and finally untracked files using
3886  * git-ls-files(1). */
3887 static bool
3888 status_open(struct view *view)
3890         struct stat statbuf;
3891         char exclude[SIZEOF_STR];
3892         char cmd[SIZEOF_STR];
3893         unsigned long prev_lineno = view->lineno;
3894         size_t i;
3896         for (i = 0; i < view->lines; i++)
3897                 free(view->line[i].data);
3898         free(view->line);
3899         view->lines = view->line_alloc = view->line_size = view->lineno = 0;
3900         view->line = NULL;
3902         if (!realloc_lines(view, view->line_size + 6))
3903                 return FALSE;
3905         if (!string_format(exclude, "%s/info/exclude", opt_git_dir))
3906                 return FALSE;
3908         string_copy(cmd, STATUS_LIST_OTHER_CMD);
3910         if (stat(exclude, &statbuf) >= 0) {
3911                 size_t cmdsize = strlen(cmd);
3913                 if (!string_format_from(cmd, &cmdsize, " %s", "--exclude-from=") ||
3914                     sq_quote(cmd, cmdsize, exclude) >= sizeof(cmd))
3915                         return FALSE;
3916         }
3918         system("git update-index -q --refresh");
3920         if (!status_run(view, STATUS_DIFF_INDEX_CMD, TRUE, LINE_STAT_STAGED) ||
3921             !status_run(view, STATUS_DIFF_FILES_CMD, TRUE, LINE_STAT_UNSTAGED) ||
3922             !status_run(view, cmd, FALSE, LINE_STAT_UNTRACKED))
3923                 return FALSE;
3925         /* If all went well restore the previous line number to stay in
3926          * the context. */
3927         if (prev_lineno < view->lines)
3928                 view->lineno = prev_lineno;
3929         else
3930                 view->lineno = view->lines - 1;
3932         return TRUE;
3935 static bool
3936 status_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
3938         struct status *status = line->data;
3939         int tilde_attr = get_line_attr(LINE_MAIN_DELIM);
3941         wmove(view->win, lineno, 0);
3943         if (selected) {
3944                 wattrset(view->win, get_line_attr(LINE_CURSOR));
3945                 wchgat(view->win, -1, 0, LINE_CURSOR, NULL);
3946                 tilde_attr = -1;
3948         } else if (!status && line->type != LINE_STAT_NONE) {
3949                 wattrset(view->win, get_line_attr(LINE_STAT_SECTION));
3950                 wchgat(view->win, -1, 0, LINE_STAT_SECTION, NULL);
3952         } else {
3953                 wattrset(view->win, get_line_attr(line->type));
3954         }
3956         if (!status) {
3957                 char *text;
3959                 switch (line->type) {
3960                 case LINE_STAT_STAGED:
3961                         text = "Changes to be committed:";
3962                         break;
3964                 case LINE_STAT_UNSTAGED:
3965                         text = "Changed but not updated:";
3966                         break;
3968                 case LINE_STAT_UNTRACKED:
3969                         text = "Untracked files:";
3970                         break;
3972                 case LINE_STAT_NONE:
3973                         text = "    (no files)";
3974                         break;
3976                 default:
3977                         return FALSE;
3978                 }
3980                 draw_text(view, text, view->width, TRUE, tilde_attr);
3981                 return TRUE;
3982         }
3984         waddch(view->win, status->status);
3985         if (!selected)
3986                 wattrset(view->win, A_NORMAL);
3987         wmove(view->win, lineno, 4);
3988         if (view->width < 5)
3989                 return TRUE;
3991         draw_text(view, status->new.name, view->width - 5, TRUE, tilde_attr);
3992         return TRUE;
3995 static enum request
3996 status_enter(struct view *view, struct line *line)
3998         struct status *status = line->data;
3999         char oldpath[SIZEOF_STR] = "";
4000         char newpath[SIZEOF_STR] = "";
4001         char *info;
4002         size_t cmdsize = 0;
4004         if (line->type == LINE_STAT_NONE ||
4005             (!status && line[1].type == LINE_STAT_NONE)) {
4006                 report("No file to diff");
4007                 return REQ_NONE;
4008         }
4010         if (status) {
4011                 if (sq_quote(oldpath, 0, status->old.name) >= sizeof(oldpath))
4012                         return REQ_QUIT;
4013                 /* Diffs for unmerged entries are empty when pasing the
4014                  * new path, so leave it empty. */
4015                 if (status->status != 'U' &&
4016                     sq_quote(newpath, 0, status->new.name) >= sizeof(newpath))
4017                         return REQ_QUIT;
4018         }
4020         if (opt_cdup[0] &&
4021             line->type != LINE_STAT_UNTRACKED &&
4022             !string_format_from(opt_cmd, &cmdsize, "cd %s;", opt_cdup))
4023                 return REQ_QUIT;
4025         switch (line->type) {
4026         case LINE_STAT_STAGED:
4027                 if (!string_format_from(opt_cmd, &cmdsize,
4028                                         STATUS_DIFF_INDEX_SHOW_CMD, oldpath, newpath))
4029                         return REQ_QUIT;
4030                 if (status)
4031                         info = "Staged changes to %s";
4032                 else
4033                         info = "Staged changes";
4034                 break;
4036         case LINE_STAT_UNSTAGED:
4037                 if (!string_format_from(opt_cmd, &cmdsize,
4038                                         STATUS_DIFF_FILES_SHOW_CMD, oldpath, newpath))
4039                         return REQ_QUIT;
4040                 if (status)
4041                         info = "Unstaged changes to %s";
4042                 else
4043                         info = "Unstaged changes";
4044                 break;
4046         case LINE_STAT_UNTRACKED:
4047                 if (opt_pipe)
4048                         return REQ_QUIT;
4051                 if (!status) {
4052                         report("No file to show");
4053                         return REQ_NONE;
4054                 }
4056                 opt_pipe = fopen(status->new.name, "r");
4057                 info = "Untracked file %s";
4058                 break;
4060         default:
4061                 die("line type %d not handled in switch", line->type);
4062         }
4064         open_view(view, REQ_VIEW_STAGE, OPEN_RELOAD | OPEN_SPLIT);
4065         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
4066                 if (status) {
4067                         stage_status = *status;
4068                 } else {
4069                         memset(&stage_status, 0, sizeof(stage_status));
4070                 }
4072                 stage_line_type = line->type;
4073                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
4074         }
4076         return REQ_NONE;
4080 static bool
4081 status_update_file(struct view *view, struct status *status, enum line_type type)
4083         char cmd[SIZEOF_STR];
4084         char buf[SIZEOF_STR];
4085         size_t cmdsize = 0;
4086         size_t bufsize = 0;
4087         size_t written = 0;
4088         FILE *pipe;
4090         if (opt_cdup[0] &&
4091             type != LINE_STAT_UNTRACKED &&
4092             !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
4093                 return FALSE;
4095         switch (type) {
4096         case LINE_STAT_STAGED:
4097                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
4098                                         status->old.mode,
4099                                         status->old.rev,
4100                                         status->old.name, 0))
4101                         return FALSE;
4103                 string_add(cmd, cmdsize, "git update-index -z --index-info");
4104                 break;
4106         case LINE_STAT_UNSTAGED:
4107         case LINE_STAT_UNTRACKED:
4108                 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
4109                         return FALSE;
4111                 string_add(cmd, cmdsize, "git update-index -z --add --remove --stdin");
4112                 break;
4114         default:
4115                 die("line type %d not handled in switch", type);
4116         }
4118         pipe = popen(cmd, "w");
4119         if (!pipe)
4120                 return FALSE;
4122         while (!ferror(pipe) && written < bufsize) {
4123                 written += fwrite(buf + written, 1, bufsize - written, pipe);
4124         }
4126         pclose(pipe);
4128         if (written != bufsize)
4129                 return FALSE;
4131         return TRUE;
4134 static void
4135 status_update(struct view *view)
4137         struct line *line = &view->line[view->lineno];
4139         assert(view->lines);
4141         if (!line->data) {
4142                 while (++line < view->line + view->lines && line->data) {
4143                         if (!status_update_file(view, line->data, line->type))
4144                                 report("Failed to update file status");
4145                 }
4147                 if (!line[-1].data) {
4148                         report("Nothing to update");
4149                         return;
4150                 }
4152         } else if (!status_update_file(view, line->data, line->type)) {
4153                 report("Failed to update file status");
4154         }
4157 static enum request
4158 status_request(struct view *view, enum request request, struct line *line)
4160         struct status *status = line->data;
4162         switch (request) {
4163         case REQ_STATUS_UPDATE:
4164                 status_update(view);
4165                 break;
4167         case REQ_STATUS_MERGE:
4168                 if (!status || status->status != 'U') {
4169                         report("Merging only possible for files with unmerged status ('U').");
4170                         return REQ_NONE;
4171                 }
4172                 open_mergetool(status->new.name);
4173                 break;
4175         case REQ_EDIT:
4176                 if (!status)
4177                         return request;
4179                 open_editor(status->status != '?', status->new.name);
4180                 break;
4182         case REQ_VIEW_BLAME:
4183                 if (status) {
4184                         string_copy(opt_file, status->new.name);
4185                         opt_ref[0] = 0;
4186                 }
4187                 return request;
4189         case REQ_ENTER:
4190                 /* After returning the status view has been split to
4191                  * show the stage view. No further reloading is
4192                  * necessary. */
4193                 status_enter(view, line);
4194                 return REQ_NONE;
4196         case REQ_REFRESH:
4197                 /* Simply reload the view. */
4198                 break;
4200         default:
4201                 return request;
4202         }
4204         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
4206         return REQ_NONE;
4209 static void
4210 status_select(struct view *view, struct line *line)
4212         struct status *status = line->data;
4213         char file[SIZEOF_STR] = "all files";
4214         char *text;
4215         char *key;
4217         if (status && !string_format(file, "'%s'", status->new.name))
4218                 return;
4220         if (!status && line[1].type == LINE_STAT_NONE)
4221                 line++;
4223         switch (line->type) {
4224         case LINE_STAT_STAGED:
4225                 text = "Press %s to unstage %s for commit";
4226                 break;
4228         case LINE_STAT_UNSTAGED:
4229                 text = "Press %s to stage %s for commit";
4230                 break;
4232         case LINE_STAT_UNTRACKED:
4233                 text = "Press %s to stage %s for addition";
4234                 break;
4236         case LINE_STAT_NONE:
4237                 text = "Nothing to update";
4238                 break;
4240         default:
4241                 die("line type %d not handled in switch", line->type);
4242         }
4244         if (status && status->status == 'U') {
4245                 text = "Press %s to resolve conflict in %s";
4246                 key = get_key(REQ_STATUS_MERGE);
4248         } else {
4249                 key = get_key(REQ_STATUS_UPDATE);
4250         }
4252         string_format(view->ref, text, key, file);
4255 static bool
4256 status_grep(struct view *view, struct line *line)
4258         struct status *status = line->data;
4259         enum { S_STATUS, S_NAME, S_END } state;
4260         char buf[2] = "?";
4261         regmatch_t pmatch;
4263         if (!status)
4264                 return FALSE;
4266         for (state = S_STATUS; state < S_END; state++) {
4267                 char *text;
4269                 switch (state) {
4270                 case S_NAME:    text = status->new.name;        break;
4271                 case S_STATUS:
4272                         buf[0] = status->status;
4273                         text = buf;
4274                         break;
4276                 default:
4277                         return FALSE;
4278                 }
4280                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4281                         return TRUE;
4282         }
4284         return FALSE;
4287 static struct view_ops status_ops = {
4288         "file",
4289         status_open,
4290         NULL,
4291         status_draw,
4292         status_request,
4293         status_grep,
4294         status_select,
4295 };
4298 static bool
4299 stage_diff_line(FILE *pipe, struct line *line)
4301         char *buf = line->data;
4302         size_t bufsize = strlen(buf);
4303         size_t written = 0;
4305         while (!ferror(pipe) && written < bufsize) {
4306                 written += fwrite(buf + written, 1, bufsize - written, pipe);
4307         }
4309         fputc('\n', pipe);
4311         return written == bufsize;
4314 static struct line *
4315 stage_diff_hdr(struct view *view, struct line *line)
4317         int diff_hdr_dir = line->type == LINE_DIFF_CHUNK ? -1 : 1;
4318         struct line *diff_hdr;
4320         if (line->type == LINE_DIFF_CHUNK)
4321                 diff_hdr = line - 1;
4322         else
4323                 diff_hdr = view->line + 1;
4325         while (diff_hdr > view->line && diff_hdr < view->line + view->lines) {
4326                 if (diff_hdr->type == LINE_DIFF_HEADER)
4327                         return diff_hdr;
4329                 diff_hdr += diff_hdr_dir;
4330         }
4332         return NULL;
4335 static bool
4336 stage_update_chunk(struct view *view, struct line *line)
4338         char cmd[SIZEOF_STR];
4339         size_t cmdsize = 0;
4340         struct line *diff_hdr, *diff_chunk, *diff_end;
4341         FILE *pipe;
4343         diff_hdr = stage_diff_hdr(view, line);
4344         if (!diff_hdr)
4345                 return FALSE;
4347         if (opt_cdup[0] &&
4348             !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
4349                 return FALSE;
4351         if (!string_format_from(cmd, &cmdsize,
4352                                 "git apply --cached %s - && "
4353                                 "git update-index -q --unmerged --refresh 2>/dev/null",
4354                                 stage_line_type == LINE_STAT_STAGED ? "-R" : ""))
4355                 return FALSE;
4357         pipe = popen(cmd, "w");
4358         if (!pipe)
4359                 return FALSE;
4361         diff_end = view->line + view->lines;
4362         if (line->type != LINE_DIFF_CHUNK) {
4363                 diff_chunk = diff_hdr;
4365         } else {
4366                 for (diff_chunk = line + 1; diff_chunk < diff_end; diff_chunk++)
4367                         if (diff_chunk->type == LINE_DIFF_CHUNK ||
4368                             diff_chunk->type == LINE_DIFF_HEADER)
4369                                 diff_end = diff_chunk;
4371                 diff_chunk = line;
4373                 while (diff_hdr->type != LINE_DIFF_CHUNK) {
4374                         switch (diff_hdr->type) {
4375                         case LINE_DIFF_HEADER:
4376                         case LINE_DIFF_INDEX:
4377                         case LINE_DIFF_ADD:
4378                         case LINE_DIFF_DEL:
4379                                 break;
4381                         default:
4382                                 diff_hdr++;
4383                                 continue;
4384                         }
4386                         if (!stage_diff_line(pipe, diff_hdr++)) {
4387                                 pclose(pipe);
4388                                 return FALSE;
4389                         }
4390                 }
4391         }
4393         while (diff_chunk < diff_end && stage_diff_line(pipe, diff_chunk))
4394                 diff_chunk++;
4396         pclose(pipe);
4398         if (diff_chunk != diff_end)
4399                 return FALSE;
4401         return TRUE;
4404 static void
4405 stage_update(struct view *view, struct line *line)
4407         if (stage_line_type != LINE_STAT_UNTRACKED &&
4408             (line->type == LINE_DIFF_CHUNK || !stage_status.status)) {
4409                 if (!stage_update_chunk(view, line)) {
4410                         report("Failed to apply chunk");
4411                         return;
4412                 }
4414         } else if (!status_update_file(view, &stage_status, stage_line_type)) {
4415                 report("Failed to update file");
4416                 return;
4417         }
4419         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
4421         view = VIEW(REQ_VIEW_STATUS);
4422         if (view_is_displayed(view))
4423                 status_enter(view, &view->line[view->lineno]);
4426 static enum request
4427 stage_request(struct view *view, enum request request, struct line *line)
4429         switch (request) {
4430         case REQ_STATUS_UPDATE:
4431                 stage_update(view, line);
4432                 break;
4434         case REQ_EDIT:
4435                 if (!stage_status.new.name[0])
4436                         return request;
4438                 open_editor(stage_status.status != '?', stage_status.new.name);
4439                 break;
4441         case REQ_VIEW_BLAME:
4442                 if (stage_status.new.name[0]) {
4443                         string_copy(opt_file, stage_status.new.name);
4444                         opt_ref[0] = 0;
4445                 }
4446                 return request;
4448         case REQ_ENTER:
4449                 pager_request(view, request, line);
4450                 break;
4452         default:
4453                 return request;
4454         }
4456         return REQ_NONE;
4459 static struct view_ops stage_ops = {
4460         "line",
4461         NULL,
4462         pager_read,
4463         pager_draw,
4464         stage_request,
4465         pager_grep,
4466         pager_select,
4467 };
4470 /*
4471  * Revision graph
4472  */
4474 struct commit {
4475         char id[SIZEOF_REV];            /* SHA1 ID. */
4476         char title[128];                /* First line of the commit message. */
4477         char author[75];                /* Author of the commit. */
4478         struct tm time;                 /* Date from the author ident. */
4479         struct ref **refs;              /* Repository references. */
4480         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
4481         size_t graph_size;              /* The width of the graph array. */
4482         bool has_parents;               /* Rewritten --parents seen. */
4483 };
4485 /* Size of rev graph with no  "padding" columns */
4486 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
4488 struct rev_graph {
4489         struct rev_graph *prev, *next, *parents;
4490         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
4491         size_t size;
4492         struct commit *commit;
4493         size_t pos;
4494         unsigned int boundary:1;
4495 };
4497 /* Parents of the commit being visualized. */
4498 static struct rev_graph graph_parents[4];
4500 /* The current stack of revisions on the graph. */
4501 static struct rev_graph graph_stacks[4] = {
4502         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
4503         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
4504         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
4505         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
4506 };
4508 static inline bool
4509 graph_parent_is_merge(struct rev_graph *graph)
4511         return graph->parents->size > 1;
4514 static inline void
4515 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
4517         struct commit *commit = graph->commit;
4519         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
4520                 commit->graph[commit->graph_size++] = symbol;
4523 static void
4524 done_rev_graph(struct rev_graph *graph)
4526         if (graph_parent_is_merge(graph) &&
4527             graph->pos < graph->size - 1 &&
4528             graph->next->size == graph->size + graph->parents->size - 1) {
4529                 size_t i = graph->pos + graph->parents->size - 1;
4531                 graph->commit->graph_size = i * 2;
4532                 while (i < graph->next->size - 1) {
4533                         append_to_rev_graph(graph, ' ');
4534                         append_to_rev_graph(graph, '\\');
4535                         i++;
4536                 }
4537         }
4539         graph->size = graph->pos = 0;
4540         graph->commit = NULL;
4541         memset(graph->parents, 0, sizeof(*graph->parents));
4544 static void
4545 push_rev_graph(struct rev_graph *graph, char *parent)
4547         int i;
4549         /* "Collapse" duplicate parents lines.
4550          *
4551          * FIXME: This needs to also update update the drawn graph but
4552          * for now it just serves as a method for pruning graph lines. */
4553         for (i = 0; i < graph->size; i++)
4554                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
4555                         return;
4557         if (graph->size < SIZEOF_REVITEMS) {
4558                 string_copy_rev(graph->rev[graph->size++], parent);
4559         }
4562 static chtype
4563 get_rev_graph_symbol(struct rev_graph *graph)
4565         chtype symbol;
4567         if (graph->boundary)
4568                 symbol = REVGRAPH_BOUND;
4569         else if (graph->parents->size == 0)
4570                 symbol = REVGRAPH_INIT;
4571         else if (graph_parent_is_merge(graph))
4572                 symbol = REVGRAPH_MERGE;
4573         else if (graph->pos >= graph->size)
4574                 symbol = REVGRAPH_BRANCH;
4575         else
4576                 symbol = REVGRAPH_COMMIT;
4578         return symbol;
4581 static void
4582 draw_rev_graph(struct rev_graph *graph)
4584         struct rev_filler {
4585                 chtype separator, line;
4586         };
4587         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
4588         static struct rev_filler fillers[] = {
4589                 { ' ',  REVGRAPH_LINE },
4590                 { '`',  '.' },
4591                 { '\'', ' ' },
4592                 { '/',  ' ' },
4593         };
4594         chtype symbol = get_rev_graph_symbol(graph);
4595         struct rev_filler *filler;
4596         size_t i;
4598         filler = &fillers[DEFAULT];
4600         for (i = 0; i < graph->pos; i++) {
4601                 append_to_rev_graph(graph, filler->line);
4602                 if (graph_parent_is_merge(graph->prev) &&
4603                     graph->prev->pos == i)
4604                         filler = &fillers[RSHARP];
4606                 append_to_rev_graph(graph, filler->separator);
4607         }
4609         /* Place the symbol for this revision. */
4610         append_to_rev_graph(graph, symbol);
4612         if (graph->prev->size > graph->size)
4613                 filler = &fillers[RDIAG];
4614         else
4615                 filler = &fillers[DEFAULT];
4617         i++;
4619         for (; i < graph->size; i++) {
4620                 append_to_rev_graph(graph, filler->separator);
4621                 append_to_rev_graph(graph, filler->line);
4622                 if (graph_parent_is_merge(graph->prev) &&
4623                     i < graph->prev->pos + graph->parents->size)
4624                         filler = &fillers[RSHARP];
4625                 if (graph->prev->size > graph->size)
4626                         filler = &fillers[LDIAG];
4627         }
4629         if (graph->prev->size > graph->size) {
4630                 append_to_rev_graph(graph, filler->separator);
4631                 if (filler->line != ' ')
4632                         append_to_rev_graph(graph, filler->line);
4633         }
4636 /* Prepare the next rev graph */
4637 static void
4638 prepare_rev_graph(struct rev_graph *graph)
4640         size_t i;
4642         /* First, traverse all lines of revisions up to the active one. */
4643         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
4644                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
4645                         break;
4647                 push_rev_graph(graph->next, graph->rev[graph->pos]);
4648         }
4650         /* Interleave the new revision parent(s). */
4651         for (i = 0; !graph->boundary && i < graph->parents->size; i++)
4652                 push_rev_graph(graph->next, graph->parents->rev[i]);
4654         /* Lastly, put any remaining revisions. */
4655         for (i = graph->pos + 1; i < graph->size; i++)
4656                 push_rev_graph(graph->next, graph->rev[i]);
4659 static void
4660 update_rev_graph(struct rev_graph *graph)
4662         /* If this is the finalizing update ... */
4663         if (graph->commit)
4664                 prepare_rev_graph(graph);
4666         /* Graph visualization needs a one rev look-ahead,
4667          * so the first update doesn't visualize anything. */
4668         if (!graph->prev->commit)
4669                 return;
4671         draw_rev_graph(graph->prev);
4672         done_rev_graph(graph->prev->prev);
4676 /*
4677  * Main view backend
4678  */
4680 static bool
4681 main_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
4683         char buf[DATE_COLS + 1];
4684         struct commit *commit = line->data;
4685         enum line_type type;
4686         int col = 0;
4687         size_t timelen;
4688         int tilde_attr;
4689         int space;
4691         if (!*commit->author)
4692                 return FALSE;
4694         space = view->width;
4695         wmove(view->win, lineno, col);
4697         if (selected) {
4698                 type = LINE_CURSOR;
4699                 wattrset(view->win, get_line_attr(type));
4700                 wchgat(view->win, -1, 0, type, NULL);
4701                 tilde_attr = -1;
4702         } else {
4703                 type = LINE_MAIN_COMMIT;
4704                 wattrset(view->win, get_line_attr(LINE_MAIN_DATE));
4705                 tilde_attr = get_line_attr(LINE_MAIN_DELIM);
4706         }
4708         if (opt_date) {
4709                 int n;
4711                 timelen = strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time);
4712                 n = draw_text(view, buf, view->width - col, FALSE, tilde_attr);
4713                 draw_text(view, " ", view->width - col - n, FALSE, tilde_attr);
4715                 col += DATE_COLS;
4716                 wmove(view->win, lineno, col);
4717                 if (col >= view->width)
4718                         return TRUE;
4719         }
4720         if (type != LINE_CURSOR)
4721                 wattrset(view->win, get_line_attr(LINE_MAIN_AUTHOR));
4723         if (opt_author) {
4724                 int max_len;
4726                 max_len = view->width - col;
4727                 if (max_len > AUTHOR_COLS - 1)
4728                         max_len = AUTHOR_COLS - 1;
4729                 draw_text(view, commit->author, max_len, TRUE, tilde_attr);
4730                 col += AUTHOR_COLS;
4731                 if (col >= view->width)
4732                         return TRUE;
4733         }
4735         if (opt_rev_graph && commit->graph_size) {
4736                 size_t graph_size = view->width - col;
4737                 size_t i;
4739                 if (type != LINE_CURSOR)
4740                         wattrset(view->win, get_line_attr(LINE_MAIN_REVGRAPH));
4741                 wmove(view->win, lineno, col);
4742                 if (graph_size > commit->graph_size)
4743                         graph_size = commit->graph_size;
4744                 /* Using waddch() instead of waddnstr() ensures that
4745                  * they'll be rendered correctly for the cursor line. */
4746                 for (i = 0; i < graph_size; i++)
4747                         waddch(view->win, commit->graph[i]);
4749                 col += commit->graph_size + 1;
4750                 if (col >= view->width)
4751                         return TRUE;
4752                 waddch(view->win, ' ');
4753         }
4754         if (type != LINE_CURSOR)
4755                 wattrset(view->win, A_NORMAL);
4757         wmove(view->win, lineno, col);
4759         if (opt_show_refs && commit->refs) {
4760                 size_t i = 0;
4762                 do {
4763                         if (type == LINE_CURSOR)
4764                                 ;
4765                         else if (commit->refs[i]->ltag)
4766                                 wattrset(view->win, get_line_attr(LINE_MAIN_LOCAL_TAG));
4767                         else if (commit->refs[i]->tag)
4768                                 wattrset(view->win, get_line_attr(LINE_MAIN_TAG));
4769                         else if (commit->refs[i]->remote)
4770                                 wattrset(view->win, get_line_attr(LINE_MAIN_REMOTE));
4771                         else
4772                                 wattrset(view->win, get_line_attr(LINE_MAIN_REF));
4774                         col += draw_text(view, "[", view->width - col, TRUE, tilde_attr);
4775                         col += draw_text(view, commit->refs[i]->name, view->width - col,
4776                                          TRUE, tilde_attr);
4777                         col += draw_text(view, "]", view->width - col, TRUE, tilde_attr);
4778                         if (type != LINE_CURSOR)
4779                                 wattrset(view->win, A_NORMAL);
4780                         col += draw_text(view, " ", view->width - col, TRUE, tilde_attr);
4781                         if (col >= view->width)
4782                                 return TRUE;
4783                 } while (commit->refs[i++]->next);
4784         }
4786         if (type != LINE_CURSOR)
4787                 wattrset(view->win, get_line_attr(type));
4789         draw_text(view, commit->title, view->width - col, TRUE, tilde_attr);
4790         return TRUE;
4793 /* Reads git log --pretty=raw output and parses it into the commit struct. */
4794 static bool
4795 main_read(struct view *view, char *line)
4797         static struct rev_graph *graph = graph_stacks;
4798         enum line_type type;
4799         struct commit *commit;
4801         if (!line) {
4802                 update_rev_graph(graph);
4803                 return TRUE;
4804         }
4806         type = get_line_type(line);
4807         if (type == LINE_COMMIT) {
4808                 commit = calloc(1, sizeof(struct commit));
4809                 if (!commit)
4810                         return FALSE;
4812                 line += STRING_SIZE("commit ");
4813                 if (*line == '-') {
4814                         graph->boundary = 1;
4815                         line++;
4816                 }
4818                 string_copy_rev(commit->id, line);
4819                 commit->refs = get_refs(commit->id);
4820                 graph->commit = commit;
4821                 add_line_data(view, commit, LINE_MAIN_COMMIT);
4823                 while ((line = strchr(line, ' '))) {
4824                         line++;
4825                         push_rev_graph(graph->parents, line);
4826                         commit->has_parents = TRUE;
4827                 }
4828                 return TRUE;
4829         }
4831         if (!view->lines)
4832                 return TRUE;
4833         commit = view->line[view->lines - 1].data;
4835         switch (type) {
4836         case LINE_PARENT:
4837                 if (commit->has_parents)
4838                         break;
4839                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
4840                 break;
4842         case LINE_AUTHOR:
4843         {
4844                 /* Parse author lines where the name may be empty:
4845                  *      author  <email@address.tld> 1138474660 +0100
4846                  */
4847                 char *ident = line + STRING_SIZE("author ");
4848                 char *nameend = strchr(ident, '<');
4849                 char *emailend = strchr(ident, '>');
4851                 if (!nameend || !emailend)
4852                         break;
4854                 update_rev_graph(graph);
4855                 graph = graph->next;
4857                 *nameend = *emailend = 0;
4858                 ident = chomp_string(ident);
4859                 if (!*ident) {
4860                         ident = chomp_string(nameend + 1);
4861                         if (!*ident)
4862                                 ident = "Unknown";
4863                 }
4865                 string_ncopy(commit->author, ident, strlen(ident));
4867                 /* Parse epoch and timezone */
4868                 if (emailend[1] == ' ') {
4869                         char *secs = emailend + 2;
4870                         char *zone = strchr(secs, ' ');
4871                         time_t time = (time_t) atol(secs);
4873                         if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
4874                                 long tz;
4876                                 zone++;
4877                                 tz  = ('0' - zone[1]) * 60 * 60 * 10;
4878                                 tz += ('0' - zone[2]) * 60 * 60;
4879                                 tz += ('0' - zone[3]) * 60;
4880                                 tz += ('0' - zone[4]) * 60;
4882                                 if (zone[0] == '-')
4883                                         tz = -tz;
4885                                 time -= tz;
4886                         }
4888                         gmtime_r(&time, &commit->time);
4889                 }
4890                 break;
4891         }
4892         default:
4893                 /* Fill in the commit title if it has not already been set. */
4894                 if (commit->title[0])
4895                         break;
4897                 /* Require titles to start with a non-space character at the
4898                  * offset used by git log. */
4899                 if (strncmp(line, "    ", 4))
4900                         break;
4901                 line += 4;
4902                 /* Well, if the title starts with a whitespace character,
4903                  * try to be forgiving.  Otherwise we end up with no title. */
4904                 while (isspace(*line))
4905                         line++;
4906                 if (*line == '\0')
4907                         break;
4908                 /* FIXME: More graceful handling of titles; append "..." to
4909                  * shortened titles, etc. */
4911                 string_ncopy(commit->title, line, strlen(line));
4912         }
4914         return TRUE;
4917 static enum request
4918 main_request(struct view *view, enum request request, struct line *line)
4920         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4922         if (request == REQ_ENTER)
4923                 open_view(view, REQ_VIEW_DIFF, flags);
4924         else
4925                 return request;
4927         return REQ_NONE;
4930 static bool
4931 main_grep(struct view *view, struct line *line)
4933         struct commit *commit = line->data;
4934         enum { S_TITLE, S_AUTHOR, S_DATE, S_END } state;
4935         char buf[DATE_COLS + 1];
4936         regmatch_t pmatch;
4938         for (state = S_TITLE; state < S_END; state++) {
4939                 char *text;
4941                 switch (state) {
4942                 case S_TITLE:   text = commit->title;   break;
4943                 case S_AUTHOR:  text = commit->author;  break;
4944                 case S_DATE:
4945                         if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
4946                                 continue;
4947                         text = buf;
4948                         break;
4950                 default:
4951                         return FALSE;
4952                 }
4954                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4955                         return TRUE;
4956         }
4958         return FALSE;
4961 static void
4962 main_select(struct view *view, struct line *line)
4964         struct commit *commit = line->data;
4966         string_copy_rev(view->ref, commit->id);
4967         string_copy_rev(ref_commit, view->ref);
4970 static struct view_ops main_ops = {
4971         "commit",
4972         NULL,
4973         main_read,
4974         main_draw,
4975         main_request,
4976         main_grep,
4977         main_select,
4978 };
4981 /*
4982  * Unicode / UTF-8 handling
4983  *
4984  * NOTE: Much of the following code for dealing with unicode is derived from
4985  * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
4986  * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
4987  */
4989 /* I've (over)annotated a lot of code snippets because I am not entirely
4990  * confident that the approach taken by this small UTF-8 interface is correct.
4991  * --jonas */
4993 static inline int
4994 unicode_width(unsigned long c)
4996         if (c >= 0x1100 &&
4997            (c <= 0x115f                         /* Hangul Jamo */
4998             || c == 0x2329
4999             || c == 0x232a
5000             || (c >= 0x2e80  && c <= 0xa4cf && c != 0x303f)
5001                                                 /* CJK ... Yi */
5002             || (c >= 0xac00  && c <= 0xd7a3)    /* Hangul Syllables */
5003             || (c >= 0xf900  && c <= 0xfaff)    /* CJK Compatibility Ideographs */
5004             || (c >= 0xfe30  && c <= 0xfe6f)    /* CJK Compatibility Forms */
5005             || (c >= 0xff00  && c <= 0xff60)    /* Fullwidth Forms */
5006             || (c >= 0xffe0  && c <= 0xffe6)
5007             || (c >= 0x20000 && c <= 0x2fffd)
5008             || (c >= 0x30000 && c <= 0x3fffd)))
5009                 return 2;
5011         if (c == '\t')
5012                 return opt_tab_size;
5014         return 1;
5017 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
5018  * Illegal bytes are set one. */
5019 static const unsigned char utf8_bytes[256] = {
5020         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,
5021         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,
5022         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,
5023         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,
5024         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,
5025         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,
5026         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,
5027         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,
5028 };
5030 /* Decode UTF-8 multi-byte representation into a unicode character. */
5031 static inline unsigned long
5032 utf8_to_unicode(const char *string, size_t length)
5034         unsigned long unicode;
5036         switch (length) {
5037         case 1:
5038                 unicode  =   string[0];
5039                 break;
5040         case 2:
5041                 unicode  =  (string[0] & 0x1f) << 6;
5042                 unicode +=  (string[1] & 0x3f);
5043                 break;
5044         case 3:
5045                 unicode  =  (string[0] & 0x0f) << 12;
5046                 unicode += ((string[1] & 0x3f) << 6);
5047                 unicode +=  (string[2] & 0x3f);
5048                 break;
5049         case 4:
5050                 unicode  =  (string[0] & 0x0f) << 18;
5051                 unicode += ((string[1] & 0x3f) << 12);
5052                 unicode += ((string[2] & 0x3f) << 6);
5053                 unicode +=  (string[3] & 0x3f);
5054                 break;
5055         case 5:
5056                 unicode  =  (string[0] & 0x0f) << 24;
5057                 unicode += ((string[1] & 0x3f) << 18);
5058                 unicode += ((string[2] & 0x3f) << 12);
5059                 unicode += ((string[3] & 0x3f) << 6);
5060                 unicode +=  (string[4] & 0x3f);
5061                 break;
5062         case 6:
5063                 unicode  =  (string[0] & 0x01) << 30;
5064                 unicode += ((string[1] & 0x3f) << 24);
5065                 unicode += ((string[2] & 0x3f) << 18);
5066                 unicode += ((string[3] & 0x3f) << 12);
5067                 unicode += ((string[4] & 0x3f) << 6);
5068                 unicode +=  (string[5] & 0x3f);
5069                 break;
5070         default:
5071                 die("Invalid unicode length");
5072         }
5074         /* Invalid characters could return the special 0xfffd value but NUL
5075          * should be just as good. */
5076         return unicode > 0xffff ? 0 : unicode;
5079 /* Calculates how much of string can be shown within the given maximum width
5080  * and sets trimmed parameter to non-zero value if all of string could not be
5081  * shown. If the reserve flag is TRUE, it will reserve at least one
5082  * trailing character, which can be useful when drawing a delimiter.
5083  *
5084  * Returns the number of bytes to output from string to satisfy max_width. */
5085 static size_t
5086 utf8_length(const char *string, size_t max_width, int *trimmed, bool reserve)
5088         const char *start = string;
5089         const char *end = strchr(string, '\0');
5090         unsigned char last_bytes = 0;
5091         size_t width = 0;
5093         *trimmed = 0;
5095         while (string < end) {
5096                 int c = *(unsigned char *) string;
5097                 unsigned char bytes = utf8_bytes[c];
5098                 size_t ucwidth;
5099                 unsigned long unicode;
5101                 if (string + bytes > end)
5102                         break;
5104                 /* Change representation to figure out whether
5105                  * it is a single- or double-width character. */
5107                 unicode = utf8_to_unicode(string, bytes);
5108                 /* FIXME: Graceful handling of invalid unicode character. */
5109                 if (!unicode)
5110                         break;
5112                 ucwidth = unicode_width(unicode);
5113                 width  += ucwidth;
5114                 if (width > max_width) {
5115                         *trimmed = 1;
5116                         if (reserve && width - ucwidth == max_width) {
5117                                 string -= last_bytes;
5118                         }
5119                         break;
5120                 }
5122                 string  += bytes;
5123                 last_bytes = bytes;
5124         }
5126         return string - start;
5130 /*
5131  * Status management
5132  */
5134 /* Whether or not the curses interface has been initialized. */
5135 static bool cursed = FALSE;
5137 /* The status window is used for polling keystrokes. */
5138 static WINDOW *status_win;
5140 static bool status_empty = TRUE;
5142 /* Update status and title window. */
5143 static void
5144 report(const char *msg, ...)
5146         struct view *view = display[current_view];
5148         if (input_mode)
5149                 return;
5151         if (!view) {
5152                 char buf[SIZEOF_STR];
5153                 va_list args;
5155                 va_start(args, msg);
5156                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
5157                         buf[sizeof(buf) - 1] = 0;
5158                         buf[sizeof(buf) - 2] = '.';
5159                         buf[sizeof(buf) - 3] = '.';
5160                         buf[sizeof(buf) - 4] = '.';
5161                 }
5162                 va_end(args);
5163                 die("%s", buf);
5164         }
5166         if (!status_empty || *msg) {
5167                 va_list args;
5169                 va_start(args, msg);
5171                 wmove(status_win, 0, 0);
5172                 if (*msg) {
5173                         vwprintw(status_win, msg, args);
5174                         status_empty = FALSE;
5175                 } else {
5176                         status_empty = TRUE;
5177                 }
5178                 wclrtoeol(status_win);
5179                 wrefresh(status_win);
5181                 va_end(args);
5182         }
5184         update_view_title(view);
5185         update_display_cursor(view);
5188 /* Controls when nodelay should be in effect when polling user input. */
5189 static void
5190 set_nonblocking_input(bool loading)
5192         static unsigned int loading_views;
5194         if ((loading == FALSE && loading_views-- == 1) ||
5195             (loading == TRUE  && loading_views++ == 0))
5196                 nodelay(status_win, loading);
5199 static void
5200 init_display(void)
5202         int x, y;
5204         /* Initialize the curses library */
5205         if (isatty(STDIN_FILENO)) {
5206                 cursed = !!initscr();
5207         } else {
5208                 /* Leave stdin and stdout alone when acting as a pager. */
5209                 FILE *io = fopen("/dev/tty", "r+");
5211                 if (!io)
5212                         die("Failed to open /dev/tty");
5213                 cursed = !!newterm(NULL, io, io);
5214         }
5216         if (!cursed)
5217                 die("Failed to initialize curses");
5219         nonl();         /* Tell curses not to do NL->CR/NL on output */
5220         cbreak();       /* Take input chars one at a time, no wait for \n */
5221         noecho();       /* Don't echo input */
5222         leaveok(stdscr, TRUE);
5224         if (has_colors())
5225                 init_colors();
5227         getmaxyx(stdscr, y, x);
5228         status_win = newwin(1, 0, y - 1, 0);
5229         if (!status_win)
5230                 die("Failed to create status window");
5232         /* Enable keyboard mapping */
5233         keypad(status_win, TRUE);
5234         wbkgdset(status_win, get_line_attr(LINE_STATUS));
5237 static char *
5238 read_prompt(const char *prompt)
5240         enum { READING, STOP, CANCEL } status = READING;
5241         static char buf[sizeof(opt_cmd) - STRING_SIZE("git \0")];
5242         int pos = 0;
5244         while (status == READING) {
5245                 struct view *view;
5246                 int i, key;
5248                 input_mode = TRUE;
5250                 foreach_view (view, i)
5251                         update_view(view);
5253                 input_mode = FALSE;
5255                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
5256                 wclrtoeol(status_win);
5258                 /* Refresh, accept single keystroke of input */
5259                 key = wgetch(status_win);
5260                 switch (key) {
5261                 case KEY_RETURN:
5262                 case KEY_ENTER:
5263                 case '\n':
5264                         status = pos ? STOP : CANCEL;
5265                         break;
5267                 case KEY_BACKSPACE:
5268                         if (pos > 0)
5269                                 pos--;
5270                         else
5271                                 status = CANCEL;
5272                         break;
5274                 case KEY_ESC:
5275                         status = CANCEL;
5276                         break;
5278                 case ERR:
5279                         break;
5281                 default:
5282                         if (pos >= sizeof(buf)) {
5283                                 report("Input string too long");
5284                                 return NULL;
5285                         }
5287                         if (isprint(key))
5288                                 buf[pos++] = (char) key;
5289                 }
5290         }
5292         /* Clear the status window */
5293         status_empty = FALSE;
5294         report("");
5296         if (status == CANCEL)
5297                 return NULL;
5299         buf[pos++] = 0;
5301         return buf;
5304 /*
5305  * Repository references
5306  */
5308 static struct ref *refs = NULL;
5309 static size_t refs_alloc = 0;
5310 static size_t refs_size = 0;
5312 /* Id <-> ref store */
5313 static struct ref ***id_refs = NULL;
5314 static size_t id_refs_alloc = 0;
5315 static size_t id_refs_size = 0;
5317 static struct ref **
5318 get_refs(char *id)
5320         struct ref ***tmp_id_refs;
5321         struct ref **ref_list = NULL;
5322         size_t ref_list_alloc = 0;
5323         size_t ref_list_size = 0;
5324         size_t i;
5326         for (i = 0; i < id_refs_size; i++)
5327                 if (!strcmp(id, id_refs[i][0]->id))
5328                         return id_refs[i];
5330         tmp_id_refs = realloc_items(id_refs, &id_refs_alloc, id_refs_size + 1,
5331                                     sizeof(*id_refs));
5332         if (!tmp_id_refs)
5333                 return NULL;
5335         id_refs = tmp_id_refs;
5337         for (i = 0; i < refs_size; i++) {
5338                 struct ref **tmp;
5340                 if (strcmp(id, refs[i].id))
5341                         continue;
5343                 tmp = realloc_items(ref_list, &ref_list_alloc,
5344                                     ref_list_size + 1, sizeof(*ref_list));
5345                 if (!tmp) {
5346                         if (ref_list)
5347                                 free(ref_list);
5348                         return NULL;
5349                 }
5351                 ref_list = tmp;
5352                 if (ref_list_size > 0)
5353                         ref_list[ref_list_size - 1]->next = 1;
5354                 ref_list[ref_list_size] = &refs[i];
5356                 /* XXX: The properties of the commit chains ensures that we can
5357                  * safely modify the shared ref. The repo references will
5358                  * always be similar for the same id. */
5359                 ref_list[ref_list_size]->next = 0;
5360                 ref_list_size++;
5361         }
5363         if (ref_list)
5364                 id_refs[id_refs_size++] = ref_list;
5366         return ref_list;
5369 static int
5370 read_ref(char *id, size_t idlen, char *name, size_t namelen)
5372         struct ref *ref;
5373         bool tag = FALSE;
5374         bool ltag = FALSE;
5375         bool remote = FALSE;
5376         bool check_replace = FALSE;
5378         if (!strncmp(name, "refs/tags/", STRING_SIZE("refs/tags/"))) {
5379                 if (!strcmp(name + namelen - 3, "^{}")) {
5380                         namelen -= 3;
5381                         name[namelen] = 0;
5382                         if (refs_size > 0 && refs[refs_size - 1].ltag == TRUE)
5383                                 check_replace = TRUE;
5384                 } else {
5385                         ltag = TRUE;
5386                 }
5388                 tag = TRUE;
5389                 namelen -= STRING_SIZE("refs/tags/");
5390                 name    += STRING_SIZE("refs/tags/");
5392         } else if (!strncmp(name, "refs/remotes/", STRING_SIZE("refs/remotes/"))) {
5393                 remote = TRUE;
5394                 namelen -= STRING_SIZE("refs/remotes/");
5395                 name    += STRING_SIZE("refs/remotes/");
5397         } else if (!strncmp(name, "refs/heads/", STRING_SIZE("refs/heads/"))) {
5398                 namelen -= STRING_SIZE("refs/heads/");
5399                 name    += STRING_SIZE("refs/heads/");
5401         } else if (!strcmp(name, "HEAD")) {
5402                 return OK;
5403         }
5405         if (check_replace && !strcmp(name, refs[refs_size - 1].name)) {
5406                 /* it's an annotated tag, replace the previous sha1 with the
5407                  * resolved commit id; relies on the fact git-ls-remote lists
5408                  * the commit id of an annotated tag right beofre the commit id
5409                  * it points to. */
5410                 refs[refs_size - 1].ltag = ltag;
5411                 string_copy_rev(refs[refs_size - 1].id, id);
5413                 return OK;
5414         }
5415         refs = realloc_items(refs, &refs_alloc, refs_size + 1, sizeof(*refs));
5416         if (!refs)
5417                 return ERR;
5419         ref = &refs[refs_size++];
5420         ref->name = malloc(namelen + 1);
5421         if (!ref->name)
5422                 return ERR;
5424         strncpy(ref->name, name, namelen);
5425         ref->name[namelen] = 0;
5426         ref->tag = tag;
5427         ref->ltag = ltag;
5428         ref->remote = remote;
5429         string_copy_rev(ref->id, id);
5431         return OK;
5434 static int
5435 load_refs(void)
5437         const char *cmd_env = getenv("TIG_LS_REMOTE");
5438         const char *cmd = cmd_env && *cmd_env ? cmd_env : TIG_LS_REMOTE;
5440         return read_properties(popen(cmd, "r"), "\t", read_ref);
5443 static int
5444 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
5446         if (!strcmp(name, "i18n.commitencoding"))
5447                 string_ncopy(opt_encoding, value, valuelen);
5449         if (!strcmp(name, "core.editor"))
5450                 string_ncopy(opt_editor, value, valuelen);
5452         return OK;
5455 static int
5456 load_repo_config(void)
5458         return read_properties(popen(GIT_CONFIG " --list", "r"),
5459                                "=", read_repo_config_option);
5462 static int
5463 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
5465         if (!opt_git_dir[0]) {
5466                 string_ncopy(opt_git_dir, name, namelen);
5468         } else if (opt_is_inside_work_tree == -1) {
5469                 /* This can be 3 different values depending on the
5470                  * version of git being used. If git-rev-parse does not
5471                  * understand --is-inside-work-tree it will simply echo
5472                  * the option else either "true" or "false" is printed.
5473                  * Default to true for the unknown case. */
5474                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
5476         } else {
5477                 string_ncopy(opt_cdup, name, namelen);
5478         }
5480         return OK;
5483 /* XXX: The line outputted by "--show-cdup" can be empty so the option
5484  * must be the last one! */
5485 static int
5486 load_repo_info(void)
5488         return read_properties(popen("git rev-parse --git-dir --is-inside-work-tree --show-cdup 2>/dev/null", "r"),
5489                                "=", read_repo_info);
5492 static int
5493 read_properties(FILE *pipe, const char *separators,
5494                 int (*read_property)(char *, size_t, char *, size_t))
5496         char buffer[BUFSIZ];
5497         char *name;
5498         int state = OK;
5500         if (!pipe)
5501                 return ERR;
5503         while (state == OK && (name = fgets(buffer, sizeof(buffer), pipe))) {
5504                 char *value;
5505                 size_t namelen;
5506                 size_t valuelen;
5508                 name = chomp_string(name);
5509                 namelen = strcspn(name, separators);
5511                 if (name[namelen]) {
5512                         name[namelen] = 0;
5513                         value = chomp_string(name + namelen + 1);
5514                         valuelen = strlen(value);
5516                 } else {
5517                         value = "";
5518                         valuelen = 0;
5519                 }
5521                 state = read_property(name, namelen, value, valuelen);
5522         }
5524         if (state != ERR && ferror(pipe))
5525                 state = ERR;
5527         pclose(pipe);
5529         return state;
5533 /*
5534  * Main
5535  */
5537 static void __NORETURN
5538 quit(int sig)
5540         /* XXX: Restore tty modes and let the OS cleanup the rest! */
5541         if (cursed)
5542                 endwin();
5543         exit(0);
5546 static void __NORETURN
5547 die(const char *err, ...)
5549         va_list args;
5551         endwin();
5553         va_start(args, err);
5554         fputs("tig: ", stderr);
5555         vfprintf(stderr, err, args);
5556         fputs("\n", stderr);
5557         va_end(args);
5559         exit(1);
5562 static void
5563 warn(const char *msg, ...)
5565         va_list args;
5567         va_start(args, msg);
5568         fputs("tig warning: ", stderr);
5569         vfprintf(stderr, msg, args);
5570         fputs("\n", stderr);
5571         va_end(args);
5574 int
5575 main(int argc, char *argv[])
5577         struct view *view;
5578         enum request request;
5579         size_t i;
5581         signal(SIGINT, quit);
5583         if (setlocale(LC_ALL, "")) {
5584                 char *codeset = nl_langinfo(CODESET);
5586                 string_ncopy(opt_codeset, codeset, strlen(codeset));
5587         }
5589         if (load_repo_info() == ERR)
5590                 die("Failed to load repo info.");
5592         if (load_options() == ERR)
5593                 die("Failed to load user config.");
5595         /* Load the repo config file so options can be overwritten from
5596          * the command line. */
5597         if (load_repo_config() == ERR)
5598                 die("Failed to load repo config.");
5600         if (!parse_options(argc, argv))
5601                 return 0;
5603         /* Require a git repository unless when running in pager mode. */
5604         if (!opt_git_dir[0])
5605                 die("Not a git repository");
5607         if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
5608                 opt_utf8 = FALSE;
5610         if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
5611                 opt_iconv = iconv_open(opt_codeset, opt_encoding);
5612                 if (opt_iconv == ICONV_NONE)
5613                         die("Failed to initialize character set conversion");
5614         }
5616         if (load_refs() == ERR)
5617                 die("Failed to load refs.");
5619         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
5620                 view->cmd_env = getenv(view->cmd_env);
5622         request = opt_request;
5624         init_display();
5626         while (view_driver(display[current_view], request)) {
5627                 int key;
5628                 int i;
5630                 foreach_view (view, i)
5631                         update_view(view);
5633                 /* Refresh, accept single keystroke of input */
5634                 key = wgetch(status_win);
5636                 /* wgetch() with nodelay() enabled returns ERR when there's no
5637                  * input. */
5638                 if (key == ERR) {
5639                         request = REQ_NONE;
5640                         continue;
5641                 }
5643                 request = get_keybinding(display[current_view]->keymap, key);
5645                 /* Some low-level request handling. This keeps access to
5646                  * status_win restricted. */
5647                 switch (request) {
5648                 case REQ_PROMPT:
5649                 {
5650                         char *cmd = read_prompt(":");
5652                         if (cmd && string_format(opt_cmd, "git %s", cmd)) {
5653                                 if (strncmp(cmd, "show", 4) && isspace(cmd[4])) {
5654                                         opt_request = REQ_VIEW_DIFF;
5655                                 } else {
5656                                         opt_request = REQ_VIEW_PAGER;
5657                                 }
5658                                 break;
5659                         }
5661                         request = REQ_NONE;
5662                         break;
5663                 }
5664                 case REQ_SEARCH:
5665                 case REQ_SEARCH_BACK:
5666                 {
5667                         const char *prompt = request == REQ_SEARCH
5668                                            ? "/" : "?";
5669                         char *search = read_prompt(prompt);
5671                         if (search)
5672                                 string_ncopy(opt_search, search, strlen(search));
5673                         else
5674                                 request = REQ_NONE;
5675                         break;
5676                 }
5677                 case REQ_SCREEN_RESIZE:
5678                 {
5679                         int height, width;
5681                         getmaxyx(stdscr, height, width);
5683                         /* Resize the status view and let the view driver take
5684                          * care of resizing the displayed views. */
5685                         wresize(status_win, 1, width);
5686                         mvwin(status_win, height - 1, 0);
5687                         wrefresh(status_win);
5688                         break;
5689                 }
5690                 default:
5691                         break;
5692                 }
5693         }
5695         quit(0);
5697         return 0;