Code

6f25bec53f3435f7c6fb82068edd00ec318a64cd
[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 #ifdef HAVE_NCURSESW_NCURSES_H
49 #include <ncursesw/ncurses.h>
50 #else
51 #ifdef HAVE_NCURSES_NCURSES_H
52 #include <ncurses/ncurses.h>
53 #else
54 #include <ncurses.h>
55 #endif
56 #endif
58 #if __GNUC__ >= 3
59 #define __NORETURN __attribute__((__noreturn__))
60 #else
61 #define __NORETURN
62 #endif
64 static void __NORETURN die(const char *err, ...);
65 static void warn(const char *msg, ...);
66 static void report(const char *msg, ...);
67 static int read_properties(FILE *pipe, const char *separators, int (*read)(char *, size_t, char *, size_t));
68 static void set_nonblocking_input(bool loading);
69 static size_t utf8_length(const char *string, int *width, size_t max_width, int *trimmed, bool reserve);
70 static bool prompt_yesno(const char *prompt);
71 static int load_refs(void);
73 #define ABS(x)          ((x) >= 0  ? (x) : -(x))
74 #define MIN(x, y)       ((x) < (y) ? (x) :  (y))
76 #define ARRAY_SIZE(x)   (sizeof(x) / sizeof(x[0]))
77 #define STRING_SIZE(x)  (sizeof(x) - 1)
79 #define SIZEOF_STR      1024    /* Default string size. */
80 #define SIZEOF_REF      256     /* Size of symbolic or SHA1 ID. */
81 #define SIZEOF_REV      41      /* Holds a SHA-1 and an ending NUL. */
82 #define SIZEOF_ARG      32      /* Default argument array size. */
84 /* Revision graph */
86 #define REVGRAPH_INIT   'I'
87 #define REVGRAPH_MERGE  'M'
88 #define REVGRAPH_BRANCH '+'
89 #define REVGRAPH_COMMIT '*'
90 #define REVGRAPH_BOUND  '^'
92 #define SIZEOF_REVGRAPH 19      /* Size of revision ancestry graphics. */
94 /* This color name can be used to refer to the default term colors. */
95 #define COLOR_DEFAULT   (-1)
97 #define ICONV_NONE      ((iconv_t) -1)
98 #ifndef ICONV_CONST
99 #define ICONV_CONST     /* nothing */
100 #endif
102 /* The format and size of the date column in the main view. */
103 #define DATE_FORMAT     "%Y-%m-%d %H:%M"
104 #define DATE_COLS       STRING_SIZE("2006-04-29 14:21 ")
106 #define AUTHOR_COLS     20
107 #define ID_COLS         8
109 /* The default interval between line numbers. */
110 #define NUMBER_INTERVAL 5
112 #define TAB_SIZE        8
114 #define SCALE_SPLIT_VIEW(height)        ((height) * 2 / 3)
116 #define NULL_ID         "0000000000000000000000000000000000000000"
118 #ifndef GIT_CONFIG
119 #define GIT_CONFIG "config"
120 #endif
122 #define TIG_LS_REMOTE \
123         "git ls-remote . 2>/dev/null"
125 #define TIG_DIFF_CMD \
126         "git show --pretty=fuller --no-color --root --patch-with-stat --find-copies-harder -C %s 2>/dev/null"
128 #define TIG_LOG_CMD     \
129         "git log --no-color --cc --stat -n100 %s 2>/dev/null"
131 #define TIG_MAIN_BASE \
132         "git log --no-color --pretty=raw --parents --topo-order"
134 #define TIG_MAIN_CMD \
135         TIG_MAIN_BASE " %s 2>/dev/null"
137 #define TIG_TREE_CMD    \
138         "git ls-tree %s %s"
140 #define TIG_BLOB_CMD    \
141         "git cat-file blob %s"
143 /* XXX: Needs to be defined to the empty string. */
144 #define TIG_HELP_CMD    ""
145 #define TIG_PAGER_CMD   ""
146 #define TIG_STATUS_CMD  ""
147 #define TIG_STAGE_CMD   ""
148 #define TIG_BLAME_CMD   ""
150 /* Some ascii-shorthands fitted into the ncurses namespace. */
151 #define KEY_TAB         '\t'
152 #define KEY_RETURN      '\r'
153 #define KEY_ESC         27
156 struct ref {
157         char *name;             /* Ref name; tag or head names are shortened. */
158         char id[SIZEOF_REV];    /* Commit SHA1 ID */
159         unsigned int head:1;    /* Is it the current HEAD? */
160         unsigned int tag:1;     /* Is it a tag? */
161         unsigned int ltag:1;    /* If so, is the tag local? */
162         unsigned int remote:1;  /* Is it a remote ref? */
163         unsigned int tracked:1; /* Is it the remote for the current HEAD? */
164         unsigned int next:1;    /* For ref lists: are there more refs? */
165 };
167 static struct ref **get_refs(const char *id);
169 struct int_map {
170         const char *name;
171         int namelen;
172         int value;
173 };
175 static int
176 set_from_int_map(struct int_map *map, size_t map_size,
177                  int *value, const char *name, int namelen)
180         int i;
182         for (i = 0; i < map_size; i++)
183                 if (namelen == map[i].namelen &&
184                     !strncasecmp(name, map[i].name, namelen)) {
185                         *value = map[i].value;
186                         return OK;
187                 }
189         return ERR;
193 /*
194  * String helpers
195  */
197 static inline void
198 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
200         if (srclen > dstlen - 1)
201                 srclen = dstlen - 1;
203         strncpy(dst, src, srclen);
204         dst[srclen] = 0;
207 /* Shorthands for safely copying into a fixed buffer. */
209 #define string_copy(dst, src) \
210         string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
212 #define string_ncopy(dst, src, srclen) \
213         string_ncopy_do(dst, sizeof(dst), src, srclen)
215 #define string_copy_rev(dst, src) \
216         string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
218 #define string_add(dst, from, src) \
219         string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
221 static char *
222 chomp_string(char *name)
224         int namelen;
226         while (isspace(*name))
227                 name++;
229         namelen = strlen(name) - 1;
230         while (namelen > 0 && isspace(name[namelen]))
231                 name[namelen--] = 0;
233         return name;
236 static bool
237 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
239         va_list args;
240         size_t pos = bufpos ? *bufpos : 0;
242         va_start(args, fmt);
243         pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
244         va_end(args);
246         if (bufpos)
247                 *bufpos = pos;
249         return pos >= bufsize ? FALSE : TRUE;
252 #define string_format(buf, fmt, args...) \
253         string_nformat(buf, sizeof(buf), NULL, fmt, args)
255 #define string_format_from(buf, from, fmt, args...) \
256         string_nformat(buf, sizeof(buf), from, fmt, args)
258 static int
259 string_enum_compare(const char *str1, const char *str2, int len)
261         size_t i;
263 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
265         /* Diff-Header == DIFF_HEADER */
266         for (i = 0; i < len; i++) {
267                 if (toupper(str1[i]) == toupper(str2[i]))
268                         continue;
270                 if (string_enum_sep(str1[i]) &&
271                     string_enum_sep(str2[i]))
272                         continue;
274                 return str1[i] - str2[i];
275         }
277         return 0;
280 #define prefixcmp(str1, str2) \
281         strncmp(str1, str2, STRING_SIZE(str2))
283 static inline int
284 suffixcmp(const char *str, int slen, const char *suffix)
286         size_t len = slen >= 0 ? slen : strlen(str);
287         size_t suffixlen = strlen(suffix);
289         return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
292 /* Shell quoting
293  *
294  * NOTE: The following is a slightly modified copy of the git project's shell
295  * quoting routines found in the quote.c file.
296  *
297  * Help to copy the thing properly quoted for the shell safety.  any single
298  * quote is replaced with '\'', any exclamation point is replaced with '\!',
299  * and the whole thing is enclosed in a
300  *
301  * E.g.
302  *  original     sq_quote     result
303  *  name     ==> name      ==> 'name'
304  *  a b      ==> a b       ==> 'a b'
305  *  a'b      ==> a'\''b    ==> 'a'\''b'
306  *  a!b      ==> a'\!'b    ==> 'a'\!'b'
307  */
309 static size_t
310 sq_quote(char buf[SIZEOF_STR], size_t bufsize, const char *src)
312         char c;
314 #define BUFPUT(x) do { if (bufsize < SIZEOF_STR) buf[bufsize++] = (x); } while (0)
316         BUFPUT('\'');
317         while ((c = *src++)) {
318                 if (c == '\'' || c == '!') {
319                         BUFPUT('\'');
320                         BUFPUT('\\');
321                         BUFPUT(c);
322                         BUFPUT('\'');
323                 } else {
324                         BUFPUT(c);
325                 }
326         }
327         BUFPUT('\'');
329         if (bufsize < SIZEOF_STR)
330                 buf[bufsize] = 0;
332         return bufsize;
336 /*
337  * User requests
338  */
340 #define REQ_INFO \
341         /* XXX: Keep the view request first and in sync with views[]. */ \
342         REQ_GROUP("View switching") \
343         REQ_(VIEW_MAIN,         "Show main view"), \
344         REQ_(VIEW_DIFF,         "Show diff view"), \
345         REQ_(VIEW_LOG,          "Show log view"), \
346         REQ_(VIEW_TREE,         "Show tree view"), \
347         REQ_(VIEW_BLOB,         "Show blob view"), \
348         REQ_(VIEW_BLAME,        "Show blame view"), \
349         REQ_(VIEW_HELP,         "Show help page"), \
350         REQ_(VIEW_PAGER,        "Show pager view"), \
351         REQ_(VIEW_STATUS,       "Show status view"), \
352         REQ_(VIEW_STAGE,        "Show stage view"), \
353         \
354         REQ_GROUP("View manipulation") \
355         REQ_(ENTER,             "Enter current line and scroll"), \
356         REQ_(NEXT,              "Move to next"), \
357         REQ_(PREVIOUS,          "Move to previous"), \
358         REQ_(VIEW_NEXT,         "Move focus to next view"), \
359         REQ_(REFRESH,           "Reload and refresh"), \
360         REQ_(MAXIMIZE,          "Maximize the current view"), \
361         REQ_(VIEW_CLOSE,        "Close the current view"), \
362         REQ_(QUIT,              "Close all views and quit"), \
363         \
364         REQ_GROUP("View specific requests") \
365         REQ_(STATUS_UPDATE,     "Update file status"), \
366         REQ_(STATUS_REVERT,     "Revert file changes"), \
367         REQ_(STATUS_MERGE,      "Merge file using external tool"), \
368         REQ_(STAGE_NEXT,        "Find next chunk to stage"), \
369         REQ_(TREE_PARENT,       "Switch to parent directory in tree view"), \
370         \
371         REQ_GROUP("Cursor navigation") \
372         REQ_(MOVE_UP,           "Move cursor one line up"), \
373         REQ_(MOVE_DOWN,         "Move cursor one line down"), \
374         REQ_(MOVE_PAGE_DOWN,    "Move cursor one page down"), \
375         REQ_(MOVE_PAGE_UP,      "Move cursor one page up"), \
376         REQ_(MOVE_FIRST_LINE,   "Move cursor to first line"), \
377         REQ_(MOVE_LAST_LINE,    "Move cursor to last line"), \
378         \
379         REQ_GROUP("Scrolling") \
380         REQ_(SCROLL_LINE_UP,    "Scroll one line up"), \
381         REQ_(SCROLL_LINE_DOWN,  "Scroll one line down"), \
382         REQ_(SCROLL_PAGE_UP,    "Scroll one page up"), \
383         REQ_(SCROLL_PAGE_DOWN,  "Scroll one page down"), \
384         \
385         REQ_GROUP("Searching") \
386         REQ_(SEARCH,            "Search the view"), \
387         REQ_(SEARCH_BACK,       "Search backwards in the view"), \
388         REQ_(FIND_NEXT,         "Find next search match"), \
389         REQ_(FIND_PREV,         "Find previous search match"), \
390         \
391         REQ_GROUP("Option manipulation") \
392         REQ_(TOGGLE_LINENO,     "Toggle line numbers"), \
393         REQ_(TOGGLE_DATE,       "Toggle date display"), \
394         REQ_(TOGGLE_AUTHOR,     "Toggle author display"), \
395         REQ_(TOGGLE_REV_GRAPH,  "Toggle revision graph visualization"), \
396         REQ_(TOGGLE_REFS,       "Toggle reference display (tags/branches)"), \
397         \
398         REQ_GROUP("Misc") \
399         REQ_(PROMPT,            "Bring up the prompt"), \
400         REQ_(SCREEN_REDRAW,     "Redraw the screen"), \
401         REQ_(SCREEN_RESIZE,     "Resize the screen"), \
402         REQ_(SHOW_VERSION,      "Show version information"), \
403         REQ_(STOP_LOADING,      "Stop all loading views"), \
404         REQ_(EDIT,              "Open in editor"), \
405         REQ_(NONE,              "Do nothing")
408 /* User action requests. */
409 enum request {
410 #define REQ_GROUP(help)
411 #define REQ_(req, help) REQ_##req
413         /* Offset all requests to avoid conflicts with ncurses getch values. */
414         REQ_OFFSET = KEY_MAX + 1,
415         REQ_INFO
417 #undef  REQ_GROUP
418 #undef  REQ_
419 };
421 struct request_info {
422         enum request request;
423         const char *name;
424         int namelen;
425         const char *help;
426 };
428 static struct request_info req_info[] = {
429 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
430 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
431         REQ_INFO
432 #undef  REQ_GROUP
433 #undef  REQ_
434 };
436 static enum request
437 get_request(const char *name)
439         int namelen = strlen(name);
440         int i;
442         for (i = 0; i < ARRAY_SIZE(req_info); i++)
443                 if (req_info[i].namelen == namelen &&
444                     !string_enum_compare(req_info[i].name, name, namelen))
445                         return req_info[i].request;
447         return REQ_NONE;
451 /*
452  * Options
453  */
455 static const char usage[] =
456 "tig " TIG_VERSION " (" __DATE__ ")\n"
457 "\n"
458 "Usage: tig        [options] [revs] [--] [paths]\n"
459 "   or: tig show   [options] [revs] [--] [paths]\n"
460 "   or: tig blame  [rev] path\n"
461 "   or: tig status\n"
462 "   or: tig <      [git command output]\n"
463 "\n"
464 "Options:\n"
465 "  -v, --version   Show version and exit\n"
466 "  -h, --help      Show help message and exit";
468 /* Option and state variables. */
469 static bool opt_date                    = TRUE;
470 static bool opt_author                  = TRUE;
471 static bool opt_line_number             = FALSE;
472 static bool opt_line_graphics           = TRUE;
473 static bool opt_rev_graph               = FALSE;
474 static bool opt_show_refs               = TRUE;
475 static int opt_num_interval             = NUMBER_INTERVAL;
476 static int opt_tab_size                 = TAB_SIZE;
477 static int opt_author_cols              = AUTHOR_COLS-1;
478 static char opt_cmd[SIZEOF_STR]         = "";
479 static char opt_path[SIZEOF_STR]        = "";
480 static char opt_file[SIZEOF_STR]        = "";
481 static char opt_ref[SIZEOF_REF]         = "";
482 static char opt_head[SIZEOF_REF]        = "";
483 static char opt_remote[SIZEOF_REF]      = "";
484 static bool opt_no_head                 = TRUE;
485 static FILE *opt_pipe                   = NULL;
486 static char opt_encoding[20]            = "UTF-8";
487 static bool opt_utf8                    = TRUE;
488 static char opt_codeset[20]             = "UTF-8";
489 static iconv_t opt_iconv                = ICONV_NONE;
490 static char opt_search[SIZEOF_STR]      = "";
491 static char opt_cdup[SIZEOF_STR]        = "";
492 static char opt_git_dir[SIZEOF_STR]     = "";
493 static signed char opt_is_inside_work_tree      = -1; /* set to TRUE or FALSE */
494 static char opt_editor[SIZEOF_STR]      = "";
495 static FILE *opt_tty                    = NULL;
497 static enum request
498 parse_options(int argc, const char *argv[])
500         enum request request = REQ_VIEW_MAIN;
501         size_t buf_size;
502         const char *subcommand;
503         bool seen_dashdash = FALSE;
504         int i;
506         if (!isatty(STDIN_FILENO)) {
507                 opt_pipe = stdin;
508                 return REQ_VIEW_PAGER;
509         }
511         if (argc <= 1)
512                 return REQ_VIEW_MAIN;
514         subcommand = argv[1];
515         if (!strcmp(subcommand, "status") || !strcmp(subcommand, "-S")) {
516                 if (!strcmp(subcommand, "-S"))
517                         warn("`-S' has been deprecated; use `tig status' instead");
518                 if (argc > 2)
519                         warn("ignoring arguments after `%s'", subcommand);
520                 return REQ_VIEW_STATUS;
522         } else if (!strcmp(subcommand, "blame")) {
523                 if (argc <= 2 || argc > 4)
524                         die("invalid number of options to blame\n\n%s", usage);
526                 i = 2;
527                 if (argc == 4) {
528                         string_ncopy(opt_ref, argv[i], strlen(argv[i]));
529                         i++;
530                 }
532                 string_ncopy(opt_file, argv[i], strlen(argv[i]));
533                 return REQ_VIEW_BLAME;
535         } else if (!strcmp(subcommand, "show")) {
536                 request = REQ_VIEW_DIFF;
538         } else if (!strcmp(subcommand, "log") || !strcmp(subcommand, "diff")) {
539                 request = subcommand[0] == 'l' ? REQ_VIEW_LOG : REQ_VIEW_DIFF;
540                 warn("`tig %s' has been deprecated", subcommand);
542         } else {
543                 subcommand = NULL;
544         }
546         if (!subcommand)
547                 /* XXX: This is vulnerable to the user overriding
548                  * options required for the main view parser. */
549                 string_copy(opt_cmd, TIG_MAIN_BASE);
550         else
551                 string_format(opt_cmd, "git %s", subcommand);
553         buf_size = strlen(opt_cmd);
555         for (i = 1 + !!subcommand; i < argc; i++) {
556                 const char *opt = argv[i];
558                 if (seen_dashdash || !strcmp(opt, "--")) {
559                         seen_dashdash = TRUE;
561                 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
562                         printf("tig version %s\n", TIG_VERSION);
563                         return REQ_NONE;
565                 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
566                         printf("%s\n", usage);
567                         return REQ_NONE;
568                 }
570                 opt_cmd[buf_size++] = ' ';
571                 buf_size = sq_quote(opt_cmd, buf_size, opt);
572                 if (buf_size >= sizeof(opt_cmd))
573                         die("command too long");
574         }
576         opt_cmd[buf_size] = 0;
578         return request;
582 /*
583  * Line-oriented content detection.
584  */
586 #define LINE_INFO \
587 LINE(DIFF_HEADER,  "diff --git ",       COLOR_YELLOW,   COLOR_DEFAULT,  0), \
588 LINE(DIFF_CHUNK,   "@@",                COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
589 LINE(DIFF_ADD,     "+",                 COLOR_GREEN,    COLOR_DEFAULT,  0), \
590 LINE(DIFF_DEL,     "-",                 COLOR_RED,      COLOR_DEFAULT,  0), \
591 LINE(DIFF_INDEX,        "index ",         COLOR_BLUE,   COLOR_DEFAULT,  0), \
592 LINE(DIFF_OLDMODE,      "old file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
593 LINE(DIFF_NEWMODE,      "new file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
594 LINE(DIFF_COPY_FROM,    "copy from",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
595 LINE(DIFF_COPY_TO,      "copy to",        COLOR_YELLOW, COLOR_DEFAULT,  0), \
596 LINE(DIFF_RENAME_FROM,  "rename from",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
597 LINE(DIFF_RENAME_TO,    "rename to",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
598 LINE(DIFF_SIMILARITY,   "similarity ",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
599 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
600 LINE(DIFF_TREE,         "diff-tree ",     COLOR_BLUE,   COLOR_DEFAULT,  0), \
601 LINE(PP_AUTHOR,    "Author: ",          COLOR_CYAN,     COLOR_DEFAULT,  0), \
602 LINE(PP_COMMIT,    "Commit: ",          COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
603 LINE(PP_MERGE,     "Merge: ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
604 LINE(PP_DATE,      "Date:   ",          COLOR_YELLOW,   COLOR_DEFAULT,  0), \
605 LINE(PP_ADATE,     "AuthorDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
606 LINE(PP_CDATE,     "CommitDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
607 LINE(PP_REFS,      "Refs: ",            COLOR_RED,      COLOR_DEFAULT,  0), \
608 LINE(COMMIT,       "commit ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
609 LINE(PARENT,       "parent ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
610 LINE(TREE,         "tree ",             COLOR_BLUE,     COLOR_DEFAULT,  0), \
611 LINE(AUTHOR,       "author ",           COLOR_CYAN,     COLOR_DEFAULT,  0), \
612 LINE(COMMITTER,    "committer ",        COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
613 LINE(SIGNOFF,      "    Signed-off-by", COLOR_YELLOW,   COLOR_DEFAULT,  0), \
614 LINE(ACKED,        "    Acked-by",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
615 LINE(DEFAULT,      "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
616 LINE(CURSOR,       "",                  COLOR_WHITE,    COLOR_GREEN,    A_BOLD), \
617 LINE(STATUS,       "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
618 LINE(DELIMITER,    "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
619 LINE(DATE,         "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
620 LINE(LINE_NUMBER,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
621 LINE(TITLE_BLUR,   "",                  COLOR_WHITE,    COLOR_BLUE,     0), \
622 LINE(TITLE_FOCUS,  "",                  COLOR_WHITE,    COLOR_BLUE,     A_BOLD), \
623 LINE(MAIN_AUTHOR,  "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
624 LINE(MAIN_COMMIT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
625 LINE(MAIN_TAG,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
626 LINE(MAIN_LOCAL_TAG,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
627 LINE(MAIN_REMOTE,  "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
628 LINE(MAIN_TRACKED, "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_BOLD), \
629 LINE(MAIN_REF,     "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
630 LINE(MAIN_HEAD,    "",                  COLOR_CYAN,     COLOR_DEFAULT,  A_BOLD), \
631 LINE(MAIN_REVGRAPH,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
632 LINE(TREE_DIR,     "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
633 LINE(TREE_FILE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
634 LINE(STAT_HEAD,    "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
635 LINE(STAT_SECTION, "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
636 LINE(STAT_NONE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
637 LINE(STAT_STAGED,  "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
638 LINE(STAT_UNSTAGED,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
639 LINE(STAT_UNTRACKED,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
640 LINE(BLAME_ID,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0)
642 enum line_type {
643 #define LINE(type, line, fg, bg, attr) \
644         LINE_##type
645         LINE_INFO,
646         LINE_NONE
647 #undef  LINE
648 };
650 struct line_info {
651         const char *name;       /* Option name. */
652         int namelen;            /* Size of option name. */
653         const char *line;       /* The start of line to match. */
654         int linelen;            /* Size of string to match. */
655         int fg, bg, attr;       /* Color and text attributes for the lines. */
656 };
658 static struct line_info line_info[] = {
659 #define LINE(type, line, fg, bg, attr) \
660         { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
661         LINE_INFO
662 #undef  LINE
663 };
665 static enum line_type
666 get_line_type(const char *line)
668         int linelen = strlen(line);
669         enum line_type type;
671         for (type = 0; type < ARRAY_SIZE(line_info); type++)
672                 /* Case insensitive search matches Signed-off-by lines better. */
673                 if (linelen >= line_info[type].linelen &&
674                     !strncasecmp(line_info[type].line, line, line_info[type].linelen))
675                         return type;
677         return LINE_DEFAULT;
680 static inline int
681 get_line_attr(enum line_type type)
683         assert(type < ARRAY_SIZE(line_info));
684         return COLOR_PAIR(type) | line_info[type].attr;
687 static struct line_info *
688 get_line_info(const char *name)
690         size_t namelen = strlen(name);
691         enum line_type type;
693         for (type = 0; type < ARRAY_SIZE(line_info); type++)
694                 if (namelen == line_info[type].namelen &&
695                     !string_enum_compare(line_info[type].name, name, namelen))
696                         return &line_info[type];
698         return NULL;
701 static void
702 init_colors(void)
704         int default_bg = line_info[LINE_DEFAULT].bg;
705         int default_fg = line_info[LINE_DEFAULT].fg;
706         enum line_type type;
708         start_color();
710         if (assume_default_colors(default_fg, default_bg) == ERR) {
711                 default_bg = COLOR_BLACK;
712                 default_fg = COLOR_WHITE;
713         }
715         for (type = 0; type < ARRAY_SIZE(line_info); type++) {
716                 struct line_info *info = &line_info[type];
717                 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
718                 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
720                 init_pair(type, fg, bg);
721         }
724 struct line {
725         enum line_type type;
727         /* State flags */
728         unsigned int selected:1;
729         unsigned int dirty:1;
731         void *data;             /* User data */
732 };
735 /*
736  * Keys
737  */
739 struct keybinding {
740         int alias;
741         enum request request;
742         struct keybinding *next;
743 };
745 static struct keybinding default_keybindings[] = {
746         /* View switching */
747         { 'm',          REQ_VIEW_MAIN },
748         { 'd',          REQ_VIEW_DIFF },
749         { 'l',          REQ_VIEW_LOG },
750         { 't',          REQ_VIEW_TREE },
751         { 'f',          REQ_VIEW_BLOB },
752         { 'B',          REQ_VIEW_BLAME },
753         { 'p',          REQ_VIEW_PAGER },
754         { 'h',          REQ_VIEW_HELP },
755         { 'S',          REQ_VIEW_STATUS },
756         { 'c',          REQ_VIEW_STAGE },
758         /* View manipulation */
759         { 'q',          REQ_VIEW_CLOSE },
760         { KEY_TAB,      REQ_VIEW_NEXT },
761         { KEY_RETURN,   REQ_ENTER },
762         { KEY_UP,       REQ_PREVIOUS },
763         { KEY_DOWN,     REQ_NEXT },
764         { 'R',          REQ_REFRESH },
765         { KEY_F(5),     REQ_REFRESH },
766         { 'O',          REQ_MAXIMIZE },
768         /* Cursor navigation */
769         { 'k',          REQ_MOVE_UP },
770         { 'j',          REQ_MOVE_DOWN },
771         { KEY_HOME,     REQ_MOVE_FIRST_LINE },
772         { KEY_END,      REQ_MOVE_LAST_LINE },
773         { KEY_NPAGE,    REQ_MOVE_PAGE_DOWN },
774         { ' ',          REQ_MOVE_PAGE_DOWN },
775         { KEY_PPAGE,    REQ_MOVE_PAGE_UP },
776         { 'b',          REQ_MOVE_PAGE_UP },
777         { '-',          REQ_MOVE_PAGE_UP },
779         /* Scrolling */
780         { KEY_IC,       REQ_SCROLL_LINE_UP },
781         { KEY_DC,       REQ_SCROLL_LINE_DOWN },
782         { 'w',          REQ_SCROLL_PAGE_UP },
783         { 's',          REQ_SCROLL_PAGE_DOWN },
785         /* Searching */
786         { '/',          REQ_SEARCH },
787         { '?',          REQ_SEARCH_BACK },
788         { 'n',          REQ_FIND_NEXT },
789         { 'N',          REQ_FIND_PREV },
791         /* Misc */
792         { 'Q',          REQ_QUIT },
793         { 'z',          REQ_STOP_LOADING },
794         { 'v',          REQ_SHOW_VERSION },
795         { 'r',          REQ_SCREEN_REDRAW },
796         { '.',          REQ_TOGGLE_LINENO },
797         { 'D',          REQ_TOGGLE_DATE },
798         { 'A',          REQ_TOGGLE_AUTHOR },
799         { 'g',          REQ_TOGGLE_REV_GRAPH },
800         { 'F',          REQ_TOGGLE_REFS },
801         { ':',          REQ_PROMPT },
802         { 'u',          REQ_STATUS_UPDATE },
803         { '!',          REQ_STATUS_REVERT },
804         { 'M',          REQ_STATUS_MERGE },
805         { '@',          REQ_STAGE_NEXT },
806         { ',',          REQ_TREE_PARENT },
807         { 'e',          REQ_EDIT },
809         /* Using the ncurses SIGWINCH handler. */
810         { KEY_RESIZE,   REQ_SCREEN_RESIZE },
811 };
813 #define KEYMAP_INFO \
814         KEYMAP_(GENERIC), \
815         KEYMAP_(MAIN), \
816         KEYMAP_(DIFF), \
817         KEYMAP_(LOG), \
818         KEYMAP_(TREE), \
819         KEYMAP_(BLOB), \
820         KEYMAP_(BLAME), \
821         KEYMAP_(PAGER), \
822         KEYMAP_(HELP), \
823         KEYMAP_(STATUS), \
824         KEYMAP_(STAGE)
826 enum keymap {
827 #define KEYMAP_(name) KEYMAP_##name
828         KEYMAP_INFO
829 #undef  KEYMAP_
830 };
832 static struct int_map keymap_table[] = {
833 #define KEYMAP_(name) { #name, STRING_SIZE(#name), KEYMAP_##name }
834         KEYMAP_INFO
835 #undef  KEYMAP_
836 };
838 #define set_keymap(map, name) \
839         set_from_int_map(keymap_table, ARRAY_SIZE(keymap_table), map, name, strlen(name))
841 static struct keybinding *keybindings[ARRAY_SIZE(keymap_table)];
843 static void
844 add_keybinding(enum keymap keymap, enum request request, int key)
846         struct keybinding *keybinding;
848         keybinding = calloc(1, sizeof(*keybinding));
849         if (!keybinding)
850                 die("Failed to allocate keybinding");
852         keybinding->alias = key;
853         keybinding->request = request;
854         keybinding->next = keybindings[keymap];
855         keybindings[keymap] = keybinding;
858 /* Looks for a key binding first in the given map, then in the generic map, and
859  * lastly in the default keybindings. */
860 static enum request
861 get_keybinding(enum keymap keymap, int key)
863         struct keybinding *kbd;
864         int i;
866         for (kbd = keybindings[keymap]; kbd; kbd = kbd->next)
867                 if (kbd->alias == key)
868                         return kbd->request;
870         for (kbd = keybindings[KEYMAP_GENERIC]; kbd; kbd = kbd->next)
871                 if (kbd->alias == key)
872                         return kbd->request;
874         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
875                 if (default_keybindings[i].alias == key)
876                         return default_keybindings[i].request;
878         return (enum request) key;
882 struct key {
883         const char *name;
884         int value;
885 };
887 static struct key key_table[] = {
888         { "Enter",      KEY_RETURN },
889         { "Space",      ' ' },
890         { "Backspace",  KEY_BACKSPACE },
891         { "Tab",        KEY_TAB },
892         { "Escape",     KEY_ESC },
893         { "Left",       KEY_LEFT },
894         { "Right",      KEY_RIGHT },
895         { "Up",         KEY_UP },
896         { "Down",       KEY_DOWN },
897         { "Insert",     KEY_IC },
898         { "Delete",     KEY_DC },
899         { "Hash",       '#' },
900         { "Home",       KEY_HOME },
901         { "End",        KEY_END },
902         { "PageUp",     KEY_PPAGE },
903         { "PageDown",   KEY_NPAGE },
904         { "F1",         KEY_F(1) },
905         { "F2",         KEY_F(2) },
906         { "F3",         KEY_F(3) },
907         { "F4",         KEY_F(4) },
908         { "F5",         KEY_F(5) },
909         { "F6",         KEY_F(6) },
910         { "F7",         KEY_F(7) },
911         { "F8",         KEY_F(8) },
912         { "F9",         KEY_F(9) },
913         { "F10",        KEY_F(10) },
914         { "F11",        KEY_F(11) },
915         { "F12",        KEY_F(12) },
916 };
918 static int
919 get_key_value(const char *name)
921         int i;
923         for (i = 0; i < ARRAY_SIZE(key_table); i++)
924                 if (!strcasecmp(key_table[i].name, name))
925                         return key_table[i].value;
927         if (strlen(name) == 1 && isprint(*name))
928                 return (int) *name;
930         return ERR;
933 static const char *
934 get_key_name(int key_value)
936         static char key_char[] = "'X'";
937         const char *seq = NULL;
938         int key;
940         for (key = 0; key < ARRAY_SIZE(key_table); key++)
941                 if (key_table[key].value == key_value)
942                         seq = key_table[key].name;
944         if (seq == NULL &&
945             key_value < 127 &&
946             isprint(key_value)) {
947                 key_char[1] = (char) key_value;
948                 seq = key_char;
949         }
951         return seq ? seq : "(no key)";
954 static const char *
955 get_key(enum request request)
957         static char buf[BUFSIZ];
958         size_t pos = 0;
959         char *sep = "";
960         int i;
962         buf[pos] = 0;
964         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
965                 struct keybinding *keybinding = &default_keybindings[i];
967                 if (keybinding->request != request)
968                         continue;
970                 if (!string_format_from(buf, &pos, "%s%s", sep,
971                                         get_key_name(keybinding->alias)))
972                         return "Too many keybindings!";
973                 sep = ", ";
974         }
976         return buf;
979 struct run_request {
980         enum keymap keymap;
981         int key;
982         char cmd[SIZEOF_STR];
983 };
985 static struct run_request *run_request;
986 static size_t run_requests;
988 static enum request
989 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
991         struct run_request *req;
992         char cmd[SIZEOF_STR];
993         size_t bufpos;
995         for (bufpos = 0; argc > 0; argc--, argv++)
996                 if (!string_format_from(cmd, &bufpos, "%s ", *argv))
997                         return REQ_NONE;
999         req = realloc(run_request, (run_requests + 1) * sizeof(*run_request));
1000         if (!req)
1001                 return REQ_NONE;
1003         run_request = req;
1004         req = &run_request[run_requests++];
1005         string_copy(req->cmd, cmd);
1006         req->keymap = keymap;
1007         req->key = key;
1009         return REQ_NONE + run_requests;
1012 static struct run_request *
1013 get_run_request(enum request request)
1015         if (request <= REQ_NONE)
1016                 return NULL;
1017         return &run_request[request - REQ_NONE - 1];
1020 static void
1021 add_builtin_run_requests(void)
1023         struct {
1024                 enum keymap keymap;
1025                 int key;
1026                 const char *argv[1];
1027         } reqs[] = {
1028                 { KEYMAP_MAIN,    'C', { "git cherry-pick %(commit)" } },
1029                 { KEYMAP_GENERIC, 'G', { "git gc" } },
1030         };
1031         int i;
1033         for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1034                 enum request req;
1036                 req = add_run_request(reqs[i].keymap, reqs[i].key, 1, reqs[i].argv);
1037                 if (req != REQ_NONE)
1038                         add_keybinding(reqs[i].keymap, req, reqs[i].key);
1039         }
1042 /*
1043  * User config file handling.
1044  */
1046 static struct int_map color_map[] = {
1047 #define COLOR_MAP(name) { #name, STRING_SIZE(#name), COLOR_##name }
1048         COLOR_MAP(DEFAULT),
1049         COLOR_MAP(BLACK),
1050         COLOR_MAP(BLUE),
1051         COLOR_MAP(CYAN),
1052         COLOR_MAP(GREEN),
1053         COLOR_MAP(MAGENTA),
1054         COLOR_MAP(RED),
1055         COLOR_MAP(WHITE),
1056         COLOR_MAP(YELLOW),
1057 };
1059 #define set_color(color, name) \
1060         set_from_int_map(color_map, ARRAY_SIZE(color_map), color, name, strlen(name))
1062 static struct int_map attr_map[] = {
1063 #define ATTR_MAP(name) { #name, STRING_SIZE(#name), A_##name }
1064         ATTR_MAP(NORMAL),
1065         ATTR_MAP(BLINK),
1066         ATTR_MAP(BOLD),
1067         ATTR_MAP(DIM),
1068         ATTR_MAP(REVERSE),
1069         ATTR_MAP(STANDOUT),
1070         ATTR_MAP(UNDERLINE),
1071 };
1073 #define set_attribute(attr, name) \
1074         set_from_int_map(attr_map, ARRAY_SIZE(attr_map), attr, name, strlen(name))
1076 static int   config_lineno;
1077 static bool  config_errors;
1078 static const char *config_msg;
1080 /* Wants: object fgcolor bgcolor [attr] */
1081 static int
1082 option_color_command(int argc, const char *argv[])
1084         struct line_info *info;
1086         if (argc != 3 && argc != 4) {
1087                 config_msg = "Wrong number of arguments given to color command";
1088                 return ERR;
1089         }
1091         info = get_line_info(argv[0]);
1092         if (!info) {
1093                 if (!string_enum_compare(argv[0], "main-delim", strlen("main-delim"))) {
1094                         info = get_line_info("delimiter");
1096                 } else if (!string_enum_compare(argv[0], "main-date", strlen("main-date"))) {
1097                         info = get_line_info("date");
1099                 } else {
1100                         config_msg = "Unknown color name";
1101                         return ERR;
1102                 }
1103         }
1105         if (set_color(&info->fg, argv[1]) == ERR ||
1106             set_color(&info->bg, argv[2]) == ERR) {
1107                 config_msg = "Unknown color";
1108                 return ERR;
1109         }
1111         if (argc == 4 && set_attribute(&info->attr, argv[3]) == ERR) {
1112                 config_msg = "Unknown attribute";
1113                 return ERR;
1114         }
1116         return OK;
1119 static bool parse_bool(const char *s)
1121         return (!strcmp(s, "1") || !strcmp(s, "true") ||
1122                 !strcmp(s, "yes")) ? TRUE : FALSE;
1125 static int
1126 parse_int(const char *s, int default_value, int min, int max)
1128         int value = atoi(s);
1130         return (value < min || value > max) ? default_value : value;
1133 /* Wants: name = value */
1134 static int
1135 option_set_command(int argc, const char *argv[])
1137         if (argc != 3) {
1138                 config_msg = "Wrong number of arguments given to set command";
1139                 return ERR;
1140         }
1142         if (strcmp(argv[1], "=")) {
1143                 config_msg = "No value assigned";
1144                 return ERR;
1145         }
1147         if (!strcmp(argv[0], "show-author")) {
1148                 opt_author = parse_bool(argv[2]);
1149                 return OK;
1150         }
1152         if (!strcmp(argv[0], "show-date")) {
1153                 opt_date = parse_bool(argv[2]);
1154                 return OK;
1155         }
1157         if (!strcmp(argv[0], "show-rev-graph")) {
1158                 opt_rev_graph = parse_bool(argv[2]);
1159                 return OK;
1160         }
1162         if (!strcmp(argv[0], "show-refs")) {
1163                 opt_show_refs = parse_bool(argv[2]);
1164                 return OK;
1165         }
1167         if (!strcmp(argv[0], "show-line-numbers")) {
1168                 opt_line_number = parse_bool(argv[2]);
1169                 return OK;
1170         }
1172         if (!strcmp(argv[0], "line-graphics")) {
1173                 opt_line_graphics = parse_bool(argv[2]);
1174                 return OK;
1175         }
1177         if (!strcmp(argv[0], "line-number-interval")) {
1178                 opt_num_interval = parse_int(argv[2], opt_num_interval, 1, 1024);
1179                 return OK;
1180         }
1182         if (!strcmp(argv[0], "author-width")) {
1183                 opt_author_cols = parse_int(argv[2], opt_author_cols, 0, 1024);
1184                 return OK;
1185         }
1187         if (!strcmp(argv[0], "tab-size")) {
1188                 opt_tab_size = parse_int(argv[2], opt_tab_size, 1, 1024);
1189                 return OK;
1190         }
1192         if (!strcmp(argv[0], "commit-encoding")) {
1193                 const char *arg = argv[2];
1194                 int arglen = strlen(arg);
1196                 switch (arg[0]) {
1197                 case '"':
1198                 case '\'':
1199                         if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1200                                 config_msg = "Unmatched quotation";
1201                                 return ERR;
1202                         }
1203                         arg += 1; arglen -= 2;
1204                 default:
1205                         string_ncopy(opt_encoding, arg, strlen(arg));
1206                         return OK;
1207                 }
1208         }
1210         config_msg = "Unknown variable name";
1211         return ERR;
1214 /* Wants: mode request key */
1215 static int
1216 option_bind_command(int argc, const char *argv[])
1218         enum request request;
1219         int keymap;
1220         int key;
1222         if (argc < 3) {
1223                 config_msg = "Wrong number of arguments given to bind command";
1224                 return ERR;
1225         }
1227         if (set_keymap(&keymap, argv[0]) == ERR) {
1228                 config_msg = "Unknown key map";
1229                 return ERR;
1230         }
1232         key = get_key_value(argv[1]);
1233         if (key == ERR) {
1234                 config_msg = "Unknown key";
1235                 return ERR;
1236         }
1238         request = get_request(argv[2]);
1239         if (request == REQ_NONE) {
1240                 const char *obsolete[] = { "cherry-pick" };
1241                 size_t namelen = strlen(argv[2]);
1242                 int i;
1244                 for (i = 0; i < ARRAY_SIZE(obsolete); i++) {
1245                         if (namelen == strlen(obsolete[i]) &&
1246                             !string_enum_compare(obsolete[i], argv[2], namelen)) {
1247                                 config_msg = "Obsolete request name";
1248                                 return ERR;
1249                         }
1250                 }
1251         }
1252         if (request == REQ_NONE && *argv[2]++ == '!')
1253                 request = add_run_request(keymap, key, argc - 2, argv + 2);
1254         if (request == REQ_NONE) {
1255                 config_msg = "Unknown request name";
1256                 return ERR;
1257         }
1259         add_keybinding(keymap, request, key);
1261         return OK;
1264 static int
1265 set_option(const char *opt, char *value)
1267         const char *argv[SIZEOF_ARG];
1268         int valuelen;
1269         int argc = 0;
1271         /* Tokenize */
1272         while (argc < ARRAY_SIZE(argv) && (valuelen = strcspn(value, " \t"))) {
1273                 argv[argc++] = value;
1274                 value += valuelen;
1276                 /* Nothing more to tokenize or last available token. */
1277                 if (!*value || argc >= ARRAY_SIZE(argv))
1278                         break;
1280                 *value++ = 0;
1281                 while (isspace(*value))
1282                         value++;
1283         }
1285         if (!strcmp(opt, "color"))
1286                 return option_color_command(argc, argv);
1288         if (!strcmp(opt, "set"))
1289                 return option_set_command(argc, argv);
1291         if (!strcmp(opt, "bind"))
1292                 return option_bind_command(argc, argv);
1294         config_msg = "Unknown option command";
1295         return ERR;
1298 static int
1299 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
1301         int status = OK;
1303         config_lineno++;
1304         config_msg = "Internal error";
1306         /* Check for comment markers, since read_properties() will
1307          * only ensure opt and value are split at first " \t". */
1308         optlen = strcspn(opt, "#");
1309         if (optlen == 0)
1310                 return OK;
1312         if (opt[optlen] != 0) {
1313                 config_msg = "No option value";
1314                 status = ERR;
1316         }  else {
1317                 /* Look for comment endings in the value. */
1318                 size_t len = strcspn(value, "#");
1320                 if (len < valuelen) {
1321                         valuelen = len;
1322                         value[valuelen] = 0;
1323                 }
1325                 status = set_option(opt, value);
1326         }
1328         if (status == ERR) {
1329                 fprintf(stderr, "Error on line %d, near '%.*s': %s\n",
1330                         config_lineno, (int) optlen, opt, config_msg);
1331                 config_errors = TRUE;
1332         }
1334         /* Always keep going if errors are encountered. */
1335         return OK;
1338 static void
1339 load_option_file(const char *path)
1341         FILE *file;
1343         /* It's ok that the file doesn't exist. */
1344         file = fopen(path, "r");
1345         if (!file)
1346                 return;
1348         config_lineno = 0;
1349         config_errors = FALSE;
1351         if (read_properties(file, " \t", read_option) == ERR ||
1352             config_errors == TRUE)
1353                 fprintf(stderr, "Errors while loading %s.\n", path);
1356 static int
1357 load_options(void)
1359         const char *home = getenv("HOME");
1360         const char *tigrc_user = getenv("TIGRC_USER");
1361         const char *tigrc_system = getenv("TIGRC_SYSTEM");
1362         char buf[SIZEOF_STR];
1364         add_builtin_run_requests();
1366         if (!tigrc_system) {
1367                 if (!string_format(buf, "%s/tigrc", SYSCONFDIR))
1368                         return ERR;
1369                 tigrc_system = buf;
1370         }
1371         load_option_file(tigrc_system);
1373         if (!tigrc_user) {
1374                 if (!home || !string_format(buf, "%s/.tigrc", home))
1375                         return ERR;
1376                 tigrc_user = buf;
1377         }
1378         load_option_file(tigrc_user);
1380         return OK;
1384 /*
1385  * The viewer
1386  */
1388 struct view;
1389 struct view_ops;
1391 /* The display array of active views and the index of the current view. */
1392 static struct view *display[2];
1393 static unsigned int current_view;
1395 /* Reading from the prompt? */
1396 static bool input_mode = FALSE;
1398 #define foreach_displayed_view(view, i) \
1399         for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
1401 #define displayed_views()       (display[1] != NULL ? 2 : 1)
1403 /* Current head and commit ID */
1404 static char ref_blob[SIZEOF_REF]        = "";
1405 static char ref_commit[SIZEOF_REF]      = "HEAD";
1406 static char ref_head[SIZEOF_REF]        = "HEAD";
1408 struct view {
1409         const char *name;       /* View name */
1410         const char *cmd_fmt;    /* Default command line format */
1411         const char *cmd_env;    /* Command line set via environment */
1412         const char *id;         /* Points to either of ref_{head,commit,blob} */
1414         struct view_ops *ops;   /* View operations */
1416         enum keymap keymap;     /* What keymap does this view have */
1417         bool git_dir;           /* Whether the view requires a git directory. */
1419         char cmd[SIZEOF_STR];   /* Command buffer */
1420         char ref[SIZEOF_REF];   /* Hovered commit reference */
1421         char vid[SIZEOF_REF];   /* View ID. Set to id member when updating. */
1423         int height, width;      /* The width and height of the main window */
1424         WINDOW *win;            /* The main window */
1425         WINDOW *title;          /* The title window living below the main window */
1427         /* Navigation */
1428         unsigned long offset;   /* Offset of the window top */
1429         unsigned long lineno;   /* Current line number */
1431         /* Searching */
1432         char grep[SIZEOF_STR];  /* Search string */
1433         regex_t *regex;         /* Pre-compiled regex */
1435         /* If non-NULL, points to the view that opened this view. If this view
1436          * is closed tig will switch back to the parent view. */
1437         struct view *parent;
1439         /* Buffering */
1440         size_t lines;           /* Total number of lines */
1441         struct line *line;      /* Line index */
1442         size_t line_alloc;      /* Total number of allocated lines */
1443         size_t line_size;       /* Total number of used lines */
1444         unsigned int digits;    /* Number of digits in the lines member. */
1446         /* Drawing */
1447         struct line *curline;   /* Line currently being drawn. */
1448         enum line_type curtype; /* Attribute currently used for drawing. */
1449         unsigned long col;      /* Column when drawing. */
1451         /* Loading */
1452         FILE *pipe;
1453         time_t start_time;
1454 };
1456 struct view_ops {
1457         /* What type of content being displayed. Used in the title bar. */
1458         const char *type;
1459         /* Open and reads in all view content. */
1460         bool (*open)(struct view *view);
1461         /* Read one line; updates view->line. */
1462         bool (*read)(struct view *view, char *data);
1463         /* Draw one line; @lineno must be < view->height. */
1464         bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
1465         /* Depending on view handle a special requests. */
1466         enum request (*request)(struct view *view, enum request request, struct line *line);
1467         /* Search for regex in a line. */
1468         bool (*grep)(struct view *view, struct line *line);
1469         /* Select line */
1470         void (*select)(struct view *view, struct line *line);
1471 };
1473 static struct view_ops blame_ops;
1474 static struct view_ops blob_ops;
1475 static struct view_ops help_ops;
1476 static struct view_ops log_ops;
1477 static struct view_ops main_ops;
1478 static struct view_ops pager_ops;
1479 static struct view_ops stage_ops;
1480 static struct view_ops status_ops;
1481 static struct view_ops tree_ops;
1483 #define VIEW_STR(name, cmd, env, ref, ops, map, git) \
1484         { name, cmd, #env, ref, ops, map, git }
1486 #define VIEW_(id, name, ops, git, ref) \
1487         VIEW_STR(name, TIG_##id##_CMD,  TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
1490 static struct view views[] = {
1491         VIEW_(MAIN,   "main",   &main_ops,   TRUE,  ref_head),
1492         VIEW_(DIFF,   "diff",   &pager_ops,  TRUE,  ref_commit),
1493         VIEW_(LOG,    "log",    &log_ops,    TRUE,  ref_head),
1494         VIEW_(TREE,   "tree",   &tree_ops,   TRUE,  ref_commit),
1495         VIEW_(BLOB,   "blob",   &blob_ops,   TRUE,  ref_blob),
1496         VIEW_(BLAME,  "blame",  &blame_ops,  TRUE,  ref_commit),
1497         VIEW_(HELP,   "help",   &help_ops,   FALSE, ""),
1498         VIEW_(PAGER,  "pager",  &pager_ops,  FALSE, "stdin"),
1499         VIEW_(STATUS, "status", &status_ops, TRUE,  ""),
1500         VIEW_(STAGE,  "stage",  &stage_ops,  TRUE,  ""),
1501 };
1503 #define VIEW(req)       (&views[(req) - REQ_OFFSET - 1])
1504 #define VIEW_REQ(view)  ((view) - views + REQ_OFFSET + 1)
1506 #define foreach_view(view, i) \
1507         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
1509 #define view_is_displayed(view) \
1510         (view == display[0] || view == display[1])
1513 enum line_graphic {
1514         LINE_GRAPHIC_VLINE
1515 };
1517 static int line_graphics[] = {
1518         /* LINE_GRAPHIC_VLINE: */ '|'
1519 };
1521 static inline void
1522 set_view_attr(struct view *view, enum line_type type)
1524         if (!view->curline->selected && view->curtype != type) {
1525                 wattrset(view->win, get_line_attr(type));
1526                 wchgat(view->win, -1, 0, type, NULL);
1527                 view->curtype = type;
1528         }
1531 static int
1532 draw_chars(struct view *view, enum line_type type, const char *string,
1533            int max_len, bool use_tilde)
1535         int len = 0;
1536         int col = 0;
1537         int trimmed = FALSE;
1539         if (max_len <= 0)
1540                 return 0;
1542         if (opt_utf8) {
1543                 len = utf8_length(string, &col, max_len, &trimmed, use_tilde);
1544         } else {
1545                 col = len = strlen(string);
1546                 if (len > max_len) {
1547                         if (use_tilde) {
1548                                 max_len -= 1;
1549                         }
1550                         col = len = max_len;
1551                         trimmed = TRUE;
1552                 }
1553         }
1555         set_view_attr(view, type);
1556         waddnstr(view->win, string, len);
1557         if (trimmed && use_tilde) {
1558                 set_view_attr(view, LINE_DELIMITER);
1559                 waddch(view->win, '~');
1560                 col++;
1561         }
1563         return col;
1566 static int
1567 draw_space(struct view *view, enum line_type type, int max, int spaces)
1569         static char space[] = "                    ";
1570         int col = 0;
1572         spaces = MIN(max, spaces);
1574         while (spaces > 0) {
1575                 int len = MIN(spaces, sizeof(space) - 1);
1577                 col += draw_chars(view, type, space, spaces, FALSE);
1578                 spaces -= len;
1579         }
1581         return col;
1584 static bool
1585 draw_lineno(struct view *view, unsigned int lineno)
1587         char number[10];
1588         int digits3 = view->digits < 3 ? 3 : view->digits;
1589         int max_number = MIN(digits3, STRING_SIZE(number));
1590         int max = view->width - view->col;
1591         int col;
1593         if (max < max_number)
1594                 max_number = max;
1596         lineno += view->offset + 1;
1597         if (lineno == 1 || (lineno % opt_num_interval) == 0) {
1598                 static char fmt[] = "%1ld";
1600                 if (view->digits <= 9)
1601                         fmt[1] = '0' + digits3;
1603                 if (!string_format(number, fmt, lineno))
1604                         number[0] = 0;
1605                 col = draw_chars(view, LINE_LINE_NUMBER, number, max_number, TRUE);
1606         } else {
1607                 col = draw_space(view, LINE_LINE_NUMBER, max_number, max_number);
1608         }
1610         if (col < max) {
1611                 set_view_attr(view, LINE_DEFAULT);
1612                 waddch(view->win, line_graphics[LINE_GRAPHIC_VLINE]);
1613                 col++;
1614         }
1616         if (col < max)
1617                 col += draw_space(view, LINE_DEFAULT, max - col, 1);
1618         view->col += col;
1620         return view->width - view->col <= 0;
1623 static bool
1624 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
1626         view->col += draw_chars(view, type, string, view->width - view->col, trim);
1627         return view->width - view->col <= 0;
1630 static bool
1631 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
1633         int max = view->width - view->col;
1634         int i;
1636         if (max < size)
1637                 size = max;
1639         set_view_attr(view, type);
1640         /* Using waddch() instead of waddnstr() ensures that
1641          * they'll be rendered correctly for the cursor line. */
1642         for (i = 0; i < size; i++)
1643                 waddch(view->win, graphic[i]);
1645         view->col += size;
1646         if (size < max) {
1647                 waddch(view->win, ' ');
1648                 view->col++;
1649         }
1651         return view->width - view->col <= 0;
1654 static bool
1655 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
1657         int max = MIN(view->width - view->col, len);
1658         int col;
1660         if (text)
1661                 col = draw_chars(view, type, text, max - 1, trim);
1662         else
1663                 col = draw_space(view, type, max - 1, max - 1);
1665         view->col += col + draw_space(view, LINE_DEFAULT, max - col, max - col);
1666         return view->width - view->col <= 0;
1669 static bool
1670 draw_date(struct view *view, struct tm *time)
1672         char buf[DATE_COLS];
1673         char *date;
1674         int timelen = 0;
1676         if (time)
1677                 timelen = strftime(buf, sizeof(buf), DATE_FORMAT, time);
1678         date = timelen ? buf : NULL;
1680         return draw_field(view, LINE_DATE, date, DATE_COLS, FALSE);
1683 static bool
1684 draw_view_line(struct view *view, unsigned int lineno)
1686         struct line *line;
1687         bool selected = (view->offset + lineno == view->lineno);
1688         bool draw_ok;
1690         assert(view_is_displayed(view));
1692         if (view->offset + lineno >= view->lines)
1693                 return FALSE;
1695         line = &view->line[view->offset + lineno];
1697         wmove(view->win, lineno, 0);
1698         view->col = 0;
1699         view->curline = line;
1700         view->curtype = LINE_NONE;
1701         line->selected = FALSE;
1703         if (selected) {
1704                 set_view_attr(view, LINE_CURSOR);
1705                 line->selected = TRUE;
1706                 view->ops->select(view, line);
1707         } else if (line->selected) {
1708                 wclrtoeol(view->win);
1709         }
1711         scrollok(view->win, FALSE);
1712         draw_ok = view->ops->draw(view, line, lineno);
1713         scrollok(view->win, TRUE);
1715         return draw_ok;
1718 static void
1719 redraw_view_dirty(struct view *view)
1721         bool dirty = FALSE;
1722         int lineno;
1724         for (lineno = 0; lineno < view->height; lineno++) {
1725                 struct line *line = &view->line[view->offset + lineno];
1727                 if (!line->dirty)
1728                         continue;
1729                 line->dirty = 0;
1730                 dirty = TRUE;
1731                 if (!draw_view_line(view, lineno))
1732                         break;
1733         }
1735         if (!dirty)
1736                 return;
1737         redrawwin(view->win);
1738         if (input_mode)
1739                 wnoutrefresh(view->win);
1740         else
1741                 wrefresh(view->win);
1744 static void
1745 redraw_view_from(struct view *view, int lineno)
1747         assert(0 <= lineno && lineno < view->height);
1749         for (; lineno < view->height; lineno++) {
1750                 if (!draw_view_line(view, lineno))
1751                         break;
1752         }
1754         redrawwin(view->win);
1755         if (input_mode)
1756                 wnoutrefresh(view->win);
1757         else
1758                 wrefresh(view->win);
1761 static void
1762 redraw_view(struct view *view)
1764         wclear(view->win);
1765         redraw_view_from(view, 0);
1769 static void
1770 update_view_title(struct view *view)
1772         char buf[SIZEOF_STR];
1773         char state[SIZEOF_STR];
1774         size_t bufpos = 0, statelen = 0;
1776         assert(view_is_displayed(view));
1778         if (view != VIEW(REQ_VIEW_STATUS) && (view->lines || view->pipe)) {
1779                 unsigned int view_lines = view->offset + view->height;
1780                 unsigned int lines = view->lines
1781                                    ? MIN(view_lines, view->lines) * 100 / view->lines
1782                                    : 0;
1784                 string_format_from(state, &statelen, "- %s %d of %d (%d%%)",
1785                                    view->ops->type,
1786                                    view->lineno + 1,
1787                                    view->lines,
1788                                    lines);
1790                 if (view->pipe) {
1791                         time_t secs = time(NULL) - view->start_time;
1793                         /* Three git seconds are a long time ... */
1794                         if (secs > 2)
1795                                 string_format_from(state, &statelen, " %lds", secs);
1796                 }
1797         }
1799         string_format_from(buf, &bufpos, "[%s]", view->name);
1800         if (*view->ref && bufpos < view->width) {
1801                 size_t refsize = strlen(view->ref);
1802                 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
1804                 if (minsize < view->width)
1805                         refsize = view->width - minsize + 7;
1806                 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
1807         }
1809         if (statelen && bufpos < view->width) {
1810                 string_format_from(buf, &bufpos, " %s", state);
1811         }
1813         if (view == display[current_view])
1814                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
1815         else
1816                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
1818         mvwaddnstr(view->title, 0, 0, buf, bufpos);
1819         wclrtoeol(view->title);
1820         wmove(view->title, 0, view->width - 1);
1822         if (input_mode)
1823                 wnoutrefresh(view->title);
1824         else
1825                 wrefresh(view->title);
1828 static void
1829 resize_display(void)
1831         int offset, i;
1832         struct view *base = display[0];
1833         struct view *view = display[1] ? display[1] : display[0];
1835         /* Setup window dimensions */
1837         getmaxyx(stdscr, base->height, base->width);
1839         /* Make room for the status window. */
1840         base->height -= 1;
1842         if (view != base) {
1843                 /* Horizontal split. */
1844                 view->width   = base->width;
1845                 view->height  = SCALE_SPLIT_VIEW(base->height);
1846                 base->height -= view->height;
1848                 /* Make room for the title bar. */
1849                 view->height -= 1;
1850         }
1852         /* Make room for the title bar. */
1853         base->height -= 1;
1855         offset = 0;
1857         foreach_displayed_view (view, i) {
1858                 if (!view->win) {
1859                         view->win = newwin(view->height, 0, offset, 0);
1860                         if (!view->win)
1861                                 die("Failed to create %s view", view->name);
1863                         scrollok(view->win, TRUE);
1865                         view->title = newwin(1, 0, offset + view->height, 0);
1866                         if (!view->title)
1867                                 die("Failed to create title window");
1869                 } else {
1870                         wresize(view->win, view->height, view->width);
1871                         mvwin(view->win,   offset, 0);
1872                         mvwin(view->title, offset + view->height, 0);
1873                 }
1875                 offset += view->height + 1;
1876         }
1879 static void
1880 redraw_display(void)
1882         struct view *view;
1883         int i;
1885         foreach_displayed_view (view, i) {
1886                 redraw_view(view);
1887                 update_view_title(view);
1888         }
1891 static void
1892 update_display_cursor(struct view *view)
1894         /* Move the cursor to the right-most column of the cursor line.
1895          *
1896          * XXX: This could turn out to be a bit expensive, but it ensures that
1897          * the cursor does not jump around. */
1898         if (view->lines) {
1899                 wmove(view->win, view->lineno - view->offset, view->width - 1);
1900                 wrefresh(view->win);
1901         }
1904 /*
1905  * Navigation
1906  */
1908 /* Scrolling backend */
1909 static void
1910 do_scroll_view(struct view *view, int lines)
1912         bool redraw_current_line = FALSE;
1914         /* The rendering expects the new offset. */
1915         view->offset += lines;
1917         assert(0 <= view->offset && view->offset < view->lines);
1918         assert(lines);
1920         /* Move current line into the view. */
1921         if (view->lineno < view->offset) {
1922                 view->lineno = view->offset;
1923                 redraw_current_line = TRUE;
1924         } else if (view->lineno >= view->offset + view->height) {
1925                 view->lineno = view->offset + view->height - 1;
1926                 redraw_current_line = TRUE;
1927         }
1929         assert(view->offset <= view->lineno && view->lineno < view->lines);
1931         /* Redraw the whole screen if scrolling is pointless. */
1932         if (view->height < ABS(lines)) {
1933                 redraw_view(view);
1935         } else {
1936                 int line = lines > 0 ? view->height - lines : 0;
1937                 int end = line + ABS(lines);
1939                 wscrl(view->win, lines);
1941                 for (; line < end; line++) {
1942                         if (!draw_view_line(view, line))
1943                                 break;
1944                 }
1946                 if (redraw_current_line)
1947                         draw_view_line(view, view->lineno - view->offset);
1948         }
1950         redrawwin(view->win);
1951         wrefresh(view->win);
1952         report("");
1955 /* Scroll frontend */
1956 static void
1957 scroll_view(struct view *view, enum request request)
1959         int lines = 1;
1961         assert(view_is_displayed(view));
1963         switch (request) {
1964         case REQ_SCROLL_PAGE_DOWN:
1965                 lines = view->height;
1966         case REQ_SCROLL_LINE_DOWN:
1967                 if (view->offset + lines > view->lines)
1968                         lines = view->lines - view->offset;
1970                 if (lines == 0 || view->offset + view->height >= view->lines) {
1971                         report("Cannot scroll beyond the last line");
1972                         return;
1973                 }
1974                 break;
1976         case REQ_SCROLL_PAGE_UP:
1977                 lines = view->height;
1978         case REQ_SCROLL_LINE_UP:
1979                 if (lines > view->offset)
1980                         lines = view->offset;
1982                 if (lines == 0) {
1983                         report("Cannot scroll beyond the first line");
1984                         return;
1985                 }
1987                 lines = -lines;
1988                 break;
1990         default:
1991                 die("request %d not handled in switch", request);
1992         }
1994         do_scroll_view(view, lines);
1997 /* Cursor moving */
1998 static void
1999 move_view(struct view *view, enum request request)
2001         int scroll_steps = 0;
2002         int steps;
2004         switch (request) {
2005         case REQ_MOVE_FIRST_LINE:
2006                 steps = -view->lineno;
2007                 break;
2009         case REQ_MOVE_LAST_LINE:
2010                 steps = view->lines - view->lineno - 1;
2011                 break;
2013         case REQ_MOVE_PAGE_UP:
2014                 steps = view->height > view->lineno
2015                       ? -view->lineno : -view->height;
2016                 break;
2018         case REQ_MOVE_PAGE_DOWN:
2019                 steps = view->lineno + view->height >= view->lines
2020                       ? view->lines - view->lineno - 1 : view->height;
2021                 break;
2023         case REQ_MOVE_UP:
2024                 steps = -1;
2025                 break;
2027         case REQ_MOVE_DOWN:
2028                 steps = 1;
2029                 break;
2031         default:
2032                 die("request %d not handled in switch", request);
2033         }
2035         if (steps <= 0 && view->lineno == 0) {
2036                 report("Cannot move beyond the first line");
2037                 return;
2039         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2040                 report("Cannot move beyond the last line");
2041                 return;
2042         }
2044         /* Move the current line */
2045         view->lineno += steps;
2046         assert(0 <= view->lineno && view->lineno < view->lines);
2048         /* Check whether the view needs to be scrolled */
2049         if (view->lineno < view->offset ||
2050             view->lineno >= view->offset + view->height) {
2051                 scroll_steps = steps;
2052                 if (steps < 0 && -steps > view->offset) {
2053                         scroll_steps = -view->offset;
2055                 } else if (steps > 0) {
2056                         if (view->lineno == view->lines - 1 &&
2057                             view->lines > view->height) {
2058                                 scroll_steps = view->lines - view->offset - 1;
2059                                 if (scroll_steps >= view->height)
2060                                         scroll_steps -= view->height - 1;
2061                         }
2062                 }
2063         }
2065         if (!view_is_displayed(view)) {
2066                 view->offset += scroll_steps;
2067                 assert(0 <= view->offset && view->offset < view->lines);
2068                 view->ops->select(view, &view->line[view->lineno]);
2069                 return;
2070         }
2072         /* Repaint the old "current" line if we be scrolling */
2073         if (ABS(steps) < view->height)
2074                 draw_view_line(view, view->lineno - steps - view->offset);
2076         if (scroll_steps) {
2077                 do_scroll_view(view, scroll_steps);
2078                 return;
2079         }
2081         /* Draw the current line */
2082         draw_view_line(view, view->lineno - view->offset);
2084         redrawwin(view->win);
2085         wrefresh(view->win);
2086         report("");
2090 /*
2091  * Searching
2092  */
2094 static void search_view(struct view *view, enum request request);
2096 static bool
2097 find_next_line(struct view *view, unsigned long lineno, struct line *line)
2099         assert(view_is_displayed(view));
2101         if (!view->ops->grep(view, line))
2102                 return FALSE;
2104         if (lineno - view->offset >= view->height) {
2105                 view->offset = lineno;
2106                 view->lineno = lineno;
2107                 redraw_view(view);
2109         } else {
2110                 unsigned long old_lineno = view->lineno - view->offset;
2112                 view->lineno = lineno;
2113                 draw_view_line(view, old_lineno);
2115                 draw_view_line(view, view->lineno - view->offset);
2116                 redrawwin(view->win);
2117                 wrefresh(view->win);
2118         }
2120         report("Line %ld matches '%s'", lineno + 1, view->grep);
2121         return TRUE;
2124 static void
2125 find_next(struct view *view, enum request request)
2127         unsigned long lineno = view->lineno;
2128         int direction;
2130         if (!*view->grep) {
2131                 if (!*opt_search)
2132                         report("No previous search");
2133                 else
2134                         search_view(view, request);
2135                 return;
2136         }
2138         switch (request) {
2139         case REQ_SEARCH:
2140         case REQ_FIND_NEXT:
2141                 direction = 1;
2142                 break;
2144         case REQ_SEARCH_BACK:
2145         case REQ_FIND_PREV:
2146                 direction = -1;
2147                 break;
2149         default:
2150                 return;
2151         }
2153         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
2154                 lineno += direction;
2156         /* Note, lineno is unsigned long so will wrap around in which case it
2157          * will become bigger than view->lines. */
2158         for (; lineno < view->lines; lineno += direction) {
2159                 struct line *line = &view->line[lineno];
2161                 if (find_next_line(view, lineno, line))
2162                         return;
2163         }
2165         report("No match found for '%s'", view->grep);
2168 static void
2169 search_view(struct view *view, enum request request)
2171         int regex_err;
2173         if (view->regex) {
2174                 regfree(view->regex);
2175                 *view->grep = 0;
2176         } else {
2177                 view->regex = calloc(1, sizeof(*view->regex));
2178                 if (!view->regex)
2179                         return;
2180         }
2182         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
2183         if (regex_err != 0) {
2184                 char buf[SIZEOF_STR] = "unknown error";
2186                 regerror(regex_err, view->regex, buf, sizeof(buf));
2187                 report("Search failed: %s", buf);
2188                 return;
2189         }
2191         string_copy(view->grep, opt_search);
2193         find_next(view, request);
2196 /*
2197  * Incremental updating
2198  */
2200 static void
2201 reset_view(struct view *view)
2203         int i;
2205         for (i = 0; i < view->lines; i++)
2206                 free(view->line[i].data);
2207         free(view->line);
2209         view->line = NULL;
2210         view->offset = 0;
2211         view->lines  = 0;
2212         view->lineno = 0;
2213         view->line_size = 0;
2214         view->line_alloc = 0;
2215         view->vid[0] = 0;
2218 static void
2219 end_update(struct view *view, bool force)
2221         if (!view->pipe)
2222                 return;
2223         while (!view->ops->read(view, NULL))
2224                 if (!force)
2225                         return;
2226         set_nonblocking_input(FALSE);
2227         if (view->pipe == stdin)
2228                 fclose(view->pipe);
2229         else
2230                 pclose(view->pipe);
2231         view->pipe = NULL;
2234 static bool
2235 begin_update(struct view *view, bool refresh)
2237         if (opt_cmd[0]) {
2238                 string_copy(view->cmd, opt_cmd);
2239                 opt_cmd[0] = 0;
2240                 /* When running random commands, initially show the
2241                  * command in the title. However, it maybe later be
2242                  * overwritten if a commit line is selected. */
2243                 if (view == VIEW(REQ_VIEW_PAGER))
2244                         string_copy(view->ref, view->cmd);
2245                 else
2246                         view->ref[0] = 0;
2248         } else if (view == VIEW(REQ_VIEW_TREE)) {
2249                 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
2250                 char path[SIZEOF_STR];
2252                 if (strcmp(view->vid, view->id))
2253                         opt_path[0] = path[0] = 0;
2254                 else if (sq_quote(path, 0, opt_path) >= sizeof(path))
2255                         return FALSE;
2257                 if (!string_format(view->cmd, format, view->id, path))
2258                         return FALSE;
2260         } else if (!refresh) {
2261                 const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
2262                 const char *id = view->id;
2264                 if (!string_format(view->cmd, format, id, id, id, id, id))
2265                         return FALSE;
2267                 /* Put the current ref_* value to the view title ref
2268                  * member. This is needed by the blob view. Most other
2269                  * views sets it automatically after loading because the
2270                  * first line is a commit line. */
2271                 string_copy_rev(view->ref, view->id);
2272         }
2274         /* Special case for the pager view. */
2275         if (opt_pipe) {
2276                 view->pipe = opt_pipe;
2277                 opt_pipe = NULL;
2278         } else {
2279                 view->pipe = popen(view->cmd, "r");
2280         }
2282         if (!view->pipe)
2283                 return FALSE;
2285         set_nonblocking_input(TRUE);
2286         reset_view(view);
2287         string_copy_rev(view->vid, view->id);
2289         view->start_time = time(NULL);
2291         return TRUE;
2294 #define ITEM_CHUNK_SIZE 256
2295 static void *
2296 realloc_items(void *mem, size_t *size, size_t new_size, size_t item_size)
2298         size_t num_chunks = *size / ITEM_CHUNK_SIZE;
2299         size_t num_chunks_new = (new_size + ITEM_CHUNK_SIZE - 1) / ITEM_CHUNK_SIZE;
2301         if (mem == NULL || num_chunks != num_chunks_new) {
2302                 *size = num_chunks_new * ITEM_CHUNK_SIZE;
2303                 mem = realloc(mem, *size * item_size);
2304         }
2306         return mem;
2309 static struct line *
2310 realloc_lines(struct view *view, size_t line_size)
2312         size_t alloc = view->line_alloc;
2313         struct line *tmp = realloc_items(view->line, &alloc, line_size,
2314                                          sizeof(*view->line));
2316         if (!tmp)
2317                 return NULL;
2319         view->line = tmp;
2320         view->line_alloc = alloc;
2321         view->line_size = line_size;
2322         return view->line;
2325 static bool
2326 update_view(struct view *view)
2328         char in_buffer[BUFSIZ];
2329         char out_buffer[BUFSIZ * 2];
2330         char *line;
2331         /* The number of lines to read. If too low it will cause too much
2332          * redrawing (and possible flickering), if too high responsiveness
2333          * will suffer. */
2334         unsigned long lines = view->height;
2335         int redraw_from = -1;
2337         if (!view->pipe)
2338                 return TRUE;
2340         /* Only redraw if lines are visible. */
2341         if (view->offset + view->height >= view->lines)
2342                 redraw_from = view->lines - view->offset;
2344         /* FIXME: This is probably not perfect for backgrounded views. */
2345         if (!realloc_lines(view, view->lines + lines))
2346                 goto alloc_error;
2348         while ((line = fgets(in_buffer, sizeof(in_buffer), view->pipe))) {
2349                 size_t linelen = strlen(line);
2351                 if (linelen)
2352                         line[linelen - 1] = 0;
2354                 if (opt_iconv != ICONV_NONE) {
2355                         ICONV_CONST char *inbuf = line;
2356                         size_t inlen = linelen;
2358                         char *outbuf = out_buffer;
2359                         size_t outlen = sizeof(out_buffer);
2361                         size_t ret;
2363                         ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen);
2364                         if (ret != (size_t) -1) {
2365                                 line = out_buffer;
2366                                 linelen = strlen(out_buffer);
2367                         }
2368                 }
2370                 if (!view->ops->read(view, line))
2371                         goto alloc_error;
2373                 if (lines-- == 1)
2374                         break;
2375         }
2377         {
2378                 int digits;
2380                 lines = view->lines;
2381                 for (digits = 0; lines; digits++)
2382                         lines /= 10;
2384                 /* Keep the displayed view in sync with line number scaling. */
2385                 if (digits != view->digits) {
2386                         view->digits = digits;
2387                         redraw_from = 0;
2388                 }
2389         }
2391         if (ferror(view->pipe) && errno != 0) {
2392                 report("Failed to read: %s", strerror(errno));
2393                 end_update(view, TRUE);
2395         } else if (feof(view->pipe)) {
2396                 report("");
2397                 end_update(view, FALSE);
2398         }
2400         if (view == VIEW(REQ_VIEW_TREE)) {
2401                 /* Clear the view and redraw everything since the tree sorting
2402                  * might have rearranged things. */
2403                 redraw_view(view);
2405         } else if (redraw_from >= 0) {
2406                 /* If this is an incremental update, redraw the previous line
2407                  * since for commits some members could have changed when
2408                  * loading the main view. */
2409                 if (redraw_from > 0)
2410                         redraw_from--;
2412                 /* Since revision graph visualization requires knowledge
2413                  * about the parent commit, it causes a further one-off
2414                  * needed to be redrawn for incremental updates. */
2415                 if (redraw_from > 0 && opt_rev_graph)
2416                         redraw_from--;
2418                 /* Incrementally draw avoids flickering. */
2419                 redraw_view_from(view, redraw_from);
2420         }
2422         if (view == VIEW(REQ_VIEW_BLAME))
2423                 redraw_view_dirty(view);
2425         /* Update the title _after_ the redraw so that if the redraw picks up a
2426          * commit reference in view->ref it'll be available here. */
2427         update_view_title(view);
2428         return TRUE;
2430 alloc_error:
2431         report("Allocation failure");
2432         end_update(view, TRUE);
2433         return FALSE;
2436 static struct line *
2437 add_line_data(struct view *view, void *data, enum line_type type)
2439         struct line *line = &view->line[view->lines++];
2441         memset(line, 0, sizeof(*line));
2442         line->type = type;
2443         line->data = data;
2445         return line;
2448 static struct line *
2449 add_line_text(struct view *view, const char *text, enum line_type type)
2451         char *data = text ? strdup(text) : NULL;
2453         return data ? add_line_data(view, data, type) : NULL;
2457 /*
2458  * View opening
2459  */
2461 enum open_flags {
2462         OPEN_DEFAULT = 0,       /* Use default view switching. */
2463         OPEN_SPLIT = 1,         /* Split current view. */
2464         OPEN_BACKGROUNDED = 2,  /* Backgrounded. */
2465         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
2466         OPEN_NOMAXIMIZE = 8,    /* Do not maximize the current view. */
2467         OPEN_REFRESH = 16,      /* Refresh view using previous command. */
2468 };
2470 static void
2471 open_view(struct view *prev, enum request request, enum open_flags flags)
2473         bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
2474         bool split = !!(flags & OPEN_SPLIT);
2475         bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH));
2476         bool nomaximize = !!(flags & (OPEN_NOMAXIMIZE | OPEN_REFRESH));
2477         struct view *view = VIEW(request);
2478         int nviews = displayed_views();
2479         struct view *base_view = display[0];
2481         if (view == prev && nviews == 1 && !reload) {
2482                 report("Already in %s view", view->name);
2483                 return;
2484         }
2486         if (view->git_dir && !opt_git_dir[0]) {
2487                 report("The %s view is disabled in pager view", view->name);
2488                 return;
2489         }
2491         if (split) {
2492                 display[1] = view;
2493                 if (!backgrounded)
2494                         current_view = 1;
2495         } else if (!nomaximize) {
2496                 /* Maximize the current view. */
2497                 memset(display, 0, sizeof(display));
2498                 current_view = 0;
2499                 display[current_view] = view;
2500         }
2502         /* Resize the view when switching between split- and full-screen,
2503          * or when switching between two different full-screen views. */
2504         if (nviews != displayed_views() ||
2505             (nviews == 1 && base_view != display[0]))
2506                 resize_display();
2508         if (view->pipe)
2509                 end_update(view, TRUE);
2511         if (view->ops->open) {
2512                 if (!view->ops->open(view)) {
2513                         report("Failed to load %s view", view->name);
2514                         return;
2515                 }
2517         } else if ((reload || strcmp(view->vid, view->id)) &&
2518                    !begin_update(view, flags & OPEN_REFRESH)) {
2519                 report("Failed to load %s view", view->name);
2520                 return;
2521         }
2523         if (split && prev->lineno - prev->offset >= prev->height) {
2524                 /* Take the title line into account. */
2525                 int lines = prev->lineno - prev->offset - prev->height + 1;
2527                 /* Scroll the view that was split if the current line is
2528                  * outside the new limited view. */
2529                 do_scroll_view(prev, lines);
2530         }
2532         if (prev && view != prev) {
2533                 if (split && !backgrounded) {
2534                         /* "Blur" the previous view. */
2535                         update_view_title(prev);
2536                 }
2538                 view->parent = prev;
2539         }
2541         if (view->pipe && view->lines == 0) {
2542                 /* Clear the old view and let the incremental updating refill
2543                  * the screen. */
2544                 werase(view->win);
2545                 report("");
2546         } else if (view_is_displayed(view)) {
2547                 redraw_view(view);
2548                 report("");
2549         }
2551         /* If the view is backgrounded the above calls to report()
2552          * won't redraw the view title. */
2553         if (backgrounded)
2554                 update_view_title(view);
2557 static bool
2558 run_confirm(const char *cmd, const char *prompt)
2560         bool confirmation = prompt_yesno(prompt);
2562         if (confirmation)
2563                 system(cmd);
2565         return confirmation;
2568 static void
2569 open_external_viewer(const char *cmd)
2571         def_prog_mode();           /* save current tty modes */
2572         endwin();                  /* restore original tty modes */
2573         system(cmd);
2574         fprintf(stderr, "Press Enter to continue");
2575         getc(opt_tty);
2576         reset_prog_mode();
2577         redraw_display();
2580 static void
2581 open_mergetool(const char *file)
2583         char cmd[SIZEOF_STR];
2584         char file_sq[SIZEOF_STR];
2586         if (sq_quote(file_sq, 0, file) < sizeof(file_sq) &&
2587             string_format(cmd, "git mergetool %s", file_sq)) {
2588                 open_external_viewer(cmd);
2589         }
2592 static void
2593 open_editor(bool from_root, const char *file)
2595         char cmd[SIZEOF_STR];
2596         char file_sq[SIZEOF_STR];
2597         const char *editor;
2598         char *prefix = from_root ? opt_cdup : "";
2600         editor = getenv("GIT_EDITOR");
2601         if (!editor && *opt_editor)
2602                 editor = opt_editor;
2603         if (!editor)
2604                 editor = getenv("VISUAL");
2605         if (!editor)
2606                 editor = getenv("EDITOR");
2607         if (!editor)
2608                 editor = "vi";
2610         if (sq_quote(file_sq, 0, file) < sizeof(file_sq) &&
2611             string_format(cmd, "%s %s%s", editor, prefix, file_sq)) {
2612                 open_external_viewer(cmd);
2613         }
2616 static void
2617 open_run_request(enum request request)
2619         struct run_request *req = get_run_request(request);
2620         char buf[SIZEOF_STR * 2];
2621         size_t bufpos;
2622         char *cmd;
2624         if (!req) {
2625                 report("Unknown run request");
2626                 return;
2627         }
2629         bufpos = 0;
2630         cmd = req->cmd;
2632         while (cmd) {
2633                 char *next = strstr(cmd, "%(");
2634                 int len = next - cmd;
2635                 char *value;
2637                 if (!next) {
2638                         len = strlen(cmd);
2639                         value = "";
2641                 } else if (!strncmp(next, "%(head)", 7)) {
2642                         value = ref_head;
2644                 } else if (!strncmp(next, "%(commit)", 9)) {
2645                         value = ref_commit;
2647                 } else if (!strncmp(next, "%(blob)", 7)) {
2648                         value = ref_blob;
2650                 } else {
2651                         report("Unknown replacement in run request: `%s`", req->cmd);
2652                         return;
2653                 }
2655                 if (!string_format_from(buf, &bufpos, "%.*s%s", len, cmd, value))
2656                         return;
2658                 if (next)
2659                         next = strchr(next, ')') + 1;
2660                 cmd = next;
2661         }
2663         open_external_viewer(buf);
2666 /*
2667  * User request switch noodle
2668  */
2670 static int
2671 view_driver(struct view *view, enum request request)
2673         int i;
2675         if (request == REQ_NONE) {
2676                 doupdate();
2677                 return TRUE;
2678         }
2680         if (request > REQ_NONE) {
2681                 open_run_request(request);
2682                 /* FIXME: When all views can refresh always do this. */
2683                 if (view == VIEW(REQ_VIEW_STATUS) ||
2684                     view == VIEW(REQ_VIEW_MAIN) ||
2685                     view == VIEW(REQ_VIEW_LOG) ||
2686                     view == VIEW(REQ_VIEW_STAGE))
2687                         request = REQ_REFRESH;
2688                 else
2689                         return TRUE;
2690         }
2692         if (view && view->lines) {
2693                 request = view->ops->request(view, request, &view->line[view->lineno]);
2694                 if (request == REQ_NONE)
2695                         return TRUE;
2696         }
2698         switch (request) {
2699         case REQ_MOVE_UP:
2700         case REQ_MOVE_DOWN:
2701         case REQ_MOVE_PAGE_UP:
2702         case REQ_MOVE_PAGE_DOWN:
2703         case REQ_MOVE_FIRST_LINE:
2704         case REQ_MOVE_LAST_LINE:
2705                 move_view(view, request);
2706                 break;
2708         case REQ_SCROLL_LINE_DOWN:
2709         case REQ_SCROLL_LINE_UP:
2710         case REQ_SCROLL_PAGE_DOWN:
2711         case REQ_SCROLL_PAGE_UP:
2712                 scroll_view(view, request);
2713                 break;
2715         case REQ_VIEW_BLAME:
2716                 if (!opt_file[0]) {
2717                         report("No file chosen, press %s to open tree view",
2718                                get_key(REQ_VIEW_TREE));
2719                         break;
2720                 }
2721                 open_view(view, request, OPEN_DEFAULT);
2722                 break;
2724         case REQ_VIEW_BLOB:
2725                 if (!ref_blob[0]) {
2726                         report("No file chosen, press %s to open tree view",
2727                                get_key(REQ_VIEW_TREE));
2728                         break;
2729                 }
2730                 open_view(view, request, OPEN_DEFAULT);
2731                 break;
2733         case REQ_VIEW_PAGER:
2734                 if (!opt_pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
2735                         report("No pager content, press %s to run command from prompt",
2736                                get_key(REQ_PROMPT));
2737                         break;
2738                 }
2739                 open_view(view, request, OPEN_DEFAULT);
2740                 break;
2742         case REQ_VIEW_STAGE:
2743                 if (!VIEW(REQ_VIEW_STAGE)->lines) {
2744                         report("No stage content, press %s to open the status view and choose file",
2745                                get_key(REQ_VIEW_STATUS));
2746                         break;
2747                 }
2748                 open_view(view, request, OPEN_DEFAULT);
2749                 break;
2751         case REQ_VIEW_STATUS:
2752                 if (opt_is_inside_work_tree == FALSE) {
2753                         report("The status view requires a working tree");
2754                         break;
2755                 }
2756                 open_view(view, request, OPEN_DEFAULT);
2757                 break;
2759         case REQ_VIEW_MAIN:
2760         case REQ_VIEW_DIFF:
2761         case REQ_VIEW_LOG:
2762         case REQ_VIEW_TREE:
2763         case REQ_VIEW_HELP:
2764                 open_view(view, request, OPEN_DEFAULT);
2765                 break;
2767         case REQ_NEXT:
2768         case REQ_PREVIOUS:
2769                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
2771                 if ((view == VIEW(REQ_VIEW_DIFF) &&
2772                      view->parent == VIEW(REQ_VIEW_MAIN)) ||
2773                    (view == VIEW(REQ_VIEW_DIFF) &&
2774                      view->parent == VIEW(REQ_VIEW_BLAME)) ||
2775                    (view == VIEW(REQ_VIEW_STAGE) &&
2776                      view->parent == VIEW(REQ_VIEW_STATUS)) ||
2777                    (view == VIEW(REQ_VIEW_BLOB) &&
2778                      view->parent == VIEW(REQ_VIEW_TREE))) {
2779                         int line;
2781                         view = view->parent;
2782                         line = view->lineno;
2783                         move_view(view, request);
2784                         if (view_is_displayed(view))
2785                                 update_view_title(view);
2786                         if (line != view->lineno)
2787                                 view->ops->request(view, REQ_ENTER,
2788                                                    &view->line[view->lineno]);
2790                 } else {
2791                         move_view(view, request);
2792                 }
2793                 break;
2795         case REQ_VIEW_NEXT:
2796         {
2797                 int nviews = displayed_views();
2798                 int next_view = (current_view + 1) % nviews;
2800                 if (next_view == current_view) {
2801                         report("Only one view is displayed");
2802                         break;
2803                 }
2805                 current_view = next_view;
2806                 /* Blur out the title of the previous view. */
2807                 update_view_title(view);
2808                 report("");
2809                 break;
2810         }
2811         case REQ_REFRESH:
2812                 report("Refreshing is not yet supported for the %s view", view->name);
2813                 break;
2815         case REQ_MAXIMIZE:
2816                 if (displayed_views() == 2)
2817                         open_view(view, VIEW_REQ(view), OPEN_DEFAULT);
2818                 break;
2820         case REQ_TOGGLE_LINENO:
2821                 opt_line_number = !opt_line_number;
2822                 redraw_display();
2823                 break;
2825         case REQ_TOGGLE_DATE:
2826                 opt_date = !opt_date;
2827                 redraw_display();
2828                 break;
2830         case REQ_TOGGLE_AUTHOR:
2831                 opt_author = !opt_author;
2832                 redraw_display();
2833                 break;
2835         case REQ_TOGGLE_REV_GRAPH:
2836                 opt_rev_graph = !opt_rev_graph;
2837                 redraw_display();
2838                 break;
2840         case REQ_TOGGLE_REFS:
2841                 opt_show_refs = !opt_show_refs;
2842                 redraw_display();
2843                 break;
2845         case REQ_SEARCH:
2846         case REQ_SEARCH_BACK:
2847                 search_view(view, request);
2848                 break;
2850         case REQ_FIND_NEXT:
2851         case REQ_FIND_PREV:
2852                 find_next(view, request);
2853                 break;
2855         case REQ_STOP_LOADING:
2856                 for (i = 0; i < ARRAY_SIZE(views); i++) {
2857                         view = &views[i];
2858                         if (view->pipe)
2859                                 report("Stopped loading the %s view", view->name),
2860                         end_update(view, TRUE);
2861                 }
2862                 break;
2864         case REQ_SHOW_VERSION:
2865                 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
2866                 return TRUE;
2868         case REQ_SCREEN_RESIZE:
2869                 resize_display();
2870                 /* Fall-through */
2871         case REQ_SCREEN_REDRAW:
2872                 redraw_display();
2873                 break;
2875         case REQ_EDIT:
2876                 report("Nothing to edit");
2877                 break;
2879         case REQ_ENTER:
2880                 report("Nothing to enter");
2881                 break;
2883         case REQ_VIEW_CLOSE:
2884                 /* XXX: Mark closed views by letting view->parent point to the
2885                  * view itself. Parents to closed view should never be
2886                  * followed. */
2887                 if (view->parent &&
2888                     view->parent->parent != view->parent) {
2889                         memset(display, 0, sizeof(display));
2890                         current_view = 0;
2891                         display[current_view] = view->parent;
2892                         view->parent = view;
2893                         resize_display();
2894                         redraw_display();
2895                         report("");
2896                         break;
2897                 }
2898                 /* Fall-through */
2899         case REQ_QUIT:
2900                 return FALSE;
2902         default:
2903                 report("Unknown key, press 'h' for help");
2904                 return TRUE;
2905         }
2907         return TRUE;
2911 /*
2912  * Pager backend
2913  */
2915 static bool
2916 pager_draw(struct view *view, struct line *line, unsigned int lineno)
2918         char *text = line->data;
2920         if (opt_line_number && draw_lineno(view, lineno))
2921                 return TRUE;
2923         draw_text(view, line->type, text, TRUE);
2924         return TRUE;
2927 static bool
2928 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
2930         char refbuf[SIZEOF_STR];
2931         char *ref = NULL;
2932         FILE *pipe;
2934         if (!string_format(refbuf, "git describe %s 2>/dev/null", commit_id))
2935                 return TRUE;
2937         pipe = popen(refbuf, "r");
2938         if (!pipe)
2939                 return TRUE;
2941         if ((ref = fgets(refbuf, sizeof(refbuf), pipe)))
2942                 ref = chomp_string(ref);
2943         pclose(pipe);
2945         if (!ref || !*ref)
2946                 return TRUE;
2948         /* This is the only fatal call, since it can "corrupt" the buffer. */
2949         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
2950                 return FALSE;
2952         return TRUE;
2955 static void
2956 add_pager_refs(struct view *view, struct line *line)
2958         char buf[SIZEOF_STR];
2959         char *commit_id = (char *)line->data + STRING_SIZE("commit ");
2960         struct ref **refs;
2961         size_t bufpos = 0, refpos = 0;
2962         const char *sep = "Refs: ";
2963         bool is_tag = FALSE;
2965         assert(line->type == LINE_COMMIT);
2967         refs = get_refs(commit_id);
2968         if (!refs) {
2969                 if (view == VIEW(REQ_VIEW_DIFF))
2970                         goto try_add_describe_ref;
2971                 return;
2972         }
2974         do {
2975                 struct ref *ref = refs[refpos];
2976                 const char *fmt = ref->tag    ? "%s[%s]" :
2977                                   ref->remote ? "%s<%s>" : "%s%s";
2979                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
2980                         return;
2981                 sep = ", ";
2982                 if (ref->tag)
2983                         is_tag = TRUE;
2984         } while (refs[refpos++]->next);
2986         if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
2987 try_add_describe_ref:
2988                 /* Add <tag>-g<commit_id> "fake" reference. */
2989                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
2990                         return;
2991         }
2993         if (bufpos == 0)
2994                 return;
2996         if (!realloc_lines(view, view->line_size + 1))
2997                 return;
2999         add_line_text(view, buf, LINE_PP_REFS);
3002 static bool
3003 pager_read(struct view *view, char *data)
3005         struct line *line;
3007         if (!data)
3008                 return TRUE;
3010         line = add_line_text(view, data, get_line_type(data));
3011         if (!line)
3012                 return FALSE;
3014         if (line->type == LINE_COMMIT &&
3015             (view == VIEW(REQ_VIEW_DIFF) ||
3016              view == VIEW(REQ_VIEW_LOG)))
3017                 add_pager_refs(view, line);
3019         return TRUE;
3022 static enum request
3023 pager_request(struct view *view, enum request request, struct line *line)
3025         int split = 0;
3027         if (request != REQ_ENTER)
3028                 return request;
3030         if (line->type == LINE_COMMIT &&
3031            (view == VIEW(REQ_VIEW_LOG) ||
3032             view == VIEW(REQ_VIEW_PAGER))) {
3033                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
3034                 split = 1;
3035         }
3037         /* Always scroll the view even if it was split. That way
3038          * you can use Enter to scroll through the log view and
3039          * split open each commit diff. */
3040         scroll_view(view, REQ_SCROLL_LINE_DOWN);
3042         /* FIXME: A minor workaround. Scrolling the view will call report("")
3043          * but if we are scrolling a non-current view this won't properly
3044          * update the view title. */
3045         if (split)
3046                 update_view_title(view);
3048         return REQ_NONE;
3051 static bool
3052 pager_grep(struct view *view, struct line *line)
3054         regmatch_t pmatch;
3055         char *text = line->data;
3057         if (!*text)
3058                 return FALSE;
3060         if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
3061                 return FALSE;
3063         return TRUE;
3066 static void
3067 pager_select(struct view *view, struct line *line)
3069         if (line->type == LINE_COMMIT) {
3070                 char *text = (char *)line->data + STRING_SIZE("commit ");
3072                 if (view != VIEW(REQ_VIEW_PAGER))
3073                         string_copy_rev(view->ref, text);
3074                 string_copy_rev(ref_commit, text);
3075         }
3078 static struct view_ops pager_ops = {
3079         "line",
3080         NULL,
3081         pager_read,
3082         pager_draw,
3083         pager_request,
3084         pager_grep,
3085         pager_select,
3086 };
3088 static enum request
3089 log_request(struct view *view, enum request request, struct line *line)
3091         switch (request) {
3092         case REQ_REFRESH:
3093                 load_refs();
3094                 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
3095                 return REQ_NONE;
3096         default:
3097                 return pager_request(view, request, line);
3098         }
3101 static struct view_ops log_ops = {
3102         "line",
3103         NULL,
3104         pager_read,
3105         pager_draw,
3106         log_request,
3107         pager_grep,
3108         pager_select,
3109 };
3112 /*
3113  * Help backend
3114  */
3116 static bool
3117 help_open(struct view *view)
3119         char buf[BUFSIZ];
3120         int lines = ARRAY_SIZE(req_info) + 2;
3121         int i;
3123         if (view->lines > 0)
3124                 return TRUE;
3126         for (i = 0; i < ARRAY_SIZE(req_info); i++)
3127                 if (!req_info[i].request)
3128                         lines++;
3130         lines += run_requests + 1;
3132         view->line = calloc(lines, sizeof(*view->line));
3133         if (!view->line)
3134                 return FALSE;
3136         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
3138         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
3139                 const char *key;
3141                 if (req_info[i].request == REQ_NONE)
3142                         continue;
3144                 if (!req_info[i].request) {
3145                         add_line_text(view, "", LINE_DEFAULT);
3146                         add_line_text(view, req_info[i].help, LINE_DEFAULT);
3147                         continue;
3148                 }
3150                 key = get_key(req_info[i].request);
3151                 if (!*key)
3152                         key = "(no key defined)";
3154                 if (!string_format(buf, "    %-25s %s", key, req_info[i].help))
3155                         continue;
3157                 add_line_text(view, buf, LINE_DEFAULT);
3158         }
3160         if (run_requests) {
3161                 add_line_text(view, "", LINE_DEFAULT);
3162                 add_line_text(view, "External commands:", LINE_DEFAULT);
3163         }
3165         for (i = 0; i < run_requests; i++) {
3166                 struct run_request *req = get_run_request(REQ_NONE + i + 1);
3167                 const char *key;
3169                 if (!req)
3170                         continue;
3172                 key = get_key_name(req->key);
3173                 if (!*key)
3174                         key = "(no key defined)";
3176                 if (!string_format(buf, "    %-10s %-14s `%s`",
3177                                    keymap_table[req->keymap].name,
3178                                    key, req->cmd))
3179                         continue;
3181                 add_line_text(view, buf, LINE_DEFAULT);
3182         }
3184         return TRUE;
3187 static struct view_ops help_ops = {
3188         "line",
3189         help_open,
3190         NULL,
3191         pager_draw,
3192         pager_request,
3193         pager_grep,
3194         pager_select,
3195 };
3198 /*
3199  * Tree backend
3200  */
3202 struct tree_stack_entry {
3203         struct tree_stack_entry *prev;  /* Entry below this in the stack */
3204         unsigned long lineno;           /* Line number to restore */
3205         char *name;                     /* Position of name in opt_path */
3206 };
3208 /* The top of the path stack. */
3209 static struct tree_stack_entry *tree_stack = NULL;
3210 unsigned long tree_lineno = 0;
3212 static void
3213 pop_tree_stack_entry(void)
3215         struct tree_stack_entry *entry = tree_stack;
3217         tree_lineno = entry->lineno;
3218         entry->name[0] = 0;
3219         tree_stack = entry->prev;
3220         free(entry);
3223 static void
3224 push_tree_stack_entry(const char *name, unsigned long lineno)
3226         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
3227         size_t pathlen = strlen(opt_path);
3229         if (!entry)
3230                 return;
3232         entry->prev = tree_stack;
3233         entry->name = opt_path + pathlen;
3234         tree_stack = entry;
3236         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
3237                 pop_tree_stack_entry();
3238                 return;
3239         }
3241         /* Move the current line to the first tree entry. */
3242         tree_lineno = 1;
3243         entry->lineno = lineno;
3246 /* Parse output from git-ls-tree(1):
3247  *
3248  * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile
3249  * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README
3250  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
3251  * 100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38 web.conf
3252  */
3254 #define SIZEOF_TREE_ATTR \
3255         STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t")
3257 #define TREE_UP_FORMAT "040000 tree %s\t.."
3259 static int
3260 tree_compare_entry(enum line_type type1, const char *name1,
3261                    enum line_type type2, const char *name2)
3263         if (type1 != type2) {
3264                 if (type1 == LINE_TREE_DIR)
3265                         return -1;
3266                 return 1;
3267         }
3269         return strcmp(name1, name2);
3272 static const char *
3273 tree_path(struct line *line)
3275         const char *path = line->data;
3277         return path + SIZEOF_TREE_ATTR;
3280 static bool
3281 tree_read(struct view *view, char *text)
3283         size_t textlen = text ? strlen(text) : 0;
3284         char buf[SIZEOF_STR];
3285         unsigned long pos;
3286         enum line_type type;
3287         bool first_read = view->lines == 0;
3289         if (!text)
3290                 return TRUE;
3291         if (textlen <= SIZEOF_TREE_ATTR)
3292                 return FALSE;
3294         type = text[STRING_SIZE("100644 ")] == 't'
3295              ? LINE_TREE_DIR : LINE_TREE_FILE;
3297         if (first_read) {
3298                 /* Add path info line */
3299                 if (!string_format(buf, "Directory path /%s", opt_path) ||
3300                     !realloc_lines(view, view->line_size + 1) ||
3301                     !add_line_text(view, buf, LINE_DEFAULT))
3302                         return FALSE;
3304                 /* Insert "link" to parent directory. */
3305                 if (*opt_path) {
3306                         if (!string_format(buf, TREE_UP_FORMAT, view->ref) ||
3307                             !realloc_lines(view, view->line_size + 1) ||
3308                             !add_line_text(view, buf, LINE_TREE_DIR))
3309                                 return FALSE;
3310                 }
3311         }
3313         /* Strip the path part ... */
3314         if (*opt_path) {
3315                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
3316                 size_t striplen = strlen(opt_path);
3317                 char *path = text + SIZEOF_TREE_ATTR;
3319                 if (pathlen > striplen)
3320                         memmove(path, path + striplen,
3321                                 pathlen - striplen + 1);
3322         }
3324         /* Skip "Directory ..." and ".." line. */
3325         for (pos = 1 + !!*opt_path; pos < view->lines; pos++) {
3326                 struct line *line = &view->line[pos];
3327                 const char *path1 = tree_path(line);
3328                 char *path2 = text + SIZEOF_TREE_ATTR;
3329                 int cmp = tree_compare_entry(line->type, path1, type, path2);
3331                 if (cmp <= 0)
3332                         continue;
3334                 text = strdup(text);
3335                 if (!text)
3336                         return FALSE;
3338                 if (view->lines > pos)
3339                         memmove(&view->line[pos + 1], &view->line[pos],
3340                                 (view->lines - pos) * sizeof(*line));
3342                 line = &view->line[pos];
3343                 line->data = text;
3344                 line->type = type;
3345                 view->lines++;
3346                 return TRUE;
3347         }
3349         if (!add_line_text(view, text, type))
3350                 return FALSE;
3352         if (tree_lineno > view->lineno) {
3353                 view->lineno = tree_lineno;
3354                 tree_lineno = 0;
3355         }
3357         return TRUE;
3360 static enum request
3361 tree_request(struct view *view, enum request request, struct line *line)
3363         enum open_flags flags;
3365         if (request == REQ_VIEW_BLAME) {
3366                 const char *filename = tree_path(line);
3368                 if (line->type != LINE_TREE_FILE) {
3369                         report("Blame only supported for files");
3370                         return REQ_NONE;
3371                 }
3373                 string_copy(opt_ref, view->vid);
3374                 string_format(opt_file, "%s%s", opt_path, filename);
3375                 return request;
3376         }
3377         if (request == REQ_TREE_PARENT) {
3378                 if (*opt_path) {
3379                         /* fake 'cd  ..' */
3380                         request = REQ_ENTER;
3381                         line = &view->line[1];
3382                 } else {
3383                         /* quit view if at top of tree */
3384                         return REQ_VIEW_CLOSE;
3385                 }
3386         }
3387         if (request != REQ_ENTER)
3388                 return request;
3390         /* Cleanup the stack if the tree view is at a different tree. */
3391         while (!*opt_path && tree_stack)
3392                 pop_tree_stack_entry();
3394         switch (line->type) {
3395         case LINE_TREE_DIR:
3396                 /* Depending on whether it is a subdir or parent (updir?) link
3397                  * mangle the path buffer. */
3398                 if (line == &view->line[1] && *opt_path) {
3399                         pop_tree_stack_entry();
3401                 } else {
3402                         const char *basename = tree_path(line);
3404                         push_tree_stack_entry(basename, view->lineno);
3405                 }
3407                 /* Trees and subtrees share the same ID, so they are not not
3408                  * unique like blobs. */
3409                 flags = OPEN_RELOAD;
3410                 request = REQ_VIEW_TREE;
3411                 break;
3413         case LINE_TREE_FILE:
3414                 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
3415                 request = REQ_VIEW_BLOB;
3416                 break;
3418         default:
3419                 return TRUE;
3420         }
3422         open_view(view, request, flags);
3423         if (request == REQ_VIEW_TREE) {
3424                 view->lineno = tree_lineno;
3425         }
3427         return REQ_NONE;
3430 static void
3431 tree_select(struct view *view, struct line *line)
3433         char *text = (char *)line->data + STRING_SIZE("100644 blob ");
3435         if (line->type == LINE_TREE_FILE) {
3436                 string_copy_rev(ref_blob, text);
3438         } else if (line->type != LINE_TREE_DIR) {
3439                 return;
3440         }
3442         string_copy_rev(view->ref, text);
3445 static struct view_ops tree_ops = {
3446         "file",
3447         NULL,
3448         tree_read,
3449         pager_draw,
3450         tree_request,
3451         pager_grep,
3452         tree_select,
3453 };
3455 static bool
3456 blob_read(struct view *view, char *line)
3458         if (!line)
3459                 return TRUE;
3460         return add_line_text(view, line, LINE_DEFAULT) != NULL;
3463 static struct view_ops blob_ops = {
3464         "line",
3465         NULL,
3466         blob_read,
3467         pager_draw,
3468         pager_request,
3469         pager_grep,
3470         pager_select,
3471 };
3473 /*
3474  * Blame backend
3475  *
3476  * Loading the blame view is a two phase job:
3477  *
3478  *  1. File content is read either using opt_file from the
3479  *     filesystem or using git-cat-file.
3480  *  2. Then blame information is incrementally added by
3481  *     reading output from git-blame.
3482  */
3484 struct blame_commit {
3485         char id[SIZEOF_REV];            /* SHA1 ID. */
3486         char title[128];                /* First line of the commit message. */
3487         char author[75];                /* Author of the commit. */
3488         struct tm time;                 /* Date from the author ident. */
3489         char filename[128];             /* Name of file. */
3490 };
3492 struct blame {
3493         struct blame_commit *commit;
3494         unsigned int header:1;
3495         char text[1];
3496 };
3498 #define BLAME_CAT_FILE_CMD "git cat-file blob %s:%s"
3499 #define BLAME_INCREMENTAL_CMD "git blame --incremental %s -- %s"
3501 static bool
3502 blame_open(struct view *view)
3504         char path[SIZEOF_STR];
3505         char ref[SIZEOF_STR] = "";
3507         if (sq_quote(path, 0, opt_file) >= sizeof(path))
3508                 return FALSE;
3510         if (*opt_ref && sq_quote(ref, 0, opt_ref) >= sizeof(ref))
3511                 return FALSE;
3513         if (*opt_ref) {
3514                 if (!string_format(view->cmd, BLAME_CAT_FILE_CMD, ref, path))
3515                         return FALSE;
3516         } else {
3517                 view->pipe = fopen(opt_file, "r");
3518                 if (!view->pipe &&
3519                     !string_format(view->cmd, BLAME_CAT_FILE_CMD, "HEAD", path))
3520                         return FALSE;
3521         }
3523         if (!view->pipe)
3524                 view->pipe = popen(view->cmd, "r");
3525         if (!view->pipe)
3526                 return FALSE;
3528         if (!string_format(view->cmd, BLAME_INCREMENTAL_CMD, ref, path))
3529                 return FALSE;
3531         reset_view(view);
3532         string_format(view->ref, "%s ...", opt_file);
3533         string_copy_rev(view->vid, opt_file);
3534         set_nonblocking_input(TRUE);
3535         view->start_time = time(NULL);
3537         return TRUE;
3540 static struct blame_commit *
3541 get_blame_commit(struct view *view, const char *id)
3543         size_t i;
3545         for (i = 0; i < view->lines; i++) {
3546                 struct blame *blame = view->line[i].data;
3548                 if (!blame->commit)
3549                         continue;
3551                 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
3552                         return blame->commit;
3553         }
3555         {
3556                 struct blame_commit *commit = calloc(1, sizeof(*commit));
3558                 if (commit)
3559                         string_ncopy(commit->id, id, SIZEOF_REV);
3560                 return commit;
3561         }
3564 static bool
3565 parse_number(const char **posref, size_t *number, size_t min, size_t max)
3567         const char *pos = *posref;
3569         *posref = NULL;
3570         pos = strchr(pos + 1, ' ');
3571         if (!pos || !isdigit(pos[1]))
3572                 return FALSE;
3573         *number = atoi(pos + 1);
3574         if (*number < min || *number > max)
3575                 return FALSE;
3577         *posref = pos;
3578         return TRUE;
3581 static struct blame_commit *
3582 parse_blame_commit(struct view *view, const char *text, int *blamed)
3584         struct blame_commit *commit;
3585         struct blame *blame;
3586         const char *pos = text + SIZEOF_REV - 1;
3587         size_t lineno;
3588         size_t group;
3590         if (strlen(text) <= SIZEOF_REV || *pos != ' ')
3591                 return NULL;
3593         if (!parse_number(&pos, &lineno, 1, view->lines) ||
3594             !parse_number(&pos, &group, 1, view->lines - lineno + 1))
3595                 return NULL;
3597         commit = get_blame_commit(view, text);
3598         if (!commit)
3599                 return NULL;
3601         *blamed += group;
3602         while (group--) {
3603                 struct line *line = &view->line[lineno + group - 1];
3605                 blame = line->data;
3606                 blame->commit = commit;
3607                 blame->header = !group;
3608                 line->dirty = 1;
3609         }
3611         return commit;
3614 static bool
3615 blame_read_file(struct view *view, const char *line)
3617         if (!line) {
3618                 FILE *pipe = NULL;
3620                 if (view->lines > 0)
3621                         pipe = popen(view->cmd, "r");
3622                 else if (!view->parent)
3623                         die("No blame exist for %s", view->vid);
3624                 view->cmd[0] = 0;
3625                 if (!pipe) {
3626                         report("Failed to load blame data");
3627                         return TRUE;
3628                 }
3630                 fclose(view->pipe);
3631                 view->pipe = pipe;
3632                 return FALSE;
3634         } else {
3635                 size_t linelen = strlen(line);
3636                 struct blame *blame = malloc(sizeof(*blame) + linelen);
3638                 blame->commit = NULL;
3639                 strncpy(blame->text, line, linelen);
3640                 blame->text[linelen] = 0;
3641                 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
3642         }
3645 static bool
3646 match_blame_header(const char *name, char **line)
3648         size_t namelen = strlen(name);
3649         bool matched = !strncmp(name, *line, namelen);
3651         if (matched)
3652                 *line += namelen;
3654         return matched;
3657 static bool
3658 blame_read(struct view *view, char *line)
3660         static struct blame_commit *commit = NULL;
3661         static int blamed = 0;
3662         static time_t author_time;
3664         if (*view->cmd)
3665                 return blame_read_file(view, line);
3667         if (!line) {
3668                 /* Reset all! */
3669                 commit = NULL;
3670                 blamed = 0;
3671                 string_format(view->ref, "%s", view->vid);
3672                 if (view_is_displayed(view)) {
3673                         update_view_title(view);
3674                         redraw_view_from(view, 0);
3675                 }
3676                 return TRUE;
3677         }
3679         if (!commit) {
3680                 commit = parse_blame_commit(view, line, &blamed);
3681                 string_format(view->ref, "%s %2d%%", view->vid,
3682                               blamed * 100 / view->lines);
3684         } else if (match_blame_header("author ", &line)) {
3685                 string_ncopy(commit->author, line, strlen(line));
3687         } else if (match_blame_header("author-time ", &line)) {
3688                 author_time = (time_t) atol(line);
3690         } else if (match_blame_header("author-tz ", &line)) {
3691                 long tz;
3693                 tz  = ('0' - line[1]) * 60 * 60 * 10;
3694                 tz += ('0' - line[2]) * 60 * 60;
3695                 tz += ('0' - line[3]) * 60;
3696                 tz += ('0' - line[4]) * 60;
3698                 if (line[0] == '-')
3699                         tz = -tz;
3701                 author_time -= tz;
3702                 gmtime_r(&author_time, &commit->time);
3704         } else if (match_blame_header("summary ", &line)) {
3705                 string_ncopy(commit->title, line, strlen(line));
3707         } else if (match_blame_header("filename ", &line)) {
3708                 string_ncopy(commit->filename, line, strlen(line));
3709                 commit = NULL;
3710         }
3712         return TRUE;
3715 static bool
3716 blame_draw(struct view *view, struct line *line, unsigned int lineno)
3718         struct blame *blame = line->data;
3719         struct tm *time = NULL;
3720         const char *id = NULL, *author = NULL;
3722         if (blame->commit && *blame->commit->filename) {
3723                 id = blame->commit->id;
3724                 author = blame->commit->author;
3725                 time = &blame->commit->time;
3726         }
3728         if (opt_date && draw_date(view, time))
3729                 return TRUE;
3731         if (opt_author &&
3732             draw_field(view, LINE_MAIN_AUTHOR, author, opt_author_cols, TRUE))
3733                 return TRUE;
3735         if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
3736                 return TRUE;
3738         if (draw_lineno(view, lineno))
3739                 return TRUE;
3741         draw_text(view, LINE_DEFAULT, blame->text, TRUE);
3742         return TRUE;
3745 static enum request
3746 blame_request(struct view *view, enum request request, struct line *line)
3748         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
3749         struct blame *blame = line->data;
3751         switch (request) {
3752         case REQ_ENTER:
3753                 if (!blame->commit) {
3754                         report("No commit loaded yet");
3755                         break;
3756                 }
3758                 if (!strcmp(blame->commit->id, NULL_ID)) {
3759                         char path[SIZEOF_STR];
3761                         if (sq_quote(path, 0, view->vid) >= sizeof(path))
3762                                 break;
3763                         string_format(opt_cmd, "git diff-index --root --patch-with-stat -C -M --cached HEAD -- %s 2>/dev/null", path);
3764                 }
3766                 open_view(view, REQ_VIEW_DIFF, flags);
3767                 break;
3769         default:
3770                 return request;
3771         }
3773         return REQ_NONE;
3776 static bool
3777 blame_grep(struct view *view, struct line *line)
3779         struct blame *blame = line->data;
3780         struct blame_commit *commit = blame->commit;
3781         regmatch_t pmatch;
3783 #define MATCH(text, on)                                                 \
3784         (on && *text && regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
3786         if (commit) {
3787                 char buf[DATE_COLS + 1];
3789                 if (MATCH(commit->title, 1) ||
3790                     MATCH(commit->author, opt_author) ||
3791                     MATCH(commit->id, opt_date))
3792                         return TRUE;
3794                 if (strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time) &&
3795                     MATCH(buf, 1))
3796                         return TRUE;
3797         }
3799         return MATCH(blame->text, 1);
3801 #undef MATCH
3804 static void
3805 blame_select(struct view *view, struct line *line)
3807         struct blame *blame = line->data;
3808         struct blame_commit *commit = blame->commit;
3810         if (!commit)
3811                 return;
3813         if (!strcmp(commit->id, NULL_ID))
3814                 string_ncopy(ref_commit, "HEAD", 4);
3815         else
3816                 string_copy_rev(ref_commit, commit->id);
3819 static struct view_ops blame_ops = {
3820         "line",
3821         blame_open,
3822         blame_read,
3823         blame_draw,
3824         blame_request,
3825         blame_grep,
3826         blame_select,
3827 };
3829 /*
3830  * Status backend
3831  */
3833 struct status {
3834         char status;
3835         struct {
3836                 mode_t mode;
3837                 char rev[SIZEOF_REV];
3838                 char name[SIZEOF_STR];
3839         } old;
3840         struct {
3841                 mode_t mode;
3842                 char rev[SIZEOF_REV];
3843                 char name[SIZEOF_STR];
3844         } new;
3845 };
3847 static char status_onbranch[SIZEOF_STR];
3848 static struct status stage_status;
3849 static enum line_type stage_line_type;
3850 static size_t stage_chunks;
3851 static int *stage_chunk;
3853 /* This should work even for the "On branch" line. */
3854 static inline bool
3855 status_has_none(struct view *view, struct line *line)
3857         return line < view->line + view->lines && !line[1].data;
3860 /* Get fields from the diff line:
3861  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
3862  */
3863 static inline bool
3864 status_get_diff(struct status *file, const char *buf, size_t bufsize)
3866         const char *old_mode = buf +  1;
3867         const char *new_mode = buf +  8;
3868         const char *old_rev  = buf + 15;
3869         const char *new_rev  = buf + 56;
3870         const char *status   = buf + 97;
3872         if (bufsize < 99 ||
3873             old_mode[-1] != ':' ||
3874             new_mode[-1] != ' ' ||
3875             old_rev[-1]  != ' ' ||
3876             new_rev[-1]  != ' ' ||
3877             status[-1]   != ' ')
3878                 return FALSE;
3880         file->status = *status;
3882         string_copy_rev(file->old.rev, old_rev);
3883         string_copy_rev(file->new.rev, new_rev);
3885         file->old.mode = strtoul(old_mode, NULL, 8);
3886         file->new.mode = strtoul(new_mode, NULL, 8);
3888         file->old.name[0] = file->new.name[0] = 0;
3890         return TRUE;
3893 static bool
3894 status_run(struct view *view, const char cmd[], char status, enum line_type type)
3896         struct status *file = NULL;
3897         struct status *unmerged = NULL;
3898         char buf[SIZEOF_STR * 4];
3899         size_t bufsize = 0;
3900         FILE *pipe;
3902         pipe = popen(cmd, "r");
3903         if (!pipe)
3904                 return FALSE;
3906         add_line_data(view, NULL, type);
3908         while (!feof(pipe) && !ferror(pipe)) {
3909                 char *sep;
3910                 size_t readsize;
3912                 readsize = fread(buf + bufsize, 1, sizeof(buf) - bufsize, pipe);
3913                 if (!readsize)
3914                         break;
3915                 bufsize += readsize;
3917                 /* Process while we have NUL chars. */
3918                 while ((sep = memchr(buf, 0, bufsize))) {
3919                         size_t sepsize = sep - buf + 1;
3921                         if (!file) {
3922                                 if (!realloc_lines(view, view->line_size + 1))
3923                                         goto error_out;
3925                                 file = calloc(1, sizeof(*file));
3926                                 if (!file)
3927                                         goto error_out;
3929                                 add_line_data(view, file, type);
3930                         }
3932                         /* Parse diff info part. */
3933                         if (status) {
3934                                 file->status = status;
3935                                 if (status == 'A')
3936                                         string_copy(file->old.rev, NULL_ID);
3938                         } else if (!file->status) {
3939                                 if (!status_get_diff(file, buf, sepsize))
3940                                         goto error_out;
3942                                 bufsize -= sepsize;
3943                                 memmove(buf, sep + 1, bufsize);
3945                                 sep = memchr(buf, 0, bufsize);
3946                                 if (!sep)
3947                                         break;
3948                                 sepsize = sep - buf + 1;
3950                                 /* Collapse all 'M'odified entries that
3951                                  * follow a associated 'U'nmerged entry.
3952                                  */
3953                                 if (file->status == 'U') {
3954                                         unmerged = file;
3956                                 } else if (unmerged) {
3957                                         int collapse = !strcmp(buf, unmerged->new.name);
3959                                         unmerged = NULL;
3960                                         if (collapse) {
3961                                                 free(file);
3962                                                 view->lines--;
3963                                                 continue;
3964                                         }
3965                                 }
3966                         }
3968                         /* Grab the old name for rename/copy. */
3969                         if (!*file->old.name &&
3970                             (file->status == 'R' || file->status == 'C')) {
3971                                 sepsize = sep - buf + 1;
3972                                 string_ncopy(file->old.name, buf, sepsize);
3973                                 bufsize -= sepsize;
3974                                 memmove(buf, sep + 1, bufsize);
3976                                 sep = memchr(buf, 0, bufsize);
3977                                 if (!sep)
3978                                         break;
3979                                 sepsize = sep - buf + 1;
3980                         }
3982                         /* git-ls-files just delivers a NUL separated
3983                          * list of file names similar to the second half
3984                          * of the git-diff-* output. */
3985                         string_ncopy(file->new.name, buf, sepsize);
3986                         if (!*file->old.name)
3987                                 string_copy(file->old.name, file->new.name);
3988                         bufsize -= sepsize;
3989                         memmove(buf, sep + 1, bufsize);
3990                         file = NULL;
3991                 }
3992         }
3994         if (ferror(pipe)) {
3995 error_out:
3996                 pclose(pipe);
3997                 return FALSE;
3998         }
4000         if (!view->line[view->lines - 1].data)
4001                 add_line_data(view, NULL, LINE_STAT_NONE);
4003         pclose(pipe);
4004         return TRUE;
4007 /* Don't show unmerged entries in the staged section. */
4008 #define STATUS_DIFF_INDEX_CMD "git diff-index -z --diff-filter=ACDMRTXB --cached -M HEAD"
4009 #define STATUS_DIFF_FILES_CMD "git diff-files -z"
4010 #define STATUS_LIST_OTHER_CMD \
4011         "git ls-files -z --others --exclude-standard"
4012 #define STATUS_LIST_NO_HEAD_CMD \
4013         "git ls-files -z --cached --exclude-standard"
4015 #define STATUS_DIFF_INDEX_SHOW_CMD \
4016         "git diff-index --root --patch-with-stat -C -M --cached HEAD -- %s %s 2>/dev/null"
4018 #define STATUS_DIFF_FILES_SHOW_CMD \
4019         "git diff-files --root --patch-with-stat -C -M -- %s %s 2>/dev/null"
4021 #define STATUS_DIFF_NO_HEAD_SHOW_CMD \
4022         "git diff --no-color --patch-with-stat /dev/null %s 2>/dev/null"
4024 /* First parse staged info using git-diff-index(1), then parse unstaged
4025  * info using git-diff-files(1), and finally untracked files using
4026  * git-ls-files(1). */
4027 static bool
4028 status_open(struct view *view)
4030         unsigned long prev_lineno = view->lineno;
4032         reset_view(view);
4034         if (!realloc_lines(view, view->line_size + 7))
4035                 return FALSE;
4037         add_line_data(view, NULL, LINE_STAT_HEAD);
4038         if (opt_no_head)
4039                 string_copy(status_onbranch, "Initial commit");
4040         else if (!*opt_head)
4041                 string_copy(status_onbranch, "Not currently on any branch");
4042         else if (!string_format(status_onbranch, "On branch %s", opt_head))
4043                 return FALSE;
4045         system("git update-index -q --refresh >/dev/null 2>/dev/null");
4047         if (opt_no_head) {
4048                 if (!status_run(view, STATUS_LIST_NO_HEAD_CMD, 'A', LINE_STAT_STAGED))
4049                         return FALSE;
4050         } else if (!status_run(view, STATUS_DIFF_INDEX_CMD, 0, LINE_STAT_STAGED)) {
4051                 return FALSE;
4052         }
4054         if (!status_run(view, STATUS_DIFF_FILES_CMD, 0, LINE_STAT_UNSTAGED) ||
4055             !status_run(view, STATUS_LIST_OTHER_CMD, '?', LINE_STAT_UNTRACKED))
4056                 return FALSE;
4058         /* If all went well restore the previous line number to stay in
4059          * the context or select a line with something that can be
4060          * updated. */
4061         if (prev_lineno >= view->lines)
4062                 prev_lineno = view->lines - 1;
4063         while (prev_lineno < view->lines && !view->line[prev_lineno].data)
4064                 prev_lineno++;
4065         while (prev_lineno > 0 && !view->line[prev_lineno].data)
4066                 prev_lineno--;
4068         /* If the above fails, always skip the "On branch" line. */
4069         if (prev_lineno < view->lines)
4070                 view->lineno = prev_lineno;
4071         else
4072                 view->lineno = 1;
4074         if (view->lineno < view->offset)
4075                 view->offset = view->lineno;
4076         else if (view->offset + view->height <= view->lineno)
4077                 view->offset = view->lineno - view->height + 1;
4079         return TRUE;
4082 static bool
4083 status_draw(struct view *view, struct line *line, unsigned int lineno)
4085         struct status *status = line->data;
4086         enum line_type type;
4087         const char *text;
4089         if (!status) {
4090                 switch (line->type) {
4091                 case LINE_STAT_STAGED:
4092                         type = LINE_STAT_SECTION;
4093                         text = "Changes to be committed:";
4094                         break;
4096                 case LINE_STAT_UNSTAGED:
4097                         type = LINE_STAT_SECTION;
4098                         text = "Changed but not updated:";
4099                         break;
4101                 case LINE_STAT_UNTRACKED:
4102                         type = LINE_STAT_SECTION;
4103                         text = "Untracked files:";
4104                         break;
4106                 case LINE_STAT_NONE:
4107                         type = LINE_DEFAULT;
4108                         text = "    (no files)";
4109                         break;
4111                 case LINE_STAT_HEAD:
4112                         type = LINE_STAT_HEAD;
4113                         text = status_onbranch;
4114                         break;
4116                 default:
4117                         return FALSE;
4118                 }
4119         } else {
4120                 static char buf[] = { '?', ' ', ' ', ' ', 0 };
4122                 buf[0] = status->status;
4123                 if (draw_text(view, line->type, buf, TRUE))
4124                         return TRUE;
4125                 type = LINE_DEFAULT;
4126                 text = status->new.name;
4127         }
4129         draw_text(view, type, text, TRUE);
4130         return TRUE;
4133 static enum request
4134 status_enter(struct view *view, struct line *line)
4136         struct status *status = line->data;
4137         char oldpath[SIZEOF_STR] = "";
4138         char newpath[SIZEOF_STR] = "";
4139         const char *info;
4140         size_t cmdsize = 0;
4141         enum open_flags split;
4143         if (line->type == LINE_STAT_NONE ||
4144             (!status && line[1].type == LINE_STAT_NONE)) {
4145                 report("No file to diff");
4146                 return REQ_NONE;
4147         }
4149         if (status) {
4150                 if (sq_quote(oldpath, 0, status->old.name) >= sizeof(oldpath))
4151                         return REQ_QUIT;
4152                 /* Diffs for unmerged entries are empty when pasing the
4153                  * new path, so leave it empty. */
4154                 if (status->status != 'U' &&
4155                     sq_quote(newpath, 0, status->new.name) >= sizeof(newpath))
4156                         return REQ_QUIT;
4157         }
4159         if (opt_cdup[0] &&
4160             line->type != LINE_STAT_UNTRACKED &&
4161             !string_format_from(opt_cmd, &cmdsize, "cd %s;", opt_cdup))
4162                 return REQ_QUIT;
4164         switch (line->type) {
4165         case LINE_STAT_STAGED:
4166                 if (opt_no_head) {
4167                         if (!string_format_from(opt_cmd, &cmdsize,
4168                                                 STATUS_DIFF_NO_HEAD_SHOW_CMD,
4169                                                 newpath))
4170                                 return REQ_QUIT;
4171                 } else {
4172                         if (!string_format_from(opt_cmd, &cmdsize,
4173                                                 STATUS_DIFF_INDEX_SHOW_CMD,
4174                                                 oldpath, newpath))
4175                                 return REQ_QUIT;
4176                 }
4178                 if (status)
4179                         info = "Staged changes to %s";
4180                 else
4181                         info = "Staged changes";
4182                 break;
4184         case LINE_STAT_UNSTAGED:
4185                 if (!string_format_from(opt_cmd, &cmdsize,
4186                                         STATUS_DIFF_FILES_SHOW_CMD, oldpath, newpath))
4187                         return REQ_QUIT;
4188                 if (status)
4189                         info = "Unstaged changes to %s";
4190                 else
4191                         info = "Unstaged changes";
4192                 break;
4194         case LINE_STAT_UNTRACKED:
4195                 if (opt_pipe)
4196                         return REQ_QUIT;
4198                 if (!status) {
4199                         report("No file to show");
4200                         return REQ_NONE;
4201                 }
4203                 if (!suffixcmp(status->new.name, -1, "/")) {
4204                         report("Cannot display a directory");
4205                         return REQ_NONE;
4206                 }
4208                 opt_pipe = fopen(status->new.name, "r");
4209                 info = "Untracked file %s";
4210                 break;
4212         case LINE_STAT_HEAD:
4213                 return REQ_NONE;
4215         default:
4216                 die("line type %d not handled in switch", line->type);
4217         }
4219         split = view_is_displayed(view) ? OPEN_SPLIT : 0;
4220         open_view(view, REQ_VIEW_STAGE, OPEN_RELOAD | split);
4221         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
4222                 if (status) {
4223                         stage_status = *status;
4224                 } else {
4225                         memset(&stage_status, 0, sizeof(stage_status));
4226                 }
4228                 stage_line_type = line->type;
4229                 stage_chunks = 0;
4230                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
4231         }
4233         return REQ_NONE;
4236 static bool
4237 status_exists(struct status *status, enum line_type type)
4239         struct view *view = VIEW(REQ_VIEW_STATUS);
4240         struct line *line;
4242         for (line = view->line; line < view->line + view->lines; line++) {
4243                 struct status *pos = line->data;
4245                 if (line->type == type && pos &&
4246                     !strcmp(status->new.name, pos->new.name))
4247                         return TRUE;
4248         }
4250         return FALSE;
4254 static FILE *
4255 status_update_prepare(enum line_type type)
4257         char cmd[SIZEOF_STR];
4258         size_t cmdsize = 0;
4260         if (opt_cdup[0] &&
4261             type != LINE_STAT_UNTRACKED &&
4262             !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
4263                 return NULL;
4265         switch (type) {
4266         case LINE_STAT_STAGED:
4267                 string_add(cmd, cmdsize, "git update-index -z --index-info");
4268                 break;
4270         case LINE_STAT_UNSTAGED:
4271         case LINE_STAT_UNTRACKED:
4272                 string_add(cmd, cmdsize, "git update-index -z --add --remove --stdin");
4273                 break;
4275         default:
4276                 die("line type %d not handled in switch", type);
4277         }
4279         return popen(cmd, "w");
4282 static bool
4283 status_update_write(FILE *pipe, struct status *status, enum line_type type)
4285         char buf[SIZEOF_STR];
4286         size_t bufsize = 0;
4287         size_t written = 0;
4289         switch (type) {
4290         case LINE_STAT_STAGED:
4291                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
4292                                         status->old.mode,
4293                                         status->old.rev,
4294                                         status->old.name, 0))
4295                         return FALSE;
4296                 break;
4298         case LINE_STAT_UNSTAGED:
4299         case LINE_STAT_UNTRACKED:
4300                 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
4301                         return FALSE;
4302                 break;
4304         default:
4305                 die("line type %d not handled in switch", type);
4306         }
4308         while (!ferror(pipe) && written < bufsize) {
4309                 written += fwrite(buf + written, 1, bufsize - written, pipe);
4310         }
4312         return written == bufsize;
4315 static bool
4316 status_update_file(struct status *status, enum line_type type)
4318         FILE *pipe = status_update_prepare(type);
4319         bool result;
4321         if (!pipe)
4322                 return FALSE;
4324         result = status_update_write(pipe, status, type);
4325         pclose(pipe);
4326         return result;
4329 static bool
4330 status_update_files(struct view *view, struct line *line)
4332         FILE *pipe = status_update_prepare(line->type);
4333         bool result = TRUE;
4334         struct line *pos = view->line + view->lines;
4335         int files = 0;
4336         int file, done;
4338         if (!pipe)
4339                 return FALSE;
4341         for (pos = line; pos < view->line + view->lines && pos->data; pos++)
4342                 files++;
4344         for (file = 0, done = 0; result && file < files; line++, file++) {
4345                 int almost_done = file * 100 / files;
4347                 if (almost_done > done) {
4348                         done = almost_done;
4349                         string_format(view->ref, "updating file %u of %u (%d%% done)",
4350                                       file, files, done);
4351                         update_view_title(view);
4352                 }
4353                 result = status_update_write(pipe, line->data, line->type);
4354         }
4356         pclose(pipe);
4357         return result;
4360 static bool
4361 status_update(struct view *view)
4363         struct line *line = &view->line[view->lineno];
4365         assert(view->lines);
4367         if (!line->data) {
4368                 /* This should work even for the "On branch" line. */
4369                 if (line < view->line + view->lines && !line[1].data) {
4370                         report("Nothing to update");
4371                         return FALSE;
4372                 }
4374                 if (!status_update_files(view, line + 1)) {
4375                         report("Failed to update file status");
4376                         return FALSE;
4377                 }
4379         } else if (!status_update_file(line->data, line->type)) {
4380                 report("Failed to update file status");
4381                 return FALSE;
4382         }
4384         return TRUE;
4387 static bool
4388 status_revert(struct status *status, enum line_type type, bool has_none)
4390         if (!status || type != LINE_STAT_UNSTAGED) {
4391                 if (type == LINE_STAT_STAGED) {
4392                         report("Cannot revert changes to staged files");
4393                 } else if (type == LINE_STAT_UNTRACKED) {
4394                         report("Cannot revert changes to untracked files");
4395                 } else if (has_none) {
4396                         report("Nothing to revert");
4397                 } else {
4398                         report("Cannot revert changes to multiple files");
4399                 }
4400                 return FALSE;
4402         } else {
4403                 char cmd[SIZEOF_STR];
4404                 char file_sq[SIZEOF_STR];
4406                 if (sq_quote(file_sq, 0, status->old.name) >= sizeof(file_sq) ||
4407                     !string_format(cmd, "git checkout -- %s%s", opt_cdup, file_sq))
4408                         return FALSE;
4410                 return run_confirm(cmd, "Are you sure you want to overwrite any changes?");
4411         }
4414 static enum request
4415 status_request(struct view *view, enum request request, struct line *line)
4417         struct status *status = line->data;
4419         switch (request) {
4420         case REQ_STATUS_UPDATE:
4421                 if (!status_update(view))
4422                         return REQ_NONE;
4423                 break;
4425         case REQ_STATUS_REVERT:
4426                 if (!status_revert(status, line->type, status_has_none(view, line)))
4427                         return REQ_NONE;
4428                 break;
4430         case REQ_STATUS_MERGE:
4431                 if (!status || status->status != 'U') {
4432                         report("Merging only possible for files with unmerged status ('U').");
4433                         return REQ_NONE;
4434                 }
4435                 open_mergetool(status->new.name);
4436                 break;
4438         case REQ_EDIT:
4439                 if (!status)
4440                         return request;
4441                 if (status->status == 'D') {
4442                         report("File has been deleted.");
4443                         return REQ_NONE;
4444                 }
4446                 open_editor(status->status != '?', status->new.name);
4447                 break;
4449         case REQ_VIEW_BLAME:
4450                 if (status) {
4451                         string_copy(opt_file, status->new.name);
4452                         opt_ref[0] = 0;
4453                 }
4454                 return request;
4456         case REQ_ENTER:
4457                 /* After returning the status view has been split to
4458                  * show the stage view. No further reloading is
4459                  * necessary. */
4460                 status_enter(view, line);
4461                 return REQ_NONE;
4463         case REQ_REFRESH:
4464                 /* Simply reload the view. */
4465                 break;
4467         default:
4468                 return request;
4469         }
4471         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
4473         return REQ_NONE;
4476 static void
4477 status_select(struct view *view, struct line *line)
4479         struct status *status = line->data;
4480         char file[SIZEOF_STR] = "all files";
4481         const char *text;
4482         const char *key;
4484         if (status && !string_format(file, "'%s'", status->new.name))
4485                 return;
4487         if (!status && line[1].type == LINE_STAT_NONE)
4488                 line++;
4490         switch (line->type) {
4491         case LINE_STAT_STAGED:
4492                 text = "Press %s to unstage %s for commit";
4493                 break;
4495         case LINE_STAT_UNSTAGED:
4496                 text = "Press %s to stage %s for commit";
4497                 break;
4499         case LINE_STAT_UNTRACKED:
4500                 text = "Press %s to stage %s for addition";
4501                 break;
4503         case LINE_STAT_HEAD:
4504         case LINE_STAT_NONE:
4505                 text = "Nothing to update";
4506                 break;
4508         default:
4509                 die("line type %d not handled in switch", line->type);
4510         }
4512         if (status && status->status == 'U') {
4513                 text = "Press %s to resolve conflict in %s";
4514                 key = get_key(REQ_STATUS_MERGE);
4516         } else {
4517                 key = get_key(REQ_STATUS_UPDATE);
4518         }
4520         string_format(view->ref, text, key, file);
4523 static bool
4524 status_grep(struct view *view, struct line *line)
4526         struct status *status = line->data;
4527         enum { S_STATUS, S_NAME, S_END } state;
4528         char buf[2] = "?";
4529         regmatch_t pmatch;
4531         if (!status)
4532                 return FALSE;
4534         for (state = S_STATUS; state < S_END; state++) {
4535                 const char *text;
4537                 switch (state) {
4538                 case S_NAME:    text = status->new.name;        break;
4539                 case S_STATUS:
4540                         buf[0] = status->status;
4541                         text = buf;
4542                         break;
4544                 default:
4545                         return FALSE;
4546                 }
4548                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
4549                         return TRUE;
4550         }
4552         return FALSE;
4555 static struct view_ops status_ops = {
4556         "file",
4557         status_open,
4558         NULL,
4559         status_draw,
4560         status_request,
4561         status_grep,
4562         status_select,
4563 };
4566 static bool
4567 stage_diff_line(FILE *pipe, struct line *line)
4569         const char *buf = line->data;
4570         size_t bufsize = strlen(buf);
4571         size_t written = 0;
4573         while (!ferror(pipe) && written < bufsize) {
4574                 written += fwrite(buf + written, 1, bufsize - written, pipe);
4575         }
4577         fputc('\n', pipe);
4579         return written == bufsize;
4582 static bool
4583 stage_diff_write(FILE *pipe, struct line *line, struct line *end)
4585         while (line < end) {
4586                 if (!stage_diff_line(pipe, line++))
4587                         return FALSE;
4588                 if (line->type == LINE_DIFF_CHUNK ||
4589                     line->type == LINE_DIFF_HEADER)
4590                         break;
4591         }
4593         return TRUE;
4596 static struct line *
4597 stage_diff_find(struct view *view, struct line *line, enum line_type type)
4599         for (; view->line < line; line--)
4600                 if (line->type == type)
4601                         return line;
4603         return NULL;
4606 static bool
4607 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
4609         char cmd[SIZEOF_STR];
4610         size_t cmdsize = 0;
4611         struct line *diff_hdr;
4612         FILE *pipe;
4614         diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
4615         if (!diff_hdr)
4616                 return FALSE;
4618         if (opt_cdup[0] &&
4619             !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
4620                 return FALSE;
4622         if (!string_format_from(cmd, &cmdsize,
4623                                 "git apply --whitespace=nowarn %s %s - && "
4624                                 "git update-index -q --unmerged --refresh 2>/dev/null",
4625                                 revert ? "" : "--cached",
4626                                 revert || stage_line_type == LINE_STAT_STAGED ? "-R" : ""))
4627                 return FALSE;
4629         pipe = popen(cmd, "w");
4630         if (!pipe)
4631                 return FALSE;
4633         if (!stage_diff_write(pipe, diff_hdr, chunk) ||
4634             !stage_diff_write(pipe, chunk, view->line + view->lines))
4635                 chunk = NULL;
4637         pclose(pipe);
4639         return chunk ? TRUE : FALSE;
4642 static bool
4643 stage_update(struct view *view, struct line *line)
4645         struct line *chunk = NULL;
4647         if (!opt_no_head && stage_line_type != LINE_STAT_UNTRACKED)
4648                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
4650         if (chunk) {
4651                 if (!stage_apply_chunk(view, chunk, FALSE)) {
4652                         report("Failed to apply chunk");
4653                         return FALSE;
4654                 }
4656         } else if (!stage_status.status) {
4657                 view = VIEW(REQ_VIEW_STATUS);
4659                 for (line = view->line; line < view->line + view->lines; line++)
4660                         if (line->type == stage_line_type)
4661                                 break;
4663                 if (!status_update_files(view, line + 1)) {
4664                         report("Failed to update files");
4665                         return FALSE;
4666                 }
4668         } else if (!status_update_file(&stage_status, stage_line_type)) {
4669                 report("Failed to update file");
4670                 return FALSE;
4671         }
4673         return TRUE;
4676 static bool
4677 stage_revert(struct view *view, struct line *line)
4679         struct line *chunk = NULL;
4681         if (!opt_no_head && stage_line_type == LINE_STAT_UNSTAGED)
4682                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
4684         if (chunk) {
4685                 if (!prompt_yesno("Are you sure you want to revert changes?"))
4686                         return FALSE;
4688                 if (!stage_apply_chunk(view, chunk, TRUE)) {
4689                         report("Failed to revert chunk");
4690                         return FALSE;
4691                 }
4692                 return TRUE;
4694         } else {
4695                 return status_revert(stage_status.status ? &stage_status : NULL,
4696                                      stage_line_type, FALSE);
4697         }
4701 static void
4702 stage_next(struct view *view, struct line *line)
4704         int i;
4706         if (!stage_chunks) {
4707                 static size_t alloc = 0;
4708                 int *tmp;
4710                 for (line = view->line; line < view->line + view->lines; line++) {
4711                         if (line->type != LINE_DIFF_CHUNK)
4712                                 continue;
4714                         tmp = realloc_items(stage_chunk, &alloc,
4715                                             stage_chunks, sizeof(*tmp));
4716                         if (!tmp) {
4717                                 report("Allocation failure");
4718                                 return;
4719                         }
4721                         stage_chunk = tmp;
4722                         stage_chunk[stage_chunks++] = line - view->line;
4723                 }
4724         }
4726         for (i = 0; i < stage_chunks; i++) {
4727                 if (stage_chunk[i] > view->lineno) {
4728                         do_scroll_view(view, stage_chunk[i] - view->lineno);
4729                         report("Chunk %d of %d", i + 1, stage_chunks);
4730                         return;
4731                 }
4732         }
4734         report("No next chunk found");
4737 static enum request
4738 stage_request(struct view *view, enum request request, struct line *line)
4740         switch (request) {
4741         case REQ_STATUS_UPDATE:
4742                 if (!stage_update(view, line))
4743                         return REQ_NONE;
4744                 break;
4746         case REQ_STATUS_REVERT:
4747                 if (!stage_revert(view, line))
4748                         return REQ_NONE;
4749                 break;
4751         case REQ_STAGE_NEXT:
4752                 if (stage_line_type == LINE_STAT_UNTRACKED) {
4753                         report("File is untracked; press %s to add",
4754                                get_key(REQ_STATUS_UPDATE));
4755                         return REQ_NONE;
4756                 }
4757                 stage_next(view, line);
4758                 return REQ_NONE;
4760         case REQ_EDIT:
4761                 if (!stage_status.new.name[0])
4762                         return request;
4763                 if (stage_status.status == 'D') {
4764                         report("File has been deleted.");
4765                         return REQ_NONE;
4766                 }
4768                 open_editor(stage_status.status != '?', stage_status.new.name);
4769                 break;
4771         case REQ_REFRESH:
4772                 /* Reload everything ... */
4773                 break;
4775         case REQ_VIEW_BLAME:
4776                 if (stage_status.new.name[0]) {
4777                         string_copy(opt_file, stage_status.new.name);
4778                         opt_ref[0] = 0;
4779                 }
4780                 return request;
4782         case REQ_ENTER:
4783                 return pager_request(view, request, line);
4785         default:
4786                 return request;
4787         }
4789         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD | OPEN_NOMAXIMIZE);
4791         /* Check whether the staged entry still exists, and close the
4792          * stage view if it doesn't. */
4793         if (!status_exists(&stage_status, stage_line_type))
4794                 return REQ_VIEW_CLOSE;
4796         if (stage_line_type == LINE_STAT_UNTRACKED) {
4797                 if (!suffixcmp(stage_status.new.name, -1, "/")) {
4798                         report("Cannot display a directory");
4799                         return REQ_NONE;
4800                 }
4802                 opt_pipe = fopen(stage_status.new.name, "r");
4803         }
4804         open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
4806         return REQ_NONE;
4809 static struct view_ops stage_ops = {
4810         "line",
4811         NULL,
4812         pager_read,
4813         pager_draw,
4814         stage_request,
4815         pager_grep,
4816         pager_select,
4817 };
4820 /*
4821  * Revision graph
4822  */
4824 struct commit {
4825         char id[SIZEOF_REV];            /* SHA1 ID. */
4826         char title[128];                /* First line of the commit message. */
4827         char author[75];                /* Author of the commit. */
4828         struct tm time;                 /* Date from the author ident. */
4829         struct ref **refs;              /* Repository references. */
4830         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
4831         size_t graph_size;              /* The width of the graph array. */
4832         bool has_parents;               /* Rewritten --parents seen. */
4833 };
4835 /* Size of rev graph with no  "padding" columns */
4836 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
4838 struct rev_graph {
4839         struct rev_graph *prev, *next, *parents;
4840         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
4841         size_t size;
4842         struct commit *commit;
4843         size_t pos;
4844         unsigned int boundary:1;
4845 };
4847 /* Parents of the commit being visualized. */
4848 static struct rev_graph graph_parents[4];
4850 /* The current stack of revisions on the graph. */
4851 static struct rev_graph graph_stacks[4] = {
4852         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
4853         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
4854         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
4855         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
4856 };
4858 static inline bool
4859 graph_parent_is_merge(struct rev_graph *graph)
4861         return graph->parents->size > 1;
4864 static inline void
4865 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
4867         struct commit *commit = graph->commit;
4869         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
4870                 commit->graph[commit->graph_size++] = symbol;
4873 static void
4874 clear_rev_graph(struct rev_graph *graph)
4876         graph->boundary = 0;
4877         graph->size = graph->pos = 0;
4878         graph->commit = NULL;
4879         memset(graph->parents, 0, sizeof(*graph->parents));
4882 static void
4883 done_rev_graph(struct rev_graph *graph)
4885         if (graph_parent_is_merge(graph) &&
4886             graph->pos < graph->size - 1 &&
4887             graph->next->size == graph->size + graph->parents->size - 1) {
4888                 size_t i = graph->pos + graph->parents->size - 1;
4890                 graph->commit->graph_size = i * 2;
4891                 while (i < graph->next->size - 1) {
4892                         append_to_rev_graph(graph, ' ');
4893                         append_to_rev_graph(graph, '\\');
4894                         i++;
4895                 }
4896         }
4898         clear_rev_graph(graph);
4901 static void
4902 push_rev_graph(struct rev_graph *graph, const char *parent)
4904         int i;
4906         /* "Collapse" duplicate parents lines.
4907          *
4908          * FIXME: This needs to also update update the drawn graph but
4909          * for now it just serves as a method for pruning graph lines. */
4910         for (i = 0; i < graph->size; i++)
4911                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
4912                         return;
4914         if (graph->size < SIZEOF_REVITEMS) {
4915                 string_copy_rev(graph->rev[graph->size++], parent);
4916         }
4919 static chtype
4920 get_rev_graph_symbol(struct rev_graph *graph)
4922         chtype symbol;
4924         if (graph->boundary)
4925                 symbol = REVGRAPH_BOUND;
4926         else if (graph->parents->size == 0)
4927                 symbol = REVGRAPH_INIT;
4928         else if (graph_parent_is_merge(graph))
4929                 symbol = REVGRAPH_MERGE;
4930         else if (graph->pos >= graph->size)
4931                 symbol = REVGRAPH_BRANCH;
4932         else
4933                 symbol = REVGRAPH_COMMIT;
4935         return symbol;
4938 static void
4939 draw_rev_graph(struct rev_graph *graph)
4941         struct rev_filler {
4942                 chtype separator, line;
4943         };
4944         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
4945         static struct rev_filler fillers[] = {
4946                 { ' ',  '|' },
4947                 { '`',  '.' },
4948                 { '\'', ' ' },
4949                 { '/',  ' ' },
4950         };
4951         chtype symbol = get_rev_graph_symbol(graph);
4952         struct rev_filler *filler;
4953         size_t i;
4955         if (opt_line_graphics)
4956                 fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE];
4958         filler = &fillers[DEFAULT];
4960         for (i = 0; i < graph->pos; i++) {
4961                 append_to_rev_graph(graph, filler->line);
4962                 if (graph_parent_is_merge(graph->prev) &&
4963                     graph->prev->pos == i)
4964                         filler = &fillers[RSHARP];
4966                 append_to_rev_graph(graph, filler->separator);
4967         }
4969         /* Place the symbol for this revision. */
4970         append_to_rev_graph(graph, symbol);
4972         if (graph->prev->size > graph->size)
4973                 filler = &fillers[RDIAG];
4974         else
4975                 filler = &fillers[DEFAULT];
4977         i++;
4979         for (; i < graph->size; i++) {
4980                 append_to_rev_graph(graph, filler->separator);
4981                 append_to_rev_graph(graph, filler->line);
4982                 if (graph_parent_is_merge(graph->prev) &&
4983                     i < graph->prev->pos + graph->parents->size)
4984                         filler = &fillers[RSHARP];
4985                 if (graph->prev->size > graph->size)
4986                         filler = &fillers[LDIAG];
4987         }
4989         if (graph->prev->size > graph->size) {
4990                 append_to_rev_graph(graph, filler->separator);
4991                 if (filler->line != ' ')
4992                         append_to_rev_graph(graph, filler->line);
4993         }
4996 /* Prepare the next rev graph */
4997 static void
4998 prepare_rev_graph(struct rev_graph *graph)
5000         size_t i;
5002         /* First, traverse all lines of revisions up to the active one. */
5003         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
5004                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
5005                         break;
5007                 push_rev_graph(graph->next, graph->rev[graph->pos]);
5008         }
5010         /* Interleave the new revision parent(s). */
5011         for (i = 0; !graph->boundary && i < graph->parents->size; i++)
5012                 push_rev_graph(graph->next, graph->parents->rev[i]);
5014         /* Lastly, put any remaining revisions. */
5015         for (i = graph->pos + 1; i < graph->size; i++)
5016                 push_rev_graph(graph->next, graph->rev[i]);
5019 static void
5020 update_rev_graph(struct rev_graph *graph)
5022         /* If this is the finalizing update ... */
5023         if (graph->commit)
5024                 prepare_rev_graph(graph);
5026         /* Graph visualization needs a one rev look-ahead,
5027          * so the first update doesn't visualize anything. */
5028         if (!graph->prev->commit)
5029                 return;
5031         draw_rev_graph(graph->prev);
5032         done_rev_graph(graph->prev->prev);
5036 /*
5037  * Main view backend
5038  */
5040 static bool
5041 main_draw(struct view *view, struct line *line, unsigned int lineno)
5043         struct commit *commit = line->data;
5045         if (!*commit->author)
5046                 return FALSE;
5048         if (opt_date && draw_date(view, &commit->time))
5049                 return TRUE;
5051         if (opt_author &&
5052             draw_field(view, LINE_MAIN_AUTHOR, commit->author, opt_author_cols, TRUE))
5053                 return TRUE;
5055         if (opt_rev_graph && commit->graph_size &&
5056             draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
5057                 return TRUE;
5059         if (opt_show_refs && commit->refs) {
5060                 size_t i = 0;
5062                 do {
5063                         enum line_type type;
5065                         if (commit->refs[i]->head)
5066                                 type = LINE_MAIN_HEAD;
5067                         else if (commit->refs[i]->ltag)
5068                                 type = LINE_MAIN_LOCAL_TAG;
5069                         else if (commit->refs[i]->tag)
5070                                 type = LINE_MAIN_TAG;
5071                         else if (commit->refs[i]->tracked)
5072                                 type = LINE_MAIN_TRACKED;
5073                         else if (commit->refs[i]->remote)
5074                                 type = LINE_MAIN_REMOTE;
5075                         else
5076                                 type = LINE_MAIN_REF;
5078                         if (draw_text(view, type, "[", TRUE) ||
5079                             draw_text(view, type, commit->refs[i]->name, TRUE) ||
5080                             draw_text(view, type, "]", TRUE))
5081                                 return TRUE;
5083                         if (draw_text(view, LINE_DEFAULT, " ", TRUE))
5084                                 return TRUE;
5085                 } while (commit->refs[i++]->next);
5086         }
5088         draw_text(view, LINE_DEFAULT, commit->title, TRUE);
5089         return TRUE;
5092 /* Reads git log --pretty=raw output and parses it into the commit struct. */
5093 static bool
5094 main_read(struct view *view, char *line)
5096         static struct rev_graph *graph = graph_stacks;
5097         enum line_type type;
5098         struct commit *commit;
5100         if (!line) {
5101                 int i;
5103                 if (!view->lines && !view->parent)
5104                         die("No revisions match the given arguments.");
5105                 if (view->lines > 0) {
5106                         commit = view->line[view->lines - 1].data;
5107                         if (!*commit->author) {
5108                                 view->lines--;
5109                                 free(commit);
5110                                 graph->commit = NULL;
5111                         }
5112                 }
5113                 update_rev_graph(graph);
5115                 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
5116                         clear_rev_graph(&graph_stacks[i]);
5117                 return TRUE;
5118         }
5120         type = get_line_type(line);
5121         if (type == LINE_COMMIT) {
5122                 commit = calloc(1, sizeof(struct commit));
5123                 if (!commit)
5124                         return FALSE;
5126                 line += STRING_SIZE("commit ");
5127                 if (*line == '-') {
5128                         graph->boundary = 1;
5129                         line++;
5130                 }
5132                 string_copy_rev(commit->id, line);
5133                 commit->refs = get_refs(commit->id);
5134                 graph->commit = commit;
5135                 add_line_data(view, commit, LINE_MAIN_COMMIT);
5137                 while ((line = strchr(line, ' '))) {
5138                         line++;
5139                         push_rev_graph(graph->parents, line);
5140                         commit->has_parents = TRUE;
5141                 }
5142                 return TRUE;
5143         }
5145         if (!view->lines)
5146                 return TRUE;
5147         commit = view->line[view->lines - 1].data;
5149         switch (type) {
5150         case LINE_PARENT:
5151                 if (commit->has_parents)
5152                         break;
5153                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
5154                 break;
5156         case LINE_AUTHOR:
5157         {
5158                 /* Parse author lines where the name may be empty:
5159                  *      author  <email@address.tld> 1138474660 +0100
5160                  */
5161                 char *ident = line + STRING_SIZE("author ");
5162                 char *nameend = strchr(ident, '<');
5163                 char *emailend = strchr(ident, '>');
5165                 if (!nameend || !emailend)
5166                         break;
5168                 update_rev_graph(graph);
5169                 graph = graph->next;
5171                 *nameend = *emailend = 0;
5172                 ident = chomp_string(ident);
5173                 if (!*ident) {
5174                         ident = chomp_string(nameend + 1);
5175                         if (!*ident)
5176                                 ident = "Unknown";
5177                 }
5179                 string_ncopy(commit->author, ident, strlen(ident));
5181                 /* Parse epoch and timezone */
5182                 if (emailend[1] == ' ') {
5183                         char *secs = emailend + 2;
5184                         char *zone = strchr(secs, ' ');
5185                         time_t time = (time_t) atol(secs);
5187                         if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
5188                                 long tz;
5190                                 zone++;
5191                                 tz  = ('0' - zone[1]) * 60 * 60 * 10;
5192                                 tz += ('0' - zone[2]) * 60 * 60;
5193                                 tz += ('0' - zone[3]) * 60;
5194                                 tz += ('0' - zone[4]) * 60;
5196                                 if (zone[0] == '-')
5197                                         tz = -tz;
5199                                 time -= tz;
5200                         }
5202                         gmtime_r(&time, &commit->time);
5203                 }
5204                 break;
5205         }
5206         default:
5207                 /* Fill in the commit title if it has not already been set. */
5208                 if (commit->title[0])
5209                         break;
5211                 /* Require titles to start with a non-space character at the
5212                  * offset used by git log. */
5213                 if (strncmp(line, "    ", 4))
5214                         break;
5215                 line += 4;
5216                 /* Well, if the title starts with a whitespace character,
5217                  * try to be forgiving.  Otherwise we end up with no title. */
5218                 while (isspace(*line))
5219                         line++;
5220                 if (*line == '\0')
5221                         break;
5222                 /* FIXME: More graceful handling of titles; append "..." to
5223                  * shortened titles, etc. */
5225                 string_ncopy(commit->title, line, strlen(line));
5226         }
5228         return TRUE;
5231 static enum request
5232 main_request(struct view *view, enum request request, struct line *line)
5234         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
5236         switch (request) {
5237         case REQ_ENTER:
5238                 open_view(view, REQ_VIEW_DIFF, flags);
5239                 break;
5240         case REQ_REFRESH:
5241                 load_refs();
5242                 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
5243                 break;
5244         default:
5245                 return request;
5246         }
5248         return REQ_NONE;
5251 static bool
5252 grep_refs(struct ref **refs, regex_t *regex)
5254         regmatch_t pmatch;
5255         size_t i = 0;
5257         if (!refs)
5258                 return FALSE;
5259         do {
5260                 if (regexec(regex, refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
5261                         return TRUE;
5262         } while (refs[i++]->next);
5264         return FALSE;
5267 static bool
5268 main_grep(struct view *view, struct line *line)
5270         struct commit *commit = line->data;
5271         enum { S_TITLE, S_AUTHOR, S_DATE, S_REFS, S_END } state;
5272         char buf[DATE_COLS + 1];
5273         regmatch_t pmatch;
5275         for (state = S_TITLE; state < S_END; state++) {
5276                 char *text;
5278                 switch (state) {
5279                 case S_TITLE:   text = commit->title;   break;
5280                 case S_AUTHOR:
5281                         if (!opt_author)
5282                                 continue;
5283                         text = commit->author;
5284                         break;
5285                 case S_DATE:
5286                         if (!opt_date)
5287                                 continue;
5288                         if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
5289                                 continue;
5290                         text = buf;
5291                         break;
5292                 case S_REFS:
5293                         if (!opt_show_refs)
5294                                 continue;
5295                         if (grep_refs(commit->refs, view->regex) == TRUE)
5296                                 return TRUE;
5297                         continue;
5298                 default:
5299                         return FALSE;
5300                 }
5302                 if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
5303                         return TRUE;
5304         }
5306         return FALSE;
5309 static void
5310 main_select(struct view *view, struct line *line)
5312         struct commit *commit = line->data;
5314         string_copy_rev(view->ref, commit->id);
5315         string_copy_rev(ref_commit, view->ref);
5318 static struct view_ops main_ops = {
5319         "commit",
5320         NULL,
5321         main_read,
5322         main_draw,
5323         main_request,
5324         main_grep,
5325         main_select,
5326 };
5329 /*
5330  * Unicode / UTF-8 handling
5331  *
5332  * NOTE: Much of the following code for dealing with unicode is derived from
5333  * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
5334  * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
5335  */
5337 /* I've (over)annotated a lot of code snippets because I am not entirely
5338  * confident that the approach taken by this small UTF-8 interface is correct.
5339  * --jonas */
5341 static inline int
5342 unicode_width(unsigned long c)
5344         if (c >= 0x1100 &&
5345            (c <= 0x115f                         /* Hangul Jamo */
5346             || c == 0x2329
5347             || c == 0x232a
5348             || (c >= 0x2e80  && c <= 0xa4cf && c != 0x303f)
5349                                                 /* CJK ... Yi */
5350             || (c >= 0xac00  && c <= 0xd7a3)    /* Hangul Syllables */
5351             || (c >= 0xf900  && c <= 0xfaff)    /* CJK Compatibility Ideographs */
5352             || (c >= 0xfe30  && c <= 0xfe6f)    /* CJK Compatibility Forms */
5353             || (c >= 0xff00  && c <= 0xff60)    /* Fullwidth Forms */
5354             || (c >= 0xffe0  && c <= 0xffe6)
5355             || (c >= 0x20000 && c <= 0x2fffd)
5356             || (c >= 0x30000 && c <= 0x3fffd)))
5357                 return 2;
5359         if (c == '\t')
5360                 return opt_tab_size;
5362         return 1;
5365 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
5366  * Illegal bytes are set one. */
5367 static const unsigned char utf8_bytes[256] = {
5368         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,
5369         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,
5370         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,
5371         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,
5372         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,
5373         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,
5374         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,
5375         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,
5376 };
5378 /* Decode UTF-8 multi-byte representation into a unicode character. */
5379 static inline unsigned long
5380 utf8_to_unicode(const char *string, size_t length)
5382         unsigned long unicode;
5384         switch (length) {
5385         case 1:
5386                 unicode  =   string[0];
5387                 break;
5388         case 2:
5389                 unicode  =  (string[0] & 0x1f) << 6;
5390                 unicode +=  (string[1] & 0x3f);
5391                 break;
5392         case 3:
5393                 unicode  =  (string[0] & 0x0f) << 12;
5394                 unicode += ((string[1] & 0x3f) << 6);
5395                 unicode +=  (string[2] & 0x3f);
5396                 break;
5397         case 4:
5398                 unicode  =  (string[0] & 0x0f) << 18;
5399                 unicode += ((string[1] & 0x3f) << 12);
5400                 unicode += ((string[2] & 0x3f) << 6);
5401                 unicode +=  (string[3] & 0x3f);
5402                 break;
5403         case 5:
5404                 unicode  =  (string[0] & 0x0f) << 24;
5405                 unicode += ((string[1] & 0x3f) << 18);
5406                 unicode += ((string[2] & 0x3f) << 12);
5407                 unicode += ((string[3] & 0x3f) << 6);
5408                 unicode +=  (string[4] & 0x3f);
5409                 break;
5410         case 6:
5411                 unicode  =  (string[0] & 0x01) << 30;
5412                 unicode += ((string[1] & 0x3f) << 24);
5413                 unicode += ((string[2] & 0x3f) << 18);
5414                 unicode += ((string[3] & 0x3f) << 12);
5415                 unicode += ((string[4] & 0x3f) << 6);
5416                 unicode +=  (string[5] & 0x3f);
5417                 break;
5418         default:
5419                 die("Invalid unicode length");
5420         }
5422         /* Invalid characters could return the special 0xfffd value but NUL
5423          * should be just as good. */
5424         return unicode > 0xffff ? 0 : unicode;
5427 /* Calculates how much of string can be shown within the given maximum width
5428  * and sets trimmed parameter to non-zero value if all of string could not be
5429  * shown. If the reserve flag is TRUE, it will reserve at least one
5430  * trailing character, which can be useful when drawing a delimiter.
5431  *
5432  * Returns the number of bytes to output from string to satisfy max_width. */
5433 static size_t
5434 utf8_length(const char *string, int *width, size_t max_width, int *trimmed, bool reserve)
5436         const char *start = string;
5437         const char *end = strchr(string, '\0');
5438         unsigned char last_bytes = 0;
5439         size_t last_ucwidth = 0;
5441         *width = 0;
5442         *trimmed = 0;
5444         while (string < end) {
5445                 int c = *(unsigned char *) string;
5446                 unsigned char bytes = utf8_bytes[c];
5447                 size_t ucwidth;
5448                 unsigned long unicode;
5450                 if (string + bytes > end)
5451                         break;
5453                 /* Change representation to figure out whether
5454                  * it is a single- or double-width character. */
5456                 unicode = utf8_to_unicode(string, bytes);
5457                 /* FIXME: Graceful handling of invalid unicode character. */
5458                 if (!unicode)
5459                         break;
5461                 ucwidth = unicode_width(unicode);
5462                 *width  += ucwidth;
5463                 if (*width > max_width) {
5464                         *trimmed = 1;
5465                         *width -= ucwidth;
5466                         if (reserve && *width == max_width) {
5467                                 string -= last_bytes;
5468                                 *width -= last_ucwidth;
5469                         }
5470                         break;
5471                 }
5473                 string  += bytes;
5474                 last_bytes = bytes;
5475                 last_ucwidth = ucwidth;
5476         }
5478         return string - start;
5482 /*
5483  * Status management
5484  */
5486 /* Whether or not the curses interface has been initialized. */
5487 static bool cursed = FALSE;
5489 /* The status window is used for polling keystrokes. */
5490 static WINDOW *status_win;
5492 static bool status_empty = TRUE;
5494 /* Update status and title window. */
5495 static void
5496 report(const char *msg, ...)
5498         struct view *view = display[current_view];
5500         if (input_mode)
5501                 return;
5503         if (!view) {
5504                 char buf[SIZEOF_STR];
5505                 va_list args;
5507                 va_start(args, msg);
5508                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
5509                         buf[sizeof(buf) - 1] = 0;
5510                         buf[sizeof(buf) - 2] = '.';
5511                         buf[sizeof(buf) - 3] = '.';
5512                         buf[sizeof(buf) - 4] = '.';
5513                 }
5514                 va_end(args);
5515                 die("%s", buf);
5516         }
5518         if (!status_empty || *msg) {
5519                 va_list args;
5521                 va_start(args, msg);
5523                 wmove(status_win, 0, 0);
5524                 if (*msg) {
5525                         vwprintw(status_win, msg, args);
5526                         status_empty = FALSE;
5527                 } else {
5528                         status_empty = TRUE;
5529                 }
5530                 wclrtoeol(status_win);
5531                 wrefresh(status_win);
5533                 va_end(args);
5534         }
5536         update_view_title(view);
5537         update_display_cursor(view);
5540 /* Controls when nodelay should be in effect when polling user input. */
5541 static void
5542 set_nonblocking_input(bool loading)
5544         static unsigned int loading_views;
5546         if ((loading == FALSE && loading_views-- == 1) ||
5547             (loading == TRUE  && loading_views++ == 0))
5548                 nodelay(status_win, loading);
5551 static void
5552 init_display(void)
5554         int x, y;
5556         /* Initialize the curses library */
5557         if (isatty(STDIN_FILENO)) {
5558                 cursed = !!initscr();
5559                 opt_tty = stdin;
5560         } else {
5561                 /* Leave stdin and stdout alone when acting as a pager. */
5562                 opt_tty = fopen("/dev/tty", "r+");
5563                 if (!opt_tty)
5564                         die("Failed to open /dev/tty");
5565                 cursed = !!newterm(NULL, opt_tty, opt_tty);
5566         }
5568         if (!cursed)
5569                 die("Failed to initialize curses");
5571         nonl();         /* Tell curses not to do NL->CR/NL on output */
5572         cbreak();       /* Take input chars one at a time, no wait for \n */
5573         noecho();       /* Don't echo input */
5574         leaveok(stdscr, TRUE);
5576         if (has_colors())
5577                 init_colors();
5579         getmaxyx(stdscr, y, x);
5580         status_win = newwin(1, 0, y - 1, 0);
5581         if (!status_win)
5582                 die("Failed to create status window");
5584         /* Enable keyboard mapping */
5585         keypad(status_win, TRUE);
5586         wbkgdset(status_win, get_line_attr(LINE_STATUS));
5588         TABSIZE = opt_tab_size;
5589         if (opt_line_graphics) {
5590                 line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
5591         }
5594 static bool
5595 prompt_yesno(const char *prompt)
5597         enum { WAIT, STOP, CANCEL  } status = WAIT;
5598         bool answer = FALSE;
5600         while (status == WAIT) {
5601                 struct view *view;
5602                 int i, key;
5604                 input_mode = TRUE;
5606                 foreach_view (view, i)
5607                         update_view(view);
5609                 input_mode = FALSE;
5611                 mvwprintw(status_win, 0, 0, "%s [Yy]/[Nn]", prompt);
5612                 wclrtoeol(status_win);
5614                 /* Refresh, accept single keystroke of input */
5615                 key = wgetch(status_win);
5616                 switch (key) {
5617                 case ERR:
5618                         break;
5620                 case 'y':
5621                 case 'Y':
5622                         answer = TRUE;
5623                         status = STOP;
5624                         break;
5626                 case KEY_ESC:
5627                 case KEY_RETURN:
5628                 case KEY_ENTER:
5629                 case KEY_BACKSPACE:
5630                 case 'n':
5631                 case 'N':
5632                 case '\n':
5633                 default:
5634                         answer = FALSE;
5635                         status = CANCEL;
5636                 }
5637         }
5639         /* Clear the status window */
5640         status_empty = FALSE;
5641         report("");
5643         return answer;
5646 static char *
5647 read_prompt(const char *prompt)
5649         enum { READING, STOP, CANCEL } status = READING;
5650         static char buf[sizeof(opt_cmd) - STRING_SIZE("git \0")];
5651         int pos = 0;
5653         while (status == READING) {
5654                 struct view *view;
5655                 int i, key;
5657                 input_mode = TRUE;
5659                 foreach_view (view, i)
5660                         update_view(view);
5662                 input_mode = FALSE;
5664                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
5665                 wclrtoeol(status_win);
5667                 /* Refresh, accept single keystroke of input */
5668                 key = wgetch(status_win);
5669                 switch (key) {
5670                 case KEY_RETURN:
5671                 case KEY_ENTER:
5672                 case '\n':
5673                         status = pos ? STOP : CANCEL;
5674                         break;
5676                 case KEY_BACKSPACE:
5677                         if (pos > 0)
5678                                 pos--;
5679                         else
5680                                 status = CANCEL;
5681                         break;
5683                 case KEY_ESC:
5684                         status = CANCEL;
5685                         break;
5687                 case ERR:
5688                         break;
5690                 default:
5691                         if (pos >= sizeof(buf)) {
5692                                 report("Input string too long");
5693                                 return NULL;
5694                         }
5696                         if (isprint(key))
5697                                 buf[pos++] = (char) key;
5698                 }
5699         }
5701         /* Clear the status window */
5702         status_empty = FALSE;
5703         report("");
5705         if (status == CANCEL)
5706                 return NULL;
5708         buf[pos++] = 0;
5710         return buf;
5713 /*
5714  * Repository references
5715  */
5717 static struct ref *refs = NULL;
5718 static size_t refs_alloc = 0;
5719 static size_t refs_size = 0;
5721 /* Id <-> ref store */
5722 static struct ref ***id_refs = NULL;
5723 static size_t id_refs_alloc = 0;
5724 static size_t id_refs_size = 0;
5726 static int
5727 compare_refs(const void *ref1_, const void *ref2_)
5729         const struct ref *ref1 = *(const struct ref **)ref1_;
5730         const struct ref *ref2 = *(const struct ref **)ref2_;
5732         if (ref1->tag != ref2->tag)
5733                 return ref2->tag - ref1->tag;
5734         if (ref1->ltag != ref2->ltag)
5735                 return ref2->ltag - ref2->ltag;
5736         if (ref1->head != ref2->head)
5737                 return ref2->head - ref1->head;
5738         if (ref1->tracked != ref2->tracked)
5739                 return ref2->tracked - ref1->tracked;
5740         if (ref1->remote != ref2->remote)
5741                 return ref2->remote - ref1->remote;
5742         return strcmp(ref1->name, ref2->name);
5745 static struct ref **
5746 get_refs(const char *id)
5748         struct ref ***tmp_id_refs;
5749         struct ref **ref_list = NULL;
5750         size_t ref_list_alloc = 0;
5751         size_t ref_list_size = 0;
5752         size_t i;
5754         for (i = 0; i < id_refs_size; i++)
5755                 if (!strcmp(id, id_refs[i][0]->id))
5756                         return id_refs[i];
5758         tmp_id_refs = realloc_items(id_refs, &id_refs_alloc, id_refs_size + 1,
5759                                     sizeof(*id_refs));
5760         if (!tmp_id_refs)
5761                 return NULL;
5763         id_refs = tmp_id_refs;
5765         for (i = 0; i < refs_size; i++) {
5766                 struct ref **tmp;
5768                 if (strcmp(id, refs[i].id))
5769                         continue;
5771                 tmp = realloc_items(ref_list, &ref_list_alloc,
5772                                     ref_list_size + 1, sizeof(*ref_list));
5773                 if (!tmp) {
5774                         if (ref_list)
5775                                 free(ref_list);
5776                         return NULL;
5777                 }
5779                 ref_list = tmp;
5780                 ref_list[ref_list_size] = &refs[i];
5781                 /* XXX: The properties of the commit chains ensures that we can
5782                  * safely modify the shared ref. The repo references will
5783                  * always be similar for the same id. */
5784                 ref_list[ref_list_size]->next = 1;
5786                 ref_list_size++;
5787         }
5789         if (ref_list) {
5790                 qsort(ref_list, ref_list_size, sizeof(*ref_list), compare_refs);
5791                 ref_list[ref_list_size - 1]->next = 0;
5792                 id_refs[id_refs_size++] = ref_list;
5793         }
5795         return ref_list;
5798 static int
5799 read_ref(char *id, size_t idlen, char *name, size_t namelen)
5801         struct ref *ref;
5802         bool tag = FALSE;
5803         bool ltag = FALSE;
5804         bool remote = FALSE;
5805         bool tracked = FALSE;
5806         bool check_replace = FALSE;
5807         bool head = FALSE;
5809         if (!prefixcmp(name, "refs/tags/")) {
5810                 if (!suffixcmp(name, namelen, "^{}")) {
5811                         namelen -= 3;
5812                         name[namelen] = 0;
5813                         if (refs_size > 0 && refs[refs_size - 1].ltag == TRUE)
5814                                 check_replace = TRUE;
5815                 } else {
5816                         ltag = TRUE;
5817                 }
5819                 tag = TRUE;
5820                 namelen -= STRING_SIZE("refs/tags/");
5821                 name    += STRING_SIZE("refs/tags/");
5823         } else if (!prefixcmp(name, "refs/remotes/")) {
5824                 remote = TRUE;
5825                 namelen -= STRING_SIZE("refs/remotes/");
5826                 name    += STRING_SIZE("refs/remotes/");
5827                 tracked  = !strcmp(opt_remote, name);
5829         } else if (!prefixcmp(name, "refs/heads/")) {
5830                 namelen -= STRING_SIZE("refs/heads/");
5831                 name    += STRING_SIZE("refs/heads/");
5832                 head     = !strncmp(opt_head, name, namelen);
5834         } else if (!strcmp(name, "HEAD")) {
5835                 opt_no_head = FALSE;
5836                 return OK;
5837         }
5839         if (check_replace && !strcmp(name, refs[refs_size - 1].name)) {
5840                 /* it's an annotated tag, replace the previous sha1 with the
5841                  * resolved commit id; relies on the fact git-ls-remote lists
5842                  * the commit id of an annotated tag right before the commit id
5843                  * it points to. */
5844                 refs[refs_size - 1].ltag = ltag;
5845                 string_copy_rev(refs[refs_size - 1].id, id);
5847                 return OK;
5848         }
5849         refs = realloc_items(refs, &refs_alloc, refs_size + 1, sizeof(*refs));
5850         if (!refs)
5851                 return ERR;
5853         ref = &refs[refs_size++];
5854         ref->name = malloc(namelen + 1);
5855         if (!ref->name)
5856                 return ERR;
5858         strncpy(ref->name, name, namelen);
5859         ref->name[namelen] = 0;
5860         ref->head = head;
5861         ref->tag = tag;
5862         ref->ltag = ltag;
5863         ref->remote = remote;
5864         ref->tracked = tracked;
5865         string_copy_rev(ref->id, id);
5867         return OK;
5870 static int
5871 load_refs(void)
5873         const char *cmd_env = getenv("TIG_LS_REMOTE");
5874         const char *cmd = cmd_env && *cmd_env ? cmd_env : TIG_LS_REMOTE;
5876         if (!*opt_git_dir)
5877                 return OK;
5879         while (refs_size > 0)
5880                 free(refs[--refs_size].name);
5881         while (id_refs_size > 0)
5882                 free(id_refs[--id_refs_size]);
5884         return read_properties(popen(cmd, "r"), "\t", read_ref);
5887 static int
5888 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
5890         if (!strcmp(name, "i18n.commitencoding"))
5891                 string_ncopy(opt_encoding, value, valuelen);
5893         if (!strcmp(name, "core.editor"))
5894                 string_ncopy(opt_editor, value, valuelen);
5896         /* branch.<head>.remote */
5897         if (*opt_head &&
5898             !strncmp(name, "branch.", 7) &&
5899             !strncmp(name + 7, opt_head, strlen(opt_head)) &&
5900             !strcmp(name + 7 + strlen(opt_head), ".remote"))
5901                 string_ncopy(opt_remote, value, valuelen);
5903         if (*opt_head && *opt_remote &&
5904             !strncmp(name, "branch.", 7) &&
5905             !strncmp(name + 7, opt_head, strlen(opt_head)) &&
5906             !strcmp(name + 7 + strlen(opt_head), ".merge")) {
5907                 size_t from = strlen(opt_remote);
5909                 if (!prefixcmp(value, "refs/heads/")) {
5910                         value += STRING_SIZE("refs/heads/");
5911                         valuelen -= STRING_SIZE("refs/heads/");
5912                 }
5914                 if (!string_format_from(opt_remote, &from, "/%s", value))
5915                         opt_remote[0] = 0;
5916         }
5918         return OK;
5921 static int
5922 load_git_config(void)
5924         return read_properties(popen("git " GIT_CONFIG " --list", "r"),
5925                                "=", read_repo_config_option);
5928 static int
5929 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
5931         if (!opt_git_dir[0]) {
5932                 string_ncopy(opt_git_dir, name, namelen);
5934         } else if (opt_is_inside_work_tree == -1) {
5935                 /* This can be 3 different values depending on the
5936                  * version of git being used. If git-rev-parse does not
5937                  * understand --is-inside-work-tree it will simply echo
5938                  * the option else either "true" or "false" is printed.
5939                  * Default to true for the unknown case. */
5940                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
5942         } else if (opt_cdup[0] == ' ') {
5943                 string_ncopy(opt_cdup, name, namelen);
5944         } else {
5945                 if (!prefixcmp(name, "refs/heads/")) {
5946                         namelen -= STRING_SIZE("refs/heads/");
5947                         name    += STRING_SIZE("refs/heads/");
5948                         string_ncopy(opt_head, name, namelen);
5949                 }
5950         }
5952         return OK;
5955 static int
5956 load_repo_info(void)
5958         int result;
5959         FILE *pipe = popen("(git rev-parse --git-dir --is-inside-work-tree "
5960                            " --show-cdup; git symbolic-ref HEAD) 2>/dev/null", "r");
5962         /* XXX: The line outputted by "--show-cdup" can be empty so
5963          * initialize it to something invalid to make it possible to
5964          * detect whether it has been set or not. */
5965         opt_cdup[0] = ' ';
5967         result = read_properties(pipe, "=", read_repo_info);
5968         if (opt_cdup[0] == ' ')
5969                 opt_cdup[0] = 0;
5971         return result;
5974 static int
5975 read_properties(FILE *pipe, const char *separators,
5976                 int (*read_property)(char *, size_t, char *, size_t))
5978         char buffer[BUFSIZ];
5979         char *name;
5980         int state = OK;
5982         if (!pipe)
5983                 return ERR;
5985         while (state == OK && (name = fgets(buffer, sizeof(buffer), pipe))) {
5986                 char *value;
5987                 size_t namelen;
5988                 size_t valuelen;
5990                 name = chomp_string(name);
5991                 namelen = strcspn(name, separators);
5993                 if (name[namelen]) {
5994                         name[namelen] = 0;
5995                         value = chomp_string(name + namelen + 1);
5996                         valuelen = strlen(value);
5998                 } else {
5999                         value = "";
6000                         valuelen = 0;
6001                 }
6003                 state = read_property(name, namelen, value, valuelen);
6004         }
6006         if (state != ERR && ferror(pipe))
6007                 state = ERR;
6009         pclose(pipe);
6011         return state;
6015 /*
6016  * Main
6017  */
6019 static void __NORETURN
6020 quit(int sig)
6022         /* XXX: Restore tty modes and let the OS cleanup the rest! */
6023         if (cursed)
6024                 endwin();
6025         exit(0);
6028 static void __NORETURN
6029 die(const char *err, ...)
6031         va_list args;
6033         endwin();
6035         va_start(args, err);
6036         fputs("tig: ", stderr);
6037         vfprintf(stderr, err, args);
6038         fputs("\n", stderr);
6039         va_end(args);
6041         exit(1);
6044 static void
6045 warn(const char *msg, ...)
6047         va_list args;
6049         va_start(args, msg);
6050         fputs("tig warning: ", stderr);
6051         vfprintf(stderr, msg, args);
6052         fputs("\n", stderr);
6053         va_end(args);
6056 int
6057 main(int argc, const char *argv[])
6059         struct view *view;
6060         enum request request;
6061         size_t i;
6063         signal(SIGINT, quit);
6065         if (setlocale(LC_ALL, "")) {
6066                 char *codeset = nl_langinfo(CODESET);
6068                 string_ncopy(opt_codeset, codeset, strlen(codeset));
6069         }
6071         if (load_repo_info() == ERR)
6072                 die("Failed to load repo info.");
6074         if (load_options() == ERR)
6075                 die("Failed to load user config.");
6077         if (load_git_config() == ERR)
6078                 die("Failed to load repo config.");
6080         request = parse_options(argc, argv);
6081         if (request == REQ_NONE)
6082                 return 0;
6084         /* Require a git repository unless when running in pager mode. */
6085         if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
6086                 die("Not a git repository");
6088         if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
6089                 opt_utf8 = FALSE;
6091         if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
6092                 opt_iconv = iconv_open(opt_codeset, opt_encoding);
6093                 if (opt_iconv == ICONV_NONE)
6094                         die("Failed to initialize character set conversion");
6095         }
6097         if (load_refs() == ERR)
6098                 die("Failed to load refs.");
6100         foreach_view (view, i)
6101                 view->cmd_env = getenv(view->cmd_env);
6103         init_display();
6105         while (view_driver(display[current_view], request)) {
6106                 int key;
6107                 int i;
6109                 foreach_view (view, i)
6110                         update_view(view);
6111                 view = display[current_view];
6113                 /* Refresh, accept single keystroke of input */
6114                 key = wgetch(status_win);
6116                 /* wgetch() with nodelay() enabled returns ERR when there's no
6117                  * input. */
6118                 if (key == ERR) {
6119                         request = REQ_NONE;
6120                         continue;
6121                 }
6123                 request = get_keybinding(view->keymap, key);
6125                 /* Some low-level request handling. This keeps access to
6126                  * status_win restricted. */
6127                 switch (request) {
6128                 case REQ_PROMPT:
6129                 {
6130                         char *cmd = read_prompt(":");
6132                         if (cmd && string_format(opt_cmd, "git %s", cmd)) {
6133                                 if (strncmp(cmd, "show", 4) && isspace(cmd[4])) {
6134                                         request = REQ_VIEW_DIFF;
6135                                 } else {
6136                                         request = REQ_VIEW_PAGER;
6137                                 }
6139                                 /* Always reload^Wrerun commands from the prompt. */
6140                                 open_view(view, request, OPEN_RELOAD);
6141                         }
6143                         request = REQ_NONE;
6144                         break;
6145                 }
6146                 case REQ_SEARCH:
6147                 case REQ_SEARCH_BACK:
6148                 {
6149                         const char *prompt = request == REQ_SEARCH ? "/" : "?";
6150                         char *search = read_prompt(prompt);
6152                         if (search)
6153                                 string_ncopy(opt_search, search, strlen(search));
6154                         else
6155                                 request = REQ_NONE;
6156                         break;
6157                 }
6158                 case REQ_SCREEN_RESIZE:
6159                 {
6160                         int height, width;
6162                         getmaxyx(stdscr, height, width);
6164                         /* Resize the status view and let the view driver take
6165                          * care of resizing the displayed views. */
6166                         wresize(status_win, 1, width);
6167                         mvwin(status_win, height - 1, 0);
6168                         wrefresh(status_win);
6169                         break;
6170                 }
6171                 default:
6172                         break;
6173                 }
6174         }
6176         quit(0);
6178         return 0;