Code

Fall back to retry if no diff will be shown
[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 input_status {
144         INPUT_OK,
145         INPUT_SKIP,
146         INPUT_STOP,
147         INPUT_CANCEL
148 };
150 typedef enum input_status (*input_handler)(void *data, char *buf, int c);
152 static char *prompt_input(const char *prompt, input_handler handler, void *data);
153 static bool prompt_yesno(const char *prompt);
155 struct menu_item {
156         int hotkey;
157         const char *text;
158         void *data;
159 };
161 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected);
163 /*
164  * Allocation helpers ... Entering macro hell to never be seen again.
165  */
167 #define DEFINE_ALLOCATOR(name, type, chunk_size)                                \
168 static type *                                                                   \
169 name(type **mem, size_t size, size_t increase)                                  \
170 {                                                                               \
171         size_t num_chunks = (size + chunk_size - 1) / chunk_size;               \
172         size_t num_chunks_new = (size + increase + chunk_size - 1) / chunk_size;\
173         type *tmp = *mem;                                                       \
174                                                                                 \
175         if (mem == NULL || num_chunks != num_chunks_new) {                      \
176                 tmp = realloc(tmp, num_chunks_new * chunk_size * sizeof(type)); \
177                 if (tmp)                                                        \
178                         *mem = tmp;                                             \
179         }                                                                       \
180                                                                                 \
181         return tmp;                                                             \
184 /*
185  * String helpers
186  */
188 static inline void
189 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
191         if (srclen > dstlen - 1)
192                 srclen = dstlen - 1;
194         strncpy(dst, src, srclen);
195         dst[srclen] = 0;
198 /* Shorthands for safely copying into a fixed buffer. */
200 #define string_copy(dst, src) \
201         string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
203 #define string_ncopy(dst, src, srclen) \
204         string_ncopy_do(dst, sizeof(dst), src, srclen)
206 #define string_copy_rev(dst, src) \
207         string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
209 #define string_add(dst, from, src) \
210         string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
212 static size_t
213 string_expand(char *dst, size_t dstlen, const char *src, int tabsize)
215         size_t size, pos;
217         for (size = pos = 0; size < dstlen - 1 && src[pos]; pos++) {
218                 if (src[pos] == '\t') {
219                         size_t expanded = tabsize - (size % tabsize);
221                         if (expanded + size >= dstlen - 1)
222                                 expanded = dstlen - size - 1;
223                         memcpy(dst + size, "        ", expanded);
224                         size += expanded;
225                 } else {
226                         dst[size++] = src[pos];
227                 }
228         }
230         dst[size] = 0;
231         return pos;
234 static char *
235 chomp_string(char *name)
237         int namelen;
239         while (isspace(*name))
240                 name++;
242         namelen = strlen(name) - 1;
243         while (namelen > 0 && isspace(name[namelen]))
244                 name[namelen--] = 0;
246         return name;
249 static bool
250 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
252         va_list args;
253         size_t pos = bufpos ? *bufpos : 0;
255         va_start(args, fmt);
256         pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
257         va_end(args);
259         if (bufpos)
260                 *bufpos = pos;
262         return pos >= bufsize ? FALSE : TRUE;
265 #define string_format(buf, fmt, args...) \
266         string_nformat(buf, sizeof(buf), NULL, fmt, args)
268 #define string_format_from(buf, from, fmt, args...) \
269         string_nformat(buf, sizeof(buf), from, fmt, args)
271 static int
272 string_enum_compare(const char *str1, const char *str2, int len)
274         size_t i;
276 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
278         /* Diff-Header == DIFF_HEADER */
279         for (i = 0; i < len; i++) {
280                 if (toupper(str1[i]) == toupper(str2[i]))
281                         continue;
283                 if (string_enum_sep(str1[i]) &&
284                     string_enum_sep(str2[i]))
285                         continue;
287                 return str1[i] - str2[i];
288         }
290         return 0;
293 #define enum_equals(entry, str, len) \
294         ((entry).namelen == (len) && !string_enum_compare((entry).name, str, len))
296 struct enum_map {
297         const char *name;
298         int namelen;
299         int value;
300 };
302 #define ENUM_MAP(name, value) { name, STRING_SIZE(name), value }
304 static char *
305 enum_map_name(const char *name, size_t namelen)
307         static char buf[SIZEOF_STR];
308         int bufpos;
310         for (bufpos = 0; bufpos <= namelen; bufpos++) {
311                 buf[bufpos] = tolower(name[bufpos]);
312                 if (buf[bufpos] == '_')
313                         buf[bufpos] = '-';
314         }
316         buf[bufpos] = 0;
317         return buf;
320 #define enum_name(entry) enum_map_name((entry).name, (entry).namelen)
322 static bool
323 map_enum_do(const struct enum_map *map, size_t map_size, int *value, const char *name)
325         size_t namelen = strlen(name);
326         int i;
328         for (i = 0; i < map_size; i++)
329                 if (enum_equals(map[i], name, namelen)) {
330                         *value = map[i].value;
331                         return TRUE;
332                 }
334         return FALSE;
337 #define map_enum(attr, map, name) \
338         map_enum_do(map, ARRAY_SIZE(map), attr, name)
340 #define prefixcmp(str1, str2) \
341         strncmp(str1, str2, STRING_SIZE(str2))
343 static inline int
344 suffixcmp(const char *str, int slen, const char *suffix)
346         size_t len = slen >= 0 ? slen : strlen(str);
347         size_t suffixlen = strlen(suffix);
349         return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
353 /*
354  * Unicode / UTF-8 handling
355  *
356  * NOTE: Much of the following code for dealing with Unicode is derived from
357  * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
358  * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
359  */
361 static inline int
362 unicode_width(unsigned long c, int tab_size)
364         if (c >= 0x1100 &&
365            (c <= 0x115f                         /* Hangul Jamo */
366             || c == 0x2329
367             || c == 0x232a
368             || (c >= 0x2e80  && c <= 0xa4cf && c != 0x303f)
369                                                 /* CJK ... Yi */
370             || (c >= 0xac00  && c <= 0xd7a3)    /* Hangul Syllables */
371             || (c >= 0xf900  && c <= 0xfaff)    /* CJK Compatibility Ideographs */
372             || (c >= 0xfe30  && c <= 0xfe6f)    /* CJK Compatibility Forms */
373             || (c >= 0xff00  && c <= 0xff60)    /* Fullwidth Forms */
374             || (c >= 0xffe0  && c <= 0xffe6)
375             || (c >= 0x20000 && c <= 0x2fffd)
376             || (c >= 0x30000 && c <= 0x3fffd)))
377                 return 2;
379         if (c == '\t')
380                 return tab_size;
382         return 1;
385 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
386  * Illegal bytes are set one. */
387 static const unsigned char utf8_bytes[256] = {
388         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,
389         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,
390         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,
391         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,
392         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,
393         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,
394         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,
395         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,
396 };
398 static inline unsigned char
399 utf8_char_length(const char *string, const char *end)
401         int c = *(unsigned char *) string;
403         return utf8_bytes[c];
406 /* Decode UTF-8 multi-byte representation into a Unicode character. */
407 static inline unsigned long
408 utf8_to_unicode(const char *string, size_t length)
410         unsigned long unicode;
412         switch (length) {
413         case 1:
414                 unicode  =   string[0];
415                 break;
416         case 2:
417                 unicode  =  (string[0] & 0x1f) << 6;
418                 unicode +=  (string[1] & 0x3f);
419                 break;
420         case 3:
421                 unicode  =  (string[0] & 0x0f) << 12;
422                 unicode += ((string[1] & 0x3f) << 6);
423                 unicode +=  (string[2] & 0x3f);
424                 break;
425         case 4:
426                 unicode  =  (string[0] & 0x0f) << 18;
427                 unicode += ((string[1] & 0x3f) << 12);
428                 unicode += ((string[2] & 0x3f) << 6);
429                 unicode +=  (string[3] & 0x3f);
430                 break;
431         case 5:
432                 unicode  =  (string[0] & 0x0f) << 24;
433                 unicode += ((string[1] & 0x3f) << 18);
434                 unicode += ((string[2] & 0x3f) << 12);
435                 unicode += ((string[3] & 0x3f) << 6);
436                 unicode +=  (string[4] & 0x3f);
437                 break;
438         case 6:
439                 unicode  =  (string[0] & 0x01) << 30;
440                 unicode += ((string[1] & 0x3f) << 24);
441                 unicode += ((string[2] & 0x3f) << 18);
442                 unicode += ((string[3] & 0x3f) << 12);
443                 unicode += ((string[4] & 0x3f) << 6);
444                 unicode +=  (string[5] & 0x3f);
445                 break;
446         default:
447                 return 0;
448         }
450         /* Invalid characters could return the special 0xfffd value but NUL
451          * should be just as good. */
452         return unicode > 0xffff ? 0 : unicode;
455 /* Calculates how much of string can be shown within the given maximum width
456  * and sets trimmed parameter to non-zero value if all of string could not be
457  * shown. If the reserve flag is TRUE, it will reserve at least one
458  * trailing character, which can be useful when drawing a delimiter.
459  *
460  * Returns the number of bytes to output from string to satisfy max_width. */
461 static size_t
462 utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve, int tab_size)
464         const char *string = *start;
465         const char *end = strchr(string, '\0');
466         unsigned char last_bytes = 0;
467         size_t last_ucwidth = 0;
469         *width = 0;
470         *trimmed = 0;
472         while (string < end) {
473                 unsigned char bytes = utf8_char_length(string, end);
474                 size_t ucwidth;
475                 unsigned long unicode;
477                 if (string + bytes > end)
478                         break;
480                 /* Change representation to figure out whether
481                  * it is a single- or double-width character. */
483                 unicode = utf8_to_unicode(string, bytes);
484                 /* FIXME: Graceful handling of invalid Unicode character. */
485                 if (!unicode)
486                         break;
488                 ucwidth = unicode_width(unicode, tab_size);
489                 if (skip > 0) {
490                         skip -= ucwidth <= skip ? ucwidth : skip;
491                         *start += bytes;
492                 }
493                 *width  += ucwidth;
494                 if (*width > max_width) {
495                         *trimmed = 1;
496                         *width -= ucwidth;
497                         if (reserve && *width == max_width) {
498                                 string -= last_bytes;
499                                 *width -= last_ucwidth;
500                         }
501                         break;
502                 }
504                 string  += bytes;
505                 last_bytes = ucwidth ? bytes : 0;
506                 last_ucwidth = ucwidth;
507         }
509         return string - *start;
513 #define DATE_INFO \
514         DATE_(NO), \
515         DATE_(DEFAULT), \
516         DATE_(LOCAL), \
517         DATE_(RELATIVE), \
518         DATE_(SHORT)
520 enum date {
521 #define DATE_(name) DATE_##name
522         DATE_INFO
523 #undef  DATE_
524 };
526 static const struct enum_map date_map[] = {
527 #define DATE_(name) ENUM_MAP(#name, DATE_##name)
528         DATE_INFO
529 #undef  DATE_
530 };
532 struct time {
533         time_t sec;
534         int tz;
535 };
537 static inline int timecmp(const struct time *t1, const struct time *t2)
539         return t1->sec - t2->sec;
542 static const char *
543 mkdate(const struct time *time, enum date date)
545         static char buf[DATE_COLS + 1];
546         static const struct enum_map reldate[] = {
547                 { "second", 1,                  60 * 2 },
548                 { "minute", 60,                 60 * 60 * 2 },
549                 { "hour",   60 * 60,            60 * 60 * 24 * 2 },
550                 { "day",    60 * 60 * 24,       60 * 60 * 24 * 7 * 2 },
551                 { "week",   60 * 60 * 24 * 7,   60 * 60 * 24 * 7 * 5 },
552                 { "month",  60 * 60 * 24 * 30,  60 * 60 * 24 * 30 * 12 },
553         };
554         struct tm tm;
556         if (!date || !time || !time->sec)
557                 return "";
559         if (date == DATE_RELATIVE) {
560                 struct timeval now;
561                 time_t date = time->sec + time->tz;
562                 time_t seconds;
563                 int i;
565                 gettimeofday(&now, NULL);
566                 seconds = now.tv_sec < date ? date - now.tv_sec : now.tv_sec - date;
567                 for (i = 0; i < ARRAY_SIZE(reldate); i++) {
568                         if (seconds >= reldate[i].value)
569                                 continue;
571                         seconds /= reldate[i].namelen;
572                         if (!string_format(buf, "%ld %s%s %s",
573                                            seconds, reldate[i].name,
574                                            seconds > 1 ? "s" : "",
575                                            now.tv_sec >= date ? "ago" : "ahead"))
576                                 break;
577                         return buf;
578                 }
579         }
581         if (date == DATE_LOCAL) {
582                 time_t date = time->sec + time->tz;
583                 localtime_r(&date, &tm);
584         }
585         else {
586                 gmtime_r(&time->sec, &tm);
587         }
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);
675 static void
676 argv_free(const char *argv[])
678         int argc;
680         if (!argv)
681                 return;
682         for (argc = 0; argv[argc]; argc++)
683                 free((void *) argv[argc]);
684         argv[0] = NULL;
687 static size_t
688 argv_size(const char **argv)
690         int argc = 0;
692         while (argv && argv[argc])
693                 argc++;
695         return argc;
698 DEFINE_ALLOCATOR(argv_realloc, const char *, SIZEOF_ARG)
700 static bool
701 argv_append(const char ***argv, const char *arg)
703         size_t argc = argv_size(*argv);
705         if (!argv_realloc(argv, argc, 2))
706                 return FALSE;
708         (*argv)[argc++] = strdup(arg);
709         (*argv)[argc] = NULL;
710         return TRUE;
713 static bool
714 argv_append_array(const char ***dst_argv, const char *src_argv[])
716         int i;
718         for (i = 0; src_argv && src_argv[i]; i++)
719                 if (!argv_append(dst_argv, src_argv[i]))
720                         return FALSE;
721         return TRUE;
724 static bool
725 argv_copy(const char ***dst, const char *src[])
727         int argc;
729         for (argc = 0; src[argc]; argc++)
730                 if (!argv_append(dst, src[argc]))
731                         return FALSE;
732         return TRUE;
736 /*
737  * Executing external commands.
738  */
740 enum io_type {
741         IO_FD,                  /* File descriptor based IO. */
742         IO_BG,                  /* Execute command in the background. */
743         IO_FG,                  /* Execute command with same std{in,out,err}. */
744         IO_RD,                  /* Read only fork+exec IO. */
745         IO_WR,                  /* Write only fork+exec IO. */
746         IO_AP,                  /* Append fork+exec output to file. */
747 };
749 struct io {
750         int pipe;               /* Pipe end for reading or writing. */
751         pid_t pid;              /* PID of spawned process. */
752         int error;              /* Error status. */
753         char *buf;              /* Read buffer. */
754         size_t bufalloc;        /* Allocated buffer size. */
755         size_t bufsize;         /* Buffer content size. */
756         char *bufpos;           /* Current buffer position. */
757         unsigned int eof:1;     /* Has end of file been reached. */
758 };
760 static void
761 io_init(struct io *io)
763         memset(io, 0, sizeof(*io));
764         io->pipe = -1;
767 static bool
768 io_open(struct io *io, const char *fmt, ...)
770         char name[SIZEOF_STR] = "";
771         bool fits;
772         va_list args;
774         io_init(io);
776         va_start(args, fmt);
777         fits = vsnprintf(name, sizeof(name), fmt, args) < sizeof(name);
778         va_end(args);
780         if (!fits) {
781                 io->error = ENAMETOOLONG;
782                 return FALSE;
783         }
784         io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
785         if (io->pipe == -1)
786                 io->error = errno;
787         return io->pipe != -1;
790 static bool
791 io_kill(struct io *io)
793         return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
796 static bool
797 io_done(struct io *io)
799         pid_t pid = io->pid;
801         if (io->pipe != -1)
802                 close(io->pipe);
803         free(io->buf);
804         io_init(io);
806         while (pid > 0) {
807                 int status;
808                 pid_t waiting = waitpid(pid, &status, 0);
810                 if (waiting < 0) {
811                         if (errno == EINTR)
812                                 continue;
813                         io->error = errno;
814                         return FALSE;
815                 }
817                 return waiting == pid &&
818                        !WIFSIGNALED(status) &&
819                        WIFEXITED(status) &&
820                        !WEXITSTATUS(status);
821         }
823         return TRUE;
826 static bool
827 io_run(struct io *io, enum io_type type, const char *dir, const char *argv[], ...)
829         int pipefds[2] = { -1, -1 };
830         va_list args;
832         io_init(io);
834         if ((type == IO_RD || type == IO_WR) && pipe(pipefds) < 0) {
835                 io->error = errno;
836                 return FALSE;
837         } else if (type == IO_AP) {
838                 va_start(args, argv);
839                 pipefds[1] = va_arg(args, int);
840                 va_end(args);
841         }
843         if ((io->pid = fork())) {
844                 if (io->pid == -1)
845                         io->error = errno;
846                 if (pipefds[!(type == IO_WR)] != -1)
847                         close(pipefds[!(type == IO_WR)]);
848                 if (io->pid != -1) {
849                         io->pipe = pipefds[!!(type == IO_WR)];
850                         return TRUE;
851                 }
853         } else {
854                 if (type != IO_FG) {
855                         int devnull = open("/dev/null", O_RDWR);
856                         int readfd  = type == IO_WR ? pipefds[0] : devnull;
857                         int writefd = (type == IO_RD || type == IO_AP)
858                                                         ? pipefds[1] : devnull;
860                         dup2(readfd,  STDIN_FILENO);
861                         dup2(writefd, STDOUT_FILENO);
862                         dup2(devnull, STDERR_FILENO);
864                         close(devnull);
865                         if (pipefds[0] != -1)
866                                 close(pipefds[0]);
867                         if (pipefds[1] != -1)
868                                 close(pipefds[1]);
869                 }
871                 if (dir && *dir && chdir(dir) == -1)
872                         exit(errno);
874                 execvp(argv[0], (char *const*) argv);
875                 exit(errno);
876         }
878         if (pipefds[!!(type == IO_WR)] != -1)
879                 close(pipefds[!!(type == IO_WR)]);
880         return FALSE;
883 static bool
884 io_complete(enum io_type type, const char **argv, const char *dir, int fd)
886         struct io io;
888         return io_run(&io, type, dir, argv, fd) && io_done(&io);
891 static bool
892 io_run_bg(const char **argv)
894         return io_complete(IO_BG, argv, NULL, -1);
897 static bool
898 io_run_fg(const char **argv, const char *dir)
900         return io_complete(IO_FG, argv, dir, -1);
903 static bool
904 io_run_append(const char **argv, int fd)
906         return io_complete(IO_AP, argv, NULL, fd);
909 static bool
910 io_eof(struct io *io)
912         return io->eof;
915 static int
916 io_error(struct io *io)
918         return io->error;
921 static char *
922 io_strerror(struct io *io)
924         return strerror(io->error);
927 static bool
928 io_can_read(struct io *io)
930         struct timeval tv = { 0, 500 };
931         fd_set fds;
933         FD_ZERO(&fds);
934         FD_SET(io->pipe, &fds);
936         return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
939 static ssize_t
940 io_read(struct io *io, void *buf, size_t bufsize)
942         do {
943                 ssize_t readsize = read(io->pipe, buf, bufsize);
945                 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
946                         continue;
947                 else if (readsize == -1)
948                         io->error = errno;
949                 else if (readsize == 0)
950                         io->eof = 1;
951                 return readsize;
952         } while (1);
955 DEFINE_ALLOCATOR(io_realloc_buf, char, BUFSIZ)
957 static char *
958 io_get(struct io *io, int c, bool can_read)
960         char *eol;
961         ssize_t readsize;
963         while (TRUE) {
964                 if (io->bufsize > 0) {
965                         eol = memchr(io->bufpos, c, io->bufsize);
966                         if (eol) {
967                                 char *line = io->bufpos;
969                                 *eol = 0;
970                                 io->bufpos = eol + 1;
971                                 io->bufsize -= io->bufpos - line;
972                                 return line;
973                         }
974                 }
976                 if (io_eof(io)) {
977                         if (io->bufsize) {
978                                 io->bufpos[io->bufsize] = 0;
979                                 io->bufsize = 0;
980                                 return io->bufpos;
981                         }
982                         return NULL;
983                 }
985                 if (!can_read)
986                         return NULL;
988                 if (io->bufsize > 0 && io->bufpos > io->buf)
989                         memmove(io->buf, io->bufpos, io->bufsize);
991                 if (io->bufalloc == io->bufsize) {
992                         if (!io_realloc_buf(&io->buf, io->bufalloc, BUFSIZ))
993                                 return NULL;
994                         io->bufalloc += BUFSIZ;
995                 }
997                 io->bufpos = io->buf;
998                 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
999                 if (io_error(io))
1000                         return NULL;
1001                 io->bufsize += readsize;
1002         }
1005 static bool
1006 io_write(struct io *io, const void *buf, size_t bufsize)
1008         size_t written = 0;
1010         while (!io_error(io) && written < bufsize) {
1011                 ssize_t size;
1013                 size = write(io->pipe, buf + written, bufsize - written);
1014                 if (size < 0 && (errno == EAGAIN || errno == EINTR))
1015                         continue;
1016                 else if (size == -1)
1017                         io->error = errno;
1018                 else
1019                         written += size;
1020         }
1022         return written == bufsize;
1025 static bool
1026 io_read_buf(struct io *io, char buf[], size_t bufsize)
1028         char *result = io_get(io, '\n', TRUE);
1030         if (result) {
1031                 result = chomp_string(result);
1032                 string_ncopy_do(buf, bufsize, result, strlen(result));
1033         }
1035         return io_done(io) && result;
1038 static bool
1039 io_run_buf(const char **argv, char buf[], size_t bufsize)
1041         struct io io;
1043         return io_run(&io, IO_RD, NULL, argv) && io_read_buf(&io, buf, bufsize);
1046 static int
1047 io_load(struct io *io, const char *separators,
1048         int (*read_property)(char *, size_t, char *, size_t))
1050         char *name;
1051         int state = OK;
1053         while (state == OK && (name = io_get(io, '\n', TRUE))) {
1054                 char *value;
1055                 size_t namelen;
1056                 size_t valuelen;
1058                 name = chomp_string(name);
1059                 namelen = strcspn(name, separators);
1061                 if (name[namelen]) {
1062                         name[namelen] = 0;
1063                         value = chomp_string(name + namelen + 1);
1064                         valuelen = strlen(value);
1066                 } else {
1067                         value = "";
1068                         valuelen = 0;
1069                 }
1071                 state = read_property(name, namelen, value, valuelen);
1072         }
1074         if (state != ERR && io_error(io))
1075                 state = ERR;
1076         io_done(io);
1078         return state;
1081 static int
1082 io_run_load(const char **argv, const char *separators,
1083             int (*read_property)(char *, size_t, char *, size_t))
1085         struct io io;
1087         if (!io_run(&io, IO_RD, NULL, argv))
1088                 return ERR;
1089         return io_load(&io, separators, read_property);
1093 /*
1094  * User requests
1095  */
1097 #define REQ_INFO \
1098         /* XXX: Keep the view request first and in sync with views[]. */ \
1099         REQ_GROUP("View switching") \
1100         REQ_(VIEW_MAIN,         "Show main view"), \
1101         REQ_(VIEW_DIFF,         "Show diff view"), \
1102         REQ_(VIEW_LOG,          "Show log view"), \
1103         REQ_(VIEW_TREE,         "Show tree view"), \
1104         REQ_(VIEW_BLOB,         "Show blob view"), \
1105         REQ_(VIEW_BLAME,        "Show blame view"), \
1106         REQ_(VIEW_BRANCH,       "Show branch view"), \
1107         REQ_(VIEW_HELP,         "Show help page"), \
1108         REQ_(VIEW_PAGER,        "Show pager view"), \
1109         REQ_(VIEW_STATUS,       "Show status view"), \
1110         REQ_(VIEW_STAGE,        "Show stage view"), \
1111         \
1112         REQ_GROUP("View manipulation") \
1113         REQ_(ENTER,             "Enter current line and scroll"), \
1114         REQ_(NEXT,              "Move to next"), \
1115         REQ_(PREVIOUS,          "Move to previous"), \
1116         REQ_(PARENT,            "Move to parent"), \
1117         REQ_(VIEW_NEXT,         "Move focus to next view"), \
1118         REQ_(REFRESH,           "Reload and refresh"), \
1119         REQ_(MAXIMIZE,          "Maximize the current view"), \
1120         REQ_(VIEW_CLOSE,        "Close the current view"), \
1121         REQ_(QUIT,              "Close all views and quit"), \
1122         \
1123         REQ_GROUP("View specific requests") \
1124         REQ_(STATUS_UPDATE,     "Update file status"), \
1125         REQ_(STATUS_REVERT,     "Revert file changes"), \
1126         REQ_(STATUS_MERGE,      "Merge file using external tool"), \
1127         REQ_(STAGE_NEXT,        "Find next chunk to stage"), \
1128         \
1129         REQ_GROUP("Cursor navigation") \
1130         REQ_(MOVE_UP,           "Move cursor one line up"), \
1131         REQ_(MOVE_DOWN,         "Move cursor one line down"), \
1132         REQ_(MOVE_PAGE_DOWN,    "Move cursor one page down"), \
1133         REQ_(MOVE_PAGE_UP,      "Move cursor one page up"), \
1134         REQ_(MOVE_FIRST_LINE,   "Move cursor to first line"), \
1135         REQ_(MOVE_LAST_LINE,    "Move cursor to last line"), \
1136         \
1137         REQ_GROUP("Scrolling") \
1138         REQ_(SCROLL_LEFT,       "Scroll two columns left"), \
1139         REQ_(SCROLL_RIGHT,      "Scroll two columns right"), \
1140         REQ_(SCROLL_LINE_UP,    "Scroll one line up"), \
1141         REQ_(SCROLL_LINE_DOWN,  "Scroll one line down"), \
1142         REQ_(SCROLL_PAGE_UP,    "Scroll one page up"), \
1143         REQ_(SCROLL_PAGE_DOWN,  "Scroll one page down"), \
1144         \
1145         REQ_GROUP("Searching") \
1146         REQ_(SEARCH,            "Search the view"), \
1147         REQ_(SEARCH_BACK,       "Search backwards in the view"), \
1148         REQ_(FIND_NEXT,         "Find next search match"), \
1149         REQ_(FIND_PREV,         "Find previous search match"), \
1150         \
1151         REQ_GROUP("Option manipulation") \
1152         REQ_(OPTIONS,           "Open option menu"), \
1153         REQ_(TOGGLE_LINENO,     "Toggle line numbers"), \
1154         REQ_(TOGGLE_DATE,       "Toggle date display"), \
1155         REQ_(TOGGLE_DATE_SHORT, "Toggle short (date-only) dates"), \
1156         REQ_(TOGGLE_AUTHOR,     "Toggle author display"), \
1157         REQ_(TOGGLE_REV_GRAPH,  "Toggle revision graph visualization"), \
1158         REQ_(TOGGLE_REFS,       "Toggle reference display (tags/branches)"), \
1159         REQ_(TOGGLE_SORT_ORDER, "Toggle ascending/descending sort order"), \
1160         REQ_(TOGGLE_SORT_FIELD, "Toggle field to sort by"), \
1161         \
1162         REQ_GROUP("Misc") \
1163         REQ_(PROMPT,            "Bring up the prompt"), \
1164         REQ_(SCREEN_REDRAW,     "Redraw the screen"), \
1165         REQ_(SHOW_VERSION,      "Show version information"), \
1166         REQ_(STOP_LOADING,      "Stop all loading views"), \
1167         REQ_(EDIT,              "Open in editor"), \
1168         REQ_(NONE,              "Do nothing")
1171 /* User action requests. */
1172 enum request {
1173 #define REQ_GROUP(help)
1174 #define REQ_(req, help) REQ_##req
1176         /* Offset all requests to avoid conflicts with ncurses getch values. */
1177         REQ_UNKNOWN = KEY_MAX + 1,
1178         REQ_OFFSET,
1179         REQ_INFO
1181 #undef  REQ_GROUP
1182 #undef  REQ_
1183 };
1185 struct request_info {
1186         enum request request;
1187         const char *name;
1188         int namelen;
1189         const char *help;
1190 };
1192 static const struct request_info req_info[] = {
1193 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
1194 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
1195         REQ_INFO
1196 #undef  REQ_GROUP
1197 #undef  REQ_
1198 };
1200 static enum request
1201 get_request(const char *name)
1203         int namelen = strlen(name);
1204         int i;
1206         for (i = 0; i < ARRAY_SIZE(req_info); i++)
1207                 if (enum_equals(req_info[i], name, namelen))
1208                         return req_info[i].request;
1210         return REQ_UNKNOWN;
1214 /*
1215  * Options
1216  */
1218 /* Option and state variables. */
1219 static enum date opt_date               = DATE_DEFAULT;
1220 static enum author opt_author           = AUTHOR_DEFAULT;
1221 static bool opt_line_number             = FALSE;
1222 static bool opt_line_graphics           = TRUE;
1223 static bool opt_rev_graph               = FALSE;
1224 static bool opt_show_refs               = TRUE;
1225 static int opt_num_interval             = 5;
1226 static double opt_hscroll               = 0.50;
1227 static double opt_scale_split_view      = 2.0 / 3.0;
1228 static int opt_tab_size                 = 8;
1229 static int opt_author_cols              = AUTHOR_COLS;
1230 static char opt_path[SIZEOF_STR]        = "";
1231 static char opt_file[SIZEOF_STR]        = "";
1232 static char opt_ref[SIZEOF_REF]         = "";
1233 static char opt_head[SIZEOF_REF]        = "";
1234 static char opt_remote[SIZEOF_REF]      = "";
1235 static char opt_encoding[20]            = "UTF-8";
1236 static iconv_t opt_iconv_in             = ICONV_NONE;
1237 static iconv_t opt_iconv_out            = ICONV_NONE;
1238 static char opt_search[SIZEOF_STR]      = "";
1239 static char opt_cdup[SIZEOF_STR]        = "";
1240 static char opt_prefix[SIZEOF_STR]      = "";
1241 static char opt_git_dir[SIZEOF_STR]     = "";
1242 static signed char opt_is_inside_work_tree      = -1; /* set to TRUE or FALSE */
1243 static char opt_editor[SIZEOF_STR]      = "";
1244 static FILE *opt_tty                    = NULL;
1245 static const char **opt_diff_args       = NULL;
1246 static const char **opt_rev_args        = NULL;
1247 static const char **opt_file_args       = NULL;
1249 #define is_initial_commit()     (!get_ref_head())
1250 #define is_head_commit(rev)     (!strcmp((rev), "HEAD") || (get_ref_head() && !strcmp(rev, get_ref_head()->id)))
1253 /*
1254  * Line-oriented content detection.
1255  */
1257 #define LINE_INFO \
1258 LINE(DIFF_HEADER,  "diff --git ",       COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1259 LINE(DIFF_CHUNK,   "@@",                COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1260 LINE(DIFF_ADD,     "+",                 COLOR_GREEN,    COLOR_DEFAULT,  0), \
1261 LINE(DIFF_DEL,     "-",                 COLOR_RED,      COLOR_DEFAULT,  0), \
1262 LINE(DIFF_INDEX,        "index ",         COLOR_BLUE,   COLOR_DEFAULT,  0), \
1263 LINE(DIFF_OLDMODE,      "old file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1264 LINE(DIFF_NEWMODE,      "new file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1265 LINE(DIFF_COPY_FROM,    "copy from",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
1266 LINE(DIFF_COPY_TO,      "copy to",        COLOR_YELLOW, COLOR_DEFAULT,  0), \
1267 LINE(DIFF_RENAME_FROM,  "rename from",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
1268 LINE(DIFF_RENAME_TO,    "rename to",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
1269 LINE(DIFF_SIMILARITY,   "similarity ",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
1270 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1271 LINE(DIFF_TREE,         "diff-tree ",     COLOR_BLUE,   COLOR_DEFAULT,  0), \
1272 LINE(PP_AUTHOR,    "Author: ",          COLOR_CYAN,     COLOR_DEFAULT,  0), \
1273 LINE(PP_COMMIT,    "Commit: ",          COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1274 LINE(PP_MERGE,     "Merge: ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
1275 LINE(PP_DATE,      "Date:   ",          COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1276 LINE(PP_ADATE,     "AuthorDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1277 LINE(PP_CDATE,     "CommitDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1278 LINE(PP_REFS,      "Refs: ",            COLOR_RED,      COLOR_DEFAULT,  0), \
1279 LINE(COMMIT,       "commit ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
1280 LINE(PARENT,       "parent ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
1281 LINE(TREE,         "tree ",             COLOR_BLUE,     COLOR_DEFAULT,  0), \
1282 LINE(AUTHOR,       "author ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
1283 LINE(COMMITTER,    "committer ",        COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1284 LINE(SIGNOFF,      "    Signed-off-by", COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1285 LINE(ACKED,        "    Acked-by",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1286 LINE(TESTED,       "    Tested-by",     COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1287 LINE(REVIEWED,     "    Reviewed-by",   COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1288 LINE(DEFAULT,      "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
1289 LINE(CURSOR,       "",                  COLOR_WHITE,    COLOR_GREEN,    A_BOLD), \
1290 LINE(STATUS,       "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
1291 LINE(DELIMITER,    "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1292 LINE(DATE,         "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
1293 LINE(MODE,         "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1294 LINE(LINE_NUMBER,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1295 LINE(TITLE_BLUR,   "",                  COLOR_WHITE,    COLOR_BLUE,     0), \
1296 LINE(TITLE_FOCUS,  "",                  COLOR_WHITE,    COLOR_BLUE,     A_BOLD), \
1297 LINE(MAIN_COMMIT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
1298 LINE(MAIN_TAG,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
1299 LINE(MAIN_LOCAL_TAG,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1300 LINE(MAIN_REMOTE,  "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1301 LINE(MAIN_TRACKED, "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_BOLD), \
1302 LINE(MAIN_REF,     "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1303 LINE(MAIN_HEAD,    "",                  COLOR_CYAN,     COLOR_DEFAULT,  A_BOLD), \
1304 LINE(MAIN_REVGRAPH,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1305 LINE(TREE_HEAD,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_BOLD), \
1306 LINE(TREE_DIR,     "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_NORMAL), \
1307 LINE(TREE_FILE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
1308 LINE(STAT_HEAD,    "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1309 LINE(STAT_SECTION, "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1310 LINE(STAT_NONE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
1311 LINE(STAT_STAGED,  "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1312 LINE(STAT_UNSTAGED,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1313 LINE(STAT_UNTRACKED,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1314 LINE(HELP_KEYMAP,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1315 LINE(HELP_GROUP,   "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
1316 LINE(BLAME_ID,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0)
1318 enum line_type {
1319 #define LINE(type, line, fg, bg, attr) \
1320         LINE_##type
1321         LINE_INFO,
1322         LINE_NONE
1323 #undef  LINE
1324 };
1326 struct line_info {
1327         const char *name;       /* Option name. */
1328         int namelen;            /* Size of option name. */
1329         const char *line;       /* The start of line to match. */
1330         int linelen;            /* Size of string to match. */
1331         int fg, bg, attr;       /* Color and text attributes for the lines. */
1332 };
1334 static struct line_info line_info[] = {
1335 #define LINE(type, line, fg, bg, attr) \
1336         { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1337         LINE_INFO
1338 #undef  LINE
1339 };
1341 static enum line_type
1342 get_line_type(const char *line)
1344         int linelen = strlen(line);
1345         enum line_type type;
1347         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1348                 /* Case insensitive search matches Signed-off-by lines better. */
1349                 if (linelen >= line_info[type].linelen &&
1350                     !strncasecmp(line_info[type].line, line, line_info[type].linelen))
1351                         return type;
1353         return LINE_DEFAULT;
1356 static inline int
1357 get_line_attr(enum line_type type)
1359         assert(type < ARRAY_SIZE(line_info));
1360         return COLOR_PAIR(type) | line_info[type].attr;
1363 static struct line_info *
1364 get_line_info(const char *name)
1366         size_t namelen = strlen(name);
1367         enum line_type type;
1369         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1370                 if (enum_equals(line_info[type], name, namelen))
1371                         return &line_info[type];
1373         return NULL;
1376 static void
1377 init_colors(void)
1379         int default_bg = line_info[LINE_DEFAULT].bg;
1380         int default_fg = line_info[LINE_DEFAULT].fg;
1381         enum line_type type;
1383         start_color();
1385         if (assume_default_colors(default_fg, default_bg) == ERR) {
1386                 default_bg = COLOR_BLACK;
1387                 default_fg = COLOR_WHITE;
1388         }
1390         for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1391                 struct line_info *info = &line_info[type];
1392                 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1393                 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1395                 init_pair(type, fg, bg);
1396         }
1399 struct line {
1400         enum line_type type;
1402         /* State flags */
1403         unsigned int selected:1;
1404         unsigned int dirty:1;
1405         unsigned int cleareol:1;
1406         unsigned int other:16;
1408         void *data;             /* User data */
1409 };
1412 /*
1413  * Keys
1414  */
1416 struct keybinding {
1417         int alias;
1418         enum request request;
1419 };
1421 static struct keybinding default_keybindings[] = {
1422         /* View switching */
1423         { 'm',          REQ_VIEW_MAIN },
1424         { 'd',          REQ_VIEW_DIFF },
1425         { 'l',          REQ_VIEW_LOG },
1426         { 't',          REQ_VIEW_TREE },
1427         { 'f',          REQ_VIEW_BLOB },
1428         { 'B',          REQ_VIEW_BLAME },
1429         { 'H',          REQ_VIEW_BRANCH },
1430         { 'p',          REQ_VIEW_PAGER },
1431         { 'h',          REQ_VIEW_HELP },
1432         { 'S',          REQ_VIEW_STATUS },
1433         { 'c',          REQ_VIEW_STAGE },
1435         /* View manipulation */
1436         { 'q',          REQ_VIEW_CLOSE },
1437         { KEY_TAB,      REQ_VIEW_NEXT },
1438         { KEY_RETURN,   REQ_ENTER },
1439         { KEY_UP,       REQ_PREVIOUS },
1440         { KEY_DOWN,     REQ_NEXT },
1441         { 'R',          REQ_REFRESH },
1442         { KEY_F(5),     REQ_REFRESH },
1443         { 'O',          REQ_MAXIMIZE },
1445         /* Cursor navigation */
1446         { 'k',          REQ_MOVE_UP },
1447         { 'j',          REQ_MOVE_DOWN },
1448         { KEY_HOME,     REQ_MOVE_FIRST_LINE },
1449         { KEY_END,      REQ_MOVE_LAST_LINE },
1450         { KEY_NPAGE,    REQ_MOVE_PAGE_DOWN },
1451         { ' ',          REQ_MOVE_PAGE_DOWN },
1452         { KEY_PPAGE,    REQ_MOVE_PAGE_UP },
1453         { 'b',          REQ_MOVE_PAGE_UP },
1454         { '-',          REQ_MOVE_PAGE_UP },
1456         /* Scrolling */
1457         { KEY_LEFT,     REQ_SCROLL_LEFT },
1458         { KEY_RIGHT,    REQ_SCROLL_RIGHT },
1459         { KEY_IC,       REQ_SCROLL_LINE_UP },
1460         { KEY_DC,       REQ_SCROLL_LINE_DOWN },
1461         { 'w',          REQ_SCROLL_PAGE_UP },
1462         { 's',          REQ_SCROLL_PAGE_DOWN },
1464         /* Searching */
1465         { '/',          REQ_SEARCH },
1466         { '?',          REQ_SEARCH_BACK },
1467         { 'n',          REQ_FIND_NEXT },
1468         { 'N',          REQ_FIND_PREV },
1470         /* Misc */
1471         { 'Q',          REQ_QUIT },
1472         { 'z',          REQ_STOP_LOADING },
1473         { 'v',          REQ_SHOW_VERSION },
1474         { 'r',          REQ_SCREEN_REDRAW },
1475         { 'o',          REQ_OPTIONS },
1476         { '.',          REQ_TOGGLE_LINENO },
1477         { 'D',          REQ_TOGGLE_DATE },
1478         { 'A',          REQ_TOGGLE_AUTHOR },
1479         { 'g',          REQ_TOGGLE_REV_GRAPH },
1480         { 'F',          REQ_TOGGLE_REFS },
1481         { 'I',          REQ_TOGGLE_SORT_ORDER },
1482         { 'i',          REQ_TOGGLE_SORT_FIELD },
1483         { ':',          REQ_PROMPT },
1484         { 'u',          REQ_STATUS_UPDATE },
1485         { '!',          REQ_STATUS_REVERT },
1486         { 'M',          REQ_STATUS_MERGE },
1487         { '@',          REQ_STAGE_NEXT },
1488         { ',',          REQ_PARENT },
1489         { 'e',          REQ_EDIT },
1490 };
1492 #define KEYMAP_INFO \
1493         KEYMAP_(GENERIC), \
1494         KEYMAP_(MAIN), \
1495         KEYMAP_(DIFF), \
1496         KEYMAP_(LOG), \
1497         KEYMAP_(TREE), \
1498         KEYMAP_(BLOB), \
1499         KEYMAP_(BLAME), \
1500         KEYMAP_(BRANCH), \
1501         KEYMAP_(PAGER), \
1502         KEYMAP_(HELP), \
1503         KEYMAP_(STATUS), \
1504         KEYMAP_(STAGE)
1506 enum keymap {
1507 #define KEYMAP_(name) KEYMAP_##name
1508         KEYMAP_INFO
1509 #undef  KEYMAP_
1510 };
1512 static const struct enum_map keymap_table[] = {
1513 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1514         KEYMAP_INFO
1515 #undef  KEYMAP_
1516 };
1518 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1520 struct keybinding_table {
1521         struct keybinding *data;
1522         size_t size;
1523 };
1525 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1527 static void
1528 add_keybinding(enum keymap keymap, enum request request, int key)
1530         struct keybinding_table *table = &keybindings[keymap];
1531         size_t i;
1533         for (i = 0; i < keybindings[keymap].size; i++) {
1534                 if (keybindings[keymap].data[i].alias == key) {
1535                         keybindings[keymap].data[i].request = request;
1536                         return;
1537                 }
1538         }
1540         table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1541         if (!table->data)
1542                 die("Failed to allocate keybinding");
1543         table->data[table->size].alias = key;
1544         table->data[table->size++].request = request;
1546         if (request == REQ_NONE && keymap == KEYMAP_GENERIC) {
1547                 int i;
1549                 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1550                         if (default_keybindings[i].alias == key)
1551                                 default_keybindings[i].request = REQ_NONE;
1552         }
1555 /* Looks for a key binding first in the given map, then in the generic map, and
1556  * lastly in the default keybindings. */
1557 static enum request
1558 get_keybinding(enum keymap keymap, int key)
1560         size_t i;
1562         for (i = 0; i < keybindings[keymap].size; i++)
1563                 if (keybindings[keymap].data[i].alias == key)
1564                         return keybindings[keymap].data[i].request;
1566         for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1567                 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1568                         return keybindings[KEYMAP_GENERIC].data[i].request;
1570         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1571                 if (default_keybindings[i].alias == key)
1572                         return default_keybindings[i].request;
1574         return (enum request) key;
1578 struct key {
1579         const char *name;
1580         int value;
1581 };
1583 static const struct key key_table[] = {
1584         { "Enter",      KEY_RETURN },
1585         { "Space",      ' ' },
1586         { "Backspace",  KEY_BACKSPACE },
1587         { "Tab",        KEY_TAB },
1588         { "Escape",     KEY_ESC },
1589         { "Left",       KEY_LEFT },
1590         { "Right",      KEY_RIGHT },
1591         { "Up",         KEY_UP },
1592         { "Down",       KEY_DOWN },
1593         { "Insert",     KEY_IC },
1594         { "Delete",     KEY_DC },
1595         { "Hash",       '#' },
1596         { "Home",       KEY_HOME },
1597         { "End",        KEY_END },
1598         { "PageUp",     KEY_PPAGE },
1599         { "PageDown",   KEY_NPAGE },
1600         { "F1",         KEY_F(1) },
1601         { "F2",         KEY_F(2) },
1602         { "F3",         KEY_F(3) },
1603         { "F4",         KEY_F(4) },
1604         { "F5",         KEY_F(5) },
1605         { "F6",         KEY_F(6) },
1606         { "F7",         KEY_F(7) },
1607         { "F8",         KEY_F(8) },
1608         { "F9",         KEY_F(9) },
1609         { "F10",        KEY_F(10) },
1610         { "F11",        KEY_F(11) },
1611         { "F12",        KEY_F(12) },
1612 };
1614 static int
1615 get_key_value(const char *name)
1617         int i;
1619         for (i = 0; i < ARRAY_SIZE(key_table); i++)
1620                 if (!strcasecmp(key_table[i].name, name))
1621                         return key_table[i].value;
1623         if (strlen(name) == 1 && isprint(*name))
1624                 return (int) *name;
1626         return ERR;
1629 static const char *
1630 get_key_name(int key_value)
1632         static char key_char[] = "'X'";
1633         const char *seq = NULL;
1634         int key;
1636         for (key = 0; key < ARRAY_SIZE(key_table); key++)
1637                 if (key_table[key].value == key_value)
1638                         seq = key_table[key].name;
1640         if (seq == NULL &&
1641             key_value < 127 &&
1642             isprint(key_value)) {
1643                 key_char[1] = (char) key_value;
1644                 seq = key_char;
1645         }
1647         return seq ? seq : "(no key)";
1650 static bool
1651 append_key(char *buf, size_t *pos, const struct keybinding *keybinding)
1653         const char *sep = *pos > 0 ? ", " : "";
1654         const char *keyname = get_key_name(keybinding->alias);
1656         return string_nformat(buf, BUFSIZ, pos, "%s%s", sep, keyname);
1659 static bool
1660 append_keymap_request_keys(char *buf, size_t *pos, enum request request,
1661                            enum keymap keymap, bool all)
1663         int i;
1665         for (i = 0; i < keybindings[keymap].size; i++) {
1666                 if (keybindings[keymap].data[i].request == request) {
1667                         if (!append_key(buf, pos, &keybindings[keymap].data[i]))
1668                                 return FALSE;
1669                         if (!all)
1670                                 break;
1671                 }
1672         }
1674         return TRUE;
1677 #define get_key(keymap, request) get_keys(keymap, request, FALSE)
1679 static const char *
1680 get_keys(enum keymap keymap, enum request request, bool all)
1682         static char buf[BUFSIZ];
1683         size_t pos = 0;
1684         int i;
1686         buf[pos] = 0;
1688         if (!append_keymap_request_keys(buf, &pos, request, keymap, all))
1689                 return "Too many keybindings!";
1690         if (pos > 0 && !all)
1691                 return buf;
1693         if (keymap != KEYMAP_GENERIC) {
1694                 /* Only the generic keymap includes the default keybindings when
1695                  * listing all keys. */
1696                 if (all)
1697                         return buf;
1699                 if (!append_keymap_request_keys(buf, &pos, request, KEYMAP_GENERIC, all))
1700                         return "Too many keybindings!";
1701                 if (pos)
1702                         return buf;
1703         }
1705         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1706                 if (default_keybindings[i].request == request) {
1707                         if (!append_key(buf, &pos, &default_keybindings[i]))
1708                                 return "Too many keybindings!";
1709                         if (!all)
1710                                 return buf;
1711                 }
1712         }
1714         return buf;
1717 struct run_request {
1718         enum keymap keymap;
1719         int key;
1720         const char **argv;
1721 };
1723 static struct run_request *run_request;
1724 static size_t run_requests;
1726 DEFINE_ALLOCATOR(realloc_run_requests, struct run_request, 8)
1728 static enum request
1729 add_run_request(enum keymap keymap, int key, const char **argv)
1731         struct run_request *req;
1733         if (!realloc_run_requests(&run_request, run_requests, 1))
1734                 return REQ_NONE;
1736         req = &run_request[run_requests];
1737         req->keymap = keymap;
1738         req->key = key;
1739         req->argv = NULL;
1741         if (!argv_copy(&req->argv, argv))
1742                 return REQ_NONE;
1744         return REQ_NONE + ++run_requests;
1747 static struct run_request *
1748 get_run_request(enum request request)
1750         if (request <= REQ_NONE)
1751                 return NULL;
1752         return &run_request[request - REQ_NONE - 1];
1755 static void
1756 add_builtin_run_requests(void)
1758         const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1759         const char *checkout[] = { "git", "checkout", "%(branch)", NULL };
1760         const char *commit[] = { "git", "commit", NULL };
1761         const char *gc[] = { "git", "gc", NULL };
1762         struct run_request reqs[] = {
1763                 { KEYMAP_MAIN,    'C', cherry_pick },
1764                 { KEYMAP_STATUS,  'C', commit },
1765                 { KEYMAP_BRANCH,  'C', checkout },
1766                 { KEYMAP_GENERIC, 'G', gc },
1767         };
1768         int i;
1770         for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1771                 enum request req = get_keybinding(reqs[i].keymap, reqs[i].key);
1773                 if (req != reqs[i].key)
1774                         continue;
1775                 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argv);
1776                 if (req != REQ_NONE)
1777                         add_keybinding(reqs[i].keymap, req, reqs[i].key);
1778         }
1781 /*
1782  * User config file handling.
1783  */
1785 static int   config_lineno;
1786 static bool  config_errors;
1787 static const char *config_msg;
1789 static const struct enum_map color_map[] = {
1790 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1791         COLOR_MAP(DEFAULT),
1792         COLOR_MAP(BLACK),
1793         COLOR_MAP(BLUE),
1794         COLOR_MAP(CYAN),
1795         COLOR_MAP(GREEN),
1796         COLOR_MAP(MAGENTA),
1797         COLOR_MAP(RED),
1798         COLOR_MAP(WHITE),
1799         COLOR_MAP(YELLOW),
1800 };
1802 static const struct enum_map attr_map[] = {
1803 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1804         ATTR_MAP(NORMAL),
1805         ATTR_MAP(BLINK),
1806         ATTR_MAP(BOLD),
1807         ATTR_MAP(DIM),
1808         ATTR_MAP(REVERSE),
1809         ATTR_MAP(STANDOUT),
1810         ATTR_MAP(UNDERLINE),
1811 };
1813 #define set_attribute(attr, name)       map_enum(attr, attr_map, name)
1815 static int parse_step(double *opt, const char *arg)
1817         *opt = atoi(arg);
1818         if (!strchr(arg, '%'))
1819                 return OK;
1821         /* "Shift down" so 100% and 1 does not conflict. */
1822         *opt = (*opt - 1) / 100;
1823         if (*opt >= 1.0) {
1824                 *opt = 0.99;
1825                 config_msg = "Step value larger than 100%";
1826                 return ERR;
1827         }
1828         if (*opt < 0.0) {
1829                 *opt = 1;
1830                 config_msg = "Invalid step value";
1831                 return ERR;
1832         }
1833         return OK;
1836 static int
1837 parse_int(int *opt, const char *arg, int min, int max)
1839         int value = atoi(arg);
1841         if (min <= value && value <= max) {
1842                 *opt = value;
1843                 return OK;
1844         }
1846         config_msg = "Integer value out of bound";
1847         return ERR;
1850 static bool
1851 set_color(int *color, const char *name)
1853         if (map_enum(color, color_map, name))
1854                 return TRUE;
1855         if (!prefixcmp(name, "color"))
1856                 return parse_int(color, name + 5, 0, 255) == OK;
1857         return FALSE;
1860 /* Wants: object fgcolor bgcolor [attribute] */
1861 static int
1862 option_color_command(int argc, const char *argv[])
1864         struct line_info *info;
1866         if (argc < 3) {
1867                 config_msg = "Wrong number of arguments given to color command";
1868                 return ERR;
1869         }
1871         info = get_line_info(argv[0]);
1872         if (!info) {
1873                 static const struct enum_map obsolete[] = {
1874                         ENUM_MAP("main-delim",  LINE_DELIMITER),
1875                         ENUM_MAP("main-date",   LINE_DATE),
1876                         ENUM_MAP("main-author", LINE_AUTHOR),
1877                 };
1878                 int index;
1880                 if (!map_enum(&index, obsolete, argv[0])) {
1881                         config_msg = "Unknown color name";
1882                         return ERR;
1883                 }
1884                 info = &line_info[index];
1885         }
1887         if (!set_color(&info->fg, argv[1]) ||
1888             !set_color(&info->bg, argv[2])) {
1889                 config_msg = "Unknown color";
1890                 return ERR;
1891         }
1893         info->attr = 0;
1894         while (argc-- > 3) {
1895                 int attr;
1897                 if (!set_attribute(&attr, argv[argc])) {
1898                         config_msg = "Unknown attribute";
1899                         return ERR;
1900                 }
1901                 info->attr |= attr;
1902         }
1904         return OK;
1907 static int parse_bool(bool *opt, const char *arg)
1909         *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1910                 ? TRUE : FALSE;
1911         return OK;
1914 static int parse_enum_do(unsigned int *opt, const char *arg,
1915                          const struct enum_map *map, size_t map_size)
1917         bool is_true;
1919         assert(map_size > 1);
1921         if (map_enum_do(map, map_size, (int *) opt, arg))
1922                 return OK;
1924         if (parse_bool(&is_true, arg) != OK)
1925                 return ERR;
1927         *opt = is_true ? map[1].value : map[0].value;
1928         return OK;
1931 #define parse_enum(opt, arg, map) \
1932         parse_enum_do(opt, arg, map, ARRAY_SIZE(map))
1934 static int
1935 parse_string(char *opt, const char *arg, size_t optsize)
1937         int arglen = strlen(arg);
1939         switch (arg[0]) {
1940         case '\"':
1941         case '\'':
1942                 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1943                         config_msg = "Unmatched quotation";
1944                         return ERR;
1945                 }
1946                 arg += 1; arglen -= 2;
1947         default:
1948                 string_ncopy_do(opt, optsize, arg, arglen);
1949                 return OK;
1950         }
1953 /* Wants: name = value */
1954 static int
1955 option_set_command(int argc, const char *argv[])
1957         if (argc != 3) {
1958                 config_msg = "Wrong number of arguments given to set command";
1959                 return ERR;
1960         }
1962         if (strcmp(argv[1], "=")) {
1963                 config_msg = "No value assigned";
1964                 return ERR;
1965         }
1967         if (!strcmp(argv[0], "show-author"))
1968                 return parse_enum(&opt_author, argv[2], author_map);
1970         if (!strcmp(argv[0], "show-date"))
1971                 return parse_enum(&opt_date, argv[2], date_map);
1973         if (!strcmp(argv[0], "show-rev-graph"))
1974                 return parse_bool(&opt_rev_graph, argv[2]);
1976         if (!strcmp(argv[0], "show-refs"))
1977                 return parse_bool(&opt_show_refs, argv[2]);
1979         if (!strcmp(argv[0], "show-line-numbers"))
1980                 return parse_bool(&opt_line_number, argv[2]);
1982         if (!strcmp(argv[0], "line-graphics"))
1983                 return parse_bool(&opt_line_graphics, argv[2]);
1985         if (!strcmp(argv[0], "line-number-interval"))
1986                 return parse_int(&opt_num_interval, argv[2], 1, 1024);
1988         if (!strcmp(argv[0], "author-width"))
1989                 return parse_int(&opt_author_cols, argv[2], 0, 1024);
1991         if (!strcmp(argv[0], "horizontal-scroll"))
1992                 return parse_step(&opt_hscroll, argv[2]);
1994         if (!strcmp(argv[0], "split-view-height"))
1995                 return parse_step(&opt_scale_split_view, argv[2]);
1997         if (!strcmp(argv[0], "tab-size"))
1998                 return parse_int(&opt_tab_size, argv[2], 1, 1024);
2000         if (!strcmp(argv[0], "commit-encoding"))
2001                 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
2003         config_msg = "Unknown variable name";
2004         return ERR;
2007 /* Wants: mode request key */
2008 static int
2009 option_bind_command(int argc, const char *argv[])
2011         enum request request;
2012         int keymap = -1;
2013         int key;
2015         if (argc < 3) {
2016                 config_msg = "Wrong number of arguments given to bind command";
2017                 return ERR;
2018         }
2020         if (!set_keymap(&keymap, argv[0])) {
2021                 config_msg = "Unknown key map";
2022                 return ERR;
2023         }
2025         key = get_key_value(argv[1]);
2026         if (key == ERR) {
2027                 config_msg = "Unknown key";
2028                 return ERR;
2029         }
2031         request = get_request(argv[2]);
2032         if (request == REQ_UNKNOWN) {
2033                 static const struct enum_map obsolete[] = {
2034                         ENUM_MAP("cherry-pick",         REQ_NONE),
2035                         ENUM_MAP("screen-resize",       REQ_NONE),
2036                         ENUM_MAP("tree-parent",         REQ_PARENT),
2037                 };
2038                 int alias;
2040                 if (map_enum(&alias, obsolete, argv[2])) {
2041                         if (alias != REQ_NONE)
2042                                 add_keybinding(keymap, alias, key);
2043                         config_msg = "Obsolete request name";
2044                         return ERR;
2045                 }
2046         }
2047         if (request == REQ_UNKNOWN && *argv[2]++ == '!')
2048                 request = add_run_request(keymap, key, argv + 2);
2049         if (request == REQ_UNKNOWN) {
2050                 config_msg = "Unknown request name";
2051                 return ERR;
2052         }
2054         add_keybinding(keymap, request, key);
2056         return OK;
2059 static int
2060 set_option(const char *opt, char *value)
2062         const char *argv[SIZEOF_ARG];
2063         int argc = 0;
2065         if (!argv_from_string(argv, &argc, value)) {
2066                 config_msg = "Too many option arguments";
2067                 return ERR;
2068         }
2070         if (!strcmp(opt, "color"))
2071                 return option_color_command(argc, argv);
2073         if (!strcmp(opt, "set"))
2074                 return option_set_command(argc, argv);
2076         if (!strcmp(opt, "bind"))
2077                 return option_bind_command(argc, argv);
2079         config_msg = "Unknown option command";
2080         return ERR;
2083 static int
2084 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
2086         int status = OK;
2088         config_lineno++;
2089         config_msg = "Internal error";
2091         /* Check for comment markers, since read_properties() will
2092          * only ensure opt and value are split at first " \t". */
2093         optlen = strcspn(opt, "#");
2094         if (optlen == 0)
2095                 return OK;
2097         if (opt[optlen] != 0) {
2098                 config_msg = "No option value";
2099                 status = ERR;
2101         }  else {
2102                 /* Look for comment endings in the value. */
2103                 size_t len = strcspn(value, "#");
2105                 if (len < valuelen) {
2106                         valuelen = len;
2107                         value[valuelen] = 0;
2108                 }
2110                 status = set_option(opt, value);
2111         }
2113         if (status == ERR) {
2114                 warn("Error on line %d, near '%.*s': %s",
2115                      config_lineno, (int) optlen, opt, config_msg);
2116                 config_errors = TRUE;
2117         }
2119         /* Always keep going if errors are encountered. */
2120         return OK;
2123 static void
2124 load_option_file(const char *path)
2126         struct io io;
2128         /* It's OK that the file doesn't exist. */
2129         if (!io_open(&io, "%s", path))
2130                 return;
2132         config_lineno = 0;
2133         config_errors = FALSE;
2135         if (io_load(&io, " \t", read_option) == ERR ||
2136             config_errors == TRUE)
2137                 warn("Errors while loading %s.", path);
2140 static int
2141 load_options(void)
2143         const char *home = getenv("HOME");
2144         const char *tigrc_user = getenv("TIGRC_USER");
2145         const char *tigrc_system = getenv("TIGRC_SYSTEM");
2146         const char *tig_diff_opts = getenv("TIG_DIFF_OPTS");
2147         char buf[SIZEOF_STR];
2149         if (!tigrc_system)
2150                 tigrc_system = SYSCONFDIR "/tigrc";
2151         load_option_file(tigrc_system);
2153         if (!tigrc_user) {
2154                 if (!home || !string_format(buf, "%s/.tigrc", home))
2155                         return ERR;
2156                 tigrc_user = buf;
2157         }
2158         load_option_file(tigrc_user);
2160         /* Add _after_ loading config files to avoid adding run requests
2161          * that conflict with keybindings. */
2162         add_builtin_run_requests();
2164         if (!opt_diff_args && tig_diff_opts && *tig_diff_opts) {
2165                 static const char *diff_opts[SIZEOF_ARG] = { NULL };
2166                 int argc = 0;
2168                 if (!string_format(buf, "%s", tig_diff_opts) ||
2169                     !argv_from_string(diff_opts, &argc, buf))
2170                         die("TIG_DIFF_OPTS contains too many arguments");
2171                 else if (!argv_copy(&opt_diff_args, diff_opts))
2172                         die("Failed to format TIG_DIFF_OPTS arguments");
2173         }
2175         return OK;
2179 /*
2180  * The viewer
2181  */
2183 struct view;
2184 struct view_ops;
2186 /* The display array of active views and the index of the current view. */
2187 static struct view *display[2];
2188 static unsigned int current_view;
2190 #define foreach_displayed_view(view, i) \
2191         for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
2193 #define displayed_views()       (display[1] != NULL ? 2 : 1)
2195 /* Current head and commit ID */
2196 static char ref_blob[SIZEOF_REF]        = "";
2197 static char ref_commit[SIZEOF_REF]      = "HEAD";
2198 static char ref_head[SIZEOF_REF]        = "HEAD";
2199 static char ref_branch[SIZEOF_REF]      = "";
2201 enum view_type {
2202         VIEW_MAIN,
2203         VIEW_DIFF,
2204         VIEW_LOG,
2205         VIEW_TREE,
2206         VIEW_BLOB,
2207         VIEW_BLAME,
2208         VIEW_BRANCH,
2209         VIEW_HELP,
2210         VIEW_PAGER,
2211         VIEW_STATUS,
2212         VIEW_STAGE,
2213 };
2215 struct view {
2216         enum view_type type;    /* View type */
2217         const char *name;       /* View name */
2218         const char *cmd_env;    /* Command line set via environment */
2219         const char *id;         /* Points to either of ref_{head,commit,blob} */
2221         struct view_ops *ops;   /* View operations */
2223         enum keymap keymap;     /* What keymap does this view have */
2224         bool git_dir;           /* Whether the view requires a git directory. */
2226         char ref[SIZEOF_REF];   /* Hovered commit reference */
2227         char vid[SIZEOF_REF];   /* View ID. Set to id member when updating. */
2229         int height, width;      /* The width and height of the main window */
2230         WINDOW *win;            /* The main window */
2231         WINDOW *title;          /* The title window living below the main window */
2233         /* Navigation */
2234         unsigned long offset;   /* Offset of the window top */
2235         unsigned long yoffset;  /* Offset from the window side. */
2236         unsigned long lineno;   /* Current line number */
2237         unsigned long p_offset; /* Previous offset of the window top */
2238         unsigned long p_yoffset;/* Previous offset from the window side */
2239         unsigned long p_lineno; /* Previous current line number */
2240         bool p_restore;         /* Should the previous position be restored. */
2242         /* Searching */
2243         char grep[SIZEOF_STR];  /* Search string */
2244         regex_t *regex;         /* Pre-compiled regexp */
2246         /* If non-NULL, points to the view that opened this view. If this view
2247          * is closed tig will switch back to the parent view. */
2248         struct view *parent;
2249         struct view *prev;
2251         /* Buffering */
2252         size_t lines;           /* Total number of lines */
2253         struct line *line;      /* Line index */
2254         unsigned int digits;    /* Number of digits in the lines member. */
2256         /* Drawing */
2257         struct line *curline;   /* Line currently being drawn. */
2258         enum line_type curtype; /* Attribute currently used for drawing. */
2259         unsigned long col;      /* Column when drawing. */
2260         bool has_scrolled;      /* View was scrolled. */
2262         /* Loading */
2263         const char **argv;      /* Shell command arguments. */
2264         const char *dir;        /* Directory from which to execute. */
2265         struct io io;
2266         struct io *pipe;
2267         time_t start_time;
2268         time_t update_secs;
2269 };
2271 struct view_ops {
2272         /* What type of content being displayed. Used in the title bar. */
2273         const char *type;
2274         /* Default command arguments. */
2275         const char **argv;
2276         /* Open and reads in all view content. */
2277         bool (*open)(struct view *view);
2278         /* Read one line; updates view->line. */
2279         bool (*read)(struct view *view, char *data);
2280         /* Draw one line; @lineno must be < view->height. */
2281         bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
2282         /* Depending on view handle a special requests. */
2283         enum request (*request)(struct view *view, enum request request, struct line *line);
2284         /* Search for regexp in a line. */
2285         bool (*grep)(struct view *view, struct line *line);
2286         /* Select line */
2287         void (*select)(struct view *view, struct line *line);
2288         /* Prepare view for loading */
2289         bool (*prepare)(struct view *view);
2290 };
2292 static struct view_ops blame_ops;
2293 static struct view_ops blob_ops;
2294 static struct view_ops diff_ops;
2295 static struct view_ops help_ops;
2296 static struct view_ops log_ops;
2297 static struct view_ops main_ops;
2298 static struct view_ops pager_ops;
2299 static struct view_ops stage_ops;
2300 static struct view_ops status_ops;
2301 static struct view_ops tree_ops;
2302 static struct view_ops branch_ops;
2304 #define VIEW_STR(type, name, env, ref, ops, map, git) \
2305         { type, name, #env, ref, ops, map, git }
2307 #define VIEW_(id, name, ops, git, ref) \
2308         VIEW_STR(VIEW_##id, name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
2310 static struct view views[] = {
2311         VIEW_(MAIN,   "main",   &main_ops,   TRUE,  ref_head),
2312         VIEW_(DIFF,   "diff",   &diff_ops,   TRUE,  ref_commit),
2313         VIEW_(LOG,    "log",    &log_ops,    TRUE,  ref_head),
2314         VIEW_(TREE,   "tree",   &tree_ops,   TRUE,  ref_commit),
2315         VIEW_(BLOB,   "blob",   &blob_ops,   TRUE,  ref_blob),
2316         VIEW_(BLAME,  "blame",  &blame_ops,  TRUE,  ref_commit),
2317         VIEW_(BRANCH, "branch", &branch_ops, TRUE,  ref_head),
2318         VIEW_(HELP,   "help",   &help_ops,   FALSE, ""),
2319         VIEW_(PAGER,  "pager",  &pager_ops,  FALSE, "stdin"),
2320         VIEW_(STATUS, "status", &status_ops, TRUE,  ""),
2321         VIEW_(STAGE,  "stage",  &stage_ops,  TRUE,  ""),
2322 };
2324 #define VIEW(req)       (&views[(req) - REQ_OFFSET - 1])
2326 #define foreach_view(view, i) \
2327         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2329 #define view_is_displayed(view) \
2330         (view == display[0] || view == display[1])
2332 static enum request
2333 view_request(struct view *view, enum request request)
2335         if (!view || !view->lines)
2336                 return request;
2337         return view->ops->request(view, request, &view->line[view->lineno]);
2341 /*
2342  * View drawing.
2343  */
2345 static inline void
2346 set_view_attr(struct view *view, enum line_type type)
2348         if (!view->curline->selected && view->curtype != type) {
2349                 (void) wattrset(view->win, get_line_attr(type));
2350                 wchgat(view->win, -1, 0, type, NULL);
2351                 view->curtype = type;
2352         }
2355 static int
2356 draw_chars(struct view *view, enum line_type type, const char *string,
2357            int max_len, bool use_tilde)
2359         static char out_buffer[BUFSIZ * 2];
2360         int len = 0;
2361         int col = 0;
2362         int trimmed = FALSE;
2363         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2365         if (max_len <= 0)
2366                 return 0;
2368         len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde, opt_tab_size);
2370         set_view_attr(view, type);
2371         if (len > 0) {
2372                 if (opt_iconv_out != ICONV_NONE) {
2373                         ICONV_CONST char *inbuf = (ICONV_CONST char *) string;
2374                         size_t inlen = len + 1;
2376                         char *outbuf = out_buffer;
2377                         size_t outlen = sizeof(out_buffer);
2379                         size_t ret;
2381                         ret = iconv(opt_iconv_out, &inbuf, &inlen, &outbuf, &outlen);
2382                         if (ret != (size_t) -1) {
2383                                 string = out_buffer;
2384                                 len = sizeof(out_buffer) - outlen;
2385                         }
2386                 }
2388                 waddnstr(view->win, string, len);
2389         }
2390         if (trimmed && use_tilde) {
2391                 set_view_attr(view, LINE_DELIMITER);
2392                 waddch(view->win, '~');
2393                 col++;
2394         }
2396         return col;
2399 static int
2400 draw_space(struct view *view, enum line_type type, int max, int spaces)
2402         static char space[] = "                    ";
2403         int col = 0;
2405         spaces = MIN(max, spaces);
2407         while (spaces > 0) {
2408                 int len = MIN(spaces, sizeof(space) - 1);
2410                 col += draw_chars(view, type, space, len, FALSE);
2411                 spaces -= len;
2412         }
2414         return col;
2417 static bool
2418 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
2420         char text[SIZEOF_STR];
2422         do {
2423                 size_t pos = string_expand(text, sizeof(text), string, opt_tab_size);
2425                 view->col += draw_chars(view, type, text, view->width + view->yoffset - view->col, trim);
2426                 string += pos;
2427         } while (*string && view->width + view->yoffset > view->col);
2429         return view->width + view->yoffset <= view->col;
2432 static bool
2433 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
2435         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2436         int max = view->width + view->yoffset - view->col;
2437         int i;
2439         if (max < size)
2440                 size = max;
2442         set_view_attr(view, type);
2443         /* Using waddch() instead of waddnstr() ensures that
2444          * they'll be rendered correctly for the cursor line. */
2445         for (i = skip; i < size; i++)
2446                 waddch(view->win, graphic[i]);
2448         view->col += size;
2449         if (size < max && skip <= size)
2450                 waddch(view->win, ' ');
2451         view->col++;
2453         return view->width + view->yoffset <= view->col;
2456 static bool
2457 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
2459         int max = MIN(view->width + view->yoffset - view->col, len);
2460         int col;
2462         if (text)
2463                 col = draw_chars(view, type, text, max - 1, trim);
2464         else
2465                 col = draw_space(view, type, max - 1, max - 1);
2467         view->col += col;
2468         view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
2469         return view->width + view->yoffset <= view->col;
2472 static bool
2473 draw_date(struct view *view, struct time *time)
2475         const char *date = mkdate(time, opt_date);
2476         int cols = opt_date == DATE_SHORT ? DATE_SHORT_COLS : DATE_COLS;
2478         return draw_field(view, LINE_DATE, date, cols, FALSE);
2481 static bool
2482 draw_author(struct view *view, const char *author)
2484         bool trim = opt_author_cols == 0 || opt_author_cols > 5;
2485         bool abbreviate = opt_author == AUTHOR_ABBREVIATED || !trim;
2487         if (abbreviate && author)
2488                 author = get_author_initials(author);
2490         return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2493 static bool
2494 draw_mode(struct view *view, mode_t mode)
2496         const char *str;
2498         if (S_ISDIR(mode))
2499                 str = "drwxr-xr-x";
2500         else if (S_ISLNK(mode))
2501                 str = "lrwxrwxrwx";
2502         else if (S_ISGITLINK(mode))
2503                 str = "m---------";
2504         else if (S_ISREG(mode) && mode & S_IXUSR)
2505                 str = "-rwxr-xr-x";
2506         else if (S_ISREG(mode))
2507                 str = "-rw-r--r--";
2508         else
2509                 str = "----------";
2511         return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2514 static bool
2515 draw_lineno(struct view *view, unsigned int lineno)
2517         char number[10];
2518         int digits3 = view->digits < 3 ? 3 : view->digits;
2519         int max = MIN(view->width + view->yoffset - view->col, digits3);
2520         char *text = NULL;
2521         chtype separator = opt_line_graphics ? ACS_VLINE : '|';
2523         lineno += view->offset + 1;
2524         if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2525                 static char fmt[] = "%1ld";
2527                 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2528                 if (string_format(number, fmt, lineno))
2529                         text = number;
2530         }
2531         if (text)
2532                 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2533         else
2534                 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2535         return draw_graphic(view, LINE_DEFAULT, &separator, 1);
2538 static bool
2539 draw_view_line(struct view *view, unsigned int lineno)
2541         struct line *line;
2542         bool selected = (view->offset + lineno == view->lineno);
2544         assert(view_is_displayed(view));
2546         if (view->offset + lineno >= view->lines)
2547                 return FALSE;
2549         line = &view->line[view->offset + lineno];
2551         wmove(view->win, lineno, 0);
2552         if (line->cleareol)
2553                 wclrtoeol(view->win);
2554         view->col = 0;
2555         view->curline = line;
2556         view->curtype = LINE_NONE;
2557         line->selected = FALSE;
2558         line->dirty = line->cleareol = 0;
2560         if (selected) {
2561                 set_view_attr(view, LINE_CURSOR);
2562                 line->selected = TRUE;
2563                 view->ops->select(view, line);
2564         }
2566         return view->ops->draw(view, line, lineno);
2569 static void
2570 redraw_view_dirty(struct view *view)
2572         bool dirty = FALSE;
2573         int lineno;
2575         for (lineno = 0; lineno < view->height; lineno++) {
2576                 if (view->offset + lineno >= view->lines)
2577                         break;
2578                 if (!view->line[view->offset + lineno].dirty)
2579                         continue;
2580                 dirty = TRUE;
2581                 if (!draw_view_line(view, lineno))
2582                         break;
2583         }
2585         if (!dirty)
2586                 return;
2587         wnoutrefresh(view->win);
2590 static void
2591 redraw_view_from(struct view *view, int lineno)
2593         assert(0 <= lineno && lineno < view->height);
2595         for (; lineno < view->height; lineno++) {
2596                 if (!draw_view_line(view, lineno))
2597                         break;
2598         }
2600         wnoutrefresh(view->win);
2603 static void
2604 redraw_view(struct view *view)
2606         werase(view->win);
2607         redraw_view_from(view, 0);
2611 static void
2612 update_view_title(struct view *view)
2614         char buf[SIZEOF_STR];
2615         char state[SIZEOF_STR];
2616         size_t bufpos = 0, statelen = 0;
2618         assert(view_is_displayed(view));
2620         if (view->type != VIEW_STATUS && view->lines) {
2621                 unsigned int view_lines = view->offset + view->height;
2622                 unsigned int lines = view->lines
2623                                    ? MIN(view_lines, view->lines) * 100 / view->lines
2624                                    : 0;
2626                 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2627                                    view->ops->type,
2628                                    view->lineno + 1,
2629                                    view->lines,
2630                                    lines);
2632         }
2634         if (view->pipe) {
2635                 time_t secs = time(NULL) - view->start_time;
2637                 /* Three git seconds are a long time ... */
2638                 if (secs > 2)
2639                         string_format_from(state, &statelen, " loading %lds", secs);
2640         }
2642         string_format_from(buf, &bufpos, "[%s]", view->name);
2643         if (*view->ref && bufpos < view->width) {
2644                 size_t refsize = strlen(view->ref);
2645                 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2647                 if (minsize < view->width)
2648                         refsize = view->width - minsize + 7;
2649                 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2650         }
2652         if (statelen && bufpos < view->width) {
2653                 string_format_from(buf, &bufpos, "%s", state);
2654         }
2656         if (view == display[current_view])
2657                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2658         else
2659                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2661         mvwaddnstr(view->title, 0, 0, buf, bufpos);
2662         wclrtoeol(view->title);
2663         wnoutrefresh(view->title);
2666 static int
2667 apply_step(double step, int value)
2669         if (step >= 1)
2670                 return (int) step;
2671         value *= step + 0.01;
2672         return value ? value : 1;
2675 static void
2676 resize_display(void)
2678         int offset, i;
2679         struct view *base = display[0];
2680         struct view *view = display[1] ? display[1] : display[0];
2682         /* Setup window dimensions */
2684         getmaxyx(stdscr, base->height, base->width);
2686         /* Make room for the status window. */
2687         base->height -= 1;
2689         if (view != base) {
2690                 /* Horizontal split. */
2691                 view->width   = base->width;
2692                 view->height  = apply_step(opt_scale_split_view, base->height);
2693                 view->height  = MAX(view->height, MIN_VIEW_HEIGHT);
2694                 view->height  = MIN(view->height, base->height - MIN_VIEW_HEIGHT);
2695                 base->height -= view->height;
2697                 /* Make room for the title bar. */
2698                 view->height -= 1;
2699         }
2701         /* Make room for the title bar. */
2702         base->height -= 1;
2704         offset = 0;
2706         foreach_displayed_view (view, i) {
2707                 if (!view->win) {
2708                         view->win = newwin(view->height, 0, offset, 0);
2709                         if (!view->win)
2710                                 die("Failed to create %s view", view->name);
2712                         scrollok(view->win, FALSE);
2714                         view->title = newwin(1, 0, offset + view->height, 0);
2715                         if (!view->title)
2716                                 die("Failed to create title window");
2718                 } else {
2719                         wresize(view->win, view->height, view->width);
2720                         mvwin(view->win,   offset, 0);
2721                         mvwin(view->title, offset + view->height, 0);
2722                 }
2724                 offset += view->height + 1;
2725         }
2728 static void
2729 redraw_display(bool clear)
2731         struct view *view;
2732         int i;
2734         foreach_displayed_view (view, i) {
2735                 if (clear)
2736                         wclear(view->win);
2737                 redraw_view(view);
2738                 update_view_title(view);
2739         }
2743 /*
2744  * Option management
2745  */
2747 static void
2748 toggle_enum_option_do(unsigned int *opt, const char *help,
2749                       const struct enum_map *map, size_t size)
2751         *opt = (*opt + 1) % size;
2752         redraw_display(FALSE);
2753         report("Displaying %s %s", enum_name(map[*opt]), help);
2756 #define toggle_enum_option(opt, help, map) \
2757         toggle_enum_option_do(opt, help, map, ARRAY_SIZE(map))
2759 #define toggle_date() toggle_enum_option(&opt_date, "dates", date_map)
2760 #define toggle_author() toggle_enum_option(&opt_author, "author names", author_map)
2762 static void
2763 toggle_view_option(bool *option, const char *help)
2765         *option = !*option;
2766         redraw_display(FALSE);
2767         report("%sabling %s", *option ? "En" : "Dis", help);
2770 static void
2771 open_option_menu(void)
2773         const struct menu_item menu[] = {
2774                 { '.', "line numbers", &opt_line_number },
2775                 { 'D', "date display", &opt_date },
2776                 { 'A', "author display", &opt_author },
2777                 { 'g', "revision graph display", &opt_rev_graph },
2778                 { 'F', "reference display", &opt_show_refs },
2779                 { 0 }
2780         };
2781         int selected = 0;
2783         if (prompt_menu("Toggle option", menu, &selected)) {
2784                 if (menu[selected].data == &opt_date)
2785                         toggle_date();
2786                 else if (menu[selected].data == &opt_author)
2787                         toggle_author();
2788                 else
2789                         toggle_view_option(menu[selected].data, menu[selected].text);
2790         }
2793 static void
2794 maximize_view(struct view *view)
2796         memset(display, 0, sizeof(display));
2797         current_view = 0;
2798         display[current_view] = view;
2799         resize_display();
2800         redraw_display(FALSE);
2801         report("");
2805 /*
2806  * Navigation
2807  */
2809 static bool
2810 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2812         if (lineno >= view->lines)
2813                 lineno = view->lines > 0 ? view->lines - 1 : 0;
2815         if (offset > lineno || offset + view->height <= lineno) {
2816                 unsigned long half = view->height / 2;
2818                 if (lineno > half)
2819                         offset = lineno - half;
2820                 else
2821                         offset = 0;
2822         }
2824         if (offset != view->offset || lineno != view->lineno) {
2825                 view->offset = offset;
2826                 view->lineno = lineno;
2827                 return TRUE;
2828         }
2830         return FALSE;
2833 /* Scrolling backend */
2834 static void
2835 do_scroll_view(struct view *view, int lines)
2837         bool redraw_current_line = FALSE;
2839         /* The rendering expects the new offset. */
2840         view->offset += lines;
2842         assert(0 <= view->offset && view->offset < view->lines);
2843         assert(lines);
2845         /* Move current line into the view. */
2846         if (view->lineno < view->offset) {
2847                 view->lineno = view->offset;
2848                 redraw_current_line = TRUE;
2849         } else if (view->lineno >= view->offset + view->height) {
2850                 view->lineno = view->offset + view->height - 1;
2851                 redraw_current_line = TRUE;
2852         }
2854         assert(view->offset <= view->lineno && view->lineno < view->lines);
2856         /* Redraw the whole screen if scrolling is pointless. */
2857         if (view->height < ABS(lines)) {
2858                 redraw_view(view);
2860         } else {
2861                 int line = lines > 0 ? view->height - lines : 0;
2862                 int end = line + ABS(lines);
2864                 scrollok(view->win, TRUE);
2865                 wscrl(view->win, lines);
2866                 scrollok(view->win, FALSE);
2868                 while (line < end && draw_view_line(view, line))
2869                         line++;
2871                 if (redraw_current_line)
2872                         draw_view_line(view, view->lineno - view->offset);
2873                 wnoutrefresh(view->win);
2874         }
2876         view->has_scrolled = TRUE;
2877         report("");
2880 /* Scroll frontend */
2881 static void
2882 scroll_view(struct view *view, enum request request)
2884         int lines = 1;
2886         assert(view_is_displayed(view));
2888         switch (request) {
2889         case REQ_SCROLL_LEFT:
2890                 if (view->yoffset == 0) {
2891                         report("Cannot scroll beyond the first column");
2892                         return;
2893                 }
2894                 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2895                         view->yoffset = 0;
2896                 else
2897                         view->yoffset -= apply_step(opt_hscroll, view->width);
2898                 redraw_view_from(view, 0);
2899                 report("");
2900                 return;
2901         case REQ_SCROLL_RIGHT:
2902                 view->yoffset += apply_step(opt_hscroll, view->width);
2903                 redraw_view(view);
2904                 report("");
2905                 return;
2906         case REQ_SCROLL_PAGE_DOWN:
2907                 lines = view->height;
2908         case REQ_SCROLL_LINE_DOWN:
2909                 if (view->offset + lines > view->lines)
2910                         lines = view->lines - view->offset;
2912                 if (lines == 0 || view->offset + view->height >= view->lines) {
2913                         report("Cannot scroll beyond the last line");
2914                         return;
2915                 }
2916                 break;
2918         case REQ_SCROLL_PAGE_UP:
2919                 lines = view->height;
2920         case REQ_SCROLL_LINE_UP:
2921                 if (lines > view->offset)
2922                         lines = view->offset;
2924                 if (lines == 0) {
2925                         report("Cannot scroll beyond the first line");
2926                         return;
2927                 }
2929                 lines = -lines;
2930                 break;
2932         default:
2933                 die("request %d not handled in switch", request);
2934         }
2936         do_scroll_view(view, lines);
2939 /* Cursor moving */
2940 static void
2941 move_view(struct view *view, enum request request)
2943         int scroll_steps = 0;
2944         int steps;
2946         switch (request) {
2947         case REQ_MOVE_FIRST_LINE:
2948                 steps = -view->lineno;
2949                 break;
2951         case REQ_MOVE_LAST_LINE:
2952                 steps = view->lines - view->lineno - 1;
2953                 break;
2955         case REQ_MOVE_PAGE_UP:
2956                 steps = view->height > view->lineno
2957                       ? -view->lineno : -view->height;
2958                 break;
2960         case REQ_MOVE_PAGE_DOWN:
2961                 steps = view->lineno + view->height >= view->lines
2962                       ? view->lines - view->lineno - 1 : view->height;
2963                 break;
2965         case REQ_MOVE_UP:
2966                 steps = -1;
2967                 break;
2969         case REQ_MOVE_DOWN:
2970                 steps = 1;
2971                 break;
2973         default:
2974                 die("request %d not handled in switch", request);
2975         }
2977         if (steps <= 0 && view->lineno == 0) {
2978                 report("Cannot move beyond the first line");
2979                 return;
2981         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2982                 report("Cannot move beyond the last line");
2983                 return;
2984         }
2986         /* Move the current line */
2987         view->lineno += steps;
2988         assert(0 <= view->lineno && view->lineno < view->lines);
2990         /* Check whether the view needs to be scrolled */
2991         if (view->lineno < view->offset ||
2992             view->lineno >= view->offset + view->height) {
2993                 scroll_steps = steps;
2994                 if (steps < 0 && -steps > view->offset) {
2995                         scroll_steps = -view->offset;
2997                 } else if (steps > 0) {
2998                         if (view->lineno == view->lines - 1 &&
2999                             view->lines > view->height) {
3000                                 scroll_steps = view->lines - view->offset - 1;
3001                                 if (scroll_steps >= view->height)
3002                                         scroll_steps -= view->height - 1;
3003                         }
3004                 }
3005         }
3007         if (!view_is_displayed(view)) {
3008                 view->offset += scroll_steps;
3009                 assert(0 <= view->offset && view->offset < view->lines);
3010                 view->ops->select(view, &view->line[view->lineno]);
3011                 return;
3012         }
3014         /* Repaint the old "current" line if we be scrolling */
3015         if (ABS(steps) < view->height)
3016                 draw_view_line(view, view->lineno - steps - view->offset);
3018         if (scroll_steps) {
3019                 do_scroll_view(view, scroll_steps);
3020                 return;
3021         }
3023         /* Draw the current line */
3024         draw_view_line(view, view->lineno - view->offset);
3026         wnoutrefresh(view->win);
3027         report("");
3031 /*
3032  * Searching
3033  */
3035 static void search_view(struct view *view, enum request request);
3037 static bool
3038 grep_text(struct view *view, const char *text[])
3040         regmatch_t pmatch;
3041         size_t i;
3043         for (i = 0; text[i]; i++)
3044                 if (*text[i] &&
3045                     regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
3046                         return TRUE;
3047         return FALSE;
3050 static void
3051 select_view_line(struct view *view, unsigned long lineno)
3053         unsigned long old_lineno = view->lineno;
3054         unsigned long old_offset = view->offset;
3056         if (goto_view_line(view, view->offset, lineno)) {
3057                 if (view_is_displayed(view)) {
3058                         if (old_offset != view->offset) {
3059                                 redraw_view(view);
3060                         } else {
3061                                 draw_view_line(view, old_lineno - view->offset);
3062                                 draw_view_line(view, view->lineno - view->offset);
3063                                 wnoutrefresh(view->win);
3064                         }
3065                 } else {
3066                         view->ops->select(view, &view->line[view->lineno]);
3067                 }
3068         }
3071 static void
3072 find_next(struct view *view, enum request request)
3074         unsigned long lineno = view->lineno;
3075         int direction;
3077         if (!*view->grep) {
3078                 if (!*opt_search)
3079                         report("No previous search");
3080                 else
3081                         search_view(view, request);
3082                 return;
3083         }
3085         switch (request) {
3086         case REQ_SEARCH:
3087         case REQ_FIND_NEXT:
3088                 direction = 1;
3089                 break;
3091         case REQ_SEARCH_BACK:
3092         case REQ_FIND_PREV:
3093                 direction = -1;
3094                 break;
3096         default:
3097                 return;
3098         }
3100         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
3101                 lineno += direction;
3103         /* Note, lineno is unsigned long so will wrap around in which case it
3104          * will become bigger than view->lines. */
3105         for (; lineno < view->lines; lineno += direction) {
3106                 if (view->ops->grep(view, &view->line[lineno])) {
3107                         select_view_line(view, lineno);
3108                         report("Line %ld matches '%s'", lineno + 1, view->grep);
3109                         return;
3110                 }
3111         }
3113         report("No match found for '%s'", view->grep);
3116 static void
3117 search_view(struct view *view, enum request request)
3119         int regex_err;
3121         if (view->regex) {
3122                 regfree(view->regex);
3123                 *view->grep = 0;
3124         } else {
3125                 view->regex = calloc(1, sizeof(*view->regex));
3126                 if (!view->regex)
3127                         return;
3128         }
3130         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
3131         if (regex_err != 0) {
3132                 char buf[SIZEOF_STR] = "unknown error";
3134                 regerror(regex_err, view->regex, buf, sizeof(buf));
3135                 report("Search failed: %s", buf);
3136                 return;
3137         }
3139         string_copy(view->grep, opt_search);
3141         find_next(view, request);
3144 /*
3145  * Incremental updating
3146  */
3148 static void
3149 reset_view(struct view *view)
3151         int i;
3153         for (i = 0; i < view->lines; i++)
3154                 free(view->line[i].data);
3155         free(view->line);
3157         view->p_offset = view->offset;
3158         view->p_yoffset = view->yoffset;
3159         view->p_lineno = view->lineno;
3161         view->line = NULL;
3162         view->offset = 0;
3163         view->yoffset = 0;
3164         view->lines  = 0;
3165         view->lineno = 0;
3166         view->vid[0] = 0;
3167         view->update_secs = 0;
3170 static const char *
3171 format_arg(const char *name)
3173         static struct {
3174                 const char *name;
3175                 size_t namelen;
3176                 const char *value;
3177                 const char *value_if_empty;
3178         } vars[] = {
3179 #define FORMAT_VAR(name, value, value_if_empty) \
3180         { name, STRING_SIZE(name), value, value_if_empty }
3181                 FORMAT_VAR("%(directory)",      opt_path,       ""),
3182                 FORMAT_VAR("%(file)",           opt_file,       ""),
3183                 FORMAT_VAR("%(ref)",            opt_ref,        "HEAD"),
3184                 FORMAT_VAR("%(head)",           ref_head,       ""),
3185                 FORMAT_VAR("%(commit)",         ref_commit,     ""),
3186                 FORMAT_VAR("%(blob)",           ref_blob,       ""),
3187                 FORMAT_VAR("%(branch)",         ref_branch,     ""),
3188         };
3189         int i;
3191         for (i = 0; i < ARRAY_SIZE(vars); i++)
3192                 if (!strncmp(name, vars[i].name, vars[i].namelen))
3193                         return *vars[i].value ? vars[i].value : vars[i].value_if_empty;
3195         report("Unknown replacement: `%s`", name);
3196         return NULL;
3199 static bool
3200 format_argv(const char ***dst_argv, const char *src_argv[], bool replace)
3202         char buf[SIZEOF_STR];
3203         int argc;
3205         argv_free(*dst_argv);
3207         for (argc = 0; src_argv[argc]; argc++) {
3208                 const char *arg = src_argv[argc];
3209                 size_t bufpos = 0;
3211                 if (!strcmp(arg, "%(fileargs)")) {
3212                         if (!argv_append_array(dst_argv, opt_file_args))
3213                                 break;
3214                         continue;
3216                 } else if (!strcmp(arg, "%(diffargs)")) {
3217                         if (!argv_append_array(dst_argv, opt_diff_args))
3218                                 break;
3219                         continue;
3221                 } else if (!strcmp(arg, "%(revargs)")) {
3222                         if (!argv_append_array(dst_argv, opt_rev_args))
3223                                 break;
3224                         continue;
3225                 }
3227                 while (arg) {
3228                         char *next = strstr(arg, "%(");
3229                         int len = next - arg;
3230                         const char *value;
3232                         if (!next || !replace) {
3233                                 len = strlen(arg);
3234                                 value = "";
3236                         } else {
3237                                 value = format_arg(next);
3239                                 if (!value) {
3240                                         return FALSE;
3241                                 }
3242                         }
3244                         if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
3245                                 return FALSE;
3247                         arg = next && replace ? strchr(next, ')') + 1 : NULL;
3248                 }
3250                 if (!argv_append(dst_argv, buf))
3251                         break;
3252         }
3254         return src_argv[argc] == NULL;
3257 static bool
3258 restore_view_position(struct view *view)
3260         if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
3261                 return FALSE;
3263         /* Changing the view position cancels the restoring. */
3264         /* FIXME: Changing back to the first line is not detected. */
3265         if (view->offset != 0 || view->lineno != 0) {
3266                 view->p_restore = FALSE;
3267                 return FALSE;
3268         }
3270         if (goto_view_line(view, view->p_offset, view->p_lineno) &&
3271             view_is_displayed(view))
3272                 werase(view->win);
3274         view->yoffset = view->p_yoffset;
3275         view->p_restore = FALSE;
3277         return TRUE;
3280 static void
3281 end_update(struct view *view, bool force)
3283         if (!view->pipe)
3284                 return;
3285         while (!view->ops->read(view, NULL))
3286                 if (!force)
3287                         return;
3288         if (force)
3289                 io_kill(view->pipe);
3290         io_done(view->pipe);
3291         view->pipe = NULL;
3294 static void
3295 setup_update(struct view *view, const char *vid)
3297         reset_view(view);
3298         string_copy_rev(view->vid, vid);
3299         view->pipe = &view->io;
3300         view->start_time = time(NULL);
3303 static bool
3304 prepare_io(struct view *view, const char *dir, const char *argv[], bool replace)
3306         view->dir = dir;
3307         return format_argv(&view->argv, argv, replace);
3310 static bool
3311 prepare_update(struct view *view, const char *argv[], const char *dir)
3313         if (view->pipe)
3314                 end_update(view, TRUE);
3315         return prepare_io(view, dir, argv, FALSE);
3318 static bool
3319 start_update(struct view *view, const char **argv, const char *dir)
3321         if (view->pipe)
3322                 io_done(view->pipe);
3323         return prepare_io(view, dir, argv, FALSE) &&
3324                io_run(&view->io, IO_RD, dir, view->argv);
3327 static bool
3328 prepare_update_file(struct view *view, const char *name)
3330         if (view->pipe)
3331                 end_update(view, TRUE);
3332         argv_free(view->argv);
3333         return io_open(&view->io, "%s/%s", opt_cdup[0] ? opt_cdup : ".", name);
3336 static bool
3337 begin_update(struct view *view, bool refresh)
3339         if (view->pipe)
3340                 end_update(view, TRUE);
3342         if (!refresh) {
3343                 if (view->ops->prepare) {
3344                         if (!view->ops->prepare(view))
3345                                 return FALSE;
3346                 } else if (!prepare_io(view, NULL, view->ops->argv, TRUE)) {
3347                         return FALSE;
3348                 }
3350                 /* Put the current ref_* value to the view title ref
3351                  * member. This is needed by the blob view. Most other
3352                  * views sets it automatically after loading because the
3353                  * first line is a commit line. */
3354                 string_copy_rev(view->ref, view->id);
3355         }
3357         if (view->argv && view->argv[0] &&
3358             !io_run(&view->io, IO_RD, view->dir, view->argv))
3359                 return FALSE;
3361         setup_update(view, view->id);
3363         return TRUE;
3366 static bool
3367 update_view(struct view *view)
3369         char out_buffer[BUFSIZ * 2];
3370         char *line;
3371         /* Clear the view and redraw everything since the tree sorting
3372          * might have rearranged things. */
3373         bool redraw = view->lines == 0;
3374         bool can_read = TRUE;
3376         if (!view->pipe)
3377                 return TRUE;
3379         if (!io_can_read(view->pipe)) {
3380                 if (view->lines == 0 && view_is_displayed(view)) {
3381                         time_t secs = time(NULL) - view->start_time;
3383                         if (secs > 1 && secs > view->update_secs) {
3384                                 if (view->update_secs == 0)
3385                                         redraw_view(view);
3386                                 update_view_title(view);
3387                                 view->update_secs = secs;
3388                         }
3389                 }
3390                 return TRUE;
3391         }
3393         for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
3394                 if (opt_iconv_in != ICONV_NONE) {
3395                         ICONV_CONST char *inbuf = line;
3396                         size_t inlen = strlen(line) + 1;
3398                         char *outbuf = out_buffer;
3399                         size_t outlen = sizeof(out_buffer);
3401                         size_t ret;
3403                         ret = iconv(opt_iconv_in, &inbuf, &inlen, &outbuf, &outlen);
3404                         if (ret != (size_t) -1)
3405                                 line = out_buffer;
3406                 }
3408                 if (!view->ops->read(view, line)) {
3409                         report("Allocation failure");
3410                         end_update(view, TRUE);
3411                         return FALSE;
3412                 }
3413         }
3415         {
3416                 unsigned long lines = view->lines;
3417                 int digits;
3419                 for (digits = 0; lines; digits++)
3420                         lines /= 10;
3422                 /* Keep the displayed view in sync with line number scaling. */
3423                 if (digits != view->digits) {
3424                         view->digits = digits;
3425                         if (opt_line_number || view->type == VIEW_BLAME)
3426                                 redraw = TRUE;
3427                 }
3428         }
3430         if (io_error(view->pipe)) {
3431                 report("Failed to read: %s", io_strerror(view->pipe));
3432                 end_update(view, TRUE);
3434         } else if (io_eof(view->pipe)) {
3435                 if (view_is_displayed(view))
3436                         report("");
3437                 end_update(view, FALSE);
3438         }
3440         if (restore_view_position(view))
3441                 redraw = TRUE;
3443         if (!view_is_displayed(view))
3444                 return TRUE;
3446         if (redraw)
3447                 redraw_view_from(view, 0);
3448         else
3449                 redraw_view_dirty(view);
3451         /* Update the title _after_ the redraw so that if the redraw picks up a
3452          * commit reference in view->ref it'll be available here. */
3453         update_view_title(view);
3454         return TRUE;
3457 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
3459 static struct line *
3460 add_line_data(struct view *view, void *data, enum line_type type)
3462         struct line *line;
3464         if (!realloc_lines(&view->line, view->lines, 1))
3465                 return NULL;
3467         line = &view->line[view->lines++];
3468         memset(line, 0, sizeof(*line));
3469         line->type = type;
3470         line->data = data;
3471         line->dirty = 1;
3473         return line;
3476 static struct line *
3477 add_line_text(struct view *view, const char *text, enum line_type type)
3479         char *data = text ? strdup(text) : NULL;
3481         return data ? add_line_data(view, data, type) : NULL;
3484 static struct line *
3485 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
3487         char buf[SIZEOF_STR];
3488         va_list args;
3490         va_start(args, fmt);
3491         if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
3492                 buf[0] = 0;
3493         va_end(args);
3495         return buf[0] ? add_line_text(view, buf, type) : NULL;
3498 /*
3499  * View opening
3500  */
3502 enum open_flags {
3503         OPEN_DEFAULT = 0,       /* Use default view switching. */
3504         OPEN_SPLIT = 1,         /* Split current view. */
3505         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
3506         OPEN_REFRESH = 16,      /* Refresh view using previous command. */
3507         OPEN_PREPARED = 32,     /* Open already prepared command. */
3508 };
3510 static void
3511 open_view(struct view *prev, enum request request, enum open_flags flags)
3513         bool split = !!(flags & OPEN_SPLIT);
3514         bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
3515         bool nomaximize = !!(flags & OPEN_REFRESH);
3516         struct view *view = VIEW(request);
3517         int nviews = displayed_views();
3518         struct view *base_view = display[0];
3520         if (view == prev && nviews == 1 && !reload) {
3521                 report("Already in %s view", view->name);
3522                 return;
3523         }
3525         if (view->git_dir && !opt_git_dir[0]) {
3526                 report("The %s view is disabled in pager view", view->name);
3527                 return;
3528         }
3530         if (split) {
3531                 display[1] = view;
3532                 current_view = 1;
3533                 view->parent = prev;
3534         } else if (!nomaximize) {
3535                 /* Maximize the current view. */
3536                 memset(display, 0, sizeof(display));
3537                 current_view = 0;
3538                 display[current_view] = view;
3539         }
3541         /* No prev signals that this is the first loaded view. */
3542         if (prev && view != prev) {
3543                 view->prev = prev;
3544         }
3546         /* Resize the view when switching between split- and full-screen,
3547          * or when switching between two different full-screen views. */
3548         if (nviews != displayed_views() ||
3549             (nviews == 1 && base_view != display[0]))
3550                 resize_display();
3552         if (view->ops->open) {
3553                 if (view->pipe)
3554                         end_update(view, TRUE);
3555                 if (!view->ops->open(view)) {
3556                         report("Failed to load %s view", view->name);
3557                         return;
3558                 }
3559                 restore_view_position(view);
3561         } else if ((reload || strcmp(view->vid, view->id)) &&
3562                    !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3563                 report("Failed to load %s view", view->name);
3564                 return;
3565         }
3567         if (split && prev->lineno - prev->offset >= prev->height) {
3568                 /* Take the title line into account. */
3569                 int lines = prev->lineno - prev->offset - prev->height + 1;
3571                 /* Scroll the view that was split if the current line is
3572                  * outside the new limited view. */
3573                 do_scroll_view(prev, lines);
3574         }
3576         if (prev && view != prev && split && view_is_displayed(prev)) {
3577                 /* "Blur" the previous view. */
3578                 update_view_title(prev);
3579         }
3581         if (view->pipe && view->lines == 0) {
3582                 /* Clear the old view and let the incremental updating refill
3583                  * the screen. */
3584                 werase(view->win);
3585                 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3586                 report("");
3587         } else if (view_is_displayed(view)) {
3588                 redraw_view(view);
3589                 report("");
3590         }
3593 static void
3594 open_external_viewer(const char *argv[], const char *dir)
3596         def_prog_mode();           /* save current tty modes */
3597         endwin();                  /* restore original tty modes */
3598         io_run_fg(argv, dir);
3599         fprintf(stderr, "Press Enter to continue");
3600         getc(opt_tty);
3601         reset_prog_mode();
3602         redraw_display(TRUE);
3605 static void
3606 open_mergetool(const char *file)
3608         const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3610         open_external_viewer(mergetool_argv, opt_cdup);
3613 static void
3614 open_editor(const char *file)
3616         const char *editor_argv[] = { "vi", file, NULL };
3617         const char *editor;
3619         editor = getenv("GIT_EDITOR");
3620         if (!editor && *opt_editor)
3621                 editor = opt_editor;
3622         if (!editor)
3623                 editor = getenv("VISUAL");
3624         if (!editor)
3625                 editor = getenv("EDITOR");
3626         if (!editor)
3627                 editor = "vi";
3629         editor_argv[0] = editor;
3630         open_external_viewer(editor_argv, opt_cdup);
3633 static void
3634 open_run_request(enum request request)
3636         struct run_request *req = get_run_request(request);
3637         const char **argv = NULL;
3639         if (!req) {
3640                 report("Unknown run request");
3641                 return;
3642         }
3644         if (format_argv(&argv, req->argv, TRUE))
3645                 open_external_viewer(argv, NULL);
3646         if (argv)
3647                 argv_free(argv);
3648         free(argv);
3651 /*
3652  * User request switch noodle
3653  */
3655 static int
3656 view_driver(struct view *view, enum request request)
3658         int i;
3660         if (request == REQ_NONE)
3661                 return TRUE;
3663         if (request > REQ_NONE) {
3664                 open_run_request(request);
3665                 view_request(view, REQ_REFRESH);
3666                 return TRUE;
3667         }
3669         request = view_request(view, request);
3670         if (request == REQ_NONE)
3671                 return TRUE;
3673         switch (request) {
3674         case REQ_MOVE_UP:
3675         case REQ_MOVE_DOWN:
3676         case REQ_MOVE_PAGE_UP:
3677         case REQ_MOVE_PAGE_DOWN:
3678         case REQ_MOVE_FIRST_LINE:
3679         case REQ_MOVE_LAST_LINE:
3680                 move_view(view, request);
3681                 break;
3683         case REQ_SCROLL_LEFT:
3684         case REQ_SCROLL_RIGHT:
3685         case REQ_SCROLL_LINE_DOWN:
3686         case REQ_SCROLL_LINE_UP:
3687         case REQ_SCROLL_PAGE_DOWN:
3688         case REQ_SCROLL_PAGE_UP:
3689                 scroll_view(view, request);
3690                 break;
3692         case REQ_VIEW_BLAME:
3693                 if (!opt_file[0]) {
3694                         report("No file chosen, press %s to open tree view",
3695                                get_key(view->keymap, REQ_VIEW_TREE));
3696                         break;
3697                 }
3698                 open_view(view, request, OPEN_DEFAULT);
3699                 break;
3701         case REQ_VIEW_BLOB:
3702                 if (!ref_blob[0]) {
3703                         report("No file chosen, press %s to open tree view",
3704                                get_key(view->keymap, REQ_VIEW_TREE));
3705                         break;
3706                 }
3707                 open_view(view, request, OPEN_DEFAULT);
3708                 break;
3710         case REQ_VIEW_PAGER:
3711                 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3712                         report("No pager content, press %s to run command from prompt",
3713                                get_key(view->keymap, REQ_PROMPT));
3714                         break;
3715                 }
3716                 open_view(view, request, OPEN_DEFAULT);
3717                 break;
3719         case REQ_VIEW_STAGE:
3720                 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3721                         report("No stage content, press %s to open the status view and choose file",
3722                                get_key(view->keymap, REQ_VIEW_STATUS));
3723                         break;
3724                 }
3725                 open_view(view, request, OPEN_DEFAULT);
3726                 break;
3728         case REQ_VIEW_STATUS:
3729                 if (opt_is_inside_work_tree == FALSE) {
3730                         report("The status view requires a working tree");
3731                         break;
3732                 }
3733                 open_view(view, request, OPEN_DEFAULT);
3734                 break;
3736         case REQ_VIEW_MAIN:
3737         case REQ_VIEW_DIFF:
3738         case REQ_VIEW_LOG:
3739         case REQ_VIEW_TREE:
3740         case REQ_VIEW_HELP:
3741         case REQ_VIEW_BRANCH:
3742                 open_view(view, request, OPEN_DEFAULT);
3743                 break;
3745         case REQ_NEXT:
3746         case REQ_PREVIOUS:
3747                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3749                 if (view->parent) {
3750                         int line;
3752                         view = view->parent;
3753                         line = view->lineno;
3754                         move_view(view, request);
3755                         if (view_is_displayed(view))
3756                                 update_view_title(view);
3757                         if (line != view->lineno)
3758                                 view_request(view, REQ_ENTER);
3759                 } else {
3760                         move_view(view, request);
3761                 }
3762                 break;
3764         case REQ_VIEW_NEXT:
3765         {
3766                 int nviews = displayed_views();
3767                 int next_view = (current_view + 1) % nviews;
3769                 if (next_view == current_view) {
3770                         report("Only one view is displayed");
3771                         break;
3772                 }
3774                 current_view = next_view;
3775                 /* Blur out the title of the previous view. */
3776                 update_view_title(view);
3777                 report("");
3778                 break;
3779         }
3780         case REQ_REFRESH:
3781                 report("Refreshing is not yet supported for the %s view", view->name);
3782                 break;
3784         case REQ_MAXIMIZE:
3785                 if (displayed_views() == 2)
3786                         maximize_view(view);
3787                 break;
3789         case REQ_OPTIONS:
3790                 open_option_menu();
3791                 break;
3793         case REQ_TOGGLE_LINENO:
3794                 toggle_view_option(&opt_line_number, "line numbers");
3795                 break;
3797         case REQ_TOGGLE_DATE:
3798                 toggle_date();
3799                 break;
3801         case REQ_TOGGLE_AUTHOR:
3802                 toggle_author();
3803                 break;
3805         case REQ_TOGGLE_REV_GRAPH:
3806                 toggle_view_option(&opt_rev_graph, "revision graph display");
3807                 break;
3809         case REQ_TOGGLE_REFS:
3810                 toggle_view_option(&opt_show_refs, "reference display");
3811                 break;
3813         case REQ_TOGGLE_SORT_FIELD:
3814         case REQ_TOGGLE_SORT_ORDER:
3815                 report("Sorting is not yet supported for the %s view", view->name);
3816                 break;
3818         case REQ_SEARCH:
3819         case REQ_SEARCH_BACK:
3820                 search_view(view, request);
3821                 break;
3823         case REQ_FIND_NEXT:
3824         case REQ_FIND_PREV:
3825                 find_next(view, request);
3826                 break;
3828         case REQ_STOP_LOADING:
3829                 foreach_view(view, i) {
3830                         if (view->pipe)
3831                                 report("Stopped loading the %s view", view->name),
3832                         end_update(view, TRUE);
3833                 }
3834                 break;
3836         case REQ_SHOW_VERSION:
3837                 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3838                 return TRUE;
3840         case REQ_SCREEN_REDRAW:
3841                 redraw_display(TRUE);
3842                 break;
3844         case REQ_EDIT:
3845                 report("Nothing to edit");
3846                 break;
3848         case REQ_ENTER:
3849                 report("Nothing to enter");
3850                 break;
3852         case REQ_VIEW_CLOSE:
3853                 /* XXX: Mark closed views by letting view->prev point to the
3854                  * view itself. Parents to closed view should never be
3855                  * followed. */
3856                 if (view->prev && view->prev != view) {
3857                         maximize_view(view->prev);
3858                         view->prev = view;
3859                         break;
3860                 }
3861                 /* Fall-through */
3862         case REQ_QUIT:
3863                 return FALSE;
3865         default:
3866                 report("Unknown key, press %s for help",
3867                        get_key(view->keymap, REQ_VIEW_HELP));
3868                 return TRUE;
3869         }
3871         return TRUE;
3875 /*
3876  * View backend utilities
3877  */
3879 enum sort_field {
3880         ORDERBY_NAME,
3881         ORDERBY_DATE,
3882         ORDERBY_AUTHOR,
3883 };
3885 struct sort_state {
3886         const enum sort_field *fields;
3887         size_t size, current;
3888         bool reverse;
3889 };
3891 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3892 #define get_sort_field(state) ((state).fields[(state).current])
3893 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3895 static void
3896 sort_view(struct view *view, enum request request, struct sort_state *state,
3897           int (*compare)(const void *, const void *))
3899         switch (request) {
3900         case REQ_TOGGLE_SORT_FIELD:
3901                 state->current = (state->current + 1) % state->size;
3902                 break;
3904         case REQ_TOGGLE_SORT_ORDER:
3905                 state->reverse = !state->reverse;
3906                 break;
3907         default:
3908                 die("Not a sort request");
3909         }
3911         qsort(view->line, view->lines, sizeof(*view->line), compare);
3912         redraw_view(view);
3915 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3917 /* Small author cache to reduce memory consumption. It uses binary
3918  * search to lookup or find place to position new entries. No entries
3919  * are ever freed. */
3920 static const char *
3921 get_author(const char *name)
3923         static const char **authors;
3924         static size_t authors_size;
3925         int from = 0, to = authors_size - 1;
3927         while (from <= to) {
3928                 size_t pos = (to + from) / 2;
3929                 int cmp = strcmp(name, authors[pos]);
3931                 if (!cmp)
3932                         return authors[pos];
3934                 if (cmp < 0)
3935                         to = pos - 1;
3936                 else
3937                         from = pos + 1;
3938         }
3940         if (!realloc_authors(&authors, authors_size, 1))
3941                 return NULL;
3942         name = strdup(name);
3943         if (!name)
3944                 return NULL;
3946         memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3947         authors[from] = name;
3948         authors_size++;
3950         return name;
3953 static void
3954 parse_timesec(struct time *time, const char *sec)
3956         time->sec = (time_t) atol(sec);
3959 static void
3960 parse_timezone(struct time *time, const char *zone)
3962         long tz;
3964         tz  = ('0' - zone[1]) * 60 * 60 * 10;
3965         tz += ('0' - zone[2]) * 60 * 60;
3966         tz += ('0' - zone[3]) * 60 * 10;
3967         tz += ('0' - zone[4]) * 60;
3969         if (zone[0] == '-')
3970                 tz = -tz;
3972         time->tz = tz;
3973         time->sec -= tz;
3976 /* Parse author lines where the name may be empty:
3977  *      author  <email@address.tld> 1138474660 +0100
3978  */
3979 static void
3980 parse_author_line(char *ident, const char **author, struct time *time)
3982         char *nameend = strchr(ident, '<');
3983         char *emailend = strchr(ident, '>');
3985         if (nameend && emailend)
3986                 *nameend = *emailend = 0;
3987         ident = chomp_string(ident);
3988         if (!*ident) {
3989                 if (nameend)
3990                         ident = chomp_string(nameend + 1);
3991                 if (!*ident)
3992                         ident = "Unknown";
3993         }
3995         *author = get_author(ident);
3997         /* Parse epoch and timezone */
3998         if (emailend && emailend[1] == ' ') {
3999                 char *secs = emailend + 2;
4000                 char *zone = strchr(secs, ' ');
4002                 parse_timesec(time, secs);
4004                 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
4005                         parse_timezone(time, zone + 1);
4006         }
4009 /*
4010  * Pager backend
4011  */
4013 static bool
4014 pager_draw(struct view *view, struct line *line, unsigned int lineno)
4016         if (opt_line_number && draw_lineno(view, lineno))
4017                 return TRUE;
4019         draw_text(view, line->type, line->data, TRUE);
4020         return TRUE;
4023 static bool
4024 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
4026         const char *describe_argv[] = { "git", "describe", commit_id, NULL };
4027         char ref[SIZEOF_STR];
4029         if (!io_run_buf(describe_argv, ref, sizeof(ref)) || !*ref)
4030                 return TRUE;
4032         /* This is the only fatal call, since it can "corrupt" the buffer. */
4033         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
4034                 return FALSE;
4036         return TRUE;
4039 static void
4040 add_pager_refs(struct view *view, struct line *line)
4042         char buf[SIZEOF_STR];
4043         char *commit_id = (char *)line->data + STRING_SIZE("commit ");
4044         struct ref_list *list;
4045         size_t bufpos = 0, i;
4046         const char *sep = "Refs: ";
4047         bool is_tag = FALSE;
4049         assert(line->type == LINE_COMMIT);
4051         list = get_ref_list(commit_id);
4052         if (!list) {
4053                 if (view->type == VIEW_DIFF)
4054                         goto try_add_describe_ref;
4055                 return;
4056         }
4058         for (i = 0; i < list->size; i++) {
4059                 struct ref *ref = list->refs[i];
4060                 const char *fmt = ref->tag    ? "%s[%s]" :
4061                                   ref->remote ? "%s<%s>" : "%s%s";
4063                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
4064                         return;
4065                 sep = ", ";
4066                 if (ref->tag)
4067                         is_tag = TRUE;
4068         }
4070         if (!is_tag && view->type == VIEW_DIFF) {
4071 try_add_describe_ref:
4072                 /* Add <tag>-g<commit_id> "fake" reference. */
4073                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
4074                         return;
4075         }
4077         if (bufpos == 0)
4078                 return;
4080         add_line_text(view, buf, LINE_PP_REFS);
4083 static bool
4084 pager_read(struct view *view, char *data)
4086         struct line *line;
4088         if (!data)
4089                 return TRUE;
4091         line = add_line_text(view, data, get_line_type(data));
4092         if (!line)
4093                 return FALSE;
4095         if (line->type == LINE_COMMIT &&
4096             (view->type == VIEW_DIFF ||
4097              view->type == VIEW_LOG))
4098                 add_pager_refs(view, line);
4100         return TRUE;
4103 static enum request
4104 pager_request(struct view *view, enum request request, struct line *line)
4106         int split = 0;
4108         if (request != REQ_ENTER)
4109                 return request;
4111         if (line->type == LINE_COMMIT &&
4112            (view->type == VIEW_LOG ||
4113             view->type == VIEW_PAGER)) {
4114                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
4115                 split = 1;
4116         }
4118         /* Always scroll the view even if it was split. That way
4119          * you can use Enter to scroll through the log view and
4120          * split open each commit diff. */
4121         scroll_view(view, REQ_SCROLL_LINE_DOWN);
4123         /* FIXME: A minor workaround. Scrolling the view will call report("")
4124          * but if we are scrolling a non-current view this won't properly
4125          * update the view title. */
4126         if (split)
4127                 update_view_title(view);
4129         return REQ_NONE;
4132 static bool
4133 pager_grep(struct view *view, struct line *line)
4135         const char *text[] = { line->data, NULL };
4137         return grep_text(view, text);
4140 static void
4141 pager_select(struct view *view, struct line *line)
4143         if (line->type == LINE_COMMIT) {
4144                 char *text = (char *)line->data + STRING_SIZE("commit ");
4146                 if (view->type != VIEW_PAGER)
4147                         string_copy_rev(view->ref, text);
4148                 string_copy_rev(ref_commit, text);
4149         }
4152 static struct view_ops pager_ops = {
4153         "line",
4154         NULL,
4155         NULL,
4156         pager_read,
4157         pager_draw,
4158         pager_request,
4159         pager_grep,
4160         pager_select,
4161 };
4163 static const char *log_argv[SIZEOF_ARG] = {
4164         "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
4165 };
4167 static enum request
4168 log_request(struct view *view, enum request request, struct line *line)
4170         switch (request) {
4171         case REQ_REFRESH:
4172                 load_refs();
4173                 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
4174                 return REQ_NONE;
4175         default:
4176                 return pager_request(view, request, line);
4177         }
4180 static struct view_ops log_ops = {
4181         "line",
4182         log_argv,
4183         NULL,
4184         pager_read,
4185         pager_draw,
4186         log_request,
4187         pager_grep,
4188         pager_select,
4189 };
4191 static const char *diff_argv[SIZEOF_ARG] = {
4192         "git", "show", "--pretty=fuller", "--no-color", "--root",
4193                 "--patch-with-stat", "--find-copies-harder", "-C",
4194                 "%(diffargs)", "%(commit)", "--", "%(fileargs)", NULL
4195 };
4197 static bool
4198 diff_read(struct view *view, char *data)
4200         if (!data) {
4201                 /* Fall back to retry if no diff will be shown. */
4202                 if (view->lines == 0 && opt_file_args) {
4203                         int pos = argv_size(view->argv)
4204                                 - argv_size(opt_file_args) - 1;
4206                         if (pos > 0 && !strcmp(view->argv[pos], "--")) {
4207                                 for (; view->argv[pos]; pos++) {
4208                                         free((void *) view->argv[pos]);
4209                                         view->argv[pos] = NULL;
4210                                 }
4212                                 if (view->pipe)
4213                                         io_done(view->pipe);
4214                                 if (io_run(&view->io, IO_RD, view->dir, view->argv))
4215                                         return FALSE;
4216                         }
4217                 }
4218                 return TRUE;
4219         }
4221         return pager_read(view, data);
4224 static struct view_ops diff_ops = {
4225         "line",
4226         diff_argv,
4227         NULL,
4228         diff_read,
4229         pager_draw,
4230         pager_request,
4231         pager_grep,
4232         pager_select,
4233 };
4235 /*
4236  * Help backend
4237  */
4239 static bool help_keymap_hidden[ARRAY_SIZE(keymap_table)];
4241 static bool
4242 help_open_keymap_title(struct view *view, enum keymap keymap)
4244         struct line *line;
4246         line = add_line_format(view, LINE_HELP_KEYMAP, "[%c] %s bindings",
4247                                help_keymap_hidden[keymap] ? '+' : '-',
4248                                enum_name(keymap_table[keymap]));
4249         if (line)
4250                 line->other = keymap;
4252         return help_keymap_hidden[keymap];
4255 static void
4256 help_open_keymap(struct view *view, enum keymap keymap)
4258         const char *group = NULL;
4259         char buf[SIZEOF_STR];
4260         size_t bufpos;
4261         bool add_title = TRUE;
4262         int i;
4264         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
4265                 const char *key = NULL;
4267                 if (req_info[i].request == REQ_NONE)
4268                         continue;
4270                 if (!req_info[i].request) {
4271                         group = req_info[i].help;
4272                         continue;
4273                 }
4275                 key = get_keys(keymap, req_info[i].request, TRUE);
4276                 if (!key || !*key)
4277                         continue;
4279                 if (add_title && help_open_keymap_title(view, keymap))
4280                         return;
4281                 add_title = FALSE;
4283                 if (group) {
4284                         add_line_text(view, group, LINE_HELP_GROUP);
4285                         group = NULL;
4286                 }
4288                 add_line_format(view, LINE_DEFAULT, "    %-25s %-20s %s", key,
4289                                 enum_name(req_info[i]), req_info[i].help);
4290         }
4292         group = "External commands:";
4294         for (i = 0; i < run_requests; i++) {
4295                 struct run_request *req = get_run_request(REQ_NONE + i + 1);
4296                 const char *key;
4297                 int argc;
4299                 if (!req || req->keymap != keymap)
4300                         continue;
4302                 key = get_key_name(req->key);
4303                 if (!*key)
4304                         key = "(no key defined)";
4306                 if (add_title && help_open_keymap_title(view, keymap))
4307                         return;
4308                 if (group) {
4309                         add_line_text(view, group, LINE_HELP_GROUP);
4310                         group = NULL;
4311                 }
4313                 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
4314                         if (!string_format_from(buf, &bufpos, "%s%s",
4315                                                 argc ? " " : "", req->argv[argc]))
4316                                 return;
4318                 add_line_format(view, LINE_DEFAULT, "    %-25s `%s`", key, buf);
4319         }
4322 static bool
4323 help_open(struct view *view)
4325         enum keymap keymap;
4327         reset_view(view);
4328         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
4329         add_line_text(view, "", LINE_DEFAULT);
4331         for (keymap = 0; keymap < ARRAY_SIZE(keymap_table); keymap++)
4332                 help_open_keymap(view, keymap);
4334         return TRUE;
4337 static enum request
4338 help_request(struct view *view, enum request request, struct line *line)
4340         switch (request) {
4341         case REQ_ENTER:
4342                 if (line->type == LINE_HELP_KEYMAP) {
4343                         help_keymap_hidden[line->other] =
4344                                 !help_keymap_hidden[line->other];
4345                         view->p_restore = TRUE;
4346                         open_view(view, REQ_VIEW_HELP, OPEN_REFRESH);
4347                 }
4349                 return REQ_NONE;
4350         default:
4351                 return pager_request(view, request, line);
4352         }
4355 static struct view_ops help_ops = {
4356         "line",
4357         NULL,
4358         help_open,
4359         NULL,
4360         pager_draw,
4361         help_request,
4362         pager_grep,
4363         pager_select,
4364 };
4367 /*
4368  * Tree backend
4369  */
4371 struct tree_stack_entry {
4372         struct tree_stack_entry *prev;  /* Entry below this in the stack */
4373         unsigned long lineno;           /* Line number to restore */
4374         char *name;                     /* Position of name in opt_path */
4375 };
4377 /* The top of the path stack. */
4378 static struct tree_stack_entry *tree_stack = NULL;
4379 unsigned long tree_lineno = 0;
4381 static void
4382 pop_tree_stack_entry(void)
4384         struct tree_stack_entry *entry = tree_stack;
4386         tree_lineno = entry->lineno;
4387         entry->name[0] = 0;
4388         tree_stack = entry->prev;
4389         free(entry);
4392 static void
4393 push_tree_stack_entry(const char *name, unsigned long lineno)
4395         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
4396         size_t pathlen = strlen(opt_path);
4398         if (!entry)
4399                 return;
4401         entry->prev = tree_stack;
4402         entry->name = opt_path + pathlen;
4403         tree_stack = entry;
4405         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
4406                 pop_tree_stack_entry();
4407                 return;
4408         }
4410         /* Move the current line to the first tree entry. */
4411         tree_lineno = 1;
4412         entry->lineno = lineno;
4415 /* Parse output from git-ls-tree(1):
4416  *
4417  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4418  */
4420 #define SIZEOF_TREE_ATTR \
4421         STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4423 #define SIZEOF_TREE_MODE \
4424         STRING_SIZE("100644 ")
4426 #define TREE_ID_OFFSET \
4427         STRING_SIZE("100644 blob ")
4429 struct tree_entry {
4430         char id[SIZEOF_REV];
4431         mode_t mode;
4432         struct time time;               /* Date from the author ident. */
4433         const char *author;             /* Author of the commit. */
4434         char name[1];
4435 };
4437 static const char *
4438 tree_path(const struct line *line)
4440         return ((struct tree_entry *) line->data)->name;
4443 static int
4444 tree_compare_entry(const struct line *line1, const struct line *line2)
4446         if (line1->type != line2->type)
4447                 return line1->type == LINE_TREE_DIR ? -1 : 1;
4448         return strcmp(tree_path(line1), tree_path(line2));
4451 static const enum sort_field tree_sort_fields[] = {
4452         ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4453 };
4454 static struct sort_state tree_sort_state = SORT_STATE(tree_sort_fields);
4456 static int
4457 tree_compare(const void *l1, const void *l2)
4459         const struct line *line1 = (const struct line *) l1;
4460         const struct line *line2 = (const struct line *) l2;
4461         const struct tree_entry *entry1 = ((const struct line *) l1)->data;
4462         const struct tree_entry *entry2 = ((const struct line *) l2)->data;
4464         if (line1->type == LINE_TREE_HEAD)
4465                 return -1;
4466         if (line2->type == LINE_TREE_HEAD)
4467                 return 1;
4469         switch (get_sort_field(tree_sort_state)) {
4470         case ORDERBY_DATE:
4471                 return sort_order(tree_sort_state, timecmp(&entry1->time, &entry2->time));
4473         case ORDERBY_AUTHOR:
4474                 return sort_order(tree_sort_state, strcmp(entry1->author, entry2->author));
4476         case ORDERBY_NAME:
4477         default:
4478                 return sort_order(tree_sort_state, tree_compare_entry(line1, line2));
4479         }
4483 static struct line *
4484 tree_entry(struct view *view, enum line_type type, const char *path,
4485            const char *mode, const char *id)
4487         struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
4488         struct line *line = entry ? add_line_data(view, entry, type) : NULL;
4490         if (!entry || !line) {
4491                 free(entry);
4492                 return NULL;
4493         }
4495         strncpy(entry->name, path, strlen(path));
4496         if (mode)
4497                 entry->mode = strtoul(mode, NULL, 8);
4498         if (id)
4499                 string_copy_rev(entry->id, id);
4501         return line;
4504 static bool
4505 tree_read_date(struct view *view, char *text, bool *read_date)
4507         static const char *author_name;
4508         static struct time author_time;
4510         if (!text && *read_date) {
4511                 *read_date = FALSE;
4512                 return TRUE;
4514         } else if (!text) {
4515                 char *path = *opt_path ? opt_path : ".";
4516                 /* Find next entry to process */
4517                 const char *log_file[] = {
4518                         "git", "log", "--no-color", "--pretty=raw",
4519                                 "--cc", "--raw", view->id, "--", path, NULL
4520                 };
4522                 if (!view->lines) {
4523                         tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
4524                         report("Tree is empty");
4525                         return TRUE;
4526                 }
4528                 if (!start_update(view, log_file, opt_cdup)) {
4529                         report("Failed to load tree data");
4530                         return TRUE;
4531                 }
4533                 *read_date = TRUE;
4534                 return FALSE;
4536         } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
4537                 parse_author_line(text + STRING_SIZE("author "),
4538                                   &author_name, &author_time);
4540         } else if (*text == ':') {
4541                 char *pos;
4542                 size_t annotated = 1;
4543                 size_t i;
4545                 pos = strchr(text, '\t');
4546                 if (!pos)
4547                         return TRUE;
4548                 text = pos + 1;
4549                 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
4550                         text += strlen(opt_path);
4551                 pos = strchr(text, '/');
4552                 if (pos)
4553                         *pos = 0;
4555                 for (i = 1; i < view->lines; i++) {
4556                         struct line *line = &view->line[i];
4557                         struct tree_entry *entry = line->data;
4559                         annotated += !!entry->author;
4560                         if (entry->author || strcmp(entry->name, text))
4561                                 continue;
4563                         entry->author = author_name;
4564                         entry->time = author_time;
4565                         line->dirty = 1;
4566                         break;
4567                 }
4569                 if (annotated == view->lines)
4570                         io_kill(view->pipe);
4571         }
4572         return TRUE;
4575 static bool
4576 tree_read(struct view *view, char *text)
4578         static bool read_date = FALSE;
4579         struct tree_entry *data;
4580         struct line *entry, *line;
4581         enum line_type type;
4582         size_t textlen = text ? strlen(text) : 0;
4583         char *path = text + SIZEOF_TREE_ATTR;
4585         if (read_date || !text)
4586                 return tree_read_date(view, text, &read_date);
4588         if (textlen <= SIZEOF_TREE_ATTR)
4589                 return FALSE;
4590         if (view->lines == 0 &&
4591             !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
4592                 return FALSE;
4594         /* Strip the path part ... */
4595         if (*opt_path) {
4596                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
4597                 size_t striplen = strlen(opt_path);
4599                 if (pathlen > striplen)
4600                         memmove(path, path + striplen,
4601                                 pathlen - striplen + 1);
4603                 /* Insert "link" to parent directory. */
4604                 if (view->lines == 1 &&
4605                     !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
4606                         return FALSE;
4607         }
4609         type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
4610         entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
4611         if (!entry)
4612                 return FALSE;
4613         data = entry->data;
4615         /* Skip "Directory ..." and ".." line. */
4616         for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
4617                 if (tree_compare_entry(line, entry) <= 0)
4618                         continue;
4620                 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4622                 line->data = data;
4623                 line->type = type;
4624                 for (; line <= entry; line++)
4625                         line->dirty = line->cleareol = 1;
4626                 return TRUE;
4627         }
4629         if (tree_lineno > view->lineno) {
4630                 view->lineno = tree_lineno;
4631                 tree_lineno = 0;
4632         }
4634         return TRUE;
4637 static bool
4638 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4640         struct tree_entry *entry = line->data;
4642         if (line->type == LINE_TREE_HEAD) {
4643                 if (draw_text(view, line->type, "Directory path /", TRUE))
4644                         return TRUE;
4645         } else {
4646                 if (draw_mode(view, entry->mode))
4647                         return TRUE;
4649                 if (opt_author && draw_author(view, entry->author))
4650                         return TRUE;
4652                 if (opt_date && draw_date(view, &entry->time))
4653                         return TRUE;
4654         }
4655         if (draw_text(view, line->type, entry->name, TRUE))
4656                 return TRUE;
4657         return TRUE;
4660 static void
4661 open_blob_editor(const char *id)
4663         const char *blob_argv[] = { "git", "cat-file", "blob", id, NULL };
4664         char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4665         int fd = mkstemp(file);
4667         if (fd == -1)
4668                 report("Failed to create temporary file");
4669         else if (!io_run_append(blob_argv, fd))
4670                 report("Failed to save blob data to file");
4671         else
4672                 open_editor(file);
4673         if (fd != -1)
4674                 unlink(file);
4677 static enum request
4678 tree_request(struct view *view, enum request request, struct line *line)
4680         enum open_flags flags;
4681         struct tree_entry *entry = line->data;
4683         switch (request) {
4684         case REQ_VIEW_BLAME:
4685                 if (line->type != LINE_TREE_FILE) {
4686                         report("Blame only supported for files");
4687                         return REQ_NONE;
4688                 }
4690                 string_copy(opt_ref, view->vid);
4691                 return request;
4693         case REQ_EDIT:
4694                 if (line->type != LINE_TREE_FILE) {
4695                         report("Edit only supported for files");
4696                 } else if (!is_head_commit(view->vid)) {
4697                         open_blob_editor(entry->id);
4698                 } else {
4699                         open_editor(opt_file);
4700                 }
4701                 return REQ_NONE;
4703         case REQ_TOGGLE_SORT_FIELD:
4704         case REQ_TOGGLE_SORT_ORDER:
4705                 sort_view(view, request, &tree_sort_state, tree_compare);
4706                 return REQ_NONE;
4708         case REQ_PARENT:
4709                 if (!*opt_path) {
4710                         /* quit view if at top of tree */
4711                         return REQ_VIEW_CLOSE;
4712                 }
4713                 /* fake 'cd  ..' */
4714                 line = &view->line[1];
4715                 break;
4717         case REQ_ENTER:
4718                 break;
4720         default:
4721                 return request;
4722         }
4724         /* Cleanup the stack if the tree view is at a different tree. */
4725         while (!*opt_path && tree_stack)
4726                 pop_tree_stack_entry();
4728         switch (line->type) {
4729         case LINE_TREE_DIR:
4730                 /* Depending on whether it is a subdirectory or parent link
4731                  * mangle the path buffer. */
4732                 if (line == &view->line[1] && *opt_path) {
4733                         pop_tree_stack_entry();
4735                 } else {
4736                         const char *basename = tree_path(line);
4738                         push_tree_stack_entry(basename, view->lineno);
4739                 }
4741                 /* Trees and subtrees share the same ID, so they are not not
4742                  * unique like blobs. */
4743                 flags = OPEN_RELOAD;
4744                 request = REQ_VIEW_TREE;
4745                 break;
4747         case LINE_TREE_FILE:
4748                 flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
4749                 request = REQ_VIEW_BLOB;
4750                 break;
4752         default:
4753                 return REQ_NONE;
4754         }
4756         open_view(view, request, flags);
4757         if (request == REQ_VIEW_TREE)
4758                 view->lineno = tree_lineno;
4760         return REQ_NONE;
4763 static bool
4764 tree_grep(struct view *view, struct line *line)
4766         struct tree_entry *entry = line->data;
4767         const char *text[] = {
4768                 entry->name,
4769                 opt_author ? entry->author : "",
4770                 mkdate(&entry->time, opt_date),
4771                 NULL
4772         };
4774         return grep_text(view, text);
4777 static void
4778 tree_select(struct view *view, struct line *line)
4780         struct tree_entry *entry = line->data;
4782         if (line->type == LINE_TREE_FILE) {
4783                 string_copy_rev(ref_blob, entry->id);
4784                 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4786         } else if (line->type != LINE_TREE_DIR) {
4787                 return;
4788         }
4790         string_copy_rev(view->ref, entry->id);
4793 static bool
4794 tree_prepare(struct view *view)
4796         if (view->lines == 0 && opt_prefix[0]) {
4797                 char *pos = opt_prefix;
4799                 while (pos && *pos) {
4800                         char *end = strchr(pos, '/');
4802                         if (end)
4803                                 *end = 0;
4804                         push_tree_stack_entry(pos, 0);
4805                         pos = end;
4806                         if (end) {
4807                                 *end = '/';
4808                                 pos++;
4809                         }
4810                 }
4812         } else if (strcmp(view->vid, view->id)) {
4813                 opt_path[0] = 0;
4814         }
4816         return prepare_io(view, opt_cdup, view->ops->argv, TRUE);
4819 static const char *tree_argv[SIZEOF_ARG] = {
4820         "git", "ls-tree", "%(commit)", "%(directory)", NULL
4821 };
4823 static struct view_ops tree_ops = {
4824         "file",
4825         tree_argv,
4826         NULL,
4827         tree_read,
4828         tree_draw,
4829         tree_request,
4830         tree_grep,
4831         tree_select,
4832         tree_prepare,
4833 };
4835 static bool
4836 blob_read(struct view *view, char *line)
4838         if (!line)
4839                 return TRUE;
4840         return add_line_text(view, line, LINE_DEFAULT) != NULL;
4843 static enum request
4844 blob_request(struct view *view, enum request request, struct line *line)
4846         switch (request) {
4847         case REQ_EDIT:
4848                 open_blob_editor(view->vid);
4849                 return REQ_NONE;
4850         default:
4851                 return pager_request(view, request, line);
4852         }
4855 static const char *blob_argv[SIZEOF_ARG] = {
4856         "git", "cat-file", "blob", "%(blob)", NULL
4857 };
4859 static struct view_ops blob_ops = {
4860         "line",
4861         blob_argv,
4862         NULL,
4863         blob_read,
4864         pager_draw,
4865         blob_request,
4866         pager_grep,
4867         pager_select,
4868 };
4870 /*
4871  * Blame backend
4872  *
4873  * Loading the blame view is a two phase job:
4874  *
4875  *  1. File content is read either using opt_file from the
4876  *     filesystem or using git-cat-file.
4877  *  2. Then blame information is incrementally added by
4878  *     reading output from git-blame.
4879  */
4881 struct blame_commit {
4882         char id[SIZEOF_REV];            /* SHA1 ID. */
4883         char title[128];                /* First line of the commit message. */
4884         const char *author;             /* Author of the commit. */
4885         struct time time;               /* Date from the author ident. */
4886         char filename[128];             /* Name of file. */
4887         char parent_id[SIZEOF_REV];     /* Parent/previous SHA1 ID. */
4888         char parent_filename[128];      /* Parent/previous name of file. */
4889 };
4891 struct blame {
4892         struct blame_commit *commit;
4893         unsigned long lineno;
4894         char text[1];
4895 };
4897 static bool
4898 blame_open(struct view *view)
4900         char path[SIZEOF_STR];
4901         size_t i;
4903         if (!view->prev && *opt_prefix) {
4904                 string_copy(path, opt_file);
4905                 if (!string_format(opt_file, "%s%s", opt_prefix, path))
4906                         return FALSE;
4907         }
4909         if (*opt_ref || !io_open(&view->io, "%s%s", opt_cdup, opt_file)) {
4910                 const char *blame_cat_file_argv[] = {
4911                         "git", "cat-file", "blob", path, NULL
4912                 };
4914                 if (!string_format(path, "%s:%s", opt_ref, opt_file) ||
4915                     !start_update(view, blame_cat_file_argv, opt_cdup))
4916                         return FALSE;
4917         }
4919         /* First pass: remove multiple references to the same commit. */
4920         for (i = 0; i < view->lines; i++) {
4921                 struct blame *blame = view->line[i].data;
4923                 if (blame->commit && blame->commit->id[0])
4924                         blame->commit->id[0] = 0;
4925                 else
4926                         blame->commit = NULL;
4927         }
4929         /* Second pass: free existing references. */
4930         for (i = 0; i < view->lines; i++) {
4931                 struct blame *blame = view->line[i].data;
4933                 if (blame->commit)
4934                         free(blame->commit);
4935         }
4937         setup_update(view, opt_file);
4938         string_format(view->ref, "%s ...", opt_file);
4940         return TRUE;
4943 static struct blame_commit *
4944 get_blame_commit(struct view *view, const char *id)
4946         size_t i;
4948         for (i = 0; i < view->lines; i++) {
4949                 struct blame *blame = view->line[i].data;
4951                 if (!blame->commit)
4952                         continue;
4954                 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4955                         return blame->commit;
4956         }
4958         {
4959                 struct blame_commit *commit = calloc(1, sizeof(*commit));
4961                 if (commit)
4962                         string_ncopy(commit->id, id, SIZEOF_REV);
4963                 return commit;
4964         }
4967 static bool
4968 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4970         const char *pos = *posref;
4972         *posref = NULL;
4973         pos = strchr(pos + 1, ' ');
4974         if (!pos || !isdigit(pos[1]))
4975                 return FALSE;
4976         *number = atoi(pos + 1);
4977         if (*number < min || *number > max)
4978                 return FALSE;
4980         *posref = pos;
4981         return TRUE;
4984 static struct blame_commit *
4985 parse_blame_commit(struct view *view, const char *text, int *blamed)
4987         struct blame_commit *commit;
4988         struct blame *blame;
4989         const char *pos = text + SIZEOF_REV - 2;
4990         size_t orig_lineno = 0;
4991         size_t lineno;
4992         size_t group;
4994         if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
4995                 return NULL;
4997         if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
4998             !parse_number(&pos, &lineno, 1, view->lines) ||
4999             !parse_number(&pos, &group, 1, view->lines - lineno + 1))
5000                 return NULL;
5002         commit = get_blame_commit(view, text);
5003         if (!commit)
5004                 return NULL;
5006         *blamed += group;
5007         while (group--) {
5008                 struct line *line = &view->line[lineno + group - 1];
5010                 blame = line->data;
5011                 blame->commit = commit;
5012                 blame->lineno = orig_lineno + group - 1;
5013                 line->dirty = 1;
5014         }
5016         return commit;
5019 static bool
5020 blame_read_file(struct view *view, const char *line, bool *read_file)
5022         if (!line) {
5023                 const char *blame_argv[] = {
5024                         "git", "blame", "--incremental",
5025                                 *opt_ref ? opt_ref : "--incremental", "--", opt_file, NULL
5026                 };
5028                 if (view->lines == 0 && !view->prev)
5029                         die("No blame exist for %s", view->vid);
5031                 if (view->lines == 0 || !start_update(view, blame_argv, opt_cdup)) {
5032                         report("Failed to load blame data");
5033                         return TRUE;
5034                 }
5036                 *read_file = FALSE;
5037                 return FALSE;
5039         } else {
5040                 size_t linelen = strlen(line);
5041                 struct blame *blame = malloc(sizeof(*blame) + linelen);
5043                 if (!blame)
5044                         return FALSE;
5046                 blame->commit = NULL;
5047                 strncpy(blame->text, line, linelen);
5048                 blame->text[linelen] = 0;
5049                 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
5050         }
5053 static bool
5054 match_blame_header(const char *name, char **line)
5056         size_t namelen = strlen(name);
5057         bool matched = !strncmp(name, *line, namelen);
5059         if (matched)
5060                 *line += namelen;
5062         return matched;
5065 static bool
5066 blame_read(struct view *view, char *line)
5068         static struct blame_commit *commit = NULL;
5069         static int blamed = 0;
5070         static bool read_file = TRUE;
5072         if (read_file)
5073                 return blame_read_file(view, line, &read_file);
5075         if (!line) {
5076                 /* Reset all! */
5077                 commit = NULL;
5078                 blamed = 0;
5079                 read_file = TRUE;
5080                 string_format(view->ref, "%s", view->vid);
5081                 if (view_is_displayed(view)) {
5082                         update_view_title(view);
5083                         redraw_view_from(view, 0);
5084                 }
5085                 return TRUE;
5086         }
5088         if (!commit) {
5089                 commit = parse_blame_commit(view, line, &blamed);
5090                 string_format(view->ref, "%s %2d%%", view->vid,
5091                               view->lines ? blamed * 100 / view->lines : 0);
5093         } else if (match_blame_header("author ", &line)) {
5094                 commit->author = get_author(line);
5096         } else if (match_blame_header("author-time ", &line)) {
5097                 parse_timesec(&commit->time, line);
5099         } else if (match_blame_header("author-tz ", &line)) {
5100                 parse_timezone(&commit->time, line);
5102         } else if (match_blame_header("summary ", &line)) {
5103                 string_ncopy(commit->title, line, strlen(line));
5105         } else if (match_blame_header("previous ", &line)) {
5106                 if (strlen(line) <= SIZEOF_REV)
5107                         return FALSE;
5108                 string_copy_rev(commit->parent_id, line);
5109                 line += SIZEOF_REV;
5110                 string_ncopy(commit->parent_filename, line, strlen(line));
5112         } else if (match_blame_header("filename ", &line)) {
5113                 string_ncopy(commit->filename, line, strlen(line));
5114                 commit = NULL;
5115         }
5117         return TRUE;
5120 static bool
5121 blame_draw(struct view *view, struct line *line, unsigned int lineno)
5123         struct blame *blame = line->data;
5124         struct time *time = NULL;
5125         const char *id = NULL, *author = NULL;
5127         if (blame->commit && *blame->commit->filename) {
5128                 id = blame->commit->id;
5129                 author = blame->commit->author;
5130                 time = &blame->commit->time;
5131         }
5133         if (opt_date && draw_date(view, time))
5134                 return TRUE;
5136         if (opt_author && draw_author(view, author))
5137                 return TRUE;
5139         if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
5140                 return TRUE;
5142         if (draw_lineno(view, lineno))
5143                 return TRUE;
5145         draw_text(view, LINE_DEFAULT, blame->text, TRUE);
5146         return TRUE;
5149 static bool
5150 check_blame_commit(struct blame *blame, bool check_null_id)
5152         if (!blame->commit)
5153                 report("Commit data not loaded yet");
5154         else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
5155                 report("No commit exist for the selected line");
5156         else
5157                 return TRUE;
5158         return FALSE;
5161 static void
5162 setup_blame_parent_line(struct view *view, struct blame *blame)
5164         char from[SIZEOF_REF + SIZEOF_STR];
5165         char to[SIZEOF_REF + SIZEOF_STR];
5166         const char *diff_tree_argv[] = {
5167                 "git", "diff", "--no-textconv", "--no-extdiff", "--no-color",
5168                         "-U0", from, to, "--", NULL
5169         };
5170         struct io io;
5171         int parent_lineno = -1;
5172         int blamed_lineno = -1;
5173         char *line;
5175         if (!string_format(from, "%s:%s", opt_ref, opt_file) ||
5176             !string_format(to, "%s:%s", blame->commit->id, blame->commit->filename) ||
5177             !io_run(&io, IO_RD, NULL, diff_tree_argv))
5178                 return;
5180         while ((line = io_get(&io, '\n', TRUE))) {
5181                 if (*line == '@') {
5182                         char *pos = strchr(line, '+');
5184                         parent_lineno = atoi(line + 4);
5185                         if (pos)
5186                                 blamed_lineno = atoi(pos + 1);
5188                 } else if (*line == '+' && parent_lineno != -1) {
5189                         if (blame->lineno == blamed_lineno - 1 &&
5190                             !strcmp(blame->text, line + 1)) {
5191                                 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
5192                                 break;
5193                         }
5194                         blamed_lineno++;
5195                 }
5196         }
5198         io_done(&io);
5201 static enum request
5202 blame_request(struct view *view, enum request request, struct line *line)
5204         enum open_flags flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
5205         struct blame *blame = line->data;
5207         switch (request) {
5208         case REQ_VIEW_BLAME:
5209                 if (check_blame_commit(blame, TRUE)) {
5210                         string_copy(opt_ref, blame->commit->id);
5211                         string_copy(opt_file, blame->commit->filename);
5212                         if (blame->lineno)
5213                                 view->lineno = blame->lineno;
5214                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5215                 }
5216                 break;
5218         case REQ_PARENT:
5219                 if (!check_blame_commit(blame, TRUE))
5220                         break;
5221                 if (!*blame->commit->parent_id) {
5222                         report("The selected commit has no parents");
5223                 } else {
5224                         string_copy_rev(opt_ref, blame->commit->parent_id);
5225                         string_copy(opt_file, blame->commit->parent_filename);
5226                         setup_blame_parent_line(view, blame);
5227                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5228                 }
5229                 break;
5231         case REQ_ENTER:
5232                 if (!check_blame_commit(blame, FALSE))
5233                         break;
5235                 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
5236                     !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
5237                         break;
5239                 if (!strcmp(blame->commit->id, NULL_ID)) {
5240                         struct view *diff = VIEW(REQ_VIEW_DIFF);
5241                         const char *diff_index_argv[] = {
5242                                 "git", "diff-index", "--root", "--patch-with-stat",
5243                                         "-C", "-M", "HEAD", "--", view->vid, NULL
5244                         };
5246                         if (!*blame->commit->parent_id) {
5247                                 diff_index_argv[1] = "diff";
5248                                 diff_index_argv[2] = "--no-color";
5249                                 diff_index_argv[6] = "--";
5250                                 diff_index_argv[7] = "/dev/null";
5251                         }
5253                         if (!prepare_update(diff, diff_index_argv, NULL)) {
5254                                 report("Failed to allocate diff command");
5255                                 break;
5256                         }
5257                         flags |= OPEN_PREPARED;
5258                 }
5260                 open_view(view, REQ_VIEW_DIFF, flags);
5261                 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
5262                         string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
5263                 break;
5265         default:
5266                 return request;
5267         }
5269         return REQ_NONE;
5272 static bool
5273 blame_grep(struct view *view, struct line *line)
5275         struct blame *blame = line->data;
5276         struct blame_commit *commit = blame->commit;
5277         const char *text[] = {
5278                 blame->text,
5279                 commit ? commit->title : "",
5280                 commit ? commit->id : "",
5281                 commit && opt_author ? commit->author : "",
5282                 commit ? mkdate(&commit->time, opt_date) : "",
5283                 NULL
5284         };
5286         return grep_text(view, text);
5289 static void
5290 blame_select(struct view *view, struct line *line)
5292         struct blame *blame = line->data;
5293         struct blame_commit *commit = blame->commit;
5295         if (!commit)
5296                 return;
5298         if (!strcmp(commit->id, NULL_ID))
5299                 string_ncopy(ref_commit, "HEAD", 4);
5300         else
5301                 string_copy_rev(ref_commit, commit->id);
5304 static struct view_ops blame_ops = {
5305         "line",
5306         NULL,
5307         blame_open,
5308         blame_read,
5309         blame_draw,
5310         blame_request,
5311         blame_grep,
5312         blame_select,
5313 };
5315 /*
5316  * Branch backend
5317  */
5319 struct branch {
5320         const char *author;             /* Author of the last commit. */
5321         struct time time;               /* Date of the last activity. */
5322         const struct ref *ref;          /* Name and commit ID information. */
5323 };
5325 static const struct ref branch_all;
5327 static const enum sort_field branch_sort_fields[] = {
5328         ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
5329 };
5330 static struct sort_state branch_sort_state = SORT_STATE(branch_sort_fields);
5332 static int
5333 branch_compare(const void *l1, const void *l2)
5335         const struct branch *branch1 = ((const struct line *) l1)->data;
5336         const struct branch *branch2 = ((const struct line *) l2)->data;
5338         switch (get_sort_field(branch_sort_state)) {
5339         case ORDERBY_DATE:
5340                 return sort_order(branch_sort_state, timecmp(&branch1->time, &branch2->time));
5342         case ORDERBY_AUTHOR:
5343                 return sort_order(branch_sort_state, strcmp(branch1->author, branch2->author));
5345         case ORDERBY_NAME:
5346         default:
5347                 return sort_order(branch_sort_state, strcmp(branch1->ref->name, branch2->ref->name));
5348         }
5351 static bool
5352 branch_draw(struct view *view, struct line *line, unsigned int lineno)
5354         struct branch *branch = line->data;
5355         enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
5357         if (opt_date && draw_date(view, &branch->time))
5358                 return TRUE;
5360         if (opt_author && draw_author(view, branch->author))
5361                 return TRUE;
5363         draw_text(view, type, branch->ref == &branch_all ? "All branches" : branch->ref->name, TRUE);
5364         return TRUE;
5367 static enum request
5368 branch_request(struct view *view, enum request request, struct line *line)
5370         struct branch *branch = line->data;
5372         switch (request) {
5373         case REQ_REFRESH:
5374                 load_refs();
5375                 open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
5376                 return REQ_NONE;
5378         case REQ_TOGGLE_SORT_FIELD:
5379         case REQ_TOGGLE_SORT_ORDER:
5380                 sort_view(view, request, &branch_sort_state, branch_compare);
5381                 return REQ_NONE;
5383         case REQ_ENTER:
5384         {
5385                 const struct ref *ref = branch->ref;
5386                 const char *all_branches_argv[] = {
5387                         "git", "log", "--no-color", "--pretty=raw", "--parents",
5388                               "--topo-order",
5389                               ref == &branch_all ? "--all" : ref->name, NULL
5390                 };
5391                 struct view *main_view = VIEW(REQ_VIEW_MAIN);
5393                 if (!prepare_update(main_view, all_branches_argv, NULL))
5394                         report("Failed to load view of all branches");
5395                 else
5396                         open_view(view, REQ_VIEW_MAIN, OPEN_PREPARED | OPEN_SPLIT);
5397                 return REQ_NONE;
5398         }
5399         default:
5400                 return request;
5401         }
5404 static bool
5405 branch_read(struct view *view, char *line)
5407         static char id[SIZEOF_REV];
5408         struct branch *reference;
5409         size_t i;
5411         if (!line)
5412                 return TRUE;
5414         switch (get_line_type(line)) {
5415         case LINE_COMMIT:
5416                 string_copy_rev(id, line + STRING_SIZE("commit "));
5417                 return TRUE;
5419         case LINE_AUTHOR:
5420                 for (i = 0, reference = NULL; i < view->lines; i++) {
5421                         struct branch *branch = view->line[i].data;
5423                         if (strcmp(branch->ref->id, id))
5424                                 continue;
5426                         view->line[i].dirty = TRUE;
5427                         if (reference) {
5428                                 branch->author = reference->author;
5429                                 branch->time = reference->time;
5430                                 continue;
5431                         }
5433                         parse_author_line(line + STRING_SIZE("author "),
5434                                           &branch->author, &branch->time);
5435                         reference = branch;
5436                 }
5437                 return TRUE;
5439         default:
5440                 return TRUE;
5441         }
5445 static bool
5446 branch_open_visitor(void *data, const struct ref *ref)
5448         struct view *view = data;
5449         struct branch *branch;
5451         if (ref->tag || ref->ltag || ref->remote)
5452                 return TRUE;
5454         branch = calloc(1, sizeof(*branch));
5455         if (!branch)
5456                 return FALSE;
5458         branch->ref = ref;
5459         return !!add_line_data(view, branch, LINE_DEFAULT);
5462 static bool
5463 branch_open(struct view *view)
5465         const char *branch_log[] = {
5466                 "git", "log", "--no-color", "--pretty=raw",
5467                         "--simplify-by-decoration", "--all", NULL
5468         };
5470         if (!start_update(view, branch_log, NULL)) {
5471                 report("Failed to load branch data");
5472                 return TRUE;
5473         }
5475         setup_update(view, view->id);
5476         branch_open_visitor(view, &branch_all);
5477         foreach_ref(branch_open_visitor, view);
5478         view->p_restore = TRUE;
5480         return TRUE;
5483 static bool
5484 branch_grep(struct view *view, struct line *line)
5486         struct branch *branch = line->data;
5487         const char *text[] = {
5488                 branch->ref->name,
5489                 branch->author,
5490                 NULL
5491         };
5493         return grep_text(view, text);
5496 static void
5497 branch_select(struct view *view, struct line *line)
5499         struct branch *branch = line->data;
5501         string_copy_rev(view->ref, branch->ref->id);
5502         string_copy_rev(ref_commit, branch->ref->id);
5503         string_copy_rev(ref_head, branch->ref->id);
5504         string_copy_rev(ref_branch, branch->ref->name);
5507 static struct view_ops branch_ops = {
5508         "branch",
5509         NULL,
5510         branch_open,
5511         branch_read,
5512         branch_draw,
5513         branch_request,
5514         branch_grep,
5515         branch_select,
5516 };
5518 /*
5519  * Status backend
5520  */
5522 struct status {
5523         char status;
5524         struct {
5525                 mode_t mode;
5526                 char rev[SIZEOF_REV];
5527                 char name[SIZEOF_STR];
5528         } old;
5529         struct {
5530                 mode_t mode;
5531                 char rev[SIZEOF_REV];
5532                 char name[SIZEOF_STR];
5533         } new;
5534 };
5536 static char status_onbranch[SIZEOF_STR];
5537 static struct status stage_status;
5538 static enum line_type stage_line_type;
5539 static size_t stage_chunks;
5540 static int *stage_chunk;
5542 DEFINE_ALLOCATOR(realloc_ints, int, 32)
5544 /* This should work even for the "On branch" line. */
5545 static inline bool
5546 status_has_none(struct view *view, struct line *line)
5548         return line < view->line + view->lines && !line[1].data;
5551 /* Get fields from the diff line:
5552  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5553  */
5554 static inline bool
5555 status_get_diff(struct status *file, const char *buf, size_t bufsize)
5557         const char *old_mode = buf +  1;
5558         const char *new_mode = buf +  8;
5559         const char *old_rev  = buf + 15;
5560         const char *new_rev  = buf + 56;
5561         const char *status   = buf + 97;
5563         if (bufsize < 98 ||
5564             old_mode[-1] != ':' ||
5565             new_mode[-1] != ' ' ||
5566             old_rev[-1]  != ' ' ||
5567             new_rev[-1]  != ' ' ||
5568             status[-1]   != ' ')
5569                 return FALSE;
5571         file->status = *status;
5573         string_copy_rev(file->old.rev, old_rev);
5574         string_copy_rev(file->new.rev, new_rev);
5576         file->old.mode = strtoul(old_mode, NULL, 8);
5577         file->new.mode = strtoul(new_mode, NULL, 8);
5579         file->old.name[0] = file->new.name[0] = 0;
5581         return TRUE;
5584 static bool
5585 status_run(struct view *view, const char *argv[], char status, enum line_type type)
5587         struct status *unmerged = NULL;
5588         char *buf;
5589         struct io io;
5591         if (!io_run(&io, IO_RD, opt_cdup, argv))
5592                 return FALSE;
5594         add_line_data(view, NULL, type);
5596         while ((buf = io_get(&io, 0, TRUE))) {
5597                 struct status *file = unmerged;
5599                 if (!file) {
5600                         file = calloc(1, sizeof(*file));
5601                         if (!file || !add_line_data(view, file, type))
5602                                 goto error_out;
5603                 }
5605                 /* Parse diff info part. */
5606                 if (status) {
5607                         file->status = status;
5608                         if (status == 'A')
5609                                 string_copy(file->old.rev, NULL_ID);
5611                 } else if (!file->status || file == unmerged) {
5612                         if (!status_get_diff(file, buf, strlen(buf)))
5613                                 goto error_out;
5615                         buf = io_get(&io, 0, TRUE);
5616                         if (!buf)
5617                                 break;
5619                         /* Collapse all modified entries that follow an
5620                          * associated unmerged entry. */
5621                         if (unmerged == file) {
5622                                 unmerged->status = 'U';
5623                                 unmerged = NULL;
5624                         } else if (file->status == 'U') {
5625                                 unmerged = file;
5626                         }
5627                 }
5629                 /* Grab the old name for rename/copy. */
5630                 if (!*file->old.name &&
5631                     (file->status == 'R' || file->status == 'C')) {
5632                         string_ncopy(file->old.name, buf, strlen(buf));
5634                         buf = io_get(&io, 0, TRUE);
5635                         if (!buf)
5636                                 break;
5637                 }
5639                 /* git-ls-files just delivers a NUL separated list of
5640                  * file names similar to the second half of the
5641                  * git-diff-* output. */
5642                 string_ncopy(file->new.name, buf, strlen(buf));
5643                 if (!*file->old.name)
5644                         string_copy(file->old.name, file->new.name);
5645                 file = NULL;
5646         }
5648         if (io_error(&io)) {
5649 error_out:
5650                 io_done(&io);
5651                 return FALSE;
5652         }
5654         if (!view->line[view->lines - 1].data)
5655                 add_line_data(view, NULL, LINE_STAT_NONE);
5657         io_done(&io);
5658         return TRUE;
5661 /* Don't show unmerged entries in the staged section. */
5662 static const char *status_diff_index_argv[] = {
5663         "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5664                              "--cached", "-M", "HEAD", NULL
5665 };
5667 static const char *status_diff_files_argv[] = {
5668         "git", "diff-files", "-z", NULL
5669 };
5671 static const char *status_list_other_argv[] = {
5672         "git", "ls-files", "-z", "--others", "--exclude-standard", opt_prefix, NULL
5673 };
5675 static const char *status_list_no_head_argv[] = {
5676         "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5677 };
5679 static const char *update_index_argv[] = {
5680         "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5681 };
5683 /* Restore the previous line number to stay in the context or select a
5684  * line with something that can be updated. */
5685 static void
5686 status_restore(struct view *view)
5688         if (view->p_lineno >= view->lines)
5689                 view->p_lineno = view->lines - 1;
5690         while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
5691                 view->p_lineno++;
5692         while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
5693                 view->p_lineno--;
5695         /* If the above fails, always skip the "On branch" line. */
5696         if (view->p_lineno < view->lines)
5697                 view->lineno = view->p_lineno;
5698         else
5699                 view->lineno = 1;
5701         if (view->lineno < view->offset)
5702                 view->offset = view->lineno;
5703         else if (view->offset + view->height <= view->lineno)
5704                 view->offset = view->lineno - view->height + 1;
5706         view->p_restore = FALSE;
5709 static void
5710 status_update_onbranch(void)
5712         static const char *paths[][2] = {
5713                 { "rebase-apply/rebasing",      "Rebasing" },
5714                 { "rebase-apply/applying",      "Applying mailbox" },
5715                 { "rebase-apply/",              "Rebasing mailbox" },
5716                 { "rebase-merge/interactive",   "Interactive rebase" },
5717                 { "rebase-merge/",              "Rebase merge" },
5718                 { "MERGE_HEAD",                 "Merging" },
5719                 { "BISECT_LOG",                 "Bisecting" },
5720                 { "HEAD",                       "On branch" },
5721         };
5722         char buf[SIZEOF_STR];
5723         struct stat stat;
5724         int i;
5726         if (is_initial_commit()) {
5727                 string_copy(status_onbranch, "Initial commit");
5728                 return;
5729         }
5731         for (i = 0; i < ARRAY_SIZE(paths); i++) {
5732                 char *head = opt_head;
5734                 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5735                     lstat(buf, &stat) < 0)
5736                         continue;
5738                 if (!*opt_head) {
5739                         struct io io;
5741                         if (io_open(&io, "%s/rebase-merge/head-name", opt_git_dir) &&
5742                             io_read_buf(&io, buf, sizeof(buf))) {
5743                                 head = buf;
5744                                 if (!prefixcmp(head, "refs/heads/"))
5745                                         head += STRING_SIZE("refs/heads/");
5746                         }
5747                 }
5749                 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5750                         string_copy(status_onbranch, opt_head);
5751                 return;
5752         }
5754         string_copy(status_onbranch, "Not currently on any branch");
5757 /* First parse staged info using git-diff-index(1), then parse unstaged
5758  * info using git-diff-files(1), and finally untracked files using
5759  * git-ls-files(1). */
5760 static bool
5761 status_open(struct view *view)
5763         reset_view(view);
5765         add_line_data(view, NULL, LINE_STAT_HEAD);
5766         status_update_onbranch();
5768         io_run_bg(update_index_argv);
5770         if (is_initial_commit()) {
5771                 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5772                         return FALSE;
5773         } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5774                 return FALSE;
5775         }
5777         if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5778             !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5779                 return FALSE;
5781         /* Restore the exact position or use the specialized restore
5782          * mode? */
5783         if (!view->p_restore)
5784                 status_restore(view);
5785         return TRUE;
5788 static bool
5789 status_draw(struct view *view, struct line *line, unsigned int lineno)
5791         struct status *status = line->data;
5792         enum line_type type;
5793         const char *text;
5795         if (!status) {
5796                 switch (line->type) {
5797                 case LINE_STAT_STAGED:
5798                         type = LINE_STAT_SECTION;
5799                         text = "Changes to be committed:";
5800                         break;
5802                 case LINE_STAT_UNSTAGED:
5803                         type = LINE_STAT_SECTION;
5804                         text = "Changed but not updated:";
5805                         break;
5807                 case LINE_STAT_UNTRACKED:
5808                         type = LINE_STAT_SECTION;
5809                         text = "Untracked files:";
5810                         break;
5812                 case LINE_STAT_NONE:
5813                         type = LINE_DEFAULT;
5814                         text = "  (no files)";
5815                         break;
5817                 case LINE_STAT_HEAD:
5818                         type = LINE_STAT_HEAD;
5819                         text = status_onbranch;
5820                         break;
5822                 default:
5823                         return FALSE;
5824                 }
5825         } else {
5826                 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5828                 buf[0] = status->status;
5829                 if (draw_text(view, line->type, buf, TRUE))
5830                         return TRUE;
5831                 type = LINE_DEFAULT;
5832                 text = status->new.name;
5833         }
5835         draw_text(view, type, text, TRUE);
5836         return TRUE;
5839 static enum request
5840 status_load_error(struct view *view, struct view *stage, const char *path)
5842         if (displayed_views() == 2 || display[current_view] != view)
5843                 maximize_view(view);
5844         report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5845         return REQ_NONE;
5848 static enum request
5849 status_enter(struct view *view, struct line *line)
5851         struct status *status = line->data;
5852         const char *oldpath = status ? status->old.name : NULL;
5853         /* Diffs for unmerged entries are empty when passing the new
5854          * path, so leave it empty. */
5855         const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5856         const char *info;
5857         enum open_flags split;
5858         struct view *stage = VIEW(REQ_VIEW_STAGE);
5860         if (line->type == LINE_STAT_NONE ||
5861             (!status && line[1].type == LINE_STAT_NONE)) {
5862                 report("No file to diff");
5863                 return REQ_NONE;
5864         }
5866         switch (line->type) {
5867         case LINE_STAT_STAGED:
5868                 if (is_initial_commit()) {
5869                         const char *no_head_diff_argv[] = {
5870                                 "git", "diff", "--no-color", "--patch-with-stat",
5871                                         "--", "/dev/null", newpath, NULL
5872                         };
5874                         if (!prepare_update(stage, no_head_diff_argv, opt_cdup))
5875                                 return status_load_error(view, stage, newpath);
5876                 } else {
5877                         const char *index_show_argv[] = {
5878                                 "git", "diff-index", "--root", "--patch-with-stat",
5879                                         "-C", "-M", "--cached", "HEAD", "--",
5880                                         oldpath, newpath, NULL
5881                         };
5883                         if (!prepare_update(stage, index_show_argv, opt_cdup))
5884                                 return status_load_error(view, stage, newpath);
5885                 }
5887                 if (status)
5888                         info = "Staged changes to %s";
5889                 else
5890                         info = "Staged changes";
5891                 break;
5893         case LINE_STAT_UNSTAGED:
5894         {
5895                 const char *files_show_argv[] = {
5896                         "git", "diff-files", "--root", "--patch-with-stat",
5897                                 "-C", "-M", "--", oldpath, newpath, NULL
5898                 };
5900                 if (!prepare_update(stage, files_show_argv, opt_cdup))
5901                         return status_load_error(view, stage, newpath);
5902                 if (status)
5903                         info = "Unstaged changes to %s";
5904                 else
5905                         info = "Unstaged changes";
5906                 break;
5907         }
5908         case LINE_STAT_UNTRACKED:
5909                 if (!newpath) {
5910                         report("No file to show");
5911                         return REQ_NONE;
5912                 }
5914                 if (!suffixcmp(status->new.name, -1, "/")) {
5915                         report("Cannot display a directory");
5916                         return REQ_NONE;
5917                 }
5919                 if (!prepare_update_file(stage, newpath))
5920                         return status_load_error(view, stage, newpath);
5921                 info = "Untracked file %s";
5922                 break;
5924         case LINE_STAT_HEAD:
5925                 return REQ_NONE;
5927         default:
5928                 die("line type %d not handled in switch", line->type);
5929         }
5931         split = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
5932         open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5933         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5934                 if (status) {
5935                         stage_status = *status;
5936                 } else {
5937                         memset(&stage_status, 0, sizeof(stage_status));
5938                 }
5940                 stage_line_type = line->type;
5941                 stage_chunks = 0;
5942                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5943         }
5945         return REQ_NONE;
5948 static bool
5949 status_exists(struct status *status, enum line_type type)
5951         struct view *view = VIEW(REQ_VIEW_STATUS);
5952         unsigned long lineno;
5954         for (lineno = 0; lineno < view->lines; lineno++) {
5955                 struct line *line = &view->line[lineno];
5956                 struct status *pos = line->data;
5958                 if (line->type != type)
5959                         continue;
5960                 if (!pos && (!status || !status->status) && line[1].data) {
5961                         select_view_line(view, lineno);
5962                         return TRUE;
5963                 }
5964                 if (pos && !strcmp(status->new.name, pos->new.name)) {
5965                         select_view_line(view, lineno);
5966                         return TRUE;
5967                 }
5968         }
5970         return FALSE;
5974 static bool
5975 status_update_prepare(struct io *io, enum line_type type)
5977         const char *staged_argv[] = {
5978                 "git", "update-index", "-z", "--index-info", NULL
5979         };
5980         const char *others_argv[] = {
5981                 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5982         };
5984         switch (type) {
5985         case LINE_STAT_STAGED:
5986                 return io_run(io, IO_WR, opt_cdup, staged_argv);
5988         case LINE_STAT_UNSTAGED:
5989         case LINE_STAT_UNTRACKED:
5990                 return io_run(io, IO_WR, opt_cdup, others_argv);
5992         default:
5993                 die("line type %d not handled in switch", type);
5994                 return FALSE;
5995         }
5998 static bool
5999 status_update_write(struct io *io, struct status *status, enum line_type type)
6001         char buf[SIZEOF_STR];
6002         size_t bufsize = 0;
6004         switch (type) {
6005         case LINE_STAT_STAGED:
6006                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
6007                                         status->old.mode,
6008                                         status->old.rev,
6009                                         status->old.name, 0))
6010                         return FALSE;
6011                 break;
6013         case LINE_STAT_UNSTAGED:
6014         case LINE_STAT_UNTRACKED:
6015                 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
6016                         return FALSE;
6017                 break;
6019         default:
6020                 die("line type %d not handled in switch", type);
6021         }
6023         return io_write(io, buf, bufsize);
6026 static bool
6027 status_update_file(struct status *status, enum line_type type)
6029         struct io io;
6030         bool result;
6032         if (!status_update_prepare(&io, type))
6033                 return FALSE;
6035         result = status_update_write(&io, status, type);
6036         return io_done(&io) && result;
6039 static bool
6040 status_update_files(struct view *view, struct line *line)
6042         char buf[sizeof(view->ref)];
6043         struct io io;
6044         bool result = TRUE;
6045         struct line *pos = view->line + view->lines;
6046         int files = 0;
6047         int file, done;
6048         int cursor_y = -1, cursor_x = -1;
6050         if (!status_update_prepare(&io, line->type))
6051                 return FALSE;
6053         for (pos = line; pos < view->line + view->lines && pos->data; pos++)
6054                 files++;
6056         string_copy(buf, view->ref);
6057         getsyx(cursor_y, cursor_x);
6058         for (file = 0, done = 5; result && file < files; line++, file++) {
6059                 int almost_done = file * 100 / files;
6061                 if (almost_done > done) {
6062                         done = almost_done;
6063                         string_format(view->ref, "updating file %u of %u (%d%% done)",
6064                                       file, files, done);
6065                         update_view_title(view);
6066                         setsyx(cursor_y, cursor_x);
6067                         doupdate();
6068                 }
6069                 result = status_update_write(&io, line->data, line->type);
6070         }
6071         string_copy(view->ref, buf);
6073         return io_done(&io) && result;
6076 static bool
6077 status_update(struct view *view)
6079         struct line *line = &view->line[view->lineno];
6081         assert(view->lines);
6083         if (!line->data) {
6084                 /* This should work even for the "On branch" line. */
6085                 if (line < view->line + view->lines && !line[1].data) {
6086                         report("Nothing to update");
6087                         return FALSE;
6088                 }
6090                 if (!status_update_files(view, line + 1)) {
6091                         report("Failed to update file status");
6092                         return FALSE;
6093                 }
6095         } else if (!status_update_file(line->data, line->type)) {
6096                 report("Failed to update file status");
6097                 return FALSE;
6098         }
6100         return TRUE;
6103 static bool
6104 status_revert(struct status *status, enum line_type type, bool has_none)
6106         if (!status || type != LINE_STAT_UNSTAGED) {
6107                 if (type == LINE_STAT_STAGED) {
6108                         report("Cannot revert changes to staged files");
6109                 } else if (type == LINE_STAT_UNTRACKED) {
6110                         report("Cannot revert changes to untracked files");
6111                 } else if (has_none) {
6112                         report("Nothing to revert");
6113                 } else {
6114                         report("Cannot revert changes to multiple files");
6115                 }
6117         } else if (prompt_yesno("Are you sure you want to revert changes?")) {
6118                 char mode[10] = "100644";
6119                 const char *reset_argv[] = {
6120                         "git", "update-index", "--cacheinfo", mode,
6121                                 status->old.rev, status->old.name, NULL
6122                 };
6123                 const char *checkout_argv[] = {
6124                         "git", "checkout", "--", status->old.name, NULL
6125                 };
6127                 if (status->status == 'U') {
6128                         string_format(mode, "%5o", status->old.mode);
6130                         if (status->old.mode == 0 && status->new.mode == 0) {
6131                                 reset_argv[2] = "--force-remove";
6132                                 reset_argv[3] = status->old.name;
6133                                 reset_argv[4] = NULL;
6134                         }
6136                         if (!io_run_fg(reset_argv, opt_cdup))
6137                                 return FALSE;
6138                         if (status->old.mode == 0 && status->new.mode == 0)
6139                                 return TRUE;
6140                 }
6142                 return io_run_fg(checkout_argv, opt_cdup);
6143         }
6145         return FALSE;
6148 static enum request
6149 status_request(struct view *view, enum request request, struct line *line)
6151         struct status *status = line->data;
6153         switch (request) {
6154         case REQ_STATUS_UPDATE:
6155                 if (!status_update(view))
6156                         return REQ_NONE;
6157                 break;
6159         case REQ_STATUS_REVERT:
6160                 if (!status_revert(status, line->type, status_has_none(view, line)))
6161                         return REQ_NONE;
6162                 break;
6164         case REQ_STATUS_MERGE:
6165                 if (!status || status->status != 'U') {
6166                         report("Merging only possible for files with unmerged status ('U').");
6167                         return REQ_NONE;
6168                 }
6169                 open_mergetool(status->new.name);
6170                 break;
6172         case REQ_EDIT:
6173                 if (!status)
6174                         return request;
6175                 if (status->status == 'D') {
6176                         report("File has been deleted.");
6177                         return REQ_NONE;
6178                 }
6180                 open_editor(status->new.name);
6181                 break;
6183         case REQ_VIEW_BLAME:
6184                 if (status)
6185                         opt_ref[0] = 0;
6186                 return request;
6188         case REQ_ENTER:
6189                 /* After returning the status view has been split to
6190                  * show the stage view. No further reloading is
6191                  * necessary. */
6192                 return status_enter(view, line);
6194         case REQ_REFRESH:
6195                 /* Simply reload the view. */
6196                 break;
6198         default:
6199                 return request;
6200         }
6202         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
6204         return REQ_NONE;
6207 static void
6208 status_select(struct view *view, struct line *line)
6210         struct status *status = line->data;
6211         char file[SIZEOF_STR] = "all files";
6212         const char *text;
6213         const char *key;
6215         if (status && !string_format(file, "'%s'", status->new.name))
6216                 return;
6218         if (!status && line[1].type == LINE_STAT_NONE)
6219                 line++;
6221         switch (line->type) {
6222         case LINE_STAT_STAGED:
6223                 text = "Press %s to unstage %s for commit";
6224                 break;
6226         case LINE_STAT_UNSTAGED:
6227                 text = "Press %s to stage %s for commit";
6228                 break;
6230         case LINE_STAT_UNTRACKED:
6231                 text = "Press %s to stage %s for addition";
6232                 break;
6234         case LINE_STAT_HEAD:
6235         case LINE_STAT_NONE:
6236                 text = "Nothing to update";
6237                 break;
6239         default:
6240                 die("line type %d not handled in switch", line->type);
6241         }
6243         if (status && status->status == 'U') {
6244                 text = "Press %s to resolve conflict in %s";
6245                 key = get_key(KEYMAP_STATUS, REQ_STATUS_MERGE);
6247         } else {
6248                 key = get_key(KEYMAP_STATUS, REQ_STATUS_UPDATE);
6249         }
6251         string_format(view->ref, text, key, file);
6252         if (status)
6253                 string_copy(opt_file, status->new.name);
6256 static bool
6257 status_grep(struct view *view, struct line *line)
6259         struct status *status = line->data;
6261         if (status) {
6262                 const char buf[2] = { status->status, 0 };
6263                 const char *text[] = { status->new.name, buf, NULL };
6265                 return grep_text(view, text);
6266         }
6268         return FALSE;
6271 static struct view_ops status_ops = {
6272         "file",
6273         NULL,
6274         status_open,
6275         NULL,
6276         status_draw,
6277         status_request,
6278         status_grep,
6279         status_select,
6280 };
6283 static bool
6284 stage_diff_write(struct io *io, struct line *line, struct line *end)
6286         while (line < end) {
6287                 if (!io_write(io, line->data, strlen(line->data)) ||
6288                     !io_write(io, "\n", 1))
6289                         return FALSE;
6290                 line++;
6291                 if (line->type == LINE_DIFF_CHUNK ||
6292                     line->type == LINE_DIFF_HEADER)
6293                         break;
6294         }
6296         return TRUE;
6299 static struct line *
6300 stage_diff_find(struct view *view, struct line *line, enum line_type type)
6302         for (; view->line < line; line--)
6303                 if (line->type == type)
6304                         return line;
6306         return NULL;
6309 static bool
6310 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
6312         const char *apply_argv[SIZEOF_ARG] = {
6313                 "git", "apply", "--whitespace=nowarn", NULL
6314         };
6315         struct line *diff_hdr;
6316         struct io io;
6317         int argc = 3;
6319         diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
6320         if (!diff_hdr)
6321                 return FALSE;
6323         if (!revert)
6324                 apply_argv[argc++] = "--cached";
6325         if (revert || stage_line_type == LINE_STAT_STAGED)
6326                 apply_argv[argc++] = "-R";
6327         apply_argv[argc++] = "-";
6328         apply_argv[argc++] = NULL;
6329         if (!io_run(&io, IO_WR, opt_cdup, apply_argv))
6330                 return FALSE;
6332         if (!stage_diff_write(&io, diff_hdr, chunk) ||
6333             !stage_diff_write(&io, chunk, view->line + view->lines))
6334                 chunk = NULL;
6336         io_done(&io);
6337         io_run_bg(update_index_argv);
6339         return chunk ? TRUE : FALSE;
6342 static bool
6343 stage_update(struct view *view, struct line *line)
6345         struct line *chunk = NULL;
6347         if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
6348                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6350         if (chunk) {
6351                 if (!stage_apply_chunk(view, chunk, FALSE)) {
6352                         report("Failed to apply chunk");
6353                         return FALSE;
6354                 }
6356         } else if (!stage_status.status) {
6357                 view = VIEW(REQ_VIEW_STATUS);
6359                 for (line = view->line; line < view->line + view->lines; line++)
6360                         if (line->type == stage_line_type)
6361                                 break;
6363                 if (!status_update_files(view, line + 1)) {
6364                         report("Failed to update files");
6365                         return FALSE;
6366                 }
6368         } else if (!status_update_file(&stage_status, stage_line_type)) {
6369                 report("Failed to update file");
6370                 return FALSE;
6371         }
6373         return TRUE;
6376 static bool
6377 stage_revert(struct view *view, struct line *line)
6379         struct line *chunk = NULL;
6381         if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
6382                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6384         if (chunk) {
6385                 if (!prompt_yesno("Are you sure you want to revert changes?"))
6386                         return FALSE;
6388                 if (!stage_apply_chunk(view, chunk, TRUE)) {
6389                         report("Failed to revert chunk");
6390                         return FALSE;
6391                 }
6392                 return TRUE;
6394         } else {
6395                 return status_revert(stage_status.status ? &stage_status : NULL,
6396                                      stage_line_type, FALSE);
6397         }
6401 static void
6402 stage_next(struct view *view, struct line *line)
6404         int i;
6406         if (!stage_chunks) {
6407                 for (line = view->line; line < view->line + view->lines; line++) {
6408                         if (line->type != LINE_DIFF_CHUNK)
6409                                 continue;
6411                         if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
6412                                 report("Allocation failure");
6413                                 return;
6414                         }
6416                         stage_chunk[stage_chunks++] = line - view->line;
6417                 }
6418         }
6420         for (i = 0; i < stage_chunks; i++) {
6421                 if (stage_chunk[i] > view->lineno) {
6422                         do_scroll_view(view, stage_chunk[i] - view->lineno);
6423                         report("Chunk %d of %d", i + 1, stage_chunks);
6424                         return;
6425                 }
6426         }
6428         report("No next chunk found");
6431 static enum request
6432 stage_request(struct view *view, enum request request, struct line *line)
6434         switch (request) {
6435         case REQ_STATUS_UPDATE:
6436                 if (!stage_update(view, line))
6437                         return REQ_NONE;
6438                 break;
6440         case REQ_STATUS_REVERT:
6441                 if (!stage_revert(view, line))
6442                         return REQ_NONE;
6443                 break;
6445         case REQ_STAGE_NEXT:
6446                 if (stage_line_type == LINE_STAT_UNTRACKED) {
6447                         report("File is untracked; press %s to add",
6448                                get_key(KEYMAP_STAGE, REQ_STATUS_UPDATE));
6449                         return REQ_NONE;
6450                 }
6451                 stage_next(view, line);
6452                 return REQ_NONE;
6454         case REQ_EDIT:
6455                 if (!stage_status.new.name[0])
6456                         return request;
6457                 if (stage_status.status == 'D') {
6458                         report("File has been deleted.");
6459                         return REQ_NONE;
6460                 }
6462                 open_editor(stage_status.new.name);
6463                 break;
6465         case REQ_REFRESH:
6466                 /* Reload everything ... */
6467                 break;
6469         case REQ_VIEW_BLAME:
6470                 if (stage_status.new.name[0]) {
6471                         string_copy(opt_file, stage_status.new.name);
6472                         opt_ref[0] = 0;
6473                 }
6474                 return request;
6476         case REQ_ENTER:
6477                 return pager_request(view, request, line);
6479         default:
6480                 return request;
6481         }
6483         VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
6484         open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
6486         /* Check whether the staged entry still exists, and close the
6487          * stage view if it doesn't. */
6488         if (!status_exists(&stage_status, stage_line_type)) {
6489                 status_restore(VIEW(REQ_VIEW_STATUS));
6490                 return REQ_VIEW_CLOSE;
6491         }
6493         if (stage_line_type == LINE_STAT_UNTRACKED) {
6494                 if (!suffixcmp(stage_status.new.name, -1, "/")) {
6495                         report("Cannot display a directory");
6496                         return REQ_NONE;
6497                 }
6499                 if (!prepare_update_file(view, stage_status.new.name)) {
6500                         report("Failed to open file: %s", strerror(errno));
6501                         return REQ_NONE;
6502                 }
6503         }
6504         open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
6506         return REQ_NONE;
6509 static struct view_ops stage_ops = {
6510         "line",
6511         NULL,
6512         NULL,
6513         pager_read,
6514         pager_draw,
6515         stage_request,
6516         pager_grep,
6517         pager_select,
6518 };
6521 /*
6522  * Revision graph
6523  */
6525 struct commit {
6526         char id[SIZEOF_REV];            /* SHA1 ID. */
6527         char title[128];                /* First line of the commit message. */
6528         const char *author;             /* Author of the commit. */
6529         struct time time;               /* Date from the author ident. */
6530         struct ref_list *refs;          /* Repository references. */
6531         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
6532         size_t graph_size;              /* The width of the graph array. */
6533         bool has_parents;               /* Rewritten --parents seen. */
6534 };
6536 /* Size of rev graph with no  "padding" columns */
6537 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6539 struct rev_graph {
6540         struct rev_graph *prev, *next, *parents;
6541         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
6542         size_t size;
6543         struct commit *commit;
6544         size_t pos;
6545         unsigned int boundary:1;
6546 };
6548 /* Parents of the commit being visualized. */
6549 static struct rev_graph graph_parents[4];
6551 /* The current stack of revisions on the graph. */
6552 static struct rev_graph graph_stacks[4] = {
6553         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
6554         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
6555         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
6556         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
6557 };
6559 static inline bool
6560 graph_parent_is_merge(struct rev_graph *graph)
6562         return graph->parents->size > 1;
6565 static inline void
6566 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
6568         struct commit *commit = graph->commit;
6570         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
6571                 commit->graph[commit->graph_size++] = symbol;
6574 static void
6575 clear_rev_graph(struct rev_graph *graph)
6577         graph->boundary = 0;
6578         graph->size = graph->pos = 0;
6579         graph->commit = NULL;
6580         memset(graph->parents, 0, sizeof(*graph->parents));
6583 static void
6584 done_rev_graph(struct rev_graph *graph)
6586         if (graph_parent_is_merge(graph) &&
6587             graph->pos < graph->size - 1 &&
6588             graph->next->size == graph->size + graph->parents->size - 1) {
6589                 size_t i = graph->pos + graph->parents->size - 1;
6591                 graph->commit->graph_size = i * 2;
6592                 while (i < graph->next->size - 1) {
6593                         append_to_rev_graph(graph, ' ');
6594                         append_to_rev_graph(graph, '\\');
6595                         i++;
6596                 }
6597         }
6599         clear_rev_graph(graph);
6602 static void
6603 push_rev_graph(struct rev_graph *graph, const char *parent)
6605         int i;
6607         /* "Collapse" duplicate parents lines.
6608          *
6609          * FIXME: This needs to also update update the drawn graph but
6610          * for now it just serves as a method for pruning graph lines. */
6611         for (i = 0; i < graph->size; i++)
6612                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
6613                         return;
6615         if (graph->size < SIZEOF_REVITEMS) {
6616                 string_copy_rev(graph->rev[graph->size++], parent);
6617         }
6620 static chtype
6621 get_rev_graph_symbol(struct rev_graph *graph)
6623         chtype symbol;
6625         if (graph->boundary)
6626                 symbol = REVGRAPH_BOUND;
6627         else if (graph->parents->size == 0)
6628                 symbol = REVGRAPH_INIT;
6629         else if (graph_parent_is_merge(graph))
6630                 symbol = REVGRAPH_MERGE;
6631         else if (graph->pos >= graph->size)
6632                 symbol = REVGRAPH_BRANCH;
6633         else
6634                 symbol = REVGRAPH_COMMIT;
6636         return symbol;
6639 static void
6640 draw_rev_graph(struct rev_graph *graph)
6642         struct rev_filler {
6643                 chtype separator, line;
6644         };
6645         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
6646         static struct rev_filler fillers[] = {
6647                 { ' ',  '|' },
6648                 { '`',  '.' },
6649                 { '\'', ' ' },
6650                 { '/',  ' ' },
6651         };
6652         chtype symbol = get_rev_graph_symbol(graph);
6653         struct rev_filler *filler;
6654         size_t i;
6656         fillers[DEFAULT].line = opt_line_graphics ? ACS_VLINE : '|';
6657         filler = &fillers[DEFAULT];
6659         for (i = 0; i < graph->pos; i++) {
6660                 append_to_rev_graph(graph, filler->line);
6661                 if (graph_parent_is_merge(graph->prev) &&
6662                     graph->prev->pos == i)
6663                         filler = &fillers[RSHARP];
6665                 append_to_rev_graph(graph, filler->separator);
6666         }
6668         /* Place the symbol for this revision. */
6669         append_to_rev_graph(graph, symbol);
6671         if (graph->prev->size > graph->size)
6672                 filler = &fillers[RDIAG];
6673         else
6674                 filler = &fillers[DEFAULT];
6676         i++;
6678         for (; i < graph->size; i++) {
6679                 append_to_rev_graph(graph, filler->separator);
6680                 append_to_rev_graph(graph, filler->line);
6681                 if (graph_parent_is_merge(graph->prev) &&
6682                     i < graph->prev->pos + graph->parents->size)
6683                         filler = &fillers[RSHARP];
6684                 if (graph->prev->size > graph->size)
6685                         filler = &fillers[LDIAG];
6686         }
6688         if (graph->prev->size > graph->size) {
6689                 append_to_rev_graph(graph, filler->separator);
6690                 if (filler->line != ' ')
6691                         append_to_rev_graph(graph, filler->line);
6692         }
6695 /* Prepare the next rev graph */
6696 static void
6697 prepare_rev_graph(struct rev_graph *graph)
6699         size_t i;
6701         /* First, traverse all lines of revisions up to the active one. */
6702         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6703                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6704                         break;
6706                 push_rev_graph(graph->next, graph->rev[graph->pos]);
6707         }
6709         /* Interleave the new revision parent(s). */
6710         for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6711                 push_rev_graph(graph->next, graph->parents->rev[i]);
6713         /* Lastly, put any remaining revisions. */
6714         for (i = graph->pos + 1; i < graph->size; i++)
6715                 push_rev_graph(graph->next, graph->rev[i]);
6718 static void
6719 update_rev_graph(struct view *view, struct rev_graph *graph)
6721         /* If this is the finalizing update ... */
6722         if (graph->commit)
6723                 prepare_rev_graph(graph);
6725         /* Graph visualization needs a one rev look-ahead,
6726          * so the first update doesn't visualize anything. */
6727         if (!graph->prev->commit)
6728                 return;
6730         if (view->lines > 2)
6731                 view->line[view->lines - 3].dirty = 1;
6732         if (view->lines > 1)
6733                 view->line[view->lines - 2].dirty = 1;
6734         draw_rev_graph(graph->prev);
6735         done_rev_graph(graph->prev->prev);
6739 /*
6740  * Main view backend
6741  */
6743 static const char *main_argv[SIZEOF_ARG] = {
6744         "git", "log", "--no-color", "--pretty=raw", "--parents",
6745                 "--topo-order", "%(diffargs)", "%(revargs)",
6746                 "--", "%(fileargs)", NULL
6747 };
6749 static bool
6750 main_draw(struct view *view, struct line *line, unsigned int lineno)
6752         struct commit *commit = line->data;
6754         if (!commit->author)
6755                 return FALSE;
6757         if (opt_date && draw_date(view, &commit->time))
6758                 return TRUE;
6760         if (opt_author && draw_author(view, commit->author))
6761                 return TRUE;
6763         if (opt_rev_graph && commit->graph_size &&
6764             draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6765                 return TRUE;
6767         if (opt_show_refs && commit->refs) {
6768                 size_t i;
6770                 for (i = 0; i < commit->refs->size; i++) {
6771                         struct ref *ref = commit->refs->refs[i];
6772                         enum line_type type;
6774                         if (ref->head)
6775                                 type = LINE_MAIN_HEAD;
6776                         else if (ref->ltag)
6777                                 type = LINE_MAIN_LOCAL_TAG;
6778                         else if (ref->tag)
6779                                 type = LINE_MAIN_TAG;
6780                         else if (ref->tracked)
6781                                 type = LINE_MAIN_TRACKED;
6782                         else if (ref->remote)
6783                                 type = LINE_MAIN_REMOTE;
6784                         else
6785                                 type = LINE_MAIN_REF;
6787                         if (draw_text(view, type, "[", TRUE) ||
6788                             draw_text(view, type, ref->name, TRUE) ||
6789                             draw_text(view, type, "]", TRUE))
6790                                 return TRUE;
6792                         if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6793                                 return TRUE;
6794                 }
6795         }
6797         draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6798         return TRUE;
6801 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6802 static bool
6803 main_read(struct view *view, char *line)
6805         static struct rev_graph *graph = graph_stacks;
6806         enum line_type type;
6807         struct commit *commit;
6809         if (!line) {
6810                 int i;
6812                 if (!view->lines && !view->prev)
6813                         die("No revisions match the given arguments.");
6814                 if (view->lines > 0) {
6815                         commit = view->line[view->lines - 1].data;
6816                         view->line[view->lines - 1].dirty = 1;
6817                         if (!commit->author) {
6818                                 view->lines--;
6819                                 free(commit);
6820                                 graph->commit = NULL;
6821                         }
6822                 }
6823                 update_rev_graph(view, graph);
6825                 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6826                         clear_rev_graph(&graph_stacks[i]);
6827                 return TRUE;
6828         }
6830         type = get_line_type(line);
6831         if (type == LINE_COMMIT) {
6832                 commit = calloc(1, sizeof(struct commit));
6833                 if (!commit)
6834                         return FALSE;
6836                 line += STRING_SIZE("commit ");
6837                 if (*line == '-') {
6838                         graph->boundary = 1;
6839                         line++;
6840                 }
6842                 string_copy_rev(commit->id, line);
6843                 commit->refs = get_ref_list(commit->id);
6844                 graph->commit = commit;
6845                 add_line_data(view, commit, LINE_MAIN_COMMIT);
6847                 while ((line = strchr(line, ' '))) {
6848                         line++;
6849                         push_rev_graph(graph->parents, line);
6850                         commit->has_parents = TRUE;
6851                 }
6852                 return TRUE;
6853         }
6855         if (!view->lines)
6856                 return TRUE;
6857         commit = view->line[view->lines - 1].data;
6859         switch (type) {
6860         case LINE_PARENT:
6861                 if (commit->has_parents)
6862                         break;
6863                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6864                 break;
6866         case LINE_AUTHOR:
6867                 parse_author_line(line + STRING_SIZE("author "),
6868                                   &commit->author, &commit->time);
6869                 update_rev_graph(view, graph);
6870                 graph = graph->next;
6871                 break;
6873         default:
6874                 /* Fill in the commit title if it has not already been set. */
6875                 if (commit->title[0])
6876                         break;
6878                 /* Require titles to start with a non-space character at the
6879                  * offset used by git log. */
6880                 if (strncmp(line, "    ", 4))
6881                         break;
6882                 line += 4;
6883                 /* Well, if the title starts with a whitespace character,
6884                  * try to be forgiving.  Otherwise we end up with no title. */
6885                 while (isspace(*line))
6886                         line++;
6887                 if (*line == '\0')
6888                         break;
6889                 /* FIXME: More graceful handling of titles; append "..." to
6890                  * shortened titles, etc. */
6892                 string_expand(commit->title, sizeof(commit->title), line, 1);
6893                 view->line[view->lines - 1].dirty = 1;
6894         }
6896         return TRUE;
6899 static enum request
6900 main_request(struct view *view, enum request request, struct line *line)
6902         enum open_flags flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
6904         switch (request) {
6905         case REQ_ENTER:
6906                 open_view(view, REQ_VIEW_DIFF, flags);
6907                 break;
6908         case REQ_REFRESH:
6909                 load_refs();
6910                 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6911                 break;
6912         default:
6913                 return request;
6914         }
6916         return REQ_NONE;
6919 static bool
6920 grep_refs(struct ref_list *list, regex_t *regex)
6922         regmatch_t pmatch;
6923         size_t i;
6925         if (!opt_show_refs || !list)
6926                 return FALSE;
6928         for (i = 0; i < list->size; i++) {
6929                 if (regexec(regex, list->refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6930                         return TRUE;
6931         }
6933         return FALSE;
6936 static bool
6937 main_grep(struct view *view, struct line *line)
6939         struct commit *commit = line->data;
6940         const char *text[] = {
6941                 commit->title,
6942                 opt_author ? commit->author : "",
6943                 mkdate(&commit->time, opt_date),
6944                 NULL
6945         };
6947         return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6950 static void
6951 main_select(struct view *view, struct line *line)
6953         struct commit *commit = line->data;
6955         string_copy_rev(view->ref, commit->id);
6956         string_copy_rev(ref_commit, view->ref);
6959 static struct view_ops main_ops = {
6960         "commit",
6961         main_argv,
6962         NULL,
6963         main_read,
6964         main_draw,
6965         main_request,
6966         main_grep,
6967         main_select,
6968 };
6971 /*
6972  * Status management
6973  */
6975 /* Whether or not the curses interface has been initialized. */
6976 static bool cursed = FALSE;
6978 /* Terminal hacks and workarounds. */
6979 static bool use_scroll_redrawwin;
6980 static bool use_scroll_status_wclear;
6982 /* The status window is used for polling keystrokes. */
6983 static WINDOW *status_win;
6985 /* Reading from the prompt? */
6986 static bool input_mode = FALSE;
6988 static bool status_empty = FALSE;
6990 /* Update status and title window. */
6991 static void
6992 report(const char *msg, ...)
6994         struct view *view = display[current_view];
6996         if (input_mode)
6997                 return;
6999         if (!view) {
7000                 char buf[SIZEOF_STR];
7001                 va_list args;
7003                 va_start(args, msg);
7004                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
7005                         buf[sizeof(buf) - 1] = 0;
7006                         buf[sizeof(buf) - 2] = '.';
7007                         buf[sizeof(buf) - 3] = '.';
7008                         buf[sizeof(buf) - 4] = '.';
7009                 }
7010                 va_end(args);
7011                 die("%s", buf);
7012         }
7014         if (!status_empty || *msg) {
7015                 va_list args;
7017                 va_start(args, msg);
7019                 wmove(status_win, 0, 0);
7020                 if (view->has_scrolled && use_scroll_status_wclear)
7021                         wclear(status_win);
7022                 if (*msg) {
7023                         vwprintw(status_win, msg, args);
7024                         status_empty = FALSE;
7025                 } else {
7026                         status_empty = TRUE;
7027                 }
7028                 wclrtoeol(status_win);
7029                 wnoutrefresh(status_win);
7031                 va_end(args);
7032         }
7034         update_view_title(view);
7037 static void
7038 init_display(void)
7040         const char *term;
7041         int x, y;
7043         /* Initialize the curses library */
7044         if (isatty(STDIN_FILENO)) {
7045                 cursed = !!initscr();
7046                 opt_tty = stdin;
7047         } else {
7048                 /* Leave stdin and stdout alone when acting as a pager. */
7049                 opt_tty = fopen("/dev/tty", "r+");
7050                 if (!opt_tty)
7051                         die("Failed to open /dev/tty");
7052                 cursed = !!newterm(NULL, opt_tty, opt_tty);
7053         }
7055         if (!cursed)
7056                 die("Failed to initialize curses");
7058         nonl();         /* Disable conversion and detect newlines from input. */
7059         cbreak();       /* Take input chars one at a time, no wait for \n */
7060         noecho();       /* Don't echo input */
7061         leaveok(stdscr, FALSE);
7063         if (has_colors())
7064                 init_colors();
7066         getmaxyx(stdscr, y, x);
7067         status_win = newwin(1, 0, y - 1, 0);
7068         if (!status_win)
7069                 die("Failed to create status window");
7071         /* Enable keyboard mapping */
7072         keypad(status_win, TRUE);
7073         wbkgdset(status_win, get_line_attr(LINE_STATUS));
7075         TABSIZE = opt_tab_size;
7077         term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
7078         if (term && !strcmp(term, "gnome-terminal")) {
7079                 /* In the gnome-terminal-emulator, the message from
7080                  * scrolling up one line when impossible followed by
7081                  * scrolling down one line causes corruption of the
7082                  * status line. This is fixed by calling wclear. */
7083                 use_scroll_status_wclear = TRUE;
7084                 use_scroll_redrawwin = FALSE;
7086         } else if (term && !strcmp(term, "xrvt-xpm")) {
7087                 /* No problems with full optimizations in xrvt-(unicode)
7088                  * and aterm. */
7089                 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
7091         } else {
7092                 /* When scrolling in (u)xterm the last line in the
7093                  * scrolling direction will update slowly. */
7094                 use_scroll_redrawwin = TRUE;
7095                 use_scroll_status_wclear = FALSE;
7096         }
7099 static int
7100 get_input(int prompt_position)
7102         struct view *view;
7103         int i, key, cursor_y, cursor_x;
7105         if (prompt_position)
7106                 input_mode = TRUE;
7108         while (TRUE) {
7109                 bool loading = FALSE;
7111                 foreach_view (view, i) {
7112                         update_view(view);
7113                         if (view_is_displayed(view) && view->has_scrolled &&
7114                             use_scroll_redrawwin)
7115                                 redrawwin(view->win);
7116                         view->has_scrolled = FALSE;
7117                         if (view->pipe)
7118                                 loading = TRUE;
7119                 }
7121                 /* Update the cursor position. */
7122                 if (prompt_position) {
7123                         getbegyx(status_win, cursor_y, cursor_x);
7124                         cursor_x = prompt_position;
7125                 } else {
7126                         view = display[current_view];
7127                         getbegyx(view->win, cursor_y, cursor_x);
7128                         cursor_x = view->width - 1;
7129                         cursor_y += view->lineno - view->offset;
7130                 }
7131                 setsyx(cursor_y, cursor_x);
7133                 /* Refresh, accept single keystroke of input */
7134                 doupdate();
7135                 nodelay(status_win, loading);
7136                 key = wgetch(status_win);
7138                 /* wgetch() with nodelay() enabled returns ERR when
7139                  * there's no input. */
7140                 if (key == ERR) {
7142                 } else if (key == KEY_RESIZE) {
7143                         int height, width;
7145                         getmaxyx(stdscr, height, width);
7147                         wresize(status_win, 1, width);
7148                         mvwin(status_win, height - 1, 0);
7149                         wnoutrefresh(status_win);
7150                         resize_display();
7151                         redraw_display(TRUE);
7153                 } else {
7154                         input_mode = FALSE;
7155                         return key;
7156                 }
7157         }
7160 static char *
7161 prompt_input(const char *prompt, input_handler handler, void *data)
7163         enum input_status status = INPUT_OK;
7164         static char buf[SIZEOF_STR];
7165         size_t pos = 0;
7167         buf[pos] = 0;
7169         while (status == INPUT_OK || status == INPUT_SKIP) {
7170                 int key;
7172                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
7173                 wclrtoeol(status_win);
7175                 key = get_input(pos + 1);
7176                 switch (key) {
7177                 case KEY_RETURN:
7178                 case KEY_ENTER:
7179                 case '\n':
7180                         status = pos ? INPUT_STOP : INPUT_CANCEL;
7181                         break;
7183                 case KEY_BACKSPACE:
7184                         if (pos > 0)
7185                                 buf[--pos] = 0;
7186                         else
7187                                 status = INPUT_CANCEL;
7188                         break;
7190                 case KEY_ESC:
7191                         status = INPUT_CANCEL;
7192                         break;
7194                 default:
7195                         if (pos >= sizeof(buf)) {
7196                                 report("Input string too long");
7197                                 return NULL;
7198                         }
7200                         status = handler(data, buf, key);
7201                         if (status == INPUT_OK)
7202                                 buf[pos++] = (char) key;
7203                 }
7204         }
7206         /* Clear the status window */
7207         status_empty = FALSE;
7208         report("");
7210         if (status == INPUT_CANCEL)
7211                 return NULL;
7213         buf[pos++] = 0;
7215         return buf;
7218 static enum input_status
7219 prompt_yesno_handler(void *data, char *buf, int c)
7221         if (c == 'y' || c == 'Y')
7222                 return INPUT_STOP;
7223         if (c == 'n' || c == 'N')
7224                 return INPUT_CANCEL;
7225         return INPUT_SKIP;
7228 static bool
7229 prompt_yesno(const char *prompt)
7231         char prompt2[SIZEOF_STR];
7233         if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
7234                 return FALSE;
7236         return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
7239 static enum input_status
7240 read_prompt_handler(void *data, char *buf, int c)
7242         return isprint(c) ? INPUT_OK : INPUT_SKIP;
7245 static char *
7246 read_prompt(const char *prompt)
7248         return prompt_input(prompt, read_prompt_handler, NULL);
7251 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected)
7253         enum input_status status = INPUT_OK;
7254         int size = 0;
7256         while (items[size].text)
7257                 size++;
7259         while (status == INPUT_OK) {
7260                 const struct menu_item *item = &items[*selected];
7261                 int key;
7262                 int i;
7264                 mvwprintw(status_win, 0, 0, "%s (%d of %d) ",
7265                           prompt, *selected + 1, size);
7266                 if (item->hotkey)
7267                         wprintw(status_win, "[%c] ", (char) item->hotkey);
7268                 wprintw(status_win, "%s", item->text);
7269                 wclrtoeol(status_win);
7271                 key = get_input(COLS - 1);
7272                 switch (key) {
7273                 case KEY_RETURN:
7274                 case KEY_ENTER:
7275                 case '\n':
7276                         status = INPUT_STOP;
7277                         break;
7279                 case KEY_LEFT:
7280                 case KEY_UP:
7281                         *selected = *selected - 1;
7282                         if (*selected < 0)
7283                                 *selected = size - 1;
7284                         break;
7286                 case KEY_RIGHT:
7287                 case KEY_DOWN:
7288                         *selected = (*selected + 1) % size;
7289                         break;
7291                 case KEY_ESC:
7292                         status = INPUT_CANCEL;
7293                         break;
7295                 default:
7296                         for (i = 0; items[i].text; i++)
7297                                 if (items[i].hotkey == key) {
7298                                         *selected = i;
7299                                         status = INPUT_STOP;
7300                                         break;
7301                                 }
7302                 }
7303         }
7305         /* Clear the status window */
7306         status_empty = FALSE;
7307         report("");
7309         return status != INPUT_CANCEL;
7312 /*
7313  * Repository properties
7314  */
7316 static struct ref **refs = NULL;
7317 static size_t refs_size = 0;
7318 static struct ref *refs_head = NULL;
7320 static struct ref_list **ref_lists = NULL;
7321 static size_t ref_lists_size = 0;
7323 DEFINE_ALLOCATOR(realloc_refs, struct ref *, 256)
7324 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
7325 DEFINE_ALLOCATOR(realloc_ref_lists, struct ref_list *, 8)
7327 static int
7328 compare_refs(const void *ref1_, const void *ref2_)
7330         const struct ref *ref1 = *(const struct ref **)ref1_;
7331         const struct ref *ref2 = *(const struct ref **)ref2_;
7333         if (ref1->tag != ref2->tag)
7334                 return ref2->tag - ref1->tag;
7335         if (ref1->ltag != ref2->ltag)
7336                 return ref2->ltag - ref2->ltag;
7337         if (ref1->head != ref2->head)
7338                 return ref2->head - ref1->head;
7339         if (ref1->tracked != ref2->tracked)
7340                 return ref2->tracked - ref1->tracked;
7341         if (ref1->remote != ref2->remote)
7342                 return ref2->remote - ref1->remote;
7343         return strcmp(ref1->name, ref2->name);
7346 static void
7347 foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data)
7349         size_t i;
7351         for (i = 0; i < refs_size; i++)
7352                 if (!visitor(data, refs[i]))
7353                         break;
7356 static struct ref *
7357 get_ref_head()
7359         return refs_head;
7362 static struct ref_list *
7363 get_ref_list(const char *id)
7365         struct ref_list *list;
7366         size_t i;
7368         for (i = 0; i < ref_lists_size; i++)
7369                 if (!strcmp(id, ref_lists[i]->id))
7370                         return ref_lists[i];
7372         if (!realloc_ref_lists(&ref_lists, ref_lists_size, 1))
7373                 return NULL;
7374         list = calloc(1, sizeof(*list));
7375         if (!list)
7376                 return NULL;
7378         for (i = 0; i < refs_size; i++) {
7379                 if (!strcmp(id, refs[i]->id) &&
7380                     realloc_refs_list(&list->refs, list->size, 1))
7381                         list->refs[list->size++] = refs[i];
7382         }
7384         if (!list->refs) {
7385                 free(list);
7386                 return NULL;
7387         }
7389         qsort(list->refs, list->size, sizeof(*list->refs), compare_refs);
7390         ref_lists[ref_lists_size++] = list;
7391         return list;
7394 static int
7395 read_ref(char *id, size_t idlen, char *name, size_t namelen)
7397         struct ref *ref = NULL;
7398         bool tag = FALSE;
7399         bool ltag = FALSE;
7400         bool remote = FALSE;
7401         bool tracked = FALSE;
7402         bool head = FALSE;
7403         int from = 0, to = refs_size - 1;
7405         if (!prefixcmp(name, "refs/tags/")) {
7406                 if (!suffixcmp(name, namelen, "^{}")) {
7407                         namelen -= 3;
7408                         name[namelen] = 0;
7409                 } else {
7410                         ltag = TRUE;
7411                 }
7413                 tag = TRUE;
7414                 namelen -= STRING_SIZE("refs/tags/");
7415                 name    += STRING_SIZE("refs/tags/");
7417         } else if (!prefixcmp(name, "refs/remotes/")) {
7418                 remote = TRUE;
7419                 namelen -= STRING_SIZE("refs/remotes/");
7420                 name    += STRING_SIZE("refs/remotes/");
7421                 tracked  = !strcmp(opt_remote, name);
7423         } else if (!prefixcmp(name, "refs/heads/")) {
7424                 namelen -= STRING_SIZE("refs/heads/");
7425                 name    += STRING_SIZE("refs/heads/");
7426                 if (!strncmp(opt_head, name, namelen))
7427                         return OK;
7429         } else if (!strcmp(name, "HEAD")) {
7430                 head     = TRUE;
7431                 if (*opt_head) {
7432                         namelen  = strlen(opt_head);
7433                         name     = opt_head;
7434                 }
7435         }
7437         /* If we are reloading or it's an annotated tag, replace the
7438          * previous SHA1 with the resolved commit id; relies on the fact
7439          * git-ls-remote lists the commit id of an annotated tag right
7440          * before the commit id it points to. */
7441         while (from <= to) {
7442                 size_t pos = (to + from) / 2;
7443                 int cmp = strcmp(name, refs[pos]->name);
7445                 if (!cmp) {
7446                         ref = refs[pos];
7447                         break;
7448                 }
7450                 if (cmp < 0)
7451                         to = pos - 1;
7452                 else
7453                         from = pos + 1;
7454         }
7456         if (!ref) {
7457                 if (!realloc_refs(&refs, refs_size, 1))
7458                         return ERR;
7459                 ref = calloc(1, sizeof(*ref) + namelen);
7460                 if (!ref)
7461                         return ERR;
7462                 memmove(refs + from + 1, refs + from,
7463                         (refs_size - from) * sizeof(*refs));
7464                 refs[from] = ref;
7465                 strncpy(ref->name, name, namelen);
7466                 refs_size++;
7467         }
7469         ref->head = head;
7470         ref->tag = tag;
7471         ref->ltag = ltag;
7472         ref->remote = remote;
7473         ref->tracked = tracked;
7474         string_copy_rev(ref->id, id);
7476         if (head)
7477                 refs_head = ref;
7478         return OK;
7481 static int
7482 load_refs(void)
7484         const char *head_argv[] = {
7485                 "git", "symbolic-ref", "HEAD", NULL
7486         };
7487         static const char *ls_remote_argv[SIZEOF_ARG] = {
7488                 "git", "ls-remote", opt_git_dir, NULL
7489         };
7490         static bool init = FALSE;
7491         size_t i;
7493         if (!init) {
7494                 if (!argv_from_env(ls_remote_argv, "TIG_LS_REMOTE"))
7495                         die("TIG_LS_REMOTE contains too many arguments");
7496                 init = TRUE;
7497         }
7499         if (!*opt_git_dir)
7500                 return OK;
7502         if (io_run_buf(head_argv, opt_head, sizeof(opt_head)) &&
7503             !prefixcmp(opt_head, "refs/heads/")) {
7504                 char *offset = opt_head + STRING_SIZE("refs/heads/");
7506                 memmove(opt_head, offset, strlen(offset) + 1);
7507         }
7509         refs_head = NULL;
7510         for (i = 0; i < refs_size; i++)
7511                 refs[i]->id[0] = 0;
7513         if (io_run_load(ls_remote_argv, "\t", read_ref) == ERR)
7514                 return ERR;
7516         /* Update the ref lists to reflect changes. */
7517         for (i = 0; i < ref_lists_size; i++) {
7518                 struct ref_list *list = ref_lists[i];
7519                 size_t old, new;
7521                 for (old = new = 0; old < list->size; old++)
7522                         if (!strcmp(list->id, list->refs[old]->id))
7523                                 list->refs[new++] = list->refs[old];
7524                 list->size = new;
7525         }
7527         return OK;
7530 static void
7531 set_remote_branch(const char *name, const char *value, size_t valuelen)
7533         if (!strcmp(name, ".remote")) {
7534                 string_ncopy(opt_remote, value, valuelen);
7536         } else if (*opt_remote && !strcmp(name, ".merge")) {
7537                 size_t from = strlen(opt_remote);
7539                 if (!prefixcmp(value, "refs/heads/"))
7540                         value += STRING_SIZE("refs/heads/");
7542                 if (!string_format_from(opt_remote, &from, "/%s", value))
7543                         opt_remote[0] = 0;
7544         }
7547 static void
7548 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
7550         const char *argv[SIZEOF_ARG] = { name, "=" };
7551         int argc = 1 + (cmd == option_set_command);
7552         int error = ERR;
7554         if (!argv_from_string(argv, &argc, value))
7555                 config_msg = "Too many option arguments";
7556         else
7557                 error = cmd(argc, argv);
7559         if (error == ERR)
7560                 warn("Option 'tig.%s': %s", name, config_msg);
7563 static bool
7564 set_environment_variable(const char *name, const char *value)
7566         size_t len = strlen(name) + 1 + strlen(value) + 1;
7567         char *env = malloc(len);
7569         if (env &&
7570             string_nformat(env, len, NULL, "%s=%s", name, value) &&
7571             putenv(env) == 0)
7572                 return TRUE;
7573         free(env);
7574         return FALSE;
7577 static void
7578 set_work_tree(const char *value)
7580         char cwd[SIZEOF_STR];
7582         if (!getcwd(cwd, sizeof(cwd)))
7583                 die("Failed to get cwd path: %s", strerror(errno));
7584         if (chdir(opt_git_dir) < 0)
7585                 die("Failed to chdir(%s): %s", strerror(errno));
7586         if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7587                 die("Failed to get git path: %s", strerror(errno));
7588         if (chdir(cwd) < 0)
7589                 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7590         if (chdir(value) < 0)
7591                 die("Failed to chdir(%s): %s", value, strerror(errno));
7592         if (!getcwd(cwd, sizeof(cwd)))
7593                 die("Failed to get cwd path: %s", strerror(errno));
7594         if (!set_environment_variable("GIT_WORK_TREE", cwd))
7595                 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7596         if (!set_environment_variable("GIT_DIR", opt_git_dir))
7597                 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7598         opt_is_inside_work_tree = TRUE;
7601 static int
7602 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
7604         if (!strcmp(name, "i18n.commitencoding"))
7605                 string_ncopy(opt_encoding, value, valuelen);
7607         else if (!strcmp(name, "core.editor"))
7608                 string_ncopy(opt_editor, value, valuelen);
7610         else if (!strcmp(name, "core.worktree"))
7611                 set_work_tree(value);
7613         else if (!prefixcmp(name, "tig.color."))
7614                 set_repo_config_option(name + 10, value, option_color_command);
7616         else if (!prefixcmp(name, "tig.bind."))
7617                 set_repo_config_option(name + 9, value, option_bind_command);
7619         else if (!prefixcmp(name, "tig."))
7620                 set_repo_config_option(name + 4, value, option_set_command);
7622         else if (*opt_head && !prefixcmp(name, "branch.") &&
7623                  !strncmp(name + 7, opt_head, strlen(opt_head)))
7624                 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7626         return OK;
7629 static int
7630 load_git_config(void)
7632         const char *config_list_argv[] = { "git", "config", "--list", NULL };
7634         return io_run_load(config_list_argv, "=", read_repo_config_option);
7637 static int
7638 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
7640         if (!opt_git_dir[0]) {
7641                 string_ncopy(opt_git_dir, name, namelen);
7643         } else if (opt_is_inside_work_tree == -1) {
7644                 /* This can be 3 different values depending on the
7645                  * version of git being used. If git-rev-parse does not
7646                  * understand --is-inside-work-tree it will simply echo
7647                  * the option else either "true" or "false" is printed.
7648                  * Default to true for the unknown case. */
7649                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7651         } else if (*name == '.') {
7652                 string_ncopy(opt_cdup, name, namelen);
7654         } else {
7655                 string_ncopy(opt_prefix, name, namelen);
7656         }
7658         return OK;
7661 static int
7662 load_repo_info(void)
7664         const char *rev_parse_argv[] = {
7665                 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7666                         "--show-cdup", "--show-prefix", NULL
7667         };
7669         return io_run_load(rev_parse_argv, "=", read_repo_info);
7673 /*
7674  * Main
7675  */
7677 static const char usage[] =
7678 "tig " TIG_VERSION " (" __DATE__ ")\n"
7679 "\n"
7680 "Usage: tig        [options] [revs] [--] [paths]\n"
7681 "   or: tig show   [options] [revs] [--] [paths]\n"
7682 "   or: tig blame  [rev] path\n"
7683 "   or: tig status\n"
7684 "   or: tig <      [git command output]\n"
7685 "\n"
7686 "Options:\n"
7687 "  -v, --version   Show version and exit\n"
7688 "  -h, --help      Show help message and exit";
7690 static void __NORETURN
7691 quit(int sig)
7693         /* XXX: Restore tty modes and let the OS cleanup the rest! */
7694         if (cursed)
7695                 endwin();
7696         exit(0);
7699 static void __NORETURN
7700 die(const char *err, ...)
7702         va_list args;
7704         endwin();
7706         va_start(args, err);
7707         fputs("tig: ", stderr);
7708         vfprintf(stderr, err, args);
7709         fputs("\n", stderr);
7710         va_end(args);
7712         exit(1);
7715 static void
7716 warn(const char *msg, ...)
7718         va_list args;
7720         va_start(args, msg);
7721         fputs("tig warning: ", stderr);
7722         vfprintf(stderr, msg, args);
7723         fputs("\n", stderr);
7724         va_end(args);
7727 static const char ***filter_args;
7729 static int
7730 read_filter_args(char *name, size_t namelen, char *value, size_t valuelen)
7732         return argv_append(filter_args, name) ? OK : ERR;
7735 static void
7736 filter_rev_parse(const char ***args, const char *arg1, const char *arg2, const char *argv[])
7738         const char *rev_parse_argv[SIZEOF_ARG] = { "git", "rev-parse", arg1, arg2 };
7739         const char **all_argv = NULL;
7741         filter_args = args;
7742         if (!argv_append_array(&all_argv, rev_parse_argv) ||
7743             !argv_append_array(&all_argv, argv) ||
7744             !io_run_load(all_argv, "\n", read_filter_args) == ERR)
7745                 die("Failed to split arguments");
7746         argv_free(all_argv);
7747         free(all_argv);
7750 static void
7751 filter_options(const char *argv[])
7753         filter_rev_parse(&opt_file_args, "--no-revs", "--no-flags", argv);
7754         filter_rev_parse(&opt_diff_args, "--no-revs", "--flags", argv);
7755         filter_rev_parse(&opt_rev_args, "--symbolic", "--revs-only", argv);
7758 static enum request
7759 parse_options(int argc, const char *argv[])
7761         enum request request = REQ_VIEW_MAIN;
7762         const char *subcommand;
7763         bool seen_dashdash = FALSE;
7764         const char **filter_argv = NULL;
7765         int i;
7767         if (!isatty(STDIN_FILENO)) {
7768                 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
7769                 return REQ_VIEW_PAGER;
7770         }
7772         if (argc <= 1)
7773                 return REQ_VIEW_MAIN;
7775         subcommand = argv[1];
7776         if (!strcmp(subcommand, "status")) {
7777                 if (argc > 2)
7778                         warn("ignoring arguments after `%s'", subcommand);
7779                 return REQ_VIEW_STATUS;
7781         } else if (!strcmp(subcommand, "blame")) {
7782                 if (argc <= 2 || argc > 4)
7783                         die("invalid number of options to blame\n\n%s", usage);
7785                 i = 2;
7786                 if (argc == 4) {
7787                         string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7788                         i++;
7789                 }
7791                 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7792                 return REQ_VIEW_BLAME;
7794         } else if (!strcmp(subcommand, "show")) {
7795                 request = REQ_VIEW_DIFF;
7797         } else {
7798                 subcommand = NULL;
7799         }
7801         for (i = 1 + !!subcommand; i < argc; i++) {
7802                 const char *opt = argv[i];
7804                 if (seen_dashdash) {
7805                         argv_append(&opt_file_args, opt);
7806                         continue;
7808                 } else if (!strcmp(opt, "--")) {
7809                         seen_dashdash = TRUE;
7810                         continue;
7812                 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7813                         printf("tig version %s\n", TIG_VERSION);
7814                         quit(0);
7816                 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7817                         printf("%s\n", usage);
7818                         quit(0);
7820                 } else if (!strcmp(opt, "--all")) {
7821                         argv_append(&opt_rev_args, opt);
7822                         continue;
7823                 }
7825                 if (!argv_append(&filter_argv, opt))
7826                         die("command too long");
7827         }
7829         if (filter_argv)
7830                 filter_options(filter_argv);
7832         return request;
7835 int
7836 main(int argc, const char *argv[])
7838         const char *codeset = "UTF-8";
7839         enum request request = parse_options(argc, argv);
7840         struct view *view;
7841         size_t i;
7843         signal(SIGINT, quit);
7844         signal(SIGPIPE, SIG_IGN);
7846         if (setlocale(LC_ALL, "")) {
7847                 codeset = nl_langinfo(CODESET);
7848         }
7850         if (load_repo_info() == ERR)
7851                 die("Failed to load repo info.");
7853         if (load_options() == ERR)
7854                 die("Failed to load user config.");
7856         if (load_git_config() == ERR)
7857                 die("Failed to load repo config.");
7859         /* Require a git repository unless when running in pager mode. */
7860         if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7861                 die("Not a git repository");
7863         if (*opt_encoding && strcmp(codeset, "UTF-8")) {
7864                 opt_iconv_in = iconv_open("UTF-8", opt_encoding);
7865                 if (opt_iconv_in == ICONV_NONE)
7866                         die("Failed to initialize character set conversion");
7867         }
7869         if (codeset && strcmp(codeset, "UTF-8")) {
7870                 opt_iconv_out = iconv_open(codeset, "UTF-8");
7871                 if (opt_iconv_out == ICONV_NONE)
7872                         die("Failed to initialize character set conversion");
7873         }
7875         if (load_refs() == ERR)
7876                 die("Failed to load refs.");
7878         foreach_view (view, i) {
7879                 if (getenv(view->cmd_env))
7880                         warn("Use of the %s environment variable is deprecated,"
7881                              " use options or TIG_DIFF_ARGS instead",
7882                              view->cmd_env);
7883                 if (!argv_from_env(view->ops->argv, view->cmd_env))
7884                         die("Too many arguments in the `%s` environment variable",
7885                             view->cmd_env);
7886         }
7888         init_display();
7890         while (view_driver(display[current_view], request)) {
7891                 int key = get_input(0);
7893                 view = display[current_view];
7894                 request = get_keybinding(view->keymap, key);
7896                 /* Some low-level request handling. This keeps access to
7897                  * status_win restricted. */
7898                 switch (request) {
7899                 case REQ_NONE:
7900                         report("Unknown key, press %s for help",
7901                                get_key(view->keymap, REQ_VIEW_HELP));
7902                         break;
7903                 case REQ_PROMPT:
7904                 {
7905                         char *cmd = read_prompt(":");
7907                         if (cmd && isdigit(*cmd)) {
7908                                 int lineno = view->lineno + 1;
7910                                 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7911                                         select_view_line(view, lineno - 1);
7912                                         report("");
7913                                 } else {
7914                                         report("Unable to parse '%s' as a line number", cmd);
7915                                 }
7917                         } else if (cmd) {
7918                                 struct view *next = VIEW(REQ_VIEW_PAGER);
7919                                 const char *argv[SIZEOF_ARG] = { "git" };
7920                                 int argc = 1;
7922                                 /* When running random commands, initially show the
7923                                  * command in the title. However, it maybe later be
7924                                  * overwritten if a commit line is selected. */
7925                                 string_ncopy(next->ref, cmd, strlen(cmd));
7927                                 if (!argv_from_string(argv, &argc, cmd)) {
7928                                         report("Too many arguments");
7929                                 } else if (!prepare_update(next, argv, NULL)) {
7930                                         report("Failed to format command");
7931                                 } else {
7932                                         open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7933                                 }
7934                         }
7936                         request = REQ_NONE;
7937                         break;
7938                 }
7939                 case REQ_SEARCH:
7940                 case REQ_SEARCH_BACK:
7941                 {
7942                         const char *prompt = request == REQ_SEARCH ? "/" : "?";
7943                         char *search = read_prompt(prompt);
7945                         if (search)
7946                                 string_ncopy(opt_search, search, strlen(search));
7947                         else if (*opt_search)
7948                                 request = request == REQ_SEARCH ?
7949                                         REQ_FIND_NEXT :
7950                                         REQ_FIND_PREV;
7951                         else
7952                                 request = REQ_NONE;
7953                         break;
7954                 }
7955                 default:
7956                         break;
7957                 }
7958         }
7960         quit(0);
7962         return 0;