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