Code

argv: make prepare_update use FORMAT_NONE
[tig.git] / tig.c
1 /* Copyright (c) 2006-2010 Jonas Fonseca <fonseca@diku.dk>
2  *
3  * This program is free software; you can redistribute it and/or
4  * modify it under the terms of the GNU General Public License as
5  * published by the Free Software Foundation; either version 2 of
6  * the License, or (at your option) any later version.
7  *
8  * This program is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11  * GNU General Public License for more details.
12  */
14 #ifdef HAVE_CONFIG_H
15 #include "config.h"
16 #endif
18 #ifndef TIG_VERSION
19 #define TIG_VERSION "unknown-version"
20 #endif
22 #ifndef DEBUG
23 #define NDEBUG
24 #endif
26 #include <assert.h>
27 #include <errno.h>
28 #include <ctype.h>
29 #include <signal.h>
30 #include <stdarg.h>
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <sys/types.h>
35 #include <sys/wait.h>
36 #include <sys/stat.h>
37 #include <sys/select.h>
38 #include <unistd.h>
39 #include <sys/time.h>
40 #include <time.h>
41 #include <fcntl.h>
43 #include <regex.h>
45 #include <locale.h>
46 #include <langinfo.h>
47 #include <iconv.h>
49 /* ncurses(3): Must be defined to have extended wide-character functions. */
50 #define _XOPEN_SOURCE_EXTENDED
52 #ifdef HAVE_NCURSESW_NCURSES_H
53 #include <ncursesw/ncurses.h>
54 #else
55 #ifdef HAVE_NCURSES_NCURSES_H
56 #include <ncurses/ncurses.h>
57 #else
58 #include <ncurses.h>
59 #endif
60 #endif
62 #if __GNUC__ >= 3
63 #define __NORETURN __attribute__((__noreturn__))
64 #else
65 #define __NORETURN
66 #endif
68 static void __NORETURN die(const char *err, ...);
69 static void warn(const char *msg, ...);
70 static void report(const char *msg, ...);
72 #define ABS(x)          ((x) >= 0  ? (x) : -(x))
73 #define MIN(x, y)       ((x) < (y) ? (x) :  (y))
74 #define MAX(x, y)       ((x) > (y) ? (x) :  (y))
76 #define ARRAY_SIZE(x)   (sizeof(x) / sizeof(x[0]))
77 #define STRING_SIZE(x)  (sizeof(x) - 1)
79 #define SIZEOF_STR      1024    /* Default string size. */
80 #define SIZEOF_REF      256     /* Size of symbolic or SHA1 ID. */
81 #define SIZEOF_REV      41      /* Holds a SHA-1 and an ending NUL. */
82 #define SIZEOF_ARG      32      /* Default argument array size. */
84 /* Revision graph */
86 #define REVGRAPH_INIT   'I'
87 #define REVGRAPH_MERGE  'M'
88 #define REVGRAPH_BRANCH '+'
89 #define REVGRAPH_COMMIT '*'
90 #define REVGRAPH_BOUND  '^'
92 #define SIZEOF_REVGRAPH 19      /* Size of revision ancestry graphics. */
94 /* This color name can be used to refer to the default term colors. */
95 #define COLOR_DEFAULT   (-1)
97 #define ICONV_NONE      ((iconv_t) -1)
98 #ifndef ICONV_CONST
99 #define ICONV_CONST     /* nothing */
100 #endif
102 /* The format and size of the date column in the main view. */
103 #define DATE_FORMAT     "%Y-%m-%d %H:%M"
104 #define DATE_COLS       STRING_SIZE("2006-04-29 14:21 ")
105 #define DATE_SHORT_COLS STRING_SIZE("2006-04-29 ")
107 #define ID_COLS         8
108 #define AUTHOR_COLS     19
110 #define MIN_VIEW_HEIGHT 4
112 #define NULL_ID         "0000000000000000000000000000000000000000"
114 #define S_ISGITLINK(mode) (((mode) & S_IFMT) == 0160000)
116 /* Some ASCII-shorthands fitted into the ncurses namespace. */
117 #define KEY_TAB         '\t'
118 #define KEY_RETURN      '\r'
119 #define KEY_ESC         27
122 struct ref {
123         char id[SIZEOF_REV];    /* Commit SHA1 ID */
124         unsigned int head:1;    /* Is it the current HEAD? */
125         unsigned int tag:1;     /* Is it a tag? */
126         unsigned int ltag:1;    /* If so, is the tag local? */
127         unsigned int remote:1;  /* Is it a remote ref? */
128         unsigned int tracked:1; /* Is it the remote for the current HEAD? */
129         char name[1];           /* Ref name; tag or head names are shortened. */
130 };
132 struct ref_list {
133         char id[SIZEOF_REV];    /* Commit SHA1 ID */
134         size_t size;            /* Number of refs. */
135         struct ref **refs;      /* References for this ID. */
136 };
138 static struct ref *get_ref_head();
139 static struct ref_list *get_ref_list(const char *id);
140 static void foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data);
141 static int load_refs(void);
143 enum format_flags {
144         FORMAT_ALL,             /* Perform replacement in all arguments. */
145         FORMAT_DASH,            /* Perform replacement up until "--". */
146         FORMAT_NONE             /* No replacement should be performed. */
147 };
149 static bool format_argv(const char *dst[], const char *src[], enum format_flags flags);
151 enum input_status {
152         INPUT_OK,
153         INPUT_SKIP,
154         INPUT_STOP,
155         INPUT_CANCEL
156 };
158 typedef enum input_status (*input_handler)(void *data, char *buf, int c);
160 static char *prompt_input(const char *prompt, input_handler handler, void *data);
161 static bool prompt_yesno(const char *prompt);
163 struct menu_item {
164         int hotkey;
165         const char *text;
166         void *data;
167 };
169 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected);
171 /*
172  * Allocation helpers ... Entering macro hell to never be seen again.
173  */
175 #define DEFINE_ALLOCATOR(name, type, chunk_size)                                \
176 static type *                                                                   \
177 name(type **mem, size_t size, size_t increase)                                  \
178 {                                                                               \
179         size_t num_chunks = (size + chunk_size - 1) / chunk_size;               \
180         size_t num_chunks_new = (size + increase + chunk_size - 1) / chunk_size;\
181         type *tmp = *mem;                                                       \
182                                                                                 \
183         if (mem == NULL || num_chunks != num_chunks_new) {                      \
184                 tmp = realloc(tmp, num_chunks_new * chunk_size * sizeof(type)); \
185                 if (tmp)                                                        \
186                         *mem = tmp;                                             \
187         }                                                                       \
188                                                                                 \
189         return tmp;                                                             \
192 /*
193  * String helpers
194  */
196 static inline void
197 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
199         if (srclen > dstlen - 1)
200                 srclen = dstlen - 1;
202         strncpy(dst, src, srclen);
203         dst[srclen] = 0;
206 /* Shorthands for safely copying into a fixed buffer. */
208 #define string_copy(dst, src) \
209         string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
211 #define string_ncopy(dst, src, srclen) \
212         string_ncopy_do(dst, sizeof(dst), src, srclen)
214 #define string_copy_rev(dst, src) \
215         string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
217 #define string_add(dst, from, src) \
218         string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
220 static void
221 string_expand(char *dst, size_t dstlen, const char *src, int tabsize)
223         size_t size, pos;
225         for (size = pos = 0; size < dstlen - 1 && src[pos]; pos++) {
226                 if (src[pos] == '\t') {
227                         size_t expanded = tabsize - (size % tabsize);
229                         if (expanded + size >= dstlen - 1)
230                                 expanded = dstlen - size - 1;
231                         memcpy(dst + size, "        ", expanded);
232                         size += expanded;
233                 } else {
234                         dst[size++] = src[pos];
235                 }
236         }
238         dst[size] = 0;
241 static char *
242 chomp_string(char *name)
244         int namelen;
246         while (isspace(*name))
247                 name++;
249         namelen = strlen(name) - 1;
250         while (namelen > 0 && isspace(name[namelen]))
251                 name[namelen--] = 0;
253         return name;
256 static bool
257 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
259         va_list args;
260         size_t pos = bufpos ? *bufpos : 0;
262         va_start(args, fmt);
263         pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
264         va_end(args);
266         if (bufpos)
267                 *bufpos = pos;
269         return pos >= bufsize ? FALSE : TRUE;
272 #define string_format(buf, fmt, args...) \
273         string_nformat(buf, sizeof(buf), NULL, fmt, args)
275 #define string_format_from(buf, from, fmt, args...) \
276         string_nformat(buf, sizeof(buf), from, fmt, args)
278 static int
279 string_enum_compare(const char *str1, const char *str2, int len)
281         size_t i;
283 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
285         /* Diff-Header == DIFF_HEADER */
286         for (i = 0; i < len; i++) {
287                 if (toupper(str1[i]) == toupper(str2[i]))
288                         continue;
290                 if (string_enum_sep(str1[i]) &&
291                     string_enum_sep(str2[i]))
292                         continue;
294                 return str1[i] - str2[i];
295         }
297         return 0;
300 #define enum_equals(entry, str, len) \
301         ((entry).namelen == (len) && !string_enum_compare((entry).name, str, len))
303 struct enum_map {
304         const char *name;
305         int namelen;
306         int value;
307 };
309 #define ENUM_MAP(name, value) { name, STRING_SIZE(name), value }
311 static char *
312 enum_map_name(const char *name, size_t namelen)
314         static char buf[SIZEOF_STR];
315         int bufpos;
317         for (bufpos = 0; bufpos <= namelen; bufpos++) {
318                 buf[bufpos] = tolower(name[bufpos]);
319                 if (buf[bufpos] == '_')
320                         buf[bufpos] = '-';
321         }
323         buf[bufpos] = 0;
324         return buf;
327 #define enum_name(entry) enum_map_name((entry).name, (entry).namelen)
329 static bool
330 map_enum_do(const struct enum_map *map, size_t map_size, int *value, const char *name)
332         size_t namelen = strlen(name);
333         int i;
335         for (i = 0; i < map_size; i++)
336                 if (enum_equals(map[i], name, namelen)) {
337                         *value = map[i].value;
338                         return TRUE;
339                 }
341         return FALSE;
344 #define map_enum(attr, map, name) \
345         map_enum_do(map, ARRAY_SIZE(map), attr, name)
347 #define prefixcmp(str1, str2) \
348         strncmp(str1, str2, STRING_SIZE(str2))
350 static inline int
351 suffixcmp(const char *str, int slen, const char *suffix)
353         size_t len = slen >= 0 ? slen : strlen(str);
354         size_t suffixlen = strlen(suffix);
356         return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
360 /*
361  * Unicode / UTF-8 handling
362  *
363  * NOTE: Much of the following code for dealing with Unicode is derived from
364  * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
365  * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
366  */
368 static inline int
369 unicode_width(unsigned long c, int tab_size)
371         if (c >= 0x1100 &&
372            (c <= 0x115f                         /* Hangul Jamo */
373             || c == 0x2329
374             || c == 0x232a
375             || (c >= 0x2e80  && c <= 0xa4cf && c != 0x303f)
376                                                 /* CJK ... Yi */
377             || (c >= 0xac00  && c <= 0xd7a3)    /* Hangul Syllables */
378             || (c >= 0xf900  && c <= 0xfaff)    /* CJK Compatibility Ideographs */
379             || (c >= 0xfe30  && c <= 0xfe6f)    /* CJK Compatibility Forms */
380             || (c >= 0xff00  && c <= 0xff60)    /* Fullwidth Forms */
381             || (c >= 0xffe0  && c <= 0xffe6)
382             || (c >= 0x20000 && c <= 0x2fffd)
383             || (c >= 0x30000 && c <= 0x3fffd)))
384                 return 2;
386         if (c == '\t')
387                 return tab_size;
389         return 1;
392 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
393  * Illegal bytes are set one. */
394 static const unsigned char utf8_bytes[256] = {
395         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,
396         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,
397         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,
398         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,
399         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,
400         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,
401         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,
402         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,
403 };
405 static inline unsigned char
406 utf8_char_length(const char *string, const char *end)
408         int c = *(unsigned char *) string;
410         return utf8_bytes[c];
413 /* Decode UTF-8 multi-byte representation into a Unicode character. */
414 static inline unsigned long
415 utf8_to_unicode(const char *string, size_t length)
417         unsigned long unicode;
419         switch (length) {
420         case 1:
421                 unicode  =   string[0];
422                 break;
423         case 2:
424                 unicode  =  (string[0] & 0x1f) << 6;
425                 unicode +=  (string[1] & 0x3f);
426                 break;
427         case 3:
428                 unicode  =  (string[0] & 0x0f) << 12;
429                 unicode += ((string[1] & 0x3f) << 6);
430                 unicode +=  (string[2] & 0x3f);
431                 break;
432         case 4:
433                 unicode  =  (string[0] & 0x0f) << 18;
434                 unicode += ((string[1] & 0x3f) << 12);
435                 unicode += ((string[2] & 0x3f) << 6);
436                 unicode +=  (string[3] & 0x3f);
437                 break;
438         case 5:
439                 unicode  =  (string[0] & 0x0f) << 24;
440                 unicode += ((string[1] & 0x3f) << 18);
441                 unicode += ((string[2] & 0x3f) << 12);
442                 unicode += ((string[3] & 0x3f) << 6);
443                 unicode +=  (string[4] & 0x3f);
444                 break;
445         case 6:
446                 unicode  =  (string[0] & 0x01) << 30;
447                 unicode += ((string[1] & 0x3f) << 24);
448                 unicode += ((string[2] & 0x3f) << 18);
449                 unicode += ((string[3] & 0x3f) << 12);
450                 unicode += ((string[4] & 0x3f) << 6);
451                 unicode +=  (string[5] & 0x3f);
452                 break;
453         default:
454                 return 0;
455         }
457         /* Invalid characters could return the special 0xfffd value but NUL
458          * should be just as good. */
459         return unicode > 0xffff ? 0 : unicode;
462 /* Calculates how much of string can be shown within the given maximum width
463  * and sets trimmed parameter to non-zero value if all of string could not be
464  * shown. If the reserve flag is TRUE, it will reserve at least one
465  * trailing character, which can be useful when drawing a delimiter.
466  *
467  * Returns the number of bytes to output from string to satisfy max_width. */
468 static size_t
469 utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve, int tab_size)
471         const char *string = *start;
472         const char *end = strchr(string, '\0');
473         unsigned char last_bytes = 0;
474         size_t last_ucwidth = 0;
476         *width = 0;
477         *trimmed = 0;
479         while (string < end) {
480                 unsigned char bytes = utf8_char_length(string, end);
481                 size_t ucwidth;
482                 unsigned long unicode;
484                 if (string + bytes > end)
485                         break;
487                 /* Change representation to figure out whether
488                  * it is a single- or double-width character. */
490                 unicode = utf8_to_unicode(string, bytes);
491                 /* FIXME: Graceful handling of invalid Unicode character. */
492                 if (!unicode)
493                         break;
495                 ucwidth = unicode_width(unicode, tab_size);
496                 if (skip > 0) {
497                         skip -= ucwidth <= skip ? ucwidth : skip;
498                         *start += bytes;
499                 }
500                 *width  += ucwidth;
501                 if (*width > max_width) {
502                         *trimmed = 1;
503                         *width -= ucwidth;
504                         if (reserve && *width == max_width) {
505                                 string -= last_bytes;
506                                 *width -= last_ucwidth;
507                         }
508                         break;
509                 }
511                 string  += bytes;
512                 last_bytes = ucwidth ? bytes : 0;
513                 last_ucwidth = ucwidth;
514         }
516         return string - *start;
520 #define DATE_INFO \
521         DATE_(NO), \
522         DATE_(DEFAULT), \
523         DATE_(RELATIVE), \
524         DATE_(SHORT)
526 enum date {
527 #define DATE_(name) DATE_##name
528         DATE_INFO
529 #undef  DATE_
530 };
532 static const struct enum_map date_map[] = {
533 #define DATE_(name) ENUM_MAP(#name, DATE_##name)
534         DATE_INFO
535 #undef  DATE_
536 };
538 struct time {
539         time_t sec;
540         int tz;
541 };
543 static inline int timecmp(const struct time *t1, const struct time *t2)
545         return t1->sec - t2->sec;
548 static const char *
549 mkdate(const struct time *time, enum date date)
551         static char buf[DATE_COLS + 1];
552         static const struct enum_map reldate[] = {
553                 { "second", 1,                  60 * 2 },
554                 { "minute", 60,                 60 * 60 * 2 },
555                 { "hour",   60 * 60,            60 * 60 * 24 * 2 },
556                 { "day",    60 * 60 * 24,       60 * 60 * 24 * 7 * 2 },
557                 { "week",   60 * 60 * 24 * 7,   60 * 60 * 24 * 7 * 5 },
558                 { "month",  60 * 60 * 24 * 30,  60 * 60 * 24 * 30 * 12 },
559         };
560         struct tm tm;
562         if (!date || !time || !time->sec)
563                 return "";
565         if (date == DATE_RELATIVE) {
566                 struct timeval now;
567                 time_t date = time->sec + time->tz;
568                 time_t seconds;
569                 int i;
571                 gettimeofday(&now, NULL);
572                 seconds = now.tv_sec < date ? date - now.tv_sec : now.tv_sec - date;
573                 for (i = 0; i < ARRAY_SIZE(reldate); i++) {
574                         if (seconds >= reldate[i].value)
575                                 continue;
577                         seconds /= reldate[i].namelen;
578                         if (!string_format(buf, "%ld %s%s %s",
579                                            seconds, reldate[i].name,
580                                            seconds > 1 ? "s" : "",
581                                            now.tv_sec >= date ? "ago" : "ahead"))
582                                 break;
583                         return buf;
584                 }
585         }
587         gmtime_r(&time->sec, &tm);
588         return strftime(buf, sizeof(buf), DATE_FORMAT, &tm) ? buf : NULL;
592 #define AUTHOR_VALUES \
593         AUTHOR_(NO), \
594         AUTHOR_(FULL), \
595         AUTHOR_(ABBREVIATED)
597 enum author {
598 #define AUTHOR_(name) AUTHOR_##name
599         AUTHOR_VALUES,
600 #undef  AUTHOR_
601         AUTHOR_DEFAULT = AUTHOR_FULL
602 };
604 static const struct enum_map author_map[] = {
605 #define AUTHOR_(name) ENUM_MAP(#name, AUTHOR_##name)
606         AUTHOR_VALUES
607 #undef  AUTHOR_
608 };
610 static const char *
611 get_author_initials(const char *author)
613         static char initials[AUTHOR_COLS * 6 + 1];
614         size_t pos = 0;
615         const char *end = strchr(author, '\0');
617 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@' || (c) == '-')
619         memset(initials, 0, sizeof(initials));
620         while (author < end) {
621                 unsigned char bytes;
622                 size_t i;
624                 while (is_initial_sep(*author))
625                         author++;
627                 bytes = utf8_char_length(author, end);
628                 if (bytes < sizeof(initials) - 1 - pos) {
629                         while (bytes--) {
630                                 initials[pos++] = *author++;
631                         }
632                 }
634                 for (i = pos; author < end && !is_initial_sep(*author); author++) {
635                         if (i < sizeof(initials) - 1)
636                                 initials[i++] = *author;
637                 }
639                 initials[i++] = 0;
640         }
642         return initials;
646 static bool
647 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
649         int valuelen;
651         while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
652                 bool advance = cmd[valuelen] != 0;
654                 cmd[valuelen] = 0;
655                 argv[(*argc)++] = chomp_string(cmd);
656                 cmd = chomp_string(cmd + valuelen + advance);
657         }
659         if (*argc < SIZEOF_ARG)
660                 argv[*argc] = NULL;
661         return *argc < SIZEOF_ARG;
664 static bool
665 argv_from_env(const char **argv, const char *name)
667         char *env = argv ? getenv(name) : NULL;
668         int argc = 0;
670         if (env && *env)
671                 env = strdup(env);
672         return !env || argv_from_string(argv, &argc, env);
676 /*
677  * Executing external commands.
678  */
680 enum io_type {
681         IO_FD,                  /* File descriptor based IO. */
682         IO_BG,                  /* Execute command in the background. */
683         IO_FG,                  /* Execute command with same std{in,out,err}. */
684         IO_RD,                  /* Read only fork+exec IO. */
685         IO_WR,                  /* Write only fork+exec IO. */
686         IO_AP,                  /* Append fork+exec output to file. */
687 };
689 struct io {
690         enum io_type type;      /* The requested type of pipe. */
691         const char *dir;        /* Directory from which to execute. */
692         pid_t pid;              /* PID of spawned process. */
693         int pipe;               /* Pipe end for reading or writing. */
694         int error;              /* Error status. */
695         const char *argv[SIZEOF_ARG];   /* Shell command arguments. */
696         char *buf;              /* Read buffer. */
697         size_t bufalloc;        /* Allocated buffer size. */
698         size_t bufsize;         /* Buffer content size. */
699         char *bufpos;           /* Current buffer position. */
700         unsigned int eof:1;     /* Has end of file been reached. */
701 };
703 static void
704 io_reset(struct io *io)
706         io->pipe = -1;
707         io->pid = 0;
708         io->buf = io->bufpos = NULL;
709         io->bufalloc = io->bufsize = 0;
710         io->error = 0;
711         io->eof = 0;
714 static void
715 io_init(struct io *io, const char *dir, enum io_type type)
717         io_reset(io);
718         io->type = type;
719         io->dir = dir;
722 static bool
723 io_format(struct io *io, const char *dir, enum io_type type,
724           const char *argv[], enum format_flags flags)
726         io_init(io, dir, type);
727         return format_argv(io->argv, argv, flags);
730 static bool
731 io_open(struct io *io, const char *fmt, ...)
733         char name[SIZEOF_STR] = "";
734         bool fits;
735         va_list args;
737         io_init(io, NULL, IO_FD);
739         va_start(args, fmt);
740         fits = vsnprintf(name, sizeof(name), fmt, args) < sizeof(name);
741         va_end(args);
743         if (!fits) {
744                 io->error = ENAMETOOLONG;
745                 return FALSE;
746         }
747         io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
748         if (io->pipe == -1)
749                 io->error = errno;
750         return io->pipe != -1;
753 static bool
754 io_kill(struct io *io)
756         return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
759 static bool
760 io_done(struct io *io)
762         pid_t pid = io->pid;
764         if (io->pipe != -1)
765                 close(io->pipe);
766         free(io->buf);
767         io_reset(io);
769         while (pid > 0) {
770                 int status;
771                 pid_t waiting = waitpid(pid, &status, 0);
773                 if (waiting < 0) {
774                         if (errno == EINTR)
775                                 continue;
776                         io->error = errno;
777                         return FALSE;
778                 }
780                 return waiting == pid &&
781                        !WIFSIGNALED(status) &&
782                        WIFEXITED(status) &&
783                        !WEXITSTATUS(status);
784         }
786         return TRUE;
789 static bool
790 io_start(struct io *io)
792         int pipefds[2] = { -1, -1 };
794         if (io->type == IO_FD)
795                 return TRUE;
797         if ((io->type == IO_RD || io->type == IO_WR) && pipe(pipefds) < 0) {
798                 io->error = errno;
799                 return FALSE;
800         } else if (io->type == IO_AP) {
801                 pipefds[1] = io->pipe;
802         }
804         if ((io->pid = fork())) {
805                 if (io->pid == -1)
806                         io->error = errno;
807                 if (pipefds[!(io->type == IO_WR)] != -1)
808                         close(pipefds[!(io->type == IO_WR)]);
809                 if (io->pid != -1) {
810                         io->pipe = pipefds[!!(io->type == IO_WR)];
811                         return TRUE;
812                 }
814         } else {
815                 if (io->type != IO_FG) {
816                         int devnull = open("/dev/null", O_RDWR);
817                         int readfd  = io->type == IO_WR ? pipefds[0] : devnull;
818                         int writefd = (io->type == IO_RD || io->type == IO_AP)
819                                                         ? pipefds[1] : devnull;
821                         dup2(readfd,  STDIN_FILENO);
822                         dup2(writefd, STDOUT_FILENO);
823                         dup2(devnull, STDERR_FILENO);
825                         close(devnull);
826                         if (pipefds[0] != -1)
827                                 close(pipefds[0]);
828                         if (pipefds[1] != -1)
829                                 close(pipefds[1]);
830                 }
832                 if (io->dir && *io->dir && chdir(io->dir) == -1)
833                         exit(errno);
835                 execvp(io->argv[0], (char *const*) io->argv);
836                 exit(errno);
837         }
839         if (pipefds[!!(io->type == IO_WR)] != -1)
840                 close(pipefds[!!(io->type == IO_WR)]);
841         return FALSE;
844 static bool
845 io_run(struct io *io, const char **argv, const char *dir, enum io_type type)
847         io_init(io, dir, type);
848         if (!format_argv(io->argv, argv, FORMAT_NONE))
849                 return FALSE;
850         return io_start(io);
853 static int
854 io_complete(struct io *io)
856         return io_start(io) && io_done(io);
859 static int
860 io_run_bg(const char **argv)
862         struct io io = {};
864         if (!io_format(&io, NULL, IO_BG, argv, FORMAT_NONE))
865                 return FALSE;
866         return io_complete(&io);
869 static bool
870 io_run_fg(const char **argv, const char *dir)
872         struct io io = {};
874         if (!io_format(&io, dir, IO_FG, argv, FORMAT_NONE))
875                 return FALSE;
876         return io_complete(&io);
879 static bool
880 io_run_append(const char **argv, enum format_flags flags, int fd)
882         struct io io = {};
884         if (!io_format(&io, NULL, IO_AP, argv, flags)) {
885                 close(fd);
886                 return FALSE;
887         }
889         io.pipe = fd;
890         return io_complete(&io);
893 static bool
894 io_run_rd(struct io *io, const char **argv, const char *dir, enum format_flags flags)
896         return io_format(io, dir, IO_RD, argv, flags) && io_start(io);
899 static bool
900 io_eof(struct io *io)
902         return io->eof;
905 static int
906 io_error(struct io *io)
908         return io->error;
911 static char *
912 io_strerror(struct io *io)
914         return strerror(io->error);
917 static bool
918 io_can_read(struct io *io)
920         struct timeval tv = { 0, 500 };
921         fd_set fds;
923         FD_ZERO(&fds);
924         FD_SET(io->pipe, &fds);
926         return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
929 static ssize_t
930 io_read(struct io *io, void *buf, size_t bufsize)
932         do {
933                 ssize_t readsize = read(io->pipe, buf, bufsize);
935                 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
936                         continue;
937                 else if (readsize == -1)
938                         io->error = errno;
939                 else if (readsize == 0)
940                         io->eof = 1;
941                 return readsize;
942         } while (1);
945 DEFINE_ALLOCATOR(io_realloc_buf, char, BUFSIZ)
947 static char *
948 io_get(struct io *io, int c, bool can_read)
950         char *eol;
951         ssize_t readsize;
953         while (TRUE) {
954                 if (io->bufsize > 0) {
955                         eol = memchr(io->bufpos, c, io->bufsize);
956                         if (eol) {
957                                 char *line = io->bufpos;
959                                 *eol = 0;
960                                 io->bufpos = eol + 1;
961                                 io->bufsize -= io->bufpos - line;
962                                 return line;
963                         }
964                 }
966                 if (io_eof(io)) {
967                         if (io->bufsize) {
968                                 io->bufpos[io->bufsize] = 0;
969                                 io->bufsize = 0;
970                                 return io->bufpos;
971                         }
972                         return NULL;
973                 }
975                 if (!can_read)
976                         return NULL;
978                 if (io->bufsize > 0 && io->bufpos > io->buf)
979                         memmove(io->buf, io->bufpos, io->bufsize);
981                 if (io->bufalloc == io->bufsize) {
982                         if (!io_realloc_buf(&io->buf, io->bufalloc, BUFSIZ))
983                                 return NULL;
984                         io->bufalloc += BUFSIZ;
985                 }
987                 io->bufpos = io->buf;
988                 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
989                 if (io_error(io))
990                         return NULL;
991                 io->bufsize += readsize;
992         }
995 static bool
996 io_write(struct io *io, const void *buf, size_t bufsize)
998         size_t written = 0;
1000         while (!io_error(io) && written < bufsize) {
1001                 ssize_t size;
1003                 size = write(io->pipe, buf + written, bufsize - written);
1004                 if (size < 0 && (errno == EAGAIN || errno == EINTR))
1005                         continue;
1006                 else if (size == -1)
1007                         io->error = errno;
1008                 else
1009                         written += size;
1010         }
1012         return written == bufsize;
1015 static bool
1016 io_read_buf(struct io *io, char buf[], size_t bufsize)
1018         char *result = io_get(io, '\n', TRUE);
1020         if (result) {
1021                 result = chomp_string(result);
1022                 string_ncopy_do(buf, bufsize, result, strlen(result));
1023         }
1025         return io_done(io) && result;
1028 static bool
1029 io_run_buf(const char **argv, char buf[], size_t bufsize)
1031         struct io io = {};
1033         return io_run_rd(&io, argv, NULL, FORMAT_NONE)
1034             && io_read_buf(&io, buf, bufsize);
1037 static int
1038 io_load(struct io *io, const char *separators,
1039         int (*read_property)(char *, size_t, char *, size_t))
1041         char *name;
1042         int state = OK;
1044         if (!io_start(io))
1045                 return ERR;
1047         while (state == OK && (name = io_get(io, '\n', TRUE))) {
1048                 char *value;
1049                 size_t namelen;
1050                 size_t valuelen;
1052                 name = chomp_string(name);
1053                 namelen = strcspn(name, separators);
1055                 if (name[namelen]) {
1056                         name[namelen] = 0;
1057                         value = chomp_string(name + namelen + 1);
1058                         valuelen = strlen(value);
1060                 } else {
1061                         value = "";
1062                         valuelen = 0;
1063                 }
1065                 state = read_property(name, namelen, value, valuelen);
1066         }
1068         if (state != ERR && io_error(io))
1069                 state = ERR;
1070         io_done(io);
1072         return state;
1075 static int
1076 io_run_load(const char **argv, const char *separators,
1077             int (*read_property)(char *, size_t, char *, size_t))
1079         struct io io = {};
1081         return io_format(&io, NULL, IO_RD, argv, FORMAT_NONE)
1082                 ? io_load(&io, separators, read_property) : ERR;
1086 /*
1087  * User requests
1088  */
1090 #define REQ_INFO \
1091         /* XXX: Keep the view request first and in sync with views[]. */ \
1092         REQ_GROUP("View switching") \
1093         REQ_(VIEW_MAIN,         "Show main view"), \
1094         REQ_(VIEW_DIFF,         "Show diff view"), \
1095         REQ_(VIEW_LOG,          "Show log view"), \
1096         REQ_(VIEW_TREE,         "Show tree view"), \
1097         REQ_(VIEW_BLOB,         "Show blob view"), \
1098         REQ_(VIEW_BLAME,        "Show blame view"), \
1099         REQ_(VIEW_BRANCH,       "Show branch view"), \
1100         REQ_(VIEW_HELP,         "Show help page"), \
1101         REQ_(VIEW_PAGER,        "Show pager view"), \
1102         REQ_(VIEW_STATUS,       "Show status view"), \
1103         REQ_(VIEW_STAGE,        "Show stage view"), \
1104         \
1105         REQ_GROUP("View manipulation") \
1106         REQ_(ENTER,             "Enter current line and scroll"), \
1107         REQ_(NEXT,              "Move to next"), \
1108         REQ_(PREVIOUS,          "Move to previous"), \
1109         REQ_(PARENT,            "Move to parent"), \
1110         REQ_(VIEW_NEXT,         "Move focus to next view"), \
1111         REQ_(REFRESH,           "Reload and refresh"), \
1112         REQ_(MAXIMIZE,          "Maximize the current view"), \
1113         REQ_(VIEW_CLOSE,        "Close the current view"), \
1114         REQ_(QUIT,              "Close all views and quit"), \
1115         \
1116         REQ_GROUP("View specific requests") \
1117         REQ_(STATUS_UPDATE,     "Update file status"), \
1118         REQ_(STATUS_REVERT,     "Revert file changes"), \
1119         REQ_(STATUS_MERGE,      "Merge file using external tool"), \
1120         REQ_(STAGE_NEXT,        "Find next chunk to stage"), \
1121         \
1122         REQ_GROUP("Cursor navigation") \
1123         REQ_(MOVE_UP,           "Move cursor one line up"), \
1124         REQ_(MOVE_DOWN,         "Move cursor one line down"), \
1125         REQ_(MOVE_PAGE_DOWN,    "Move cursor one page down"), \
1126         REQ_(MOVE_PAGE_UP,      "Move cursor one page up"), \
1127         REQ_(MOVE_FIRST_LINE,   "Move cursor to first line"), \
1128         REQ_(MOVE_LAST_LINE,    "Move cursor to last line"), \
1129         \
1130         REQ_GROUP("Scrolling") \
1131         REQ_(SCROLL_LEFT,       "Scroll two columns left"), \
1132         REQ_(SCROLL_RIGHT,      "Scroll two columns right"), \
1133         REQ_(SCROLL_LINE_UP,    "Scroll one line up"), \
1134         REQ_(SCROLL_LINE_DOWN,  "Scroll one line down"), \
1135         REQ_(SCROLL_PAGE_UP,    "Scroll one page up"), \
1136         REQ_(SCROLL_PAGE_DOWN,  "Scroll one page down"), \
1137         \
1138         REQ_GROUP("Searching") \
1139         REQ_(SEARCH,            "Search the view"), \
1140         REQ_(SEARCH_BACK,       "Search backwards in the view"), \
1141         REQ_(FIND_NEXT,         "Find next search match"), \
1142         REQ_(FIND_PREV,         "Find previous search match"), \
1143         \
1144         REQ_GROUP("Option manipulation") \
1145         REQ_(OPTIONS,           "Open option menu"), \
1146         REQ_(TOGGLE_LINENO,     "Toggle line numbers"), \
1147         REQ_(TOGGLE_DATE,       "Toggle date display"), \
1148         REQ_(TOGGLE_DATE_SHORT, "Toggle short (date-only) dates"), \
1149         REQ_(TOGGLE_AUTHOR,     "Toggle author display"), \
1150         REQ_(TOGGLE_REV_GRAPH,  "Toggle revision graph visualization"), \
1151         REQ_(TOGGLE_REFS,       "Toggle reference display (tags/branches)"), \
1152         REQ_(TOGGLE_SORT_ORDER, "Toggle ascending/descending sort order"), \
1153         REQ_(TOGGLE_SORT_FIELD, "Toggle field to sort by"), \
1154         \
1155         REQ_GROUP("Misc") \
1156         REQ_(PROMPT,            "Bring up the prompt"), \
1157         REQ_(SCREEN_REDRAW,     "Redraw the screen"), \
1158         REQ_(SHOW_VERSION,      "Show version information"), \
1159         REQ_(STOP_LOADING,      "Stop all loading views"), \
1160         REQ_(EDIT,              "Open in editor"), \
1161         REQ_(NONE,              "Do nothing")
1164 /* User action requests. */
1165 enum request {
1166 #define REQ_GROUP(help)
1167 #define REQ_(req, help) REQ_##req
1169         /* Offset all requests to avoid conflicts with ncurses getch values. */
1170         REQ_OFFSET = KEY_MAX + 1,
1171         REQ_INFO
1173 #undef  REQ_GROUP
1174 #undef  REQ_
1175 };
1177 struct request_info {
1178         enum request request;
1179         const char *name;
1180         int namelen;
1181         const char *help;
1182 };
1184 static const struct request_info req_info[] = {
1185 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
1186 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
1187         REQ_INFO
1188 #undef  REQ_GROUP
1189 #undef  REQ_
1190 };
1192 static enum request
1193 get_request(const char *name)
1195         int namelen = strlen(name);
1196         int i;
1198         for (i = 0; i < ARRAY_SIZE(req_info); i++)
1199                 if (enum_equals(req_info[i], name, namelen))
1200                         return req_info[i].request;
1202         return REQ_NONE;
1206 /*
1207  * Options
1208  */
1210 /* Option and state variables. */
1211 static enum date opt_date               = DATE_DEFAULT;
1212 static enum author opt_author           = AUTHOR_DEFAULT;
1213 static bool opt_line_number             = FALSE;
1214 static bool opt_line_graphics           = TRUE;
1215 static bool opt_rev_graph               = FALSE;
1216 static bool opt_show_refs               = TRUE;
1217 static int opt_num_interval             = 5;
1218 static double opt_hscroll               = 0.50;
1219 static double opt_scale_split_view      = 2.0 / 3.0;
1220 static int opt_tab_size                 = 8;
1221 static int opt_author_cols              = AUTHOR_COLS;
1222 static char opt_path[SIZEOF_STR]        = "";
1223 static char opt_file[SIZEOF_STR]        = "";
1224 static char opt_ref[SIZEOF_REF]         = "";
1225 static char opt_head[SIZEOF_REF]        = "";
1226 static char opt_remote[SIZEOF_REF]      = "";
1227 static char opt_encoding[20]            = "UTF-8";
1228 static iconv_t opt_iconv_in             = ICONV_NONE;
1229 static iconv_t opt_iconv_out            = ICONV_NONE;
1230 static char opt_search[SIZEOF_STR]      = "";
1231 static char opt_cdup[SIZEOF_STR]        = "";
1232 static char opt_prefix[SIZEOF_STR]      = "";
1233 static char opt_git_dir[SIZEOF_STR]     = "";
1234 static signed char opt_is_inside_work_tree      = -1; /* set to TRUE or FALSE */
1235 static char opt_editor[SIZEOF_STR]      = "";
1236 static FILE *opt_tty                    = NULL;
1238 #define is_initial_commit()     (!get_ref_head())
1239 #define is_head_commit(rev)     (!strcmp((rev), "HEAD") || (get_ref_head() && !strcmp(rev, get_ref_head()->id)))
1242 /*
1243  * Line-oriented content detection.
1244  */
1246 #define LINE_INFO \
1247 LINE(DIFF_HEADER,  "diff --git ",       COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1248 LINE(DIFF_CHUNK,   "@@",                COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1249 LINE(DIFF_ADD,     "+",                 COLOR_GREEN,    COLOR_DEFAULT,  0), \
1250 LINE(DIFF_DEL,     "-",                 COLOR_RED,      COLOR_DEFAULT,  0), \
1251 LINE(DIFF_INDEX,        "index ",         COLOR_BLUE,   COLOR_DEFAULT,  0), \
1252 LINE(DIFF_OLDMODE,      "old file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1253 LINE(DIFF_NEWMODE,      "new file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1254 LINE(DIFF_COPY_FROM,    "copy from",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
1255 LINE(DIFF_COPY_TO,      "copy to",        COLOR_YELLOW, COLOR_DEFAULT,  0), \
1256 LINE(DIFF_RENAME_FROM,  "rename from",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
1257 LINE(DIFF_RENAME_TO,    "rename to",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
1258 LINE(DIFF_SIMILARITY,   "similarity ",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
1259 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1260 LINE(DIFF_TREE,         "diff-tree ",     COLOR_BLUE,   COLOR_DEFAULT,  0), \
1261 LINE(PP_AUTHOR,    "Author: ",          COLOR_CYAN,     COLOR_DEFAULT,  0), \
1262 LINE(PP_COMMIT,    "Commit: ",          COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1263 LINE(PP_MERGE,     "Merge: ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
1264 LINE(PP_DATE,      "Date:   ",          COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1265 LINE(PP_ADATE,     "AuthorDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1266 LINE(PP_CDATE,     "CommitDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1267 LINE(PP_REFS,      "Refs: ",            COLOR_RED,      COLOR_DEFAULT,  0), \
1268 LINE(COMMIT,       "commit ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
1269 LINE(PARENT,       "parent ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
1270 LINE(TREE,         "tree ",             COLOR_BLUE,     COLOR_DEFAULT,  0), \
1271 LINE(AUTHOR,       "author ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
1272 LINE(COMMITTER,    "committer ",        COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1273 LINE(SIGNOFF,      "    Signed-off-by", COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1274 LINE(ACKED,        "    Acked-by",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1275 LINE(DEFAULT,      "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
1276 LINE(CURSOR,       "",                  COLOR_WHITE,    COLOR_GREEN,    A_BOLD), \
1277 LINE(STATUS,       "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
1278 LINE(DELIMITER,    "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1279 LINE(DATE,         "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
1280 LINE(MODE,         "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1281 LINE(LINE_NUMBER,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1282 LINE(TITLE_BLUR,   "",                  COLOR_WHITE,    COLOR_BLUE,     0), \
1283 LINE(TITLE_FOCUS,  "",                  COLOR_WHITE,    COLOR_BLUE,     A_BOLD), \
1284 LINE(MAIN_COMMIT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
1285 LINE(MAIN_TAG,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
1286 LINE(MAIN_LOCAL_TAG,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1287 LINE(MAIN_REMOTE,  "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1288 LINE(MAIN_TRACKED, "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_BOLD), \
1289 LINE(MAIN_REF,     "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1290 LINE(MAIN_HEAD,    "",                  COLOR_CYAN,     COLOR_DEFAULT,  A_BOLD), \
1291 LINE(MAIN_REVGRAPH,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1292 LINE(TREE_HEAD,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_BOLD), \
1293 LINE(TREE_DIR,     "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_NORMAL), \
1294 LINE(TREE_FILE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
1295 LINE(STAT_HEAD,    "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1296 LINE(STAT_SECTION, "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1297 LINE(STAT_NONE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
1298 LINE(STAT_STAGED,  "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1299 LINE(STAT_UNSTAGED,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1300 LINE(STAT_UNTRACKED,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1301 LINE(HELP_KEYMAP,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1302 LINE(HELP_GROUP,   "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
1303 LINE(BLAME_ID,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0)
1305 enum line_type {
1306 #define LINE(type, line, fg, bg, attr) \
1307         LINE_##type
1308         LINE_INFO,
1309         LINE_NONE
1310 #undef  LINE
1311 };
1313 struct line_info {
1314         const char *name;       /* Option name. */
1315         int namelen;            /* Size of option name. */
1316         const char *line;       /* The start of line to match. */
1317         int linelen;            /* Size of string to match. */
1318         int fg, bg, attr;       /* Color and text attributes for the lines. */
1319 };
1321 static struct line_info line_info[] = {
1322 #define LINE(type, line, fg, bg, attr) \
1323         { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1324         LINE_INFO
1325 #undef  LINE
1326 };
1328 static enum line_type
1329 get_line_type(const char *line)
1331         int linelen = strlen(line);
1332         enum line_type type;
1334         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1335                 /* Case insensitive search matches Signed-off-by lines better. */
1336                 if (linelen >= line_info[type].linelen &&
1337                     !strncasecmp(line_info[type].line, line, line_info[type].linelen))
1338                         return type;
1340         return LINE_DEFAULT;
1343 static inline int
1344 get_line_attr(enum line_type type)
1346         assert(type < ARRAY_SIZE(line_info));
1347         return COLOR_PAIR(type) | line_info[type].attr;
1350 static struct line_info *
1351 get_line_info(const char *name)
1353         size_t namelen = strlen(name);
1354         enum line_type type;
1356         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1357                 if (enum_equals(line_info[type], name, namelen))
1358                         return &line_info[type];
1360         return NULL;
1363 static void
1364 init_colors(void)
1366         int default_bg = line_info[LINE_DEFAULT].bg;
1367         int default_fg = line_info[LINE_DEFAULT].fg;
1368         enum line_type type;
1370         start_color();
1372         if (assume_default_colors(default_fg, default_bg) == ERR) {
1373                 default_bg = COLOR_BLACK;
1374                 default_fg = COLOR_WHITE;
1375         }
1377         for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1378                 struct line_info *info = &line_info[type];
1379                 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1380                 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1382                 init_pair(type, fg, bg);
1383         }
1386 struct line {
1387         enum line_type type;
1389         /* State flags */
1390         unsigned int selected:1;
1391         unsigned int dirty:1;
1392         unsigned int cleareol:1;
1393         unsigned int other:16;
1395         void *data;             /* User data */
1396 };
1399 /*
1400  * Keys
1401  */
1403 struct keybinding {
1404         int alias;
1405         enum request request;
1406 };
1408 static const struct keybinding default_keybindings[] = {
1409         /* View switching */
1410         { 'm',          REQ_VIEW_MAIN },
1411         { 'd',          REQ_VIEW_DIFF },
1412         { 'l',          REQ_VIEW_LOG },
1413         { 't',          REQ_VIEW_TREE },
1414         { 'f',          REQ_VIEW_BLOB },
1415         { 'B',          REQ_VIEW_BLAME },
1416         { 'H',          REQ_VIEW_BRANCH },
1417         { 'p',          REQ_VIEW_PAGER },
1418         { 'h',          REQ_VIEW_HELP },
1419         { 'S',          REQ_VIEW_STATUS },
1420         { 'c',          REQ_VIEW_STAGE },
1422         /* View manipulation */
1423         { 'q',          REQ_VIEW_CLOSE },
1424         { KEY_TAB,      REQ_VIEW_NEXT },
1425         { KEY_RETURN,   REQ_ENTER },
1426         { KEY_UP,       REQ_PREVIOUS },
1427         { KEY_DOWN,     REQ_NEXT },
1428         { 'R',          REQ_REFRESH },
1429         { KEY_F(5),     REQ_REFRESH },
1430         { 'O',          REQ_MAXIMIZE },
1432         /* Cursor navigation */
1433         { 'k',          REQ_MOVE_UP },
1434         { 'j',          REQ_MOVE_DOWN },
1435         { KEY_HOME,     REQ_MOVE_FIRST_LINE },
1436         { KEY_END,      REQ_MOVE_LAST_LINE },
1437         { KEY_NPAGE,    REQ_MOVE_PAGE_DOWN },
1438         { ' ',          REQ_MOVE_PAGE_DOWN },
1439         { KEY_PPAGE,    REQ_MOVE_PAGE_UP },
1440         { 'b',          REQ_MOVE_PAGE_UP },
1441         { '-',          REQ_MOVE_PAGE_UP },
1443         /* Scrolling */
1444         { KEY_LEFT,     REQ_SCROLL_LEFT },
1445         { KEY_RIGHT,    REQ_SCROLL_RIGHT },
1446         { KEY_IC,       REQ_SCROLL_LINE_UP },
1447         { KEY_DC,       REQ_SCROLL_LINE_DOWN },
1448         { 'w',          REQ_SCROLL_PAGE_UP },
1449         { 's',          REQ_SCROLL_PAGE_DOWN },
1451         /* Searching */
1452         { '/',          REQ_SEARCH },
1453         { '?',          REQ_SEARCH_BACK },
1454         { 'n',          REQ_FIND_NEXT },
1455         { 'N',          REQ_FIND_PREV },
1457         /* Misc */
1458         { 'Q',          REQ_QUIT },
1459         { 'z',          REQ_STOP_LOADING },
1460         { 'v',          REQ_SHOW_VERSION },
1461         { 'r',          REQ_SCREEN_REDRAW },
1462         { 'o',          REQ_OPTIONS },
1463         { '.',          REQ_TOGGLE_LINENO },
1464         { 'D',          REQ_TOGGLE_DATE },
1465         { 'A',          REQ_TOGGLE_AUTHOR },
1466         { 'g',          REQ_TOGGLE_REV_GRAPH },
1467         { 'F',          REQ_TOGGLE_REFS },
1468         { 'I',          REQ_TOGGLE_SORT_ORDER },
1469         { 'i',          REQ_TOGGLE_SORT_FIELD },
1470         { ':',          REQ_PROMPT },
1471         { 'u',          REQ_STATUS_UPDATE },
1472         { '!',          REQ_STATUS_REVERT },
1473         { 'M',          REQ_STATUS_MERGE },
1474         { '@',          REQ_STAGE_NEXT },
1475         { ',',          REQ_PARENT },
1476         { 'e',          REQ_EDIT },
1477 };
1479 #define KEYMAP_INFO \
1480         KEYMAP_(GENERIC), \
1481         KEYMAP_(MAIN), \
1482         KEYMAP_(DIFF), \
1483         KEYMAP_(LOG), \
1484         KEYMAP_(TREE), \
1485         KEYMAP_(BLOB), \
1486         KEYMAP_(BLAME), \
1487         KEYMAP_(BRANCH), \
1488         KEYMAP_(PAGER), \
1489         KEYMAP_(HELP), \
1490         KEYMAP_(STATUS), \
1491         KEYMAP_(STAGE)
1493 enum keymap {
1494 #define KEYMAP_(name) KEYMAP_##name
1495         KEYMAP_INFO
1496 #undef  KEYMAP_
1497 };
1499 static const struct enum_map keymap_table[] = {
1500 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1501         KEYMAP_INFO
1502 #undef  KEYMAP_
1503 };
1505 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1507 struct keybinding_table {
1508         struct keybinding *data;
1509         size_t size;
1510 };
1512 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1514 static void
1515 add_keybinding(enum keymap keymap, enum request request, int key)
1517         struct keybinding_table *table = &keybindings[keymap];
1519         table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1520         if (!table->data)
1521                 die("Failed to allocate keybinding");
1522         table->data[table->size].alias = key;
1523         table->data[table->size++].request = request;
1526 /* Looks for a key binding first in the given map, then in the generic map, and
1527  * lastly in the default keybindings. */
1528 static enum request
1529 get_keybinding(enum keymap keymap, int key)
1531         size_t i;
1533         for (i = 0; i < keybindings[keymap].size; i++)
1534                 if (keybindings[keymap].data[i].alias == key)
1535                         return keybindings[keymap].data[i].request;
1537         for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1538                 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1539                         return keybindings[KEYMAP_GENERIC].data[i].request;
1541         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1542                 if (default_keybindings[i].alias == key)
1543                         return default_keybindings[i].request;
1545         return (enum request) key;
1549 struct key {
1550         const char *name;
1551         int value;
1552 };
1554 static const struct key key_table[] = {
1555         { "Enter",      KEY_RETURN },
1556         { "Space",      ' ' },
1557         { "Backspace",  KEY_BACKSPACE },
1558         { "Tab",        KEY_TAB },
1559         { "Escape",     KEY_ESC },
1560         { "Left",       KEY_LEFT },
1561         { "Right",      KEY_RIGHT },
1562         { "Up",         KEY_UP },
1563         { "Down",       KEY_DOWN },
1564         { "Insert",     KEY_IC },
1565         { "Delete",     KEY_DC },
1566         { "Hash",       '#' },
1567         { "Home",       KEY_HOME },
1568         { "End",        KEY_END },
1569         { "PageUp",     KEY_PPAGE },
1570         { "PageDown",   KEY_NPAGE },
1571         { "F1",         KEY_F(1) },
1572         { "F2",         KEY_F(2) },
1573         { "F3",         KEY_F(3) },
1574         { "F4",         KEY_F(4) },
1575         { "F5",         KEY_F(5) },
1576         { "F6",         KEY_F(6) },
1577         { "F7",         KEY_F(7) },
1578         { "F8",         KEY_F(8) },
1579         { "F9",         KEY_F(9) },
1580         { "F10",        KEY_F(10) },
1581         { "F11",        KEY_F(11) },
1582         { "F12",        KEY_F(12) },
1583 };
1585 static int
1586 get_key_value(const char *name)
1588         int i;
1590         for (i = 0; i < ARRAY_SIZE(key_table); i++)
1591                 if (!strcasecmp(key_table[i].name, name))
1592                         return key_table[i].value;
1594         if (strlen(name) == 1 && isprint(*name))
1595                 return (int) *name;
1597         return ERR;
1600 static const char *
1601 get_key_name(int key_value)
1603         static char key_char[] = "'X'";
1604         const char *seq = NULL;
1605         int key;
1607         for (key = 0; key < ARRAY_SIZE(key_table); key++)
1608                 if (key_table[key].value == key_value)
1609                         seq = key_table[key].name;
1611         if (seq == NULL &&
1612             key_value < 127 &&
1613             isprint(key_value)) {
1614                 key_char[1] = (char) key_value;
1615                 seq = key_char;
1616         }
1618         return seq ? seq : "(no key)";
1621 static bool
1622 append_key(char *buf, size_t *pos, const struct keybinding *keybinding)
1624         const char *sep = *pos > 0 ? ", " : "";
1625         const char *keyname = get_key_name(keybinding->alias);
1627         return string_nformat(buf, BUFSIZ, pos, "%s%s", sep, keyname);
1630 static bool
1631 append_keymap_request_keys(char *buf, size_t *pos, enum request request,
1632                            enum keymap keymap, bool all)
1634         int i;
1636         for (i = 0; i < keybindings[keymap].size; i++) {
1637                 if (keybindings[keymap].data[i].request == request) {
1638                         if (!append_key(buf, pos, &keybindings[keymap].data[i]))
1639                                 return FALSE;
1640                         if (!all)
1641                                 break;
1642                 }
1643         }
1645         return TRUE;
1648 #define get_key(keymap, request) get_keys(keymap, request, FALSE)
1650 static const char *
1651 get_keys(enum keymap keymap, enum request request, bool all)
1653         static char buf[BUFSIZ];
1654         size_t pos = 0;
1655         int i;
1657         buf[pos] = 0;
1659         if (!append_keymap_request_keys(buf, &pos, request, keymap, all))
1660                 return "Too many keybindings!";
1661         if (pos > 0 && !all)
1662                 return buf;
1664         if (keymap != KEYMAP_GENERIC) {
1665                 /* Only the generic keymap includes the default keybindings when
1666                  * listing all keys. */
1667                 if (all)
1668                         return buf;
1670                 if (!append_keymap_request_keys(buf, &pos, request, KEYMAP_GENERIC, all))
1671                         return "Too many keybindings!";
1672                 if (pos)
1673                         return buf;
1674         }
1676         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1677                 if (default_keybindings[i].request == request) {
1678                         if (!append_key(buf, &pos, &default_keybindings[i]))
1679                                 return "Too many keybindings!";
1680                         if (!all)
1681                                 return buf;
1682                 }
1683         }
1685         return buf;
1688 struct run_request {
1689         enum keymap keymap;
1690         int key;
1691         const char *argv[SIZEOF_ARG];
1692 };
1694 static struct run_request *run_request;
1695 static size_t run_requests;
1697 DEFINE_ALLOCATOR(realloc_run_requests, struct run_request, 8)
1699 static enum request
1700 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
1702         struct run_request *req;
1704         if (argc >= ARRAY_SIZE(req->argv) - 1)
1705                 return REQ_NONE;
1707         if (!realloc_run_requests(&run_request, run_requests, 1))
1708                 return REQ_NONE;
1710         req = &run_request[run_requests];
1711         req->keymap = keymap;
1712         req->key = key;
1713         req->argv[0] = NULL;
1715         if (!format_argv(req->argv, argv, FORMAT_NONE))
1716                 return REQ_NONE;
1718         return REQ_NONE + ++run_requests;
1721 static struct run_request *
1722 get_run_request(enum request request)
1724         if (request <= REQ_NONE)
1725                 return NULL;
1726         return &run_request[request - REQ_NONE - 1];
1729 static void
1730 add_builtin_run_requests(void)
1732         const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1733         const char *checkout[] = { "git", "checkout", "%(branch)", NULL };
1734         const char *commit[] = { "git", "commit", NULL };
1735         const char *gc[] = { "git", "gc", NULL };
1736         struct {
1737                 enum keymap keymap;
1738                 int key;
1739                 int argc;
1740                 const char **argv;
1741         } reqs[] = {
1742                 { KEYMAP_MAIN,    'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
1743                 { KEYMAP_STATUS,  'C', ARRAY_SIZE(commit) - 1, commit },
1744                 { KEYMAP_BRANCH,  'C', ARRAY_SIZE(checkout) - 1, checkout },
1745                 { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
1746         };
1747         int i;
1749         for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1750                 enum request req;
1752                 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
1753                 if (req != REQ_NONE)
1754                         add_keybinding(reqs[i].keymap, req, reqs[i].key);
1755         }
1758 /*
1759  * User config file handling.
1760  */
1762 static int   config_lineno;
1763 static bool  config_errors;
1764 static const char *config_msg;
1766 static const struct enum_map color_map[] = {
1767 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1768         COLOR_MAP(DEFAULT),
1769         COLOR_MAP(BLACK),
1770         COLOR_MAP(BLUE),
1771         COLOR_MAP(CYAN),
1772         COLOR_MAP(GREEN),
1773         COLOR_MAP(MAGENTA),
1774         COLOR_MAP(RED),
1775         COLOR_MAP(WHITE),
1776         COLOR_MAP(YELLOW),
1777 };
1779 static const struct enum_map attr_map[] = {
1780 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1781         ATTR_MAP(NORMAL),
1782         ATTR_MAP(BLINK),
1783         ATTR_MAP(BOLD),
1784         ATTR_MAP(DIM),
1785         ATTR_MAP(REVERSE),
1786         ATTR_MAP(STANDOUT),
1787         ATTR_MAP(UNDERLINE),
1788 };
1790 #define set_attribute(attr, name)       map_enum(attr, attr_map, name)
1792 static int parse_step(double *opt, const char *arg)
1794         *opt = atoi(arg);
1795         if (!strchr(arg, '%'))
1796                 return OK;
1798         /* "Shift down" so 100% and 1 does not conflict. */
1799         *opt = (*opt - 1) / 100;
1800         if (*opt >= 1.0) {
1801                 *opt = 0.99;
1802                 config_msg = "Step value larger than 100%";
1803                 return ERR;
1804         }
1805         if (*opt < 0.0) {
1806                 *opt = 1;
1807                 config_msg = "Invalid step value";
1808                 return ERR;
1809         }
1810         return OK;
1813 static int
1814 parse_int(int *opt, const char *arg, int min, int max)
1816         int value = atoi(arg);
1818         if (min <= value && value <= max) {
1819                 *opt = value;
1820                 return OK;
1821         }
1823         config_msg = "Integer value out of bound";
1824         return ERR;
1827 static bool
1828 set_color(int *color, const char *name)
1830         if (map_enum(color, color_map, name))
1831                 return TRUE;
1832         if (!prefixcmp(name, "color"))
1833                 return parse_int(color, name + 5, 0, 255) == OK;
1834         return FALSE;
1837 /* Wants: object fgcolor bgcolor [attribute] */
1838 static int
1839 option_color_command(int argc, const char *argv[])
1841         struct line_info *info;
1843         if (argc < 3) {
1844                 config_msg = "Wrong number of arguments given to color command";
1845                 return ERR;
1846         }
1848         info = get_line_info(argv[0]);
1849         if (!info) {
1850                 static const struct enum_map obsolete[] = {
1851                         ENUM_MAP("main-delim",  LINE_DELIMITER),
1852                         ENUM_MAP("main-date",   LINE_DATE),
1853                         ENUM_MAP("main-author", LINE_AUTHOR),
1854                 };
1855                 int index;
1857                 if (!map_enum(&index, obsolete, argv[0])) {
1858                         config_msg = "Unknown color name";
1859                         return ERR;
1860                 }
1861                 info = &line_info[index];
1862         }
1864         if (!set_color(&info->fg, argv[1]) ||
1865             !set_color(&info->bg, argv[2])) {
1866                 config_msg = "Unknown color";
1867                 return ERR;
1868         }
1870         info->attr = 0;
1871         while (argc-- > 3) {
1872                 int attr;
1874                 if (!set_attribute(&attr, argv[argc])) {
1875                         config_msg = "Unknown attribute";
1876                         return ERR;
1877                 }
1878                 info->attr |= attr;
1879         }
1881         return OK;
1884 static int parse_bool(bool *opt, const char *arg)
1886         *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1887                 ? TRUE : FALSE;
1888         return OK;
1891 static int parse_enum_do(unsigned int *opt, const char *arg,
1892                          const struct enum_map *map, size_t map_size)
1894         bool is_true;
1896         assert(map_size > 1);
1898         if (map_enum_do(map, map_size, (int *) opt, arg))
1899                 return OK;
1901         if (parse_bool(&is_true, arg) != OK)
1902                 return ERR;
1904         *opt = is_true ? map[1].value : map[0].value;
1905         return OK;
1908 #define parse_enum(opt, arg, map) \
1909         parse_enum_do(opt, arg, map, ARRAY_SIZE(map))
1911 static int
1912 parse_string(char *opt, const char *arg, size_t optsize)
1914         int arglen = strlen(arg);
1916         switch (arg[0]) {
1917         case '\"':
1918         case '\'':
1919                 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1920                         config_msg = "Unmatched quotation";
1921                         return ERR;
1922                 }
1923                 arg += 1; arglen -= 2;
1924         default:
1925                 string_ncopy_do(opt, optsize, arg, arglen);
1926                 return OK;
1927         }
1930 /* Wants: name = value */
1931 static int
1932 option_set_command(int argc, const char *argv[])
1934         if (argc != 3) {
1935                 config_msg = "Wrong number of arguments given to set command";
1936                 return ERR;
1937         }
1939         if (strcmp(argv[1], "=")) {
1940                 config_msg = "No value assigned";
1941                 return ERR;
1942         }
1944         if (!strcmp(argv[0], "show-author"))
1945                 return parse_enum(&opt_author, argv[2], author_map);
1947         if (!strcmp(argv[0], "show-date"))
1948                 return parse_enum(&opt_date, argv[2], date_map);
1950         if (!strcmp(argv[0], "show-rev-graph"))
1951                 return parse_bool(&opt_rev_graph, argv[2]);
1953         if (!strcmp(argv[0], "show-refs"))
1954                 return parse_bool(&opt_show_refs, argv[2]);
1956         if (!strcmp(argv[0], "show-line-numbers"))
1957                 return parse_bool(&opt_line_number, argv[2]);
1959         if (!strcmp(argv[0], "line-graphics"))
1960                 return parse_bool(&opt_line_graphics, argv[2]);
1962         if (!strcmp(argv[0], "line-number-interval"))
1963                 return parse_int(&opt_num_interval, argv[2], 1, 1024);
1965         if (!strcmp(argv[0], "author-width"))
1966                 return parse_int(&opt_author_cols, argv[2], 0, 1024);
1968         if (!strcmp(argv[0], "horizontal-scroll"))
1969                 return parse_step(&opt_hscroll, argv[2]);
1971         if (!strcmp(argv[0], "split-view-height"))
1972                 return parse_step(&opt_scale_split_view, argv[2]);
1974         if (!strcmp(argv[0], "tab-size"))
1975                 return parse_int(&opt_tab_size, argv[2], 1, 1024);
1977         if (!strcmp(argv[0], "commit-encoding"))
1978                 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
1980         config_msg = "Unknown variable name";
1981         return ERR;
1984 /* Wants: mode request key */
1985 static int
1986 option_bind_command(int argc, const char *argv[])
1988         enum request request;
1989         int keymap = -1;
1990         int key;
1992         if (argc < 3) {
1993                 config_msg = "Wrong number of arguments given to bind command";
1994                 return ERR;
1995         }
1997         if (set_keymap(&keymap, argv[0]) == ERR) {
1998                 config_msg = "Unknown key map";
1999                 return ERR;
2000         }
2002         key = get_key_value(argv[1]);
2003         if (key == ERR) {
2004                 config_msg = "Unknown key";
2005                 return ERR;
2006         }
2008         request = get_request(argv[2]);
2009         if (request == REQ_NONE) {
2010                 static const struct enum_map obsolete[] = {
2011                         ENUM_MAP("cherry-pick",         REQ_NONE),
2012                         ENUM_MAP("screen-resize",       REQ_NONE),
2013                         ENUM_MAP("tree-parent",         REQ_PARENT),
2014                 };
2015                 int alias;
2017                 if (map_enum(&alias, obsolete, argv[2])) {
2018                         if (alias != REQ_NONE)
2019                                 add_keybinding(keymap, alias, key);
2020                         config_msg = "Obsolete request name";
2021                         return ERR;
2022                 }
2023         }
2024         if (request == REQ_NONE && *argv[2]++ == '!')
2025                 request = add_run_request(keymap, key, argc - 2, argv + 2);
2026         if (request == REQ_NONE) {
2027                 config_msg = "Unknown request name";
2028                 return ERR;
2029         }
2031         add_keybinding(keymap, request, key);
2033         return OK;
2036 static int
2037 set_option(const char *opt, char *value)
2039         const char *argv[SIZEOF_ARG];
2040         int argc = 0;
2042         if (!argv_from_string(argv, &argc, value)) {
2043                 config_msg = "Too many option arguments";
2044                 return ERR;
2045         }
2047         if (!strcmp(opt, "color"))
2048                 return option_color_command(argc, argv);
2050         if (!strcmp(opt, "set"))
2051                 return option_set_command(argc, argv);
2053         if (!strcmp(opt, "bind"))
2054                 return option_bind_command(argc, argv);
2056         config_msg = "Unknown option command";
2057         return ERR;
2060 static int
2061 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
2063         int status = OK;
2065         config_lineno++;
2066         config_msg = "Internal error";
2068         /* Check for comment markers, since read_properties() will
2069          * only ensure opt and value are split at first " \t". */
2070         optlen = strcspn(opt, "#");
2071         if (optlen == 0)
2072                 return OK;
2074         if (opt[optlen] != 0) {
2075                 config_msg = "No option value";
2076                 status = ERR;
2078         }  else {
2079                 /* Look for comment endings in the value. */
2080                 size_t len = strcspn(value, "#");
2082                 if (len < valuelen) {
2083                         valuelen = len;
2084                         value[valuelen] = 0;
2085                 }
2087                 status = set_option(opt, value);
2088         }
2090         if (status == ERR) {
2091                 warn("Error on line %d, near '%.*s': %s",
2092                      config_lineno, (int) optlen, opt, config_msg);
2093                 config_errors = TRUE;
2094         }
2096         /* Always keep going if errors are encountered. */
2097         return OK;
2100 static void
2101 load_option_file(const char *path)
2103         struct io io = {};
2105         /* It's OK that the file doesn't exist. */
2106         if (!io_open(&io, "%s", path))
2107                 return;
2109         config_lineno = 0;
2110         config_errors = FALSE;
2112         if (io_load(&io, " \t", read_option) == ERR ||
2113             config_errors == TRUE)
2114                 warn("Errors while loading %s.", path);
2117 static int
2118 load_options(void)
2120         const char *home = getenv("HOME");
2121         const char *tigrc_user = getenv("TIGRC_USER");
2122         const char *tigrc_system = getenv("TIGRC_SYSTEM");
2123         char buf[SIZEOF_STR];
2125         add_builtin_run_requests();
2127         if (!tigrc_system)
2128                 tigrc_system = SYSCONFDIR "/tigrc";
2129         load_option_file(tigrc_system);
2131         if (!tigrc_user) {
2132                 if (!home || !string_format(buf, "%s/.tigrc", home))
2133                         return ERR;
2134                 tigrc_user = buf;
2135         }
2136         load_option_file(tigrc_user);
2138         return OK;
2142 /*
2143  * The viewer
2144  */
2146 struct view;
2147 struct view_ops;
2149 /* The display array of active views and the index of the current view. */
2150 static struct view *display[2];
2151 static unsigned int current_view;
2153 #define foreach_displayed_view(view, i) \
2154         for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
2156 #define displayed_views()       (display[1] != NULL ? 2 : 1)
2158 /* Current head and commit ID */
2159 static char ref_blob[SIZEOF_REF]        = "";
2160 static char ref_commit[SIZEOF_REF]      = "HEAD";
2161 static char ref_head[SIZEOF_REF]        = "HEAD";
2162 static char ref_branch[SIZEOF_REF]      = "";
2164 struct view {
2165         const char *name;       /* View name */
2166         const char *cmd_env;    /* Command line set via environment */
2167         const char *id;         /* Points to either of ref_{head,commit,blob} */
2169         struct view_ops *ops;   /* View operations */
2171         enum keymap keymap;     /* What keymap does this view have */
2172         bool git_dir;           /* Whether the view requires a git directory. */
2174         char ref[SIZEOF_REF];   /* Hovered commit reference */
2175         char vid[SIZEOF_REF];   /* View ID. Set to id member when updating. */
2177         int height, width;      /* The width and height of the main window */
2178         WINDOW *win;            /* The main window */
2179         WINDOW *title;          /* The title window living below the main window */
2181         /* Navigation */
2182         unsigned long offset;   /* Offset of the window top */
2183         unsigned long yoffset;  /* Offset from the window side. */
2184         unsigned long lineno;   /* Current line number */
2185         unsigned long p_offset; /* Previous offset of the window top */
2186         unsigned long p_yoffset;/* Previous offset from the window side */
2187         unsigned long p_lineno; /* Previous current line number */
2188         bool p_restore;         /* Should the previous position be restored. */
2190         /* Searching */
2191         char grep[SIZEOF_STR];  /* Search string */
2192         regex_t *regex;         /* Pre-compiled regexp */
2194         /* If non-NULL, points to the view that opened this view. If this view
2195          * is closed tig will switch back to the parent view. */
2196         struct view *parent;
2198         /* Buffering */
2199         size_t lines;           /* Total number of lines */
2200         struct line *line;      /* Line index */
2201         unsigned int digits;    /* Number of digits in the lines member. */
2203         /* Drawing */
2204         struct line *curline;   /* Line currently being drawn. */
2205         enum line_type curtype; /* Attribute currently used for drawing. */
2206         unsigned long col;      /* Column when drawing. */
2207         bool has_scrolled;      /* View was scrolled. */
2209         /* Loading */
2210         struct io io;
2211         struct io *pipe;
2212         time_t start_time;
2213         time_t update_secs;
2214 };
2216 struct view_ops {
2217         /* What type of content being displayed. Used in the title bar. */
2218         const char *type;
2219         /* Default command arguments. */
2220         const char **argv;
2221         /* Open and reads in all view content. */
2222         bool (*open)(struct view *view);
2223         /* Read one line; updates view->line. */
2224         bool (*read)(struct view *view, char *data);
2225         /* Draw one line; @lineno must be < view->height. */
2226         bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
2227         /* Depending on view handle a special requests. */
2228         enum request (*request)(struct view *view, enum request request, struct line *line);
2229         /* Search for regexp in a line. */
2230         bool (*grep)(struct view *view, struct line *line);
2231         /* Select line */
2232         void (*select)(struct view *view, struct line *line);
2233         /* Prepare view for loading */
2234         bool (*prepare)(struct view *view);
2235 };
2237 static struct view_ops blame_ops;
2238 static struct view_ops blob_ops;
2239 static struct view_ops diff_ops;
2240 static struct view_ops help_ops;
2241 static struct view_ops log_ops;
2242 static struct view_ops main_ops;
2243 static struct view_ops pager_ops;
2244 static struct view_ops stage_ops;
2245 static struct view_ops status_ops;
2246 static struct view_ops tree_ops;
2247 static struct view_ops branch_ops;
2249 #define VIEW_STR(name, env, ref, ops, map, git) \
2250         { name, #env, ref, ops, map, git }
2252 #define VIEW_(id, name, ops, git, ref) \
2253         VIEW_STR(name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
2256 static struct view views[] = {
2257         VIEW_(MAIN,   "main",   &main_ops,   TRUE,  ref_head),
2258         VIEW_(DIFF,   "diff",   &diff_ops,   TRUE,  ref_commit),
2259         VIEW_(LOG,    "log",    &log_ops,    TRUE,  ref_head),
2260         VIEW_(TREE,   "tree",   &tree_ops,   TRUE,  ref_commit),
2261         VIEW_(BLOB,   "blob",   &blob_ops,   TRUE,  ref_blob),
2262         VIEW_(BLAME,  "blame",  &blame_ops,  TRUE,  ref_commit),
2263         VIEW_(BRANCH, "branch", &branch_ops, TRUE,  ref_head),
2264         VIEW_(HELP,   "help",   &help_ops,   FALSE, ""),
2265         VIEW_(PAGER,  "pager",  &pager_ops,  FALSE, "stdin"),
2266         VIEW_(STATUS, "status", &status_ops, TRUE,  ""),
2267         VIEW_(STAGE,  "stage",  &stage_ops,  TRUE,  ""),
2268 };
2270 #define VIEW(req)       (&views[(req) - REQ_OFFSET - 1])
2271 #define VIEW_REQ(view)  ((view) - views + REQ_OFFSET + 1)
2273 #define foreach_view(view, i) \
2274         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2276 #define view_is_displayed(view) \
2277         (view == display[0] || view == display[1])
2280 static inline void
2281 set_view_attr(struct view *view, enum line_type type)
2283         if (!view->curline->selected && view->curtype != type) {
2284                 (void) wattrset(view->win, get_line_attr(type));
2285                 wchgat(view->win, -1, 0, type, NULL);
2286                 view->curtype = type;
2287         }
2290 static int
2291 draw_chars(struct view *view, enum line_type type, const char *string,
2292            int max_len, bool use_tilde)
2294         static char out_buffer[BUFSIZ * 2];
2295         int len = 0;
2296         int col = 0;
2297         int trimmed = FALSE;
2298         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2300         if (max_len <= 0)
2301                 return 0;
2303         len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde, opt_tab_size);
2305         set_view_attr(view, type);
2306         if (len > 0) {
2307                 if (opt_iconv_out != ICONV_NONE) {
2308                         ICONV_CONST char *inbuf = (ICONV_CONST char *) string;
2309                         size_t inlen = len + 1;
2311                         char *outbuf = out_buffer;
2312                         size_t outlen = sizeof(out_buffer);
2314                         size_t ret;
2316                         ret = iconv(opt_iconv_out, &inbuf, &inlen, &outbuf, &outlen);
2317                         if (ret != (size_t) -1) {
2318                                 string = out_buffer;
2319                                 len = sizeof(out_buffer) - outlen;
2320                         }
2321                 }
2323                 waddnstr(view->win, string, len);
2324         }
2325         if (trimmed && use_tilde) {
2326                 set_view_attr(view, LINE_DELIMITER);
2327                 waddch(view->win, '~');
2328                 col++;
2329         }
2331         return col;
2334 static int
2335 draw_space(struct view *view, enum line_type type, int max, int spaces)
2337         static char space[] = "                    ";
2338         int col = 0;
2340         spaces = MIN(max, spaces);
2342         while (spaces > 0) {
2343                 int len = MIN(spaces, sizeof(space) - 1);
2345                 col += draw_chars(view, type, space, len, FALSE);
2346                 spaces -= len;
2347         }
2349         return col;
2352 static bool
2353 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
2355         view->col += draw_chars(view, type, string, view->width + view->yoffset - view->col, trim);
2356         return view->width + view->yoffset <= view->col;
2359 static bool
2360 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
2362         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2363         int max = view->width + view->yoffset - view->col;
2364         int i;
2366         if (max < size)
2367                 size = max;
2369         set_view_attr(view, type);
2370         /* Using waddch() instead of waddnstr() ensures that
2371          * they'll be rendered correctly for the cursor line. */
2372         for (i = skip; i < size; i++)
2373                 waddch(view->win, graphic[i]);
2375         view->col += size;
2376         if (size < max && skip <= size)
2377                 waddch(view->win, ' ');
2378         view->col++;
2380         return view->width + view->yoffset <= view->col;
2383 static bool
2384 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
2386         int max = MIN(view->width + view->yoffset - view->col, len);
2387         int col;
2389         if (text)
2390                 col = draw_chars(view, type, text, max - 1, trim);
2391         else
2392                 col = draw_space(view, type, max - 1, max - 1);
2394         view->col += col;
2395         view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
2396         return view->width + view->yoffset <= view->col;
2399 static bool
2400 draw_date(struct view *view, struct time *time)
2402         const char *date = mkdate(time, opt_date);
2403         int cols = opt_date == DATE_SHORT ? DATE_SHORT_COLS : DATE_COLS;
2405         return draw_field(view, LINE_DATE, date, cols, FALSE);
2408 static bool
2409 draw_author(struct view *view, const char *author)
2411         bool trim = opt_author_cols == 0 || opt_author_cols > 5;
2412         bool abbreviate = opt_author == AUTHOR_ABBREVIATED || !trim;
2414         if (abbreviate && author)
2415                 author = get_author_initials(author);
2417         return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2420 static bool
2421 draw_mode(struct view *view, mode_t mode)
2423         const char *str;
2425         if (S_ISDIR(mode))
2426                 str = "drwxr-xr-x";
2427         else if (S_ISLNK(mode))
2428                 str = "lrwxrwxrwx";
2429         else if (S_ISGITLINK(mode))
2430                 str = "m---------";
2431         else if (S_ISREG(mode) && mode & S_IXUSR)
2432                 str = "-rwxr-xr-x";
2433         else if (S_ISREG(mode))
2434                 str = "-rw-r--r--";
2435         else
2436                 str = "----------";
2438         return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2441 static bool
2442 draw_lineno(struct view *view, unsigned int lineno)
2444         char number[10];
2445         int digits3 = view->digits < 3 ? 3 : view->digits;
2446         int max = MIN(view->width + view->yoffset - view->col, digits3);
2447         char *text = NULL;
2448         chtype separator = opt_line_graphics ? ACS_VLINE : '|';
2450         lineno += view->offset + 1;
2451         if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2452                 static char fmt[] = "%1ld";
2454                 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2455                 if (string_format(number, fmt, lineno))
2456                         text = number;
2457         }
2458         if (text)
2459                 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2460         else
2461                 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2462         return draw_graphic(view, LINE_DEFAULT, &separator, 1);
2465 static bool
2466 draw_view_line(struct view *view, unsigned int lineno)
2468         struct line *line;
2469         bool selected = (view->offset + lineno == view->lineno);
2471         assert(view_is_displayed(view));
2473         if (view->offset + lineno >= view->lines)
2474                 return FALSE;
2476         line = &view->line[view->offset + lineno];
2478         wmove(view->win, lineno, 0);
2479         if (line->cleareol)
2480                 wclrtoeol(view->win);
2481         view->col = 0;
2482         view->curline = line;
2483         view->curtype = LINE_NONE;
2484         line->selected = FALSE;
2485         line->dirty = line->cleareol = 0;
2487         if (selected) {
2488                 set_view_attr(view, LINE_CURSOR);
2489                 line->selected = TRUE;
2490                 view->ops->select(view, line);
2491         }
2493         return view->ops->draw(view, line, lineno);
2496 static void
2497 redraw_view_dirty(struct view *view)
2499         bool dirty = FALSE;
2500         int lineno;
2502         for (lineno = 0; lineno < view->height; lineno++) {
2503                 if (view->offset + lineno >= view->lines)
2504                         break;
2505                 if (!view->line[view->offset + lineno].dirty)
2506                         continue;
2507                 dirty = TRUE;
2508                 if (!draw_view_line(view, lineno))
2509                         break;
2510         }
2512         if (!dirty)
2513                 return;
2514         wnoutrefresh(view->win);
2517 static void
2518 redraw_view_from(struct view *view, int lineno)
2520         assert(0 <= lineno && lineno < view->height);
2522         for (; lineno < view->height; lineno++) {
2523                 if (!draw_view_line(view, lineno))
2524                         break;
2525         }
2527         wnoutrefresh(view->win);
2530 static void
2531 redraw_view(struct view *view)
2533         werase(view->win);
2534         redraw_view_from(view, 0);
2538 static void
2539 update_view_title(struct view *view)
2541         char buf[SIZEOF_STR];
2542         char state[SIZEOF_STR];
2543         size_t bufpos = 0, statelen = 0;
2545         assert(view_is_displayed(view));
2547         if (view != VIEW(REQ_VIEW_STATUS) && view->lines) {
2548                 unsigned int view_lines = view->offset + view->height;
2549                 unsigned int lines = view->lines
2550                                    ? MIN(view_lines, view->lines) * 100 / view->lines
2551                                    : 0;
2553                 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2554                                    view->ops->type,
2555                                    view->lineno + 1,
2556                                    view->lines,
2557                                    lines);
2559         }
2561         if (view->pipe) {
2562                 time_t secs = time(NULL) - view->start_time;
2564                 /* Three git seconds are a long time ... */
2565                 if (secs > 2)
2566                         string_format_from(state, &statelen, " loading %lds", secs);
2567         }
2569         string_format_from(buf, &bufpos, "[%s]", view->name);
2570         if (*view->ref && bufpos < view->width) {
2571                 size_t refsize = strlen(view->ref);
2572                 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2574                 if (minsize < view->width)
2575                         refsize = view->width - minsize + 7;
2576                 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2577         }
2579         if (statelen && bufpos < view->width) {
2580                 string_format_from(buf, &bufpos, "%s", state);
2581         }
2583         if (view == display[current_view])
2584                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2585         else
2586                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2588         mvwaddnstr(view->title, 0, 0, buf, bufpos);
2589         wclrtoeol(view->title);
2590         wnoutrefresh(view->title);
2593 static int
2594 apply_step(double step, int value)
2596         if (step >= 1)
2597                 return (int) step;
2598         value *= step + 0.01;
2599         return value ? value : 1;
2602 static void
2603 resize_display(void)
2605         int offset, i;
2606         struct view *base = display[0];
2607         struct view *view = display[1] ? display[1] : display[0];
2609         /* Setup window dimensions */
2611         getmaxyx(stdscr, base->height, base->width);
2613         /* Make room for the status window. */
2614         base->height -= 1;
2616         if (view != base) {
2617                 /* Horizontal split. */
2618                 view->width   = base->width;
2619                 view->height  = apply_step(opt_scale_split_view, base->height);
2620                 view->height  = MAX(view->height, MIN_VIEW_HEIGHT);
2621                 view->height  = MIN(view->height, base->height - MIN_VIEW_HEIGHT);
2622                 base->height -= view->height;
2624                 /* Make room for the title bar. */
2625                 view->height -= 1;
2626         }
2628         /* Make room for the title bar. */
2629         base->height -= 1;
2631         offset = 0;
2633         foreach_displayed_view (view, i) {
2634                 if (!view->win) {
2635                         view->win = newwin(view->height, 0, offset, 0);
2636                         if (!view->win)
2637                                 die("Failed to create %s view", view->name);
2639                         scrollok(view->win, FALSE);
2641                         view->title = newwin(1, 0, offset + view->height, 0);
2642                         if (!view->title)
2643                                 die("Failed to create title window");
2645                 } else {
2646                         wresize(view->win, view->height, view->width);
2647                         mvwin(view->win,   offset, 0);
2648                         mvwin(view->title, offset + view->height, 0);
2649                 }
2651                 offset += view->height + 1;
2652         }
2655 static void
2656 redraw_display(bool clear)
2658         struct view *view;
2659         int i;
2661         foreach_displayed_view (view, i) {
2662                 if (clear)
2663                         wclear(view->win);
2664                 redraw_view(view);
2665                 update_view_title(view);
2666         }
2669 static void
2670 toggle_enum_option_do(unsigned int *opt, const char *help,
2671                       const struct enum_map *map, size_t size)
2673         *opt = (*opt + 1) % size;
2674         redraw_display(FALSE);
2675         report("Displaying %s %s", enum_name(map[*opt]), help);
2678 #define toggle_enum_option(opt, help, map) \
2679         toggle_enum_option_do(opt, help, map, ARRAY_SIZE(map))
2681 #define toggle_date() toggle_enum_option(&opt_date, "dates", date_map)
2682 #define toggle_author() toggle_enum_option(&opt_author, "author names", author_map)
2684 static void
2685 toggle_view_option(bool *option, const char *help)
2687         *option = !*option;
2688         redraw_display(FALSE);
2689         report("%sabling %s", *option ? "En" : "Dis", help);
2692 static void
2693 open_option_menu(void)
2695         const struct menu_item menu[] = {
2696                 { '.', "line numbers", &opt_line_number },
2697                 { 'D', "date display", &opt_date },
2698                 { 'A', "author display", &opt_author },
2699                 { 'g', "revision graph display", &opt_rev_graph },
2700                 { 'F', "reference display", &opt_show_refs },
2701                 { 0 }
2702         };
2703         int selected = 0;
2705         if (prompt_menu("Toggle option", menu, &selected)) {
2706                 if (menu[selected].data == &opt_date)
2707                         toggle_date();
2708                 else if (menu[selected].data == &opt_author)
2709                         toggle_author();
2710                 else
2711                         toggle_view_option(menu[selected].data, menu[selected].text);
2712         }
2715 static void
2716 maximize_view(struct view *view)
2718         memset(display, 0, sizeof(display));
2719         current_view = 0;
2720         display[current_view] = view;
2721         resize_display();
2722         redraw_display(FALSE);
2723         report("");
2727 /*
2728  * Navigation
2729  */
2731 static bool
2732 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2734         if (lineno >= view->lines)
2735                 lineno = view->lines > 0 ? view->lines - 1 : 0;
2737         if (offset > lineno || offset + view->height <= lineno) {
2738                 unsigned long half = view->height / 2;
2740                 if (lineno > half)
2741                         offset = lineno - half;
2742                 else
2743                         offset = 0;
2744         }
2746         if (offset != view->offset || lineno != view->lineno) {
2747                 view->offset = offset;
2748                 view->lineno = lineno;
2749                 return TRUE;
2750         }
2752         return FALSE;
2755 /* Scrolling backend */
2756 static void
2757 do_scroll_view(struct view *view, int lines)
2759         bool redraw_current_line = FALSE;
2761         /* The rendering expects the new offset. */
2762         view->offset += lines;
2764         assert(0 <= view->offset && view->offset < view->lines);
2765         assert(lines);
2767         /* Move current line into the view. */
2768         if (view->lineno < view->offset) {
2769                 view->lineno = view->offset;
2770                 redraw_current_line = TRUE;
2771         } else if (view->lineno >= view->offset + view->height) {
2772                 view->lineno = view->offset + view->height - 1;
2773                 redraw_current_line = TRUE;
2774         }
2776         assert(view->offset <= view->lineno && view->lineno < view->lines);
2778         /* Redraw the whole screen if scrolling is pointless. */
2779         if (view->height < ABS(lines)) {
2780                 redraw_view(view);
2782         } else {
2783                 int line = lines > 0 ? view->height - lines : 0;
2784                 int end = line + ABS(lines);
2786                 scrollok(view->win, TRUE);
2787                 wscrl(view->win, lines);
2788                 scrollok(view->win, FALSE);
2790                 while (line < end && draw_view_line(view, line))
2791                         line++;
2793                 if (redraw_current_line)
2794                         draw_view_line(view, view->lineno - view->offset);
2795                 wnoutrefresh(view->win);
2796         }
2798         view->has_scrolled = TRUE;
2799         report("");
2802 /* Scroll frontend */
2803 static void
2804 scroll_view(struct view *view, enum request request)
2806         int lines = 1;
2808         assert(view_is_displayed(view));
2810         switch (request) {
2811         case REQ_SCROLL_LEFT:
2812                 if (view->yoffset == 0) {
2813                         report("Cannot scroll beyond the first column");
2814                         return;
2815                 }
2816                 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2817                         view->yoffset = 0;
2818                 else
2819                         view->yoffset -= apply_step(opt_hscroll, view->width);
2820                 redraw_view_from(view, 0);
2821                 report("");
2822                 return;
2823         case REQ_SCROLL_RIGHT:
2824                 view->yoffset += apply_step(opt_hscroll, view->width);
2825                 redraw_view(view);
2826                 report("");
2827                 return;
2828         case REQ_SCROLL_PAGE_DOWN:
2829                 lines = view->height;
2830         case REQ_SCROLL_LINE_DOWN:
2831                 if (view->offset + lines > view->lines)
2832                         lines = view->lines - view->offset;
2834                 if (lines == 0 || view->offset + view->height >= view->lines) {
2835                         report("Cannot scroll beyond the last line");
2836                         return;
2837                 }
2838                 break;
2840         case REQ_SCROLL_PAGE_UP:
2841                 lines = view->height;
2842         case REQ_SCROLL_LINE_UP:
2843                 if (lines > view->offset)
2844                         lines = view->offset;
2846                 if (lines == 0) {
2847                         report("Cannot scroll beyond the first line");
2848                         return;
2849                 }
2851                 lines = -lines;
2852                 break;
2854         default:
2855                 die("request %d not handled in switch", request);
2856         }
2858         do_scroll_view(view, lines);
2861 /* Cursor moving */
2862 static void
2863 move_view(struct view *view, enum request request)
2865         int scroll_steps = 0;
2866         int steps;
2868         switch (request) {
2869         case REQ_MOVE_FIRST_LINE:
2870                 steps = -view->lineno;
2871                 break;
2873         case REQ_MOVE_LAST_LINE:
2874                 steps = view->lines - view->lineno - 1;
2875                 break;
2877         case REQ_MOVE_PAGE_UP:
2878                 steps = view->height > view->lineno
2879                       ? -view->lineno : -view->height;
2880                 break;
2882         case REQ_MOVE_PAGE_DOWN:
2883                 steps = view->lineno + view->height >= view->lines
2884                       ? view->lines - view->lineno - 1 : view->height;
2885                 break;
2887         case REQ_MOVE_UP:
2888                 steps = -1;
2889                 break;
2891         case REQ_MOVE_DOWN:
2892                 steps = 1;
2893                 break;
2895         default:
2896                 die("request %d not handled in switch", request);
2897         }
2899         if (steps <= 0 && view->lineno == 0) {
2900                 report("Cannot move beyond the first line");
2901                 return;
2903         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2904                 report("Cannot move beyond the last line");
2905                 return;
2906         }
2908         /* Move the current line */
2909         view->lineno += steps;
2910         assert(0 <= view->lineno && view->lineno < view->lines);
2912         /* Check whether the view needs to be scrolled */
2913         if (view->lineno < view->offset ||
2914             view->lineno >= view->offset + view->height) {
2915                 scroll_steps = steps;
2916                 if (steps < 0 && -steps > view->offset) {
2917                         scroll_steps = -view->offset;
2919                 } else if (steps > 0) {
2920                         if (view->lineno == view->lines - 1 &&
2921                             view->lines > view->height) {
2922                                 scroll_steps = view->lines - view->offset - 1;
2923                                 if (scroll_steps >= view->height)
2924                                         scroll_steps -= view->height - 1;
2925                         }
2926                 }
2927         }
2929         if (!view_is_displayed(view)) {
2930                 view->offset += scroll_steps;
2931                 assert(0 <= view->offset && view->offset < view->lines);
2932                 view->ops->select(view, &view->line[view->lineno]);
2933                 return;
2934         }
2936         /* Repaint the old "current" line if we be scrolling */
2937         if (ABS(steps) < view->height)
2938                 draw_view_line(view, view->lineno - steps - view->offset);
2940         if (scroll_steps) {
2941                 do_scroll_view(view, scroll_steps);
2942                 return;
2943         }
2945         /* Draw the current line */
2946         draw_view_line(view, view->lineno - view->offset);
2948         wnoutrefresh(view->win);
2949         report("");
2953 /*
2954  * Searching
2955  */
2957 static void search_view(struct view *view, enum request request);
2959 static bool
2960 grep_text(struct view *view, const char *text[])
2962         regmatch_t pmatch;
2963         size_t i;
2965         for (i = 0; text[i]; i++)
2966                 if (*text[i] &&
2967                     regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
2968                         return TRUE;
2969         return FALSE;
2972 static void
2973 select_view_line(struct view *view, unsigned long lineno)
2975         unsigned long old_lineno = view->lineno;
2976         unsigned long old_offset = view->offset;
2978         if (goto_view_line(view, view->offset, lineno)) {
2979                 if (view_is_displayed(view)) {
2980                         if (old_offset != view->offset) {
2981                                 redraw_view(view);
2982                         } else {
2983                                 draw_view_line(view, old_lineno - view->offset);
2984                                 draw_view_line(view, view->lineno - view->offset);
2985                                 wnoutrefresh(view->win);
2986                         }
2987                 } else {
2988                         view->ops->select(view, &view->line[view->lineno]);
2989                 }
2990         }
2993 static void
2994 find_next(struct view *view, enum request request)
2996         unsigned long lineno = view->lineno;
2997         int direction;
2999         if (!*view->grep) {
3000                 if (!*opt_search)
3001                         report("No previous search");
3002                 else
3003                         search_view(view, request);
3004                 return;
3005         }
3007         switch (request) {
3008         case REQ_SEARCH:
3009         case REQ_FIND_NEXT:
3010                 direction = 1;
3011                 break;
3013         case REQ_SEARCH_BACK:
3014         case REQ_FIND_PREV:
3015                 direction = -1;
3016                 break;
3018         default:
3019                 return;
3020         }
3022         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
3023                 lineno += direction;
3025         /* Note, lineno is unsigned long so will wrap around in which case it
3026          * will become bigger than view->lines. */
3027         for (; lineno < view->lines; lineno += direction) {
3028                 if (view->ops->grep(view, &view->line[lineno])) {
3029                         select_view_line(view, lineno);
3030                         report("Line %ld matches '%s'", lineno + 1, view->grep);
3031                         return;
3032                 }
3033         }
3035         report("No match found for '%s'", view->grep);
3038 static void
3039 search_view(struct view *view, enum request request)
3041         int regex_err;
3043         if (view->regex) {
3044                 regfree(view->regex);
3045                 *view->grep = 0;
3046         } else {
3047                 view->regex = calloc(1, sizeof(*view->regex));
3048                 if (!view->regex)
3049                         return;
3050         }
3052         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
3053         if (regex_err != 0) {
3054                 char buf[SIZEOF_STR] = "unknown error";
3056                 regerror(regex_err, view->regex, buf, sizeof(buf));
3057                 report("Search failed: %s", buf);
3058                 return;
3059         }
3061         string_copy(view->grep, opt_search);
3063         find_next(view, request);
3066 /*
3067  * Incremental updating
3068  */
3070 static void
3071 reset_view(struct view *view)
3073         int i;
3075         for (i = 0; i < view->lines; i++)
3076                 free(view->line[i].data);
3077         free(view->line);
3079         view->p_offset = view->offset;
3080         view->p_yoffset = view->yoffset;
3081         view->p_lineno = view->lineno;
3083         view->line = NULL;
3084         view->offset = 0;
3085         view->yoffset = 0;
3086         view->lines  = 0;
3087         view->lineno = 0;
3088         view->vid[0] = 0;
3089         view->update_secs = 0;
3092 static void
3093 free_argv(const char *argv[])
3095         int argc;
3097         for (argc = 0; argv[argc]; argc++)
3098                 free((void *) argv[argc]);
3101 static const char *
3102 format_arg(const char *name)
3104         static struct {
3105                 const char *name;
3106                 size_t namelen;
3107                 const char *value;
3108                 const char *value_if_empty;
3109         } vars[] = {
3110 #define FORMAT_VAR(name, value, value_if_empty) \
3111         { name, STRING_SIZE(name), value, value_if_empty }
3112                 FORMAT_VAR("%(directory)",      opt_path,       ""),
3113                 FORMAT_VAR("%(file)",           opt_file,       ""),
3114                 FORMAT_VAR("%(ref)",            opt_ref,        "HEAD"),
3115                 FORMAT_VAR("%(head)",           ref_head,       ""),
3116                 FORMAT_VAR("%(commit)",         ref_commit,     ""),
3117                 FORMAT_VAR("%(blob)",           ref_blob,       ""),
3118                 FORMAT_VAR("%(branch)",         ref_branch,     ""),
3119         };
3120         int i;
3122         for (i = 0; i < ARRAY_SIZE(vars); i++)
3123                 if (!strncmp(name, vars[i].name, vars[i].namelen))
3124                         return *vars[i].value ? vars[i].value : vars[i].value_if_empty;
3126         report("Unknown replacement: `%s`", name);
3127         return NULL;
3130 static bool
3131 format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
3133         char buf[SIZEOF_STR];
3134         int argc;
3135         bool noreplace = flags == FORMAT_NONE;
3137         free_argv(dst_argv);
3139         for (argc = 0; src_argv[argc]; argc++) {
3140                 const char *arg = src_argv[argc];
3141                 size_t bufpos = 0;
3143                 while (arg) {
3144                         char *next = strstr(arg, "%(");
3145                         int len = next - arg;
3146                         const char *value;
3148                         if (!next || noreplace) {
3149                                 if (flags == FORMAT_DASH && !strcmp(arg, "--"))
3150                                         noreplace = TRUE;
3151                                 len = strlen(arg);
3152                                 value = "";
3154                         } else {
3155                                 value = format_arg(next);
3157                                 if (!value) {
3158                                         return FALSE;
3159                                 }
3160                         }
3162                         if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
3163                                 return FALSE;
3165                         arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
3166                 }
3168                 dst_argv[argc] = strdup(buf);
3169                 if (!dst_argv[argc])
3170                         break;
3171         }
3173         dst_argv[argc] = NULL;
3175         return src_argv[argc] == NULL;
3178 static bool
3179 restore_view_position(struct view *view)
3181         if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
3182                 return FALSE;
3184         /* Changing the view position cancels the restoring. */
3185         /* FIXME: Changing back to the first line is not detected. */
3186         if (view->offset != 0 || view->lineno != 0) {
3187                 view->p_restore = FALSE;
3188                 return FALSE;
3189         }
3191         if (goto_view_line(view, view->p_offset, view->p_lineno) &&
3192             view_is_displayed(view))
3193                 werase(view->win);
3195         view->yoffset = view->p_yoffset;
3196         view->p_restore = FALSE;
3198         return TRUE;
3201 static void
3202 end_update(struct view *view, bool force)
3204         if (!view->pipe)
3205                 return;
3206         while (!view->ops->read(view, NULL))
3207                 if (!force)
3208                         return;
3209         if (force)
3210                 io_kill(view->pipe);
3211         io_done(view->pipe);
3212         view->pipe = NULL;
3215 static void
3216 setup_update(struct view *view, const char *vid)
3218         reset_view(view);
3219         string_copy_rev(view->vid, vid);
3220         view->pipe = &view->io;
3221         view->start_time = time(NULL);
3224 static bool
3225 prepare_update(struct view *view, const char *argv[], const char *dir)
3227         if (view->pipe)
3228                 end_update(view, TRUE);
3229         return io_format(&view->io, dir, IO_RD, argv, FORMAT_NONE);
3232 static bool
3233 prepare_update_file(struct view *view, const char *name)
3235         if (view->pipe)
3236                 end_update(view, TRUE);
3237         return io_open(&view->io, "%s/%s", opt_cdup[0] ? opt_cdup : ".", name);
3240 static bool
3241 begin_update(struct view *view, bool refresh)
3243         if (view->pipe)
3244                 end_update(view, TRUE);
3246         if (!refresh) {
3247                 if (view->ops->prepare) {
3248                         if (!view->ops->prepare(view))
3249                                 return FALSE;
3250                 } else if (!io_format(&view->io, NULL, IO_RD, view->ops->argv, FORMAT_ALL)) {
3251                         return FALSE;
3252                 }
3254                 /* Put the current ref_* value to the view title ref
3255                  * member. This is needed by the blob view. Most other
3256                  * views sets it automatically after loading because the
3257                  * first line is a commit line. */
3258                 string_copy_rev(view->ref, view->id);
3259         }
3261         if (!io_start(&view->io))
3262                 return FALSE;
3264         setup_update(view, view->id);
3266         return TRUE;
3269 static bool
3270 update_view(struct view *view)
3272         char out_buffer[BUFSIZ * 2];
3273         char *line;
3274         /* Clear the view and redraw everything since the tree sorting
3275          * might have rearranged things. */
3276         bool redraw = view->lines == 0;
3277         bool can_read = TRUE;
3279         if (!view->pipe)
3280                 return TRUE;
3282         if (!io_can_read(view->pipe)) {
3283                 if (view->lines == 0 && view_is_displayed(view)) {
3284                         time_t secs = time(NULL) - view->start_time;
3286                         if (secs > 1 && secs > view->update_secs) {
3287                                 if (view->update_secs == 0)
3288                                         redraw_view(view);
3289                                 update_view_title(view);
3290                                 view->update_secs = secs;
3291                         }
3292                 }
3293                 return TRUE;
3294         }
3296         for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
3297                 if (opt_iconv_in != ICONV_NONE) {
3298                         ICONV_CONST char *inbuf = line;
3299                         size_t inlen = strlen(line) + 1;
3301                         char *outbuf = out_buffer;
3302                         size_t outlen = sizeof(out_buffer);
3304                         size_t ret;
3306                         ret = iconv(opt_iconv_in, &inbuf, &inlen, &outbuf, &outlen);
3307                         if (ret != (size_t) -1)
3308                                 line = out_buffer;
3309                 }
3311                 if (!view->ops->read(view, line)) {
3312                         report("Allocation failure");
3313                         end_update(view, TRUE);
3314                         return FALSE;
3315                 }
3316         }
3318         {
3319                 unsigned long lines = view->lines;
3320                 int digits;
3322                 for (digits = 0; lines; digits++)
3323                         lines /= 10;
3325                 /* Keep the displayed view in sync with line number scaling. */
3326                 if (digits != view->digits) {
3327                         view->digits = digits;
3328                         if (opt_line_number || view == VIEW(REQ_VIEW_BLAME))
3329                                 redraw = TRUE;
3330                 }
3331         }
3333         if (io_error(view->pipe)) {
3334                 report("Failed to read: %s", io_strerror(view->pipe));
3335                 end_update(view, TRUE);
3337         } else if (io_eof(view->pipe)) {
3338                 report("");
3339                 end_update(view, FALSE);
3340         }
3342         if (restore_view_position(view))
3343                 redraw = TRUE;
3345         if (!view_is_displayed(view))
3346                 return TRUE;
3348         if (redraw)
3349                 redraw_view_from(view, 0);
3350         else
3351                 redraw_view_dirty(view);
3353         /* Update the title _after_ the redraw so that if the redraw picks up a
3354          * commit reference in view->ref it'll be available here. */
3355         update_view_title(view);
3356         return TRUE;
3359 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
3361 static struct line *
3362 add_line_data(struct view *view, void *data, enum line_type type)
3364         struct line *line;
3366         if (!realloc_lines(&view->line, view->lines, 1))
3367                 return NULL;
3369         line = &view->line[view->lines++];
3370         memset(line, 0, sizeof(*line));
3371         line->type = type;
3372         line->data = data;
3373         line->dirty = 1;
3375         return line;
3378 static struct line *
3379 add_line_text(struct view *view, const char *text, enum line_type type)
3381         char *data = text ? strdup(text) : NULL;
3383         return data ? add_line_data(view, data, type) : NULL;
3386 static struct line *
3387 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
3389         char buf[SIZEOF_STR];
3390         va_list args;
3392         va_start(args, fmt);
3393         if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
3394                 buf[0] = 0;
3395         va_end(args);
3397         return buf[0] ? add_line_text(view, buf, type) : NULL;
3400 /*
3401  * View opening
3402  */
3404 enum open_flags {
3405         OPEN_DEFAULT = 0,       /* Use default view switching. */
3406         OPEN_SPLIT = 1,         /* Split current view. */
3407         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
3408         OPEN_REFRESH = 16,      /* Refresh view using previous command. */
3409         OPEN_PREPARED = 32,     /* Open already prepared command. */
3410 };
3412 static void
3413 open_view(struct view *prev, enum request request, enum open_flags flags)
3415         bool split = !!(flags & OPEN_SPLIT);
3416         bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
3417         bool nomaximize = !!(flags & OPEN_REFRESH);
3418         struct view *view = VIEW(request);
3419         int nviews = displayed_views();
3420         struct view *base_view = display[0];
3422         if (view == prev && nviews == 1 && !reload) {
3423                 report("Already in %s view", view->name);
3424                 return;
3425         }
3427         if (view->git_dir && !opt_git_dir[0]) {
3428                 report("The %s view is disabled in pager view", view->name);
3429                 return;
3430         }
3432         if (split) {
3433                 display[1] = view;
3434                 current_view = 1;
3435         } else if (!nomaximize) {
3436                 /* Maximize the current view. */
3437                 memset(display, 0, sizeof(display));
3438                 current_view = 0;
3439                 display[current_view] = view;
3440         }
3442         /* No parent signals that this is the first loaded view. */
3443         if (prev && view != prev) {
3444                 view->parent = prev;
3445         }
3447         /* Resize the view when switching between split- and full-screen,
3448          * or when switching between two different full-screen views. */
3449         if (nviews != displayed_views() ||
3450             (nviews == 1 && base_view != display[0]))
3451                 resize_display();
3453         if (view->ops->open) {
3454                 if (view->pipe)
3455                         end_update(view, TRUE);
3456                 if (!view->ops->open(view)) {
3457                         report("Failed to load %s view", view->name);
3458                         return;
3459                 }
3460                 restore_view_position(view);
3462         } else if ((reload || strcmp(view->vid, view->id)) &&
3463                    !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3464                 report("Failed to load %s view", view->name);
3465                 return;
3466         }
3468         if (split && prev->lineno - prev->offset >= prev->height) {
3469                 /* Take the title line into account. */
3470                 int lines = prev->lineno - prev->offset - prev->height + 1;
3472                 /* Scroll the view that was split if the current line is
3473                  * outside the new limited view. */
3474                 do_scroll_view(prev, lines);
3475         }
3477         if (prev && view != prev && split && view_is_displayed(prev)) {
3478                 /* "Blur" the previous view. */
3479                 update_view_title(prev);
3480         }
3482         if (view->pipe && view->lines == 0) {
3483                 /* Clear the old view and let the incremental updating refill
3484                  * the screen. */
3485                 werase(view->win);
3486                 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3487                 report("");
3488         } else if (view_is_displayed(view)) {
3489                 redraw_view(view);
3490                 report("");
3491         }
3494 static void
3495 open_external_viewer(const char *argv[], const char *dir)
3497         def_prog_mode();           /* save current tty modes */
3498         endwin();                  /* restore original tty modes */
3499         io_run_fg(argv, dir);
3500         fprintf(stderr, "Press Enter to continue");
3501         getc(opt_tty);
3502         reset_prog_mode();
3503         redraw_display(TRUE);
3506 static void
3507 open_mergetool(const char *file)
3509         const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3511         open_external_viewer(mergetool_argv, opt_cdup);
3514 static void
3515 open_editor(const char *file)
3517         const char *editor_argv[] = { "vi", file, NULL };
3518         const char *editor;
3520         editor = getenv("GIT_EDITOR");
3521         if (!editor && *opt_editor)
3522                 editor = opt_editor;
3523         if (!editor)
3524                 editor = getenv("VISUAL");
3525         if (!editor)
3526                 editor = getenv("EDITOR");
3527         if (!editor)
3528                 editor = "vi";
3530         editor_argv[0] = editor;
3531         open_external_viewer(editor_argv, opt_cdup);
3534 static void
3535 open_run_request(enum request request)
3537         struct run_request *req = get_run_request(request);
3538         const char *argv[ARRAY_SIZE(req->argv)] = { NULL };
3540         if (!req) {
3541                 report("Unknown run request");
3542                 return;
3543         }
3545         if (format_argv(argv, req->argv, FORMAT_ALL))
3546                 open_external_viewer(argv, NULL);
3547         free_argv(argv);
3550 /*
3551  * User request switch noodle
3552  */
3554 static int
3555 view_driver(struct view *view, enum request request)
3557         int i;
3559         if (request == REQ_NONE)
3560                 return TRUE;
3562         if (request > REQ_NONE) {
3563                 open_run_request(request);
3564                 /* FIXME: When all views can refresh always do this. */
3565                 if (view == VIEW(REQ_VIEW_STATUS) ||
3566                     view == VIEW(REQ_VIEW_MAIN) ||
3567                     view == VIEW(REQ_VIEW_LOG) ||
3568                     view == VIEW(REQ_VIEW_BRANCH) ||
3569                     view == VIEW(REQ_VIEW_STAGE))
3570                         request = REQ_REFRESH;
3571                 else
3572                         return TRUE;
3573         }
3575         if (view && view->lines) {
3576                 request = view->ops->request(view, request, &view->line[view->lineno]);
3577                 if (request == REQ_NONE)
3578                         return TRUE;
3579         }
3581         switch (request) {
3582         case REQ_MOVE_UP:
3583         case REQ_MOVE_DOWN:
3584         case REQ_MOVE_PAGE_UP:
3585         case REQ_MOVE_PAGE_DOWN:
3586         case REQ_MOVE_FIRST_LINE:
3587         case REQ_MOVE_LAST_LINE:
3588                 move_view(view, request);
3589                 break;
3591         case REQ_SCROLL_LEFT:
3592         case REQ_SCROLL_RIGHT:
3593         case REQ_SCROLL_LINE_DOWN:
3594         case REQ_SCROLL_LINE_UP:
3595         case REQ_SCROLL_PAGE_DOWN:
3596         case REQ_SCROLL_PAGE_UP:
3597                 scroll_view(view, request);
3598                 break;
3600         case REQ_VIEW_BLAME:
3601                 if (!opt_file[0]) {
3602                         report("No file chosen, press %s to open tree view",
3603                                get_key(view->keymap, REQ_VIEW_TREE));
3604                         break;
3605                 }
3606                 open_view(view, request, OPEN_DEFAULT);
3607                 break;
3609         case REQ_VIEW_BLOB:
3610                 if (!ref_blob[0]) {
3611                         report("No file chosen, press %s to open tree view",
3612                                get_key(view->keymap, REQ_VIEW_TREE));
3613                         break;
3614                 }
3615                 open_view(view, request, OPEN_DEFAULT);
3616                 break;
3618         case REQ_VIEW_PAGER:
3619                 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3620                         report("No pager content, press %s to run command from prompt",
3621                                get_key(view->keymap, REQ_PROMPT));
3622                         break;
3623                 }
3624                 open_view(view, request, OPEN_DEFAULT);
3625                 break;
3627         case REQ_VIEW_STAGE:
3628                 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3629                         report("No stage content, press %s to open the status view and choose file",
3630                                get_key(view->keymap, REQ_VIEW_STATUS));
3631                         break;
3632                 }
3633                 open_view(view, request, OPEN_DEFAULT);
3634                 break;
3636         case REQ_VIEW_STATUS:
3637                 if (opt_is_inside_work_tree == FALSE) {
3638                         report("The status view requires a working tree");
3639                         break;
3640                 }
3641                 open_view(view, request, OPEN_DEFAULT);
3642                 break;
3644         case REQ_VIEW_MAIN:
3645         case REQ_VIEW_DIFF:
3646         case REQ_VIEW_LOG:
3647         case REQ_VIEW_TREE:
3648         case REQ_VIEW_HELP:
3649         case REQ_VIEW_BRANCH:
3650                 open_view(view, request, OPEN_DEFAULT);
3651                 break;
3653         case REQ_NEXT:
3654         case REQ_PREVIOUS:
3655                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3657                 if ((view == VIEW(REQ_VIEW_DIFF) &&
3658                      view->parent == VIEW(REQ_VIEW_MAIN)) ||
3659                    (view == VIEW(REQ_VIEW_DIFF) &&
3660                      view->parent == VIEW(REQ_VIEW_BLAME)) ||
3661                    (view == VIEW(REQ_VIEW_STAGE) &&
3662                      view->parent == VIEW(REQ_VIEW_STATUS)) ||
3663                    (view == VIEW(REQ_VIEW_BLOB) &&
3664                      view->parent == VIEW(REQ_VIEW_TREE)) ||
3665                    (view == VIEW(REQ_VIEW_MAIN) &&
3666                      view->parent == VIEW(REQ_VIEW_BRANCH))) {
3667                         int line;
3669                         view = view->parent;
3670                         line = view->lineno;
3671                         move_view(view, request);
3672                         if (view_is_displayed(view))
3673                                 update_view_title(view);
3674                         if (line != view->lineno)
3675                                 view->ops->request(view, REQ_ENTER,
3676                                                    &view->line[view->lineno]);
3678                 } else {
3679                         move_view(view, request);
3680                 }
3681                 break;
3683         case REQ_VIEW_NEXT:
3684         {
3685                 int nviews = displayed_views();
3686                 int next_view = (current_view + 1) % nviews;
3688                 if (next_view == current_view) {
3689                         report("Only one view is displayed");
3690                         break;
3691                 }
3693                 current_view = next_view;
3694                 /* Blur out the title of the previous view. */
3695                 update_view_title(view);
3696                 report("");
3697                 break;
3698         }
3699         case REQ_REFRESH:
3700                 report("Refreshing is not yet supported for the %s view", view->name);
3701                 break;
3703         case REQ_MAXIMIZE:
3704                 if (displayed_views() == 2)
3705                         maximize_view(view);
3706                 break;
3708         case REQ_OPTIONS:
3709                 open_option_menu();
3710                 break;
3712         case REQ_TOGGLE_LINENO:
3713                 toggle_view_option(&opt_line_number, "line numbers");
3714                 break;
3716         case REQ_TOGGLE_DATE:
3717                 toggle_date();
3718                 break;
3720         case REQ_TOGGLE_AUTHOR:
3721                 toggle_author();
3722                 break;
3724         case REQ_TOGGLE_REV_GRAPH:
3725                 toggle_view_option(&opt_rev_graph, "revision graph display");
3726                 break;
3728         case REQ_TOGGLE_REFS:
3729                 toggle_view_option(&opt_show_refs, "reference display");
3730                 break;
3732         case REQ_TOGGLE_SORT_FIELD:
3733         case REQ_TOGGLE_SORT_ORDER:
3734                 report("Sorting is not yet supported for the %s view", view->name);
3735                 break;
3737         case REQ_SEARCH:
3738         case REQ_SEARCH_BACK:
3739                 search_view(view, request);
3740                 break;
3742         case REQ_FIND_NEXT:
3743         case REQ_FIND_PREV:
3744                 find_next(view, request);
3745                 break;
3747         case REQ_STOP_LOADING:
3748                 for (i = 0; i < ARRAY_SIZE(views); i++) {
3749                         view = &views[i];
3750                         if (view->pipe)
3751                                 report("Stopped loading the %s view", view->name),
3752                         end_update(view, TRUE);
3753                 }
3754                 break;
3756         case REQ_SHOW_VERSION:
3757                 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3758                 return TRUE;
3760         case REQ_SCREEN_REDRAW:
3761                 redraw_display(TRUE);
3762                 break;
3764         case REQ_EDIT:
3765                 report("Nothing to edit");
3766                 break;
3768         case REQ_ENTER:
3769                 report("Nothing to enter");
3770                 break;
3772         case REQ_VIEW_CLOSE:
3773                 /* XXX: Mark closed views by letting view->parent point to the
3774                  * view itself. Parents to closed view should never be
3775                  * followed. */
3776                 if (view->parent &&
3777                     view->parent->parent != view->parent) {
3778                         maximize_view(view->parent);
3779                         view->parent = view;
3780                         break;
3781                 }
3782                 /* Fall-through */
3783         case REQ_QUIT:
3784                 return FALSE;
3786         default:
3787                 report("Unknown key, press %s for help",
3788                        get_key(view->keymap, REQ_VIEW_HELP));
3789                 return TRUE;
3790         }
3792         return TRUE;
3796 /*
3797  * View backend utilities
3798  */
3800 enum sort_field {
3801         ORDERBY_NAME,
3802         ORDERBY_DATE,
3803         ORDERBY_AUTHOR,
3804 };
3806 struct sort_state {
3807         const enum sort_field *fields;
3808         size_t size, current;
3809         bool reverse;
3810 };
3812 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3813 #define get_sort_field(state) ((state).fields[(state).current])
3814 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3816 static void
3817 sort_view(struct view *view, enum request request, struct sort_state *state,
3818           int (*compare)(const void *, const void *))
3820         switch (request) {
3821         case REQ_TOGGLE_SORT_FIELD:
3822                 state->current = (state->current + 1) % state->size;
3823                 break;
3825         case REQ_TOGGLE_SORT_ORDER:
3826                 state->reverse = !state->reverse;
3827                 break;
3828         default:
3829                 die("Not a sort request");
3830         }
3832         qsort(view->line, view->lines, sizeof(*view->line), compare);
3833         redraw_view(view);
3836 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3838 /* Small author cache to reduce memory consumption. It uses binary
3839  * search to lookup or find place to position new entries. No entries
3840  * are ever freed. */
3841 static const char *
3842 get_author(const char *name)
3844         static const char **authors;
3845         static size_t authors_size;
3846         int from = 0, to = authors_size - 1;
3848         while (from <= to) {
3849                 size_t pos = (to + from) / 2;
3850                 int cmp = strcmp(name, authors[pos]);
3852                 if (!cmp)
3853                         return authors[pos];
3855                 if (cmp < 0)
3856                         to = pos - 1;
3857                 else
3858                         from = pos + 1;
3859         }
3861         if (!realloc_authors(&authors, authors_size, 1))
3862                 return NULL;
3863         name = strdup(name);
3864         if (!name)
3865                 return NULL;
3867         memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3868         authors[from] = name;
3869         authors_size++;
3871         return name;
3874 static void
3875 parse_timesec(struct time *time, const char *sec)
3877         time->sec = (time_t) atol(sec);
3880 static void
3881 parse_timezone(struct time *time, const char *zone)
3883         long tz;
3885         tz  = ('0' - zone[1]) * 60 * 60 * 10;
3886         tz += ('0' - zone[2]) * 60 * 60;
3887         tz += ('0' - zone[3]) * 60;
3888         tz += ('0' - zone[4]);
3890         if (zone[0] == '-')
3891                 tz = -tz;
3893         time->tz = tz;
3894         time->sec -= tz;
3897 /* Parse author lines where the name may be empty:
3898  *      author  <email@address.tld> 1138474660 +0100
3899  */
3900 static void
3901 parse_author_line(char *ident, const char **author, struct time *time)
3903         char *nameend = strchr(ident, '<');
3904         char *emailend = strchr(ident, '>');
3906         if (nameend && emailend)
3907                 *nameend = *emailend = 0;
3908         ident = chomp_string(ident);
3909         if (!*ident) {
3910                 if (nameend)
3911                         ident = chomp_string(nameend + 1);
3912                 if (!*ident)
3913                         ident = "Unknown";
3914         }
3916         *author = get_author(ident);
3918         /* Parse epoch and timezone */
3919         if (emailend && emailend[1] == ' ') {
3920                 char *secs = emailend + 2;
3921                 char *zone = strchr(secs, ' ');
3923                 parse_timesec(time, secs);
3925                 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
3926                         parse_timezone(time, zone + 1);
3927         }
3930 static bool
3931 open_commit_parent_menu(char buf[SIZEOF_STR], int *parents)
3933         char rev[SIZEOF_REV];
3934         const char *revlist_argv[] = {
3935                 "git", "log", "--no-color", "-1", "--pretty=format:%s", rev, NULL
3936         };
3937         struct menu_item *items;
3938         char text[SIZEOF_STR];
3939         bool ok = TRUE;
3940         int i;
3942         items = calloc(*parents + 1, sizeof(*items));
3943         if (!items)
3944                 return FALSE;
3946         for (i = 0; i < *parents; i++) {
3947                 string_copy_rev(rev, &buf[SIZEOF_REV * i]);
3948                 if (!io_run_buf(revlist_argv, text, sizeof(text)) ||
3949                     !(items[i].text = strdup(text))) {
3950                         ok = FALSE;
3951                         break;
3952                 }
3953         }
3955         if (ok) {
3956                 *parents = 0;
3957                 ok = prompt_menu("Select parent", items, parents);
3958         }
3959         for (i = 0; items[i].text; i++)
3960                 free((char *) items[i].text);
3961         free(items);
3962         return ok;
3965 static bool
3966 select_commit_parent(const char *id, char rev[SIZEOF_REV], const char *path)
3968         char buf[SIZEOF_STR * 4];
3969         const char *revlist_argv[] = {
3970                 "git", "log", "--no-color", "-1",
3971                         "--pretty=format:%P", id, "--", path, NULL
3972         };
3973         int parents;
3975         if (!io_run_buf(revlist_argv, buf, sizeof(buf)) ||
3976             (parents = strlen(buf) / 40) < 0) {
3977                 report("Failed to get parent information");
3978                 return FALSE;
3980         } else if (parents == 0) {
3981                 if (path)
3982                         report("Path '%s' does not exist in the parent", path);
3983                 else
3984                         report("The selected commit has no parents");
3985                 return FALSE;
3986         }
3988         if (parents > 1 && !open_commit_parent_menu(buf, &parents))
3989                 return FALSE;
3991         string_copy_rev(rev, &buf[41 * parents]);
3992         return TRUE;
3995 /*
3996  * Pager backend
3997  */
3999 static bool
4000 pager_draw(struct view *view, struct line *line, unsigned int lineno)
4002         char text[SIZEOF_STR];
4004         if (opt_line_number && draw_lineno(view, lineno))
4005                 return TRUE;
4007         string_expand(text, sizeof(text), line->data, opt_tab_size);
4008         draw_text(view, line->type, text, TRUE);
4009         return TRUE;
4012 static bool
4013 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
4015         const char *describe_argv[] = { "git", "describe", commit_id, NULL };
4016         char ref[SIZEOF_STR];
4018         if (!io_run_buf(describe_argv, ref, sizeof(ref)) || !*ref)
4019                 return TRUE;
4021         /* This is the only fatal call, since it can "corrupt" the buffer. */
4022         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
4023                 return FALSE;
4025         return TRUE;
4028 static void
4029 add_pager_refs(struct view *view, struct line *line)
4031         char buf[SIZEOF_STR];
4032         char *commit_id = (char *)line->data + STRING_SIZE("commit ");
4033         struct ref_list *list;
4034         size_t bufpos = 0, i;
4035         const char *sep = "Refs: ";
4036         bool is_tag = FALSE;
4038         assert(line->type == LINE_COMMIT);
4040         list = get_ref_list(commit_id);
4041         if (!list) {
4042                 if (view == VIEW(REQ_VIEW_DIFF))
4043                         goto try_add_describe_ref;
4044                 return;
4045         }
4047         for (i = 0; i < list->size; i++) {
4048                 struct ref *ref = list->refs[i];
4049                 const char *fmt = ref->tag    ? "%s[%s]" :
4050                                   ref->remote ? "%s<%s>" : "%s%s";
4052                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
4053                         return;
4054                 sep = ", ";
4055                 if (ref->tag)
4056                         is_tag = TRUE;
4057         }
4059         if (!is_tag && view == VIEW(REQ_VIEW_DIFF)) {
4060 try_add_describe_ref:
4061                 /* Add <tag>-g<commit_id> "fake" reference. */
4062                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
4063                         return;
4064         }
4066         if (bufpos == 0)
4067                 return;
4069         add_line_text(view, buf, LINE_PP_REFS);
4072 static bool
4073 pager_read(struct view *view, char *data)
4075         struct line *line;
4077         if (!data)
4078                 return TRUE;
4080         line = add_line_text(view, data, get_line_type(data));
4081         if (!line)
4082                 return FALSE;
4084         if (line->type == LINE_COMMIT &&
4085             (view == VIEW(REQ_VIEW_DIFF) ||
4086              view == VIEW(REQ_VIEW_LOG)))
4087                 add_pager_refs(view, line);
4089         return TRUE;
4092 static enum request
4093 pager_request(struct view *view, enum request request, struct line *line)
4095         int split = 0;
4097         if (request != REQ_ENTER)
4098                 return request;
4100         if (line->type == LINE_COMMIT &&
4101            (view == VIEW(REQ_VIEW_LOG) ||
4102             view == VIEW(REQ_VIEW_PAGER))) {
4103                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
4104                 split = 1;
4105         }
4107         /* Always scroll the view even if it was split. That way
4108          * you can use Enter to scroll through the log view and
4109          * split open each commit diff. */
4110         scroll_view(view, REQ_SCROLL_LINE_DOWN);
4112         /* FIXME: A minor workaround. Scrolling the view will call report("")
4113          * but if we are scrolling a non-current view this won't properly
4114          * update the view title. */
4115         if (split)
4116                 update_view_title(view);
4118         return REQ_NONE;
4121 static bool
4122 pager_grep(struct view *view, struct line *line)
4124         const char *text[] = { line->data, NULL };
4126         return grep_text(view, text);
4129 static void
4130 pager_select(struct view *view, struct line *line)
4132         if (line->type == LINE_COMMIT) {
4133                 char *text = (char *)line->data + STRING_SIZE("commit ");
4135                 if (view != VIEW(REQ_VIEW_PAGER))
4136                         string_copy_rev(view->ref, text);
4137                 string_copy_rev(ref_commit, text);
4138         }
4141 static struct view_ops pager_ops = {
4142         "line",
4143         NULL,
4144         NULL,
4145         pager_read,
4146         pager_draw,
4147         pager_request,
4148         pager_grep,
4149         pager_select,
4150 };
4152 static const char *log_argv[SIZEOF_ARG] = {
4153         "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
4154 };
4156 static enum request
4157 log_request(struct view *view, enum request request, struct line *line)
4159         switch (request) {
4160         case REQ_REFRESH:
4161                 load_refs();
4162                 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
4163                 return REQ_NONE;
4164         default:
4165                 return pager_request(view, request, line);
4166         }
4169 static struct view_ops log_ops = {
4170         "line",
4171         log_argv,
4172         NULL,
4173         pager_read,
4174         pager_draw,
4175         log_request,
4176         pager_grep,
4177         pager_select,
4178 };
4180 static const char *diff_argv[SIZEOF_ARG] = {
4181         "git", "show", "--pretty=fuller", "--no-color", "--root",
4182                 "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL
4183 };
4185 static struct view_ops diff_ops = {
4186         "line",
4187         diff_argv,
4188         NULL,
4189         pager_read,
4190         pager_draw,
4191         pager_request,
4192         pager_grep,
4193         pager_select,
4194 };
4196 /*
4197  * Help backend
4198  */
4200 static bool help_keymap_hidden[ARRAY_SIZE(keymap_table)];
4202 static bool
4203 help_open_keymap_title(struct view *view, enum keymap keymap)
4205         struct line *line;
4207         line = add_line_format(view, LINE_HELP_KEYMAP, "[%c] %s bindings",
4208                                help_keymap_hidden[keymap] ? '+' : '-',
4209                                enum_name(keymap_table[keymap]));
4210         if (line)
4211                 line->other = keymap;
4213         return help_keymap_hidden[keymap];
4216 static void
4217 help_open_keymap(struct view *view, enum keymap keymap)
4219         const char *group = NULL;
4220         char buf[SIZEOF_STR];
4221         size_t bufpos;
4222         bool add_title = TRUE;
4223         int i;
4225         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
4226                 const char *key = NULL;
4228                 if (req_info[i].request == REQ_NONE)
4229                         continue;
4231                 if (!req_info[i].request) {
4232                         group = req_info[i].help;
4233                         continue;
4234                 }
4236                 key = get_keys(keymap, req_info[i].request, TRUE);
4237                 if (!key || !*key)
4238                         continue;
4240                 if (add_title && help_open_keymap_title(view, keymap))
4241                         return;
4242                 add_title = FALSE;
4244                 if (group) {
4245                         add_line_text(view, group, LINE_HELP_GROUP);
4246                         group = NULL;
4247                 }
4249                 add_line_format(view, LINE_DEFAULT, "    %-25s %-20s %s", key,
4250                                 enum_name(req_info[i]), req_info[i].help);
4251         }
4253         group = "External commands:";
4255         for (i = 0; i < run_requests; i++) {
4256                 struct run_request *req = get_run_request(REQ_NONE + i + 1);
4257                 const char *key;
4258                 int argc;
4260                 if (!req || req->keymap != keymap)
4261                         continue;
4263                 key = get_key_name(req->key);
4264                 if (!*key)
4265                         key = "(no key defined)";
4267                 if (add_title && help_open_keymap_title(view, keymap))
4268                         return;
4269                 if (group) {
4270                         add_line_text(view, group, LINE_HELP_GROUP);
4271                         group = NULL;
4272                 }
4274                 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
4275                         if (!string_format_from(buf, &bufpos, "%s%s",
4276                                                 argc ? " " : "", req->argv[argc]))
4277                                 return;
4279                 add_line_format(view, LINE_DEFAULT, "    %-25s `%s`", key, buf);
4280         }
4283 static bool
4284 help_open(struct view *view)
4286         enum keymap keymap;
4288         reset_view(view);
4289         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
4290         add_line_text(view, "", LINE_DEFAULT);
4292         for (keymap = 0; keymap < ARRAY_SIZE(keymap_table); keymap++)
4293                 help_open_keymap(view, keymap);
4295         return TRUE;
4298 static enum request
4299 help_request(struct view *view, enum request request, struct line *line)
4301         switch (request) {
4302         case REQ_ENTER:
4303                 if (line->type == LINE_HELP_KEYMAP) {
4304                         help_keymap_hidden[line->other] =
4305                                 !help_keymap_hidden[line->other];
4306                         view->p_restore = TRUE;
4307                         open_view(view, REQ_VIEW_HELP, OPEN_REFRESH);
4308                 }
4310                 return REQ_NONE;
4311         default:
4312                 return pager_request(view, request, line);
4313         }
4316 static struct view_ops help_ops = {
4317         "line",
4318         NULL,
4319         help_open,
4320         NULL,
4321         pager_draw,
4322         help_request,
4323         pager_grep,
4324         pager_select,
4325 };
4328 /*
4329  * Tree backend
4330  */
4332 struct tree_stack_entry {
4333         struct tree_stack_entry *prev;  /* Entry below this in the stack */
4334         unsigned long lineno;           /* Line number to restore */
4335         char *name;                     /* Position of name in opt_path */
4336 };
4338 /* The top of the path stack. */
4339 static struct tree_stack_entry *tree_stack = NULL;
4340 unsigned long tree_lineno = 0;
4342 static void
4343 pop_tree_stack_entry(void)
4345         struct tree_stack_entry *entry = tree_stack;
4347         tree_lineno = entry->lineno;
4348         entry->name[0] = 0;
4349         tree_stack = entry->prev;
4350         free(entry);
4353 static void
4354 push_tree_stack_entry(const char *name, unsigned long lineno)
4356         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
4357         size_t pathlen = strlen(opt_path);
4359         if (!entry)
4360                 return;
4362         entry->prev = tree_stack;
4363         entry->name = opt_path + pathlen;
4364         tree_stack = entry;
4366         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
4367                 pop_tree_stack_entry();
4368                 return;
4369         }
4371         /* Move the current line to the first tree entry. */
4372         tree_lineno = 1;
4373         entry->lineno = lineno;
4376 /* Parse output from git-ls-tree(1):
4377  *
4378  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4379  */
4381 #define SIZEOF_TREE_ATTR \
4382         STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4384 #define SIZEOF_TREE_MODE \
4385         STRING_SIZE("100644 ")
4387 #define TREE_ID_OFFSET \
4388         STRING_SIZE("100644 blob ")
4390 struct tree_entry {
4391         char id[SIZEOF_REV];
4392         mode_t mode;
4393         struct time time;               /* Date from the author ident. */
4394         const char *author;             /* Author of the commit. */
4395         char name[1];
4396 };
4398 static const char *
4399 tree_path(const struct line *line)
4401         return ((struct tree_entry *) line->data)->name;
4404 static int
4405 tree_compare_entry(const struct line *line1, const struct line *line2)
4407         if (line1->type != line2->type)
4408                 return line1->type == LINE_TREE_DIR ? -1 : 1;
4409         return strcmp(tree_path(line1), tree_path(line2));
4412 static const enum sort_field tree_sort_fields[] = {
4413         ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4414 };
4415 static struct sort_state tree_sort_state = SORT_STATE(tree_sort_fields);
4417 static int
4418 tree_compare(const void *l1, const void *l2)
4420         const struct line *line1 = (const struct line *) l1;
4421         const struct line *line2 = (const struct line *) l2;
4422         const struct tree_entry *entry1 = ((const struct line *) l1)->data;
4423         const struct tree_entry *entry2 = ((const struct line *) l2)->data;
4425         if (line1->type == LINE_TREE_HEAD)
4426                 return -1;
4427         if (line2->type == LINE_TREE_HEAD)
4428                 return 1;
4430         switch (get_sort_field(tree_sort_state)) {
4431         case ORDERBY_DATE:
4432                 return sort_order(tree_sort_state, timecmp(&entry1->time, &entry2->time));
4434         case ORDERBY_AUTHOR:
4435                 return sort_order(tree_sort_state, strcmp(entry1->author, entry2->author));
4437         case ORDERBY_NAME:
4438         default:
4439                 return sort_order(tree_sort_state, tree_compare_entry(line1, line2));
4440         }
4444 static struct line *
4445 tree_entry(struct view *view, enum line_type type, const char *path,
4446            const char *mode, const char *id)
4448         struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
4449         struct line *line = entry ? add_line_data(view, entry, type) : NULL;
4451         if (!entry || !line) {
4452                 free(entry);
4453                 return NULL;
4454         }
4456         strncpy(entry->name, path, strlen(path));
4457         if (mode)
4458                 entry->mode = strtoul(mode, NULL, 8);
4459         if (id)
4460                 string_copy_rev(entry->id, id);
4462         return line;
4465 static bool
4466 tree_read_date(struct view *view, char *text, bool *read_date)
4468         static const char *author_name;
4469         static struct time author_time;
4471         if (!text && *read_date) {
4472                 *read_date = FALSE;
4473                 return TRUE;
4475         } else if (!text) {
4476                 char *path = *opt_path ? opt_path : ".";
4477                 /* Find next entry to process */
4478                 const char *log_file[] = {
4479                         "git", "log", "--no-color", "--pretty=raw",
4480                                 "--cc", "--raw", view->id, "--", path, NULL
4481                 };
4482                 struct io io = {};
4484                 if (!view->lines) {
4485                         tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
4486                         report("Tree is empty");
4487                         return TRUE;
4488                 }
4490                 if (!io_run_rd(&io, log_file, opt_cdup, FORMAT_NONE)) {
4491                         report("Failed to load tree data");
4492                         return TRUE;
4493                 }
4495                 io_done(view->pipe);
4496                 view->io = io;
4497                 *read_date = TRUE;
4498                 return FALSE;
4500         } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
4501                 parse_author_line(text + STRING_SIZE("author "),
4502                                   &author_name, &author_time);
4504         } else if (*text == ':') {
4505                 char *pos;
4506                 size_t annotated = 1;
4507                 size_t i;
4509                 pos = strchr(text, '\t');
4510                 if (!pos)
4511                         return TRUE;
4512                 text = pos + 1;
4513                 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
4514                         text += strlen(opt_path);
4515                 pos = strchr(text, '/');
4516                 if (pos)
4517                         *pos = 0;
4519                 for (i = 1; i < view->lines; i++) {
4520                         struct line *line = &view->line[i];
4521                         struct tree_entry *entry = line->data;
4523                         annotated += !!entry->author;
4524                         if (entry->author || strcmp(entry->name, text))
4525                                 continue;
4527                         entry->author = author_name;
4528                         entry->time = author_time;
4529                         line->dirty = 1;
4530                         break;
4531                 }
4533                 if (annotated == view->lines)
4534                         io_kill(view->pipe);
4535         }
4536         return TRUE;
4539 static bool
4540 tree_read(struct view *view, char *text)
4542         static bool read_date = FALSE;
4543         struct tree_entry *data;
4544         struct line *entry, *line;
4545         enum line_type type;
4546         size_t textlen = text ? strlen(text) : 0;
4547         char *path = text + SIZEOF_TREE_ATTR;
4549         if (read_date || !text)
4550                 return tree_read_date(view, text, &read_date);
4552         if (textlen <= SIZEOF_TREE_ATTR)
4553                 return FALSE;
4554         if (view->lines == 0 &&
4555             !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
4556                 return FALSE;
4558         /* Strip the path part ... */
4559         if (*opt_path) {
4560                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
4561                 size_t striplen = strlen(opt_path);
4563                 if (pathlen > striplen)
4564                         memmove(path, path + striplen,
4565                                 pathlen - striplen + 1);
4567                 /* Insert "link" to parent directory. */
4568                 if (view->lines == 1 &&
4569                     !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
4570                         return FALSE;
4571         }
4573         type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
4574         entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
4575         if (!entry)
4576                 return FALSE;
4577         data = entry->data;
4579         /* Skip "Directory ..." and ".." line. */
4580         for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
4581                 if (tree_compare_entry(line, entry) <= 0)
4582                         continue;
4584                 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4586                 line->data = data;
4587                 line->type = type;
4588                 for (; line <= entry; line++)
4589                         line->dirty = line->cleareol = 1;
4590                 return TRUE;
4591         }
4593         if (tree_lineno > view->lineno) {
4594                 view->lineno = tree_lineno;
4595                 tree_lineno = 0;
4596         }
4598         return TRUE;
4601 static bool
4602 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4604         struct tree_entry *entry = line->data;
4606         if (line->type == LINE_TREE_HEAD) {
4607                 if (draw_text(view, line->type, "Directory path /", TRUE))
4608                         return TRUE;
4609         } else {
4610                 if (draw_mode(view, entry->mode))
4611                         return TRUE;
4613                 if (opt_author && draw_author(view, entry->author))
4614                         return TRUE;
4616                 if (opt_date && draw_date(view, &entry->time))
4617                         return TRUE;
4618         }
4619         if (draw_text(view, line->type, entry->name, TRUE))
4620                 return TRUE;
4621         return TRUE;
4624 static void
4625 open_blob_editor()
4627         char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4628         int fd = mkstemp(file);
4630         if (fd == -1)
4631                 report("Failed to create temporary file");
4632         else if (!io_run_append(blob_ops.argv, FORMAT_ALL, fd))
4633                 report("Failed to save blob data to file");
4634         else
4635                 open_editor(file);
4636         if (fd != -1)
4637                 unlink(file);
4640 static enum request
4641 tree_request(struct view *view, enum request request, struct line *line)
4643         enum open_flags flags;
4645         switch (request) {
4646         case REQ_VIEW_BLAME:
4647                 if (line->type != LINE_TREE_FILE) {
4648                         report("Blame only supported for files");
4649                         return REQ_NONE;
4650                 }
4652                 string_copy(opt_ref, view->vid);
4653                 return request;
4655         case REQ_EDIT:
4656                 if (line->type != LINE_TREE_FILE) {
4657                         report("Edit only supported for files");
4658                 } else if (!is_head_commit(view->vid)) {
4659                         open_blob_editor();
4660                 } else {
4661                         open_editor(opt_file);
4662                 }
4663                 return REQ_NONE;
4665         case REQ_TOGGLE_SORT_FIELD:
4666         case REQ_TOGGLE_SORT_ORDER:
4667                 sort_view(view, request, &tree_sort_state, tree_compare);
4668                 return REQ_NONE;
4670         case REQ_PARENT:
4671                 if (!*opt_path) {
4672                         /* quit view if at top of tree */
4673                         return REQ_VIEW_CLOSE;
4674                 }
4675                 /* fake 'cd  ..' */
4676                 line = &view->line[1];
4677                 break;
4679         case REQ_ENTER:
4680                 break;
4682         default:
4683                 return request;
4684         }
4686         /* Cleanup the stack if the tree view is at a different tree. */
4687         while (!*opt_path && tree_stack)
4688                 pop_tree_stack_entry();
4690         switch (line->type) {
4691         case LINE_TREE_DIR:
4692                 /* Depending on whether it is a subdirectory or parent link
4693                  * mangle the path buffer. */
4694                 if (line == &view->line[1] && *opt_path) {
4695                         pop_tree_stack_entry();
4697                 } else {
4698                         const char *basename = tree_path(line);
4700                         push_tree_stack_entry(basename, view->lineno);
4701                 }
4703                 /* Trees and subtrees share the same ID, so they are not not
4704                  * unique like blobs. */
4705                 flags = OPEN_RELOAD;
4706                 request = REQ_VIEW_TREE;
4707                 break;
4709         case LINE_TREE_FILE:
4710                 flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
4711                 request = REQ_VIEW_BLOB;
4712                 break;
4714         default:
4715                 return REQ_NONE;
4716         }
4718         open_view(view, request, flags);
4719         if (request == REQ_VIEW_TREE)
4720                 view->lineno = tree_lineno;
4722         return REQ_NONE;
4725 static bool
4726 tree_grep(struct view *view, struct line *line)
4728         struct tree_entry *entry = line->data;
4729         const char *text[] = {
4730                 entry->name,
4731                 opt_author ? entry->author : "",
4732                 mkdate(&entry->time, opt_date),
4733                 NULL
4734         };
4736         return grep_text(view, text);
4739 static void
4740 tree_select(struct view *view, struct line *line)
4742         struct tree_entry *entry = line->data;
4744         if (line->type == LINE_TREE_FILE) {
4745                 string_copy_rev(ref_blob, entry->id);
4746                 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4748         } else if (line->type != LINE_TREE_DIR) {
4749                 return;
4750         }
4752         string_copy_rev(view->ref, entry->id);
4755 static bool
4756 tree_prepare(struct view *view)
4758         if (view->lines == 0 && opt_prefix[0]) {
4759                 char *pos = opt_prefix;
4761                 while (pos && *pos) {
4762                         char *end = strchr(pos, '/');
4764                         if (end)
4765                                 *end = 0;
4766                         push_tree_stack_entry(pos, 0);
4767                         pos = end;
4768                         if (end) {
4769                                 *end = '/';
4770                                 pos++;
4771                         }
4772                 }
4774         } else if (strcmp(view->vid, view->id)) {
4775                 opt_path[0] = 0;
4776         }
4778         return io_format(&view->io, opt_cdup, IO_RD, view->ops->argv, FORMAT_ALL);
4781 static const char *tree_argv[SIZEOF_ARG] = {
4782         "git", "ls-tree", "%(commit)", "%(directory)", NULL
4783 };
4785 static struct view_ops tree_ops = {
4786         "file",
4787         tree_argv,
4788         NULL,
4789         tree_read,
4790         tree_draw,
4791         tree_request,
4792         tree_grep,
4793         tree_select,
4794         tree_prepare,
4795 };
4797 static bool
4798 blob_read(struct view *view, char *line)
4800         if (!line)
4801                 return TRUE;
4802         return add_line_text(view, line, LINE_DEFAULT) != NULL;
4805 static enum request
4806 blob_request(struct view *view, enum request request, struct line *line)
4808         switch (request) {
4809         case REQ_EDIT:
4810                 open_blob_editor();
4811                 return REQ_NONE;
4812         default:
4813                 return pager_request(view, request, line);
4814         }
4817 static const char *blob_argv[SIZEOF_ARG] = {
4818         "git", "cat-file", "blob", "%(blob)", NULL
4819 };
4821 static struct view_ops blob_ops = {
4822         "line",
4823         blob_argv,
4824         NULL,
4825         blob_read,
4826         pager_draw,
4827         blob_request,
4828         pager_grep,
4829         pager_select,
4830 };
4832 /*
4833  * Blame backend
4834  *
4835  * Loading the blame view is a two phase job:
4836  *
4837  *  1. File content is read either using opt_file from the
4838  *     filesystem or using git-cat-file.
4839  *  2. Then blame information is incrementally added by
4840  *     reading output from git-blame.
4841  */
4843 static const char *blame_head_argv[] = {
4844         "git", "blame", "--incremental", "--", "%(file)", NULL
4845 };
4847 static const char *blame_ref_argv[] = {
4848         "git", "blame", "--incremental", "%(ref)", "--", "%(file)", NULL
4849 };
4851 static const char *blame_cat_file_argv[] = {
4852         "git", "cat-file", "blob", "%(ref):%(file)", NULL
4853 };
4855 struct blame_commit {
4856         char id[SIZEOF_REV];            /* SHA1 ID. */
4857         char title[128];                /* First line of the commit message. */
4858         const char *author;             /* Author of the commit. */
4859         struct time time;               /* Date from the author ident. */
4860         char filename[128];             /* Name of file. */
4861         bool has_previous;              /* Was a "previous" line detected. */
4862 };
4864 struct blame {
4865         struct blame_commit *commit;
4866         unsigned long lineno;
4867         char text[1];
4868 };
4870 static bool
4871 blame_open(struct view *view)
4873         char path[SIZEOF_STR];
4875         if (!view->parent && *opt_prefix) {
4876                 string_copy(path, opt_file);
4877                 if (!string_format(opt_file, "%s%s", opt_prefix, path))
4878                         return FALSE;
4879         }
4881         if (*opt_ref || !io_open(&view->io, "%s%s", opt_cdup, opt_file)) {
4882                 if (!io_run_rd(&view->io, blame_cat_file_argv, opt_cdup, FORMAT_ALL))
4883                         return FALSE;
4884         }
4886         setup_update(view, opt_file);
4887         string_format(view->ref, "%s ...", opt_file);
4889         return TRUE;
4892 static struct blame_commit *
4893 get_blame_commit(struct view *view, const char *id)
4895         size_t i;
4897         for (i = 0; i < view->lines; i++) {
4898                 struct blame *blame = view->line[i].data;
4900                 if (!blame->commit)
4901                         continue;
4903                 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4904                         return blame->commit;
4905         }
4907         {
4908                 struct blame_commit *commit = calloc(1, sizeof(*commit));
4910                 if (commit)
4911                         string_ncopy(commit->id, id, SIZEOF_REV);
4912                 return commit;
4913         }
4916 static bool
4917 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4919         const char *pos = *posref;
4921         *posref = NULL;
4922         pos = strchr(pos + 1, ' ');
4923         if (!pos || !isdigit(pos[1]))
4924                 return FALSE;
4925         *number = atoi(pos + 1);
4926         if (*number < min || *number > max)
4927                 return FALSE;
4929         *posref = pos;
4930         return TRUE;
4933 static struct blame_commit *
4934 parse_blame_commit(struct view *view, const char *text, int *blamed)
4936         struct blame_commit *commit;
4937         struct blame *blame;
4938         const char *pos = text + SIZEOF_REV - 2;
4939         size_t orig_lineno = 0;
4940         size_t lineno;
4941         size_t group;
4943         if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
4944                 return NULL;
4946         if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
4947             !parse_number(&pos, &lineno, 1, view->lines) ||
4948             !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4949                 return NULL;
4951         commit = get_blame_commit(view, text);
4952         if (!commit)
4953                 return NULL;
4955         *blamed += group;
4956         while (group--) {
4957                 struct line *line = &view->line[lineno + group - 1];
4959                 blame = line->data;
4960                 blame->commit = commit;
4961                 blame->lineno = orig_lineno + group - 1;
4962                 line->dirty = 1;
4963         }
4965         return commit;
4968 static bool
4969 blame_read_file(struct view *view, const char *line, bool *read_file)
4971         if (!line) {
4972                 const char **argv = *opt_ref ? blame_ref_argv : blame_head_argv;
4973                 struct io io = {};
4975                 if (view->lines == 0 && !view->parent)
4976                         die("No blame exist for %s", view->vid);
4978                 if (view->lines == 0 || !io_run_rd(&io, argv, opt_cdup, FORMAT_ALL)) {
4979                         report("Failed to load blame data");
4980                         return TRUE;
4981                 }
4983                 io_done(view->pipe);
4984                 view->io = io;
4985                 *read_file = FALSE;
4986                 return FALSE;
4988         } else {
4989                 size_t linelen = strlen(line);
4990                 struct blame *blame = malloc(sizeof(*blame) + linelen);
4992                 if (!blame)
4993                         return FALSE;
4995                 blame->commit = NULL;
4996                 strncpy(blame->text, line, linelen);
4997                 blame->text[linelen] = 0;
4998                 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
4999         }
5002 static bool
5003 match_blame_header(const char *name, char **line)
5005         size_t namelen = strlen(name);
5006         bool matched = !strncmp(name, *line, namelen);
5008         if (matched)
5009                 *line += namelen;
5011         return matched;
5014 static bool
5015 blame_read(struct view *view, char *line)
5017         static struct blame_commit *commit = NULL;
5018         static int blamed = 0;
5019         static bool read_file = TRUE;
5021         if (read_file)
5022                 return blame_read_file(view, line, &read_file);
5024         if (!line) {
5025                 /* Reset all! */
5026                 commit = NULL;
5027                 blamed = 0;
5028                 read_file = TRUE;
5029                 string_format(view->ref, "%s", view->vid);
5030                 if (view_is_displayed(view)) {
5031                         update_view_title(view);
5032                         redraw_view_from(view, 0);
5033                 }
5034                 return TRUE;
5035         }
5037         if (!commit) {
5038                 commit = parse_blame_commit(view, line, &blamed);
5039                 string_format(view->ref, "%s %2d%%", view->vid,
5040                               view->lines ? blamed * 100 / view->lines : 0);
5042         } else if (match_blame_header("author ", &line)) {
5043                 commit->author = get_author(line);
5045         } else if (match_blame_header("author-time ", &line)) {
5046                 parse_timesec(&commit->time, line);
5048         } else if (match_blame_header("author-tz ", &line)) {
5049                 parse_timezone(&commit->time, line);
5051         } else if (match_blame_header("summary ", &line)) {
5052                 string_ncopy(commit->title, line, strlen(line));
5054         } else if (match_blame_header("previous ", &line)) {
5055                 commit->has_previous = TRUE;
5057         } else if (match_blame_header("filename ", &line)) {
5058                 string_ncopy(commit->filename, line, strlen(line));
5059                 commit = NULL;
5060         }
5062         return TRUE;
5065 static bool
5066 blame_draw(struct view *view, struct line *line, unsigned int lineno)
5068         struct blame *blame = line->data;
5069         struct time *time = NULL;
5070         const char *id = NULL, *author = NULL;
5071         char text[SIZEOF_STR];
5073         if (blame->commit && *blame->commit->filename) {
5074                 id = blame->commit->id;
5075                 author = blame->commit->author;
5076                 time = &blame->commit->time;
5077         }
5079         if (opt_date && draw_date(view, time))
5080                 return TRUE;
5082         if (opt_author && draw_author(view, author))
5083                 return TRUE;
5085         if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
5086                 return TRUE;
5088         if (draw_lineno(view, lineno))
5089                 return TRUE;
5091         string_expand(text, sizeof(text), blame->text, opt_tab_size);
5092         draw_text(view, LINE_DEFAULT, text, TRUE);
5093         return TRUE;
5096 static bool
5097 check_blame_commit(struct blame *blame, bool check_null_id)
5099         if (!blame->commit)
5100                 report("Commit data not loaded yet");
5101         else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
5102                 report("No commit exist for the selected line");
5103         else
5104                 return TRUE;
5105         return FALSE;
5108 static void
5109 setup_blame_parent_line(struct view *view, struct blame *blame)
5111         const char *diff_tree_argv[] = {
5112                 "git", "diff-tree", "-U0", blame->commit->id,
5113                         "--", blame->commit->filename, NULL
5114         };
5115         struct io io = {};
5116         int parent_lineno = -1;
5117         int blamed_lineno = -1;
5118         char *line;
5120         if (!io_run(&io, diff_tree_argv, NULL, IO_RD))
5121                 return;
5123         while ((line = io_get(&io, '\n', TRUE))) {
5124                 if (*line == '@') {
5125                         char *pos = strchr(line, '+');
5127                         parent_lineno = atoi(line + 4);
5128                         if (pos)
5129                                 blamed_lineno = atoi(pos + 1);
5131                 } else if (*line == '+' && parent_lineno != -1) {
5132                         if (blame->lineno == blamed_lineno - 1 &&
5133                             !strcmp(blame->text, line + 1)) {
5134                                 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
5135                                 break;
5136                         }
5137                         blamed_lineno++;
5138                 }
5139         }
5141         io_done(&io);
5144 static enum request
5145 blame_request(struct view *view, enum request request, struct line *line)
5147         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
5148         struct blame *blame = line->data;
5150         switch (request) {
5151         case REQ_VIEW_BLAME:
5152                 if (check_blame_commit(blame, TRUE)) {
5153                         string_copy(opt_ref, blame->commit->id);
5154                         string_copy(opt_file, blame->commit->filename);
5155                         if (blame->lineno)
5156                                 view->lineno = blame->lineno;
5157                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5158                 }
5159                 break;
5161         case REQ_PARENT:
5162                 if (check_blame_commit(blame, TRUE) &&
5163                     select_commit_parent(blame->commit->id, opt_ref,
5164                                          blame->commit->filename)) {
5165                         string_copy(opt_file, blame->commit->filename);
5166                         setup_blame_parent_line(view, blame);
5167                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5168                 }
5169                 break;
5171         case REQ_ENTER:
5172                 if (!check_blame_commit(blame, FALSE))
5173                         break;
5175                 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
5176                     !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
5177                         break;
5179                 if (!strcmp(blame->commit->id, NULL_ID)) {
5180                         struct view *diff = VIEW(REQ_VIEW_DIFF);
5181                         const char *diff_index_argv[] = {
5182                                 "git", "diff-index", "--root", "--patch-with-stat",
5183                                         "-C", "-M", "HEAD", "--", view->vid, NULL
5184                         };
5186                         if (!blame->commit->has_previous) {
5187                                 diff_index_argv[1] = "diff";
5188                                 diff_index_argv[2] = "--no-color";
5189                                 diff_index_argv[6] = "--";
5190                                 diff_index_argv[7] = "/dev/null";
5191                         }
5193                         if (!prepare_update(diff, diff_index_argv, NULL)) {
5194                                 report("Failed to allocate diff command");
5195                                 break;
5196                         }
5197                         flags |= OPEN_PREPARED;
5198                 }
5200                 open_view(view, REQ_VIEW_DIFF, flags);
5201                 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
5202                         string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
5203                 break;
5205         default:
5206                 return request;
5207         }
5209         return REQ_NONE;
5212 static bool
5213 blame_grep(struct view *view, struct line *line)
5215         struct blame *blame = line->data;
5216         struct blame_commit *commit = blame->commit;
5217         const char *text[] = {
5218                 blame->text,
5219                 commit ? commit->title : "",
5220                 commit ? commit->id : "",
5221                 commit && opt_author ? commit->author : "",
5222                 commit ? mkdate(&commit->time, opt_date) : "",
5223                 NULL
5224         };
5226         return grep_text(view, text);
5229 static void
5230 blame_select(struct view *view, struct line *line)
5232         struct blame *blame = line->data;
5233         struct blame_commit *commit = blame->commit;
5235         if (!commit)
5236                 return;
5238         if (!strcmp(commit->id, NULL_ID))
5239                 string_ncopy(ref_commit, "HEAD", 4);
5240         else
5241                 string_copy_rev(ref_commit, commit->id);
5244 static struct view_ops blame_ops = {
5245         "line",
5246         NULL,
5247         blame_open,
5248         blame_read,
5249         blame_draw,
5250         blame_request,
5251         blame_grep,
5252         blame_select,
5253 };
5255 /*
5256  * Branch backend
5257  */
5259 struct branch {
5260         const char *author;             /* Author of the last commit. */
5261         struct time time;               /* Date of the last activity. */
5262         const struct ref *ref;          /* Name and commit ID information. */
5263 };
5265 static const struct ref branch_all;
5267 static const enum sort_field branch_sort_fields[] = {
5268         ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
5269 };
5270 static struct sort_state branch_sort_state = SORT_STATE(branch_sort_fields);
5272 static int
5273 branch_compare(const void *l1, const void *l2)
5275         const struct branch *branch1 = ((const struct line *) l1)->data;
5276         const struct branch *branch2 = ((const struct line *) l2)->data;
5278         switch (get_sort_field(branch_sort_state)) {
5279         case ORDERBY_DATE:
5280                 return sort_order(branch_sort_state, timecmp(&branch1->time, &branch2->time));
5282         case ORDERBY_AUTHOR:
5283                 return sort_order(branch_sort_state, strcmp(branch1->author, branch2->author));
5285         case ORDERBY_NAME:
5286         default:
5287                 return sort_order(branch_sort_state, strcmp(branch1->ref->name, branch2->ref->name));
5288         }
5291 static bool
5292 branch_draw(struct view *view, struct line *line, unsigned int lineno)
5294         struct branch *branch = line->data;
5295         enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
5297         if (opt_date && draw_date(view, &branch->time))
5298                 return TRUE;
5300         if (opt_author && draw_author(view, branch->author))
5301                 return TRUE;
5303         draw_text(view, type, branch->ref == &branch_all ? "All branches" : branch->ref->name, TRUE);
5304         return TRUE;
5307 static enum request
5308 branch_request(struct view *view, enum request request, struct line *line)
5310         struct branch *branch = line->data;
5312         switch (request) {
5313         case REQ_REFRESH:
5314                 load_refs();
5315                 open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
5316                 return REQ_NONE;
5318         case REQ_TOGGLE_SORT_FIELD:
5319         case REQ_TOGGLE_SORT_ORDER:
5320                 sort_view(view, request, &branch_sort_state, branch_compare);
5321                 return REQ_NONE;
5323         case REQ_ENTER:
5324                 if (branch->ref == &branch_all) {
5325                         const char *all_branches_argv[] = {
5326                                 "git", "log", "--no-color", "--pretty=raw", "--parents",
5327                                       "--topo-order", "--all", NULL
5328                         };
5329                         struct view *main_view = VIEW(REQ_VIEW_MAIN);
5331                         if (!prepare_update(main_view, all_branches_argv, NULL)) {
5332                                 report("Failed to load view of all branches");
5333                                 return REQ_NONE;
5334                         }
5335                         open_view(view, REQ_VIEW_MAIN, OPEN_PREPARED | OPEN_SPLIT);
5336                 } else {
5337                         open_view(view, REQ_VIEW_MAIN, OPEN_SPLIT);
5338                 }
5339                 return REQ_NONE;
5341         default:
5342                 return request;
5343         }
5346 static bool
5347 branch_read(struct view *view, char *line)
5349         static char id[SIZEOF_REV];
5350         struct branch *reference;
5351         size_t i;
5353         if (!line)
5354                 return TRUE;
5356         switch (get_line_type(line)) {
5357         case LINE_COMMIT:
5358                 string_copy_rev(id, line + STRING_SIZE("commit "));
5359                 return TRUE;
5361         case LINE_AUTHOR:
5362                 for (i = 0, reference = NULL; i < view->lines; i++) {
5363                         struct branch *branch = view->line[i].data;
5365                         if (strcmp(branch->ref->id, id))
5366                                 continue;
5368                         view->line[i].dirty = TRUE;
5369                         if (reference) {
5370                                 branch->author = reference->author;
5371                                 branch->time = reference->time;
5372                                 continue;
5373                         }
5375                         parse_author_line(line + STRING_SIZE("author "),
5376                                           &branch->author, &branch->time);
5377                         reference = branch;
5378                 }
5379                 return TRUE;
5381         default:
5382                 return TRUE;
5383         }
5387 static bool
5388 branch_open_visitor(void *data, const struct ref *ref)
5390         struct view *view = data;
5391         struct branch *branch;
5393         if (ref->tag || ref->ltag || ref->remote)
5394                 return TRUE;
5396         branch = calloc(1, sizeof(*branch));
5397         if (!branch)
5398                 return FALSE;
5400         branch->ref = ref;
5401         return !!add_line_data(view, branch, LINE_DEFAULT);
5404 static bool
5405 branch_open(struct view *view)
5407         const char *branch_log[] = {
5408                 "git", "log", "--no-color", "--pretty=raw",
5409                         "--simplify-by-decoration", "--all", NULL
5410         };
5412         if (!io_run_rd(&view->io, branch_log, NULL, FORMAT_NONE)) {
5413                 report("Failed to load branch data");
5414                 return TRUE;
5415         }
5417         setup_update(view, view->id);
5418         branch_open_visitor(view, &branch_all);
5419         foreach_ref(branch_open_visitor, view);
5420         view->p_restore = TRUE;
5422         return TRUE;
5425 static bool
5426 branch_grep(struct view *view, struct line *line)
5428         struct branch *branch = line->data;
5429         const char *text[] = {
5430                 branch->ref->name,
5431                 branch->author,
5432                 NULL
5433         };
5435         return grep_text(view, text);
5438 static void
5439 branch_select(struct view *view, struct line *line)
5441         struct branch *branch = line->data;
5443         string_copy_rev(view->ref, branch->ref->id);
5444         string_copy_rev(ref_commit, branch->ref->id);
5445         string_copy_rev(ref_head, branch->ref->id);
5446         string_copy_rev(ref_branch, branch->ref->name);
5449 static struct view_ops branch_ops = {
5450         "branch",
5451         NULL,
5452         branch_open,
5453         branch_read,
5454         branch_draw,
5455         branch_request,
5456         branch_grep,
5457         branch_select,
5458 };
5460 /*
5461  * Status backend
5462  */
5464 struct status {
5465         char status;
5466         struct {
5467                 mode_t mode;
5468                 char rev[SIZEOF_REV];
5469                 char name[SIZEOF_STR];
5470         } old;
5471         struct {
5472                 mode_t mode;
5473                 char rev[SIZEOF_REV];
5474                 char name[SIZEOF_STR];
5475         } new;
5476 };
5478 static char status_onbranch[SIZEOF_STR];
5479 static struct status stage_status;
5480 static enum line_type stage_line_type;
5481 static size_t stage_chunks;
5482 static int *stage_chunk;
5484 DEFINE_ALLOCATOR(realloc_ints, int, 32)
5486 /* This should work even for the "On branch" line. */
5487 static inline bool
5488 status_has_none(struct view *view, struct line *line)
5490         return line < view->line + view->lines && !line[1].data;
5493 /* Get fields from the diff line:
5494  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5495  */
5496 static inline bool
5497 status_get_diff(struct status *file, const char *buf, size_t bufsize)
5499         const char *old_mode = buf +  1;
5500         const char *new_mode = buf +  8;
5501         const char *old_rev  = buf + 15;
5502         const char *new_rev  = buf + 56;
5503         const char *status   = buf + 97;
5505         if (bufsize < 98 ||
5506             old_mode[-1] != ':' ||
5507             new_mode[-1] != ' ' ||
5508             old_rev[-1]  != ' ' ||
5509             new_rev[-1]  != ' ' ||
5510             status[-1]   != ' ')
5511                 return FALSE;
5513         file->status = *status;
5515         string_copy_rev(file->old.rev, old_rev);
5516         string_copy_rev(file->new.rev, new_rev);
5518         file->old.mode = strtoul(old_mode, NULL, 8);
5519         file->new.mode = strtoul(new_mode, NULL, 8);
5521         file->old.name[0] = file->new.name[0] = 0;
5523         return TRUE;
5526 static bool
5527 status_run(struct view *view, const char *argv[], char status, enum line_type type)
5529         struct status *unmerged = NULL;
5530         char *buf;
5531         struct io io = {};
5533         if (!io_run(&io, argv, opt_cdup, IO_RD))
5534                 return FALSE;
5536         add_line_data(view, NULL, type);
5538         while ((buf = io_get(&io, 0, TRUE))) {
5539                 struct status *file = unmerged;
5541                 if (!file) {
5542                         file = calloc(1, sizeof(*file));
5543                         if (!file || !add_line_data(view, file, type))
5544                                 goto error_out;
5545                 }
5547                 /* Parse diff info part. */
5548                 if (status) {
5549                         file->status = status;
5550                         if (status == 'A')
5551                                 string_copy(file->old.rev, NULL_ID);
5553                 } else if (!file->status || file == unmerged) {
5554                         if (!status_get_diff(file, buf, strlen(buf)))
5555                                 goto error_out;
5557                         buf = io_get(&io, 0, TRUE);
5558                         if (!buf)
5559                                 break;
5561                         /* Collapse all modified entries that follow an
5562                          * associated unmerged entry. */
5563                         if (unmerged == file) {
5564                                 unmerged->status = 'U';
5565                                 unmerged = NULL;
5566                         } else if (file->status == 'U') {
5567                                 unmerged = file;
5568                         }
5569                 }
5571                 /* Grab the old name for rename/copy. */
5572                 if (!*file->old.name &&
5573                     (file->status == 'R' || file->status == 'C')) {
5574                         string_ncopy(file->old.name, buf, strlen(buf));
5576                         buf = io_get(&io, 0, TRUE);
5577                         if (!buf)
5578                                 break;
5579                 }
5581                 /* git-ls-files just delivers a NUL separated list of
5582                  * file names similar to the second half of the
5583                  * git-diff-* output. */
5584                 string_ncopy(file->new.name, buf, strlen(buf));
5585                 if (!*file->old.name)
5586                         string_copy(file->old.name, file->new.name);
5587                 file = NULL;
5588         }
5590         if (io_error(&io)) {
5591 error_out:
5592                 io_done(&io);
5593                 return FALSE;
5594         }
5596         if (!view->line[view->lines - 1].data)
5597                 add_line_data(view, NULL, LINE_STAT_NONE);
5599         io_done(&io);
5600         return TRUE;
5603 /* Don't show unmerged entries in the staged section. */
5604 static const char *status_diff_index_argv[] = {
5605         "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5606                              "--cached", "-M", "HEAD", NULL
5607 };
5609 static const char *status_diff_files_argv[] = {
5610         "git", "diff-files", "-z", NULL
5611 };
5613 static const char *status_list_other_argv[] = {
5614         "git", "ls-files", "-z", "--others", "--exclude-standard", opt_prefix, NULL
5615 };
5617 static const char *status_list_no_head_argv[] = {
5618         "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5619 };
5621 static const char *update_index_argv[] = {
5622         "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5623 };
5625 /* Restore the previous line number to stay in the context or select a
5626  * line with something that can be updated. */
5627 static void
5628 status_restore(struct view *view)
5630         if (view->p_lineno >= view->lines)
5631                 view->p_lineno = view->lines - 1;
5632         while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
5633                 view->p_lineno++;
5634         while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
5635                 view->p_lineno--;
5637         /* If the above fails, always skip the "On branch" line. */
5638         if (view->p_lineno < view->lines)
5639                 view->lineno = view->p_lineno;
5640         else
5641                 view->lineno = 1;
5643         if (view->lineno < view->offset)
5644                 view->offset = view->lineno;
5645         else if (view->offset + view->height <= view->lineno)
5646                 view->offset = view->lineno - view->height + 1;
5648         view->p_restore = FALSE;
5651 static void
5652 status_update_onbranch(void)
5654         static const char *paths[][2] = {
5655                 { "rebase-apply/rebasing",      "Rebasing" },
5656                 { "rebase-apply/applying",      "Applying mailbox" },
5657                 { "rebase-apply/",              "Rebasing mailbox" },
5658                 { "rebase-merge/interactive",   "Interactive rebase" },
5659                 { "rebase-merge/",              "Rebase merge" },
5660                 { "MERGE_HEAD",                 "Merging" },
5661                 { "BISECT_LOG",                 "Bisecting" },
5662                 { "HEAD",                       "On branch" },
5663         };
5664         char buf[SIZEOF_STR];
5665         struct stat stat;
5666         int i;
5668         if (is_initial_commit()) {
5669                 string_copy(status_onbranch, "Initial commit");
5670                 return;
5671         }
5673         for (i = 0; i < ARRAY_SIZE(paths); i++) {
5674                 char *head = opt_head;
5676                 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5677                     lstat(buf, &stat) < 0)
5678                         continue;
5680                 if (!*opt_head) {
5681                         struct io io = {};
5683                         if (io_open(&io, "%s/rebase-merge/head-name", opt_git_dir) &&
5684                             io_read_buf(&io, buf, sizeof(buf))) {
5685                                 head = buf;
5686                                 if (!prefixcmp(head, "refs/heads/"))
5687                                         head += STRING_SIZE("refs/heads/");
5688                         }
5689                 }
5691                 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5692                         string_copy(status_onbranch, opt_head);
5693                 return;
5694         }
5696         string_copy(status_onbranch, "Not currently on any branch");
5699 /* First parse staged info using git-diff-index(1), then parse unstaged
5700  * info using git-diff-files(1), and finally untracked files using
5701  * git-ls-files(1). */
5702 static bool
5703 status_open(struct view *view)
5705         reset_view(view);
5707         add_line_data(view, NULL, LINE_STAT_HEAD);
5708         status_update_onbranch();
5710         io_run_bg(update_index_argv);
5712         if (is_initial_commit()) {
5713                 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5714                         return FALSE;
5715         } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5716                 return FALSE;
5717         }
5719         if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5720             !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5721                 return FALSE;
5723         /* Restore the exact position or use the specialized restore
5724          * mode? */
5725         if (!view->p_restore)
5726                 status_restore(view);
5727         return TRUE;
5730 static bool
5731 status_draw(struct view *view, struct line *line, unsigned int lineno)
5733         struct status *status = line->data;
5734         enum line_type type;
5735         const char *text;
5737         if (!status) {
5738                 switch (line->type) {
5739                 case LINE_STAT_STAGED:
5740                         type = LINE_STAT_SECTION;
5741                         text = "Changes to be committed:";
5742                         break;
5744                 case LINE_STAT_UNSTAGED:
5745                         type = LINE_STAT_SECTION;
5746                         text = "Changed but not updated:";
5747                         break;
5749                 case LINE_STAT_UNTRACKED:
5750                         type = LINE_STAT_SECTION;
5751                         text = "Untracked files:";
5752                         break;
5754                 case LINE_STAT_NONE:
5755                         type = LINE_DEFAULT;
5756                         text = "  (no files)";
5757                         break;
5759                 case LINE_STAT_HEAD:
5760                         type = LINE_STAT_HEAD;
5761                         text = status_onbranch;
5762                         break;
5764                 default:
5765                         return FALSE;
5766                 }
5767         } else {
5768                 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5770                 buf[0] = status->status;
5771                 if (draw_text(view, line->type, buf, TRUE))
5772                         return TRUE;
5773                 type = LINE_DEFAULT;
5774                 text = status->new.name;
5775         }
5777         draw_text(view, type, text, TRUE);
5778         return TRUE;
5781 static enum request
5782 status_load_error(struct view *view, struct view *stage, const char *path)
5784         if (displayed_views() == 2 || display[current_view] != view)
5785                 maximize_view(view);
5786         report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5787         return REQ_NONE;
5790 static enum request
5791 status_enter(struct view *view, struct line *line)
5793         struct status *status = line->data;
5794         const char *oldpath = status ? status->old.name : NULL;
5795         /* Diffs for unmerged entries are empty when passing the new
5796          * path, so leave it empty. */
5797         const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5798         const char *info;
5799         enum open_flags split;
5800         struct view *stage = VIEW(REQ_VIEW_STAGE);
5802         if (line->type == LINE_STAT_NONE ||
5803             (!status && line[1].type == LINE_STAT_NONE)) {
5804                 report("No file to diff");
5805                 return REQ_NONE;
5806         }
5808         switch (line->type) {
5809         case LINE_STAT_STAGED:
5810                 if (is_initial_commit()) {
5811                         const char *no_head_diff_argv[] = {
5812                                 "git", "diff", "--no-color", "--patch-with-stat",
5813                                         "--", "/dev/null", newpath, NULL
5814                         };
5816                         if (!prepare_update(stage, no_head_diff_argv, opt_cdup))
5817                                 return status_load_error(view, stage, newpath);
5818                 } else {
5819                         const char *index_show_argv[] = {
5820                                 "git", "diff-index", "--root", "--patch-with-stat",
5821                                         "-C", "-M", "--cached", "HEAD", "--",
5822                                         oldpath, newpath, NULL
5823                         };
5825                         if (!prepare_update(stage, index_show_argv, opt_cdup))
5826                                 return status_load_error(view, stage, newpath);
5827                 }
5829                 if (status)
5830                         info = "Staged changes to %s";
5831                 else
5832                         info = "Staged changes";
5833                 break;
5835         case LINE_STAT_UNSTAGED:
5836         {
5837                 const char *files_show_argv[] = {
5838                         "git", "diff-files", "--root", "--patch-with-stat",
5839                                 "-C", "-M", "--", oldpath, newpath, NULL
5840                 };
5842                 if (!prepare_update(stage, files_show_argv, opt_cdup))
5843                         return status_load_error(view, stage, newpath);
5844                 if (status)
5845                         info = "Unstaged changes to %s";
5846                 else
5847                         info = "Unstaged changes";
5848                 break;
5849         }
5850         case LINE_STAT_UNTRACKED:
5851                 if (!newpath) {
5852                         report("No file to show");
5853                         return REQ_NONE;
5854                 }
5856                 if (!suffixcmp(status->new.name, -1, "/")) {
5857                         report("Cannot display a directory");
5858                         return REQ_NONE;
5859                 }
5861                 if (!prepare_update_file(stage, newpath))
5862                         return status_load_error(view, stage, newpath);
5863                 info = "Untracked file %s";
5864                 break;
5866         case LINE_STAT_HEAD:
5867                 return REQ_NONE;
5869         default:
5870                 die("line type %d not handled in switch", line->type);
5871         }
5873         split = view_is_displayed(view) ? OPEN_SPLIT : 0;
5874         open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5875         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5876                 if (status) {
5877                         stage_status = *status;
5878                 } else {
5879                         memset(&stage_status, 0, sizeof(stage_status));
5880                 }
5882                 stage_line_type = line->type;
5883                 stage_chunks = 0;
5884                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5885         }
5887         return REQ_NONE;
5890 static bool
5891 status_exists(struct status *status, enum line_type type)
5893         struct view *view = VIEW(REQ_VIEW_STATUS);
5894         unsigned long lineno;
5896         for (lineno = 0; lineno < view->lines; lineno++) {
5897                 struct line *line = &view->line[lineno];
5898                 struct status *pos = line->data;
5900                 if (line->type != type)
5901                         continue;
5902                 if (!pos && (!status || !status->status) && line[1].data) {
5903                         select_view_line(view, lineno);
5904                         return TRUE;
5905                 }
5906                 if (pos && !strcmp(status->new.name, pos->new.name)) {
5907                         select_view_line(view, lineno);
5908                         return TRUE;
5909                 }
5910         }
5912         return FALSE;
5916 static bool
5917 status_update_prepare(struct io *io, enum line_type type)
5919         const char *staged_argv[] = {
5920                 "git", "update-index", "-z", "--index-info", NULL
5921         };
5922         const char *others_argv[] = {
5923                 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5924         };
5926         switch (type) {
5927         case LINE_STAT_STAGED:
5928                 return io_run(io, staged_argv, opt_cdup, IO_WR);
5930         case LINE_STAT_UNSTAGED:
5931         case LINE_STAT_UNTRACKED:
5932                 return io_run(io, others_argv, opt_cdup, IO_WR);
5934         default:
5935                 die("line type %d not handled in switch", type);
5936                 return FALSE;
5937         }
5940 static bool
5941 status_update_write(struct io *io, struct status *status, enum line_type type)
5943         char buf[SIZEOF_STR];
5944         size_t bufsize = 0;
5946         switch (type) {
5947         case LINE_STAT_STAGED:
5948                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
5949                                         status->old.mode,
5950                                         status->old.rev,
5951                                         status->old.name, 0))
5952                         return FALSE;
5953                 break;
5955         case LINE_STAT_UNSTAGED:
5956         case LINE_STAT_UNTRACKED:
5957                 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
5958                         return FALSE;
5959                 break;
5961         default:
5962                 die("line type %d not handled in switch", type);
5963         }
5965         return io_write(io, buf, bufsize);
5968 static bool
5969 status_update_file(struct status *status, enum line_type type)
5971         struct io io = {};
5972         bool result;
5974         if (!status_update_prepare(&io, type))
5975                 return FALSE;
5977         result = status_update_write(&io, status, type);
5978         return io_done(&io) && result;
5981 static bool
5982 status_update_files(struct view *view, struct line *line)
5984         char buf[sizeof(view->ref)];
5985         struct io io = {};
5986         bool result = TRUE;
5987         struct line *pos = view->line + view->lines;
5988         int files = 0;
5989         int file, done;
5990         int cursor_y = -1, cursor_x = -1;
5992         if (!status_update_prepare(&io, line->type))
5993                 return FALSE;
5995         for (pos = line; pos < view->line + view->lines && pos->data; pos++)
5996                 files++;
5998         string_copy(buf, view->ref);
5999         getsyx(cursor_y, cursor_x);
6000         for (file = 0, done = 5; result && file < files; line++, file++) {
6001                 int almost_done = file * 100 / files;
6003                 if (almost_done > done) {
6004                         done = almost_done;
6005                         string_format(view->ref, "updating file %u of %u (%d%% done)",
6006                                       file, files, done);
6007                         update_view_title(view);
6008                         setsyx(cursor_y, cursor_x);
6009                         doupdate();
6010                 }
6011                 result = status_update_write(&io, line->data, line->type);
6012         }
6013         string_copy(view->ref, buf);
6015         return io_done(&io) && result;
6018 static bool
6019 status_update(struct view *view)
6021         struct line *line = &view->line[view->lineno];
6023         assert(view->lines);
6025         if (!line->data) {
6026                 /* This should work even for the "On branch" line. */
6027                 if (line < view->line + view->lines && !line[1].data) {
6028                         report("Nothing to update");
6029                         return FALSE;
6030                 }
6032                 if (!status_update_files(view, line + 1)) {
6033                         report("Failed to update file status");
6034                         return FALSE;
6035                 }
6037         } else if (!status_update_file(line->data, line->type)) {
6038                 report("Failed to update file status");
6039                 return FALSE;
6040         }
6042         return TRUE;
6045 static bool
6046 status_revert(struct status *status, enum line_type type, bool has_none)
6048         if (!status || type != LINE_STAT_UNSTAGED) {
6049                 if (type == LINE_STAT_STAGED) {
6050                         report("Cannot revert changes to staged files");
6051                 } else if (type == LINE_STAT_UNTRACKED) {
6052                         report("Cannot revert changes to untracked files");
6053                 } else if (has_none) {
6054                         report("Nothing to revert");
6055                 } else {
6056                         report("Cannot revert changes to multiple files");
6057                 }
6059         } else if (prompt_yesno("Are you sure you want to revert changes?")) {
6060                 char mode[10] = "100644";
6061                 const char *reset_argv[] = {
6062                         "git", "update-index", "--cacheinfo", mode,
6063                                 status->old.rev, status->old.name, NULL
6064                 };
6065                 const char *checkout_argv[] = {
6066                         "git", "checkout", "--", status->old.name, NULL
6067                 };
6069                 if (status->status == 'U') {
6070                         string_format(mode, "%5o", status->old.mode);
6072                         if (status->old.mode == 0 && status->new.mode == 0) {
6073                                 reset_argv[2] = "--force-remove";
6074                                 reset_argv[3] = status->old.name;
6075                                 reset_argv[4] = NULL;
6076                         }
6078                         if (!io_run_fg(reset_argv, opt_cdup))
6079                                 return FALSE;
6080                         if (status->old.mode == 0 && status->new.mode == 0)
6081                                 return TRUE;
6082                 }
6084                 return io_run_fg(checkout_argv, opt_cdup);
6085         }
6087         return FALSE;
6090 static enum request
6091 status_request(struct view *view, enum request request, struct line *line)
6093         struct status *status = line->data;
6095         switch (request) {
6096         case REQ_STATUS_UPDATE:
6097                 if (!status_update(view))
6098                         return REQ_NONE;
6099                 break;
6101         case REQ_STATUS_REVERT:
6102                 if (!status_revert(status, line->type, status_has_none(view, line)))
6103                         return REQ_NONE;
6104                 break;
6106         case REQ_STATUS_MERGE:
6107                 if (!status || status->status != 'U') {
6108                         report("Merging only possible for files with unmerged status ('U').");
6109                         return REQ_NONE;
6110                 }
6111                 open_mergetool(status->new.name);
6112                 break;
6114         case REQ_EDIT:
6115                 if (!status)
6116                         return request;
6117                 if (status->status == 'D') {
6118                         report("File has been deleted.");
6119                         return REQ_NONE;
6120                 }
6122                 open_editor(status->new.name);
6123                 break;
6125         case REQ_VIEW_BLAME:
6126                 if (status)
6127                         opt_ref[0] = 0;
6128                 return request;
6130         case REQ_ENTER:
6131                 /* After returning the status view has been split to
6132                  * show the stage view. No further reloading is
6133                  * necessary. */
6134                 return status_enter(view, line);
6136         case REQ_REFRESH:
6137                 /* Simply reload the view. */
6138                 break;
6140         default:
6141                 return request;
6142         }
6144         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
6146         return REQ_NONE;
6149 static void
6150 status_select(struct view *view, struct line *line)
6152         struct status *status = line->data;
6153         char file[SIZEOF_STR] = "all files";
6154         const char *text;
6155         const char *key;
6157         if (status && !string_format(file, "'%s'", status->new.name))
6158                 return;
6160         if (!status && line[1].type == LINE_STAT_NONE)
6161                 line++;
6163         switch (line->type) {
6164         case LINE_STAT_STAGED:
6165                 text = "Press %s to unstage %s for commit";
6166                 break;
6168         case LINE_STAT_UNSTAGED:
6169                 text = "Press %s to stage %s for commit";
6170                 break;
6172         case LINE_STAT_UNTRACKED:
6173                 text = "Press %s to stage %s for addition";
6174                 break;
6176         case LINE_STAT_HEAD:
6177         case LINE_STAT_NONE:
6178                 text = "Nothing to update";
6179                 break;
6181         default:
6182                 die("line type %d not handled in switch", line->type);
6183         }
6185         if (status && status->status == 'U') {
6186                 text = "Press %s to resolve conflict in %s";
6187                 key = get_key(KEYMAP_STATUS, REQ_STATUS_MERGE);
6189         } else {
6190                 key = get_key(KEYMAP_STATUS, REQ_STATUS_UPDATE);
6191         }
6193         string_format(view->ref, text, key, file);
6194         if (status)
6195                 string_copy(opt_file, status->new.name);
6198 static bool
6199 status_grep(struct view *view, struct line *line)
6201         struct status *status = line->data;
6203         if (status) {
6204                 const char buf[2] = { status->status, 0 };
6205                 const char *text[] = { status->new.name, buf, NULL };
6207                 return grep_text(view, text);
6208         }
6210         return FALSE;
6213 static struct view_ops status_ops = {
6214         "file",
6215         NULL,
6216         status_open,
6217         NULL,
6218         status_draw,
6219         status_request,
6220         status_grep,
6221         status_select,
6222 };
6225 static bool
6226 stage_diff_write(struct io *io, struct line *line, struct line *end)
6228         while (line < end) {
6229                 if (!io_write(io, line->data, strlen(line->data)) ||
6230                     !io_write(io, "\n", 1))
6231                         return FALSE;
6232                 line++;
6233                 if (line->type == LINE_DIFF_CHUNK ||
6234                     line->type == LINE_DIFF_HEADER)
6235                         break;
6236         }
6238         return TRUE;
6241 static struct line *
6242 stage_diff_find(struct view *view, struct line *line, enum line_type type)
6244         for (; view->line < line; line--)
6245                 if (line->type == type)
6246                         return line;
6248         return NULL;
6251 static bool
6252 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
6254         const char *apply_argv[SIZEOF_ARG] = {
6255                 "git", "apply", "--whitespace=nowarn", NULL
6256         };
6257         struct line *diff_hdr;
6258         struct io io = {};
6259         int argc = 3;
6261         diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
6262         if (!diff_hdr)
6263                 return FALSE;
6265         if (!revert)
6266                 apply_argv[argc++] = "--cached";
6267         if (revert || stage_line_type == LINE_STAT_STAGED)
6268                 apply_argv[argc++] = "-R";
6269         apply_argv[argc++] = "-";
6270         apply_argv[argc++] = NULL;
6271         if (!io_run(&io, apply_argv, opt_cdup, IO_WR))
6272                 return FALSE;
6274         if (!stage_diff_write(&io, diff_hdr, chunk) ||
6275             !stage_diff_write(&io, chunk, view->line + view->lines))
6276                 chunk = NULL;
6278         io_done(&io);
6279         io_run_bg(update_index_argv);
6281         return chunk ? TRUE : FALSE;
6284 static bool
6285 stage_update(struct view *view, struct line *line)
6287         struct line *chunk = NULL;
6289         if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
6290                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6292         if (chunk) {
6293                 if (!stage_apply_chunk(view, chunk, FALSE)) {
6294                         report("Failed to apply chunk");
6295                         return FALSE;
6296                 }
6298         } else if (!stage_status.status) {
6299                 view = VIEW(REQ_VIEW_STATUS);
6301                 for (line = view->line; line < view->line + view->lines; line++)
6302                         if (line->type == stage_line_type)
6303                                 break;
6305                 if (!status_update_files(view, line + 1)) {
6306                         report("Failed to update files");
6307                         return FALSE;
6308                 }
6310         } else if (!status_update_file(&stage_status, stage_line_type)) {
6311                 report("Failed to update file");
6312                 return FALSE;
6313         }
6315         return TRUE;
6318 static bool
6319 stage_revert(struct view *view, struct line *line)
6321         struct line *chunk = NULL;
6323         if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
6324                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6326         if (chunk) {
6327                 if (!prompt_yesno("Are you sure you want to revert changes?"))
6328                         return FALSE;
6330                 if (!stage_apply_chunk(view, chunk, TRUE)) {
6331                         report("Failed to revert chunk");
6332                         return FALSE;
6333                 }
6334                 return TRUE;
6336         } else {
6337                 return status_revert(stage_status.status ? &stage_status : NULL,
6338                                      stage_line_type, FALSE);
6339         }
6343 static void
6344 stage_next(struct view *view, struct line *line)
6346         int i;
6348         if (!stage_chunks) {
6349                 for (line = view->line; line < view->line + view->lines; line++) {
6350                         if (line->type != LINE_DIFF_CHUNK)
6351                                 continue;
6353                         if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
6354                                 report("Allocation failure");
6355                                 return;
6356                         }
6358                         stage_chunk[stage_chunks++] = line - view->line;
6359                 }
6360         }
6362         for (i = 0; i < stage_chunks; i++) {
6363                 if (stage_chunk[i] > view->lineno) {
6364                         do_scroll_view(view, stage_chunk[i] - view->lineno);
6365                         report("Chunk %d of %d", i + 1, stage_chunks);
6366                         return;
6367                 }
6368         }
6370         report("No next chunk found");
6373 static enum request
6374 stage_request(struct view *view, enum request request, struct line *line)
6376         switch (request) {
6377         case REQ_STATUS_UPDATE:
6378                 if (!stage_update(view, line))
6379                         return REQ_NONE;
6380                 break;
6382         case REQ_STATUS_REVERT:
6383                 if (!stage_revert(view, line))
6384                         return REQ_NONE;
6385                 break;
6387         case REQ_STAGE_NEXT:
6388                 if (stage_line_type == LINE_STAT_UNTRACKED) {
6389                         report("File is untracked; press %s to add",
6390                                get_key(KEYMAP_STAGE, REQ_STATUS_UPDATE));
6391                         return REQ_NONE;
6392                 }
6393                 stage_next(view, line);
6394                 return REQ_NONE;
6396         case REQ_EDIT:
6397                 if (!stage_status.new.name[0])
6398                         return request;
6399                 if (stage_status.status == 'D') {
6400                         report("File has been deleted.");
6401                         return REQ_NONE;
6402                 }
6404                 open_editor(stage_status.new.name);
6405                 break;
6407         case REQ_REFRESH:
6408                 /* Reload everything ... */
6409                 break;
6411         case REQ_VIEW_BLAME:
6412                 if (stage_status.new.name[0]) {
6413                         string_copy(opt_file, stage_status.new.name);
6414                         opt_ref[0] = 0;
6415                 }
6416                 return request;
6418         case REQ_ENTER:
6419                 return pager_request(view, request, line);
6421         default:
6422                 return request;
6423         }
6425         VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
6426         open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
6428         /* Check whether the staged entry still exists, and close the
6429          * stage view if it doesn't. */
6430         if (!status_exists(&stage_status, stage_line_type)) {
6431                 status_restore(VIEW(REQ_VIEW_STATUS));
6432                 return REQ_VIEW_CLOSE;
6433         }
6435         if (stage_line_type == LINE_STAT_UNTRACKED) {
6436                 if (!suffixcmp(stage_status.new.name, -1, "/")) {
6437                         report("Cannot display a directory");
6438                         return REQ_NONE;
6439                 }
6441                 if (!prepare_update_file(view, stage_status.new.name)) {
6442                         report("Failed to open file: %s", strerror(errno));
6443                         return REQ_NONE;
6444                 }
6445         }
6446         open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
6448         return REQ_NONE;
6451 static struct view_ops stage_ops = {
6452         "line",
6453         NULL,
6454         NULL,
6455         pager_read,
6456         pager_draw,
6457         stage_request,
6458         pager_grep,
6459         pager_select,
6460 };
6463 /*
6464  * Revision graph
6465  */
6467 struct commit {
6468         char id[SIZEOF_REV];            /* SHA1 ID. */
6469         char title[128];                /* First line of the commit message. */
6470         const char *author;             /* Author of the commit. */
6471         struct time time;               /* Date from the author ident. */
6472         struct ref_list *refs;          /* Repository references. */
6473         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
6474         size_t graph_size;              /* The width of the graph array. */
6475         bool has_parents;               /* Rewritten --parents seen. */
6476 };
6478 /* Size of rev graph with no  "padding" columns */
6479 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6481 struct rev_graph {
6482         struct rev_graph *prev, *next, *parents;
6483         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
6484         size_t size;
6485         struct commit *commit;
6486         size_t pos;
6487         unsigned int boundary:1;
6488 };
6490 /* Parents of the commit being visualized. */
6491 static struct rev_graph graph_parents[4];
6493 /* The current stack of revisions on the graph. */
6494 static struct rev_graph graph_stacks[4] = {
6495         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
6496         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
6497         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
6498         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
6499 };
6501 static inline bool
6502 graph_parent_is_merge(struct rev_graph *graph)
6504         return graph->parents->size > 1;
6507 static inline void
6508 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
6510         struct commit *commit = graph->commit;
6512         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
6513                 commit->graph[commit->graph_size++] = symbol;
6516 static void
6517 clear_rev_graph(struct rev_graph *graph)
6519         graph->boundary = 0;
6520         graph->size = graph->pos = 0;
6521         graph->commit = NULL;
6522         memset(graph->parents, 0, sizeof(*graph->parents));
6525 static void
6526 done_rev_graph(struct rev_graph *graph)
6528         if (graph_parent_is_merge(graph) &&
6529             graph->pos < graph->size - 1 &&
6530             graph->next->size == graph->size + graph->parents->size - 1) {
6531                 size_t i = graph->pos + graph->parents->size - 1;
6533                 graph->commit->graph_size = i * 2;
6534                 while (i < graph->next->size - 1) {
6535                         append_to_rev_graph(graph, ' ');
6536                         append_to_rev_graph(graph, '\\');
6537                         i++;
6538                 }
6539         }
6541         clear_rev_graph(graph);
6544 static void
6545 push_rev_graph(struct rev_graph *graph, const char *parent)
6547         int i;
6549         /* "Collapse" duplicate parents lines.
6550          *
6551          * FIXME: This needs to also update update the drawn graph but
6552          * for now it just serves as a method for pruning graph lines. */
6553         for (i = 0; i < graph->size; i++)
6554                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
6555                         return;
6557         if (graph->size < SIZEOF_REVITEMS) {
6558                 string_copy_rev(graph->rev[graph->size++], parent);
6559         }
6562 static chtype
6563 get_rev_graph_symbol(struct rev_graph *graph)
6565         chtype symbol;
6567         if (graph->boundary)
6568                 symbol = REVGRAPH_BOUND;
6569         else if (graph->parents->size == 0)
6570                 symbol = REVGRAPH_INIT;
6571         else if (graph_parent_is_merge(graph))
6572                 symbol = REVGRAPH_MERGE;
6573         else if (graph->pos >= graph->size)
6574                 symbol = REVGRAPH_BRANCH;
6575         else
6576                 symbol = REVGRAPH_COMMIT;
6578         return symbol;
6581 static void
6582 draw_rev_graph(struct rev_graph *graph)
6584         struct rev_filler {
6585                 chtype separator, line;
6586         };
6587         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
6588         static struct rev_filler fillers[] = {
6589                 { ' ',  '|' },
6590                 { '`',  '.' },
6591                 { '\'', ' ' },
6592                 { '/',  ' ' },
6593         };
6594         chtype symbol = get_rev_graph_symbol(graph);
6595         struct rev_filler *filler;
6596         size_t i;
6598         fillers[DEFAULT].line = opt_line_graphics ? ACS_VLINE : '|';
6599         filler = &fillers[DEFAULT];
6601         for (i = 0; i < graph->pos; i++) {
6602                 append_to_rev_graph(graph, filler->line);
6603                 if (graph_parent_is_merge(graph->prev) &&
6604                     graph->prev->pos == i)
6605                         filler = &fillers[RSHARP];
6607                 append_to_rev_graph(graph, filler->separator);
6608         }
6610         /* Place the symbol for this revision. */
6611         append_to_rev_graph(graph, symbol);
6613         if (graph->prev->size > graph->size)
6614                 filler = &fillers[RDIAG];
6615         else
6616                 filler = &fillers[DEFAULT];
6618         i++;
6620         for (; i < graph->size; i++) {
6621                 append_to_rev_graph(graph, filler->separator);
6622                 append_to_rev_graph(graph, filler->line);
6623                 if (graph_parent_is_merge(graph->prev) &&
6624                     i < graph->prev->pos + graph->parents->size)
6625                         filler = &fillers[RSHARP];
6626                 if (graph->prev->size > graph->size)
6627                         filler = &fillers[LDIAG];
6628         }
6630         if (graph->prev->size > graph->size) {
6631                 append_to_rev_graph(graph, filler->separator);
6632                 if (filler->line != ' ')
6633                         append_to_rev_graph(graph, filler->line);
6634         }
6637 /* Prepare the next rev graph */
6638 static void
6639 prepare_rev_graph(struct rev_graph *graph)
6641         size_t i;
6643         /* First, traverse all lines of revisions up to the active one. */
6644         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6645                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6646                         break;
6648                 push_rev_graph(graph->next, graph->rev[graph->pos]);
6649         }
6651         /* Interleave the new revision parent(s). */
6652         for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6653                 push_rev_graph(graph->next, graph->parents->rev[i]);
6655         /* Lastly, put any remaining revisions. */
6656         for (i = graph->pos + 1; i < graph->size; i++)
6657                 push_rev_graph(graph->next, graph->rev[i]);
6660 static void
6661 update_rev_graph(struct view *view, struct rev_graph *graph)
6663         /* If this is the finalizing update ... */
6664         if (graph->commit)
6665                 prepare_rev_graph(graph);
6667         /* Graph visualization needs a one rev look-ahead,
6668          * so the first update doesn't visualize anything. */
6669         if (!graph->prev->commit)
6670                 return;
6672         if (view->lines > 2)
6673                 view->line[view->lines - 3].dirty = 1;
6674         if (view->lines > 1)
6675                 view->line[view->lines - 2].dirty = 1;
6676         draw_rev_graph(graph->prev);
6677         done_rev_graph(graph->prev->prev);
6681 /*
6682  * Main view backend
6683  */
6685 static const char *main_argv[SIZEOF_ARG] = {
6686         "git", "log", "--no-color", "--pretty=raw", "--parents",
6687                       "--topo-order", "%(head)", NULL
6688 };
6690 static bool
6691 main_draw(struct view *view, struct line *line, unsigned int lineno)
6693         struct commit *commit = line->data;
6695         if (!commit->author)
6696                 return FALSE;
6698         if (opt_date && draw_date(view, &commit->time))
6699                 return TRUE;
6701         if (opt_author && draw_author(view, commit->author))
6702                 return TRUE;
6704         if (opt_rev_graph && commit->graph_size &&
6705             draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6706                 return TRUE;
6708         if (opt_show_refs && commit->refs) {
6709                 size_t i;
6711                 for (i = 0; i < commit->refs->size; i++) {
6712                         struct ref *ref = commit->refs->refs[i];
6713                         enum line_type type;
6715                         if (ref->head)
6716                                 type = LINE_MAIN_HEAD;
6717                         else if (ref->ltag)
6718                                 type = LINE_MAIN_LOCAL_TAG;
6719                         else if (ref->tag)
6720                                 type = LINE_MAIN_TAG;
6721                         else if (ref->tracked)
6722                                 type = LINE_MAIN_TRACKED;
6723                         else if (ref->remote)
6724                                 type = LINE_MAIN_REMOTE;
6725                         else
6726                                 type = LINE_MAIN_REF;
6728                         if (draw_text(view, type, "[", TRUE) ||
6729                             draw_text(view, type, ref->name, TRUE) ||
6730                             draw_text(view, type, "]", TRUE))
6731                                 return TRUE;
6733                         if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6734                                 return TRUE;
6735                 }
6736         }
6738         draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6739         return TRUE;
6742 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6743 static bool
6744 main_read(struct view *view, char *line)
6746         static struct rev_graph *graph = graph_stacks;
6747         enum line_type type;
6748         struct commit *commit;
6750         if (!line) {
6751                 int i;
6753                 if (!view->lines && !view->parent)
6754                         die("No revisions match the given arguments.");
6755                 if (view->lines > 0) {
6756                         commit = view->line[view->lines - 1].data;
6757                         view->line[view->lines - 1].dirty = 1;
6758                         if (!commit->author) {
6759                                 view->lines--;
6760                                 free(commit);
6761                                 graph->commit = NULL;
6762                         }
6763                 }
6764                 update_rev_graph(view, graph);
6766                 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6767                         clear_rev_graph(&graph_stacks[i]);
6768                 return TRUE;
6769         }
6771         type = get_line_type(line);
6772         if (type == LINE_COMMIT) {
6773                 commit = calloc(1, sizeof(struct commit));
6774                 if (!commit)
6775                         return FALSE;
6777                 line += STRING_SIZE("commit ");
6778                 if (*line == '-') {
6779                         graph->boundary = 1;
6780                         line++;
6781                 }
6783                 string_copy_rev(commit->id, line);
6784                 commit->refs = get_ref_list(commit->id);
6785                 graph->commit = commit;
6786                 add_line_data(view, commit, LINE_MAIN_COMMIT);
6788                 while ((line = strchr(line, ' '))) {
6789                         line++;
6790                         push_rev_graph(graph->parents, line);
6791                         commit->has_parents = TRUE;
6792                 }
6793                 return TRUE;
6794         }
6796         if (!view->lines)
6797                 return TRUE;
6798         commit = view->line[view->lines - 1].data;
6800         switch (type) {
6801         case LINE_PARENT:
6802                 if (commit->has_parents)
6803                         break;
6804                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6805                 break;
6807         case LINE_AUTHOR:
6808                 parse_author_line(line + STRING_SIZE("author "),
6809                                   &commit->author, &commit->time);
6810                 update_rev_graph(view, graph);
6811                 graph = graph->next;
6812                 break;
6814         default:
6815                 /* Fill in the commit title if it has not already been set. */
6816                 if (commit->title[0])
6817                         break;
6819                 /* Require titles to start with a non-space character at the
6820                  * offset used by git log. */
6821                 if (strncmp(line, "    ", 4))
6822                         break;
6823                 line += 4;
6824                 /* Well, if the title starts with a whitespace character,
6825                  * try to be forgiving.  Otherwise we end up with no title. */
6826                 while (isspace(*line))
6827                         line++;
6828                 if (*line == '\0')
6829                         break;
6830                 /* FIXME: More graceful handling of titles; append "..." to
6831                  * shortened titles, etc. */
6833                 string_expand(commit->title, sizeof(commit->title), line, 1);
6834                 view->line[view->lines - 1].dirty = 1;
6835         }
6837         return TRUE;
6840 static enum request
6841 main_request(struct view *view, enum request request, struct line *line)
6843         enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
6845         switch (request) {
6846         case REQ_ENTER:
6847                 open_view(view, REQ_VIEW_DIFF, flags);
6848                 break;
6849         case REQ_REFRESH:
6850                 load_refs();
6851                 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6852                 break;
6853         default:
6854                 return request;
6855         }
6857         return REQ_NONE;
6860 static bool
6861 grep_refs(struct ref_list *list, regex_t *regex)
6863         regmatch_t pmatch;
6864         size_t i;
6866         if (!opt_show_refs || !list)
6867                 return FALSE;
6869         for (i = 0; i < list->size; i++) {
6870                 if (regexec(regex, list->refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6871                         return TRUE;
6872         }
6874         return FALSE;
6877 static bool
6878 main_grep(struct view *view, struct line *line)
6880         struct commit *commit = line->data;
6881         const char *text[] = {
6882                 commit->title,
6883                 opt_author ? commit->author : "",
6884                 mkdate(&commit->time, opt_date),
6885                 NULL
6886         };
6888         return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6891 static void
6892 main_select(struct view *view, struct line *line)
6894         struct commit *commit = line->data;
6896         string_copy_rev(view->ref, commit->id);
6897         string_copy_rev(ref_commit, view->ref);
6900 static struct view_ops main_ops = {
6901         "commit",
6902         main_argv,
6903         NULL,
6904         main_read,
6905         main_draw,
6906         main_request,
6907         main_grep,
6908         main_select,
6909 };
6912 /*
6913  * Status management
6914  */
6916 /* Whether or not the curses interface has been initialized. */
6917 static bool cursed = FALSE;
6919 /* Terminal hacks and workarounds. */
6920 static bool use_scroll_redrawwin;
6921 static bool use_scroll_status_wclear;
6923 /* The status window is used for polling keystrokes. */
6924 static WINDOW *status_win;
6926 /* Reading from the prompt? */
6927 static bool input_mode = FALSE;
6929 static bool status_empty = FALSE;
6931 /* Update status and title window. */
6932 static void
6933 report(const char *msg, ...)
6935         struct view *view = display[current_view];
6937         if (input_mode)
6938                 return;
6940         if (!view) {
6941                 char buf[SIZEOF_STR];
6942                 va_list args;
6944                 va_start(args, msg);
6945                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6946                         buf[sizeof(buf) - 1] = 0;
6947                         buf[sizeof(buf) - 2] = '.';
6948                         buf[sizeof(buf) - 3] = '.';
6949                         buf[sizeof(buf) - 4] = '.';
6950                 }
6951                 va_end(args);
6952                 die("%s", buf);
6953         }
6955         if (!status_empty || *msg) {
6956                 va_list args;
6958                 va_start(args, msg);
6960                 wmove(status_win, 0, 0);
6961                 if (view->has_scrolled && use_scroll_status_wclear)
6962                         wclear(status_win);
6963                 if (*msg) {
6964                         vwprintw(status_win, msg, args);
6965                         status_empty = FALSE;
6966                 } else {
6967                         status_empty = TRUE;
6968                 }
6969                 wclrtoeol(status_win);
6970                 wnoutrefresh(status_win);
6972                 va_end(args);
6973         }
6975         update_view_title(view);
6978 static void
6979 init_display(void)
6981         const char *term;
6982         int x, y;
6984         /* Initialize the curses library */
6985         if (isatty(STDIN_FILENO)) {
6986                 cursed = !!initscr();
6987                 opt_tty = stdin;
6988         } else {
6989                 /* Leave stdin and stdout alone when acting as a pager. */
6990                 opt_tty = fopen("/dev/tty", "r+");
6991                 if (!opt_tty)
6992                         die("Failed to open /dev/tty");
6993                 cursed = !!newterm(NULL, opt_tty, opt_tty);
6994         }
6996         if (!cursed)
6997                 die("Failed to initialize curses");
6999         nonl();         /* Disable conversion and detect newlines from input. */
7000         cbreak();       /* Take input chars one at a time, no wait for \n */
7001         noecho();       /* Don't echo input */
7002         leaveok(stdscr, FALSE);
7004         if (has_colors())
7005                 init_colors();
7007         getmaxyx(stdscr, y, x);
7008         status_win = newwin(1, 0, y - 1, 0);
7009         if (!status_win)
7010                 die("Failed to create status window");
7012         /* Enable keyboard mapping */
7013         keypad(status_win, TRUE);
7014         wbkgdset(status_win, get_line_attr(LINE_STATUS));
7016         TABSIZE = opt_tab_size;
7018         term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
7019         if (term && !strcmp(term, "gnome-terminal")) {
7020                 /* In the gnome-terminal-emulator, the message from
7021                  * scrolling up one line when impossible followed by
7022                  * scrolling down one line causes corruption of the
7023                  * status line. This is fixed by calling wclear. */
7024                 use_scroll_status_wclear = TRUE;
7025                 use_scroll_redrawwin = FALSE;
7027         } else if (term && !strcmp(term, "xrvt-xpm")) {
7028                 /* No problems with full optimizations in xrvt-(unicode)
7029                  * and aterm. */
7030                 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
7032         } else {
7033                 /* When scrolling in (u)xterm the last line in the
7034                  * scrolling direction will update slowly. */
7035                 use_scroll_redrawwin = TRUE;
7036                 use_scroll_status_wclear = FALSE;
7037         }
7040 static int
7041 get_input(int prompt_position)
7043         struct view *view;
7044         int i, key, cursor_y, cursor_x;
7045         bool loading = FALSE;
7047         if (prompt_position)
7048                 input_mode = TRUE;
7050         while (TRUE) {
7051                 foreach_view (view, i) {
7052                         update_view(view);
7053                         if (view_is_displayed(view) && view->has_scrolled &&
7054                             use_scroll_redrawwin)
7055                                 redrawwin(view->win);
7056                         view->has_scrolled = FALSE;
7057                         if (view->pipe)
7058                                 loading = TRUE;
7059                 }
7061                 /* Update the cursor position. */
7062                 if (prompt_position) {
7063                         getbegyx(status_win, cursor_y, cursor_x);
7064                         cursor_x = prompt_position;
7065                 } else {
7066                         view = display[current_view];
7067                         getbegyx(view->win, cursor_y, cursor_x);
7068                         cursor_x = view->width - 1;
7069                         cursor_y += view->lineno - view->offset;
7070                 }
7071                 setsyx(cursor_y, cursor_x);
7073                 /* Refresh, accept single keystroke of input */
7074                 doupdate();
7075                 nodelay(status_win, loading);
7076                 key = wgetch(status_win);
7078                 /* wgetch() with nodelay() enabled returns ERR when
7079                  * there's no input. */
7080                 if (key == ERR) {
7082                 } else if (key == KEY_RESIZE) {
7083                         int height, width;
7085                         getmaxyx(stdscr, height, width);
7087                         wresize(status_win, 1, width);
7088                         mvwin(status_win, height - 1, 0);
7089                         wnoutrefresh(status_win);
7090                         resize_display();
7091                         redraw_display(TRUE);
7093                 } else {
7094                         input_mode = FALSE;
7095                         return key;
7096                 }
7097         }
7100 static char *
7101 prompt_input(const char *prompt, input_handler handler, void *data)
7103         enum input_status status = INPUT_OK;
7104         static char buf[SIZEOF_STR];
7105         size_t pos = 0;
7107         buf[pos] = 0;
7109         while (status == INPUT_OK || status == INPUT_SKIP) {
7110                 int key;
7112                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
7113                 wclrtoeol(status_win);
7115                 key = get_input(pos + 1);
7116                 switch (key) {
7117                 case KEY_RETURN:
7118                 case KEY_ENTER:
7119                 case '\n':
7120                         status = pos ? INPUT_STOP : INPUT_CANCEL;
7121                         break;
7123                 case KEY_BACKSPACE:
7124                         if (pos > 0)
7125                                 buf[--pos] = 0;
7126                         else
7127                                 status = INPUT_CANCEL;
7128                         break;
7130                 case KEY_ESC:
7131                         status = INPUT_CANCEL;
7132                         break;
7134                 default:
7135                         if (pos >= sizeof(buf)) {
7136                                 report("Input string too long");
7137                                 return NULL;
7138                         }
7140                         status = handler(data, buf, key);
7141                         if (status == INPUT_OK)
7142                                 buf[pos++] = (char) key;
7143                 }
7144         }
7146         /* Clear the status window */
7147         status_empty = FALSE;
7148         report("");
7150         if (status == INPUT_CANCEL)
7151                 return NULL;
7153         buf[pos++] = 0;
7155         return buf;
7158 static enum input_status
7159 prompt_yesno_handler(void *data, char *buf, int c)
7161         if (c == 'y' || c == 'Y')
7162                 return INPUT_STOP;
7163         if (c == 'n' || c == 'N')
7164                 return INPUT_CANCEL;
7165         return INPUT_SKIP;
7168 static bool
7169 prompt_yesno(const char *prompt)
7171         char prompt2[SIZEOF_STR];
7173         if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
7174                 return FALSE;
7176         return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
7179 static enum input_status
7180 read_prompt_handler(void *data, char *buf, int c)
7182         return isprint(c) ? INPUT_OK : INPUT_SKIP;
7185 static char *
7186 read_prompt(const char *prompt)
7188         return prompt_input(prompt, read_prompt_handler, NULL);
7191 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected)
7193         enum input_status status = INPUT_OK;
7194         int size = 0;
7196         while (items[size].text)
7197                 size++;
7199         while (status == INPUT_OK) {
7200                 const struct menu_item *item = &items[*selected];
7201                 int key;
7202                 int i;
7204                 mvwprintw(status_win, 0, 0, "%s (%d of %d) ",
7205                           prompt, *selected + 1, size);
7206                 if (item->hotkey)
7207                         wprintw(status_win, "[%c] ", (char) item->hotkey);
7208                 wprintw(status_win, "%s", item->text);
7209                 wclrtoeol(status_win);
7211                 key = get_input(COLS - 1);
7212                 switch (key) {
7213                 case KEY_RETURN:
7214                 case KEY_ENTER:
7215                 case '\n':
7216                         status = INPUT_STOP;
7217                         break;
7219                 case KEY_LEFT:
7220                 case KEY_UP:
7221                         *selected = *selected - 1;
7222                         if (*selected < 0)
7223                                 *selected = size - 1;
7224                         break;
7226                 case KEY_RIGHT:
7227                 case KEY_DOWN:
7228                         *selected = (*selected + 1) % size;
7229                         break;
7231                 case KEY_ESC:
7232                         status = INPUT_CANCEL;
7233                         break;
7235                 default:
7236                         for (i = 0; items[i].text; i++)
7237                                 if (items[i].hotkey == key) {
7238                                         *selected = i;
7239                                         status = INPUT_STOP;
7240                                         break;
7241                                 }
7242                 }
7243         }
7245         /* Clear the status window */
7246         status_empty = FALSE;
7247         report("");
7249         return status != INPUT_CANCEL;
7252 /*
7253  * Repository properties
7254  */
7256 static struct ref **refs = NULL;
7257 static size_t refs_size = 0;
7258 static struct ref *refs_head = NULL;
7260 static struct ref_list **ref_lists = NULL;
7261 static size_t ref_lists_size = 0;
7263 DEFINE_ALLOCATOR(realloc_refs, struct ref *, 256)
7264 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
7265 DEFINE_ALLOCATOR(realloc_ref_lists, struct ref_list *, 8)
7267 static int
7268 compare_refs(const void *ref1_, const void *ref2_)
7270         const struct ref *ref1 = *(const struct ref **)ref1_;
7271         const struct ref *ref2 = *(const struct ref **)ref2_;
7273         if (ref1->tag != ref2->tag)
7274                 return ref2->tag - ref1->tag;
7275         if (ref1->ltag != ref2->ltag)
7276                 return ref2->ltag - ref2->ltag;
7277         if (ref1->head != ref2->head)
7278                 return ref2->head - ref1->head;
7279         if (ref1->tracked != ref2->tracked)
7280                 return ref2->tracked - ref1->tracked;
7281         if (ref1->remote != ref2->remote)
7282                 return ref2->remote - ref1->remote;
7283         return strcmp(ref1->name, ref2->name);
7286 static void
7287 foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data)
7289         size_t i;
7291         for (i = 0; i < refs_size; i++)
7292                 if (!visitor(data, refs[i]))
7293                         break;
7296 static struct ref *
7297 get_ref_head()
7299         return refs_head;
7302 static struct ref_list *
7303 get_ref_list(const char *id)
7305         struct ref_list *list;
7306         size_t i;
7308         for (i = 0; i < ref_lists_size; i++)
7309                 if (!strcmp(id, ref_lists[i]->id))
7310                         return ref_lists[i];
7312         if (!realloc_ref_lists(&ref_lists, ref_lists_size, 1))
7313                 return NULL;
7314         list = calloc(1, sizeof(*list));
7315         if (!list)
7316                 return NULL;
7318         for (i = 0; i < refs_size; i++) {
7319                 if (!strcmp(id, refs[i]->id) &&
7320                     realloc_refs_list(&list->refs, list->size, 1))
7321                         list->refs[list->size++] = refs[i];
7322         }
7324         if (!list->refs) {
7325                 free(list);
7326                 return NULL;
7327         }
7329         qsort(list->refs, list->size, sizeof(*list->refs), compare_refs);
7330         ref_lists[ref_lists_size++] = list;
7331         return list;
7334 static int
7335 read_ref(char *id, size_t idlen, char *name, size_t namelen)
7337         struct ref *ref = NULL;
7338         bool tag = FALSE;
7339         bool ltag = FALSE;
7340         bool remote = FALSE;
7341         bool tracked = FALSE;
7342         bool head = FALSE;
7343         int from = 0, to = refs_size - 1;
7345         if (!prefixcmp(name, "refs/tags/")) {
7346                 if (!suffixcmp(name, namelen, "^{}")) {
7347                         namelen -= 3;
7348                         name[namelen] = 0;
7349                 } else {
7350                         ltag = TRUE;
7351                 }
7353                 tag = TRUE;
7354                 namelen -= STRING_SIZE("refs/tags/");
7355                 name    += STRING_SIZE("refs/tags/");
7357         } else if (!prefixcmp(name, "refs/remotes/")) {
7358                 remote = TRUE;
7359                 namelen -= STRING_SIZE("refs/remotes/");
7360                 name    += STRING_SIZE("refs/remotes/");
7361                 tracked  = !strcmp(opt_remote, name);
7363         } else if (!prefixcmp(name, "refs/heads/")) {
7364                 namelen -= STRING_SIZE("refs/heads/");
7365                 name    += STRING_SIZE("refs/heads/");
7366                 if (!strncmp(opt_head, name, namelen))
7367                         return OK;
7369         } else if (!strcmp(name, "HEAD")) {
7370                 head     = TRUE;
7371                 if (*opt_head) {
7372                         namelen  = strlen(opt_head);
7373                         name     = opt_head;
7374                 }
7375         }
7377         /* If we are reloading or it's an annotated tag, replace the
7378          * previous SHA1 with the resolved commit id; relies on the fact
7379          * git-ls-remote lists the commit id of an annotated tag right
7380          * before the commit id it points to. */
7381         while (from <= to) {
7382                 size_t pos = (to + from) / 2;
7383                 int cmp = strcmp(name, refs[pos]->name);
7385                 if (!cmp) {
7386                         ref = refs[pos];
7387                         break;
7388                 }
7390                 if (cmp < 0)
7391                         to = pos - 1;
7392                 else
7393                         from = pos + 1;
7394         }
7396         if (!ref) {
7397                 if (!realloc_refs(&refs, refs_size, 1))
7398                         return ERR;
7399                 ref = calloc(1, sizeof(*ref) + namelen);
7400                 if (!ref)
7401                         return ERR;
7402                 memmove(refs + from + 1, refs + from,
7403                         (refs_size - from) * sizeof(*refs));
7404                 refs[from] = ref;
7405                 strncpy(ref->name, name, namelen);
7406                 refs_size++;
7407         }
7409         ref->head = head;
7410         ref->tag = tag;
7411         ref->ltag = ltag;
7412         ref->remote = remote;
7413         ref->tracked = tracked;
7414         string_copy_rev(ref->id, id);
7416         if (head)
7417                 refs_head = ref;
7418         return OK;
7421 static int
7422 load_refs(void)
7424         const char *head_argv[] = {
7425                 "git", "symbolic-ref", "HEAD", NULL
7426         };
7427         static const char *ls_remote_argv[SIZEOF_ARG] = {
7428                 "git", "ls-remote", opt_git_dir, NULL
7429         };
7430         static bool init = FALSE;
7431         size_t i;
7433         if (!init) {
7434                 if (!argv_from_env(ls_remote_argv, "TIG_LS_REMOTE"))
7435                         die("TIG_LS_REMOTE contains too many arguments");
7436                 init = TRUE;
7437         }
7439         if (!*opt_git_dir)
7440                 return OK;
7442         if (io_run_buf(head_argv, opt_head, sizeof(opt_head)) &&
7443             !prefixcmp(opt_head, "refs/heads/")) {
7444                 char *offset = opt_head + STRING_SIZE("refs/heads/");
7446                 memmove(opt_head, offset, strlen(offset) + 1);
7447         }
7449         refs_head = NULL;
7450         for (i = 0; i < refs_size; i++)
7451                 refs[i]->id[0] = 0;
7453         if (io_run_load(ls_remote_argv, "\t", read_ref) == ERR)
7454                 return ERR;
7456         /* Update the ref lists to reflect changes. */
7457         for (i = 0; i < ref_lists_size; i++) {
7458                 struct ref_list *list = ref_lists[i];
7459                 size_t old, new;
7461                 for (old = new = 0; old < list->size; old++)
7462                         if (!strcmp(list->id, list->refs[old]->id))
7463                                 list->refs[new++] = list->refs[old];
7464                 list->size = new;
7465         }
7467         return OK;
7470 static void
7471 set_remote_branch(const char *name, const char *value, size_t valuelen)
7473         if (!strcmp(name, ".remote")) {
7474                 string_ncopy(opt_remote, value, valuelen);
7476         } else if (*opt_remote && !strcmp(name, ".merge")) {
7477                 size_t from = strlen(opt_remote);
7479                 if (!prefixcmp(value, "refs/heads/"))
7480                         value += STRING_SIZE("refs/heads/");
7482                 if (!string_format_from(opt_remote, &from, "/%s", value))
7483                         opt_remote[0] = 0;
7484         }
7487 static void
7488 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
7490         const char *argv[SIZEOF_ARG] = { name, "=" };
7491         int argc = 1 + (cmd == option_set_command);
7492         int error = ERR;
7494         if (!argv_from_string(argv, &argc, value))
7495                 config_msg = "Too many option arguments";
7496         else
7497                 error = cmd(argc, argv);
7499         if (error == ERR)
7500                 warn("Option 'tig.%s': %s", name, config_msg);
7503 static bool
7504 set_environment_variable(const char *name, const char *value)
7506         size_t len = strlen(name) + 1 + strlen(value) + 1;
7507         char *env = malloc(len);
7509         if (env &&
7510             string_nformat(env, len, NULL, "%s=%s", name, value) &&
7511             putenv(env) == 0)
7512                 return TRUE;
7513         free(env);
7514         return FALSE;
7517 static void
7518 set_work_tree(const char *value)
7520         char cwd[SIZEOF_STR];
7522         if (!getcwd(cwd, sizeof(cwd)))
7523                 die("Failed to get cwd path: %s", strerror(errno));
7524         if (chdir(opt_git_dir) < 0)
7525                 die("Failed to chdir(%s): %s", strerror(errno));
7526         if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7527                 die("Failed to get git path: %s", strerror(errno));
7528         if (chdir(cwd) < 0)
7529                 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7530         if (chdir(value) < 0)
7531                 die("Failed to chdir(%s): %s", value, strerror(errno));
7532         if (!getcwd(cwd, sizeof(cwd)))
7533                 die("Failed to get cwd path: %s", strerror(errno));
7534         if (!set_environment_variable("GIT_WORK_TREE", cwd))
7535                 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7536         if (!set_environment_variable("GIT_DIR", opt_git_dir))
7537                 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7538         opt_is_inside_work_tree = TRUE;
7541 static int
7542 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
7544         if (!strcmp(name, "i18n.commitencoding"))
7545                 string_ncopy(opt_encoding, value, valuelen);
7547         else if (!strcmp(name, "core.editor"))
7548                 string_ncopy(opt_editor, value, valuelen);
7550         else if (!strcmp(name, "core.worktree"))
7551                 set_work_tree(value);
7553         else if (!prefixcmp(name, "tig.color."))
7554                 set_repo_config_option(name + 10, value, option_color_command);
7556         else if (!prefixcmp(name, "tig.bind."))
7557                 set_repo_config_option(name + 9, value, option_bind_command);
7559         else if (!prefixcmp(name, "tig."))
7560                 set_repo_config_option(name + 4, value, option_set_command);
7562         else if (*opt_head && !prefixcmp(name, "branch.") &&
7563                  !strncmp(name + 7, opt_head, strlen(opt_head)))
7564                 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7566         return OK;
7569 static int
7570 load_git_config(void)
7572         const char *config_list_argv[] = { "git", "config", "--list", NULL };
7574         return io_run_load(config_list_argv, "=", read_repo_config_option);
7577 static int
7578 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
7580         if (!opt_git_dir[0]) {
7581                 string_ncopy(opt_git_dir, name, namelen);
7583         } else if (opt_is_inside_work_tree == -1) {
7584                 /* This can be 3 different values depending on the
7585                  * version of git being used. If git-rev-parse does not
7586                  * understand --is-inside-work-tree it will simply echo
7587                  * the option else either "true" or "false" is printed.
7588                  * Default to true for the unknown case. */
7589                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7591         } else if (*name == '.') {
7592                 string_ncopy(opt_cdup, name, namelen);
7594         } else {
7595                 string_ncopy(opt_prefix, name, namelen);
7596         }
7598         return OK;
7601 static int
7602 load_repo_info(void)
7604         const char *rev_parse_argv[] = {
7605                 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7606                         "--show-cdup", "--show-prefix", NULL
7607         };
7609         return io_run_load(rev_parse_argv, "=", read_repo_info);
7613 /*
7614  * Main
7615  */
7617 static const char usage[] =
7618 "tig " TIG_VERSION " (" __DATE__ ")\n"
7619 "\n"
7620 "Usage: tig        [options] [revs] [--] [paths]\n"
7621 "   or: tig show   [options] [revs] [--] [paths]\n"
7622 "   or: tig blame  [rev] path\n"
7623 "   or: tig status\n"
7624 "   or: tig <      [git command output]\n"
7625 "\n"
7626 "Options:\n"
7627 "  -v, --version   Show version and exit\n"
7628 "  -h, --help      Show help message and exit";
7630 static void __NORETURN
7631 quit(int sig)
7633         /* XXX: Restore tty modes and let the OS cleanup the rest! */
7634         if (cursed)
7635                 endwin();
7636         exit(0);
7639 static void __NORETURN
7640 die(const char *err, ...)
7642         va_list args;
7644         endwin();
7646         va_start(args, err);
7647         fputs("tig: ", stderr);
7648         vfprintf(stderr, err, args);
7649         fputs("\n", stderr);
7650         va_end(args);
7652         exit(1);
7655 static void
7656 warn(const char *msg, ...)
7658         va_list args;
7660         va_start(args, msg);
7661         fputs("tig warning: ", stderr);
7662         vfprintf(stderr, msg, args);
7663         fputs("\n", stderr);
7664         va_end(args);
7667 static enum request
7668 parse_options(int argc, const char *argv[])
7670         enum request request = REQ_VIEW_MAIN;
7671         const char *subcommand;
7672         bool seen_dashdash = FALSE;
7673         /* XXX: This is vulnerable to the user overriding options
7674          * required for the main view parser. */
7675         const char *custom_argv[SIZEOF_ARG] = {
7676                 "git", "log", "--no-color", "--pretty=raw", "--parents",
7677                         "--topo-order", NULL
7678         };
7679         int i, j = 6;
7681         if (!isatty(STDIN_FILENO)) {
7682                 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
7683                 return REQ_VIEW_PAGER;
7684         }
7686         if (argc <= 1)
7687                 return REQ_NONE;
7689         subcommand = argv[1];
7690         if (!strcmp(subcommand, "status")) {
7691                 if (argc > 2)
7692                         warn("ignoring arguments after `%s'", subcommand);
7693                 return REQ_VIEW_STATUS;
7695         } else if (!strcmp(subcommand, "blame")) {
7696                 if (argc <= 2 || argc > 4)
7697                         die("invalid number of options to blame\n\n%s", usage);
7699                 i = 2;
7700                 if (argc == 4) {
7701                         string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7702                         i++;
7703                 }
7705                 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7706                 return REQ_VIEW_BLAME;
7708         } else if (!strcmp(subcommand, "show")) {
7709                 request = REQ_VIEW_DIFF;
7711         } else {
7712                 subcommand = NULL;
7713         }
7715         if (subcommand) {
7716                 custom_argv[1] = subcommand;
7717                 j = 2;
7718         }
7720         for (i = 1 + !!subcommand; i < argc; i++) {
7721                 const char *opt = argv[i];
7723                 if (seen_dashdash || !strcmp(opt, "--")) {
7724                         seen_dashdash = TRUE;
7726                 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7727                         printf("tig version %s\n", TIG_VERSION);
7728                         quit(0);
7730                 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7731                         printf("%s\n", usage);
7732                         quit(0);
7733                 }
7735                 custom_argv[j++] = opt;
7736                 if (j >= ARRAY_SIZE(custom_argv))
7737                         die("command too long");
7738         }
7740         if (!prepare_update(VIEW(request), custom_argv, NULL))
7741                 die("Failed to format arguments");
7743         return request;
7746 int
7747 main(int argc, const char *argv[])
7749         const char *codeset = "UTF-8";
7750         enum request request = parse_options(argc, argv);
7751         struct view *view;
7752         size_t i;
7754         signal(SIGINT, quit);
7755         signal(SIGPIPE, SIG_IGN);
7757         if (setlocale(LC_ALL, "")) {
7758                 codeset = nl_langinfo(CODESET);
7759         }
7761         if (load_repo_info() == ERR)
7762                 die("Failed to load repo info.");
7764         if (load_options() == ERR)
7765                 die("Failed to load user config.");
7767         if (load_git_config() == ERR)
7768                 die("Failed to load repo config.");
7770         /* Require a git repository unless when running in pager mode. */
7771         if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7772                 die("Not a git repository");
7774         if (*opt_encoding && strcmp(codeset, "UTF-8")) {
7775                 opt_iconv_in = iconv_open("UTF-8", opt_encoding);
7776                 if (opt_iconv_in == ICONV_NONE)
7777                         die("Failed to initialize character set conversion");
7778         }
7780         if (codeset && strcmp(codeset, "UTF-8")) {
7781                 opt_iconv_out = iconv_open(codeset, "UTF-8");
7782                 if (opt_iconv_out == ICONV_NONE)
7783                         die("Failed to initialize character set conversion");
7784         }
7786         if (load_refs() == ERR)
7787                 die("Failed to load refs.");
7789         foreach_view (view, i)
7790                 if (!argv_from_env(view->ops->argv, view->cmd_env))
7791                         die("Too many arguments in the `%s` environment variable",
7792                             view->cmd_env);
7794         init_display();
7796         if (request != REQ_NONE)
7797                 open_view(NULL, request, OPEN_PREPARED);
7798         request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
7800         while (view_driver(display[current_view], request)) {
7801                 int key = get_input(0);
7803                 view = display[current_view];
7804                 request = get_keybinding(view->keymap, key);
7806                 /* Some low-level request handling. This keeps access to
7807                  * status_win restricted. */
7808                 switch (request) {
7809                 case REQ_PROMPT:
7810                 {
7811                         char *cmd = read_prompt(":");
7813                         if (cmd && isdigit(*cmd)) {
7814                                 int lineno = view->lineno + 1;
7816                                 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7817                                         select_view_line(view, lineno - 1);
7818                                         report("");
7819                                 } else {
7820                                         report("Unable to parse '%s' as a line number", cmd);
7821                                 }
7823                         } else if (cmd) {
7824                                 struct view *next = VIEW(REQ_VIEW_PAGER);
7825                                 const char *argv[SIZEOF_ARG] = { "git" };
7826                                 int argc = 1;
7828                                 /* When running random commands, initially show the
7829                                  * command in the title. However, it maybe later be
7830                                  * overwritten if a commit line is selected. */
7831                                 string_ncopy(next->ref, cmd, strlen(cmd));
7833                                 if (!argv_from_string(argv, &argc, cmd)) {
7834                                         report("Too many arguments");
7835                                 } else if (!prepare_update(next, argv, NULL)) {
7836                                         report("Failed to format command");
7837                                 } else {
7838                                         open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7839                                 }
7840                         }
7842                         request = REQ_NONE;
7843                         break;
7844                 }
7845                 case REQ_SEARCH:
7846                 case REQ_SEARCH_BACK:
7847                 {
7848                         const char *prompt = request == REQ_SEARCH ? "/" : "?";
7849                         char *search = read_prompt(prompt);
7851                         if (search)
7852                                 string_ncopy(opt_search, search, strlen(search));
7853                         else if (*opt_search)
7854                                 request = request == REQ_SEARCH ?
7855                                         REQ_FIND_NEXT :
7856                                         REQ_FIND_PREV;
7857                         else
7858                                 request = REQ_NONE;
7859                         break;
7860                 }
7861                 default:
7862                         break;
7863                 }
7864         }
7866         quit(0);
7868         return 0;