Code

Rename "main-delim" color to the more generic "delimiter"
[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         unsigned int head:1;    /* Is it the current HEAD? */
149 };
151 static struct ref **get_refs(char *id);
153 struct int_map {
154         const char *name;
155         int namelen;
156         int value;
157 };
159 static int
160 set_from_int_map(struct int_map *map, size_t map_size,
161                  int *value, const char *name, int namelen)
164         int i;
166         for (i = 0; i < map_size; i++)
167                 if (namelen == map[i].namelen &&
168                     !strncasecmp(name, map[i].name, namelen)) {
169                         *value = map[i].value;
170                         return OK;
171                 }
173         return ERR;
177 /*
178  * String helpers
179  */
181 static inline void
182 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
184         if (srclen > dstlen - 1)
185                 srclen = dstlen - 1;
187         strncpy(dst, src, srclen);
188         dst[srclen] = 0;
191 /* Shorthands for safely copying into a fixed buffer. */
193 #define string_copy(dst, src) \
194         string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
196 #define string_ncopy(dst, src, srclen) \
197         string_ncopy_do(dst, sizeof(dst), src, srclen)
199 #define string_copy_rev(dst, src) \
200         string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
202 #define string_add(dst, from, src) \
203         string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
205 static char *
206 chomp_string(char *name)
208         int namelen;
210         while (isspace(*name))
211                 name++;
213         namelen = strlen(name) - 1;
214         while (namelen > 0 && isspace(name[namelen]))
215                 name[namelen--] = 0;
217         return name;
220 static bool
221 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
223         va_list args;
224         size_t pos = bufpos ? *bufpos : 0;
226         va_start(args, fmt);
227         pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
228         va_end(args);
230         if (bufpos)
231                 *bufpos = pos;
233         return pos >= bufsize ? FALSE : TRUE;
236 #define string_format(buf, fmt, args...) \
237         string_nformat(buf, sizeof(buf), NULL, fmt, args)
239 #define string_format_from(buf, from, fmt, args...) \
240         string_nformat(buf, sizeof(buf), from, fmt, args)
242 static int
243 string_enum_compare(const char *str1, const char *str2, int len)
245         size_t i;
247 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
249         /* Diff-Header == DIFF_HEADER */
250         for (i = 0; i < len; i++) {
251                 if (toupper(str1[i]) == toupper(str2[i]))
252                         continue;
254                 if (string_enum_sep(str1[i]) &&
255                     string_enum_sep(str2[i]))
256                         continue;
258                 return str1[i] - str2[i];
259         }
261         return 0;
264 /* Shell quoting
265  *
266  * NOTE: The following is a slightly modified copy of the git project's shell
267  * quoting routines found in the quote.c file.
268  *
269  * Help to copy the thing properly quoted for the shell safety.  any single
270  * quote is replaced with '\'', any exclamation point is replaced with '\!',
271  * and the whole thing is enclosed in a
272  *
273  * E.g.
274  *  original     sq_quote     result
275  *  name     ==> name      ==> 'name'
276  *  a b      ==> a b       ==> 'a b'
277  *  a'b      ==> a'\''b    ==> 'a'\''b'
278  *  a!b      ==> a'\!'b    ==> 'a'\!'b'
279  */
281 static size_t
282 sq_quote(char buf[SIZEOF_STR], size_t bufsize, const char *src)
284         char c;
286 #define BUFPUT(x) do { if (bufsize < SIZEOF_STR) buf[bufsize++] = (x); } while (0)
288         BUFPUT('\'');
289         while ((c = *src++)) {
290                 if (c == '\'' || c == '!') {
291                         BUFPUT('\'');
292                         BUFPUT('\\');
293                         BUFPUT(c);
294                         BUFPUT('\'');
295                 } else {
296                         BUFPUT(c);
297                 }
298         }
299         BUFPUT('\'');
301         if (bufsize < SIZEOF_STR)
302                 buf[bufsize] = 0;
304         return bufsize;
308 /*
309  * User requests
310  */
312 #define REQ_INFO \
313         /* XXX: Keep the view request first and in sync with views[]. */ \
314         REQ_GROUP("View switching") \
315         REQ_(VIEW_MAIN,         "Show main view"), \
316         REQ_(VIEW_DIFF,         "Show diff view"), \
317         REQ_(VIEW_LOG,          "Show log view"), \
318         REQ_(VIEW_TREE,         "Show tree view"), \
319         REQ_(VIEW_BLOB,         "Show blob view"), \
320         REQ_(VIEW_BLAME,        "Show blame view"), \
321         REQ_(VIEW_HELP,         "Show help page"), \
322         REQ_(VIEW_PAGER,        "Show pager view"), \
323         REQ_(VIEW_STATUS,       "Show status view"), \
324         REQ_(VIEW_STAGE,        "Show stage view"), \
325         \
326         REQ_GROUP("View manipulation") \
327         REQ_(ENTER,             "Enter current line and scroll"), \
328         REQ_(NEXT,              "Move to next"), \
329         REQ_(PREVIOUS,          "Move to previous"), \
330         REQ_(VIEW_NEXT,         "Move focus to next view"), \
331         REQ_(REFRESH,           "Reload and refresh"), \
332         REQ_(VIEW_CLOSE,        "Close the current view"), \
333         REQ_(QUIT,              "Close all views and quit"), \
334         \
335         REQ_GROUP("Cursor navigation") \
336         REQ_(MOVE_UP,           "Move cursor one line up"), \
337         REQ_(MOVE_DOWN,         "Move cursor one line down"), \
338         REQ_(MOVE_PAGE_DOWN,    "Move cursor one page down"), \
339         REQ_(MOVE_PAGE_UP,      "Move cursor one page up"), \
340         REQ_(MOVE_FIRST_LINE,   "Move cursor to first line"), \
341         REQ_(MOVE_LAST_LINE,    "Move cursor to last line"), \
342         \
343         REQ_GROUP("Scrolling") \
344         REQ_(SCROLL_LINE_UP,    "Scroll one line up"), \
345         REQ_(SCROLL_LINE_DOWN,  "Scroll one line down"), \
346         REQ_(SCROLL_PAGE_UP,    "Scroll one page up"), \
347         REQ_(SCROLL_PAGE_DOWN,  "Scroll one page down"), \
348         \
349         REQ_GROUP("Searching") \
350         REQ_(SEARCH,            "Search the view"), \
351         REQ_(SEARCH_BACK,       "Search backwards in the view"), \
352         REQ_(FIND_NEXT,         "Find next search match"), \
353         REQ_(FIND_PREV,         "Find previous search match"), \
354         \
355         REQ_GROUP("Misc") \
356         REQ_(PROMPT,            "Bring up the prompt"), \
357         REQ_(SCREEN_REDRAW,     "Redraw the screen"), \
358         REQ_(SCREEN_RESIZE,     "Resize the screen"), \
359         REQ_(SHOW_VERSION,      "Show version information"), \
360         REQ_(STOP_LOADING,      "Stop all loading views"), \
361         REQ_(TOGGLE_LINENO,     "Toggle line numbers"), \
362         REQ_(TOGGLE_DATE,       "Toggle date display"), \
363         REQ_(TOGGLE_AUTHOR,     "Toggle author display"), \
364         REQ_(TOGGLE_REV_GRAPH,  "Toggle revision graph visualization"), \
365         REQ_(TOGGLE_REFS,       "Toggle reference display (tags/branches)"), \
366         REQ_(STATUS_UPDATE,     "Update file status"), \
367         REQ_(STATUS_MERGE,      "Merge file using external tool"), \
368         REQ_(TREE_PARENT,       "Switch to parent directory in tree view"), \
369         REQ_(EDIT,              "Open in editor"), \
370         REQ_(NONE,              "Do nothing")
373 /* User action requests. */
374 enum request {
375 #define REQ_GROUP(help)
376 #define REQ_(req, help) REQ_##req
378         /* Offset all requests to avoid conflicts with ncurses getch values. */
379         REQ_OFFSET = KEY_MAX + 1,
380         REQ_INFO
382 #undef  REQ_GROUP
383 #undef  REQ_
384 };
386 struct request_info {
387         enum request request;
388         char *name;
389         int namelen;
390         char *help;
391 };
393 static struct request_info req_info[] = {
394 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
395 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
396         REQ_INFO
397 #undef  REQ_GROUP
398 #undef  REQ_
399 };
401 static enum request
402 get_request(const char *name)
404         int namelen = strlen(name);
405         int i;
407         for (i = 0; i < ARRAY_SIZE(req_info); i++)
408                 if (req_info[i].namelen == namelen &&
409                     !string_enum_compare(req_info[i].name, name, namelen))
410                         return req_info[i].request;
412         return REQ_NONE;
416 /*
417  * Options
418  */
420 static const char usage[] =
421 "tig " TIG_VERSION " (" __DATE__ ")\n"
422 "\n"
423 "Usage: tig        [options] [revs] [--] [paths]\n"
424 "   or: tig show   [options] [revs] [--] [paths]\n"
425 "   or: tig blame  [rev] path\n"
426 "   or: tig status\n"
427 "   or: tig <      [git command output]\n"
428 "\n"
429 "Options:\n"
430 "  -v, --version   Show version and exit\n"
431 "  -h, --help      Show help message and exit";
433 /* Option and state variables. */
434 static bool opt_date                    = TRUE;
435 static bool opt_author                  = TRUE;
436 static bool opt_line_number             = FALSE;
437 static bool opt_rev_graph               = FALSE;
438 static bool opt_show_refs               = TRUE;
439 static int opt_num_interval             = NUMBER_INTERVAL;
440 static int opt_tab_size                 = TABSIZE;
441 static enum request opt_request         = REQ_VIEW_MAIN;
442 static char opt_cmd[SIZEOF_STR]         = "";
443 static char opt_path[SIZEOF_STR]        = "";
444 static char opt_file[SIZEOF_STR]        = "";
445 static char opt_ref[SIZEOF_REF]         = "";
446 static char opt_head[SIZEOF_REF]        = "";
447 static FILE *opt_pipe                   = NULL;
448 static char opt_encoding[20]            = "UTF-8";
449 static bool opt_utf8                    = TRUE;
450 static char opt_codeset[20]             = "UTF-8";
451 static iconv_t opt_iconv                = ICONV_NONE;
452 static char opt_search[SIZEOF_STR]      = "";
453 static char opt_cdup[SIZEOF_STR]        = "";
454 static char opt_git_dir[SIZEOF_STR]     = "";
455 static signed char opt_is_inside_work_tree      = -1; /* set to TRUE or FALSE */
456 static char opt_editor[SIZEOF_STR]      = "";
458 static bool
459 parse_options(int argc, char *argv[])
461         size_t buf_size;
462         char *subcommand;
463         bool seen_dashdash = FALSE;
464         int i;
466         if (argc <= 1)
467                 return TRUE;
469         subcommand = argv[1];
470         if (!strcmp(subcommand, "status") || !strcmp(subcommand, "-S")) {
471                 opt_request = REQ_VIEW_STATUS;
472                 if (!strcmp(subcommand, "-S"))
473                         warn("`-S' has been deprecated; use `tig status' instead");
474                 if (argc > 2)
475                         warn("ignoring arguments after `%s'", subcommand);
476                 return TRUE;
478         } else if (!strcmp(subcommand, "blame")) {
479                 opt_request = REQ_VIEW_BLAME;
480                 if (argc <= 2 || argc > 4)
481                         die("invalid number of options to blame\n\n%s", usage);
483                 i = 2;
484                 if (argc == 4) {
485                         string_ncopy(opt_ref, argv[i], strlen(argv[i]));
486                         i++;
487                 }
489                 string_ncopy(opt_file, argv[i], strlen(argv[i]));
490                 return TRUE;
492         } else if (!strcmp(subcommand, "show")) {
493                 opt_request = REQ_VIEW_DIFF;
495         } else if (!strcmp(subcommand, "log") || !strcmp(subcommand, "diff")) {
496                 opt_request = subcommand[0] == 'l'
497                             ? REQ_VIEW_LOG : REQ_VIEW_DIFF;
498                 warn("`tig %s' has been deprecated", subcommand);
500         } else {
501                 subcommand = NULL;
502         }
504         if (!subcommand)
505                 /* XXX: This is vulnerable to the user overriding
506                  * options required for the main view parser. */
507                 string_copy(opt_cmd, "git log --no-color --pretty=raw --boundary --parents");
508         else
509                 string_format(opt_cmd, "git %s", subcommand);
511         buf_size = strlen(opt_cmd);
513         for (i = 1 + !!subcommand; i < argc; i++) {
514                 char *opt = argv[i];
516                 if (seen_dashdash || !strcmp(opt, "--")) {
517                         seen_dashdash = TRUE;
519                 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
520                         printf("tig version %s\n", TIG_VERSION);
521                         return FALSE;
523                 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
524                         printf(usage);
525                         return FALSE;
526                 }
528                 opt_cmd[buf_size++] = ' ';
529                 buf_size = sq_quote(opt_cmd, buf_size, opt);
530                 if (buf_size >= sizeof(opt_cmd))
531                         die("command too long");
532         }
534         if (!isatty(STDIN_FILENO)) {
535                 opt_request = REQ_VIEW_PAGER;
536                 opt_pipe = stdin;
537                 buf_size = 0;
538         }
540         opt_cmd[buf_size] = 0;
542         return TRUE;
546 /*
547  * Line-oriented content detection.
548  */
550 #define LINE_INFO \
551 LINE(DIFF_HEADER,  "diff --git ",       COLOR_YELLOW,   COLOR_DEFAULT,  0), \
552 LINE(DIFF_CHUNK,   "@@",                COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
553 LINE(DIFF_ADD,     "+",                 COLOR_GREEN,    COLOR_DEFAULT,  0), \
554 LINE(DIFF_DEL,     "-",                 COLOR_RED,      COLOR_DEFAULT,  0), \
555 LINE(DIFF_INDEX,        "index ",         COLOR_BLUE,   COLOR_DEFAULT,  0), \
556 LINE(DIFF_OLDMODE,      "old file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
557 LINE(DIFF_NEWMODE,      "new file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
558 LINE(DIFF_COPY_FROM,    "copy from",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
559 LINE(DIFF_COPY_TO,      "copy to",        COLOR_YELLOW, COLOR_DEFAULT,  0), \
560 LINE(DIFF_RENAME_FROM,  "rename from",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
561 LINE(DIFF_RENAME_TO,    "rename to",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
562 LINE(DIFF_SIMILARITY,   "similarity ",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
563 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
564 LINE(DIFF_TREE,         "diff-tree ",     COLOR_BLUE,   COLOR_DEFAULT,  0), \
565 LINE(PP_AUTHOR,    "Author: ",          COLOR_CYAN,     COLOR_DEFAULT,  0), \
566 LINE(PP_COMMIT,    "Commit: ",          COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
567 LINE(PP_MERGE,     "Merge: ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
568 LINE(PP_DATE,      "Date:   ",          COLOR_YELLOW,   COLOR_DEFAULT,  0), \
569 LINE(PP_ADATE,     "AuthorDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
570 LINE(PP_CDATE,     "CommitDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
571 LINE(PP_REFS,      "Refs: ",            COLOR_RED,      COLOR_DEFAULT,  0), \
572 LINE(COMMIT,       "commit ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
573 LINE(PARENT,       "parent ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
574 LINE(TREE,         "tree ",             COLOR_BLUE,     COLOR_DEFAULT,  0), \
575 LINE(AUTHOR,       "author ",           COLOR_CYAN,     COLOR_DEFAULT,  0), \
576 LINE(COMMITTER,    "committer ",        COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
577 LINE(SIGNOFF,      "    Signed-off-by", COLOR_YELLOW,   COLOR_DEFAULT,  0), \
578 LINE(ACKED,        "    Acked-by",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
579 LINE(DEFAULT,      "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
580 LINE(CURSOR,       "",                  COLOR_WHITE,    COLOR_GREEN,    A_BOLD), \
581 LINE(STATUS,       "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
582 LINE(DELIMITER,    "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
583 LINE(TITLE_BLUR,   "",                  COLOR_WHITE,    COLOR_BLUE,     0), \
584 LINE(TITLE_FOCUS,  "",                  COLOR_WHITE,    COLOR_BLUE,     A_BOLD), \
585 LINE(MAIN_DATE,    "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
586 LINE(MAIN_AUTHOR,  "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
587 LINE(MAIN_COMMIT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
588 LINE(MAIN_TAG,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
589 LINE(MAIN_LOCAL_TAG,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
590 LINE(MAIN_REMOTE,  "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_BOLD), \
591 LINE(MAIN_REF,     "",                  COLOR_CYAN,     COLOR_DEFAULT,  A_BOLD), \
592 LINE(MAIN_HEAD,    "",                  COLOR_RED,      COLOR_DEFAULT,  A_BOLD), \
593 LINE(MAIN_REVGRAPH,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
594 LINE(TREE_DIR,     "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
595 LINE(TREE_FILE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
596 LINE(STAT_HEAD,    "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
597 LINE(STAT_SECTION, "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
598 LINE(STAT_NONE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
599 LINE(STAT_STAGED,  "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
600 LINE(STAT_UNSTAGED,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
601 LINE(STAT_UNTRACKED,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
602 LINE(BLAME_DATE,    "",                 COLOR_BLUE,     COLOR_DEFAULT,  0), \
603 LINE(BLAME_AUTHOR,  "",                 COLOR_GREEN,    COLOR_DEFAULT,  0), \
604 LINE(BLAME_COMMIT, "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
605 LINE(BLAME_ID,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
606 LINE(BLAME_LINENO, "",                  COLOR_CYAN,     COLOR_DEFAULT,  0)
608 enum line_type {
609 #define LINE(type, line, fg, bg, attr) \
610         LINE_##type
611         LINE_INFO
612 #undef  LINE
613 };
615 struct line_info {
616         const char *name;       /* Option name. */
617         int namelen;            /* Size of option name. */
618         const char *line;       /* The start of line to match. */
619         int linelen;            /* Size of string to match. */
620         int fg, bg, attr;       /* Color and text attributes for the lines. */
621 };
623 static struct line_info line_info[] = {
624 #define LINE(type, line, fg, bg, attr) \
625         { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
626         LINE_INFO
627 #undef  LINE
628 };
630 static enum line_type
631 get_line_type(char *line)
633         int linelen = strlen(line);
634         enum line_type type;
636         for (type = 0; type < ARRAY_SIZE(line_info); type++)
637                 /* Case insensitive search matches Signed-off-by lines better. */
638                 if (linelen >= line_info[type].linelen &&
639                     !strncasecmp(line_info[type].line, line, line_info[type].linelen))
640                         return type;
642         return LINE_DEFAULT;
645 static inline int
646 get_line_attr(enum line_type type)
648         assert(type < ARRAY_SIZE(line_info));
649         return COLOR_PAIR(type) | line_info[type].attr;
652 static struct line_info *
653 get_line_info(char *name)
655         size_t namelen = strlen(name);
656         enum line_type type;
658         for (type = 0; type < ARRAY_SIZE(line_info); type++)
659                 if (namelen == line_info[type].namelen &&
660                     !string_enum_compare(line_info[type].name, name, namelen))
661                         return &line_info[type];
663         return NULL;
666 static void
667 init_colors(void)
669         int default_bg = line_info[LINE_DEFAULT].bg;
670         int default_fg = line_info[LINE_DEFAULT].fg;
671         enum line_type type;
673         start_color();
675         if (assume_default_colors(default_fg, default_bg) == ERR) {
676                 default_bg = COLOR_BLACK;
677                 default_fg = COLOR_WHITE;
678         }
680         for (type = 0; type < ARRAY_SIZE(line_info); type++) {
681                 struct line_info *info = &line_info[type];
682                 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
683                 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
685                 init_pair(type, fg, bg);
686         }
689 struct line {
690         enum line_type type;
692         /* State flags */
693         unsigned int selected:1;
694         unsigned int dirty:1;
696         void *data;             /* User data */
697 };
700 /*
701  * Keys
702  */
704 struct keybinding {
705         int alias;
706         enum request request;
707         struct keybinding *next;
708 };
710 static struct keybinding default_keybindings[] = {
711         /* View switching */
712         { 'm',          REQ_VIEW_MAIN },
713         { 'd',          REQ_VIEW_DIFF },
714         { 'l',          REQ_VIEW_LOG },
715         { 't',          REQ_VIEW_TREE },
716         { 'f',          REQ_VIEW_BLOB },
717         { 'B',          REQ_VIEW_BLAME },
718         { 'p',          REQ_VIEW_PAGER },
719         { 'h',          REQ_VIEW_HELP },
720         { 'S',          REQ_VIEW_STATUS },
721         { 'c',          REQ_VIEW_STAGE },
723         /* View manipulation */
724         { 'q',          REQ_VIEW_CLOSE },
725         { KEY_TAB,      REQ_VIEW_NEXT },
726         { KEY_RETURN,   REQ_ENTER },
727         { KEY_UP,       REQ_PREVIOUS },
728         { KEY_DOWN,     REQ_NEXT },
729         { 'R',          REQ_REFRESH },
731         /* Cursor navigation */
732         { 'k',          REQ_MOVE_UP },
733         { 'j',          REQ_MOVE_DOWN },
734         { KEY_HOME,     REQ_MOVE_FIRST_LINE },
735         { KEY_END,      REQ_MOVE_LAST_LINE },
736         { KEY_NPAGE,    REQ_MOVE_PAGE_DOWN },
737         { ' ',          REQ_MOVE_PAGE_DOWN },
738         { KEY_PPAGE,    REQ_MOVE_PAGE_UP },
739         { 'b',          REQ_MOVE_PAGE_UP },
740         { '-',          REQ_MOVE_PAGE_UP },
742         /* Scrolling */
743         { KEY_IC,       REQ_SCROLL_LINE_UP },
744         { KEY_DC,       REQ_SCROLL_LINE_DOWN },
745         { 'w',          REQ_SCROLL_PAGE_UP },
746         { 's',          REQ_SCROLL_PAGE_DOWN },
748         /* Searching */
749         { '/',          REQ_SEARCH },
750         { '?',          REQ_SEARCH_BACK },
751         { 'n',          REQ_FIND_NEXT },
752         { 'N',          REQ_FIND_PREV },
754         /* Misc */
755         { 'Q',          REQ_QUIT },
756         { 'z',          REQ_STOP_LOADING },
757         { 'v',          REQ_SHOW_VERSION },
758         { 'r',          REQ_SCREEN_REDRAW },
759         { '.',          REQ_TOGGLE_LINENO },
760         { 'D',          REQ_TOGGLE_DATE },
761         { 'A',          REQ_TOGGLE_AUTHOR },
762         { 'g',          REQ_TOGGLE_REV_GRAPH },
763         { 'F',          REQ_TOGGLE_REFS },
764         { ':',          REQ_PROMPT },
765         { 'u',          REQ_STATUS_UPDATE },
766         { 'M',          REQ_STATUS_MERGE },
767         { ',',          REQ_TREE_PARENT },
768         { 'e',          REQ_EDIT },
770         /* Using the ncurses SIGWINCH handler. */
771         { KEY_RESIZE,   REQ_SCREEN_RESIZE },
772 };
774 #define KEYMAP_INFO \
775         KEYMAP_(GENERIC), \
776         KEYMAP_(MAIN), \
777         KEYMAP_(DIFF), \
778         KEYMAP_(LOG), \
779         KEYMAP_(TREE), \
780         KEYMAP_(BLOB), \
781         KEYMAP_(BLAME), \
782         KEYMAP_(PAGER), \
783         KEYMAP_(HELP), \
784         KEYMAP_(STATUS), \
785         KEYMAP_(STAGE)
787 enum keymap {
788 #define KEYMAP_(name) KEYMAP_##name
789         KEYMAP_INFO
790 #undef  KEYMAP_
791 };
793 static struct int_map keymap_table[] = {
794 #define KEYMAP_(name) { #name, STRING_SIZE(#name), KEYMAP_##name }
795         KEYMAP_INFO
796 #undef  KEYMAP_
797 };
799 #define set_keymap(map, name) \
800         set_from_int_map(keymap_table, ARRAY_SIZE(keymap_table), map, name, strlen(name))
802 static struct keybinding *keybindings[ARRAY_SIZE(keymap_table)];
804 static void
805 add_keybinding(enum keymap keymap, enum request request, int key)
807         struct keybinding *keybinding;
809         keybinding = calloc(1, sizeof(*keybinding));
810         if (!keybinding)
811                 die("Failed to allocate keybinding");
813         keybinding->alias = key;
814         keybinding->request = request;
815         keybinding->next = keybindings[keymap];
816         keybindings[keymap] = keybinding;
819 /* Looks for a key binding first in the given map, then in the generic map, and
820  * lastly in the default keybindings. */
821 static enum request
822 get_keybinding(enum keymap keymap, int key)
824         struct keybinding *kbd;
825         int i;
827         for (kbd = keybindings[keymap]; kbd; kbd = kbd->next)
828                 if (kbd->alias == key)
829                         return kbd->request;
831         for (kbd = keybindings[KEYMAP_GENERIC]; kbd; kbd = kbd->next)
832                 if (kbd->alias == key)
833                         return kbd->request;
835         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
836                 if (default_keybindings[i].alias == key)
837                         return default_keybindings[i].request;
839         return (enum request) key;
843 struct key {
844         char *name;
845         int value;
846 };
848 static struct key key_table[] = {
849         { "Enter",      KEY_RETURN },
850         { "Space",      ' ' },
851         { "Backspace",  KEY_BACKSPACE },
852         { "Tab",        KEY_TAB },
853         { "Escape",     KEY_ESC },
854         { "Left",       KEY_LEFT },
855         { "Right",      KEY_RIGHT },
856         { "Up",         KEY_UP },
857         { "Down",       KEY_DOWN },
858         { "Insert",     KEY_IC },
859         { "Delete",     KEY_DC },
860         { "Hash",       '#' },
861         { "Home",       KEY_HOME },
862         { "End",        KEY_END },
863         { "PageUp",     KEY_PPAGE },
864         { "PageDown",   KEY_NPAGE },
865         { "F1",         KEY_F(1) },
866         { "F2",         KEY_F(2) },
867         { "F3",         KEY_F(3) },
868         { "F4",         KEY_F(4) },
869         { "F5",         KEY_F(5) },
870         { "F6",         KEY_F(6) },
871         { "F7",         KEY_F(7) },
872         { "F8",         KEY_F(8) },
873         { "F9",         KEY_F(9) },
874         { "F10",        KEY_F(10) },
875         { "F11",        KEY_F(11) },
876         { "F12",        KEY_F(12) },
877 };
879 static int
880 get_key_value(const char *name)
882         int i;
884         for (i = 0; i < ARRAY_SIZE(key_table); i++)
885                 if (!strcasecmp(key_table[i].name, name))
886                         return key_table[i].value;
888         if (strlen(name) == 1 && isprint(*name))
889                 return (int) *name;
891         return ERR;
894 static char *
895 get_key_name(int key_value)
897         static char key_char[] = "'X'";
898         char *seq = NULL;
899         int key;
901         for (key = 0; key < ARRAY_SIZE(key_table); key++)
902                 if (key_table[key].value == key_value)
903                         seq = key_table[key].name;
905         if (seq == NULL &&
906             key_value < 127 &&
907             isprint(key_value)) {
908                 key_char[1] = (char) key_value;
909                 seq = key_char;
910         }
912         return seq ? seq : "'?'";
915 static char *
916 get_key(enum request request)
918         static char buf[BUFSIZ];
919         size_t pos = 0;
920         char *sep = "";
921         int i;
923         buf[pos] = 0;
925         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
926                 struct keybinding *keybinding = &default_keybindings[i];
928                 if (keybinding->request != request)
929                         continue;
931                 if (!string_format_from(buf, &pos, "%s%s", sep,
932                                         get_key_name(keybinding->alias)))
933                         return "Too many keybindings!";
934                 sep = ", ";
935         }
937         return buf;
940 struct run_request {
941         enum keymap keymap;
942         int key;
943         char cmd[SIZEOF_STR];
944 };
946 static struct run_request *run_request;
947 static size_t run_requests;
949 static enum request
950 add_run_request(enum keymap keymap, int key, int argc, char **argv)
952         struct run_request *tmp;
953         struct run_request req = { keymap, key };
954         size_t bufpos;
956         for (bufpos = 0; argc > 0; argc--, argv++)
957                 if (!string_format_from(req.cmd, &bufpos, "%s ", *argv))
958                         return REQ_NONE;
960         req.cmd[bufpos - 1] = 0;
962         tmp = realloc(run_request, (run_requests + 1) * sizeof(*run_request));
963         if (!tmp)
964                 return REQ_NONE;
966         run_request = tmp;
967         run_request[run_requests++] = req;
969         return REQ_NONE + run_requests;
972 static struct run_request *
973 get_run_request(enum request request)
975         if (request <= REQ_NONE)
976                 return NULL;
977         return &run_request[request - REQ_NONE - 1];
980 static void
981 add_builtin_run_requests(void)
983         struct {
984                 enum keymap keymap;
985                 int key;
986                 char *argv[1];
987         } reqs[] = {
988                 { KEYMAP_MAIN,    'C', { "git cherry-pick %(commit)" } },
989                 { KEYMAP_GENERIC, 'G', { "git gc" } },
990         };
991         int i;
993         for (i = 0; i < ARRAY_SIZE(reqs); i++) {
994                 enum request req;
996                 req = add_run_request(reqs[i].keymap, reqs[i].key, 1, reqs[i].argv);
997                 if (req != REQ_NONE)
998                         add_keybinding(reqs[i].keymap, req, reqs[i].key);
999         }
1002 /*
1003  * User config file handling.
1004  */
1006 static struct int_map color_map[] = {
1007 #define COLOR_MAP(name) { #name, STRING_SIZE(#name), COLOR_##name }
1008         COLOR_MAP(DEFAULT),
1009         COLOR_MAP(BLACK),
1010         COLOR_MAP(BLUE),
1011         COLOR_MAP(CYAN),
1012         COLOR_MAP(GREEN),
1013         COLOR_MAP(MAGENTA),
1014         COLOR_MAP(RED),
1015         COLOR_MAP(WHITE),
1016         COLOR_MAP(YELLOW),
1017 };
1019 #define set_color(color, name) \
1020         set_from_int_map(color_map, ARRAY_SIZE(color_map), color, name, strlen(name))
1022 static struct int_map attr_map[] = {
1023 #define ATTR_MAP(name) { #name, STRING_SIZE(#name), A_##name }
1024         ATTR_MAP(NORMAL),
1025         ATTR_MAP(BLINK),
1026         ATTR_MAP(BOLD),
1027         ATTR_MAP(DIM),
1028         ATTR_MAP(REVERSE),
1029         ATTR_MAP(STANDOUT),
1030         ATTR_MAP(UNDERLINE),
1031 };
1033 #define set_attribute(attr, name) \
1034         set_from_int_map(attr_map, ARRAY_SIZE(attr_map), attr, name, strlen(name))
1036 static int   config_lineno;
1037 static bool  config_errors;
1038 static char *config_msg;
1040 /* Wants: object fgcolor bgcolor [attr] */
1041 static int
1042 option_color_command(int argc, char *argv[])
1044         struct line_info *info;
1046         if (argc != 3 && argc != 4) {
1047                 config_msg = "Wrong number of arguments given to color command";
1048                 return ERR;
1049         }
1051         info = get_line_info(argv[0]);
1052         if (!info) {
1053                 if (!string_enum_compare(argv[0], "main-delim", strlen("main-delim"))) {
1054                         info = get_line_info("delimiter");
1056                 } else {
1057                         config_msg = "Unknown color name";
1058                         return ERR;
1059                 }
1060         }
1062         if (set_color(&info->fg, argv[1]) == ERR ||
1063             set_color(&info->bg, argv[2]) == ERR) {
1064                 config_msg = "Unknown color";
1065                 return ERR;
1066         }
1068         if (argc == 4 && set_attribute(&info->attr, argv[3]) == ERR) {
1069                 config_msg = "Unknown attribute";
1070                 return ERR;
1071         }
1073         return OK;
1076 static bool parse_bool(const char *s)
1078         return (!strcmp(s, "1") || !strcmp(s, "true") ||
1079                 !strcmp(s, "yes")) ? TRUE : FALSE;
1082 /* Wants: name = value */
1083 static int
1084 option_set_command(int argc, char *argv[])
1086         if (argc != 3) {
1087                 config_msg = "Wrong number of arguments given to set command";
1088                 return ERR;
1089         }
1091         if (strcmp(argv[1], "=")) {
1092                 config_msg = "No value assigned";
1093                 return ERR;
1094         }
1096         if (!strcmp(argv[0], "show-author")) {
1097                 opt_author = parse_bool(argv[2]);
1098                 return OK;
1099         }
1101         if (!strcmp(argv[0], "show-date")) {
1102                 opt_date = parse_bool(argv[2]);
1103                 return OK;
1104         }
1106         if (!strcmp(argv[0], "show-rev-graph")) {
1107                 opt_rev_graph = parse_bool(argv[2]);
1108                 return OK;
1109         }
1111         if (!strcmp(argv[0], "show-refs")) {
1112                 opt_show_refs = parse_bool(argv[2]);
1113                 return OK;
1114         }
1116         if (!strcmp(argv[0], "show-line-numbers")) {
1117                 opt_line_number = parse_bool(argv[2]);
1118                 return OK;
1119         }
1121         if (!strcmp(argv[0], "line-number-interval")) {
1122                 opt_num_interval = atoi(argv[2]);
1123                 return OK;
1124         }
1126         if (!strcmp(argv[0], "tab-size")) {
1127                 opt_tab_size = atoi(argv[2]);
1128                 return OK;
1129         }
1131         if (!strcmp(argv[0], "commit-encoding")) {
1132                 char *arg = argv[2];
1133                 int delimiter = *arg;
1134                 int i;
1136                 switch (delimiter) {
1137                 case '"':
1138                 case '\'':
1139                         for (arg++, i = 0; arg[i]; i++)
1140                                 if (arg[i] == delimiter) {
1141                                         arg[i] = 0;
1142                                         break;
1143                                 }
1144                 default:
1145                         string_ncopy(opt_encoding, arg, strlen(arg));
1146                         return OK;
1147                 }
1148         }
1150         config_msg = "Unknown variable name";
1151         return ERR;
1154 /* Wants: mode request key */
1155 static int
1156 option_bind_command(int argc, char *argv[])
1158         enum request request;
1159         int keymap;
1160         int key;
1162         if (argc < 3) {
1163                 config_msg = "Wrong number of arguments given to bind command";
1164                 return ERR;
1165         }
1167         if (set_keymap(&keymap, argv[0]) == ERR) {
1168                 config_msg = "Unknown key map";
1169                 return ERR;
1170         }
1172         key = get_key_value(argv[1]);
1173         if (key == ERR) {
1174                 config_msg = "Unknown key";
1175                 return ERR;
1176         }
1178         request = get_request(argv[2]);
1179         if (request == REQ_NONE) {
1180                 const char *obsolete[] = { "cherry-pick" };
1181                 size_t namelen = strlen(argv[2]);
1182                 int i;
1184                 for (i = 0; i < ARRAY_SIZE(obsolete); i++) {
1185                         if (namelen == strlen(obsolete[i]) &&
1186                             !string_enum_compare(obsolete[i], argv[2], namelen)) {
1187                                 config_msg = "Obsolete request name";
1188                                 return ERR;
1189                         }
1190                 }
1191         }
1192         if (request == REQ_NONE && *argv[2]++ == '!')
1193                 request = add_run_request(keymap, key, argc - 2, argv + 2);
1194         if (request == REQ_NONE) {
1195                 config_msg = "Unknown request name";
1196                 return ERR;
1197         }
1199         add_keybinding(keymap, request, key);
1201         return OK;
1204 static int
1205 set_option(char *opt, char *value)
1207         char *argv[16];
1208         int valuelen;
1209         int argc = 0;
1211         /* Tokenize */
1212         while (argc < ARRAY_SIZE(argv) && (valuelen = strcspn(value, " \t"))) {
1213                 argv[argc++] = value;
1214                 value += valuelen;
1216                 /* Nothing more to tokenize or last available token. */
1217                 if (!*value || argc >= ARRAY_SIZE(argv))
1218                         break;
1220                 *value++ = 0;
1221                 while (isspace(*value))
1222                         value++;
1223         }
1225         if (!strcmp(opt, "color"))
1226                 return option_color_command(argc, argv);
1228         if (!strcmp(opt, "set"))
1229                 return option_set_command(argc, argv);
1231         if (!strcmp(opt, "bind"))
1232                 return option_bind_command(argc, argv);
1234         config_msg = "Unknown option command";
1235         return ERR;
1238 static int
1239 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1241         int status = OK;
1243         config_lineno++;
1244         config_msg = "Internal error";
1246         /* Check for comment markers, since read_properties() will
1247          * only ensure opt and value are split at first " \t". */
1248         optlen = strcspn(opt, "#");
1249         if (optlen == 0)
1250                 return OK;
1252         if (opt[optlen] != 0) {
1253                 config_msg = "No option value";
1254                 status = ERR;
1256         }  else {
1257                 /* Look for comment endings in the value. */
1258                 size_t len = strcspn(value, "#");
1260                 if (len < valuelen) {
1261                         valuelen = len;
1262                         value[valuelen] = 0;
1263                 }
1265                 status = set_option(opt, value);
1266         }
1268         if (status == ERR) {
1269                 fprintf(stderr, "Error on line %d, near '%.*s': %s\n",
1270                         config_lineno, (int) optlen, opt, config_msg);
1271                 config_errors = TRUE;
1272         }
1274         /* Always keep going if errors are encountered. */
1275         return OK;
1278 static void
1279 load_option_file(const char *path)
1281         FILE *file;
1283         /* It's ok that the file doesn't exist. */
1284         file = fopen(path, "r");
1285         if (!file)
1286                 return;
1288         config_lineno = 0;
1289         config_errors = FALSE;
1291         if (read_properties(file, " \t", read_option) == ERR ||
1292             config_errors == TRUE)
1293                 fprintf(stderr, "Errors while loading %s.\n", path);
1296 static int
1297 load_options(void)
1299         char *home = getenv("HOME");
1300         char *tigrc_user = getenv("TIGRC_USER");
1301         char *tigrc_system = getenv("TIGRC_SYSTEM");
1302         char buf[SIZEOF_STR];
1304         add_builtin_run_requests();
1306         if (!tigrc_system) {
1307                 if (!string_format(buf, "%s/tigrc", SYSCONFDIR))
1308                         return ERR;
1309                 tigrc_system = buf;
1310         }
1311         load_option_file(tigrc_system);
1313         if (!tigrc_user) {
1314                 if (!home || !string_format(buf, "%s/.tigrc", home))
1315                         return ERR;
1316                 tigrc_user = buf;
1317         }
1318         load_option_file(tigrc_user);
1320         return OK;
1324 /*
1325  * The viewer
1326  */
1328 struct view;
1329 struct view_ops;
1331 /* The display array of active views and the index of the current view. */
1332 static struct view *display[2];
1333 static unsigned int current_view;
1335 /* Reading from the prompt? */
1336 static bool input_mode = FALSE;
1338 #define foreach_displayed_view(view, i) \
1339         for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1341 #define displayed_views()       (display[1] != NULL ? 2 : 1)
1343 /* Current head and commit ID */
1344 static char ref_blob[SIZEOF_REF]        = "";
1345 static char ref_commit[SIZEOF_REF]      = "HEAD";
1346 static char ref_head[SIZEOF_REF]        = "HEAD";
1348 struct view {
1349         const char *name;       /* View name */
1350         const char *cmd_fmt;    /* Default command line format */
1351         const char *cmd_env;    /* Command line set via environment */
1352         const char *id;         /* Points to either of ref_{head,commit,blob} */
1354         struct view_ops *ops;   /* View operations */
1356         enum keymap keymap;     /* What keymap does this view have */
1358         char cmd[SIZEOF_STR];   /* Command buffer */
1359         char ref[SIZEOF_REF];   /* Hovered commit reference */
1360         char vid[SIZEOF_REF];   /* View ID. Set to id member when updating. */
1362         int height, width;      /* The width and height of the main window */
1363         WINDOW *win;            /* The main window */
1364         WINDOW *title;          /* The title window living below the main window */
1366         /* Navigation */
1367         unsigned long offset;   /* Offset of the window top */
1368         unsigned long lineno;   /* Current line number */
1370         /* Searching */
1371         char grep[SIZEOF_STR];  /* Search string */
1372         regex_t *regex;         /* Pre-compiled regex */
1374         /* If non-NULL, points to the view that opened this view. If this view
1375          * is closed tig will switch back to the parent view. */
1376         struct view *parent;
1378         /* Buffering */
1379         size_t lines;           /* Total number of lines */
1380         struct line *line;      /* Line index */
1381         size_t line_alloc;      /* Total number of allocated lines */
1382         size_t line_size;       /* Total number of used lines */
1383         unsigned int digits;    /* Number of digits in the lines member. */
1385         /* Loading */
1386         FILE *pipe;
1387         time_t start_time;
1388 };
1390 struct view_ops {
1391         /* What type of content being displayed. Used in the title bar. */
1392         const char *type;
1393         /* Open and reads in all view content. */
1394         bool (*open)(struct view *view);
1395         /* Read one line; updates view->line. */
1396         bool (*read)(struct view *view, char *data);
1397         /* Draw one line; @lineno must be < view->height. */
1398         bool (*draw)(struct view *view, struct line *line, unsigned int lineno, bool selected);
1399         /* Depending on view handle a special requests. */
1400         enum request (*request)(struct view *view, enum request request, struct line *line);
1401         /* Search for regex in a line. */
1402         bool (*grep)(struct view *view, struct line *line);
1403         /* Select line */
1404         void (*select)(struct view *view, struct line *line);
1405 };
1407 static struct view_ops pager_ops;
1408 static struct view_ops main_ops;
1409 static struct view_ops tree_ops;
1410 static struct view_ops blob_ops;
1411 static struct view_ops blame_ops;
1412 static struct view_ops help_ops;
1413 static struct view_ops status_ops;
1414 static struct view_ops stage_ops;
1416 #define VIEW_STR(name, cmd, env, ref, ops, map) \
1417         { name, cmd, #env, ref, ops, map}
1419 #define VIEW_(id, name, ops, ref) \
1420         VIEW_STR(name, TIG_##id##_CMD,  TIG_##id##_CMD, ref, ops, KEYMAP_##id)
1423 static struct view views[] = {
1424         VIEW_(MAIN,   "main",   &main_ops,   ref_head),
1425         VIEW_(DIFF,   "diff",   &pager_ops,  ref_commit),
1426         VIEW_(LOG,    "log",    &pager_ops,  ref_head),
1427         VIEW_(TREE,   "tree",   &tree_ops,   ref_commit),
1428         VIEW_(BLOB,   "blob",   &blob_ops,   ref_blob),
1429         VIEW_(BLAME,  "blame",  &blame_ops,  ref_commit),
1430         VIEW_(HELP,   "help",   &help_ops,   ""),
1431         VIEW_(PAGER,  "pager",  &pager_ops,  "stdin"),
1432         VIEW_(STATUS, "status", &status_ops, ""),
1433         VIEW_(STAGE,  "stage",  &stage_ops,  ""),
1434 };
1436 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
1438 #define foreach_view(view, i) \
1439         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1441 #define view_is_displayed(view) \
1442         (view == display[0] || view == display[1])
1444 static int
1445 draw_text(struct view *view, const char *string, int max_len,
1446           bool use_tilde, bool selected)
1448         int len = 0;
1449         int trimmed = FALSE;
1451         if (max_len <= 0)
1452                 return 0;
1454         if (opt_utf8) {
1455                 len = utf8_length(string, max_len, &trimmed, use_tilde);
1456         } else {
1457                 len = strlen(string);
1458                 if (len > max_len) {
1459                         if (use_tilde) {
1460                                 max_len -= 1;
1461                         }
1462                         len = max_len;
1463                         trimmed = TRUE;
1464                 }
1465         }
1467         waddnstr(view->win, string, len);
1468         if (trimmed && use_tilde) {
1469                 if (!selected)
1470                         wattrset(view->win, get_line_attr(LINE_DELIMITER));
1471                 waddch(view->win, '~');
1472                 len++;
1473         }
1475         return len;
1478 static bool
1479 draw_view_line(struct view *view, unsigned int lineno)
1481         struct line *line;
1482         bool selected = (view->offset + lineno == view->lineno);
1483         bool draw_ok;
1485         assert(view_is_displayed(view));
1487         if (view->offset + lineno >= view->lines)
1488                 return FALSE;
1490         line = &view->line[view->offset + lineno];
1492         if (selected) {
1493                 line->selected = TRUE;
1494                 view->ops->select(view, line);
1495         } else if (line->selected) {
1496                 line->selected = FALSE;
1497                 wmove(view->win, lineno, 0);
1498                 wclrtoeol(view->win);
1499         }
1501         scrollok(view->win, FALSE);
1502         draw_ok = view->ops->draw(view, line, lineno, selected);
1503         scrollok(view->win, TRUE);
1505         return draw_ok;
1508 static void
1509 redraw_view_dirty(struct view *view)
1511         bool dirty = FALSE;
1512         int lineno;
1514         for (lineno = 0; lineno < view->height; lineno++) {
1515                 struct line *line = &view->line[view->offset + lineno];
1517                 if (!line->dirty)
1518                         continue;
1519                 line->dirty = 0;
1520                 dirty = TRUE;
1521                 if (!draw_view_line(view, lineno))
1522                         break;
1523         }
1525         if (!dirty)
1526                 return;
1527         redrawwin(view->win);
1528         if (input_mode)
1529                 wnoutrefresh(view->win);
1530         else
1531                 wrefresh(view->win);
1534 static void
1535 redraw_view_from(struct view *view, int lineno)
1537         assert(0 <= lineno && lineno < view->height);
1539         for (; lineno < view->height; lineno++) {
1540                 if (!draw_view_line(view, lineno))
1541                         break;
1542         }
1544         redrawwin(view->win);
1545         if (input_mode)
1546                 wnoutrefresh(view->win);
1547         else
1548                 wrefresh(view->win);
1551 static void
1552 redraw_view(struct view *view)
1554         wclear(view->win);
1555         redraw_view_from(view, 0);
1559 static void
1560 update_view_title(struct view *view)
1562         char buf[SIZEOF_STR];
1563         char state[SIZEOF_STR];
1564         size_t bufpos = 0, statelen = 0;
1566         assert(view_is_displayed(view));
1568         if (view != VIEW(REQ_VIEW_STATUS) && (view->lines || view->pipe)) {
1569                 unsigned int view_lines = view->offset + view->height;
1570                 unsigned int lines = view->lines
1571                                    ? MIN(view_lines, view->lines) * 100 / view->lines
1572                                    : 0;
1574                 string_format_from(state, &statelen, "- %s %d of %d (%d%%)",
1575                                    view->ops->type,
1576                                    view->lineno + 1,
1577                                    view->lines,
1578                                    lines);
1580                 if (view->pipe) {
1581                         time_t secs = time(NULL) - view->start_time;
1583                         /* Three git seconds are a long time ... */
1584                         if (secs > 2)
1585                                 string_format_from(state, &statelen, " %lds", secs);
1586                 }
1587         }
1589         string_format_from(buf, &bufpos, "[%s]", view->name);
1590         if (*view->ref && bufpos < view->width) {
1591                 size_t refsize = strlen(view->ref);
1592                 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
1594                 if (minsize < view->width)
1595                         refsize = view->width - minsize + 7;
1596                 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
1597         }
1599         if (statelen && bufpos < view->width) {
1600                 string_format_from(buf, &bufpos, " %s", state);
1601         }
1603         if (view == display[current_view])
1604                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
1605         else
1606                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
1608         mvwaddnstr(view->title, 0, 0, buf, bufpos);
1609         wclrtoeol(view->title);
1610         wmove(view->title, 0, view->width - 1);
1612         if (input_mode)
1613                 wnoutrefresh(view->title);
1614         else
1615                 wrefresh(view->title);
1618 static void
1619 resize_display(void)
1621         int offset, i;
1622         struct view *base = display[0];
1623         struct view *view = display[1] ? display[1] : display[0];
1625         /* Setup window dimensions */
1627         getmaxyx(stdscr, base->height, base->width);
1629         /* Make room for the status window. */
1630         base->height -= 1;
1632         if (view != base) {
1633                 /* Horizontal split. */
1634                 view->width   = base->width;
1635                 view->height  = SCALE_SPLIT_VIEW(base->height);
1636                 base->height -= view->height;
1638                 /* Make room for the title bar. */
1639                 view->height -= 1;
1640         }
1642         /* Make room for the title bar. */
1643         base->height -= 1;
1645         offset = 0;
1647         foreach_displayed_view (view, i) {
1648                 if (!view->win) {
1649                         view->win = newwin(view->height, 0, offset, 0);
1650                         if (!view->win)
1651                                 die("Failed to create %s view", view->name);
1653                         scrollok(view->win, TRUE);
1655                         view->title = newwin(1, 0, offset + view->height, 0);
1656                         if (!view->title)
1657                                 die("Failed to create title window");
1659                 } else {
1660                         wresize(view->win, view->height, view->width);
1661                         mvwin(view->win,   offset, 0);
1662                         mvwin(view->title, offset + view->height, 0);
1663                 }
1665                 offset += view->height + 1;
1666         }
1669 static void
1670 redraw_display(void)
1672         struct view *view;
1673         int i;
1675         foreach_displayed_view (view, i) {
1676                 redraw_view(view);
1677                 update_view_title(view);
1678         }
1681 static void
1682 update_display_cursor(struct view *view)
1684         /* Move the cursor to the right-most column of the cursor line.
1685          *
1686          * XXX: This could turn out to be a bit expensive, but it ensures that
1687          * the cursor does not jump around. */
1688         if (view->lines) {
1689                 wmove(view->win, view->lineno - view->offset, view->width - 1);
1690                 wrefresh(view->win);
1691         }
1694 /*
1695  * Navigation
1696  */
1698 /* Scrolling backend */
1699 static void
1700 do_scroll_view(struct view *view, int lines)
1702         bool redraw_current_line = FALSE;
1704         /* The rendering expects the new offset. */
1705         view->offset += lines;
1707         assert(0 <= view->offset && view->offset < view->lines);
1708         assert(lines);
1710         /* Move current line into the view. */
1711         if (view->lineno < view->offset) {
1712                 view->lineno = view->offset;
1713                 redraw_current_line = TRUE;
1714         } else if (view->lineno >= view->offset + view->height) {
1715                 view->lineno = view->offset + view->height - 1;
1716                 redraw_current_line = TRUE;
1717         }
1719         assert(view->offset <= view->lineno && view->lineno < view->lines);
1721         /* Redraw the whole screen if scrolling is pointless. */
1722         if (view->height < ABS(lines)) {
1723                 redraw_view(view);
1725         } else {
1726                 int line = lines > 0 ? view->height - lines : 0;
1727                 int end = line + ABS(lines);
1729                 wscrl(view->win, lines);
1731                 for (; line < end; line++) {
1732                         if (!draw_view_line(view, line))
1733                                 break;
1734                 }
1736                 if (redraw_current_line)
1737                         draw_view_line(view, view->lineno - view->offset);
1738         }
1740         redrawwin(view->win);
1741         wrefresh(view->win);
1742         report("");
1745 /* Scroll frontend */
1746 static void
1747 scroll_view(struct view *view, enum request request)
1749         int lines = 1;
1751         assert(view_is_displayed(view));
1753         switch (request) {
1754         case REQ_SCROLL_PAGE_DOWN:
1755                 lines = view->height;
1756         case REQ_SCROLL_LINE_DOWN:
1757                 if (view->offset + lines > view->lines)
1758                         lines = view->lines - view->offset;
1760                 if (lines == 0 || view->offset + view->height >= view->lines) {
1761                         report("Cannot scroll beyond the last line");
1762                         return;
1763                 }
1764                 break;
1766         case REQ_SCROLL_PAGE_UP:
1767                 lines = view->height;
1768         case REQ_SCROLL_LINE_UP:
1769                 if (lines > view->offset)
1770                         lines = view->offset;
1772                 if (lines == 0) {
1773                         report("Cannot scroll beyond the first line");
1774                         return;
1775                 }
1777                 lines = -lines;
1778                 break;
1780         default:
1781                 die("request %d not handled in switch", request);
1782         }
1784         do_scroll_view(view, lines);
1787 /* Cursor moving */
1788 static void
1789 move_view(struct view *view, enum request request)
1791         int scroll_steps = 0;
1792         int steps;
1794         switch (request) {
1795         case REQ_MOVE_FIRST_LINE:
1796                 steps = -view->lineno;
1797                 break;
1799         case REQ_MOVE_LAST_LINE:
1800                 steps = view->lines - view->lineno - 1;
1801                 break;
1803         case REQ_MOVE_PAGE_UP:
1804                 steps = view->height > view->lineno
1805                       ? -view->lineno : -view->height;
1806                 break;
1808         case REQ_MOVE_PAGE_DOWN:
1809                 steps = view->lineno + view->height >= view->lines
1810                       ? view->lines - view->lineno - 1 : view->height;
1811                 break;
1813         case REQ_MOVE_UP:
1814                 steps = -1;
1815                 break;
1817         case REQ_MOVE_DOWN:
1818                 steps = 1;
1819                 break;
1821         default:
1822                 die("request %d not handled in switch", request);
1823         }
1825         if (steps <= 0 && view->lineno == 0) {
1826                 report("Cannot move beyond the first line");
1827                 return;
1829         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
1830                 report("Cannot move beyond the last line");
1831                 return;
1832         }
1834         /* Move the current line */
1835         view->lineno += steps;
1836         assert(0 <= view->lineno && view->lineno < view->lines);
1838         /* Check whether the view needs to be scrolled */
1839         if (view->lineno < view->offset ||
1840             view->lineno >= view->offset + view->height) {
1841                 scroll_steps = steps;
1842                 if (steps < 0 && -steps > view->offset) {
1843                         scroll_steps = -view->offset;
1845                 } else if (steps > 0) {
1846                         if (view->lineno == view->lines - 1 &&
1847                             view->lines > view->height) {
1848                                 scroll_steps = view->lines - view->offset - 1;
1849                                 if (scroll_steps >= view->height)
1850                                         scroll_steps -= view->height - 1;
1851                         }
1852                 }
1853         }
1855         if (!view_is_displayed(view)) {
1856                 view->offset += scroll_steps;
1857                 assert(0 <= view->offset && view->offset < view->lines);
1858                 view->ops->select(view, &view->line[view->lineno]);
1859                 return;
1860         }
1862         /* Repaint the old "current" line if we be scrolling */
1863         if (ABS(steps) < view->height)
1864                 draw_view_line(view, view->lineno - steps - view->offset);
1866         if (scroll_steps) {
1867                 do_scroll_view(view, scroll_steps);
1868                 return;
1869         }
1871         /* Draw the current line */
1872         draw_view_line(view, view->lineno - view->offset);
1874         redrawwin(view->win);
1875         wrefresh(view->win);
1876         report("");
1880 /*
1881  * Searching
1882  */
1884 static void search_view(struct view *view, enum request request);
1886 static bool
1887 find_next_line(struct view *view, unsigned long lineno, struct line *line)
1889         assert(view_is_displayed(view));
1891         if (!view->ops->grep(view, line))
1892                 return FALSE;
1894         if (lineno - view->offset >= view->height) {
1895                 view->offset = lineno;
1896                 view->lineno = lineno;
1897                 redraw_view(view);
1899         } else {
1900                 unsigned long old_lineno = view->lineno - view->offset;
1902                 view->lineno = lineno;
1903                 draw_view_line(view, old_lineno);
1905                 draw_view_line(view, view->lineno - view->offset);
1906                 redrawwin(view->win);
1907                 wrefresh(view->win);
1908         }
1910         report("Line %ld matches '%s'", lineno + 1, view->grep);
1911         return TRUE;
1914 static void
1915 find_next(struct view *view, enum request request)
1917         unsigned long lineno = view->lineno;
1918         int direction;
1920         if (!*view->grep) {
1921                 if (!*opt_search)
1922                         report("No previous search");
1923                 else
1924                         search_view(view, request);
1925                 return;
1926         }
1928         switch (request) {
1929         case REQ_SEARCH:
1930         case REQ_FIND_NEXT:
1931                 direction = 1;
1932                 break;
1934         case REQ_SEARCH_BACK:
1935         case REQ_FIND_PREV:
1936                 direction = -1;
1937                 break;
1939         default:
1940                 return;
1941         }
1943         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
1944                 lineno += direction;
1946         /* Note, lineno is unsigned long so will wrap around in which case it
1947          * will become bigger than view->lines. */
1948         for (; lineno < view->lines; lineno += direction) {
1949                 struct line *line = &view->line[lineno];
1951                 if (find_next_line(view, lineno, line))
1952                         return;
1953         }
1955         report("No match found for '%s'", view->grep);
1958 static void
1959 search_view(struct view *view, enum request request)
1961         int regex_err;
1963         if (view->regex) {
1964                 regfree(view->regex);
1965                 *view->grep = 0;
1966         } else {
1967                 view->regex = calloc(1, sizeof(*view->regex));
1968                 if (!view->regex)
1969                         return;
1970         }
1972         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
1973         if (regex_err != 0) {
1974                 char buf[SIZEOF_STR] = "unknown error";
1976                 regerror(regex_err, view->regex, buf, sizeof(buf));
1977                 report("Search failed: %s", buf);
1978                 return;
1979         }
1981         string_copy(view->grep, opt_search);
1983         find_next(view, request);
1986 /*
1987  * Incremental updating
1988  */
1990 static void
1991 end_update(struct view *view)
1993         if (!view->pipe)
1994                 return;
1995         set_nonblocking_input(FALSE);
1996         if (view->pipe == stdin)
1997                 fclose(view->pipe);
1998         else
1999                 pclose(view->pipe);
2000         view->pipe = NULL;
2003 static bool
2004 begin_update(struct view *view)
2006         if (view->pipe)
2007                 end_update(view);
2009         if (opt_cmd[0]) {
2010                 string_copy(view->cmd, opt_cmd);
2011                 opt_cmd[0] = 0;
2012                 /* When running random commands, initially show the
2013                  * command in the title. However, it maybe later be
2014                  * overwritten if a commit line is selected. */
2015                 if (view == VIEW(REQ_VIEW_PAGER))
2016                         string_copy(view->ref, view->cmd);
2017                 else
2018                         view->ref[0] = 0;
2020         } else if (view == VIEW(REQ_VIEW_TREE)) {
2021                 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
2022                 char path[SIZEOF_STR];
2024                 if (strcmp(view->vid, view->id))
2025                         opt_path[0] = path[0] = 0;
2026                 else if (sq_quote(path, 0, opt_path) >= sizeof(path))
2027                         return FALSE;
2029                 if (!string_format(view->cmd, format, view->id, path))
2030                         return FALSE;
2032         } else {
2033                 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
2034                 const char *id = view->id;
2036                 if (!string_format(view->cmd, format, id, id, id, id, id))
2037                         return FALSE;
2039                 /* Put the current ref_* value to the view title ref
2040                  * member. This is needed by the blob view. Most other
2041                  * views sets it automatically after loading because the
2042                  * first line is a commit line. */
2043                 string_copy_rev(view->ref, view->id);
2044         }
2046         /* Special case for the pager view. */
2047         if (opt_pipe) {
2048                 view->pipe = opt_pipe;
2049                 opt_pipe = NULL;
2050         } else {
2051                 view->pipe = popen(view->cmd, "r");
2052         }
2054         if (!view->pipe)
2055                 return FALSE;
2057         set_nonblocking_input(TRUE);
2059         view->offset = 0;
2060         view->lines  = 0;
2061         view->lineno = 0;
2062         string_copy_rev(view->vid, view->id);
2064         if (view->line) {
2065                 int i;
2067                 for (i = 0; i < view->lines; i++)
2068                         if (view->line[i].data)
2069                                 free(view->line[i].data);
2071                 free(view->line);
2072                 view->line = NULL;
2073         }
2075         view->start_time = time(NULL);
2077         return TRUE;
2080 #define ITEM_CHUNK_SIZE 256
2081 static void *
2082 realloc_items(void *mem, size_t *size, size_t new_size, size_t item_size)
2084         size_t num_chunks = *size / ITEM_CHUNK_SIZE;
2085         size_t num_chunks_new = (new_size + ITEM_CHUNK_SIZE - 1) / ITEM_CHUNK_SIZE;
2087         if (mem == NULL || num_chunks != num_chunks_new) {
2088                 *size = num_chunks_new * ITEM_CHUNK_SIZE;
2089                 mem = realloc(mem, *size * item_size);
2090         }
2092         return mem;
2095 static struct line *
2096 realloc_lines(struct view *view, size_t line_size)
2098         size_t alloc = view->line_alloc;
2099         struct line *tmp = realloc_items(view->line, &alloc, line_size,
2100                                          sizeof(*view->line));
2102         if (!tmp)
2103                 return NULL;
2105         view->line = tmp;
2106         view->line_alloc = alloc;
2107         view->line_size = line_size;
2108         return view->line;
2111 static bool
2112 update_view(struct view *view)
2114         char in_buffer[BUFSIZ];
2115         char out_buffer[BUFSIZ * 2];
2116         char *line;
2117         /* The number of lines to read. If too low it will cause too much
2118          * redrawing (and possible flickering), if too high responsiveness
2119          * will suffer. */
2120         unsigned long lines = view->height;
2121         int redraw_from = -1;
2123         if (!view->pipe)
2124                 return TRUE;
2126         /* Only redraw if lines are visible. */
2127         if (view->offset + view->height >= view->lines)
2128                 redraw_from = view->lines - view->offset;
2130         /* FIXME: This is probably not perfect for backgrounded views. */
2131         if (!realloc_lines(view, view->lines + lines))
2132                 goto alloc_error;
2134         while ((line = fgets(in_buffer, sizeof(in_buffer), view->pipe))) {
2135                 size_t linelen = strlen(line);
2137                 if (linelen)
2138                         line[linelen - 1] = 0;
2140                 if (opt_iconv != ICONV_NONE) {
2141                         ICONV_CONST char *inbuf = line;
2142                         size_t inlen = linelen;
2144                         char *outbuf = out_buffer;
2145                         size_t outlen = sizeof(out_buffer);
2147                         size_t ret;
2149                         ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2150                         if (ret != (size_t) -1) {
2151                                 line = out_buffer;
2152                                 linelen = strlen(out_buffer);
2153                         }
2154                 }
2156                 if (!view->ops->read(view, line))
2157                         goto alloc_error;
2159                 if (lines-- == 1)
2160                         break;
2161         }
2163         {
2164                 int digits;
2166                 lines = view->lines;
2167                 for (digits = 0; lines; digits++)
2168                         lines /= 10;
2170                 /* Keep the displayed view in sync with line number scaling. */
2171                 if (digits != view->digits) {
2172                         view->digits = digits;
2173                         redraw_from = 0;
2174                 }
2175         }
2177         if (!view_is_displayed(view))
2178                 goto check_pipe;
2180         if (view == VIEW(REQ_VIEW_TREE)) {
2181                 /* Clear the view and redraw everything since the tree sorting
2182                  * might have rearranged things. */
2183                 redraw_view(view);
2185         } else if (redraw_from >= 0) {
2186                 /* If this is an incremental update, redraw the previous line
2187                  * since for commits some members could have changed when
2188                  * loading the main view. */
2189                 if (redraw_from > 0)
2190                         redraw_from--;
2192                 /* Since revision graph visualization requires knowledge
2193                  * about the parent commit, it causes a further one-off
2194                  * needed to be redrawn for incremental updates. */
2195                 if (redraw_from > 0 && opt_rev_graph)
2196                         redraw_from--;
2198                 /* Incrementally draw avoids flickering. */
2199                 redraw_view_from(view, redraw_from);
2200         }
2202         if (view == VIEW(REQ_VIEW_BLAME))
2203                 redraw_view_dirty(view);
2205         /* Update the title _after_ the redraw so that if the redraw picks up a
2206          * commit reference in view->ref it'll be available here. */
2207         update_view_title(view);
2209 check_pipe:
2210         if (ferror(view->pipe)) {
2211                 report("Failed to read: %s", strerror(errno));
2212                 goto end;
2214         } else if (feof(view->pipe)) {
2215                 report("");
2216                 goto end;
2217         }
2219         return TRUE;
2221 alloc_error:
2222         report("Allocation failure");
2224 end:
2225         if (view->ops->read(view, NULL))
2226                 end_update(view);
2227         return FALSE;
2230 static struct line *
2231 add_line_data(struct view *view, void *data, enum line_type type)
2233         struct line *line = &view->line[view->lines++];
2235         memset(line, 0, sizeof(*line));
2236         line->type = type;
2237         line->data = data;
2239         return line;
2242 static struct line *
2243 add_line_text(struct view *view, char *data, enum line_type type)
2245         if (data)
2246                 data = strdup(data);
2248         return data ? add_line_data(view, data, type) : NULL;
2252 /*
2253  * View opening
2254  */
2256 enum open_flags {
2257         OPEN_DEFAULT = 0,       /* Use default view switching. */
2258         OPEN_SPLIT = 1,         /* Split current view. */
2259         OPEN_BACKGROUNDED = 2,  /* Backgrounded. */
2260         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
2261 };
2263 static void
2264 open_view(struct view *prev, enum request request, enum open_flags flags)
2266         bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
2267         bool split = !!(flags & OPEN_SPLIT);
2268         bool reload = !!(flags & OPEN_RELOAD);
2269         struct view *view = VIEW(request);
2270         int nviews = displayed_views();
2271         struct view *base_view = display[0];
2273         if (view == prev && nviews == 1 && !reload) {
2274                 report("Already in %s view", view->name);
2275                 return;
2276         }
2278         if (view->ops->open) {
2279                 if (!view->ops->open(view)) {
2280                         report("Failed to load %s view", view->name);
2281                         return;
2282                 }
2284         } else if ((reload || strcmp(view->vid, view->id)) &&
2285                    !begin_update(view)) {
2286                 report("Failed to load %s view", view->name);
2287                 return;
2288         }
2290         if (split) {
2291                 display[1] = view;
2292                 if (!backgrounded)
2293                         current_view = 1;
2294         } else {
2295                 /* Maximize the current view. */
2296                 memset(display, 0, sizeof(display));
2297                 current_view = 0;
2298                 display[current_view] = view;
2299         }
2301         /* Resize the view when switching between split- and full-screen,
2302          * or when switching between two different full-screen views. */
2303         if (nviews != displayed_views() ||
2304             (nviews == 1 && base_view != display[0]))
2305                 resize_display();
2307         if (split && prev->lineno - prev->offset >= prev->height) {
2308                 /* Take the title line into account. */
2309                 int lines = prev->lineno - prev->offset - prev->height + 1;
2311                 /* Scroll the view that was split if the current line is
2312                  * outside the new limited view. */
2313                 do_scroll_view(prev, lines);
2314         }
2316         if (prev && view != prev) {
2317                 if (split && !backgrounded) {
2318                         /* "Blur" the previous view. */
2319                         update_view_title(prev);
2320                 }
2322                 view->parent = prev;
2323         }
2325         if (view->pipe && view->lines == 0) {
2326                 /* Clear the old view and let the incremental updating refill
2327                  * the screen. */
2328                 wclear(view->win);
2329                 report("");
2330         } else {
2331                 redraw_view(view);
2332                 report("");
2333         }
2335         /* If the view is backgrounded the above calls to report()
2336          * won't redraw the view title. */
2337         if (backgrounded)
2338                 update_view_title(view);
2341 static void
2342 open_external_viewer(const char *cmd)
2344         def_prog_mode();           /* save current tty modes */
2345         endwin();                  /* restore original tty modes */
2346         system(cmd);
2347         fprintf(stderr, "Press Enter to continue");
2348         getc(stdin);
2349         reset_prog_mode();
2350         redraw_display();
2353 static void
2354 open_mergetool(const char *file)
2356         char cmd[SIZEOF_STR];
2357         char file_sq[SIZEOF_STR];
2359         if (sq_quote(file_sq, 0, file) < sizeof(file_sq) &&
2360             string_format(cmd, "git mergetool %s", file_sq)) {
2361                 open_external_viewer(cmd);
2362         }
2365 static void
2366 open_editor(bool from_root, const char *file)
2368         char cmd[SIZEOF_STR];
2369         char file_sq[SIZEOF_STR];
2370         char *editor;
2371         char *prefix = from_root ? opt_cdup : "";
2373         editor = getenv("GIT_EDITOR");
2374         if (!editor && *opt_editor)
2375                 editor = opt_editor;
2376         if (!editor)
2377                 editor = getenv("VISUAL");
2378         if (!editor)
2379                 editor = getenv("EDITOR");
2380         if (!editor)
2381                 editor = "vi";
2383         if (sq_quote(file_sq, 0, file) < sizeof(file_sq) &&
2384             string_format(cmd, "%s %s%s", editor, prefix, file_sq)) {
2385                 open_external_viewer(cmd);
2386         }
2389 static void
2390 open_run_request(enum request request)
2392         struct run_request *req = get_run_request(request);
2393         char buf[SIZEOF_STR * 2];
2394         size_t bufpos;
2395         char *cmd;
2397         if (!req) {
2398                 report("Unknown run request");
2399                 return;
2400         }
2402         bufpos = 0;
2403         cmd = req->cmd;
2405         while (cmd) {
2406                 char *next = strstr(cmd, "%(");
2407                 int len = next - cmd;
2408                 char *value;
2410                 if (!next) {
2411                         len = strlen(cmd);
2412                         value = "";
2414                 } else if (!strncmp(next, "%(head)", 7)) {
2415                         value = ref_head;
2417                 } else if (!strncmp(next, "%(commit)", 9)) {
2418                         value = ref_commit;
2420                 } else if (!strncmp(next, "%(blob)", 7)) {
2421                         value = ref_blob;
2423                 } else {
2424                         report("Unknown replacement in run request: `%s`", req->cmd);
2425                         return;
2426                 }
2428                 if (!string_format_from(buf, &bufpos, "%.*s%s", len, cmd, value))
2429                         return;
2431                 if (next)
2432                         next = strchr(next, ')') + 1;
2433                 cmd = next;
2434         }
2436         open_external_viewer(buf);
2439 /*
2440  * User request switch noodle
2441  */
2443 static int
2444 view_driver(struct view *view, enum request request)
2446         int i;
2448         if (request == REQ_NONE) {
2449                 doupdate();
2450                 return TRUE;
2451         }
2453         if (request > REQ_NONE) {
2454                 open_run_request(request);
2455                 return TRUE;
2456         }
2458         if (view && view->lines) {
2459                 request = view->ops->request(view, request, &view->line[view->lineno]);
2460                 if (request == REQ_NONE)
2461                         return TRUE;
2462         }
2464         switch (request) {
2465         case REQ_MOVE_UP:
2466         case REQ_MOVE_DOWN:
2467         case REQ_MOVE_PAGE_UP:
2468         case REQ_MOVE_PAGE_DOWN:
2469         case REQ_MOVE_FIRST_LINE:
2470         case REQ_MOVE_LAST_LINE:
2471                 move_view(view, request);
2472                 break;
2474         case REQ_SCROLL_LINE_DOWN:
2475         case REQ_SCROLL_LINE_UP:
2476         case REQ_SCROLL_PAGE_DOWN:
2477         case REQ_SCROLL_PAGE_UP:
2478                 scroll_view(view, request);
2479                 break;
2481         case REQ_VIEW_BLAME:
2482                 if (!opt_file[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_BLOB:
2491                 if (!ref_blob[0]) {
2492                         report("No file chosen, press %s to open tree view",
2493                                get_key(REQ_VIEW_TREE));
2494                         break;
2495                 }
2496                 open_view(view, request, OPEN_DEFAULT);
2497                 break;
2499         case REQ_VIEW_PAGER:
2500                 if (!opt_pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
2501                         report("No pager content, press %s to run command from prompt",
2502                                get_key(REQ_PROMPT));
2503                         break;
2504                 }
2505                 open_view(view, request, OPEN_DEFAULT);
2506                 break;
2508         case REQ_VIEW_STAGE:
2509                 if (!VIEW(REQ_VIEW_STAGE)->lines) {
2510                         report("No stage content, press %s to open the status view and choose file",
2511                                get_key(REQ_VIEW_STATUS));
2512                         break;
2513                 }
2514                 open_view(view, request, OPEN_DEFAULT);
2515                 break;
2517         case REQ_VIEW_STATUS:
2518                 if (opt_is_inside_work_tree == FALSE) {
2519                         report("The status view requires a working tree");
2520                         break;
2521                 }
2522                 open_view(view, request, OPEN_DEFAULT);
2523                 break;
2525         case REQ_VIEW_MAIN:
2526         case REQ_VIEW_DIFF:
2527         case REQ_VIEW_LOG:
2528         case REQ_VIEW_TREE:
2529         case REQ_VIEW_HELP:
2530                 open_view(view, request, OPEN_DEFAULT);
2531                 break;
2533         case REQ_NEXT:
2534         case REQ_PREVIOUS:
2535                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
2537                 if ((view == VIEW(REQ_VIEW_DIFF) &&
2538                      view->parent == VIEW(REQ_VIEW_MAIN)) ||
2539                    (view == VIEW(REQ_VIEW_DIFF) &&
2540                      view->parent == VIEW(REQ_VIEW_BLAME)) ||
2541                    (view == VIEW(REQ_VIEW_STAGE) &&
2542                      view->parent == VIEW(REQ_VIEW_STATUS)) ||
2543                    (view == VIEW(REQ_VIEW_BLOB) &&
2544                      view->parent == VIEW(REQ_VIEW_TREE))) {
2545                         int line;
2547                         view = view->parent;
2548                         line = view->lineno;
2549                         move_view(view, request);
2550                         if (view_is_displayed(view))
2551                                 update_view_title(view);
2552                         if (line != view->lineno)
2553                                 view->ops->request(view, REQ_ENTER,
2554                                                    &view->line[view->lineno]);
2556                 } else {
2557                         move_view(view, request);
2558                 }
2559                 break;
2561         case REQ_VIEW_NEXT:
2562         {
2563                 int nviews = displayed_views();
2564                 int next_view = (current_view + 1) % nviews;
2566                 if (next_view == current_view) {
2567                         report("Only one view is displayed");
2568                         break;
2569                 }
2571                 current_view = next_view;
2572                 /* Blur out the title of the previous view. */
2573                 update_view_title(view);
2574                 report("");
2575                 break;
2576         }
2577         case REQ_REFRESH:
2578                 report("Refreshing is not yet supported for the %s view", view->name);
2579                 break;
2581         case REQ_TOGGLE_LINENO:
2582                 opt_line_number = !opt_line_number;
2583                 redraw_display();
2584                 break;
2586         case REQ_TOGGLE_DATE:
2587                 opt_date = !opt_date;
2588                 redraw_display();
2589                 break;
2591         case REQ_TOGGLE_AUTHOR:
2592                 opt_author = !opt_author;
2593                 redraw_display();
2594                 break;
2596         case REQ_TOGGLE_REV_GRAPH:
2597                 opt_rev_graph = !opt_rev_graph;
2598                 redraw_display();
2599                 break;
2601         case REQ_TOGGLE_REFS:
2602                 opt_show_refs = !opt_show_refs;
2603                 redraw_display();
2604                 break;
2606         case REQ_PROMPT:
2607                 /* Always reload^Wrerun commands from the prompt. */
2608                 open_view(view, opt_request, OPEN_RELOAD);
2609                 break;
2611         case REQ_SEARCH:
2612         case REQ_SEARCH_BACK:
2613                 search_view(view, request);
2614                 break;
2616         case REQ_FIND_NEXT:
2617         case REQ_FIND_PREV:
2618                 find_next(view, request);
2619                 break;
2621         case REQ_STOP_LOADING:
2622                 for (i = 0; i < ARRAY_SIZE(views); i++) {
2623                         view = &views[i];
2624                         if (view->pipe)
2625                                 report("Stopped loading the %s view", view->name),
2626                         end_update(view);
2627                 }
2628                 break;
2630         case REQ_SHOW_VERSION:
2631                 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
2632                 return TRUE;
2634         case REQ_SCREEN_RESIZE:
2635                 resize_display();
2636                 /* Fall-through */
2637         case REQ_SCREEN_REDRAW:
2638                 redraw_display();
2639                 break;
2641         case REQ_EDIT:
2642                 report("Nothing to edit");
2643                 break;
2646         case REQ_ENTER:
2647                 report("Nothing to enter");
2648                 break;
2651         case REQ_VIEW_CLOSE:
2652                 /* XXX: Mark closed views by letting view->parent point to the
2653                  * view itself. Parents to closed view should never be
2654                  * followed. */
2655                 if (view->parent &&
2656                     view->parent->parent != view->parent) {
2657                         memset(display, 0, sizeof(display));
2658                         current_view = 0;
2659                         display[current_view] = view->parent;
2660                         view->parent = view;
2661                         resize_display();
2662                         redraw_display();
2663                         break;
2664                 }
2665                 /* Fall-through */
2666         case REQ_QUIT:
2667                 return FALSE;
2669         default:
2670                 /* An unknown key will show most commonly used commands. */
2671                 report("Unknown key, press 'h' for help");
2672                 return TRUE;
2673         }
2675         return TRUE;
2679 /*
2680  * Pager backend
2681  */
2683 static bool
2684 pager_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
2686         char *text = line->data;
2687         enum line_type type = line->type;
2688         int attr;
2690         wmove(view->win, lineno, 0);
2692         if (selected) {
2693                 type = LINE_CURSOR;
2694                 wchgat(view->win, -1, 0, type, NULL);
2695         }
2697         attr = get_line_attr(type);
2698         wattrset(view->win, attr);
2700         if (opt_line_number || opt_tab_size < TABSIZE) {
2701                 static char spaces[] = "                    ";
2702                 int col_offset = 0, col = 0;
2704                 if (opt_line_number) {
2705                         unsigned long real_lineno = view->offset + lineno + 1;
2707                         if (real_lineno == 1 ||
2708                             (real_lineno % opt_num_interval) == 0) {
2709                                 wprintw(view->win, "%.*d", view->digits, real_lineno);
2711                         } else {
2712                                 waddnstr(view->win, spaces,
2713                                          MIN(view->digits, STRING_SIZE(spaces)));
2714                         }
2715                         waddstr(view->win, ": ");
2716                         col_offset = view->digits + 2;
2717                 }
2719                 while (text && col_offset + col < view->width) {
2720                         int cols_max = view->width - col_offset - col;
2721                         char *pos = text;
2722                         int cols;
2724                         if (*text == '\t') {
2725                                 text++;
2726                                 assert(sizeof(spaces) > TABSIZE);
2727                                 pos = spaces;
2728                                 cols = opt_tab_size - (col % opt_tab_size);
2730                         } else {
2731                                 text = strchr(text, '\t');
2732                                 cols = line ? text - pos : strlen(pos);
2733                         }
2735                         waddnstr(view->win, pos, MIN(cols, cols_max));
2736                         col += cols;
2737                 }
2739         } else {
2740                 draw_text(view, text, view->width, TRUE, selected);
2741         }
2743         return TRUE;
2746 static bool
2747 add_describe_ref(char *buf, size_t *bufpos, char *commit_id, const char *sep)
2749         char refbuf[SIZEOF_STR];
2750         char *ref = NULL;
2751         FILE *pipe;
2753         if (!string_format(refbuf, "git describe %s 2>/dev/null", commit_id))
2754                 return TRUE;
2756         pipe = popen(refbuf, "r");
2757         if (!pipe)
2758                 return TRUE;
2760         if ((ref = fgets(refbuf, sizeof(refbuf), pipe)))
2761                 ref = chomp_string(ref);
2762         pclose(pipe);
2764         if (!ref || !*ref)
2765                 return TRUE;
2767         /* This is the only fatal call, since it can "corrupt" the buffer. */
2768         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
2769                 return FALSE;
2771         return TRUE;
2774 static void
2775 add_pager_refs(struct view *view, struct line *line)
2777         char buf[SIZEOF_STR];
2778         char *commit_id = (char *)line->data + STRING_SIZE("commit ");
2779         struct ref **refs;
2780         size_t bufpos = 0, refpos = 0;
2781         const char *sep = "Refs: ";
2782         bool is_tag = FALSE;
2784         assert(line->type == LINE_COMMIT);
2786         refs = get_refs(commit_id);
2787         if (!refs) {
2788                 if (view == VIEW(REQ_VIEW_DIFF))
2789                         goto try_add_describe_ref;
2790                 return;
2791         }
2793         do {
2794                 struct ref *ref = refs[refpos];
2795                 char *fmt = ref->tag    ? "%s[%s]" :
2796                             ref->remote ? "%s<%s>" : "%s%s";
2798                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
2799                         return;
2800                 sep = ", ";
2801                 if (ref->tag)
2802                         is_tag = TRUE;
2803         } while (refs[refpos++]->next);
2805         if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
2806 try_add_describe_ref:
2807                 /* Add <tag>-g<commit_id> "fake" reference. */
2808                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
2809                         return;
2810         }
2812         if (bufpos == 0)
2813                 return;
2815         if (!realloc_lines(view, view->line_size + 1))
2816                 return;
2818         add_line_text(view, buf, LINE_PP_REFS);
2821 static bool
2822 pager_read(struct view *view, char *data)
2824         struct line *line;
2826         if (!data)
2827                 return TRUE;
2829         line = add_line_text(view, data, get_line_type(data));
2830         if (!line)
2831                 return FALSE;
2833         if (line->type == LINE_COMMIT &&
2834             (view == VIEW(REQ_VIEW_DIFF) ||
2835              view == VIEW(REQ_VIEW_LOG)))
2836                 add_pager_refs(view, line);
2838         return TRUE;
2841 static enum request
2842 pager_request(struct view *view, enum request request, struct line *line)
2844         int split = 0;
2846         if (request != REQ_ENTER)
2847                 return request;
2849         if (line->type == LINE_COMMIT &&
2850            (view == VIEW(REQ_VIEW_LOG) ||
2851             view == VIEW(REQ_VIEW_PAGER))) {
2852                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
2853                 split = 1;
2854         }
2856         /* Always scroll the view even if it was split. That way
2857          * you can use Enter to scroll through the log view and
2858          * split open each commit diff. */
2859         scroll_view(view, REQ_SCROLL_LINE_DOWN);
2861         /* FIXME: A minor workaround. Scrolling the view will call report("")
2862          * but if we are scrolling a non-current view this won't properly
2863          * update the view title. */
2864         if (split)
2865                 update_view_title(view);
2867         return REQ_NONE;
2870 static bool
2871 pager_grep(struct view *view, struct line *line)
2873         regmatch_t pmatch;
2874         char *text = line->data;
2876         if (!*text)
2877                 return FALSE;
2879         if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
2880                 return FALSE;
2882         return TRUE;
2885 static void
2886 pager_select(struct view *view, struct line *line)
2888         if (line->type == LINE_COMMIT) {
2889                 char *text = (char *)line->data + STRING_SIZE("commit ");
2891                 if (view != VIEW(REQ_VIEW_PAGER))
2892                         string_copy_rev(view->ref, text);
2893                 string_copy_rev(ref_commit, text);
2894         }
2897 static struct view_ops pager_ops = {
2898         "line",
2899         NULL,
2900         pager_read,
2901         pager_draw,
2902         pager_request,
2903         pager_grep,
2904         pager_select,
2905 };
2908 /*
2909  * Help backend
2910  */
2912 static bool
2913 help_open(struct view *view)
2915         char buf[BUFSIZ];
2916         int lines = ARRAY_SIZE(req_info) + 2;
2917         int i;
2919         if (view->lines > 0)
2920                 return TRUE;
2922         for (i = 0; i < ARRAY_SIZE(req_info); i++)
2923                 if (!req_info[i].request)
2924                         lines++;
2926         lines += run_requests + 1;
2928         view->line = calloc(lines, sizeof(*view->line));
2929         if (!view->line)
2930                 return FALSE;
2932         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
2934         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
2935                 char *key;
2937                 if (req_info[i].request == REQ_NONE)
2938                         continue;
2940                 if (!req_info[i].request) {
2941                         add_line_text(view, "", LINE_DEFAULT);
2942                         add_line_text(view, req_info[i].help, LINE_DEFAULT);
2943                         continue;
2944                 }
2946                 key = get_key(req_info[i].request);
2947                 if (!*key)
2948                         key = "(no key defined)";
2950                 if (!string_format(buf, "    %-25s %s", key, req_info[i].help))
2951                         continue;
2953                 add_line_text(view, buf, LINE_DEFAULT);
2954         }
2956         if (run_requests) {
2957                 add_line_text(view, "", LINE_DEFAULT);
2958                 add_line_text(view, "External commands:", LINE_DEFAULT);
2959         }
2961         for (i = 0; i < run_requests; i++) {
2962                 struct run_request *req = get_run_request(REQ_NONE + i + 1);
2963                 char *key;
2965                 if (!req)
2966                         continue;
2968                 key = get_key_name(req->key);
2969                 if (!*key)
2970                         key = "(no key defined)";
2972                 if (!string_format(buf, "    %-10s %-14s `%s`",
2973                                    keymap_table[req->keymap].name,
2974                                    key, req->cmd))
2975                         continue;
2977                 add_line_text(view, buf, LINE_DEFAULT);
2978         }
2980         return TRUE;
2983 static struct view_ops help_ops = {
2984         "line",
2985         help_open,
2986         NULL,
2987         pager_draw,
2988         pager_request,
2989         pager_grep,
2990         pager_select,
2991 };
2994 /*
2995  * Tree backend
2996  */
2998 struct tree_stack_entry {
2999         struct tree_stack_entry *prev;  /* Entry below this in the stack */
3000         unsigned long lineno;           /* Line number to restore */
3001         char *name;                     /* Position of name in opt_path */
3002 };
3004 /* The top of the path stack. */
3005 static struct tree_stack_entry *tree_stack = NULL;
3006 unsigned long tree_lineno = 0;
3008 static void
3009 pop_tree_stack_entry(void)
3011         struct tree_stack_entry *entry = tree_stack;
3013         tree_lineno = entry->lineno;
3014         entry->name[0] = 0;
3015         tree_stack = entry->prev;
3016         free(entry);
3019 static void
3020 push_tree_stack_entry(char *name, unsigned long lineno)
3022         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
3023         size_t pathlen = strlen(opt_path);
3025         if (!entry)
3026                 return;
3028         entry->prev = tree_stack;
3029         entry->name = opt_path + pathlen;
3030         tree_stack = entry;
3032         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
3033                 pop_tree_stack_entry();
3034                 return;
3035         }
3037         /* Move the current line to the first tree entry. */
3038         tree_lineno = 1;
3039         entry->lineno = lineno;
3042 /* Parse output from git-ls-tree(1):
3043  *
3044  * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile
3045  * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README
3046  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
3047  * 100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38 web.conf
3048  */
3050 #define SIZEOF_TREE_ATTR \
3051         STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t")
3053 #define TREE_UP_FORMAT "040000 tree %s\t.."
3055 static int
3056 tree_compare_entry(enum line_type type1, char *name1,
3057                    enum line_type type2, char *name2)
3059         if (type1 != type2) {
3060                 if (type1 == LINE_TREE_DIR)
3061                         return -1;
3062                 return 1;
3063         }
3065         return strcmp(name1, name2);
3068 static char *
3069 tree_path(struct line *line)
3071         char *path = line->data;
3073         return path + SIZEOF_TREE_ATTR;
3076 static bool
3077 tree_read(struct view *view, char *text)
3079         size_t textlen = text ? strlen(text) : 0;
3080         char buf[SIZEOF_STR];
3081         unsigned long pos;
3082         enum line_type type;
3083         bool first_read = view->lines == 0;
3085         if (!text)
3086                 return TRUE;
3087         if (textlen <= SIZEOF_TREE_ATTR)
3088                 return FALSE;
3090         type = text[STRING_SIZE("100644 ")] == 't'
3091              ? LINE_TREE_DIR : LINE_TREE_FILE;
3093         if (first_read) {
3094                 /* Add path info line */
3095                 if (!string_format(buf, "Directory path /%s", opt_path) ||
3096                     !realloc_lines(view, view->line_size + 1) ||
3097                     !add_line_text(view, buf, LINE_DEFAULT))
3098                         return FALSE;
3100                 /* Insert "link" to parent directory. */
3101                 if (*opt_path) {
3102                         if (!string_format(buf, TREE_UP_FORMAT, view->ref) ||
3103                             !realloc_lines(view, view->line_size + 1) ||
3104                             !add_line_text(view, buf, LINE_TREE_DIR))
3105                                 return FALSE;
3106                 }
3107         }
3109         /* Strip the path part ... */
3110         if (*opt_path) {
3111                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
3112                 size_t striplen = strlen(opt_path);
3113                 char *path = text + SIZEOF_TREE_ATTR;
3115                 if (pathlen > striplen)
3116                         memmove(path, path + striplen,
3117                                 pathlen - striplen + 1);
3118         }
3120         /* Skip "Directory ..." and ".." line. */
3121         for (pos = 1 + !!*opt_path; pos < view->lines; pos++) {
3122                 struct line *line = &view->line[pos];
3123                 char *path1 = tree_path(line);
3124                 char *path2 = text + SIZEOF_TREE_ATTR;
3125                 int cmp = tree_compare_entry(line->type, path1, type, path2);
3127                 if (cmp <= 0)
3128                         continue;
3130                 text = strdup(text);
3131                 if (!text)
3132                         return FALSE;
3134                 if (view->lines > pos)
3135                         memmove(&view->line[pos + 1], &view->line[pos],
3136                                 (view->lines - pos) * sizeof(*line));
3138                 line = &view->line[pos];
3139                 line->data = text;
3140                 line->type = type;
3141                 view->lines++;
3142                 return TRUE;
3143         }
3145         if (!add_line_text(view, text, type))
3146                 return FALSE;
3148         if (tree_lineno > view->lineno) {
3149                 view->lineno = tree_lineno;
3150                 tree_lineno = 0;
3151         }
3153         return TRUE;
3156 static enum request
3157 tree_request(struct view *view, enum request request, struct line *line)
3159         enum open_flags flags;
3161         if (request == REQ_VIEW_BLAME) {
3162                 char *filename = tree_path(line);
3164                 if (line->type == LINE_TREE_DIR) {
3165                         report("Cannot show blame for directory %s", opt_path);
3166                         return REQ_NONE;
3167                 }
3169                 string_copy(opt_ref, view->vid);
3170                 string_format(opt_file, "%s%s", opt_path, filename);
3171                 return request;
3172         }
3173         if (request == REQ_TREE_PARENT) {
3174                 if (*opt_path) {
3175                         /* fake 'cd  ..' */
3176                         request = REQ_ENTER;
3177                         line = &view->line[1];
3178                 } else {
3179                         /* quit view if at top of tree */
3180                         return REQ_VIEW_CLOSE;
3181                 }
3182         }
3183         if (request != REQ_ENTER)
3184                 return request;
3186         /* Cleanup the stack if the tree view is at a different tree. */
3187         while (!*opt_path && tree_stack)
3188                 pop_tree_stack_entry();
3190         switch (line->type) {
3191         case LINE_TREE_DIR:
3192                 /* Depending on whether it is a subdir or parent (updir?) link
3193                  * mangle the path buffer. */
3194                 if (line == &view->line[1] && *opt_path) {
3195                         pop_tree_stack_entry();
3197                 } else {
3198                         char *basename = tree_path(line);
3200                         push_tree_stack_entry(basename, view->lineno);
3201                 }
3203                 /* Trees and subtrees share the same ID, so they are not not
3204                  * unique like blobs. */
3205                 flags = OPEN_RELOAD;
3206                 request = REQ_VIEW_TREE;
3207                 break;
3209         case LINE_TREE_FILE:
3210                 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
3211                 request = REQ_VIEW_BLOB;
3212                 break;
3214         default:
3215                 return TRUE;
3216         }
3218         open_view(view, request, flags);
3219         if (request == REQ_VIEW_TREE) {
3220                 view->lineno = tree_lineno;
3221         }
3223         return REQ_NONE;
3226 static void
3227 tree_select(struct view *view, struct line *line)
3229         char *text = (char *)line->data + STRING_SIZE("100644 blob ");
3231         if (line->type == LINE_TREE_FILE) {
3232                 string_copy_rev(ref_blob, text);
3234         } else if (line->type != LINE_TREE_DIR) {
3235                 return;
3236         }
3238         string_copy_rev(view->ref, text);
3241 static struct view_ops tree_ops = {
3242         "file",
3243         NULL,
3244         tree_read,
3245         pager_draw,
3246         tree_request,
3247         pager_grep,
3248         tree_select,
3249 };
3251 static bool
3252 blob_read(struct view *view, char *line)
3254         if (!line)
3255                 return TRUE;
3256         return add_line_text(view, line, LINE_DEFAULT) != NULL;
3259 static struct view_ops blob_ops = {
3260         "line",
3261         NULL,
3262         blob_read,
3263         pager_draw,
3264         pager_request,
3265         pager_grep,
3266         pager_select,
3267 };
3269 /*
3270  * Blame backend
3271  *
3272  * Loading the blame view is a two phase job:
3273  *
3274  *  1. File content is read either using opt_file from the
3275  *     filesystem or using git-cat-file.
3276  *  2. Then blame information is incrementally added by
3277  *     reading output from git-blame.
3278  */
3280 struct blame_commit {
3281         char id[SIZEOF_REV];            /* SHA1 ID. */
3282         char title[128];                /* First line of the commit message. */
3283         char author[75];                /* Author of the commit. */
3284         struct tm time;                 /* Date from the author ident. */
3285         char filename[128];             /* Name of file. */
3286 };
3288 struct blame {
3289         struct blame_commit *commit;
3290         unsigned int header:1;
3291         char text[1];
3292 };
3294 #define BLAME_CAT_FILE_CMD "git cat-file blob %s:%s"
3295 #define BLAME_INCREMENTAL_CMD "git blame --incremental %s %s"
3297 static bool
3298 blame_open(struct view *view)
3300         char path[SIZEOF_STR];
3301         char ref[SIZEOF_STR] = "";
3303         if (sq_quote(path, 0, opt_file) >= sizeof(path))
3304                 return FALSE;
3306         if (*opt_ref && sq_quote(ref, 0, opt_ref) >= sizeof(ref))
3307                 return FALSE;
3309         if (*opt_ref) {
3310                 if (!string_format(view->cmd, BLAME_CAT_FILE_CMD, ref, path))
3311                         return FALSE;
3312         } else {
3313                 view->pipe = fopen(opt_file, "r");
3314                 if (!view->pipe &&
3315                     !string_format(view->cmd, BLAME_CAT_FILE_CMD, "HEAD", path))
3316                         return FALSE;
3317         }
3319         if (!view->pipe)
3320                 view->pipe = popen(view->cmd, "r");
3321         if (!view->pipe)
3322                 return FALSE;
3324         if (!string_format(view->cmd, BLAME_INCREMENTAL_CMD, ref, path))
3325                 return FALSE;
3327         string_format(view->ref, "%s ...", opt_file);
3328         string_copy_rev(view->vid, opt_file);
3329         set_nonblocking_input(TRUE);
3331         if (view->line) {
3332                 int i;
3334                 for (i = 0; i < view->lines; i++)
3335                         free(view->line[i].data);
3336                 free(view->line);
3337         }
3339         view->lines = view->line_alloc = view->line_size = view->lineno = 0;
3340         view->offset = view->lines  = view->lineno = 0;
3341         view->line = NULL;
3342         view->start_time = time(NULL);
3344         return TRUE;
3347 static struct blame_commit *
3348 get_blame_commit(struct view *view, const char *id)
3350         size_t i;
3352         for (i = 0; i < view->lines; i++) {
3353                 struct blame *blame = view->line[i].data;
3355                 if (!blame->commit)
3356                         continue;
3358                 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
3359                         return blame->commit;
3360         }
3362         {
3363                 struct blame_commit *commit = calloc(1, sizeof(*commit));
3365                 if (commit)
3366                         string_ncopy(commit->id, id, SIZEOF_REV);
3367                 return commit;
3368         }
3371 static bool
3372 parse_number(char **posref, size_t *number, size_t min, size_t max)
3374         char *pos = *posref;
3376         *posref = NULL;
3377         pos = strchr(pos + 1, ' ');
3378         if (!pos || !isdigit(pos[1]))
3379                 return FALSE;
3380         *number = atoi(pos + 1);
3381         if (*number < min || *number > max)
3382                 return FALSE;
3384         *posref = pos;
3385         return TRUE;
3388 static struct blame_commit *
3389 parse_blame_commit(struct view *view, char *text, int *blamed)
3391         struct blame_commit *commit;
3392         struct blame *blame;
3393         char *pos = text + SIZEOF_REV - 1;
3394         size_t lineno;
3395         size_t group;
3397         if (strlen(text) <= SIZEOF_REV || *pos != ' ')
3398                 return NULL;
3400         if (!parse_number(&pos, &lineno, 1, view->lines) ||
3401             !parse_number(&pos, &group, 1, view->lines - lineno + 1))
3402                 return NULL;
3404         commit = get_blame_commit(view, text);
3405         if (!commit)
3406                 return NULL;
3408         *blamed += group;
3409         while (group--) {
3410                 struct line *line = &view->line[lineno + group - 1];
3412                 blame = line->data;
3413                 blame->commit = commit;
3414                 line->dirty = 1;
3415         }
3416         blame->header = 1;
3418         return commit;
3421 static bool
3422 blame_read_file(struct view *view, char *line)
3424         if (!line) {
3425                 FILE *pipe = NULL;
3427                 if (view->lines > 0)
3428                         pipe = popen(view->cmd, "r");
3429                 view->cmd[0] = 0;
3430                 if (!pipe) {
3431                         report("Failed to load blame data");
3432                         return TRUE;
3433                 }
3435                 fclose(view->pipe);
3436                 view->pipe = pipe;
3437                 return FALSE;
3439         } else {
3440                 size_t linelen = strlen(line);
3441                 struct blame *blame = malloc(sizeof(*blame) + linelen);
3443                 if (!line)
3444                         return FALSE;
3446                 blame->commit = NULL;
3447                 strncpy(blame->text, line, linelen);
3448                 blame->text[linelen] = 0;
3449                 return add_line_data(view, blame, LINE_BLAME_COMMIT) != NULL;
3450         }
3453 static bool
3454 match_blame_header(const char *name, char **line)
3456         size_t namelen = strlen(name);
3457         bool matched = !strncmp(name, *line, namelen);
3459         if (matched)
3460                 *line += namelen;
3462         return matched;
3465 static bool
3466 blame_read(struct view *view, char *line)
3468         static struct blame_commit *commit = NULL;
3469         static int blamed = 0;
3470         static time_t author_time;
3472         if (*view->cmd)
3473                 return blame_read_file(view, line);
3475         if (!line) {
3476                 /* Reset all! */
3477                 commit = NULL;
3478                 blamed = 0;
3479                 string_format(view->ref, "%s", view->vid);
3480                 if (view_is_displayed(view)) {
3481                         update_view_title(view);
3482                         redraw_view_from(view, 0);
3483                 }
3484                 return TRUE;
3485         }
3487         if (!commit) {
3488                 commit = parse_blame_commit(view, line, &blamed);
3489                 string_format(view->ref, "%s %2d%%", view->vid,
3490                               blamed * 100 / view->lines);
3492         } else if (match_blame_header("author ", &line)) {
3493                 string_ncopy(commit->author, line, strlen(line));
3495         } else if (match_blame_header("author-time ", &line)) {
3496                 author_time = (time_t) atol(line);
3498         } else if (match_blame_header("author-tz ", &line)) {
3499                 long tz;
3501                 tz  = ('0' - line[1]) * 60 * 60 * 10;
3502                 tz += ('0' - line[2]) * 60 * 60;
3503                 tz += ('0' - line[3]) * 60;
3504                 tz += ('0' - line[4]) * 60;
3506                 if (line[0] == '-')
3507                         tz = -tz;
3509                 author_time -= tz;
3510                 gmtime_r(&author_time, &commit->time);
3512         } else if (match_blame_header("summary ", &line)) {
3513                 string_ncopy(commit->title, line, strlen(line));
3515         } else if (match_blame_header("filename ", &line)) {
3516                 string_ncopy(commit->filename, line, strlen(line));
3517                 commit = NULL;
3518         }
3520         return TRUE;
3523 static bool
3524 blame_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
3526         struct blame *blame = line->data;
3527         int col = 0;
3529         wmove(view->win, lineno, 0);
3531         if (selected) {
3532                 wattrset(view->win, get_line_attr(LINE_CURSOR));
3533                 wchgat(view->win, -1, 0, LINE_CURSOR, NULL);
3534         } else {
3535                 wattrset(view->win, A_NORMAL);
3536         }
3538         if (opt_date) {
3539                 int n;
3541                 if (!selected)
3542                         wattrset(view->win, get_line_attr(LINE_MAIN_DATE));
3543                 if (blame->commit) {
3544                         char buf[DATE_COLS + 1];
3545                         int timelen;
3547                         timelen = strftime(buf, sizeof(buf), DATE_FORMAT, &blame->commit->time);
3548                         n = draw_text(view, buf, view->width - col, FALSE, selected);
3549                         draw_text(view, " ", view->width - col - n, FALSE, selected);
3550                 }
3552                 col += DATE_COLS;
3553                 wmove(view->win, lineno, col);
3554                 if (col >= view->width)
3555                         return TRUE;
3556         }
3558         if (opt_author) {
3559                 int max = MIN(AUTHOR_COLS - 1, view->width - col);
3561                 if (!selected)
3562                         wattrset(view->win, get_line_attr(LINE_MAIN_AUTHOR));
3563                 if (blame->commit)
3564                         draw_text(view, blame->commit->author, max, TRUE, selected);
3565                 col += AUTHOR_COLS;
3566                 if (col >= view->width)
3567                         return TRUE;
3568                 wmove(view->win, lineno, col);
3569         }
3571         {
3572                 int max = MIN(ID_COLS - 1, view->width - col);
3574                 if (!selected)
3575                         wattrset(view->win, get_line_attr(LINE_BLAME_ID));
3576                 if (blame->commit)
3577                         draw_text(view, blame->commit->id, max, FALSE, -1);
3578                 col += ID_COLS;
3579                 if (col >= view->width)
3580                         return TRUE;
3581                 wmove(view->win, lineno, col);
3582         }
3584         {
3585                 unsigned long real_lineno = view->offset + lineno + 1;
3586                 char number[10] = "          ";
3587                 int max = MIN(view->digits, STRING_SIZE(number));
3588                 bool showtrimmed = FALSE;
3590                 if (real_lineno == 1 ||
3591                     (real_lineno % opt_num_interval) == 0) {
3592                         char fmt[] = "%1ld";
3594                         if (view->digits <= 9)
3595                                 fmt[1] = '0' + view->digits;
3597                         if (!string_format(number, fmt, real_lineno))
3598                                 number[0] = 0;
3599                         showtrimmed = TRUE;
3600                 }
3602                 if (max > view->width - col)
3603                         max = view->width - col;
3604                 if (!selected)
3605                         wattrset(view->win, get_line_attr(LINE_BLAME_LINENO));
3606                 col += draw_text(view, number, max, showtrimmed, selected);
3607                 if (col >= view->width)
3608                         return TRUE;
3609         }
3611         if (!selected)
3612                 wattrset(view->win, A_NORMAL);
3614         if (col >= view->width)
3615                 return TRUE;
3616         waddch(view->win, ACS_VLINE);
3617         col++;
3618         if (col >= view->width)
3619                 return TRUE;
3620         waddch(view->win, ' ');
3621         col++;
3622         col += draw_text(view, blame->text, view->width - col, TRUE, selected);
3624         return TRUE;
3627 static enum request
3628 blame_request(struct view *view, enum request request, struct line *line)
3630         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
3631         struct blame *blame = line->data;
3633         switch (request) {
3634         case REQ_ENTER:
3635                 if (!blame->commit) {
3636                         report("No commit loaded yet");
3637                         break;
3638                 }
3640                 if (!strcmp(blame->commit->id, "0000000000000000000000000000000000000000")) {
3641                         char path[SIZEOF_STR];
3643                         if (sq_quote(path, 0, view->vid) >= sizeof(path))
3644                                 break;
3645                         string_format(opt_cmd, "git diff-index --root --patch-with-stat -C -M --cached HEAD -- %s 2>/dev/null", path);
3646                 }
3648                 open_view(view, REQ_VIEW_DIFF, flags);
3649                 break;
3651         default:
3652                 return request;
3653         }
3655         return REQ_NONE;
3658 static bool
3659 blame_grep(struct view *view, struct line *line)
3661         struct blame *blame = line->data;
3662         struct blame_commit *commit = blame->commit;
3663         regmatch_t pmatch;
3665 #define MATCH(text) \
3666         (*text && regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
3668         if (commit) {
3669                 char buf[DATE_COLS + 1];
3671                 if (MATCH(commit->title) ||
3672                     MATCH(commit->author) ||
3673                     MATCH(commit->id))
3674                         return TRUE;
3676                 if (strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time) &&
3677                     MATCH(buf))
3678                         return TRUE;
3679         }
3681         return MATCH(blame->text);
3683 #undef MATCH
3686 static void
3687 blame_select(struct view *view, struct line *line)
3689         struct blame *blame = line->data;
3690         struct blame_commit *commit = blame->commit;
3692         if (!commit)
3693                 return;
3695         if (!strcmp(commit->id, "0000000000000000000000000000000000000000"))
3696                 string_ncopy(ref_commit, "HEAD", 4);
3697         else
3698                 string_copy_rev(ref_commit, commit->id);
3701 static struct view_ops blame_ops = {
3702         "line",
3703         blame_open,
3704         blame_read,
3705         blame_draw,
3706         blame_request,
3707         blame_grep,
3708         blame_select,
3709 };
3711 /*
3712  * Status backend
3713  */
3715 struct status {
3716         char status;
3717         struct {
3718                 mode_t mode;
3719                 char rev[SIZEOF_REV];
3720                 char name[SIZEOF_STR];
3721         } old;
3722         struct {
3723                 mode_t mode;
3724                 char rev[SIZEOF_REV];
3725                 char name[SIZEOF_STR];
3726         } new;
3727 };
3729 static char status_onbranch[SIZEOF_STR];
3730 static struct status stage_status;
3731 static enum line_type stage_line_type;
3733 /* Get fields from the diff line:
3734  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
3735  */
3736 static inline bool
3737 status_get_diff(struct status *file, char *buf, size_t bufsize)
3739         char *old_mode = buf +  1;
3740         char *new_mode = buf +  8;
3741         char *old_rev  = buf + 15;
3742         char *new_rev  = buf + 56;
3743         char *status   = buf + 97;
3745         if (bufsize < 99 ||
3746             old_mode[-1] != ':' ||
3747             new_mode[-1] != ' ' ||
3748             old_rev[-1]  != ' ' ||
3749             new_rev[-1]  != ' ' ||
3750             status[-1]   != ' ')
3751                 return FALSE;
3753         file->status = *status;
3755         string_copy_rev(file->old.rev, old_rev);
3756         string_copy_rev(file->new.rev, new_rev);
3758         file->old.mode = strtoul(old_mode, NULL, 8);
3759         file->new.mode = strtoul(new_mode, NULL, 8);
3761         file->old.name[0] = file->new.name[0] = 0;
3763         return TRUE;
3766 static bool
3767 status_run(struct view *view, const char cmd[], bool diff, enum line_type type)
3769         struct status *file = NULL;
3770         struct status *unmerged = NULL;
3771         char buf[SIZEOF_STR * 4];
3772         size_t bufsize = 0;
3773         FILE *pipe;
3775         pipe = popen(cmd, "r");
3776         if (!pipe)
3777                 return FALSE;
3779         add_line_data(view, NULL, type);
3781         while (!feof(pipe) && !ferror(pipe)) {
3782                 char *sep;
3783                 size_t readsize;
3785                 readsize = fread(buf + bufsize, 1, sizeof(buf) - bufsize, pipe);
3786                 if (!readsize)
3787                         break;
3788                 bufsize += readsize;
3790                 /* Process while we have NUL chars. */
3791                 while ((sep = memchr(buf, 0, bufsize))) {
3792                         size_t sepsize = sep - buf + 1;
3794                         if (!file) {
3795                                 if (!realloc_lines(view, view->line_size + 1))
3796                                         goto error_out;
3798                                 file = calloc(1, sizeof(*file));
3799                                 if (!file)
3800                                         goto error_out;
3802                                 add_line_data(view, file, type);
3803                         }
3805                         /* Parse diff info part. */
3806                         if (!diff) {
3807                                 file->status = '?';
3809                         } else if (!file->status) {
3810                                 if (!status_get_diff(file, buf, sepsize))
3811                                         goto error_out;
3813                                 bufsize -= sepsize;
3814                                 memmove(buf, sep + 1, bufsize);
3816                                 sep = memchr(buf, 0, bufsize);
3817                                 if (!sep)
3818                                         break;
3819                                 sepsize = sep - buf + 1;
3821                                 /* Collapse all 'M'odified entries that
3822                                  * follow a associated 'U'nmerged entry.
3823                                  */
3824                                 if (file->status == 'U') {
3825                                         unmerged = file;
3827                                 } else if (unmerged) {
3828                                         int collapse = !strcmp(buf, unmerged->new.name);
3830                                         unmerged = NULL;
3831                                         if (collapse) {
3832                                                 free(file);
3833                                                 view->lines--;
3834                                                 continue;
3835                                         }
3836                                 }
3837                         }
3839                         /* Grab the old name for rename/copy. */
3840                         if (!*file->old.name &&
3841                             (file->status == 'R' || file->status == 'C')) {
3842                                 sepsize = sep - buf + 1;
3843                                 string_ncopy(file->old.name, buf, sepsize);
3844                                 bufsize -= sepsize;
3845                                 memmove(buf, sep + 1, bufsize);
3847                                 sep = memchr(buf, 0, bufsize);
3848                                 if (!sep)
3849                                         break;
3850                                 sepsize = sep - buf + 1;
3851                         }
3853                         /* git-ls-files just delivers a NUL separated
3854                          * list of file names similar to the second half
3855                          * of the git-diff-* output. */
3856                         string_ncopy(file->new.name, buf, sepsize);
3857                         if (!*file->old.name)
3858                                 string_copy(file->old.name, file->new.name);
3859                         bufsize -= sepsize;
3860                         memmove(buf, sep + 1, bufsize);
3861                         file = NULL;
3862                 }
3863         }
3865         if (ferror(pipe)) {
3866 error_out:
3867                 pclose(pipe);
3868                 return FALSE;
3869         }
3871         if (!view->line[view->lines - 1].data)
3872                 add_line_data(view, NULL, LINE_STAT_NONE);
3874         pclose(pipe);
3875         return TRUE;
3878 /* Don't show unmerged entries in the staged section. */
3879 #define STATUS_DIFF_INDEX_CMD "git diff-index -z --diff-filter=ACDMRTXB --cached -M HEAD"
3880 #define STATUS_DIFF_FILES_CMD "git diff-files -z"
3881 #define STATUS_LIST_OTHER_CMD \
3882         "git ls-files -z --others --exclude-per-directory=.gitignore"
3884 #define STATUS_DIFF_INDEX_SHOW_CMD \
3885         "git diff-index --root --patch-with-stat -C -M --cached HEAD -- %s %s 2>/dev/null"
3887 #define STATUS_DIFF_FILES_SHOW_CMD \
3888         "git diff-files --root --patch-with-stat -C -M -- %s %s 2>/dev/null"
3890 /* First parse staged info using git-diff-index(1), then parse unstaged
3891  * info using git-diff-files(1), and finally untracked files using
3892  * git-ls-files(1). */
3893 static bool
3894 status_open(struct view *view)
3896         struct stat statbuf;
3897         char exclude[SIZEOF_STR];
3898         char cmd[SIZEOF_STR];
3899         unsigned long prev_lineno = view->lineno;
3900         size_t i;
3902         for (i = 0; i < view->lines; i++)
3903                 free(view->line[i].data);
3904         free(view->line);
3905         view->lines = view->line_alloc = view->line_size = view->lineno = 0;
3906         view->line = NULL;
3908         if (!realloc_lines(view, view->line_size + 7))
3909                 return FALSE;
3911         add_line_data(view, NULL, LINE_STAT_HEAD);
3912         if (!*opt_head)
3913                 string_copy(status_onbranch, "Not currently on any branch");
3914         else if (!string_format(status_onbranch, "On branch %s", opt_head))
3915                 return FALSE;
3917         if (!string_format(exclude, "%s/info/exclude", opt_git_dir))
3918                 return FALSE;
3920         string_copy(cmd, STATUS_LIST_OTHER_CMD);
3922         if (stat(exclude, &statbuf) >= 0) {
3923                 size_t cmdsize = strlen(cmd);
3925                 if (!string_format_from(cmd, &cmdsize, " %s", "--exclude-from=") ||
3926                     sq_quote(cmd, cmdsize, exclude) >= sizeof(cmd))
3927                         return FALSE;
3928         }
3930         system("git update-index -q --refresh");
3932         if (!status_run(view, STATUS_DIFF_INDEX_CMD, TRUE, LINE_STAT_STAGED) ||
3933             !status_run(view, STATUS_DIFF_FILES_CMD, TRUE, LINE_STAT_UNSTAGED) ||
3934             !status_run(view, cmd, FALSE, LINE_STAT_UNTRACKED))
3935                 return FALSE;
3937         /* If all went well restore the previous line number to stay in
3938          * the context or select a line with something that can be
3939          * updated. */
3940         if (prev_lineno >= view->lines)
3941                 prev_lineno = view->lines - 1;
3942         while (prev_lineno < view->lines && !view->line[prev_lineno].data)
3943                 prev_lineno++;
3945         /* If the above fails, always skip the "On branch" line. */
3946         if (prev_lineno < view->lines)
3947                 view->lineno = prev_lineno;
3948         else
3949                 view->lineno = 1;
3951         return TRUE;
3954 static bool
3955 status_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
3957         struct status *status = line->data;
3959         wmove(view->win, lineno, 0);
3961         if (selected) {
3962                 wattrset(view->win, get_line_attr(LINE_CURSOR));
3963                 wchgat(view->win, -1, 0, LINE_CURSOR, NULL);
3965         } else if (line->type == LINE_STAT_HEAD) {
3966                 wattrset(view->win, get_line_attr(LINE_STAT_HEAD));
3967                 wchgat(view->win, -1, 0, LINE_STAT_HEAD, NULL);
3969         } else if (!status && line->type != LINE_STAT_NONE) {
3970                 wattrset(view->win, get_line_attr(LINE_STAT_SECTION));
3971                 wchgat(view->win, -1, 0, LINE_STAT_SECTION, NULL);
3973         } else {
3974                 wattrset(view->win, get_line_attr(line->type));
3975         }
3977         if (!status) {
3978                 char *text;
3980                 switch (line->type) {
3981                 case LINE_STAT_STAGED:
3982                         text = "Changes to be committed:";
3983                         break;
3985                 case LINE_STAT_UNSTAGED:
3986                         text = "Changed but not updated:";
3987                         break;
3989                 case LINE_STAT_UNTRACKED:
3990                         text = "Untracked files:";
3991                         break;
3993                 case LINE_STAT_NONE:
3994                         text = "    (no files)";
3995                         break;
3997                 case LINE_STAT_HEAD:
3998                         text = status_onbranch;
3999                         break;
4001                 default:
4002                         return FALSE;
4003                 }
4005                 draw_text(view, text, view->width, TRUE, selected);
4006                 return TRUE;
4007         }
4009         waddch(view->win, status->status);
4010         if (!selected)
4011                 wattrset(view->win, A_NORMAL);
4012         wmove(view->win, lineno, 4);
4013         if (view->width < 5)
4014                 return TRUE;
4016         draw_text(view, status->new.name, view->width - 5, TRUE, selected);
4017         return TRUE;
4020 static enum request
4021 status_enter(struct view *view, struct line *line)
4023         struct status *status = line->data;
4024         char oldpath[SIZEOF_STR] = "";
4025         char newpath[SIZEOF_STR] = "";
4026         char *info;
4027         size_t cmdsize = 0;
4029         if (line->type == LINE_STAT_NONE ||
4030             (!status && line[1].type == LINE_STAT_NONE)) {
4031                 report("No file to diff");
4032                 return REQ_NONE;
4033         }
4035         if (status) {
4036                 if (sq_quote(oldpath, 0, status->old.name) >= sizeof(oldpath))
4037                         return REQ_QUIT;
4038                 /* Diffs for unmerged entries are empty when pasing the
4039                  * new path, so leave it empty. */
4040                 if (status->status != 'U' &&
4041                     sq_quote(newpath, 0, status->new.name) >= sizeof(newpath))
4042                         return REQ_QUIT;
4043         }
4045         if (opt_cdup[0] &&
4046             line->type != LINE_STAT_UNTRACKED &&
4047             !string_format_from(opt_cmd, &cmdsize, "cd %s;", opt_cdup))
4048                 return REQ_QUIT;
4050         switch (line->type) {
4051         case LINE_STAT_STAGED:
4052                 if (!string_format_from(opt_cmd, &cmdsize,
4053                                         STATUS_DIFF_INDEX_SHOW_CMD, oldpath, newpath))
4054                         return REQ_QUIT;
4055                 if (status)
4056                         info = "Staged changes to %s";
4057                 else
4058                         info = "Staged changes";
4059                 break;
4061         case LINE_STAT_UNSTAGED:
4062                 if (!string_format_from(opt_cmd, &cmdsize,
4063                                         STATUS_DIFF_FILES_SHOW_CMD, oldpath, newpath))
4064                         return REQ_QUIT;
4065                 if (status)
4066                         info = "Unstaged changes to %s";
4067                 else
4068                         info = "Unstaged changes";
4069                 break;
4071         case LINE_STAT_UNTRACKED:
4072                 if (opt_pipe)
4073                         return REQ_QUIT;
4076                 if (!status) {
4077                         report("No file to show");
4078                         return REQ_NONE;
4079                 }
4081                 opt_pipe = fopen(status->new.name, "r");
4082                 info = "Untracked file %s";
4083                 break;
4085         case LINE_STAT_HEAD:
4086                 return REQ_NONE;
4088         default:
4089                 die("line type %d not handled in switch", line->type);
4090         }
4092         open_view(view, REQ_VIEW_STAGE, OPEN_RELOAD | OPEN_SPLIT);
4093         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
4094                 if (status) {
4095                         stage_status = *status;
4096                 } else {
4097                         memset(&stage_status, 0, sizeof(stage_status));
4098                 }
4100                 stage_line_type = line->type;
4101                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
4102         }
4104         return REQ_NONE;
4108 static bool
4109 status_update_file(struct view *view, struct status *status, enum line_type type)
4111         char cmd[SIZEOF_STR];
4112         char buf[SIZEOF_STR];
4113         size_t cmdsize = 0;
4114         size_t bufsize = 0;
4115         size_t written = 0;
4116         FILE *pipe;
4118         if (opt_cdup[0] &&
4119             type != LINE_STAT_UNTRACKED &&
4120             !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
4121                 return FALSE;
4123         switch (type) {
4124         case LINE_STAT_STAGED:
4125                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
4126                                         status->old.mode,
4127                                         status->old.rev,
4128                                         status->old.name, 0))
4129                         return FALSE;
4131                 string_add(cmd, cmdsize, "git update-index -z --index-info");
4132                 break;
4134         case LINE_STAT_UNSTAGED:
4135         case LINE_STAT_UNTRACKED:
4136                 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
4137                         return FALSE;
4139                 string_add(cmd, cmdsize, "git update-index -z --add --remove --stdin");
4140                 break;
4142         case LINE_STAT_HEAD:
4143                 return TRUE;
4145         default:
4146                 die("line type %d not handled in switch", type);
4147         }
4149         pipe = popen(cmd, "w");
4150         if (!pipe)
4151                 return FALSE;
4153         while (!ferror(pipe) && written < bufsize) {
4154                 written += fwrite(buf + written, 1, bufsize - written, pipe);
4155         }
4157         pclose(pipe);
4159         if (written != bufsize)
4160                 return FALSE;
4162         return TRUE;
4165 static void
4166 status_update(struct view *view)
4168         struct line *line = &view->line[view->lineno];
4170         assert(view->lines);
4172         if (!line->data) {
4173                 while (++line < view->line + view->lines && line->data) {
4174                         if (!status_update_file(view, line->data, line->type))
4175                                 report("Failed to update file status");
4176                 }
4178                 if (!line[-1].data) {
4179                         report("Nothing to update");
4180                         return;
4181                 }
4183         } else if (!status_update_file(view, line->data, line->type)) {
4184                 report("Failed to update file status");
4185         }
4188 static enum request
4189 status_request(struct view *view, enum request request, struct line *line)
4191         struct status *status = line->data;
4193         switch (request) {
4194         case REQ_STATUS_UPDATE:
4195                 status_update(view);
4196                 break;
4198         case REQ_STATUS_MERGE:
4199                 if (!status || status->status != 'U') {
4200                         report("Merging only possible for files with unmerged status ('U').");
4201                         return REQ_NONE;
4202                 }
4203                 open_mergetool(status->new.name);
4204                 break;
4206         case REQ_EDIT:
4207                 if (!status)
4208                         return request;
4210                 open_editor(status->status != '?', status->new.name);
4211                 break;
4213         case REQ_VIEW_BLAME:
4214                 if (status) {
4215                         string_copy(opt_file, status->new.name);
4216                         opt_ref[0] = 0;
4217                 }
4218                 return request;
4220         case REQ_ENTER:
4221                 /* After returning the status view has been split to
4222                  * show the stage view. No further reloading is
4223                  * necessary. */
4224                 status_enter(view, line);
4225                 return REQ_NONE;
4227         case REQ_REFRESH:
4228                 /* Simply reload the view. */
4229                 break;
4231         default:
4232                 return request;
4233         }
4235         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
4237         return REQ_NONE;
4240 static void
4241 status_select(struct view *view, struct line *line)
4243         struct status *status = line->data;
4244         char file[SIZEOF_STR] = "all files";
4245         char *text;
4246         char *key;
4248         if (status && !string_format(file, "'%s'", status->new.name))
4249                 return;
4251         if (!status && line[1].type == LINE_STAT_NONE)
4252                 line++;
4254         switch (line->type) {
4255         case LINE_STAT_STAGED:
4256                 text = "Press %s to unstage %s for commit";
4257                 break;
4259         case LINE_STAT_UNSTAGED:
4260                 text = "Press %s to stage %s for commit";
4261                 break;
4263         case LINE_STAT_UNTRACKED:
4264                 text = "Press %s to stage %s for addition";
4265                 break;
4267         case LINE_STAT_HEAD:
4268         case LINE_STAT_NONE:
4269                 text = "Nothing to update";
4270                 break;
4272         default:
4273                 die("line type %d not handled in switch", line->type);
4274         }
4276         if (status && status->status == 'U') {
4277                 text = "Press %s to resolve conflict in %s";
4278                 key = get_key(REQ_STATUS_MERGE);
4280         } else {
4281                 key = get_key(REQ_STATUS_UPDATE);
4282         }
4284         string_format(view->ref, text, key, file);
4287 static bool
4288 status_grep(struct view *view, struct line *line)
4290         struct status *status = line->data;
4291         enum { S_STATUS, S_NAME, S_END } state;
4292         char buf[2] = "?";
4293         regmatch_t pmatch;
4295         if (!status)
4296                 return FALSE;
4298         for (state = S_STATUS; state < S_END; state++) {
4299                 char *text;
4301                 switch (state) {
4302                 case S_NAME:    text = status->new.name;        break;
4303                 case S_STATUS:
4304                         buf[0] = status->status;
4305                         text = buf;
4306                         break;
4308                 default:
4309                         return FALSE;
4310                 }
4312                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4313                         return TRUE;
4314         }
4316         return FALSE;
4319 static struct view_ops status_ops = {
4320         "file",
4321         status_open,
4322         NULL,
4323         status_draw,
4324         status_request,
4325         status_grep,
4326         status_select,
4327 };
4330 static bool
4331 stage_diff_line(FILE *pipe, struct line *line)
4333         char *buf = line->data;
4334         size_t bufsize = strlen(buf);
4335         size_t written = 0;
4337         while (!ferror(pipe) && written < bufsize) {
4338                 written += fwrite(buf + written, 1, bufsize - written, pipe);
4339         }
4341         fputc('\n', pipe);
4343         return written == bufsize;
4346 static struct line *
4347 stage_diff_hdr(struct view *view, struct line *line)
4349         int diff_hdr_dir = line->type == LINE_DIFF_CHUNK ? -1 : 1;
4350         struct line *diff_hdr;
4352         if (line->type == LINE_DIFF_CHUNK)
4353                 diff_hdr = line - 1;
4354         else
4355                 diff_hdr = view->line + 1;
4357         while (diff_hdr > view->line && diff_hdr < view->line + view->lines) {
4358                 if (diff_hdr->type == LINE_DIFF_HEADER)
4359                         return diff_hdr;
4361                 diff_hdr += diff_hdr_dir;
4362         }
4364         return NULL;
4367 static bool
4368 stage_update_chunk(struct view *view, struct line *line)
4370         char cmd[SIZEOF_STR];
4371         size_t cmdsize = 0;
4372         struct line *diff_hdr, *diff_chunk, *diff_end;
4373         FILE *pipe;
4375         diff_hdr = stage_diff_hdr(view, line);
4376         if (!diff_hdr)
4377                 return FALSE;
4379         if (opt_cdup[0] &&
4380             !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
4381                 return FALSE;
4383         if (!string_format_from(cmd, &cmdsize,
4384                                 "git apply --cached %s - && "
4385                                 "git update-index -q --unmerged --refresh 2>/dev/null",
4386                                 stage_line_type == LINE_STAT_STAGED ? "-R" : ""))
4387                 return FALSE;
4389         pipe = popen(cmd, "w");
4390         if (!pipe)
4391                 return FALSE;
4393         diff_end = view->line + view->lines;
4394         if (line->type != LINE_DIFF_CHUNK) {
4395                 diff_chunk = diff_hdr;
4397         } else {
4398                 for (diff_chunk = line + 1; diff_chunk < diff_end; diff_chunk++)
4399                         if (diff_chunk->type == LINE_DIFF_CHUNK ||
4400                             diff_chunk->type == LINE_DIFF_HEADER)
4401                                 diff_end = diff_chunk;
4403                 diff_chunk = line;
4405                 while (diff_hdr->type != LINE_DIFF_CHUNK) {
4406                         switch (diff_hdr->type) {
4407                         case LINE_DIFF_HEADER:
4408                         case LINE_DIFF_INDEX:
4409                         case LINE_DIFF_ADD:
4410                         case LINE_DIFF_DEL:
4411                                 break;
4413                         default:
4414                                 diff_hdr++;
4415                                 continue;
4416                         }
4418                         if (!stage_diff_line(pipe, diff_hdr++)) {
4419                                 pclose(pipe);
4420                                 return FALSE;
4421                         }
4422                 }
4423         }
4425         while (diff_chunk < diff_end && stage_diff_line(pipe, diff_chunk))
4426                 diff_chunk++;
4428         pclose(pipe);
4430         if (diff_chunk != diff_end)
4431                 return FALSE;
4433         return TRUE;
4436 static void
4437 stage_update(struct view *view, struct line *line)
4439         if (stage_line_type != LINE_STAT_UNTRACKED &&
4440             (line->type == LINE_DIFF_CHUNK || !stage_status.status)) {
4441                 if (!stage_update_chunk(view, line)) {
4442                         report("Failed to apply chunk");
4443                         return;
4444                 }
4446         } else if (!status_update_file(view, &stage_status, stage_line_type)) {
4447                 report("Failed to update file");
4448                 return;
4449         }
4451         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
4453         view = VIEW(REQ_VIEW_STATUS);
4454         if (view_is_displayed(view))
4455                 status_enter(view, &view->line[view->lineno]);
4458 static enum request
4459 stage_request(struct view *view, enum request request, struct line *line)
4461         switch (request) {
4462         case REQ_STATUS_UPDATE:
4463                 stage_update(view, line);
4464                 break;
4466         case REQ_EDIT:
4467                 if (!stage_status.new.name[0])
4468                         return request;
4470                 open_editor(stage_status.status != '?', stage_status.new.name);
4471                 break;
4473         case REQ_VIEW_BLAME:
4474                 if (stage_status.new.name[0]) {
4475                         string_copy(opt_file, stage_status.new.name);
4476                         opt_ref[0] = 0;
4477                 }
4478                 return request;
4480         case REQ_ENTER:
4481                 pager_request(view, request, line);
4482                 break;
4484         default:
4485                 return request;
4486         }
4488         return REQ_NONE;
4491 static struct view_ops stage_ops = {
4492         "line",
4493         NULL,
4494         pager_read,
4495         pager_draw,
4496         stage_request,
4497         pager_grep,
4498         pager_select,
4499 };
4502 /*
4503  * Revision graph
4504  */
4506 struct commit {
4507         char id[SIZEOF_REV];            /* SHA1 ID. */
4508         char title[128];                /* First line of the commit message. */
4509         char author[75];                /* Author of the commit. */
4510         struct tm time;                 /* Date from the author ident. */
4511         struct ref **refs;              /* Repository references. */
4512         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
4513         size_t graph_size;              /* The width of the graph array. */
4514         bool has_parents;               /* Rewritten --parents seen. */
4515 };
4517 /* Size of rev graph with no  "padding" columns */
4518 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
4520 struct rev_graph {
4521         struct rev_graph *prev, *next, *parents;
4522         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
4523         size_t size;
4524         struct commit *commit;
4525         size_t pos;
4526         unsigned int boundary:1;
4527 };
4529 /* Parents of the commit being visualized. */
4530 static struct rev_graph graph_parents[4];
4532 /* The current stack of revisions on the graph. */
4533 static struct rev_graph graph_stacks[4] = {
4534         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
4535         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
4536         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
4537         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
4538 };
4540 static inline bool
4541 graph_parent_is_merge(struct rev_graph *graph)
4543         return graph->parents->size > 1;
4546 static inline void
4547 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
4549         struct commit *commit = graph->commit;
4551         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
4552                 commit->graph[commit->graph_size++] = symbol;
4555 static void
4556 done_rev_graph(struct rev_graph *graph)
4558         if (graph_parent_is_merge(graph) &&
4559             graph->pos < graph->size - 1 &&
4560             graph->next->size == graph->size + graph->parents->size - 1) {
4561                 size_t i = graph->pos + graph->parents->size - 1;
4563                 graph->commit->graph_size = i * 2;
4564                 while (i < graph->next->size - 1) {
4565                         append_to_rev_graph(graph, ' ');
4566                         append_to_rev_graph(graph, '\\');
4567                         i++;
4568                 }
4569         }
4571         graph->size = graph->pos = 0;
4572         graph->commit = NULL;
4573         memset(graph->parents, 0, sizeof(*graph->parents));
4576 static void
4577 push_rev_graph(struct rev_graph *graph, char *parent)
4579         int i;
4581         /* "Collapse" duplicate parents lines.
4582          *
4583          * FIXME: This needs to also update update the drawn graph but
4584          * for now it just serves as a method for pruning graph lines. */
4585         for (i = 0; i < graph->size; i++)
4586                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
4587                         return;
4589         if (graph->size < SIZEOF_REVITEMS) {
4590                 string_copy_rev(graph->rev[graph->size++], parent);
4591         }
4594 static chtype
4595 get_rev_graph_symbol(struct rev_graph *graph)
4597         chtype symbol;
4599         if (graph->boundary)
4600                 symbol = REVGRAPH_BOUND;
4601         else if (graph->parents->size == 0)
4602                 symbol = REVGRAPH_INIT;
4603         else if (graph_parent_is_merge(graph))
4604                 symbol = REVGRAPH_MERGE;
4605         else if (graph->pos >= graph->size)
4606                 symbol = REVGRAPH_BRANCH;
4607         else
4608                 symbol = REVGRAPH_COMMIT;
4610         return symbol;
4613 static void
4614 draw_rev_graph(struct rev_graph *graph)
4616         struct rev_filler {
4617                 chtype separator, line;
4618         };
4619         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
4620         static struct rev_filler fillers[] = {
4621                 { ' ',  REVGRAPH_LINE },
4622                 { '`',  '.' },
4623                 { '\'', ' ' },
4624                 { '/',  ' ' },
4625         };
4626         chtype symbol = get_rev_graph_symbol(graph);
4627         struct rev_filler *filler;
4628         size_t i;
4630         filler = &fillers[DEFAULT];
4632         for (i = 0; i < graph->pos; i++) {
4633                 append_to_rev_graph(graph, filler->line);
4634                 if (graph_parent_is_merge(graph->prev) &&
4635                     graph->prev->pos == i)
4636                         filler = &fillers[RSHARP];
4638                 append_to_rev_graph(graph, filler->separator);
4639         }
4641         /* Place the symbol for this revision. */
4642         append_to_rev_graph(graph, symbol);
4644         if (graph->prev->size > graph->size)
4645                 filler = &fillers[RDIAG];
4646         else
4647                 filler = &fillers[DEFAULT];
4649         i++;
4651         for (; i < graph->size; i++) {
4652                 append_to_rev_graph(graph, filler->separator);
4653                 append_to_rev_graph(graph, filler->line);
4654                 if (graph_parent_is_merge(graph->prev) &&
4655                     i < graph->prev->pos + graph->parents->size)
4656                         filler = &fillers[RSHARP];
4657                 if (graph->prev->size > graph->size)
4658                         filler = &fillers[LDIAG];
4659         }
4661         if (graph->prev->size > graph->size) {
4662                 append_to_rev_graph(graph, filler->separator);
4663                 if (filler->line != ' ')
4664                         append_to_rev_graph(graph, filler->line);
4665         }
4668 /* Prepare the next rev graph */
4669 static void
4670 prepare_rev_graph(struct rev_graph *graph)
4672         size_t i;
4674         /* First, traverse all lines of revisions up to the active one. */
4675         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
4676                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
4677                         break;
4679                 push_rev_graph(graph->next, graph->rev[graph->pos]);
4680         }
4682         /* Interleave the new revision parent(s). */
4683         for (i = 0; !graph->boundary && i < graph->parents->size; i++)
4684                 push_rev_graph(graph->next, graph->parents->rev[i]);
4686         /* Lastly, put any remaining revisions. */
4687         for (i = graph->pos + 1; i < graph->size; i++)
4688                 push_rev_graph(graph->next, graph->rev[i]);
4691 static void
4692 update_rev_graph(struct rev_graph *graph)
4694         /* If this is the finalizing update ... */
4695         if (graph->commit)
4696                 prepare_rev_graph(graph);
4698         /* Graph visualization needs a one rev look-ahead,
4699          * so the first update doesn't visualize anything. */
4700         if (!graph->prev->commit)
4701                 return;
4703         draw_rev_graph(graph->prev);
4704         done_rev_graph(graph->prev->prev);
4708 /*
4709  * Main view backend
4710  */
4712 static bool
4713 main_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
4715         char buf[DATE_COLS + 1];
4716         struct commit *commit = line->data;
4717         enum line_type type;
4718         int col = 0;
4719         size_t timelen;
4720         int space;
4722         if (!*commit->author)
4723                 return FALSE;
4725         space = view->width;
4726         wmove(view->win, lineno, col);
4728         if (selected) {
4729                 type = LINE_CURSOR;
4730                 wattrset(view->win, get_line_attr(type));
4731                 wchgat(view->win, -1, 0, type, NULL);
4732         } else {
4733                 type = LINE_MAIN_COMMIT;
4734                 wattrset(view->win, get_line_attr(LINE_MAIN_DATE));
4735         }
4737         if (opt_date) {
4738                 int n;
4740                 timelen = strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time);
4741                 n = draw_text(view, buf, view->width - col, FALSE, selected);
4742                 draw_text(view, " ", view->width - col - n, FALSE, selected);
4744                 col += DATE_COLS;
4745                 wmove(view->win, lineno, col);
4746                 if (col >= view->width)
4747                         return TRUE;
4748         }
4749         if (type != LINE_CURSOR)
4750                 wattrset(view->win, get_line_attr(LINE_MAIN_AUTHOR));
4752         if (opt_author) {
4753                 int max_len;
4755                 max_len = view->width - col;
4756                 if (max_len > AUTHOR_COLS - 1)
4757                         max_len = AUTHOR_COLS - 1;
4758                 draw_text(view, commit->author, max_len, TRUE, selected);
4759                 col += AUTHOR_COLS;
4760                 if (col >= view->width)
4761                         return TRUE;
4762         }
4764         if (opt_rev_graph && commit->graph_size) {
4765                 size_t graph_size = view->width - col;
4766                 size_t i;
4768                 if (type != LINE_CURSOR)
4769                         wattrset(view->win, get_line_attr(LINE_MAIN_REVGRAPH));
4770                 wmove(view->win, lineno, col);
4771                 if (graph_size > commit->graph_size)
4772                         graph_size = commit->graph_size;
4773                 /* Using waddch() instead of waddnstr() ensures that
4774                  * they'll be rendered correctly for the cursor line. */
4775                 for (i = 0; i < graph_size; i++)
4776                         waddch(view->win, commit->graph[i]);
4778                 col += commit->graph_size + 1;
4779                 if (col >= view->width)
4780                         return TRUE;
4781                 waddch(view->win, ' ');
4782         }
4783         if (type != LINE_CURSOR)
4784                 wattrset(view->win, A_NORMAL);
4786         wmove(view->win, lineno, col);
4788         if (opt_show_refs && commit->refs) {
4789                 size_t i = 0;
4791                 do {
4792                         if (type == LINE_CURSOR)
4793                                 ;
4794                         else if (commit->refs[i]->head)
4795                                 wattrset(view->win, get_line_attr(LINE_MAIN_HEAD));
4796                         else if (commit->refs[i]->ltag)
4797                                 wattrset(view->win, get_line_attr(LINE_MAIN_LOCAL_TAG));
4798                         else if (commit->refs[i]->tag)
4799                                 wattrset(view->win, get_line_attr(LINE_MAIN_TAG));
4800                         else if (commit->refs[i]->remote)
4801                                 wattrset(view->win, get_line_attr(LINE_MAIN_REMOTE));
4802                         else
4803                                 wattrset(view->win, get_line_attr(LINE_MAIN_REF));
4805                         col += draw_text(view, "[", view->width - col, TRUE, selected);
4806                         col += draw_text(view, commit->refs[i]->name, view->width - col,
4807                                          TRUE, selected);
4808                         col += draw_text(view, "]", view->width - col, TRUE, selected);
4809                         if (type != LINE_CURSOR)
4810                                 wattrset(view->win, A_NORMAL);
4811                         col += draw_text(view, " ", view->width - col, TRUE, selected);
4812                         if (col >= view->width)
4813                                 return TRUE;
4814                 } while (commit->refs[i++]->next);
4815         }
4817         if (type != LINE_CURSOR)
4818                 wattrset(view->win, get_line_attr(type));
4820         draw_text(view, commit->title, view->width - col, TRUE, selected);
4821         return TRUE;
4824 /* Reads git log --pretty=raw output and parses it into the commit struct. */
4825 static bool
4826 main_read(struct view *view, char *line)
4828         static struct rev_graph *graph = graph_stacks;
4829         enum line_type type;
4830         struct commit *commit;
4832         if (!line) {
4833                 update_rev_graph(graph);
4834                 return TRUE;
4835         }
4837         type = get_line_type(line);
4838         if (type == LINE_COMMIT) {
4839                 commit = calloc(1, sizeof(struct commit));
4840                 if (!commit)
4841                         return FALSE;
4843                 line += STRING_SIZE("commit ");
4844                 if (*line == '-') {
4845                         graph->boundary = 1;
4846                         line++;
4847                 }
4849                 string_copy_rev(commit->id, line);
4850                 commit->refs = get_refs(commit->id);
4851                 graph->commit = commit;
4852                 add_line_data(view, commit, LINE_MAIN_COMMIT);
4854                 while ((line = strchr(line, ' '))) {
4855                         line++;
4856                         push_rev_graph(graph->parents, line);
4857                         commit->has_parents = TRUE;
4858                 }
4859                 return TRUE;
4860         }
4862         if (!view->lines)
4863                 return TRUE;
4864         commit = view->line[view->lines - 1].data;
4866         switch (type) {
4867         case LINE_PARENT:
4868                 if (commit->has_parents)
4869                         break;
4870                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
4871                 break;
4873         case LINE_AUTHOR:
4874         {
4875                 /* Parse author lines where the name may be empty:
4876                  *      author  <email@address.tld> 1138474660 +0100
4877                  */
4878                 char *ident = line + STRING_SIZE("author ");
4879                 char *nameend = strchr(ident, '<');
4880                 char *emailend = strchr(ident, '>');
4882                 if (!nameend || !emailend)
4883                         break;
4885                 update_rev_graph(graph);
4886                 graph = graph->next;
4888                 *nameend = *emailend = 0;
4889                 ident = chomp_string(ident);
4890                 if (!*ident) {
4891                         ident = chomp_string(nameend + 1);
4892                         if (!*ident)
4893                                 ident = "Unknown";
4894                 }
4896                 string_ncopy(commit->author, ident, strlen(ident));
4898                 /* Parse epoch and timezone */
4899                 if (emailend[1] == ' ') {
4900                         char *secs = emailend + 2;
4901                         char *zone = strchr(secs, ' ');
4902                         time_t time = (time_t) atol(secs);
4904                         if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
4905                                 long tz;
4907                                 zone++;
4908                                 tz  = ('0' - zone[1]) * 60 * 60 * 10;
4909                                 tz += ('0' - zone[2]) * 60 * 60;
4910                                 tz += ('0' - zone[3]) * 60;
4911                                 tz += ('0' - zone[4]) * 60;
4913                                 if (zone[0] == '-')
4914                                         tz = -tz;
4916                                 time -= tz;
4917                         }
4919                         gmtime_r(&time, &commit->time);
4920                 }
4921                 break;
4922         }
4923         default:
4924                 /* Fill in the commit title if it has not already been set. */
4925                 if (commit->title[0])
4926                         break;
4928                 /* Require titles to start with a non-space character at the
4929                  * offset used by git log. */
4930                 if (strncmp(line, "    ", 4))
4931                         break;
4932                 line += 4;
4933                 /* Well, if the title starts with a whitespace character,
4934                  * try to be forgiving.  Otherwise we end up with no title. */
4935                 while (isspace(*line))
4936                         line++;
4937                 if (*line == '\0')
4938                         break;
4939                 /* FIXME: More graceful handling of titles; append "..." to
4940                  * shortened titles, etc. */
4942                 string_ncopy(commit->title, line, strlen(line));
4943         }
4945         return TRUE;
4948 static enum request
4949 main_request(struct view *view, enum request request, struct line *line)
4951         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4953         if (request == REQ_ENTER)
4954                 open_view(view, REQ_VIEW_DIFF, flags);
4955         else
4956                 return request;
4958         return REQ_NONE;
4961 static bool
4962 main_grep(struct view *view, struct line *line)
4964         struct commit *commit = line->data;
4965         enum { S_TITLE, S_AUTHOR, S_DATE, S_END } state;
4966         char buf[DATE_COLS + 1];
4967         regmatch_t pmatch;
4969         for (state = S_TITLE; state < S_END; state++) {
4970                 char *text;
4972                 switch (state) {
4973                 case S_TITLE:   text = commit->title;   break;
4974                 case S_AUTHOR:  text = commit->author;  break;
4975                 case S_DATE:
4976                         if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
4977                                 continue;
4978                         text = buf;
4979                         break;
4981                 default:
4982                         return FALSE;
4983                 }
4985                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4986                         return TRUE;
4987         }
4989         return FALSE;
4992 static void
4993 main_select(struct view *view, struct line *line)
4995         struct commit *commit = line->data;
4997         string_copy_rev(view->ref, commit->id);
4998         string_copy_rev(ref_commit, view->ref);
5001 static struct view_ops main_ops = {
5002         "commit",
5003         NULL,
5004         main_read,
5005         main_draw,
5006         main_request,
5007         main_grep,
5008         main_select,
5009 };
5012 /*
5013  * Unicode / UTF-8 handling
5014  *
5015  * NOTE: Much of the following code for dealing with unicode is derived from
5016  * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
5017  * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
5018  */
5020 /* I've (over)annotated a lot of code snippets because I am not entirely
5021  * confident that the approach taken by this small UTF-8 interface is correct.
5022  * --jonas */
5024 static inline int
5025 unicode_width(unsigned long c)
5027         if (c >= 0x1100 &&
5028            (c <= 0x115f                         /* Hangul Jamo */
5029             || c == 0x2329
5030             || c == 0x232a
5031             || (c >= 0x2e80  && c <= 0xa4cf && c != 0x303f)
5032                                                 /* CJK ... Yi */
5033             || (c >= 0xac00  && c <= 0xd7a3)    /* Hangul Syllables */
5034             || (c >= 0xf900  && c <= 0xfaff)    /* CJK Compatibility Ideographs */
5035             || (c >= 0xfe30  && c <= 0xfe6f)    /* CJK Compatibility Forms */
5036             || (c >= 0xff00  && c <= 0xff60)    /* Fullwidth Forms */
5037             || (c >= 0xffe0  && c <= 0xffe6)
5038             || (c >= 0x20000 && c <= 0x2fffd)
5039             || (c >= 0x30000 && c <= 0x3fffd)))
5040                 return 2;
5042         if (c == '\t')
5043                 return opt_tab_size;
5045         return 1;
5048 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
5049  * Illegal bytes are set one. */
5050 static const unsigned char utf8_bytes[256] = {
5051         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,
5052         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,
5053         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,
5054         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,
5055         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,
5056         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,
5057         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,
5058         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,
5059 };
5061 /* Decode UTF-8 multi-byte representation into a unicode character. */
5062 static inline unsigned long
5063 utf8_to_unicode(const char *string, size_t length)
5065         unsigned long unicode;
5067         switch (length) {
5068         case 1:
5069                 unicode  =   string[0];
5070                 break;
5071         case 2:
5072                 unicode  =  (string[0] & 0x1f) << 6;
5073                 unicode +=  (string[1] & 0x3f);
5074                 break;
5075         case 3:
5076                 unicode  =  (string[0] & 0x0f) << 12;
5077                 unicode += ((string[1] & 0x3f) << 6);
5078                 unicode +=  (string[2] & 0x3f);
5079                 break;
5080         case 4:
5081                 unicode  =  (string[0] & 0x0f) << 18;
5082                 unicode += ((string[1] & 0x3f) << 12);
5083                 unicode += ((string[2] & 0x3f) << 6);
5084                 unicode +=  (string[3] & 0x3f);
5085                 break;
5086         case 5:
5087                 unicode  =  (string[0] & 0x0f) << 24;
5088                 unicode += ((string[1] & 0x3f) << 18);
5089                 unicode += ((string[2] & 0x3f) << 12);
5090                 unicode += ((string[3] & 0x3f) << 6);
5091                 unicode +=  (string[4] & 0x3f);
5092                 break;
5093         case 6:
5094                 unicode  =  (string[0] & 0x01) << 30;
5095                 unicode += ((string[1] & 0x3f) << 24);
5096                 unicode += ((string[2] & 0x3f) << 18);
5097                 unicode += ((string[3] & 0x3f) << 12);
5098                 unicode += ((string[4] & 0x3f) << 6);
5099                 unicode +=  (string[5] & 0x3f);
5100                 break;
5101         default:
5102                 die("Invalid unicode length");
5103         }
5105         /* Invalid characters could return the special 0xfffd value but NUL
5106          * should be just as good. */
5107         return unicode > 0xffff ? 0 : unicode;
5110 /* Calculates how much of string can be shown within the given maximum width
5111  * and sets trimmed parameter to non-zero value if all of string could not be
5112  * shown. If the reserve flag is TRUE, it will reserve at least one
5113  * trailing character, which can be useful when drawing a delimiter.
5114  *
5115  * Returns the number of bytes to output from string to satisfy max_width. */
5116 static size_t
5117 utf8_length(const char *string, size_t max_width, int *trimmed, bool reserve)
5119         const char *start = string;
5120         const char *end = strchr(string, '\0');
5121         unsigned char last_bytes = 0;
5122         size_t width = 0;
5124         *trimmed = 0;
5126         while (string < end) {
5127                 int c = *(unsigned char *) string;
5128                 unsigned char bytes = utf8_bytes[c];
5129                 size_t ucwidth;
5130                 unsigned long unicode;
5132                 if (string + bytes > end)
5133                         break;
5135                 /* Change representation to figure out whether
5136                  * it is a single- or double-width character. */
5138                 unicode = utf8_to_unicode(string, bytes);
5139                 /* FIXME: Graceful handling of invalid unicode character. */
5140                 if (!unicode)
5141                         break;
5143                 ucwidth = unicode_width(unicode);
5144                 width  += ucwidth;
5145                 if (width > max_width) {
5146                         *trimmed = 1;
5147                         if (reserve && width - ucwidth == max_width) {
5148                                 string -= last_bytes;
5149                         }
5150                         break;
5151                 }
5153                 string  += bytes;
5154                 last_bytes = bytes;
5155         }
5157         return string - start;
5161 /*
5162  * Status management
5163  */
5165 /* Whether or not the curses interface has been initialized. */
5166 static bool cursed = FALSE;
5168 /* The status window is used for polling keystrokes. */
5169 static WINDOW *status_win;
5171 static bool status_empty = TRUE;
5173 /* Update status and title window. */
5174 static void
5175 report(const char *msg, ...)
5177         struct view *view = display[current_view];
5179         if (input_mode)
5180                 return;
5182         if (!view) {
5183                 char buf[SIZEOF_STR];
5184                 va_list args;
5186                 va_start(args, msg);
5187                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
5188                         buf[sizeof(buf) - 1] = 0;
5189                         buf[sizeof(buf) - 2] = '.';
5190                         buf[sizeof(buf) - 3] = '.';
5191                         buf[sizeof(buf) - 4] = '.';
5192                 }
5193                 va_end(args);
5194                 die("%s", buf);
5195         }
5197         if (!status_empty || *msg) {
5198                 va_list args;
5200                 va_start(args, msg);
5202                 wmove(status_win, 0, 0);
5203                 if (*msg) {
5204                         vwprintw(status_win, msg, args);
5205                         status_empty = FALSE;
5206                 } else {
5207                         status_empty = TRUE;
5208                 }
5209                 wclrtoeol(status_win);
5210                 wrefresh(status_win);
5212                 va_end(args);
5213         }
5215         update_view_title(view);
5216         update_display_cursor(view);
5219 /* Controls when nodelay should be in effect when polling user input. */
5220 static void
5221 set_nonblocking_input(bool loading)
5223         static unsigned int loading_views;
5225         if ((loading == FALSE && loading_views-- == 1) ||
5226             (loading == TRUE  && loading_views++ == 0))
5227                 nodelay(status_win, loading);
5230 static void
5231 init_display(void)
5233         int x, y;
5235         /* Initialize the curses library */
5236         if (isatty(STDIN_FILENO)) {
5237                 cursed = !!initscr();
5238         } else {
5239                 /* Leave stdin and stdout alone when acting as a pager. */
5240                 FILE *io = fopen("/dev/tty", "r+");
5242                 if (!io)
5243                         die("Failed to open /dev/tty");
5244                 cursed = !!newterm(NULL, io, io);
5245         }
5247         if (!cursed)
5248                 die("Failed to initialize curses");
5250         nonl();         /* Tell curses not to do NL->CR/NL on output */
5251         cbreak();       /* Take input chars one at a time, no wait for \n */
5252         noecho();       /* Don't echo input */
5253         leaveok(stdscr, TRUE);
5255         if (has_colors())
5256                 init_colors();
5258         getmaxyx(stdscr, y, x);
5259         status_win = newwin(1, 0, y - 1, 0);
5260         if (!status_win)
5261                 die("Failed to create status window");
5263         /* Enable keyboard mapping */
5264         keypad(status_win, TRUE);
5265         wbkgdset(status_win, get_line_attr(LINE_STATUS));
5268 static char *
5269 read_prompt(const char *prompt)
5271         enum { READING, STOP, CANCEL } status = READING;
5272         static char buf[sizeof(opt_cmd) - STRING_SIZE("git \0")];
5273         int pos = 0;
5275         while (status == READING) {
5276                 struct view *view;
5277                 int i, key;
5279                 input_mode = TRUE;
5281                 foreach_view (view, i)
5282                         update_view(view);
5284                 input_mode = FALSE;
5286                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
5287                 wclrtoeol(status_win);
5289                 /* Refresh, accept single keystroke of input */
5290                 key = wgetch(status_win);
5291                 switch (key) {
5292                 case KEY_RETURN:
5293                 case KEY_ENTER:
5294                 case '\n':
5295                         status = pos ? STOP : CANCEL;
5296                         break;
5298                 case KEY_BACKSPACE:
5299                         if (pos > 0)
5300                                 pos--;
5301                         else
5302                                 status = CANCEL;
5303                         break;
5305                 case KEY_ESC:
5306                         status = CANCEL;
5307                         break;
5309                 case ERR:
5310                         break;
5312                 default:
5313                         if (pos >= sizeof(buf)) {
5314                                 report("Input string too long");
5315                                 return NULL;
5316                         }
5318                         if (isprint(key))
5319                                 buf[pos++] = (char) key;
5320                 }
5321         }
5323         /* Clear the status window */
5324         status_empty = FALSE;
5325         report("");
5327         if (status == CANCEL)
5328                 return NULL;
5330         buf[pos++] = 0;
5332         return buf;
5335 /*
5336  * Repository references
5337  */
5339 static struct ref *refs = NULL;
5340 static size_t refs_alloc = 0;
5341 static size_t refs_size = 0;
5343 /* Id <-> ref store */
5344 static struct ref ***id_refs = NULL;
5345 static size_t id_refs_alloc = 0;
5346 static size_t id_refs_size = 0;
5348 static struct ref **
5349 get_refs(char *id)
5351         struct ref ***tmp_id_refs;
5352         struct ref **ref_list = NULL;
5353         size_t ref_list_alloc = 0;
5354         size_t ref_list_size = 0;
5355         size_t i;
5357         for (i = 0; i < id_refs_size; i++)
5358                 if (!strcmp(id, id_refs[i][0]->id))
5359                         return id_refs[i];
5361         tmp_id_refs = realloc_items(id_refs, &id_refs_alloc, id_refs_size + 1,
5362                                     sizeof(*id_refs));
5363         if (!tmp_id_refs)
5364                 return NULL;
5366         id_refs = tmp_id_refs;
5368         for (i = 0; i < refs_size; i++) {
5369                 struct ref **tmp;
5371                 if (strcmp(id, refs[i].id))
5372                         continue;
5374                 tmp = realloc_items(ref_list, &ref_list_alloc,
5375                                     ref_list_size + 1, sizeof(*ref_list));
5376                 if (!tmp) {
5377                         if (ref_list)
5378                                 free(ref_list);
5379                         return NULL;
5380                 }
5382                 ref_list = tmp;
5383                 if (ref_list_size > 0)
5384                         ref_list[ref_list_size - 1]->next = 1;
5385                 ref_list[ref_list_size] = &refs[i];
5387                 /* XXX: The properties of the commit chains ensures that we can
5388                  * safely modify the shared ref. The repo references will
5389                  * always be similar for the same id. */
5390                 ref_list[ref_list_size]->next = 0;
5391                 ref_list_size++;
5392         }
5394         if (ref_list)
5395                 id_refs[id_refs_size++] = ref_list;
5397         return ref_list;
5400 static int
5401 read_ref(char *id, size_t idlen, char *name, size_t namelen)
5403         struct ref *ref;
5404         bool tag = FALSE;
5405         bool ltag = FALSE;
5406         bool remote = FALSE;
5407         bool check_replace = FALSE;
5408         bool head = FALSE;
5410         if (!strncmp(name, "refs/tags/", STRING_SIZE("refs/tags/"))) {
5411                 if (!strcmp(name + namelen - 3, "^{}")) {
5412                         namelen -= 3;
5413                         name[namelen] = 0;
5414                         if (refs_size > 0 && refs[refs_size - 1].ltag == TRUE)
5415                                 check_replace = TRUE;
5416                 } else {
5417                         ltag = TRUE;
5418                 }
5420                 tag = TRUE;
5421                 namelen -= STRING_SIZE("refs/tags/");
5422                 name    += STRING_SIZE("refs/tags/");
5424         } else if (!strncmp(name, "refs/remotes/", STRING_SIZE("refs/remotes/"))) {
5425                 remote = TRUE;
5426                 namelen -= STRING_SIZE("refs/remotes/");
5427                 name    += STRING_SIZE("refs/remotes/");
5429         } else if (!strncmp(name, "refs/heads/", STRING_SIZE("refs/heads/"))) {
5430                 namelen -= STRING_SIZE("refs/heads/");
5431                 name    += STRING_SIZE("refs/heads/");
5432                 head     = !strncmp(opt_head, name, namelen);
5434         } else if (!strcmp(name, "HEAD")) {
5435                 return OK;
5436         }
5438         if (check_replace && !strcmp(name, refs[refs_size - 1].name)) {
5439                 /* it's an annotated tag, replace the previous sha1 with the
5440                  * resolved commit id; relies on the fact git-ls-remote lists
5441                  * the commit id of an annotated tag right beofre the commit id
5442                  * it points to. */
5443                 refs[refs_size - 1].ltag = ltag;
5444                 string_copy_rev(refs[refs_size - 1].id, id);
5446                 return OK;
5447         }
5448         refs = realloc_items(refs, &refs_alloc, refs_size + 1, sizeof(*refs));
5449         if (!refs)
5450                 return ERR;
5452         ref = &refs[refs_size++];
5453         ref->name = malloc(namelen + 1);
5454         if (!ref->name)
5455                 return ERR;
5457         strncpy(ref->name, name, namelen);
5458         ref->name[namelen] = 0;
5459         ref->tag = tag;
5460         ref->ltag = ltag;
5461         ref->remote = remote;
5462         ref->head = head;
5463         string_copy_rev(ref->id, id);
5465         return OK;
5468 static int
5469 load_refs(void)
5471         const char *cmd_env = getenv("TIG_LS_REMOTE");
5472         const char *cmd = cmd_env && *cmd_env ? cmd_env : TIG_LS_REMOTE;
5474         return read_properties(popen(cmd, "r"), "\t", read_ref);
5477 static int
5478 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
5480         if (!strcmp(name, "i18n.commitencoding"))
5481                 string_ncopy(opt_encoding, value, valuelen);
5483         if (!strcmp(name, "core.editor"))
5484                 string_ncopy(opt_editor, value, valuelen);
5486         return OK;
5489 static int
5490 load_repo_config(void)
5492         return read_properties(popen(GIT_CONFIG " --list", "r"),
5493                                "=", read_repo_config_option);
5496 static int
5497 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
5499         if (!opt_git_dir[0]) {
5500                 string_ncopy(opt_git_dir, name, namelen);
5502         } else if (opt_is_inside_work_tree == -1) {
5503                 /* This can be 3 different values depending on the
5504                  * version of git being used. If git-rev-parse does not
5505                  * understand --is-inside-work-tree it will simply echo
5506                  * the option else either "true" or "false" is printed.
5507                  * Default to true for the unknown case. */
5508                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
5510         } else if (opt_cdup[0] == ' ') {
5511                 string_ncopy(opt_cdup, name, namelen);
5512         } else {
5513                 if (!strncmp(name, "refs/heads/", STRING_SIZE("refs/heads/"))) {
5514                         namelen -= STRING_SIZE("refs/heads/");
5515                         name    += STRING_SIZE("refs/heads/");
5516                         string_ncopy(opt_head, name, namelen);
5517                 }
5518         }
5520         return OK;
5523 static int
5524 load_repo_info(void)
5526         int result;
5527         FILE *pipe = popen("git rev-parse --git-dir --is-inside-work-tree "
5528                            " --show-cdup --symbolic-full-name HEAD 2>/dev/null", "r");
5530         /* XXX: The line outputted by "--show-cdup" can be empty so
5531          * initialize it to something invalid to make it possible to
5532          * detect whether it has been set or not. */
5533         opt_cdup[0] = ' ';
5535         result = read_properties(pipe, "=", read_repo_info);
5536         if (opt_cdup[0] == ' ')
5537                 opt_cdup[0] = 0;
5539         return result;
5542 static int
5543 read_properties(FILE *pipe, const char *separators,
5544                 int (*read_property)(char *, size_t, char *, size_t))
5546         char buffer[BUFSIZ];
5547         char *name;
5548         int state = OK;
5550         if (!pipe)
5551                 return ERR;
5553         while (state == OK && (name = fgets(buffer, sizeof(buffer), pipe))) {
5554                 char *value;
5555                 size_t namelen;
5556                 size_t valuelen;
5558                 name = chomp_string(name);
5559                 namelen = strcspn(name, separators);
5561                 if (name[namelen]) {
5562                         name[namelen] = 0;
5563                         value = chomp_string(name + namelen + 1);
5564                         valuelen = strlen(value);
5566                 } else {
5567                         value = "";
5568                         valuelen = 0;
5569                 }
5571                 state = read_property(name, namelen, value, valuelen);
5572         }
5574         if (state != ERR && ferror(pipe))
5575                 state = ERR;
5577         pclose(pipe);
5579         return state;
5583 /*
5584  * Main
5585  */
5587 static void __NORETURN
5588 quit(int sig)
5590         /* XXX: Restore tty modes and let the OS cleanup the rest! */
5591         if (cursed)
5592                 endwin();
5593         exit(0);
5596 static void __NORETURN
5597 die(const char *err, ...)
5599         va_list args;
5601         endwin();
5603         va_start(args, err);
5604         fputs("tig: ", stderr);
5605         vfprintf(stderr, err, args);
5606         fputs("\n", stderr);
5607         va_end(args);
5609         exit(1);
5612 static void
5613 warn(const char *msg, ...)
5615         va_list args;
5617         va_start(args, msg);
5618         fputs("tig warning: ", stderr);
5619         vfprintf(stderr, msg, args);
5620         fputs("\n", stderr);
5621         va_end(args);
5624 int
5625 main(int argc, char *argv[])
5627         struct view *view;
5628         enum request request;
5629         size_t i;
5631         signal(SIGINT, quit);
5633         if (setlocale(LC_ALL, "")) {
5634                 char *codeset = nl_langinfo(CODESET);
5636                 string_ncopy(opt_codeset, codeset, strlen(codeset));
5637         }
5639         if (load_repo_info() == ERR)
5640                 die("Failed to load repo info.");
5642         if (load_options() == ERR)
5643                 die("Failed to load user config.");
5645         /* Load the repo config file so options can be overwritten from
5646          * the command line. */
5647         if (load_repo_config() == ERR)
5648                 die("Failed to load repo config.");
5650         if (!parse_options(argc, argv))
5651                 return 0;
5653         /* Require a git repository unless when running in pager mode. */
5654         if (!opt_git_dir[0])
5655                 die("Not a git repository");
5657         if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
5658                 opt_utf8 = FALSE;
5660         if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
5661                 opt_iconv = iconv_open(opt_codeset, opt_encoding);
5662                 if (opt_iconv == ICONV_NONE)
5663                         die("Failed to initialize character set conversion");
5664         }
5666         if (load_refs() == ERR)
5667                 die("Failed to load refs.");
5669         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
5670                 view->cmd_env = getenv(view->cmd_env);
5672         request = opt_request;
5674         init_display();
5676         while (view_driver(display[current_view], request)) {
5677                 int key;
5678                 int i;
5680                 foreach_view (view, i)
5681                         update_view(view);
5683                 /* Refresh, accept single keystroke of input */
5684                 key = wgetch(status_win);
5686                 /* wgetch() with nodelay() enabled returns ERR when there's no
5687                  * input. */
5688                 if (key == ERR) {
5689                         request = REQ_NONE;
5690                         continue;
5691                 }
5693                 request = get_keybinding(display[current_view]->keymap, key);
5695                 /* Some low-level request handling. This keeps access to
5696                  * status_win restricted. */
5697                 switch (request) {
5698                 case REQ_PROMPT:
5699                 {
5700                         char *cmd = read_prompt(":");
5702                         if (cmd && string_format(opt_cmd, "git %s", cmd)) {
5703                                 if (strncmp(cmd, "show", 4) && isspace(cmd[4])) {
5704                                         opt_request = REQ_VIEW_DIFF;
5705                                 } else {
5706                                         opt_request = REQ_VIEW_PAGER;
5707                                 }
5708                                 break;
5709                         }
5711                         request = REQ_NONE;
5712                         break;
5713                 }
5714                 case REQ_SEARCH:
5715                 case REQ_SEARCH_BACK:
5716                 {
5717                         const char *prompt = request == REQ_SEARCH
5718                                            ? "/" : "?";
5719                         char *search = read_prompt(prompt);
5721                         if (search)
5722                                 string_ncopy(opt_search, search, strlen(search));
5723                         else
5724                                 request = REQ_NONE;
5725                         break;
5726                 }
5727                 case REQ_SCREEN_RESIZE:
5728                 {
5729                         int height, width;
5731                         getmaxyx(stdscr, height, width);
5733                         /* Resize the status view and let the view driver take
5734                          * care of resizing the displayed views. */
5735                         wresize(status_win, 1, width);
5736                         mvwin(status_win, height - 1, 0);
5737                         wrefresh(status_win);
5738                         break;
5739                 }
5740                 default:
5741                         break;
5742                 }
5743         }
5745         quit(0);
5747         return 0;