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