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