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