Code

Rename {diff,file,rev}-args to {diff,file,rev}args
[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 DEFINE_ALLOCATOR(argv_realloc, const char *, SIZEOF_ARG)
689 static bool
690 argv_append(const char ***argv, const char *arg)
692         int argc = 0;
694         while (*argv && (*argv)[argc])
695                 argc++;
697         if (!argv_realloc(argv, argc, 2))
698                 return FALSE;
700         (*argv)[argc++] = strdup(arg);
701         (*argv)[argc] = NULL;
702         return TRUE;
705 static bool
706 argv_append_array(const char ***dst_argv, const char *src_argv[])
708         int i;
710         for (i = 0; src_argv && src_argv[i]; i++)
711                 if (!argv_append(dst_argv, src_argv[i]))
712                         return FALSE;
713         return TRUE;
716 static bool
717 argv_copy(const char ***dst, const char *src[])
719         int argc;
721         for (argc = 0; src[argc]; argc++)
722                 if (!argv_append(dst, src[argc]))
723                         return FALSE;
724         return TRUE;
728 /*
729  * Executing external commands.
730  */
732 enum io_type {
733         IO_FD,                  /* File descriptor based IO. */
734         IO_BG,                  /* Execute command in the background. */
735         IO_FG,                  /* Execute command with same std{in,out,err}. */
736         IO_RD,                  /* Read only fork+exec IO. */
737         IO_WR,                  /* Write only fork+exec IO. */
738         IO_AP,                  /* Append fork+exec output to file. */
739 };
741 struct io {
742         int pipe;               /* Pipe end for reading or writing. */
743         pid_t pid;              /* PID of spawned process. */
744         int error;              /* Error status. */
745         char *buf;              /* Read buffer. */
746         size_t bufalloc;        /* Allocated buffer size. */
747         size_t bufsize;         /* Buffer content size. */
748         char *bufpos;           /* Current buffer position. */
749         unsigned int eof:1;     /* Has end of file been reached. */
750 };
752 static void
753 io_init(struct io *io)
755         memset(io, 0, sizeof(*io));
756         io->pipe = -1;
759 static bool
760 io_open(struct io *io, const char *fmt, ...)
762         char name[SIZEOF_STR] = "";
763         bool fits;
764         va_list args;
766         io_init(io);
768         va_start(args, fmt);
769         fits = vsnprintf(name, sizeof(name), fmt, args) < sizeof(name);
770         va_end(args);
772         if (!fits) {
773                 io->error = ENAMETOOLONG;
774                 return FALSE;
775         }
776         io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
777         if (io->pipe == -1)
778                 io->error = errno;
779         return io->pipe != -1;
782 static bool
783 io_kill(struct io *io)
785         return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
788 static bool
789 io_done(struct io *io)
791         pid_t pid = io->pid;
793         if (io->pipe != -1)
794                 close(io->pipe);
795         free(io->buf);
796         io_init(io);
798         while (pid > 0) {
799                 int status;
800                 pid_t waiting = waitpid(pid, &status, 0);
802                 if (waiting < 0) {
803                         if (errno == EINTR)
804                                 continue;
805                         io->error = errno;
806                         return FALSE;
807                 }
809                 return waiting == pid &&
810                        !WIFSIGNALED(status) &&
811                        WIFEXITED(status) &&
812                        !WEXITSTATUS(status);
813         }
815         return TRUE;
818 static bool
819 io_run(struct io *io, enum io_type type, const char *dir, const char *argv[], ...)
821         int pipefds[2] = { -1, -1 };
822         va_list args;
824         io_init(io);
826         if ((type == IO_RD || type == IO_WR) && pipe(pipefds) < 0) {
827                 io->error = errno;
828                 return FALSE;
829         } else if (type == IO_AP) {
830                 va_start(args, argv);
831                 pipefds[1] = va_arg(args, int);
832                 va_end(args);
833         }
835         if ((io->pid = fork())) {
836                 if (io->pid == -1)
837                         io->error = errno;
838                 if (pipefds[!(type == IO_WR)] != -1)
839                         close(pipefds[!(type == IO_WR)]);
840                 if (io->pid != -1) {
841                         io->pipe = pipefds[!!(type == IO_WR)];
842                         return TRUE;
843                 }
845         } else {
846                 if (type != IO_FG) {
847                         int devnull = open("/dev/null", O_RDWR);
848                         int readfd  = type == IO_WR ? pipefds[0] : devnull;
849                         int writefd = (type == IO_RD || type == IO_AP)
850                                                         ? pipefds[1] : devnull;
852                         dup2(readfd,  STDIN_FILENO);
853                         dup2(writefd, STDOUT_FILENO);
854                         dup2(devnull, STDERR_FILENO);
856                         close(devnull);
857                         if (pipefds[0] != -1)
858                                 close(pipefds[0]);
859                         if (pipefds[1] != -1)
860                                 close(pipefds[1]);
861                 }
863                 if (dir && *dir && chdir(dir) == -1)
864                         exit(errno);
866                 execvp(argv[0], (char *const*) argv);
867                 exit(errno);
868         }
870         if (pipefds[!!(type == IO_WR)] != -1)
871                 close(pipefds[!!(type == IO_WR)]);
872         return FALSE;
875 static bool
876 io_complete(enum io_type type, const char **argv, const char *dir, int fd)
878         struct io io;
880         return io_run(&io, type, dir, argv, fd) && io_done(&io);
883 static bool
884 io_run_bg(const char **argv)
886         return io_complete(IO_BG, argv, NULL, -1);
889 static bool
890 io_run_fg(const char **argv, const char *dir)
892         return io_complete(IO_FG, argv, dir, -1);
895 static bool
896 io_run_append(const char **argv, int fd)
898         return io_complete(IO_AP, argv, NULL, fd);
901 static bool
902 io_eof(struct io *io)
904         return io->eof;
907 static int
908 io_error(struct io *io)
910         return io->error;
913 static char *
914 io_strerror(struct io *io)
916         return strerror(io->error);
919 static bool
920 io_can_read(struct io *io)
922         struct timeval tv = { 0, 500 };
923         fd_set fds;
925         FD_ZERO(&fds);
926         FD_SET(io->pipe, &fds);
928         return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
931 static ssize_t
932 io_read(struct io *io, void *buf, size_t bufsize)
934         do {
935                 ssize_t readsize = read(io->pipe, buf, bufsize);
937                 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
938                         continue;
939                 else if (readsize == -1)
940                         io->error = errno;
941                 else if (readsize == 0)
942                         io->eof = 1;
943                 return readsize;
944         } while (1);
947 DEFINE_ALLOCATOR(io_realloc_buf, char, BUFSIZ)
949 static char *
950 io_get(struct io *io, int c, bool can_read)
952         char *eol;
953         ssize_t readsize;
955         while (TRUE) {
956                 if (io->bufsize > 0) {
957                         eol = memchr(io->bufpos, c, io->bufsize);
958                         if (eol) {
959                                 char *line = io->bufpos;
961                                 *eol = 0;
962                                 io->bufpos = eol + 1;
963                                 io->bufsize -= io->bufpos - line;
964                                 return line;
965                         }
966                 }
968                 if (io_eof(io)) {
969                         if (io->bufsize) {
970                                 io->bufpos[io->bufsize] = 0;
971                                 io->bufsize = 0;
972                                 return io->bufpos;
973                         }
974                         return NULL;
975                 }
977                 if (!can_read)
978                         return NULL;
980                 if (io->bufsize > 0 && io->bufpos > io->buf)
981                         memmove(io->buf, io->bufpos, io->bufsize);
983                 if (io->bufalloc == io->bufsize) {
984                         if (!io_realloc_buf(&io->buf, io->bufalloc, BUFSIZ))
985                                 return NULL;
986                         io->bufalloc += BUFSIZ;
987                 }
989                 io->bufpos = io->buf;
990                 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
991                 if (io_error(io))
992                         return NULL;
993                 io->bufsize += readsize;
994         }
997 static bool
998 io_write(struct io *io, const void *buf, size_t bufsize)
1000         size_t written = 0;
1002         while (!io_error(io) && written < bufsize) {
1003                 ssize_t size;
1005                 size = write(io->pipe, buf + written, bufsize - written);
1006                 if (size < 0 && (errno == EAGAIN || errno == EINTR))
1007                         continue;
1008                 else if (size == -1)
1009                         io->error = errno;
1010                 else
1011                         written += size;
1012         }
1014         return written == bufsize;
1017 static bool
1018 io_read_buf(struct io *io, char buf[], size_t bufsize)
1020         char *result = io_get(io, '\n', TRUE);
1022         if (result) {
1023                 result = chomp_string(result);
1024                 string_ncopy_do(buf, bufsize, result, strlen(result));
1025         }
1027         return io_done(io) && result;
1030 static bool
1031 io_run_buf(const char **argv, char buf[], size_t bufsize)
1033         struct io io;
1035         return io_run(&io, IO_RD, NULL, argv) && io_read_buf(&io, buf, bufsize);
1038 static int
1039 io_load(struct io *io, const char *separators,
1040         int (*read_property)(char *, size_t, char *, size_t))
1042         char *name;
1043         int state = OK;
1045         while (state == OK && (name = io_get(io, '\n', TRUE))) {
1046                 char *value;
1047                 size_t namelen;
1048                 size_t valuelen;
1050                 name = chomp_string(name);
1051                 namelen = strcspn(name, separators);
1053                 if (name[namelen]) {
1054                         name[namelen] = 0;
1055                         value = chomp_string(name + namelen + 1);
1056                         valuelen = strlen(value);
1058                 } else {
1059                         value = "";
1060                         valuelen = 0;
1061                 }
1063                 state = read_property(name, namelen, value, valuelen);
1064         }
1066         if (state != ERR && io_error(io))
1067                 state = ERR;
1068         io_done(io);
1070         return state;
1073 static int
1074 io_run_load(const char **argv, const char *separators,
1075             int (*read_property)(char *, size_t, char *, size_t))
1077         struct io io;
1079         if (!io_run(&io, IO_RD, NULL, argv))
1080                 return ERR;
1081         return io_load(&io, separators, read_property);
1085 /*
1086  * User requests
1087  */
1089 #define REQ_INFO \
1090         /* XXX: Keep the view request first and in sync with views[]. */ \
1091         REQ_GROUP("View switching") \
1092         REQ_(VIEW_MAIN,         "Show main view"), \
1093         REQ_(VIEW_DIFF,         "Show diff view"), \
1094         REQ_(VIEW_LOG,          "Show log view"), \
1095         REQ_(VIEW_TREE,         "Show tree view"), \
1096         REQ_(VIEW_BLOB,         "Show blob view"), \
1097         REQ_(VIEW_BLAME,        "Show blame view"), \
1098         REQ_(VIEW_BRANCH,       "Show branch view"), \
1099         REQ_(VIEW_HELP,         "Show help page"), \
1100         REQ_(VIEW_PAGER,        "Show pager view"), \
1101         REQ_(VIEW_STATUS,       "Show status view"), \
1102         REQ_(VIEW_STAGE,        "Show stage view"), \
1103         \
1104         REQ_GROUP("View manipulation") \
1105         REQ_(ENTER,             "Enter current line and scroll"), \
1106         REQ_(NEXT,              "Move to next"), \
1107         REQ_(PREVIOUS,          "Move to previous"), \
1108         REQ_(PARENT,            "Move to parent"), \
1109         REQ_(VIEW_NEXT,         "Move focus to next view"), \
1110         REQ_(REFRESH,           "Reload and refresh"), \
1111         REQ_(MAXIMIZE,          "Maximize the current view"), \
1112         REQ_(VIEW_CLOSE,        "Close the current view"), \
1113         REQ_(QUIT,              "Close all views and quit"), \
1114         \
1115         REQ_GROUP("View specific requests") \
1116         REQ_(STATUS_UPDATE,     "Update file status"), \
1117         REQ_(STATUS_REVERT,     "Revert file changes"), \
1118         REQ_(STATUS_MERGE,      "Merge file using external tool"), \
1119         REQ_(STAGE_NEXT,        "Find next chunk to stage"), \
1120         \
1121         REQ_GROUP("Cursor navigation") \
1122         REQ_(MOVE_UP,           "Move cursor one line up"), \
1123         REQ_(MOVE_DOWN,         "Move cursor one line down"), \
1124         REQ_(MOVE_PAGE_DOWN,    "Move cursor one page down"), \
1125         REQ_(MOVE_PAGE_UP,      "Move cursor one page up"), \
1126         REQ_(MOVE_FIRST_LINE,   "Move cursor to first line"), \
1127         REQ_(MOVE_LAST_LINE,    "Move cursor to last line"), \
1128         \
1129         REQ_GROUP("Scrolling") \
1130         REQ_(SCROLL_LEFT,       "Scroll two columns left"), \
1131         REQ_(SCROLL_RIGHT,      "Scroll two columns right"), \
1132         REQ_(SCROLL_LINE_UP,    "Scroll one line up"), \
1133         REQ_(SCROLL_LINE_DOWN,  "Scroll one line down"), \
1134         REQ_(SCROLL_PAGE_UP,    "Scroll one page up"), \
1135         REQ_(SCROLL_PAGE_DOWN,  "Scroll one page down"), \
1136         \
1137         REQ_GROUP("Searching") \
1138         REQ_(SEARCH,            "Search the view"), \
1139         REQ_(SEARCH_BACK,       "Search backwards in the view"), \
1140         REQ_(FIND_NEXT,         "Find next search match"), \
1141         REQ_(FIND_PREV,         "Find previous search match"), \
1142         \
1143         REQ_GROUP("Option manipulation") \
1144         REQ_(OPTIONS,           "Open option menu"), \
1145         REQ_(TOGGLE_LINENO,     "Toggle line numbers"), \
1146         REQ_(TOGGLE_DATE,       "Toggle date display"), \
1147         REQ_(TOGGLE_DATE_SHORT, "Toggle short (date-only) dates"), \
1148         REQ_(TOGGLE_AUTHOR,     "Toggle author display"), \
1149         REQ_(TOGGLE_REV_GRAPH,  "Toggle revision graph visualization"), \
1150         REQ_(TOGGLE_REFS,       "Toggle reference display (tags/branches)"), \
1151         REQ_(TOGGLE_SORT_ORDER, "Toggle ascending/descending sort order"), \
1152         REQ_(TOGGLE_SORT_FIELD, "Toggle field to sort by"), \
1153         \
1154         REQ_GROUP("Misc") \
1155         REQ_(PROMPT,            "Bring up the prompt"), \
1156         REQ_(SCREEN_REDRAW,     "Redraw the screen"), \
1157         REQ_(SHOW_VERSION,      "Show version information"), \
1158         REQ_(STOP_LOADING,      "Stop all loading views"), \
1159         REQ_(EDIT,              "Open in editor"), \
1160         REQ_(NONE,              "Do nothing")
1163 /* User action requests. */
1164 enum request {
1165 #define REQ_GROUP(help)
1166 #define REQ_(req, help) REQ_##req
1168         /* Offset all requests to avoid conflicts with ncurses getch values. */
1169         REQ_UNKNOWN = KEY_MAX + 1,
1170         REQ_OFFSET,
1171         REQ_INFO
1173 #undef  REQ_GROUP
1174 #undef  REQ_
1175 };
1177 struct request_info {
1178         enum request request;
1179         const char *name;
1180         int namelen;
1181         const char *help;
1182 };
1184 static const struct request_info req_info[] = {
1185 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
1186 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
1187         REQ_INFO
1188 #undef  REQ_GROUP
1189 #undef  REQ_
1190 };
1192 static enum request
1193 get_request(const char *name)
1195         int namelen = strlen(name);
1196         int i;
1198         for (i = 0; i < ARRAY_SIZE(req_info); i++)
1199                 if (enum_equals(req_info[i], name, namelen))
1200                         return req_info[i].request;
1202         return REQ_UNKNOWN;
1206 /*
1207  * Options
1208  */
1210 /* Option and state variables. */
1211 static enum date opt_date               = DATE_DEFAULT;
1212 static enum author opt_author           = AUTHOR_DEFAULT;
1213 static bool opt_line_number             = FALSE;
1214 static bool opt_line_graphics           = TRUE;
1215 static bool opt_rev_graph               = FALSE;
1216 static bool opt_show_refs               = TRUE;
1217 static int opt_num_interval             = 5;
1218 static double opt_hscroll               = 0.50;
1219 static double opt_scale_split_view      = 2.0 / 3.0;
1220 static int opt_tab_size                 = 8;
1221 static int opt_author_cols              = AUTHOR_COLS;
1222 static char opt_path[SIZEOF_STR]        = "";
1223 static char opt_file[SIZEOF_STR]        = "";
1224 static char opt_ref[SIZEOF_REF]         = "";
1225 static char opt_head[SIZEOF_REF]        = "";
1226 static char opt_remote[SIZEOF_REF]      = "";
1227 static char opt_encoding[20]            = "UTF-8";
1228 static iconv_t opt_iconv_in             = ICONV_NONE;
1229 static iconv_t opt_iconv_out            = ICONV_NONE;
1230 static char opt_search[SIZEOF_STR]      = "";
1231 static char opt_cdup[SIZEOF_STR]        = "";
1232 static char opt_prefix[SIZEOF_STR]      = "";
1233 static char opt_git_dir[SIZEOF_STR]     = "";
1234 static signed char opt_is_inside_work_tree      = -1; /* set to TRUE or FALSE */
1235 static char opt_editor[SIZEOF_STR]      = "";
1236 static FILE *opt_tty                    = NULL;
1237 static const char **opt_diff_args       = NULL;
1238 static const char **opt_rev_args        = NULL;
1239 static const char **opt_file_args       = NULL;
1241 #define is_initial_commit()     (!get_ref_head())
1242 #define is_head_commit(rev)     (!strcmp((rev), "HEAD") || (get_ref_head() && !strcmp(rev, get_ref_head()->id)))
1245 /*
1246  * Line-oriented content detection.
1247  */
1249 #define LINE_INFO \
1250 LINE(DIFF_HEADER,  "diff --git ",       COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1251 LINE(DIFF_CHUNK,   "@@",                COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1252 LINE(DIFF_ADD,     "+",                 COLOR_GREEN,    COLOR_DEFAULT,  0), \
1253 LINE(DIFF_DEL,     "-",                 COLOR_RED,      COLOR_DEFAULT,  0), \
1254 LINE(DIFF_INDEX,        "index ",         COLOR_BLUE,   COLOR_DEFAULT,  0), \
1255 LINE(DIFF_OLDMODE,      "old file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1256 LINE(DIFF_NEWMODE,      "new file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1257 LINE(DIFF_COPY_FROM,    "copy from",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
1258 LINE(DIFF_COPY_TO,      "copy to",        COLOR_YELLOW, COLOR_DEFAULT,  0), \
1259 LINE(DIFF_RENAME_FROM,  "rename from",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
1260 LINE(DIFF_RENAME_TO,    "rename to",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
1261 LINE(DIFF_SIMILARITY,   "similarity ",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
1262 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1263 LINE(DIFF_TREE,         "diff-tree ",     COLOR_BLUE,   COLOR_DEFAULT,  0), \
1264 LINE(PP_AUTHOR,    "Author: ",          COLOR_CYAN,     COLOR_DEFAULT,  0), \
1265 LINE(PP_COMMIT,    "Commit: ",          COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1266 LINE(PP_MERGE,     "Merge: ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
1267 LINE(PP_DATE,      "Date:   ",          COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1268 LINE(PP_ADATE,     "AuthorDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1269 LINE(PP_CDATE,     "CommitDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1270 LINE(PP_REFS,      "Refs: ",            COLOR_RED,      COLOR_DEFAULT,  0), \
1271 LINE(COMMIT,       "commit ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
1272 LINE(PARENT,       "parent ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
1273 LINE(TREE,         "tree ",             COLOR_BLUE,     COLOR_DEFAULT,  0), \
1274 LINE(AUTHOR,       "author ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
1275 LINE(COMMITTER,    "committer ",        COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1276 LINE(SIGNOFF,      "    Signed-off-by", COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1277 LINE(ACKED,        "    Acked-by",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1278 LINE(TESTED,       "    Tested-by",     COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1279 LINE(REVIEWED,     "    Reviewed-by",   COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1280 LINE(DEFAULT,      "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
1281 LINE(CURSOR,       "",                  COLOR_WHITE,    COLOR_GREEN,    A_BOLD), \
1282 LINE(STATUS,       "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
1283 LINE(DELIMITER,    "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1284 LINE(DATE,         "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
1285 LINE(MODE,         "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1286 LINE(LINE_NUMBER,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1287 LINE(TITLE_BLUR,   "",                  COLOR_WHITE,    COLOR_BLUE,     0), \
1288 LINE(TITLE_FOCUS,  "",                  COLOR_WHITE,    COLOR_BLUE,     A_BOLD), \
1289 LINE(MAIN_COMMIT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
1290 LINE(MAIN_TAG,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
1291 LINE(MAIN_LOCAL_TAG,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1292 LINE(MAIN_REMOTE,  "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1293 LINE(MAIN_TRACKED, "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_BOLD), \
1294 LINE(MAIN_REF,     "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1295 LINE(MAIN_HEAD,    "",                  COLOR_CYAN,     COLOR_DEFAULT,  A_BOLD), \
1296 LINE(MAIN_REVGRAPH,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1297 LINE(TREE_HEAD,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_BOLD), \
1298 LINE(TREE_DIR,     "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_NORMAL), \
1299 LINE(TREE_FILE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
1300 LINE(STAT_HEAD,    "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1301 LINE(STAT_SECTION, "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1302 LINE(STAT_NONE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
1303 LINE(STAT_STAGED,  "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1304 LINE(STAT_UNSTAGED,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1305 LINE(STAT_UNTRACKED,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1306 LINE(HELP_KEYMAP,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1307 LINE(HELP_GROUP,   "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
1308 LINE(BLAME_ID,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0)
1310 enum line_type {
1311 #define LINE(type, line, fg, bg, attr) \
1312         LINE_##type
1313         LINE_INFO,
1314         LINE_NONE
1315 #undef  LINE
1316 };
1318 struct line_info {
1319         const char *name;       /* Option name. */
1320         int namelen;            /* Size of option name. */
1321         const char *line;       /* The start of line to match. */
1322         int linelen;            /* Size of string to match. */
1323         int fg, bg, attr;       /* Color and text attributes for the lines. */
1324 };
1326 static struct line_info line_info[] = {
1327 #define LINE(type, line, fg, bg, attr) \
1328         { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1329         LINE_INFO
1330 #undef  LINE
1331 };
1333 static enum line_type
1334 get_line_type(const char *line)
1336         int linelen = strlen(line);
1337         enum line_type type;
1339         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1340                 /* Case insensitive search matches Signed-off-by lines better. */
1341                 if (linelen >= line_info[type].linelen &&
1342                     !strncasecmp(line_info[type].line, line, line_info[type].linelen))
1343                         return type;
1345         return LINE_DEFAULT;
1348 static inline int
1349 get_line_attr(enum line_type type)
1351         assert(type < ARRAY_SIZE(line_info));
1352         return COLOR_PAIR(type) | line_info[type].attr;
1355 static struct line_info *
1356 get_line_info(const char *name)
1358         size_t namelen = strlen(name);
1359         enum line_type type;
1361         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1362                 if (enum_equals(line_info[type], name, namelen))
1363                         return &line_info[type];
1365         return NULL;
1368 static void
1369 init_colors(void)
1371         int default_bg = line_info[LINE_DEFAULT].bg;
1372         int default_fg = line_info[LINE_DEFAULT].fg;
1373         enum line_type type;
1375         start_color();
1377         if (assume_default_colors(default_fg, default_bg) == ERR) {
1378                 default_bg = COLOR_BLACK;
1379                 default_fg = COLOR_WHITE;
1380         }
1382         for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1383                 struct line_info *info = &line_info[type];
1384                 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1385                 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1387                 init_pair(type, fg, bg);
1388         }
1391 struct line {
1392         enum line_type type;
1394         /* State flags */
1395         unsigned int selected:1;
1396         unsigned int dirty:1;
1397         unsigned int cleareol:1;
1398         unsigned int other:16;
1400         void *data;             /* User data */
1401 };
1404 /*
1405  * Keys
1406  */
1408 struct keybinding {
1409         int alias;
1410         enum request request;
1411 };
1413 static struct keybinding default_keybindings[] = {
1414         /* View switching */
1415         { 'm',          REQ_VIEW_MAIN },
1416         { 'd',          REQ_VIEW_DIFF },
1417         { 'l',          REQ_VIEW_LOG },
1418         { 't',          REQ_VIEW_TREE },
1419         { 'f',          REQ_VIEW_BLOB },
1420         { 'B',          REQ_VIEW_BLAME },
1421         { 'H',          REQ_VIEW_BRANCH },
1422         { 'p',          REQ_VIEW_PAGER },
1423         { 'h',          REQ_VIEW_HELP },
1424         { 'S',          REQ_VIEW_STATUS },
1425         { 'c',          REQ_VIEW_STAGE },
1427         /* View manipulation */
1428         { 'q',          REQ_VIEW_CLOSE },
1429         { KEY_TAB,      REQ_VIEW_NEXT },
1430         { KEY_RETURN,   REQ_ENTER },
1431         { KEY_UP,       REQ_PREVIOUS },
1432         { KEY_DOWN,     REQ_NEXT },
1433         { 'R',          REQ_REFRESH },
1434         { KEY_F(5),     REQ_REFRESH },
1435         { 'O',          REQ_MAXIMIZE },
1437         /* Cursor navigation */
1438         { 'k',          REQ_MOVE_UP },
1439         { 'j',          REQ_MOVE_DOWN },
1440         { KEY_HOME,     REQ_MOVE_FIRST_LINE },
1441         { KEY_END,      REQ_MOVE_LAST_LINE },
1442         { KEY_NPAGE,    REQ_MOVE_PAGE_DOWN },
1443         { ' ',          REQ_MOVE_PAGE_DOWN },
1444         { KEY_PPAGE,    REQ_MOVE_PAGE_UP },
1445         { 'b',          REQ_MOVE_PAGE_UP },
1446         { '-',          REQ_MOVE_PAGE_UP },
1448         /* Scrolling */
1449         { KEY_LEFT,     REQ_SCROLL_LEFT },
1450         { KEY_RIGHT,    REQ_SCROLL_RIGHT },
1451         { KEY_IC,       REQ_SCROLL_LINE_UP },
1452         { KEY_DC,       REQ_SCROLL_LINE_DOWN },
1453         { 'w',          REQ_SCROLL_PAGE_UP },
1454         { 's',          REQ_SCROLL_PAGE_DOWN },
1456         /* Searching */
1457         { '/',          REQ_SEARCH },
1458         { '?',          REQ_SEARCH_BACK },
1459         { 'n',          REQ_FIND_NEXT },
1460         { 'N',          REQ_FIND_PREV },
1462         /* Misc */
1463         { 'Q',          REQ_QUIT },
1464         { 'z',          REQ_STOP_LOADING },
1465         { 'v',          REQ_SHOW_VERSION },
1466         { 'r',          REQ_SCREEN_REDRAW },
1467         { 'o',          REQ_OPTIONS },
1468         { '.',          REQ_TOGGLE_LINENO },
1469         { 'D',          REQ_TOGGLE_DATE },
1470         { 'A',          REQ_TOGGLE_AUTHOR },
1471         { 'g',          REQ_TOGGLE_REV_GRAPH },
1472         { 'F',          REQ_TOGGLE_REFS },
1473         { 'I',          REQ_TOGGLE_SORT_ORDER },
1474         { 'i',          REQ_TOGGLE_SORT_FIELD },
1475         { ':',          REQ_PROMPT },
1476         { 'u',          REQ_STATUS_UPDATE },
1477         { '!',          REQ_STATUS_REVERT },
1478         { 'M',          REQ_STATUS_MERGE },
1479         { '@',          REQ_STAGE_NEXT },
1480         { ',',          REQ_PARENT },
1481         { 'e',          REQ_EDIT },
1482 };
1484 #define KEYMAP_INFO \
1485         KEYMAP_(GENERIC), \
1486         KEYMAP_(MAIN), \
1487         KEYMAP_(DIFF), \
1488         KEYMAP_(LOG), \
1489         KEYMAP_(TREE), \
1490         KEYMAP_(BLOB), \
1491         KEYMAP_(BLAME), \
1492         KEYMAP_(BRANCH), \
1493         KEYMAP_(PAGER), \
1494         KEYMAP_(HELP), \
1495         KEYMAP_(STATUS), \
1496         KEYMAP_(STAGE)
1498 enum keymap {
1499 #define KEYMAP_(name) KEYMAP_##name
1500         KEYMAP_INFO
1501 #undef  KEYMAP_
1502 };
1504 static const struct enum_map keymap_table[] = {
1505 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1506         KEYMAP_INFO
1507 #undef  KEYMAP_
1508 };
1510 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1512 struct keybinding_table {
1513         struct keybinding *data;
1514         size_t size;
1515 };
1517 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1519 static void
1520 add_keybinding(enum keymap keymap, enum request request, int key)
1522         struct keybinding_table *table = &keybindings[keymap];
1523         size_t i;
1525         for (i = 0; i < keybindings[keymap].size; i++) {
1526                 if (keybindings[keymap].data[i].alias == key) {
1527                         keybindings[keymap].data[i].request = request;
1528                         return;
1529                 }
1530         }
1532         table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1533         if (!table->data)
1534                 die("Failed to allocate keybinding");
1535         table->data[table->size].alias = key;
1536         table->data[table->size++].request = request;
1538         if (request == REQ_NONE && keymap == KEYMAP_GENERIC) {
1539                 int i;
1541                 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1542                         if (default_keybindings[i].alias == key)
1543                                 default_keybindings[i].request = REQ_NONE;
1544         }
1547 /* Looks for a key binding first in the given map, then in the generic map, and
1548  * lastly in the default keybindings. */
1549 static enum request
1550 get_keybinding(enum keymap keymap, int key)
1552         size_t i;
1554         for (i = 0; i < keybindings[keymap].size; i++)
1555                 if (keybindings[keymap].data[i].alias == key)
1556                         return keybindings[keymap].data[i].request;
1558         for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1559                 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1560                         return keybindings[KEYMAP_GENERIC].data[i].request;
1562         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1563                 if (default_keybindings[i].alias == key)
1564                         return default_keybindings[i].request;
1566         return (enum request) key;
1570 struct key {
1571         const char *name;
1572         int value;
1573 };
1575 static const struct key key_table[] = {
1576         { "Enter",      KEY_RETURN },
1577         { "Space",      ' ' },
1578         { "Backspace",  KEY_BACKSPACE },
1579         { "Tab",        KEY_TAB },
1580         { "Escape",     KEY_ESC },
1581         { "Left",       KEY_LEFT },
1582         { "Right",      KEY_RIGHT },
1583         { "Up",         KEY_UP },
1584         { "Down",       KEY_DOWN },
1585         { "Insert",     KEY_IC },
1586         { "Delete",     KEY_DC },
1587         { "Hash",       '#' },
1588         { "Home",       KEY_HOME },
1589         { "End",        KEY_END },
1590         { "PageUp",     KEY_PPAGE },
1591         { "PageDown",   KEY_NPAGE },
1592         { "F1",         KEY_F(1) },
1593         { "F2",         KEY_F(2) },
1594         { "F3",         KEY_F(3) },
1595         { "F4",         KEY_F(4) },
1596         { "F5",         KEY_F(5) },
1597         { "F6",         KEY_F(6) },
1598         { "F7",         KEY_F(7) },
1599         { "F8",         KEY_F(8) },
1600         { "F9",         KEY_F(9) },
1601         { "F10",        KEY_F(10) },
1602         { "F11",        KEY_F(11) },
1603         { "F12",        KEY_F(12) },
1604 };
1606 static int
1607 get_key_value(const char *name)
1609         int i;
1611         for (i = 0; i < ARRAY_SIZE(key_table); i++)
1612                 if (!strcasecmp(key_table[i].name, name))
1613                         return key_table[i].value;
1615         if (strlen(name) == 1 && isprint(*name))
1616                 return (int) *name;
1618         return ERR;
1621 static const char *
1622 get_key_name(int key_value)
1624         static char key_char[] = "'X'";
1625         const char *seq = NULL;
1626         int key;
1628         for (key = 0; key < ARRAY_SIZE(key_table); key++)
1629                 if (key_table[key].value == key_value)
1630                         seq = key_table[key].name;
1632         if (seq == NULL &&
1633             key_value < 127 &&
1634             isprint(key_value)) {
1635                 key_char[1] = (char) key_value;
1636                 seq = key_char;
1637         }
1639         return seq ? seq : "(no key)";
1642 static bool
1643 append_key(char *buf, size_t *pos, const struct keybinding *keybinding)
1645         const char *sep = *pos > 0 ? ", " : "";
1646         const char *keyname = get_key_name(keybinding->alias);
1648         return string_nformat(buf, BUFSIZ, pos, "%s%s", sep, keyname);
1651 static bool
1652 append_keymap_request_keys(char *buf, size_t *pos, enum request request,
1653                            enum keymap keymap, bool all)
1655         int i;
1657         for (i = 0; i < keybindings[keymap].size; i++) {
1658                 if (keybindings[keymap].data[i].request == request) {
1659                         if (!append_key(buf, pos, &keybindings[keymap].data[i]))
1660                                 return FALSE;
1661                         if (!all)
1662                                 break;
1663                 }
1664         }
1666         return TRUE;
1669 #define get_key(keymap, request) get_keys(keymap, request, FALSE)
1671 static const char *
1672 get_keys(enum keymap keymap, enum request request, bool all)
1674         static char buf[BUFSIZ];
1675         size_t pos = 0;
1676         int i;
1678         buf[pos] = 0;
1680         if (!append_keymap_request_keys(buf, &pos, request, keymap, all))
1681                 return "Too many keybindings!";
1682         if (pos > 0 && !all)
1683                 return buf;
1685         if (keymap != KEYMAP_GENERIC) {
1686                 /* Only the generic keymap includes the default keybindings when
1687                  * listing all keys. */
1688                 if (all)
1689                         return buf;
1691                 if (!append_keymap_request_keys(buf, &pos, request, KEYMAP_GENERIC, all))
1692                         return "Too many keybindings!";
1693                 if (pos)
1694                         return buf;
1695         }
1697         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1698                 if (default_keybindings[i].request == request) {
1699                         if (!append_key(buf, &pos, &default_keybindings[i]))
1700                                 return "Too many keybindings!";
1701                         if (!all)
1702                                 return buf;
1703                 }
1704         }
1706         return buf;
1709 struct run_request {
1710         enum keymap keymap;
1711         int key;
1712         const char **argv;
1713 };
1715 static struct run_request *run_request;
1716 static size_t run_requests;
1718 DEFINE_ALLOCATOR(realloc_run_requests, struct run_request, 8)
1720 static enum request
1721 add_run_request(enum keymap keymap, int key, const char **argv)
1723         struct run_request *req;
1725         if (!realloc_run_requests(&run_request, run_requests, 1))
1726                 return REQ_NONE;
1728         req = &run_request[run_requests];
1729         req->keymap = keymap;
1730         req->key = key;
1731         req->argv = NULL;
1733         if (!argv_copy(&req->argv, argv))
1734                 return REQ_NONE;
1736         return REQ_NONE + ++run_requests;
1739 static struct run_request *
1740 get_run_request(enum request request)
1742         if (request <= REQ_NONE)
1743                 return NULL;
1744         return &run_request[request - REQ_NONE - 1];
1747 static void
1748 add_builtin_run_requests(void)
1750         const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1751         const char *checkout[] = { "git", "checkout", "%(branch)", NULL };
1752         const char *commit[] = { "git", "commit", NULL };
1753         const char *gc[] = { "git", "gc", NULL };
1754         struct run_request reqs[] = {
1755                 { KEYMAP_MAIN,    'C', cherry_pick },
1756                 { KEYMAP_STATUS,  'C', commit },
1757                 { KEYMAP_BRANCH,  'C', checkout },
1758                 { KEYMAP_GENERIC, 'G', gc },
1759         };
1760         int i;
1762         for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1763                 enum request req = get_keybinding(reqs[i].keymap, reqs[i].key);
1765                 if (req != reqs[i].key)
1766                         continue;
1767                 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argv);
1768                 if (req != REQ_NONE)
1769                         add_keybinding(reqs[i].keymap, req, reqs[i].key);
1770         }
1773 /*
1774  * User config file handling.
1775  */
1777 static int   config_lineno;
1778 static bool  config_errors;
1779 static const char *config_msg;
1781 static const struct enum_map color_map[] = {
1782 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1783         COLOR_MAP(DEFAULT),
1784         COLOR_MAP(BLACK),
1785         COLOR_MAP(BLUE),
1786         COLOR_MAP(CYAN),
1787         COLOR_MAP(GREEN),
1788         COLOR_MAP(MAGENTA),
1789         COLOR_MAP(RED),
1790         COLOR_MAP(WHITE),
1791         COLOR_MAP(YELLOW),
1792 };
1794 static const struct enum_map attr_map[] = {
1795 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1796         ATTR_MAP(NORMAL),
1797         ATTR_MAP(BLINK),
1798         ATTR_MAP(BOLD),
1799         ATTR_MAP(DIM),
1800         ATTR_MAP(REVERSE),
1801         ATTR_MAP(STANDOUT),
1802         ATTR_MAP(UNDERLINE),
1803 };
1805 #define set_attribute(attr, name)       map_enum(attr, attr_map, name)
1807 static int parse_step(double *opt, const char *arg)
1809         *opt = atoi(arg);
1810         if (!strchr(arg, '%'))
1811                 return OK;
1813         /* "Shift down" so 100% and 1 does not conflict. */
1814         *opt = (*opt - 1) / 100;
1815         if (*opt >= 1.0) {
1816                 *opt = 0.99;
1817                 config_msg = "Step value larger than 100%";
1818                 return ERR;
1819         }
1820         if (*opt < 0.0) {
1821                 *opt = 1;
1822                 config_msg = "Invalid step value";
1823                 return ERR;
1824         }
1825         return OK;
1828 static int
1829 parse_int(int *opt, const char *arg, int min, int max)
1831         int value = atoi(arg);
1833         if (min <= value && value <= max) {
1834                 *opt = value;
1835                 return OK;
1836         }
1838         config_msg = "Integer value out of bound";
1839         return ERR;
1842 static bool
1843 set_color(int *color, const char *name)
1845         if (map_enum(color, color_map, name))
1846                 return TRUE;
1847         if (!prefixcmp(name, "color"))
1848                 return parse_int(color, name + 5, 0, 255) == OK;
1849         return FALSE;
1852 /* Wants: object fgcolor bgcolor [attribute] */
1853 static int
1854 option_color_command(int argc, const char *argv[])
1856         struct line_info *info;
1858         if (argc < 3) {
1859                 config_msg = "Wrong number of arguments given to color command";
1860                 return ERR;
1861         }
1863         info = get_line_info(argv[0]);
1864         if (!info) {
1865                 static const struct enum_map obsolete[] = {
1866                         ENUM_MAP("main-delim",  LINE_DELIMITER),
1867                         ENUM_MAP("main-date",   LINE_DATE),
1868                         ENUM_MAP("main-author", LINE_AUTHOR),
1869                 };
1870                 int index;
1872                 if (!map_enum(&index, obsolete, argv[0])) {
1873                         config_msg = "Unknown color name";
1874                         return ERR;
1875                 }
1876                 info = &line_info[index];
1877         }
1879         if (!set_color(&info->fg, argv[1]) ||
1880             !set_color(&info->bg, argv[2])) {
1881                 config_msg = "Unknown color";
1882                 return ERR;
1883         }
1885         info->attr = 0;
1886         while (argc-- > 3) {
1887                 int attr;
1889                 if (!set_attribute(&attr, argv[argc])) {
1890                         config_msg = "Unknown attribute";
1891                         return ERR;
1892                 }
1893                 info->attr |= attr;
1894         }
1896         return OK;
1899 static int parse_bool(bool *opt, const char *arg)
1901         *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1902                 ? TRUE : FALSE;
1903         return OK;
1906 static int parse_enum_do(unsigned int *opt, const char *arg,
1907                          const struct enum_map *map, size_t map_size)
1909         bool is_true;
1911         assert(map_size > 1);
1913         if (map_enum_do(map, map_size, (int *) opt, arg))
1914                 return OK;
1916         if (parse_bool(&is_true, arg) != OK)
1917                 return ERR;
1919         *opt = is_true ? map[1].value : map[0].value;
1920         return OK;
1923 #define parse_enum(opt, arg, map) \
1924         parse_enum_do(opt, arg, map, ARRAY_SIZE(map))
1926 static int
1927 parse_string(char *opt, const char *arg, size_t optsize)
1929         int arglen = strlen(arg);
1931         switch (arg[0]) {
1932         case '\"':
1933         case '\'':
1934                 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1935                         config_msg = "Unmatched quotation";
1936                         return ERR;
1937                 }
1938                 arg += 1; arglen -= 2;
1939         default:
1940                 string_ncopy_do(opt, optsize, arg, arglen);
1941                 return OK;
1942         }
1945 /* Wants: name = value */
1946 static int
1947 option_set_command(int argc, const char *argv[])
1949         if (argc != 3) {
1950                 config_msg = "Wrong number of arguments given to set command";
1951                 return ERR;
1952         }
1954         if (strcmp(argv[1], "=")) {
1955                 config_msg = "No value assigned";
1956                 return ERR;
1957         }
1959         if (!strcmp(argv[0], "show-author"))
1960                 return parse_enum(&opt_author, argv[2], author_map);
1962         if (!strcmp(argv[0], "show-date"))
1963                 return parse_enum(&opt_date, argv[2], date_map);
1965         if (!strcmp(argv[0], "show-rev-graph"))
1966                 return parse_bool(&opt_rev_graph, argv[2]);
1968         if (!strcmp(argv[0], "show-refs"))
1969                 return parse_bool(&opt_show_refs, argv[2]);
1971         if (!strcmp(argv[0], "show-line-numbers"))
1972                 return parse_bool(&opt_line_number, argv[2]);
1974         if (!strcmp(argv[0], "line-graphics"))
1975                 return parse_bool(&opt_line_graphics, argv[2]);
1977         if (!strcmp(argv[0], "line-number-interval"))
1978                 return parse_int(&opt_num_interval, argv[2], 1, 1024);
1980         if (!strcmp(argv[0], "author-width"))
1981                 return parse_int(&opt_author_cols, argv[2], 0, 1024);
1983         if (!strcmp(argv[0], "horizontal-scroll"))
1984                 return parse_step(&opt_hscroll, argv[2]);
1986         if (!strcmp(argv[0], "split-view-height"))
1987                 return parse_step(&opt_scale_split_view, argv[2]);
1989         if (!strcmp(argv[0], "tab-size"))
1990                 return parse_int(&opt_tab_size, argv[2], 1, 1024);
1992         if (!strcmp(argv[0], "commit-encoding"))
1993                 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
1995         config_msg = "Unknown variable name";
1996         return ERR;
1999 /* Wants: mode request key */
2000 static int
2001 option_bind_command(int argc, const char *argv[])
2003         enum request request;
2004         int keymap = -1;
2005         int key;
2007         if (argc < 3) {
2008                 config_msg = "Wrong number of arguments given to bind command";
2009                 return ERR;
2010         }
2012         if (!set_keymap(&keymap, argv[0])) {
2013                 config_msg = "Unknown key map";
2014                 return ERR;
2015         }
2017         key = get_key_value(argv[1]);
2018         if (key == ERR) {
2019                 config_msg = "Unknown key";
2020                 return ERR;
2021         }
2023         request = get_request(argv[2]);
2024         if (request == REQ_UNKNOWN) {
2025                 static const struct enum_map obsolete[] = {
2026                         ENUM_MAP("cherry-pick",         REQ_NONE),
2027                         ENUM_MAP("screen-resize",       REQ_NONE),
2028                         ENUM_MAP("tree-parent",         REQ_PARENT),
2029                 };
2030                 int alias;
2032                 if (map_enum(&alias, obsolete, argv[2])) {
2033                         if (alias != REQ_NONE)
2034                                 add_keybinding(keymap, alias, key);
2035                         config_msg = "Obsolete request name";
2036                         return ERR;
2037                 }
2038         }
2039         if (request == REQ_UNKNOWN && *argv[2]++ == '!')
2040                 request = add_run_request(keymap, key, argv + 2);
2041         if (request == REQ_UNKNOWN) {
2042                 config_msg = "Unknown request name";
2043                 return ERR;
2044         }
2046         add_keybinding(keymap, request, key);
2048         return OK;
2051 static int
2052 set_option(const char *opt, char *value)
2054         const char *argv[SIZEOF_ARG];
2055         int argc = 0;
2057         if (!argv_from_string(argv, &argc, value)) {
2058                 config_msg = "Too many option arguments";
2059                 return ERR;
2060         }
2062         if (!strcmp(opt, "color"))
2063                 return option_color_command(argc, argv);
2065         if (!strcmp(opt, "set"))
2066                 return option_set_command(argc, argv);
2068         if (!strcmp(opt, "bind"))
2069                 return option_bind_command(argc, argv);
2071         config_msg = "Unknown option command";
2072         return ERR;
2075 static int
2076 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
2078         int status = OK;
2080         config_lineno++;
2081         config_msg = "Internal error";
2083         /* Check for comment markers, since read_properties() will
2084          * only ensure opt and value are split at first " \t". */
2085         optlen = strcspn(opt, "#");
2086         if (optlen == 0)
2087                 return OK;
2089         if (opt[optlen] != 0) {
2090                 config_msg = "No option value";
2091                 status = ERR;
2093         }  else {
2094                 /* Look for comment endings in the value. */
2095                 size_t len = strcspn(value, "#");
2097                 if (len < valuelen) {
2098                         valuelen = len;
2099                         value[valuelen] = 0;
2100                 }
2102                 status = set_option(opt, value);
2103         }
2105         if (status == ERR) {
2106                 warn("Error on line %d, near '%.*s': %s",
2107                      config_lineno, (int) optlen, opt, config_msg);
2108                 config_errors = TRUE;
2109         }
2111         /* Always keep going if errors are encountered. */
2112         return OK;
2115 static void
2116 load_option_file(const char *path)
2118         struct io io;
2120         /* It's OK that the file doesn't exist. */
2121         if (!io_open(&io, "%s", path))
2122                 return;
2124         config_lineno = 0;
2125         config_errors = FALSE;
2127         if (io_load(&io, " \t", read_option) == ERR ||
2128             config_errors == TRUE)
2129                 warn("Errors while loading %s.", path);
2132 static int
2133 load_options(void)
2135         const char *home = getenv("HOME");
2136         const char *tigrc_user = getenv("TIGRC_USER");
2137         const char *tigrc_system = getenv("TIGRC_SYSTEM");
2138         const char *tig_diff_opts = getenv("TIG_DIFF_OPTS");
2139         char buf[SIZEOF_STR];
2141         if (!tigrc_system)
2142                 tigrc_system = SYSCONFDIR "/tigrc";
2143         load_option_file(tigrc_system);
2145         if (!tigrc_user) {
2146                 if (!home || !string_format(buf, "%s/.tigrc", home))
2147                         return ERR;
2148                 tigrc_user = buf;
2149         }
2150         load_option_file(tigrc_user);
2152         /* Add _after_ loading config files to avoid adding run requests
2153          * that conflict with keybindings. */
2154         add_builtin_run_requests();
2156         if (!opt_diff_args && tig_diff_opts && *tig_diff_opts) {
2157                 static const char *diff_opts[SIZEOF_ARG] = { NULL };
2158                 int argc = 0;
2160                 if (!string_format(buf, "%s", tig_diff_opts) ||
2161                     !argv_from_string(diff_opts, &argc, buf))
2162                         die("TIG_DIFF_OPTS contains too many arguments");
2163                 else if (!argv_copy(&opt_diff_args, diff_opts))
2164                         die("Failed to format TIG_DIFF_OPTS arguments");
2165         }
2167         return OK;
2171 /*
2172  * The viewer
2173  */
2175 struct view;
2176 struct view_ops;
2178 /* The display array of active views and the index of the current view. */
2179 static struct view *display[2];
2180 static unsigned int current_view;
2182 #define foreach_displayed_view(view, i) \
2183         for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
2185 #define displayed_views()       (display[1] != NULL ? 2 : 1)
2187 /* Current head and commit ID */
2188 static char ref_blob[SIZEOF_REF]        = "";
2189 static char ref_commit[SIZEOF_REF]      = "HEAD";
2190 static char ref_head[SIZEOF_REF]        = "HEAD";
2191 static char ref_branch[SIZEOF_REF]      = "";
2193 enum view_type {
2194         VIEW_MAIN,
2195         VIEW_DIFF,
2196         VIEW_LOG,
2197         VIEW_TREE,
2198         VIEW_BLOB,
2199         VIEW_BLAME,
2200         VIEW_BRANCH,
2201         VIEW_HELP,
2202         VIEW_PAGER,
2203         VIEW_STATUS,
2204         VIEW_STAGE,
2205 };
2207 struct view {
2208         enum view_type type;    /* View type */
2209         const char *name;       /* View name */
2210         const char *cmd_env;    /* Command line set via environment */
2211         const char *id;         /* Points to either of ref_{head,commit,blob} */
2213         struct view_ops *ops;   /* View operations */
2215         enum keymap keymap;     /* What keymap does this view have */
2216         bool git_dir;           /* Whether the view requires a git directory. */
2218         char ref[SIZEOF_REF];   /* Hovered commit reference */
2219         char vid[SIZEOF_REF];   /* View ID. Set to id member when updating. */
2221         int height, width;      /* The width and height of the main window */
2222         WINDOW *win;            /* The main window */
2223         WINDOW *title;          /* The title window living below the main window */
2225         /* Navigation */
2226         unsigned long offset;   /* Offset of the window top */
2227         unsigned long yoffset;  /* Offset from the window side. */
2228         unsigned long lineno;   /* Current line number */
2229         unsigned long p_offset; /* Previous offset of the window top */
2230         unsigned long p_yoffset;/* Previous offset from the window side */
2231         unsigned long p_lineno; /* Previous current line number */
2232         bool p_restore;         /* Should the previous position be restored. */
2234         /* Searching */
2235         char grep[SIZEOF_STR];  /* Search string */
2236         regex_t *regex;         /* Pre-compiled regexp */
2238         /* If non-NULL, points to the view that opened this view. If this view
2239          * is closed tig will switch back to the parent view. */
2240         struct view *parent;
2241         struct view *prev;
2243         /* Buffering */
2244         size_t lines;           /* Total number of lines */
2245         struct line *line;      /* Line index */
2246         unsigned int digits;    /* Number of digits in the lines member. */
2248         /* Drawing */
2249         struct line *curline;   /* Line currently being drawn. */
2250         enum line_type curtype; /* Attribute currently used for drawing. */
2251         unsigned long col;      /* Column when drawing. */
2252         bool has_scrolled;      /* View was scrolled. */
2254         /* Loading */
2255         const char **argv;      /* Shell command arguments. */
2256         const char *dir;        /* Directory from which to execute. */
2257         struct io io;
2258         struct io *pipe;
2259         time_t start_time;
2260         time_t update_secs;
2261 };
2263 struct view_ops {
2264         /* What type of content being displayed. Used in the title bar. */
2265         const char *type;
2266         /* Default command arguments. */
2267         const char **argv;
2268         /* Open and reads in all view content. */
2269         bool (*open)(struct view *view);
2270         /* Read one line; updates view->line. */
2271         bool (*read)(struct view *view, char *data);
2272         /* Draw one line; @lineno must be < view->height. */
2273         bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
2274         /* Depending on view handle a special requests. */
2275         enum request (*request)(struct view *view, enum request request, struct line *line);
2276         /* Search for regexp in a line. */
2277         bool (*grep)(struct view *view, struct line *line);
2278         /* Select line */
2279         void (*select)(struct view *view, struct line *line);
2280         /* Prepare view for loading */
2281         bool (*prepare)(struct view *view);
2282 };
2284 static struct view_ops blame_ops;
2285 static struct view_ops blob_ops;
2286 static struct view_ops diff_ops;
2287 static struct view_ops help_ops;
2288 static struct view_ops log_ops;
2289 static struct view_ops main_ops;
2290 static struct view_ops pager_ops;
2291 static struct view_ops stage_ops;
2292 static struct view_ops status_ops;
2293 static struct view_ops tree_ops;
2294 static struct view_ops branch_ops;
2296 #define VIEW_STR(type, name, env, ref, ops, map, git) \
2297         { type, name, #env, ref, ops, map, git }
2299 #define VIEW_(id, name, ops, git, ref) \
2300         VIEW_STR(VIEW_##id, name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
2302 static struct view views[] = {
2303         VIEW_(MAIN,   "main",   &main_ops,   TRUE,  ref_head),
2304         VIEW_(DIFF,   "diff",   &diff_ops,   TRUE,  ref_commit),
2305         VIEW_(LOG,    "log",    &log_ops,    TRUE,  ref_head),
2306         VIEW_(TREE,   "tree",   &tree_ops,   TRUE,  ref_commit),
2307         VIEW_(BLOB,   "blob",   &blob_ops,   TRUE,  ref_blob),
2308         VIEW_(BLAME,  "blame",  &blame_ops,  TRUE,  ref_commit),
2309         VIEW_(BRANCH, "branch", &branch_ops, TRUE,  ref_head),
2310         VIEW_(HELP,   "help",   &help_ops,   FALSE, ""),
2311         VIEW_(PAGER,  "pager",  &pager_ops,  FALSE, "stdin"),
2312         VIEW_(STATUS, "status", &status_ops, TRUE,  ""),
2313         VIEW_(STAGE,  "stage",  &stage_ops,  TRUE,  ""),
2314 };
2316 #define VIEW(req)       (&views[(req) - REQ_OFFSET - 1])
2318 #define foreach_view(view, i) \
2319         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2321 #define view_is_displayed(view) \
2322         (view == display[0] || view == display[1])
2324 static enum request
2325 view_request(struct view *view, enum request request)
2327         if (!view || !view->lines)
2328                 return request;
2329         return view->ops->request(view, request, &view->line[view->lineno]);
2333 /*
2334  * View drawing.
2335  */
2337 static inline void
2338 set_view_attr(struct view *view, enum line_type type)
2340         if (!view->curline->selected && view->curtype != type) {
2341                 (void) wattrset(view->win, get_line_attr(type));
2342                 wchgat(view->win, -1, 0, type, NULL);
2343                 view->curtype = type;
2344         }
2347 static int
2348 draw_chars(struct view *view, enum line_type type, const char *string,
2349            int max_len, bool use_tilde)
2351         static char out_buffer[BUFSIZ * 2];
2352         int len = 0;
2353         int col = 0;
2354         int trimmed = FALSE;
2355         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2357         if (max_len <= 0)
2358                 return 0;
2360         len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde, opt_tab_size);
2362         set_view_attr(view, type);
2363         if (len > 0) {
2364                 if (opt_iconv_out != ICONV_NONE) {
2365                         ICONV_CONST char *inbuf = (ICONV_CONST char *) string;
2366                         size_t inlen = len + 1;
2368                         char *outbuf = out_buffer;
2369                         size_t outlen = sizeof(out_buffer);
2371                         size_t ret;
2373                         ret = iconv(opt_iconv_out, &inbuf, &inlen, &outbuf, &outlen);
2374                         if (ret != (size_t) -1) {
2375                                 string = out_buffer;
2376                                 len = sizeof(out_buffer) - outlen;
2377                         }
2378                 }
2380                 waddnstr(view->win, string, len);
2381         }
2382         if (trimmed && use_tilde) {
2383                 set_view_attr(view, LINE_DELIMITER);
2384                 waddch(view->win, '~');
2385                 col++;
2386         }
2388         return col;
2391 static int
2392 draw_space(struct view *view, enum line_type type, int max, int spaces)
2394         static char space[] = "                    ";
2395         int col = 0;
2397         spaces = MIN(max, spaces);
2399         while (spaces > 0) {
2400                 int len = MIN(spaces, sizeof(space) - 1);
2402                 col += draw_chars(view, type, space, len, FALSE);
2403                 spaces -= len;
2404         }
2406         return col;
2409 static bool
2410 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
2412         char text[SIZEOF_STR];
2414         do {
2415                 size_t pos = string_expand(text, sizeof(text), string, opt_tab_size);
2417                 view->col += draw_chars(view, type, text, view->width + view->yoffset - view->col, trim);
2418                 string += pos;
2419         } while (*string && view->width + view->yoffset > view->col);
2421         return view->width + view->yoffset <= view->col;
2424 static bool
2425 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
2427         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2428         int max = view->width + view->yoffset - view->col;
2429         int i;
2431         if (max < size)
2432                 size = max;
2434         set_view_attr(view, type);
2435         /* Using waddch() instead of waddnstr() ensures that
2436          * they'll be rendered correctly for the cursor line. */
2437         for (i = skip; i < size; i++)
2438                 waddch(view->win, graphic[i]);
2440         view->col += size;
2441         if (size < max && skip <= size)
2442                 waddch(view->win, ' ');
2443         view->col++;
2445         return view->width + view->yoffset <= view->col;
2448 static bool
2449 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
2451         int max = MIN(view->width + view->yoffset - view->col, len);
2452         int col;
2454         if (text)
2455                 col = draw_chars(view, type, text, max - 1, trim);
2456         else
2457                 col = draw_space(view, type, max - 1, max - 1);
2459         view->col += col;
2460         view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
2461         return view->width + view->yoffset <= view->col;
2464 static bool
2465 draw_date(struct view *view, struct time *time)
2467         const char *date = mkdate(time, opt_date);
2468         int cols = opt_date == DATE_SHORT ? DATE_SHORT_COLS : DATE_COLS;
2470         return draw_field(view, LINE_DATE, date, cols, FALSE);
2473 static bool
2474 draw_author(struct view *view, const char *author)
2476         bool trim = opt_author_cols == 0 || opt_author_cols > 5;
2477         bool abbreviate = opt_author == AUTHOR_ABBREVIATED || !trim;
2479         if (abbreviate && author)
2480                 author = get_author_initials(author);
2482         return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2485 static bool
2486 draw_mode(struct view *view, mode_t mode)
2488         const char *str;
2490         if (S_ISDIR(mode))
2491                 str = "drwxr-xr-x";
2492         else if (S_ISLNK(mode))
2493                 str = "lrwxrwxrwx";
2494         else if (S_ISGITLINK(mode))
2495                 str = "m---------";
2496         else if (S_ISREG(mode) && mode & S_IXUSR)
2497                 str = "-rwxr-xr-x";
2498         else if (S_ISREG(mode))
2499                 str = "-rw-r--r--";
2500         else
2501                 str = "----------";
2503         return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2506 static bool
2507 draw_lineno(struct view *view, unsigned int lineno)
2509         char number[10];
2510         int digits3 = view->digits < 3 ? 3 : view->digits;
2511         int max = MIN(view->width + view->yoffset - view->col, digits3);
2512         char *text = NULL;
2513         chtype separator = opt_line_graphics ? ACS_VLINE : '|';
2515         lineno += view->offset + 1;
2516         if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2517                 static char fmt[] = "%1ld";
2519                 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2520                 if (string_format(number, fmt, lineno))
2521                         text = number;
2522         }
2523         if (text)
2524                 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2525         else
2526                 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2527         return draw_graphic(view, LINE_DEFAULT, &separator, 1);
2530 static bool
2531 draw_view_line(struct view *view, unsigned int lineno)
2533         struct line *line;
2534         bool selected = (view->offset + lineno == view->lineno);
2536         assert(view_is_displayed(view));
2538         if (view->offset + lineno >= view->lines)
2539                 return FALSE;
2541         line = &view->line[view->offset + lineno];
2543         wmove(view->win, lineno, 0);
2544         if (line->cleareol)
2545                 wclrtoeol(view->win);
2546         view->col = 0;
2547         view->curline = line;
2548         view->curtype = LINE_NONE;
2549         line->selected = FALSE;
2550         line->dirty = line->cleareol = 0;
2552         if (selected) {
2553                 set_view_attr(view, LINE_CURSOR);
2554                 line->selected = TRUE;
2555                 view->ops->select(view, line);
2556         }
2558         return view->ops->draw(view, line, lineno);
2561 static void
2562 redraw_view_dirty(struct view *view)
2564         bool dirty = FALSE;
2565         int lineno;
2567         for (lineno = 0; lineno < view->height; lineno++) {
2568                 if (view->offset + lineno >= view->lines)
2569                         break;
2570                 if (!view->line[view->offset + lineno].dirty)
2571                         continue;
2572                 dirty = TRUE;
2573                 if (!draw_view_line(view, lineno))
2574                         break;
2575         }
2577         if (!dirty)
2578                 return;
2579         wnoutrefresh(view->win);
2582 static void
2583 redraw_view_from(struct view *view, int lineno)
2585         assert(0 <= lineno && lineno < view->height);
2587         for (; lineno < view->height; lineno++) {
2588                 if (!draw_view_line(view, lineno))
2589                         break;
2590         }
2592         wnoutrefresh(view->win);
2595 static void
2596 redraw_view(struct view *view)
2598         werase(view->win);
2599         redraw_view_from(view, 0);
2603 static void
2604 update_view_title(struct view *view)
2606         char buf[SIZEOF_STR];
2607         char state[SIZEOF_STR];
2608         size_t bufpos = 0, statelen = 0;
2610         assert(view_is_displayed(view));
2612         if (view->type != VIEW_STATUS && view->lines) {
2613                 unsigned int view_lines = view->offset + view->height;
2614                 unsigned int lines = view->lines
2615                                    ? MIN(view_lines, view->lines) * 100 / view->lines
2616                                    : 0;
2618                 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2619                                    view->ops->type,
2620                                    view->lineno + 1,
2621                                    view->lines,
2622                                    lines);
2624         }
2626         if (view->pipe) {
2627                 time_t secs = time(NULL) - view->start_time;
2629                 /* Three git seconds are a long time ... */
2630                 if (secs > 2)
2631                         string_format_from(state, &statelen, " loading %lds", secs);
2632         }
2634         string_format_from(buf, &bufpos, "[%s]", view->name);
2635         if (*view->ref && bufpos < view->width) {
2636                 size_t refsize = strlen(view->ref);
2637                 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2639                 if (minsize < view->width)
2640                         refsize = view->width - minsize + 7;
2641                 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2642         }
2644         if (statelen && bufpos < view->width) {
2645                 string_format_from(buf, &bufpos, "%s", state);
2646         }
2648         if (view == display[current_view])
2649                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2650         else
2651                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2653         mvwaddnstr(view->title, 0, 0, buf, bufpos);
2654         wclrtoeol(view->title);
2655         wnoutrefresh(view->title);
2658 static int
2659 apply_step(double step, int value)
2661         if (step >= 1)
2662                 return (int) step;
2663         value *= step + 0.01;
2664         return value ? value : 1;
2667 static void
2668 resize_display(void)
2670         int offset, i;
2671         struct view *base = display[0];
2672         struct view *view = display[1] ? display[1] : display[0];
2674         /* Setup window dimensions */
2676         getmaxyx(stdscr, base->height, base->width);
2678         /* Make room for the status window. */
2679         base->height -= 1;
2681         if (view != base) {
2682                 /* Horizontal split. */
2683                 view->width   = base->width;
2684                 view->height  = apply_step(opt_scale_split_view, base->height);
2685                 view->height  = MAX(view->height, MIN_VIEW_HEIGHT);
2686                 view->height  = MIN(view->height, base->height - MIN_VIEW_HEIGHT);
2687                 base->height -= view->height;
2689                 /* Make room for the title bar. */
2690                 view->height -= 1;
2691         }
2693         /* Make room for the title bar. */
2694         base->height -= 1;
2696         offset = 0;
2698         foreach_displayed_view (view, i) {
2699                 if (!view->win) {
2700                         view->win = newwin(view->height, 0, offset, 0);
2701                         if (!view->win)
2702                                 die("Failed to create %s view", view->name);
2704                         scrollok(view->win, FALSE);
2706                         view->title = newwin(1, 0, offset + view->height, 0);
2707                         if (!view->title)
2708                                 die("Failed to create title window");
2710                 } else {
2711                         wresize(view->win, view->height, view->width);
2712                         mvwin(view->win,   offset, 0);
2713                         mvwin(view->title, offset + view->height, 0);
2714                 }
2716                 offset += view->height + 1;
2717         }
2720 static void
2721 redraw_display(bool clear)
2723         struct view *view;
2724         int i;
2726         foreach_displayed_view (view, i) {
2727                 if (clear)
2728                         wclear(view->win);
2729                 redraw_view(view);
2730                 update_view_title(view);
2731         }
2735 /*
2736  * Option management
2737  */
2739 static void
2740 toggle_enum_option_do(unsigned int *opt, const char *help,
2741                       const struct enum_map *map, size_t size)
2743         *opt = (*opt + 1) % size;
2744         redraw_display(FALSE);
2745         report("Displaying %s %s", enum_name(map[*opt]), help);
2748 #define toggle_enum_option(opt, help, map) \
2749         toggle_enum_option_do(opt, help, map, ARRAY_SIZE(map))
2751 #define toggle_date() toggle_enum_option(&opt_date, "dates", date_map)
2752 #define toggle_author() toggle_enum_option(&opt_author, "author names", author_map)
2754 static void
2755 toggle_view_option(bool *option, const char *help)
2757         *option = !*option;
2758         redraw_display(FALSE);
2759         report("%sabling %s", *option ? "En" : "Dis", help);
2762 static void
2763 open_option_menu(void)
2765         const struct menu_item menu[] = {
2766                 { '.', "line numbers", &opt_line_number },
2767                 { 'D', "date display", &opt_date },
2768                 { 'A', "author display", &opt_author },
2769                 { 'g', "revision graph display", &opt_rev_graph },
2770                 { 'F', "reference display", &opt_show_refs },
2771                 { 0 }
2772         };
2773         int selected = 0;
2775         if (prompt_menu("Toggle option", menu, &selected)) {
2776                 if (menu[selected].data == &opt_date)
2777                         toggle_date();
2778                 else if (menu[selected].data == &opt_author)
2779                         toggle_author();
2780                 else
2781                         toggle_view_option(menu[selected].data, menu[selected].text);
2782         }
2785 static void
2786 maximize_view(struct view *view)
2788         memset(display, 0, sizeof(display));
2789         current_view = 0;
2790         display[current_view] = view;
2791         resize_display();
2792         redraw_display(FALSE);
2793         report("");
2797 /*
2798  * Navigation
2799  */
2801 static bool
2802 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2804         if (lineno >= view->lines)
2805                 lineno = view->lines > 0 ? view->lines - 1 : 0;
2807         if (offset > lineno || offset + view->height <= lineno) {
2808                 unsigned long half = view->height / 2;
2810                 if (lineno > half)
2811                         offset = lineno - half;
2812                 else
2813                         offset = 0;
2814         }
2816         if (offset != view->offset || lineno != view->lineno) {
2817                 view->offset = offset;
2818                 view->lineno = lineno;
2819                 return TRUE;
2820         }
2822         return FALSE;
2825 /* Scrolling backend */
2826 static void
2827 do_scroll_view(struct view *view, int lines)
2829         bool redraw_current_line = FALSE;
2831         /* The rendering expects the new offset. */
2832         view->offset += lines;
2834         assert(0 <= view->offset && view->offset < view->lines);
2835         assert(lines);
2837         /* Move current line into the view. */
2838         if (view->lineno < view->offset) {
2839                 view->lineno = view->offset;
2840                 redraw_current_line = TRUE;
2841         } else if (view->lineno >= view->offset + view->height) {
2842                 view->lineno = view->offset + view->height - 1;
2843                 redraw_current_line = TRUE;
2844         }
2846         assert(view->offset <= view->lineno && view->lineno < view->lines);
2848         /* Redraw the whole screen if scrolling is pointless. */
2849         if (view->height < ABS(lines)) {
2850                 redraw_view(view);
2852         } else {
2853                 int line = lines > 0 ? view->height - lines : 0;
2854                 int end = line + ABS(lines);
2856                 scrollok(view->win, TRUE);
2857                 wscrl(view->win, lines);
2858                 scrollok(view->win, FALSE);
2860                 while (line < end && draw_view_line(view, line))
2861                         line++;
2863                 if (redraw_current_line)
2864                         draw_view_line(view, view->lineno - view->offset);
2865                 wnoutrefresh(view->win);
2866         }
2868         view->has_scrolled = TRUE;
2869         report("");
2872 /* Scroll frontend */
2873 static void
2874 scroll_view(struct view *view, enum request request)
2876         int lines = 1;
2878         assert(view_is_displayed(view));
2880         switch (request) {
2881         case REQ_SCROLL_LEFT:
2882                 if (view->yoffset == 0) {
2883                         report("Cannot scroll beyond the first column");
2884                         return;
2885                 }
2886                 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2887                         view->yoffset = 0;
2888                 else
2889                         view->yoffset -= apply_step(opt_hscroll, view->width);
2890                 redraw_view_from(view, 0);
2891                 report("");
2892                 return;
2893         case REQ_SCROLL_RIGHT:
2894                 view->yoffset += apply_step(opt_hscroll, view->width);
2895                 redraw_view(view);
2896                 report("");
2897                 return;
2898         case REQ_SCROLL_PAGE_DOWN:
2899                 lines = view->height;
2900         case REQ_SCROLL_LINE_DOWN:
2901                 if (view->offset + lines > view->lines)
2902                         lines = view->lines - view->offset;
2904                 if (lines == 0 || view->offset + view->height >= view->lines) {
2905                         report("Cannot scroll beyond the last line");
2906                         return;
2907                 }
2908                 break;
2910         case REQ_SCROLL_PAGE_UP:
2911                 lines = view->height;
2912         case REQ_SCROLL_LINE_UP:
2913                 if (lines > view->offset)
2914                         lines = view->offset;
2916                 if (lines == 0) {
2917                         report("Cannot scroll beyond the first line");
2918                         return;
2919                 }
2921                 lines = -lines;
2922                 break;
2924         default:
2925                 die("request %d not handled in switch", request);
2926         }
2928         do_scroll_view(view, lines);
2931 /* Cursor moving */
2932 static void
2933 move_view(struct view *view, enum request request)
2935         int scroll_steps = 0;
2936         int steps;
2938         switch (request) {
2939         case REQ_MOVE_FIRST_LINE:
2940                 steps = -view->lineno;
2941                 break;
2943         case REQ_MOVE_LAST_LINE:
2944                 steps = view->lines - view->lineno - 1;
2945                 break;
2947         case REQ_MOVE_PAGE_UP:
2948                 steps = view->height > view->lineno
2949                       ? -view->lineno : -view->height;
2950                 break;
2952         case REQ_MOVE_PAGE_DOWN:
2953                 steps = view->lineno + view->height >= view->lines
2954                       ? view->lines - view->lineno - 1 : view->height;
2955                 break;
2957         case REQ_MOVE_UP:
2958                 steps = -1;
2959                 break;
2961         case REQ_MOVE_DOWN:
2962                 steps = 1;
2963                 break;
2965         default:
2966                 die("request %d not handled in switch", request);
2967         }
2969         if (steps <= 0 && view->lineno == 0) {
2970                 report("Cannot move beyond the first line");
2971                 return;
2973         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2974                 report("Cannot move beyond the last line");
2975                 return;
2976         }
2978         /* Move the current line */
2979         view->lineno += steps;
2980         assert(0 <= view->lineno && view->lineno < view->lines);
2982         /* Check whether the view needs to be scrolled */
2983         if (view->lineno < view->offset ||
2984             view->lineno >= view->offset + view->height) {
2985                 scroll_steps = steps;
2986                 if (steps < 0 && -steps > view->offset) {
2987                         scroll_steps = -view->offset;
2989                 } else if (steps > 0) {
2990                         if (view->lineno == view->lines - 1 &&
2991                             view->lines > view->height) {
2992                                 scroll_steps = view->lines - view->offset - 1;
2993                                 if (scroll_steps >= view->height)
2994                                         scroll_steps -= view->height - 1;
2995                         }
2996                 }
2997         }
2999         if (!view_is_displayed(view)) {
3000                 view->offset += scroll_steps;
3001                 assert(0 <= view->offset && view->offset < view->lines);
3002                 view->ops->select(view, &view->line[view->lineno]);
3003                 return;
3004         }
3006         /* Repaint the old "current" line if we be scrolling */
3007         if (ABS(steps) < view->height)
3008                 draw_view_line(view, view->lineno - steps - view->offset);
3010         if (scroll_steps) {
3011                 do_scroll_view(view, scroll_steps);
3012                 return;
3013         }
3015         /* Draw the current line */
3016         draw_view_line(view, view->lineno - view->offset);
3018         wnoutrefresh(view->win);
3019         report("");
3023 /*
3024  * Searching
3025  */
3027 static void search_view(struct view *view, enum request request);
3029 static bool
3030 grep_text(struct view *view, const char *text[])
3032         regmatch_t pmatch;
3033         size_t i;
3035         for (i = 0; text[i]; i++)
3036                 if (*text[i] &&
3037                     regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
3038                         return TRUE;
3039         return FALSE;
3042 static void
3043 select_view_line(struct view *view, unsigned long lineno)
3045         unsigned long old_lineno = view->lineno;
3046         unsigned long old_offset = view->offset;
3048         if (goto_view_line(view, view->offset, lineno)) {
3049                 if (view_is_displayed(view)) {
3050                         if (old_offset != view->offset) {
3051                                 redraw_view(view);
3052                         } else {
3053                                 draw_view_line(view, old_lineno - view->offset);
3054                                 draw_view_line(view, view->lineno - view->offset);
3055                                 wnoutrefresh(view->win);
3056                         }
3057                 } else {
3058                         view->ops->select(view, &view->line[view->lineno]);
3059                 }
3060         }
3063 static void
3064 find_next(struct view *view, enum request request)
3066         unsigned long lineno = view->lineno;
3067         int direction;
3069         if (!*view->grep) {
3070                 if (!*opt_search)
3071                         report("No previous search");
3072                 else
3073                         search_view(view, request);
3074                 return;
3075         }
3077         switch (request) {
3078         case REQ_SEARCH:
3079         case REQ_FIND_NEXT:
3080                 direction = 1;
3081                 break;
3083         case REQ_SEARCH_BACK:
3084         case REQ_FIND_PREV:
3085                 direction = -1;
3086                 break;
3088         default:
3089                 return;
3090         }
3092         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
3093                 lineno += direction;
3095         /* Note, lineno is unsigned long so will wrap around in which case it
3096          * will become bigger than view->lines. */
3097         for (; lineno < view->lines; lineno += direction) {
3098                 if (view->ops->grep(view, &view->line[lineno])) {
3099                         select_view_line(view, lineno);
3100                         report("Line %ld matches '%s'", lineno + 1, view->grep);
3101                         return;
3102                 }
3103         }
3105         report("No match found for '%s'", view->grep);
3108 static void
3109 search_view(struct view *view, enum request request)
3111         int regex_err;
3113         if (view->regex) {
3114                 regfree(view->regex);
3115                 *view->grep = 0;
3116         } else {
3117                 view->regex = calloc(1, sizeof(*view->regex));
3118                 if (!view->regex)
3119                         return;
3120         }
3122         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
3123         if (regex_err != 0) {
3124                 char buf[SIZEOF_STR] = "unknown error";
3126                 regerror(regex_err, view->regex, buf, sizeof(buf));
3127                 report("Search failed: %s", buf);
3128                 return;
3129         }
3131         string_copy(view->grep, opt_search);
3133         find_next(view, request);
3136 /*
3137  * Incremental updating
3138  */
3140 static void
3141 reset_view(struct view *view)
3143         int i;
3145         for (i = 0; i < view->lines; i++)
3146                 free(view->line[i].data);
3147         free(view->line);
3149         view->p_offset = view->offset;
3150         view->p_yoffset = view->yoffset;
3151         view->p_lineno = view->lineno;
3153         view->line = NULL;
3154         view->offset = 0;
3155         view->yoffset = 0;
3156         view->lines  = 0;
3157         view->lineno = 0;
3158         view->vid[0] = 0;
3159         view->update_secs = 0;
3162 static const char *
3163 format_arg(const char *name)
3165         static struct {
3166                 const char *name;
3167                 size_t namelen;
3168                 const char *value;
3169                 const char *value_if_empty;
3170         } vars[] = {
3171 #define FORMAT_VAR(name, value, value_if_empty) \
3172         { name, STRING_SIZE(name), value, value_if_empty }
3173                 FORMAT_VAR("%(directory)",      opt_path,       ""),
3174                 FORMAT_VAR("%(file)",           opt_file,       ""),
3175                 FORMAT_VAR("%(ref)",            opt_ref,        "HEAD"),
3176                 FORMAT_VAR("%(head)",           ref_head,       ""),
3177                 FORMAT_VAR("%(commit)",         ref_commit,     ""),
3178                 FORMAT_VAR("%(blob)",           ref_blob,       ""),
3179                 FORMAT_VAR("%(branch)",         ref_branch,     ""),
3180         };
3181         int i;
3183         for (i = 0; i < ARRAY_SIZE(vars); i++)
3184                 if (!strncmp(name, vars[i].name, vars[i].namelen))
3185                         return *vars[i].value ? vars[i].value : vars[i].value_if_empty;
3187         report("Unknown replacement: `%s`", name);
3188         return NULL;
3191 static bool
3192 format_argv(const char ***dst_argv, const char *src_argv[], bool replace)
3194         char buf[SIZEOF_STR];
3195         int argc;
3197         argv_free(*dst_argv);
3199         for (argc = 0; src_argv[argc]; argc++) {
3200                 const char *arg = src_argv[argc];
3201                 size_t bufpos = 0;
3203                 if (!strcmp(arg, "%(fileargs)")) {
3204                         if (!argv_append_array(dst_argv, opt_file_args))
3205                                 break;
3206                         continue;
3208                 } else if (!strcmp(arg, "%(diffargs)")) {
3209                         if (!argv_append_array(dst_argv, opt_diff_args))
3210                                 break;
3211                         continue;
3213                 } else if (!strcmp(arg, "%(revargs)")) {
3214                         if (!argv_append_array(dst_argv, opt_rev_args))
3215                                 break;
3216                         continue;
3217                 }
3219                 while (arg) {
3220                         char *next = strstr(arg, "%(");
3221                         int len = next - arg;
3222                         const char *value;
3224                         if (!next || !replace) {
3225                                 len = strlen(arg);
3226                                 value = "";
3228                         } else {
3229                                 value = format_arg(next);
3231                                 if (!value) {
3232                                         return FALSE;
3233                                 }
3234                         }
3236                         if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
3237                                 return FALSE;
3239                         arg = next && replace ? strchr(next, ')') + 1 : NULL;
3240                 }
3242                 if (!argv_append(dst_argv, buf))
3243                         break;
3244         }
3246         return src_argv[argc] == NULL;
3249 static bool
3250 restore_view_position(struct view *view)
3252         if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
3253                 return FALSE;
3255         /* Changing the view position cancels the restoring. */
3256         /* FIXME: Changing back to the first line is not detected. */
3257         if (view->offset != 0 || view->lineno != 0) {
3258                 view->p_restore = FALSE;
3259                 return FALSE;
3260         }
3262         if (goto_view_line(view, view->p_offset, view->p_lineno) &&
3263             view_is_displayed(view))
3264                 werase(view->win);
3266         view->yoffset = view->p_yoffset;
3267         view->p_restore = FALSE;
3269         return TRUE;
3272 static void
3273 end_update(struct view *view, bool force)
3275         if (!view->pipe)
3276                 return;
3277         while (!view->ops->read(view, NULL))
3278                 if (!force)
3279                         return;
3280         if (force)
3281                 io_kill(view->pipe);
3282         io_done(view->pipe);
3283         view->pipe = NULL;
3286 static void
3287 setup_update(struct view *view, const char *vid)
3289         reset_view(view);
3290         string_copy_rev(view->vid, vid);
3291         view->pipe = &view->io;
3292         view->start_time = time(NULL);
3295 static bool
3296 prepare_io(struct view *view, const char *dir, const char *argv[], bool replace)
3298         view->dir = dir;
3299         return format_argv(&view->argv, argv, replace);
3302 static bool
3303 prepare_update(struct view *view, const char *argv[], const char *dir)
3305         if (view->pipe)
3306                 end_update(view, TRUE);
3307         return prepare_io(view, dir, argv, FALSE);
3310 static bool
3311 start_update(struct view *view, const char **argv, const char *dir)
3313         if (view->pipe)
3314                 io_done(view->pipe);
3315         return prepare_io(view, dir, argv, FALSE) &&
3316                io_run(&view->io, IO_RD, dir, view->argv);
3319 static bool
3320 prepare_update_file(struct view *view, const char *name)
3322         if (view->pipe)
3323                 end_update(view, TRUE);
3324         argv_free(view->argv);
3325         return io_open(&view->io, "%s/%s", opt_cdup[0] ? opt_cdup : ".", name);
3328 static bool
3329 begin_update(struct view *view, bool refresh)
3331         if (view->pipe)
3332                 end_update(view, TRUE);
3334         if (!refresh) {
3335                 if (view->ops->prepare) {
3336                         if (!view->ops->prepare(view))
3337                                 return FALSE;
3338                 } else if (!prepare_io(view, NULL, view->ops->argv, TRUE)) {
3339                         return FALSE;
3340                 }
3342                 /* Put the current ref_* value to the view title ref
3343                  * member. This is needed by the blob view. Most other
3344                  * views sets it automatically after loading because the
3345                  * first line is a commit line. */
3346                 string_copy_rev(view->ref, view->id);
3347         }
3349         if (view->argv && view->argv[0] &&
3350             !io_run(&view->io, IO_RD, view->dir, view->argv))
3351                 return FALSE;
3353         setup_update(view, view->id);
3355         return TRUE;
3358 static bool
3359 update_view(struct view *view)
3361         char out_buffer[BUFSIZ * 2];
3362         char *line;
3363         /* Clear the view and redraw everything since the tree sorting
3364          * might have rearranged things. */
3365         bool redraw = view->lines == 0;
3366         bool can_read = TRUE;
3368         if (!view->pipe)
3369                 return TRUE;
3371         if (!io_can_read(view->pipe)) {
3372                 if (view->lines == 0 && view_is_displayed(view)) {
3373                         time_t secs = time(NULL) - view->start_time;
3375                         if (secs > 1 && secs > view->update_secs) {
3376                                 if (view->update_secs == 0)
3377                                         redraw_view(view);
3378                                 update_view_title(view);
3379                                 view->update_secs = secs;
3380                         }
3381                 }
3382                 return TRUE;
3383         }
3385         for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
3386                 if (opt_iconv_in != ICONV_NONE) {
3387                         ICONV_CONST char *inbuf = line;
3388                         size_t inlen = strlen(line) + 1;
3390                         char *outbuf = out_buffer;
3391                         size_t outlen = sizeof(out_buffer);
3393                         size_t ret;
3395                         ret = iconv(opt_iconv_in, &inbuf, &inlen, &outbuf, &outlen);
3396                         if (ret != (size_t) -1)
3397                                 line = out_buffer;
3398                 }
3400                 if (!view->ops->read(view, line)) {
3401                         report("Allocation failure");
3402                         end_update(view, TRUE);
3403                         return FALSE;
3404                 }
3405         }
3407         {
3408                 unsigned long lines = view->lines;
3409                 int digits;
3411                 for (digits = 0; lines; digits++)
3412                         lines /= 10;
3414                 /* Keep the displayed view in sync with line number scaling. */
3415                 if (digits != view->digits) {
3416                         view->digits = digits;
3417                         if (opt_line_number || view->type == VIEW_BLAME)
3418                                 redraw = TRUE;
3419                 }
3420         }
3422         if (io_error(view->pipe)) {
3423                 report("Failed to read: %s", io_strerror(view->pipe));
3424                 end_update(view, TRUE);
3426         } else if (io_eof(view->pipe)) {
3427                 if (view_is_displayed(view))
3428                         report("");
3429                 end_update(view, FALSE);
3430         }
3432         if (restore_view_position(view))
3433                 redraw = TRUE;
3435         if (!view_is_displayed(view))
3436                 return TRUE;
3438         if (redraw)
3439                 redraw_view_from(view, 0);
3440         else
3441                 redraw_view_dirty(view);
3443         /* Update the title _after_ the redraw so that if the redraw picks up a
3444          * commit reference in view->ref it'll be available here. */
3445         update_view_title(view);
3446         return TRUE;
3449 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
3451 static struct line *
3452 add_line_data(struct view *view, void *data, enum line_type type)
3454         struct line *line;
3456         if (!realloc_lines(&view->line, view->lines, 1))
3457                 return NULL;
3459         line = &view->line[view->lines++];
3460         memset(line, 0, sizeof(*line));
3461         line->type = type;
3462         line->data = data;
3463         line->dirty = 1;
3465         return line;
3468 static struct line *
3469 add_line_text(struct view *view, const char *text, enum line_type type)
3471         char *data = text ? strdup(text) : NULL;
3473         return data ? add_line_data(view, data, type) : NULL;
3476 static struct line *
3477 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
3479         char buf[SIZEOF_STR];
3480         va_list args;
3482         va_start(args, fmt);
3483         if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
3484                 buf[0] = 0;
3485         va_end(args);
3487         return buf[0] ? add_line_text(view, buf, type) : NULL;
3490 /*
3491  * View opening
3492  */
3494 enum open_flags {
3495         OPEN_DEFAULT = 0,       /* Use default view switching. */
3496         OPEN_SPLIT = 1,         /* Split current view. */
3497         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
3498         OPEN_REFRESH = 16,      /* Refresh view using previous command. */
3499         OPEN_PREPARED = 32,     /* Open already prepared command. */
3500 };
3502 static void
3503 open_view(struct view *prev, enum request request, enum open_flags flags)
3505         bool split = !!(flags & OPEN_SPLIT);
3506         bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
3507         bool nomaximize = !!(flags & OPEN_REFRESH);
3508         struct view *view = VIEW(request);
3509         int nviews = displayed_views();
3510         struct view *base_view = display[0];
3512         if (view == prev && nviews == 1 && !reload) {
3513                 report("Already in %s view", view->name);
3514                 return;
3515         }
3517         if (view->git_dir && !opt_git_dir[0]) {
3518                 report("The %s view is disabled in pager view", view->name);
3519                 return;
3520         }
3522         if (split) {
3523                 display[1] = view;
3524                 current_view = 1;
3525                 view->parent = prev;
3526         } else if (!nomaximize) {
3527                 /* Maximize the current view. */
3528                 memset(display, 0, sizeof(display));
3529                 current_view = 0;
3530                 display[current_view] = view;
3531         }
3533         /* No prev signals that this is the first loaded view. */
3534         if (prev && view != prev) {
3535                 view->prev = prev;
3536         }
3538         /* Resize the view when switching between split- and full-screen,
3539          * or when switching between two different full-screen views. */
3540         if (nviews != displayed_views() ||
3541             (nviews == 1 && base_view != display[0]))
3542                 resize_display();
3544         if (view->ops->open) {
3545                 if (view->pipe)
3546                         end_update(view, TRUE);
3547                 if (!view->ops->open(view)) {
3548                         report("Failed to load %s view", view->name);
3549                         return;
3550                 }
3551                 restore_view_position(view);
3553         } else if ((reload || strcmp(view->vid, view->id)) &&
3554                    !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3555                 report("Failed to load %s view", view->name);
3556                 return;
3557         }
3559         if (split && prev->lineno - prev->offset >= prev->height) {
3560                 /* Take the title line into account. */
3561                 int lines = prev->lineno - prev->offset - prev->height + 1;
3563                 /* Scroll the view that was split if the current line is
3564                  * outside the new limited view. */
3565                 do_scroll_view(prev, lines);
3566         }
3568         if (prev && view != prev && split && view_is_displayed(prev)) {
3569                 /* "Blur" the previous view. */
3570                 update_view_title(prev);
3571         }
3573         if (view->pipe && view->lines == 0) {
3574                 /* Clear the old view and let the incremental updating refill
3575                  * the screen. */
3576                 werase(view->win);
3577                 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3578                 report("");
3579         } else if (view_is_displayed(view)) {
3580                 redraw_view(view);
3581                 report("");
3582         }
3585 static void
3586 open_external_viewer(const char *argv[], const char *dir)
3588         def_prog_mode();           /* save current tty modes */
3589         endwin();                  /* restore original tty modes */
3590         io_run_fg(argv, dir);
3591         fprintf(stderr, "Press Enter to continue");
3592         getc(opt_tty);
3593         reset_prog_mode();
3594         redraw_display(TRUE);
3597 static void
3598 open_mergetool(const char *file)
3600         const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3602         open_external_viewer(mergetool_argv, opt_cdup);
3605 static void
3606 open_editor(const char *file)
3608         const char *editor_argv[] = { "vi", file, NULL };
3609         const char *editor;
3611         editor = getenv("GIT_EDITOR");
3612         if (!editor && *opt_editor)
3613                 editor = opt_editor;
3614         if (!editor)
3615                 editor = getenv("VISUAL");
3616         if (!editor)
3617                 editor = getenv("EDITOR");
3618         if (!editor)
3619                 editor = "vi";
3621         editor_argv[0] = editor;
3622         open_external_viewer(editor_argv, opt_cdup);
3625 static void
3626 open_run_request(enum request request)
3628         struct run_request *req = get_run_request(request);
3629         const char **argv = NULL;
3631         if (!req) {
3632                 report("Unknown run request");
3633                 return;
3634         }
3636         if (format_argv(&argv, req->argv, TRUE))
3637                 open_external_viewer(argv, NULL);
3638         if (argv)
3639                 argv_free(argv);
3640         free(argv);
3643 /*
3644  * User request switch noodle
3645  */
3647 static int
3648 view_driver(struct view *view, enum request request)
3650         int i;
3652         if (request == REQ_NONE)
3653                 return TRUE;
3655         if (request > REQ_NONE) {
3656                 open_run_request(request);
3657                 view_request(view, REQ_REFRESH);
3658                 return TRUE;
3659         }
3661         request = view_request(view, request);
3662         if (request == REQ_NONE)
3663                 return TRUE;
3665         switch (request) {
3666         case REQ_MOVE_UP:
3667         case REQ_MOVE_DOWN:
3668         case REQ_MOVE_PAGE_UP:
3669         case REQ_MOVE_PAGE_DOWN:
3670         case REQ_MOVE_FIRST_LINE:
3671         case REQ_MOVE_LAST_LINE:
3672                 move_view(view, request);
3673                 break;
3675         case REQ_SCROLL_LEFT:
3676         case REQ_SCROLL_RIGHT:
3677         case REQ_SCROLL_LINE_DOWN:
3678         case REQ_SCROLL_LINE_UP:
3679         case REQ_SCROLL_PAGE_DOWN:
3680         case REQ_SCROLL_PAGE_UP:
3681                 scroll_view(view, request);
3682                 break;
3684         case REQ_VIEW_BLAME:
3685                 if (!opt_file[0]) {
3686                         report("No file chosen, press %s to open tree view",
3687                                get_key(view->keymap, REQ_VIEW_TREE));
3688                         break;
3689                 }
3690                 open_view(view, request, OPEN_DEFAULT);
3691                 break;
3693         case REQ_VIEW_BLOB:
3694                 if (!ref_blob[0]) {
3695                         report("No file chosen, press %s to open tree view",
3696                                get_key(view->keymap, REQ_VIEW_TREE));
3697                         break;
3698                 }
3699                 open_view(view, request, OPEN_DEFAULT);
3700                 break;
3702         case REQ_VIEW_PAGER:
3703                 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3704                         report("No pager content, press %s to run command from prompt",
3705                                get_key(view->keymap, REQ_PROMPT));
3706                         break;
3707                 }
3708                 open_view(view, request, OPEN_DEFAULT);
3709                 break;
3711         case REQ_VIEW_STAGE:
3712                 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3713                         report("No stage content, press %s to open the status view and choose file",
3714                                get_key(view->keymap, REQ_VIEW_STATUS));
3715                         break;
3716                 }
3717                 open_view(view, request, OPEN_DEFAULT);
3718                 break;
3720         case REQ_VIEW_STATUS:
3721                 if (opt_is_inside_work_tree == FALSE) {
3722                         report("The status view requires a working tree");
3723                         break;
3724                 }
3725                 open_view(view, request, OPEN_DEFAULT);
3726                 break;
3728         case REQ_VIEW_MAIN:
3729         case REQ_VIEW_DIFF:
3730         case REQ_VIEW_LOG:
3731         case REQ_VIEW_TREE:
3732         case REQ_VIEW_HELP:
3733         case REQ_VIEW_BRANCH:
3734                 open_view(view, request, OPEN_DEFAULT);
3735                 break;
3737         case REQ_NEXT:
3738         case REQ_PREVIOUS:
3739                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3741                 if (view->parent) {
3742                         int line;
3744                         view = view->parent;
3745                         line = view->lineno;
3746                         move_view(view, request);
3747                         if (view_is_displayed(view))
3748                                 update_view_title(view);
3749                         if (line != view->lineno)
3750                                 view_request(view, REQ_ENTER);
3751                 } else {
3752                         move_view(view, request);
3753                 }
3754                 break;
3756         case REQ_VIEW_NEXT:
3757         {
3758                 int nviews = displayed_views();
3759                 int next_view = (current_view + 1) % nviews;
3761                 if (next_view == current_view) {
3762                         report("Only one view is displayed");
3763                         break;
3764                 }
3766                 current_view = next_view;
3767                 /* Blur out the title of the previous view. */
3768                 update_view_title(view);
3769                 report("");
3770                 break;
3771         }
3772         case REQ_REFRESH:
3773                 report("Refreshing is not yet supported for the %s view", view->name);
3774                 break;
3776         case REQ_MAXIMIZE:
3777                 if (displayed_views() == 2)
3778                         maximize_view(view);
3779                 break;
3781         case REQ_OPTIONS:
3782                 open_option_menu();
3783                 break;
3785         case REQ_TOGGLE_LINENO:
3786                 toggle_view_option(&opt_line_number, "line numbers");
3787                 break;
3789         case REQ_TOGGLE_DATE:
3790                 toggle_date();
3791                 break;
3793         case REQ_TOGGLE_AUTHOR:
3794                 toggle_author();
3795                 break;
3797         case REQ_TOGGLE_REV_GRAPH:
3798                 toggle_view_option(&opt_rev_graph, "revision graph display");
3799                 break;
3801         case REQ_TOGGLE_REFS:
3802                 toggle_view_option(&opt_show_refs, "reference display");
3803                 break;
3805         case REQ_TOGGLE_SORT_FIELD:
3806         case REQ_TOGGLE_SORT_ORDER:
3807                 report("Sorting is not yet supported for the %s view", view->name);
3808                 break;
3810         case REQ_SEARCH:
3811         case REQ_SEARCH_BACK:
3812                 search_view(view, request);
3813                 break;
3815         case REQ_FIND_NEXT:
3816         case REQ_FIND_PREV:
3817                 find_next(view, request);
3818                 break;
3820         case REQ_STOP_LOADING:
3821                 foreach_view(view, i) {
3822                         if (view->pipe)
3823                                 report("Stopped loading the %s view", view->name),
3824                         end_update(view, TRUE);
3825                 }
3826                 break;
3828         case REQ_SHOW_VERSION:
3829                 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3830                 return TRUE;
3832         case REQ_SCREEN_REDRAW:
3833                 redraw_display(TRUE);
3834                 break;
3836         case REQ_EDIT:
3837                 report("Nothing to edit");
3838                 break;
3840         case REQ_ENTER:
3841                 report("Nothing to enter");
3842                 break;
3844         case REQ_VIEW_CLOSE:
3845                 /* XXX: Mark closed views by letting view->prev point to the
3846                  * view itself. Parents to closed view should never be
3847                  * followed. */
3848                 if (view->prev && view->prev != view) {
3849                         maximize_view(view->prev);
3850                         view->prev = view;
3851                         break;
3852                 }
3853                 /* Fall-through */
3854         case REQ_QUIT:
3855                 return FALSE;
3857         default:
3858                 report("Unknown key, press %s for help",
3859                        get_key(view->keymap, REQ_VIEW_HELP));
3860                 return TRUE;
3861         }
3863         return TRUE;
3867 /*
3868  * View backend utilities
3869  */
3871 enum sort_field {
3872         ORDERBY_NAME,
3873         ORDERBY_DATE,
3874         ORDERBY_AUTHOR,
3875 };
3877 struct sort_state {
3878         const enum sort_field *fields;
3879         size_t size, current;
3880         bool reverse;
3881 };
3883 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3884 #define get_sort_field(state) ((state).fields[(state).current])
3885 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3887 static void
3888 sort_view(struct view *view, enum request request, struct sort_state *state,
3889           int (*compare)(const void *, const void *))
3891         switch (request) {
3892         case REQ_TOGGLE_SORT_FIELD:
3893                 state->current = (state->current + 1) % state->size;
3894                 break;
3896         case REQ_TOGGLE_SORT_ORDER:
3897                 state->reverse = !state->reverse;
3898                 break;
3899         default:
3900                 die("Not a sort request");
3901         }
3903         qsort(view->line, view->lines, sizeof(*view->line), compare);
3904         redraw_view(view);
3907 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3909 /* Small author cache to reduce memory consumption. It uses binary
3910  * search to lookup or find place to position new entries. No entries
3911  * are ever freed. */
3912 static const char *
3913 get_author(const char *name)
3915         static const char **authors;
3916         static size_t authors_size;
3917         int from = 0, to = authors_size - 1;
3919         while (from <= to) {
3920                 size_t pos = (to + from) / 2;
3921                 int cmp = strcmp(name, authors[pos]);
3923                 if (!cmp)
3924                         return authors[pos];
3926                 if (cmp < 0)
3927                         to = pos - 1;
3928                 else
3929                         from = pos + 1;
3930         }
3932         if (!realloc_authors(&authors, authors_size, 1))
3933                 return NULL;
3934         name = strdup(name);
3935         if (!name)
3936                 return NULL;
3938         memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3939         authors[from] = name;
3940         authors_size++;
3942         return name;
3945 static void
3946 parse_timesec(struct time *time, const char *sec)
3948         time->sec = (time_t) atol(sec);
3951 static void
3952 parse_timezone(struct time *time, const char *zone)
3954         long tz;
3956         tz  = ('0' - zone[1]) * 60 * 60 * 10;
3957         tz += ('0' - zone[2]) * 60 * 60;
3958         tz += ('0' - zone[3]) * 60 * 10;
3959         tz += ('0' - zone[4]) * 60;
3961         if (zone[0] == '-')
3962                 tz = -tz;
3964         time->tz = tz;
3965         time->sec -= tz;
3968 /* Parse author lines where the name may be empty:
3969  *      author  <email@address.tld> 1138474660 +0100
3970  */
3971 static void
3972 parse_author_line(char *ident, const char **author, struct time *time)
3974         char *nameend = strchr(ident, '<');
3975         char *emailend = strchr(ident, '>');
3977         if (nameend && emailend)
3978                 *nameend = *emailend = 0;
3979         ident = chomp_string(ident);
3980         if (!*ident) {
3981                 if (nameend)
3982                         ident = chomp_string(nameend + 1);
3983                 if (!*ident)
3984                         ident = "Unknown";
3985         }
3987         *author = get_author(ident);
3989         /* Parse epoch and timezone */
3990         if (emailend && emailend[1] == ' ') {
3991                 char *secs = emailend + 2;
3992                 char *zone = strchr(secs, ' ');
3994                 parse_timesec(time, secs);
3996                 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
3997                         parse_timezone(time, zone + 1);
3998         }
4001 /*
4002  * Pager backend
4003  */
4005 static bool
4006 pager_draw(struct view *view, struct line *line, unsigned int lineno)
4008         if (opt_line_number && draw_lineno(view, lineno))
4009                 return TRUE;
4011         draw_text(view, line->type, line->data, TRUE);
4012         return TRUE;
4015 static bool
4016 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
4018         const char *describe_argv[] = { "git", "describe", commit_id, NULL };
4019         char ref[SIZEOF_STR];
4021         if (!io_run_buf(describe_argv, ref, sizeof(ref)) || !*ref)
4022                 return TRUE;
4024         /* This is the only fatal call, since it can "corrupt" the buffer. */
4025         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
4026                 return FALSE;
4028         return TRUE;
4031 static void
4032 add_pager_refs(struct view *view, struct line *line)
4034         char buf[SIZEOF_STR];
4035         char *commit_id = (char *)line->data + STRING_SIZE("commit ");
4036         struct ref_list *list;
4037         size_t bufpos = 0, i;
4038         const char *sep = "Refs: ";
4039         bool is_tag = FALSE;
4041         assert(line->type == LINE_COMMIT);
4043         list = get_ref_list(commit_id);
4044         if (!list) {
4045                 if (view->type == VIEW_DIFF)
4046                         goto try_add_describe_ref;
4047                 return;
4048         }
4050         for (i = 0; i < list->size; i++) {
4051                 struct ref *ref = list->refs[i];
4052                 const char *fmt = ref->tag    ? "%s[%s]" :
4053                                   ref->remote ? "%s<%s>" : "%s%s";
4055                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
4056                         return;
4057                 sep = ", ";
4058                 if (ref->tag)
4059                         is_tag = TRUE;
4060         }
4062         if (!is_tag && view->type == VIEW_DIFF) {
4063 try_add_describe_ref:
4064                 /* Add <tag>-g<commit_id> "fake" reference. */
4065                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
4066                         return;
4067         }
4069         if (bufpos == 0)
4070                 return;
4072         add_line_text(view, buf, LINE_PP_REFS);
4075 static bool
4076 pager_read(struct view *view, char *data)
4078         struct line *line;
4080         if (!data)
4081                 return TRUE;
4083         line = add_line_text(view, data, get_line_type(data));
4084         if (!line)
4085                 return FALSE;
4087         if (line->type == LINE_COMMIT &&
4088             (view->type == VIEW_DIFF ||
4089              view->type == VIEW_LOG))
4090                 add_pager_refs(view, line);
4092         return TRUE;
4095 static enum request
4096 pager_request(struct view *view, enum request request, struct line *line)
4098         int split = 0;
4100         if (request != REQ_ENTER)
4101                 return request;
4103         if (line->type == LINE_COMMIT &&
4104            (view->type == VIEW_LOG ||
4105             view->type == VIEW_PAGER)) {
4106                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
4107                 split = 1;
4108         }
4110         /* Always scroll the view even if it was split. That way
4111          * you can use Enter to scroll through the log view and
4112          * split open each commit diff. */
4113         scroll_view(view, REQ_SCROLL_LINE_DOWN);
4115         /* FIXME: A minor workaround. Scrolling the view will call report("")
4116          * but if we are scrolling a non-current view this won't properly
4117          * update the view title. */
4118         if (split)
4119                 update_view_title(view);
4121         return REQ_NONE;
4124 static bool
4125 pager_grep(struct view *view, struct line *line)
4127         const char *text[] = { line->data, NULL };
4129         return grep_text(view, text);
4132 static void
4133 pager_select(struct view *view, struct line *line)
4135         if (line->type == LINE_COMMIT) {
4136                 char *text = (char *)line->data + STRING_SIZE("commit ");
4138                 if (view->type != VIEW_PAGER)
4139                         string_copy_rev(view->ref, text);
4140                 string_copy_rev(ref_commit, text);
4141         }
4144 static struct view_ops pager_ops = {
4145         "line",
4146         NULL,
4147         NULL,
4148         pager_read,
4149         pager_draw,
4150         pager_request,
4151         pager_grep,
4152         pager_select,
4153 };
4155 static const char *log_argv[SIZEOF_ARG] = {
4156         "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
4157 };
4159 static enum request
4160 log_request(struct view *view, enum request request, struct line *line)
4162         switch (request) {
4163         case REQ_REFRESH:
4164                 load_refs();
4165                 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
4166                 return REQ_NONE;
4167         default:
4168                 return pager_request(view, request, line);
4169         }
4172 static struct view_ops log_ops = {
4173         "line",
4174         log_argv,
4175         NULL,
4176         pager_read,
4177         pager_draw,
4178         log_request,
4179         pager_grep,
4180         pager_select,
4181 };
4183 static const char *diff_argv[SIZEOF_ARG] = {
4184         "git", "show", "--pretty=fuller", "--no-color", "--root",
4185                 "--patch-with-stat", "--find-copies-harder", "-C",
4186                 "%(diffargs)", "%(commit)", "--", "%(fileargs)", NULL
4187 };
4189 static struct view_ops diff_ops = {
4190         "line",
4191         diff_argv,
4192         NULL,
4193         pager_read,
4194         pager_draw,
4195         pager_request,
4196         pager_grep,
4197         pager_select,
4198 };
4200 /*
4201  * Help backend
4202  */
4204 static bool help_keymap_hidden[ARRAY_SIZE(keymap_table)];
4206 static bool
4207 help_open_keymap_title(struct view *view, enum keymap keymap)
4209         struct line *line;
4211         line = add_line_format(view, LINE_HELP_KEYMAP, "[%c] %s bindings",
4212                                help_keymap_hidden[keymap] ? '+' : '-',
4213                                enum_name(keymap_table[keymap]));
4214         if (line)
4215                 line->other = keymap;
4217         return help_keymap_hidden[keymap];
4220 static void
4221 help_open_keymap(struct view *view, enum keymap keymap)
4223         const char *group = NULL;
4224         char buf[SIZEOF_STR];
4225         size_t bufpos;
4226         bool add_title = TRUE;
4227         int i;
4229         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
4230                 const char *key = NULL;
4232                 if (req_info[i].request == REQ_NONE)
4233                         continue;
4235                 if (!req_info[i].request) {
4236                         group = req_info[i].help;
4237                         continue;
4238                 }
4240                 key = get_keys(keymap, req_info[i].request, TRUE);
4241                 if (!key || !*key)
4242                         continue;
4244                 if (add_title && help_open_keymap_title(view, keymap))
4245                         return;
4246                 add_title = FALSE;
4248                 if (group) {
4249                         add_line_text(view, group, LINE_HELP_GROUP);
4250                         group = NULL;
4251                 }
4253                 add_line_format(view, LINE_DEFAULT, "    %-25s %-20s %s", key,
4254                                 enum_name(req_info[i]), req_info[i].help);
4255         }
4257         group = "External commands:";
4259         for (i = 0; i < run_requests; i++) {
4260                 struct run_request *req = get_run_request(REQ_NONE + i + 1);
4261                 const char *key;
4262                 int argc;
4264                 if (!req || req->keymap != keymap)
4265                         continue;
4267                 key = get_key_name(req->key);
4268                 if (!*key)
4269                         key = "(no key defined)";
4271                 if (add_title && help_open_keymap_title(view, keymap))
4272                         return;
4273                 if (group) {
4274                         add_line_text(view, group, LINE_HELP_GROUP);
4275                         group = NULL;
4276                 }
4278                 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
4279                         if (!string_format_from(buf, &bufpos, "%s%s",
4280                                                 argc ? " " : "", req->argv[argc]))
4281                                 return;
4283                 add_line_format(view, LINE_DEFAULT, "    %-25s `%s`", key, buf);
4284         }
4287 static bool
4288 help_open(struct view *view)
4290         enum keymap keymap;
4292         reset_view(view);
4293         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
4294         add_line_text(view, "", LINE_DEFAULT);
4296         for (keymap = 0; keymap < ARRAY_SIZE(keymap_table); keymap++)
4297                 help_open_keymap(view, keymap);
4299         return TRUE;
4302 static enum request
4303 help_request(struct view *view, enum request request, struct line *line)
4305         switch (request) {
4306         case REQ_ENTER:
4307                 if (line->type == LINE_HELP_KEYMAP) {
4308                         help_keymap_hidden[line->other] =
4309                                 !help_keymap_hidden[line->other];
4310                         view->p_restore = TRUE;
4311                         open_view(view, REQ_VIEW_HELP, OPEN_REFRESH);
4312                 }
4314                 return REQ_NONE;
4315         default:
4316                 return pager_request(view, request, line);
4317         }
4320 static struct view_ops help_ops = {
4321         "line",
4322         NULL,
4323         help_open,
4324         NULL,
4325         pager_draw,
4326         help_request,
4327         pager_grep,
4328         pager_select,
4329 };
4332 /*
4333  * Tree backend
4334  */
4336 struct tree_stack_entry {
4337         struct tree_stack_entry *prev;  /* Entry below this in the stack */
4338         unsigned long lineno;           /* Line number to restore */
4339         char *name;                     /* Position of name in opt_path */
4340 };
4342 /* The top of the path stack. */
4343 static struct tree_stack_entry *tree_stack = NULL;
4344 unsigned long tree_lineno = 0;
4346 static void
4347 pop_tree_stack_entry(void)
4349         struct tree_stack_entry *entry = tree_stack;
4351         tree_lineno = entry->lineno;
4352         entry->name[0] = 0;
4353         tree_stack = entry->prev;
4354         free(entry);
4357 static void
4358 push_tree_stack_entry(const char *name, unsigned long lineno)
4360         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
4361         size_t pathlen = strlen(opt_path);
4363         if (!entry)
4364                 return;
4366         entry->prev = tree_stack;
4367         entry->name = opt_path + pathlen;
4368         tree_stack = entry;
4370         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
4371                 pop_tree_stack_entry();
4372                 return;
4373         }
4375         /* Move the current line to the first tree entry. */
4376         tree_lineno = 1;
4377         entry->lineno = lineno;
4380 /* Parse output from git-ls-tree(1):
4381  *
4382  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4383  */
4385 #define SIZEOF_TREE_ATTR \
4386         STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4388 #define SIZEOF_TREE_MODE \
4389         STRING_SIZE("100644 ")
4391 #define TREE_ID_OFFSET \
4392         STRING_SIZE("100644 blob ")
4394 struct tree_entry {
4395         char id[SIZEOF_REV];
4396         mode_t mode;
4397         struct time time;               /* Date from the author ident. */
4398         const char *author;             /* Author of the commit. */
4399         char name[1];
4400 };
4402 static const char *
4403 tree_path(const struct line *line)
4405         return ((struct tree_entry *) line->data)->name;
4408 static int
4409 tree_compare_entry(const struct line *line1, const struct line *line2)
4411         if (line1->type != line2->type)
4412                 return line1->type == LINE_TREE_DIR ? -1 : 1;
4413         return strcmp(tree_path(line1), tree_path(line2));
4416 static const enum sort_field tree_sort_fields[] = {
4417         ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4418 };
4419 static struct sort_state tree_sort_state = SORT_STATE(tree_sort_fields);
4421 static int
4422 tree_compare(const void *l1, const void *l2)
4424         const struct line *line1 = (const struct line *) l1;
4425         const struct line *line2 = (const struct line *) l2;
4426         const struct tree_entry *entry1 = ((const struct line *) l1)->data;
4427         const struct tree_entry *entry2 = ((const struct line *) l2)->data;
4429         if (line1->type == LINE_TREE_HEAD)
4430                 return -1;
4431         if (line2->type == LINE_TREE_HEAD)
4432                 return 1;
4434         switch (get_sort_field(tree_sort_state)) {
4435         case ORDERBY_DATE:
4436                 return sort_order(tree_sort_state, timecmp(&entry1->time, &entry2->time));
4438         case ORDERBY_AUTHOR:
4439                 return sort_order(tree_sort_state, strcmp(entry1->author, entry2->author));
4441         case ORDERBY_NAME:
4442         default:
4443                 return sort_order(tree_sort_state, tree_compare_entry(line1, line2));
4444         }
4448 static struct line *
4449 tree_entry(struct view *view, enum line_type type, const char *path,
4450            const char *mode, const char *id)
4452         struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
4453         struct line *line = entry ? add_line_data(view, entry, type) : NULL;
4455         if (!entry || !line) {
4456                 free(entry);
4457                 return NULL;
4458         }
4460         strncpy(entry->name, path, strlen(path));
4461         if (mode)
4462                 entry->mode = strtoul(mode, NULL, 8);
4463         if (id)
4464                 string_copy_rev(entry->id, id);
4466         return line;
4469 static bool
4470 tree_read_date(struct view *view, char *text, bool *read_date)
4472         static const char *author_name;
4473         static struct time author_time;
4475         if (!text && *read_date) {
4476                 *read_date = FALSE;
4477                 return TRUE;
4479         } else if (!text) {
4480                 char *path = *opt_path ? opt_path : ".";
4481                 /* Find next entry to process */
4482                 const char *log_file[] = {
4483                         "git", "log", "--no-color", "--pretty=raw",
4484                                 "--cc", "--raw", view->id, "--", path, NULL
4485                 };
4487                 if (!view->lines) {
4488                         tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
4489                         report("Tree is empty");
4490                         return TRUE;
4491                 }
4493                 if (!start_update(view, log_file, opt_cdup)) {
4494                         report("Failed to load tree data");
4495                         return TRUE;
4496                 }
4498                 *read_date = TRUE;
4499                 return FALSE;
4501         } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
4502                 parse_author_line(text + STRING_SIZE("author "),
4503                                   &author_name, &author_time);
4505         } else if (*text == ':') {
4506                 char *pos;
4507                 size_t annotated = 1;
4508                 size_t i;
4510                 pos = strchr(text, '\t');
4511                 if (!pos)
4512                         return TRUE;
4513                 text = pos + 1;
4514                 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
4515                         text += strlen(opt_path);
4516                 pos = strchr(text, '/');
4517                 if (pos)
4518                         *pos = 0;
4520                 for (i = 1; i < view->lines; i++) {
4521                         struct line *line = &view->line[i];
4522                         struct tree_entry *entry = line->data;
4524                         annotated += !!entry->author;
4525                         if (entry->author || strcmp(entry->name, text))
4526                                 continue;
4528                         entry->author = author_name;
4529                         entry->time = author_time;
4530                         line->dirty = 1;
4531                         break;
4532                 }
4534                 if (annotated == view->lines)
4535                         io_kill(view->pipe);
4536         }
4537         return TRUE;
4540 static bool
4541 tree_read(struct view *view, char *text)
4543         static bool read_date = FALSE;
4544         struct tree_entry *data;
4545         struct line *entry, *line;
4546         enum line_type type;
4547         size_t textlen = text ? strlen(text) : 0;
4548         char *path = text + SIZEOF_TREE_ATTR;
4550         if (read_date || !text)
4551                 return tree_read_date(view, text, &read_date);
4553         if (textlen <= SIZEOF_TREE_ATTR)
4554                 return FALSE;
4555         if (view->lines == 0 &&
4556             !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
4557                 return FALSE;
4559         /* Strip the path part ... */
4560         if (*opt_path) {
4561                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
4562                 size_t striplen = strlen(opt_path);
4564                 if (pathlen > striplen)
4565                         memmove(path, path + striplen,
4566                                 pathlen - striplen + 1);
4568                 /* Insert "link" to parent directory. */
4569                 if (view->lines == 1 &&
4570                     !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
4571                         return FALSE;
4572         }
4574         type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
4575         entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
4576         if (!entry)
4577                 return FALSE;
4578         data = entry->data;
4580         /* Skip "Directory ..." and ".." line. */
4581         for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
4582                 if (tree_compare_entry(line, entry) <= 0)
4583                         continue;
4585                 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4587                 line->data = data;
4588                 line->type = type;
4589                 for (; line <= entry; line++)
4590                         line->dirty = line->cleareol = 1;
4591                 return TRUE;
4592         }
4594         if (tree_lineno > view->lineno) {
4595                 view->lineno = tree_lineno;
4596                 tree_lineno = 0;
4597         }
4599         return TRUE;
4602 static bool
4603 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4605         struct tree_entry *entry = line->data;
4607         if (line->type == LINE_TREE_HEAD) {
4608                 if (draw_text(view, line->type, "Directory path /", TRUE))
4609                         return TRUE;
4610         } else {
4611                 if (draw_mode(view, entry->mode))
4612                         return TRUE;
4614                 if (opt_author && draw_author(view, entry->author))
4615                         return TRUE;
4617                 if (opt_date && draw_date(view, &entry->time))
4618                         return TRUE;
4619         }
4620         if (draw_text(view, line->type, entry->name, TRUE))
4621                 return TRUE;
4622         return TRUE;
4625 static void
4626 open_blob_editor(const char *id)
4628         const char *blob_argv[] = { "git", "cat-file", "blob", id, NULL };
4629         char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4630         int fd = mkstemp(file);
4632         if (fd == -1)
4633                 report("Failed to create temporary file");
4634         else if (!io_run_append(blob_argv, fd))
4635                 report("Failed to save blob data to file");
4636         else
4637                 open_editor(file);
4638         if (fd != -1)
4639                 unlink(file);
4642 static enum request
4643 tree_request(struct view *view, enum request request, struct line *line)
4645         enum open_flags flags;
4646         struct tree_entry *entry = line->data;
4648         switch (request) {
4649         case REQ_VIEW_BLAME:
4650                 if (line->type != LINE_TREE_FILE) {
4651                         report("Blame only supported for files");
4652                         return REQ_NONE;
4653                 }
4655                 string_copy(opt_ref, view->vid);
4656                 return request;
4658         case REQ_EDIT:
4659                 if (line->type != LINE_TREE_FILE) {
4660                         report("Edit only supported for files");
4661                 } else if (!is_head_commit(view->vid)) {
4662                         open_blob_editor(entry->id);
4663                 } else {
4664                         open_editor(opt_file);
4665                 }
4666                 return REQ_NONE;
4668         case REQ_TOGGLE_SORT_FIELD:
4669         case REQ_TOGGLE_SORT_ORDER:
4670                 sort_view(view, request, &tree_sort_state, tree_compare);
4671                 return REQ_NONE;
4673         case REQ_PARENT:
4674                 if (!*opt_path) {
4675                         /* quit view if at top of tree */
4676                         return REQ_VIEW_CLOSE;
4677                 }
4678                 /* fake 'cd  ..' */
4679                 line = &view->line[1];
4680                 break;
4682         case REQ_ENTER:
4683                 break;
4685         default:
4686                 return request;
4687         }
4689         /* Cleanup the stack if the tree view is at a different tree. */
4690         while (!*opt_path && tree_stack)
4691                 pop_tree_stack_entry();
4693         switch (line->type) {
4694         case LINE_TREE_DIR:
4695                 /* Depending on whether it is a subdirectory or parent link
4696                  * mangle the path buffer. */
4697                 if (line == &view->line[1] && *opt_path) {
4698                         pop_tree_stack_entry();
4700                 } else {
4701                         const char *basename = tree_path(line);
4703                         push_tree_stack_entry(basename, view->lineno);
4704                 }
4706                 /* Trees and subtrees share the same ID, so they are not not
4707                  * unique like blobs. */
4708                 flags = OPEN_RELOAD;
4709                 request = REQ_VIEW_TREE;
4710                 break;
4712         case LINE_TREE_FILE:
4713                 flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
4714                 request = REQ_VIEW_BLOB;
4715                 break;
4717         default:
4718                 return REQ_NONE;
4719         }
4721         open_view(view, request, flags);
4722         if (request == REQ_VIEW_TREE)
4723                 view->lineno = tree_lineno;
4725         return REQ_NONE;
4728 static bool
4729 tree_grep(struct view *view, struct line *line)
4731         struct tree_entry *entry = line->data;
4732         const char *text[] = {
4733                 entry->name,
4734                 opt_author ? entry->author : "",
4735                 mkdate(&entry->time, opt_date),
4736                 NULL
4737         };
4739         return grep_text(view, text);
4742 static void
4743 tree_select(struct view *view, struct line *line)
4745         struct tree_entry *entry = line->data;
4747         if (line->type == LINE_TREE_FILE) {
4748                 string_copy_rev(ref_blob, entry->id);
4749                 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4751         } else if (line->type != LINE_TREE_DIR) {
4752                 return;
4753         }
4755         string_copy_rev(view->ref, entry->id);
4758 static bool
4759 tree_prepare(struct view *view)
4761         if (view->lines == 0 && opt_prefix[0]) {
4762                 char *pos = opt_prefix;
4764                 while (pos && *pos) {
4765                         char *end = strchr(pos, '/');
4767                         if (end)
4768                                 *end = 0;
4769                         push_tree_stack_entry(pos, 0);
4770                         pos = end;
4771                         if (end) {
4772                                 *end = '/';
4773                                 pos++;
4774                         }
4775                 }
4777         } else if (strcmp(view->vid, view->id)) {
4778                 opt_path[0] = 0;
4779         }
4781         return prepare_io(view, opt_cdup, view->ops->argv, TRUE);
4784 static const char *tree_argv[SIZEOF_ARG] = {
4785         "git", "ls-tree", "%(commit)", "%(directory)", NULL
4786 };
4788 static struct view_ops tree_ops = {
4789         "file",
4790         tree_argv,
4791         NULL,
4792         tree_read,
4793         tree_draw,
4794         tree_request,
4795         tree_grep,
4796         tree_select,
4797         tree_prepare,
4798 };
4800 static bool
4801 blob_read(struct view *view, char *line)
4803         if (!line)
4804                 return TRUE;
4805         return add_line_text(view, line, LINE_DEFAULT) != NULL;
4808 static enum request
4809 blob_request(struct view *view, enum request request, struct line *line)
4811         switch (request) {
4812         case REQ_EDIT:
4813                 open_blob_editor(view->vid);
4814                 return REQ_NONE;
4815         default:
4816                 return pager_request(view, request, line);
4817         }
4820 static const char *blob_argv[SIZEOF_ARG] = {
4821         "git", "cat-file", "blob", "%(blob)", NULL
4822 };
4824 static struct view_ops blob_ops = {
4825         "line",
4826         blob_argv,
4827         NULL,
4828         blob_read,
4829         pager_draw,
4830         blob_request,
4831         pager_grep,
4832         pager_select,
4833 };
4835 /*
4836  * Blame backend
4837  *
4838  * Loading the blame view is a two phase job:
4839  *
4840  *  1. File content is read either using opt_file from the
4841  *     filesystem or using git-cat-file.
4842  *  2. Then blame information is incrementally added by
4843  *     reading output from git-blame.
4844  */
4846 struct blame_commit {
4847         char id[SIZEOF_REV];            /* SHA1 ID. */
4848         char title[128];                /* First line of the commit message. */
4849         const char *author;             /* Author of the commit. */
4850         struct time time;               /* Date from the author ident. */
4851         char filename[128];             /* Name of file. */
4852         char parent_id[SIZEOF_REV];     /* Parent/previous SHA1 ID. */
4853         char parent_filename[128];      /* Parent/previous name of file. */
4854 };
4856 struct blame {
4857         struct blame_commit *commit;
4858         unsigned long lineno;
4859         char text[1];
4860 };
4862 static bool
4863 blame_open(struct view *view)
4865         char path[SIZEOF_STR];
4866         size_t i;
4868         if (!view->prev && *opt_prefix) {
4869                 string_copy(path, opt_file);
4870                 if (!string_format(opt_file, "%s%s", opt_prefix, path))
4871                         return FALSE;
4872         }
4874         if (*opt_ref || !io_open(&view->io, "%s%s", opt_cdup, opt_file)) {
4875                 const char *blame_cat_file_argv[] = {
4876                         "git", "cat-file", "blob", path, NULL
4877                 };
4879                 if (!string_format(path, "%s:%s", opt_ref, opt_file) ||
4880                     !start_update(view, blame_cat_file_argv, opt_cdup))
4881                         return FALSE;
4882         }
4884         /* First pass: remove multiple references to the same commit. */
4885         for (i = 0; i < view->lines; i++) {
4886                 struct blame *blame = view->line[i].data;
4888                 if (blame->commit && blame->commit->id[0])
4889                         blame->commit->id[0] = 0;
4890                 else
4891                         blame->commit = NULL;
4892         }
4894         /* Second pass: free existing references. */
4895         for (i = 0; i < view->lines; i++) {
4896                 struct blame *blame = view->line[i].data;
4898                 if (blame->commit)
4899                         free(blame->commit);
4900         }
4902         setup_update(view, opt_file);
4903         string_format(view->ref, "%s ...", opt_file);
4905         return TRUE;
4908 static struct blame_commit *
4909 get_blame_commit(struct view *view, const char *id)
4911         size_t i;
4913         for (i = 0; i < view->lines; i++) {
4914                 struct blame *blame = view->line[i].data;
4916                 if (!blame->commit)
4917                         continue;
4919                 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4920                         return blame->commit;
4921         }
4923         {
4924                 struct blame_commit *commit = calloc(1, sizeof(*commit));
4926                 if (commit)
4927                         string_ncopy(commit->id, id, SIZEOF_REV);
4928                 return commit;
4929         }
4932 static bool
4933 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4935         const char *pos = *posref;
4937         *posref = NULL;
4938         pos = strchr(pos + 1, ' ');
4939         if (!pos || !isdigit(pos[1]))
4940                 return FALSE;
4941         *number = atoi(pos + 1);
4942         if (*number < min || *number > max)
4943                 return FALSE;
4945         *posref = pos;
4946         return TRUE;
4949 static struct blame_commit *
4950 parse_blame_commit(struct view *view, const char *text, int *blamed)
4952         struct blame_commit *commit;
4953         struct blame *blame;
4954         const char *pos = text + SIZEOF_REV - 2;
4955         size_t orig_lineno = 0;
4956         size_t lineno;
4957         size_t group;
4959         if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
4960                 return NULL;
4962         if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
4963             !parse_number(&pos, &lineno, 1, view->lines) ||
4964             !parse_number(&pos, &group, 1, view->lines - lineno + 1))
4965                 return NULL;
4967         commit = get_blame_commit(view, text);
4968         if (!commit)
4969                 return NULL;
4971         *blamed += group;
4972         while (group--) {
4973                 struct line *line = &view->line[lineno + group - 1];
4975                 blame = line->data;
4976                 blame->commit = commit;
4977                 blame->lineno = orig_lineno + group - 1;
4978                 line->dirty = 1;
4979         }
4981         return commit;
4984 static bool
4985 blame_read_file(struct view *view, const char *line, bool *read_file)
4987         if (!line) {
4988                 const char *blame_argv[] = {
4989                         "git", "blame", "--incremental",
4990                                 *opt_ref ? opt_ref : "--incremental", "--", opt_file, NULL
4991                 };
4993                 if (view->lines == 0 && !view->prev)
4994                         die("No blame exist for %s", view->vid);
4996                 if (view->lines == 0 || !start_update(view, blame_argv, opt_cdup)) {
4997                         report("Failed to load blame data");
4998                         return TRUE;
4999                 }
5001                 *read_file = FALSE;
5002                 return FALSE;
5004         } else {
5005                 size_t linelen = strlen(line);
5006                 struct blame *blame = malloc(sizeof(*blame) + linelen);
5008                 if (!blame)
5009                         return FALSE;
5011                 blame->commit = NULL;
5012                 strncpy(blame->text, line, linelen);
5013                 blame->text[linelen] = 0;
5014                 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
5015         }
5018 static bool
5019 match_blame_header(const char *name, char **line)
5021         size_t namelen = strlen(name);
5022         bool matched = !strncmp(name, *line, namelen);
5024         if (matched)
5025                 *line += namelen;
5027         return matched;
5030 static bool
5031 blame_read(struct view *view, char *line)
5033         static struct blame_commit *commit = NULL;
5034         static int blamed = 0;
5035         static bool read_file = TRUE;
5037         if (read_file)
5038                 return blame_read_file(view, line, &read_file);
5040         if (!line) {
5041                 /* Reset all! */
5042                 commit = NULL;
5043                 blamed = 0;
5044                 read_file = TRUE;
5045                 string_format(view->ref, "%s", view->vid);
5046                 if (view_is_displayed(view)) {
5047                         update_view_title(view);
5048                         redraw_view_from(view, 0);
5049                 }
5050                 return TRUE;
5051         }
5053         if (!commit) {
5054                 commit = parse_blame_commit(view, line, &blamed);
5055                 string_format(view->ref, "%s %2d%%", view->vid,
5056                               view->lines ? blamed * 100 / view->lines : 0);
5058         } else if (match_blame_header("author ", &line)) {
5059                 commit->author = get_author(line);
5061         } else if (match_blame_header("author-time ", &line)) {
5062                 parse_timesec(&commit->time, line);
5064         } else if (match_blame_header("author-tz ", &line)) {
5065                 parse_timezone(&commit->time, line);
5067         } else if (match_blame_header("summary ", &line)) {
5068                 string_ncopy(commit->title, line, strlen(line));
5070         } else if (match_blame_header("previous ", &line)) {
5071                 if (strlen(line) <= SIZEOF_REV)
5072                         return FALSE;
5073                 string_copy_rev(commit->parent_id, line);
5074                 line += SIZEOF_REV;
5075                 string_ncopy(commit->parent_filename, line, strlen(line));
5077         } else if (match_blame_header("filename ", &line)) {
5078                 string_ncopy(commit->filename, line, strlen(line));
5079                 commit = NULL;
5080         }
5082         return TRUE;
5085 static bool
5086 blame_draw(struct view *view, struct line *line, unsigned int lineno)
5088         struct blame *blame = line->data;
5089         struct time *time = NULL;
5090         const char *id = NULL, *author = NULL;
5092         if (blame->commit && *blame->commit->filename) {
5093                 id = blame->commit->id;
5094                 author = blame->commit->author;
5095                 time = &blame->commit->time;
5096         }
5098         if (opt_date && draw_date(view, time))
5099                 return TRUE;
5101         if (opt_author && draw_author(view, author))
5102                 return TRUE;
5104         if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
5105                 return TRUE;
5107         if (draw_lineno(view, lineno))
5108                 return TRUE;
5110         draw_text(view, LINE_DEFAULT, blame->text, TRUE);
5111         return TRUE;
5114 static bool
5115 check_blame_commit(struct blame *blame, bool check_null_id)
5117         if (!blame->commit)
5118                 report("Commit data not loaded yet");
5119         else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
5120                 report("No commit exist for the selected line");
5121         else
5122                 return TRUE;
5123         return FALSE;
5126 static void
5127 setup_blame_parent_line(struct view *view, struct blame *blame)
5129         char from[SIZEOF_REF + SIZEOF_STR];
5130         char to[SIZEOF_REF + SIZEOF_STR];
5131         const char *diff_tree_argv[] = {
5132                 "git", "diff", "--no-textconv", "--no-extdiff", "--no-color",
5133                         "-U0", from, to, "--", NULL
5134         };
5135         struct io io;
5136         int parent_lineno = -1;
5137         int blamed_lineno = -1;
5138         char *line;
5140         if (!string_format(from, "%s:%s", opt_ref, opt_file) ||
5141             !string_format(to, "%s:%s", blame->commit->id, blame->commit->filename) ||
5142             !io_run(&io, IO_RD, NULL, diff_tree_argv))
5143                 return;
5145         while ((line = io_get(&io, '\n', TRUE))) {
5146                 if (*line == '@') {
5147                         char *pos = strchr(line, '+');
5149                         parent_lineno = atoi(line + 4);
5150                         if (pos)
5151                                 blamed_lineno = atoi(pos + 1);
5153                 } else if (*line == '+' && parent_lineno != -1) {
5154                         if (blame->lineno == blamed_lineno - 1 &&
5155                             !strcmp(blame->text, line + 1)) {
5156                                 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
5157                                 break;
5158                         }
5159                         blamed_lineno++;
5160                 }
5161         }
5163         io_done(&io);
5166 static enum request
5167 blame_request(struct view *view, enum request request, struct line *line)
5169         enum open_flags flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
5170         struct blame *blame = line->data;
5172         switch (request) {
5173         case REQ_VIEW_BLAME:
5174                 if (check_blame_commit(blame, TRUE)) {
5175                         string_copy(opt_ref, blame->commit->id);
5176                         string_copy(opt_file, blame->commit->filename);
5177                         if (blame->lineno)
5178                                 view->lineno = blame->lineno;
5179                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5180                 }
5181                 break;
5183         case REQ_PARENT:
5184                 if (!check_blame_commit(blame, TRUE))
5185                         break;
5186                 if (!*blame->commit->parent_id) {
5187                         report("The selected commit has no parents");
5188                 } else {
5189                         string_copy_rev(opt_ref, blame->commit->parent_id);
5190                         string_copy(opt_file, blame->commit->parent_filename);
5191                         setup_blame_parent_line(view, blame);
5192                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5193                 }
5194                 break;
5196         case REQ_ENTER:
5197                 if (!check_blame_commit(blame, FALSE))
5198                         break;
5200                 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
5201                     !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
5202                         break;
5204                 if (!strcmp(blame->commit->id, NULL_ID)) {
5205                         struct view *diff = VIEW(REQ_VIEW_DIFF);
5206                         const char *diff_index_argv[] = {
5207                                 "git", "diff-index", "--root", "--patch-with-stat",
5208                                         "-C", "-M", "HEAD", "--", view->vid, NULL
5209                         };
5211                         if (!*blame->commit->parent_id) {
5212                                 diff_index_argv[1] = "diff";
5213                                 diff_index_argv[2] = "--no-color";
5214                                 diff_index_argv[6] = "--";
5215                                 diff_index_argv[7] = "/dev/null";
5216                         }
5218                         if (!prepare_update(diff, diff_index_argv, NULL)) {
5219                                 report("Failed to allocate diff command");
5220                                 break;
5221                         }
5222                         flags |= OPEN_PREPARED;
5223                 }
5225                 open_view(view, REQ_VIEW_DIFF, flags);
5226                 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
5227                         string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
5228                 break;
5230         default:
5231                 return request;
5232         }
5234         return REQ_NONE;
5237 static bool
5238 blame_grep(struct view *view, struct line *line)
5240         struct blame *blame = line->data;
5241         struct blame_commit *commit = blame->commit;
5242         const char *text[] = {
5243                 blame->text,
5244                 commit ? commit->title : "",
5245                 commit ? commit->id : "",
5246                 commit && opt_author ? commit->author : "",
5247                 commit ? mkdate(&commit->time, opt_date) : "",
5248                 NULL
5249         };
5251         return grep_text(view, text);
5254 static void
5255 blame_select(struct view *view, struct line *line)
5257         struct blame *blame = line->data;
5258         struct blame_commit *commit = blame->commit;
5260         if (!commit)
5261                 return;
5263         if (!strcmp(commit->id, NULL_ID))
5264                 string_ncopy(ref_commit, "HEAD", 4);
5265         else
5266                 string_copy_rev(ref_commit, commit->id);
5269 static struct view_ops blame_ops = {
5270         "line",
5271         NULL,
5272         blame_open,
5273         blame_read,
5274         blame_draw,
5275         blame_request,
5276         blame_grep,
5277         blame_select,
5278 };
5280 /*
5281  * Branch backend
5282  */
5284 struct branch {
5285         const char *author;             /* Author of the last commit. */
5286         struct time time;               /* Date of the last activity. */
5287         const struct ref *ref;          /* Name and commit ID information. */
5288 };
5290 static const struct ref branch_all;
5292 static const enum sort_field branch_sort_fields[] = {
5293         ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
5294 };
5295 static struct sort_state branch_sort_state = SORT_STATE(branch_sort_fields);
5297 static int
5298 branch_compare(const void *l1, const void *l2)
5300         const struct branch *branch1 = ((const struct line *) l1)->data;
5301         const struct branch *branch2 = ((const struct line *) l2)->data;
5303         switch (get_sort_field(branch_sort_state)) {
5304         case ORDERBY_DATE:
5305                 return sort_order(branch_sort_state, timecmp(&branch1->time, &branch2->time));
5307         case ORDERBY_AUTHOR:
5308                 return sort_order(branch_sort_state, strcmp(branch1->author, branch2->author));
5310         case ORDERBY_NAME:
5311         default:
5312                 return sort_order(branch_sort_state, strcmp(branch1->ref->name, branch2->ref->name));
5313         }
5316 static bool
5317 branch_draw(struct view *view, struct line *line, unsigned int lineno)
5319         struct branch *branch = line->data;
5320         enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
5322         if (opt_date && draw_date(view, &branch->time))
5323                 return TRUE;
5325         if (opt_author && draw_author(view, branch->author))
5326                 return TRUE;
5328         draw_text(view, type, branch->ref == &branch_all ? "All branches" : branch->ref->name, TRUE);
5329         return TRUE;
5332 static enum request
5333 branch_request(struct view *view, enum request request, struct line *line)
5335         struct branch *branch = line->data;
5337         switch (request) {
5338         case REQ_REFRESH:
5339                 load_refs();
5340                 open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
5341                 return REQ_NONE;
5343         case REQ_TOGGLE_SORT_FIELD:
5344         case REQ_TOGGLE_SORT_ORDER:
5345                 sort_view(view, request, &branch_sort_state, branch_compare);
5346                 return REQ_NONE;
5348         case REQ_ENTER:
5349         {
5350                 const struct ref *ref = branch->ref;
5351                 const char *all_branches_argv[] = {
5352                         "git", "log", "--no-color", "--pretty=raw", "--parents",
5353                               "--topo-order",
5354                               ref == &branch_all ? "--all" : ref->name, NULL
5355                 };
5356                 struct view *main_view = VIEW(REQ_VIEW_MAIN);
5358                 if (!prepare_update(main_view, all_branches_argv, NULL))
5359                         report("Failed to load view of all branches");
5360                 else
5361                         open_view(view, REQ_VIEW_MAIN, OPEN_PREPARED | OPEN_SPLIT);
5362                 return REQ_NONE;
5363         }
5364         default:
5365                 return request;
5366         }
5369 static bool
5370 branch_read(struct view *view, char *line)
5372         static char id[SIZEOF_REV];
5373         struct branch *reference;
5374         size_t i;
5376         if (!line)
5377                 return TRUE;
5379         switch (get_line_type(line)) {
5380         case LINE_COMMIT:
5381                 string_copy_rev(id, line + STRING_SIZE("commit "));
5382                 return TRUE;
5384         case LINE_AUTHOR:
5385                 for (i = 0, reference = NULL; i < view->lines; i++) {
5386                         struct branch *branch = view->line[i].data;
5388                         if (strcmp(branch->ref->id, id))
5389                                 continue;
5391                         view->line[i].dirty = TRUE;
5392                         if (reference) {
5393                                 branch->author = reference->author;
5394                                 branch->time = reference->time;
5395                                 continue;
5396                         }
5398                         parse_author_line(line + STRING_SIZE("author "),
5399                                           &branch->author, &branch->time);
5400                         reference = branch;
5401                 }
5402                 return TRUE;
5404         default:
5405                 return TRUE;
5406         }
5410 static bool
5411 branch_open_visitor(void *data, const struct ref *ref)
5413         struct view *view = data;
5414         struct branch *branch;
5416         if (ref->tag || ref->ltag || ref->remote)
5417                 return TRUE;
5419         branch = calloc(1, sizeof(*branch));
5420         if (!branch)
5421                 return FALSE;
5423         branch->ref = ref;
5424         return !!add_line_data(view, branch, LINE_DEFAULT);
5427 static bool
5428 branch_open(struct view *view)
5430         const char *branch_log[] = {
5431                 "git", "log", "--no-color", "--pretty=raw",
5432                         "--simplify-by-decoration", "--all", NULL
5433         };
5435         if (!start_update(view, branch_log, NULL)) {
5436                 report("Failed to load branch data");
5437                 return TRUE;
5438         }
5440         setup_update(view, view->id);
5441         branch_open_visitor(view, &branch_all);
5442         foreach_ref(branch_open_visitor, view);
5443         view->p_restore = TRUE;
5445         return TRUE;
5448 static bool
5449 branch_grep(struct view *view, struct line *line)
5451         struct branch *branch = line->data;
5452         const char *text[] = {
5453                 branch->ref->name,
5454                 branch->author,
5455                 NULL
5456         };
5458         return grep_text(view, text);
5461 static void
5462 branch_select(struct view *view, struct line *line)
5464         struct branch *branch = line->data;
5466         string_copy_rev(view->ref, branch->ref->id);
5467         string_copy_rev(ref_commit, branch->ref->id);
5468         string_copy_rev(ref_head, branch->ref->id);
5469         string_copy_rev(ref_branch, branch->ref->name);
5472 static struct view_ops branch_ops = {
5473         "branch",
5474         NULL,
5475         branch_open,
5476         branch_read,
5477         branch_draw,
5478         branch_request,
5479         branch_grep,
5480         branch_select,
5481 };
5483 /*
5484  * Status backend
5485  */
5487 struct status {
5488         char status;
5489         struct {
5490                 mode_t mode;
5491                 char rev[SIZEOF_REV];
5492                 char name[SIZEOF_STR];
5493         } old;
5494         struct {
5495                 mode_t mode;
5496                 char rev[SIZEOF_REV];
5497                 char name[SIZEOF_STR];
5498         } new;
5499 };
5501 static char status_onbranch[SIZEOF_STR];
5502 static struct status stage_status;
5503 static enum line_type stage_line_type;
5504 static size_t stage_chunks;
5505 static int *stage_chunk;
5507 DEFINE_ALLOCATOR(realloc_ints, int, 32)
5509 /* This should work even for the "On branch" line. */
5510 static inline bool
5511 status_has_none(struct view *view, struct line *line)
5513         return line < view->line + view->lines && !line[1].data;
5516 /* Get fields from the diff line:
5517  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5518  */
5519 static inline bool
5520 status_get_diff(struct status *file, const char *buf, size_t bufsize)
5522         const char *old_mode = buf +  1;
5523         const char *new_mode = buf +  8;
5524         const char *old_rev  = buf + 15;
5525         const char *new_rev  = buf + 56;
5526         const char *status   = buf + 97;
5528         if (bufsize < 98 ||
5529             old_mode[-1] != ':' ||
5530             new_mode[-1] != ' ' ||
5531             old_rev[-1]  != ' ' ||
5532             new_rev[-1]  != ' ' ||
5533             status[-1]   != ' ')
5534                 return FALSE;
5536         file->status = *status;
5538         string_copy_rev(file->old.rev, old_rev);
5539         string_copy_rev(file->new.rev, new_rev);
5541         file->old.mode = strtoul(old_mode, NULL, 8);
5542         file->new.mode = strtoul(new_mode, NULL, 8);
5544         file->old.name[0] = file->new.name[0] = 0;
5546         return TRUE;
5549 static bool
5550 status_run(struct view *view, const char *argv[], char status, enum line_type type)
5552         struct status *unmerged = NULL;
5553         char *buf;
5554         struct io io;
5556         if (!io_run(&io, IO_RD, opt_cdup, argv))
5557                 return FALSE;
5559         add_line_data(view, NULL, type);
5561         while ((buf = io_get(&io, 0, TRUE))) {
5562                 struct status *file = unmerged;
5564                 if (!file) {
5565                         file = calloc(1, sizeof(*file));
5566                         if (!file || !add_line_data(view, file, type))
5567                                 goto error_out;
5568                 }
5570                 /* Parse diff info part. */
5571                 if (status) {
5572                         file->status = status;
5573                         if (status == 'A')
5574                                 string_copy(file->old.rev, NULL_ID);
5576                 } else if (!file->status || file == unmerged) {
5577                         if (!status_get_diff(file, buf, strlen(buf)))
5578                                 goto error_out;
5580                         buf = io_get(&io, 0, TRUE);
5581                         if (!buf)
5582                                 break;
5584                         /* Collapse all modified entries that follow an
5585                          * associated unmerged entry. */
5586                         if (unmerged == file) {
5587                                 unmerged->status = 'U';
5588                                 unmerged = NULL;
5589                         } else if (file->status == 'U') {
5590                                 unmerged = file;
5591                         }
5592                 }
5594                 /* Grab the old name for rename/copy. */
5595                 if (!*file->old.name &&
5596                     (file->status == 'R' || file->status == 'C')) {
5597                         string_ncopy(file->old.name, buf, strlen(buf));
5599                         buf = io_get(&io, 0, TRUE);
5600                         if (!buf)
5601                                 break;
5602                 }
5604                 /* git-ls-files just delivers a NUL separated list of
5605                  * file names similar to the second half of the
5606                  * git-diff-* output. */
5607                 string_ncopy(file->new.name, buf, strlen(buf));
5608                 if (!*file->old.name)
5609                         string_copy(file->old.name, file->new.name);
5610                 file = NULL;
5611         }
5613         if (io_error(&io)) {
5614 error_out:
5615                 io_done(&io);
5616                 return FALSE;
5617         }
5619         if (!view->line[view->lines - 1].data)
5620                 add_line_data(view, NULL, LINE_STAT_NONE);
5622         io_done(&io);
5623         return TRUE;
5626 /* Don't show unmerged entries in the staged section. */
5627 static const char *status_diff_index_argv[] = {
5628         "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5629                              "--cached", "-M", "HEAD", NULL
5630 };
5632 static const char *status_diff_files_argv[] = {
5633         "git", "diff-files", "-z", NULL
5634 };
5636 static const char *status_list_other_argv[] = {
5637         "git", "ls-files", "-z", "--others", "--exclude-standard", opt_prefix, NULL
5638 };
5640 static const char *status_list_no_head_argv[] = {
5641         "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5642 };
5644 static const char *update_index_argv[] = {
5645         "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5646 };
5648 /* Restore the previous line number to stay in the context or select a
5649  * line with something that can be updated. */
5650 static void
5651 status_restore(struct view *view)
5653         if (view->p_lineno >= view->lines)
5654                 view->p_lineno = view->lines - 1;
5655         while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
5656                 view->p_lineno++;
5657         while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
5658                 view->p_lineno--;
5660         /* If the above fails, always skip the "On branch" line. */
5661         if (view->p_lineno < view->lines)
5662                 view->lineno = view->p_lineno;
5663         else
5664                 view->lineno = 1;
5666         if (view->lineno < view->offset)
5667                 view->offset = view->lineno;
5668         else if (view->offset + view->height <= view->lineno)
5669                 view->offset = view->lineno - view->height + 1;
5671         view->p_restore = FALSE;
5674 static void
5675 status_update_onbranch(void)
5677         static const char *paths[][2] = {
5678                 { "rebase-apply/rebasing",      "Rebasing" },
5679                 { "rebase-apply/applying",      "Applying mailbox" },
5680                 { "rebase-apply/",              "Rebasing mailbox" },
5681                 { "rebase-merge/interactive",   "Interactive rebase" },
5682                 { "rebase-merge/",              "Rebase merge" },
5683                 { "MERGE_HEAD",                 "Merging" },
5684                 { "BISECT_LOG",                 "Bisecting" },
5685                 { "HEAD",                       "On branch" },
5686         };
5687         char buf[SIZEOF_STR];
5688         struct stat stat;
5689         int i;
5691         if (is_initial_commit()) {
5692                 string_copy(status_onbranch, "Initial commit");
5693                 return;
5694         }
5696         for (i = 0; i < ARRAY_SIZE(paths); i++) {
5697                 char *head = opt_head;
5699                 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5700                     lstat(buf, &stat) < 0)
5701                         continue;
5703                 if (!*opt_head) {
5704                         struct io io;
5706                         if (io_open(&io, "%s/rebase-merge/head-name", opt_git_dir) &&
5707                             io_read_buf(&io, buf, sizeof(buf))) {
5708                                 head = buf;
5709                                 if (!prefixcmp(head, "refs/heads/"))
5710                                         head += STRING_SIZE("refs/heads/");
5711                         }
5712                 }
5714                 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5715                         string_copy(status_onbranch, opt_head);
5716                 return;
5717         }
5719         string_copy(status_onbranch, "Not currently on any branch");
5722 /* First parse staged info using git-diff-index(1), then parse unstaged
5723  * info using git-diff-files(1), and finally untracked files using
5724  * git-ls-files(1). */
5725 static bool
5726 status_open(struct view *view)
5728         reset_view(view);
5730         add_line_data(view, NULL, LINE_STAT_HEAD);
5731         status_update_onbranch();
5733         io_run_bg(update_index_argv);
5735         if (is_initial_commit()) {
5736                 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5737                         return FALSE;
5738         } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5739                 return FALSE;
5740         }
5742         if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5743             !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5744                 return FALSE;
5746         /* Restore the exact position or use the specialized restore
5747          * mode? */
5748         if (!view->p_restore)
5749                 status_restore(view);
5750         return TRUE;
5753 static bool
5754 status_draw(struct view *view, struct line *line, unsigned int lineno)
5756         struct status *status = line->data;
5757         enum line_type type;
5758         const char *text;
5760         if (!status) {
5761                 switch (line->type) {
5762                 case LINE_STAT_STAGED:
5763                         type = LINE_STAT_SECTION;
5764                         text = "Changes to be committed:";
5765                         break;
5767                 case LINE_STAT_UNSTAGED:
5768                         type = LINE_STAT_SECTION;
5769                         text = "Changed but not updated:";
5770                         break;
5772                 case LINE_STAT_UNTRACKED:
5773                         type = LINE_STAT_SECTION;
5774                         text = "Untracked files:";
5775                         break;
5777                 case LINE_STAT_NONE:
5778                         type = LINE_DEFAULT;
5779                         text = "  (no files)";
5780                         break;
5782                 case LINE_STAT_HEAD:
5783                         type = LINE_STAT_HEAD;
5784                         text = status_onbranch;
5785                         break;
5787                 default:
5788                         return FALSE;
5789                 }
5790         } else {
5791                 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5793                 buf[0] = status->status;
5794                 if (draw_text(view, line->type, buf, TRUE))
5795                         return TRUE;
5796                 type = LINE_DEFAULT;
5797                 text = status->new.name;
5798         }
5800         draw_text(view, type, text, TRUE);
5801         return TRUE;
5804 static enum request
5805 status_load_error(struct view *view, struct view *stage, const char *path)
5807         if (displayed_views() == 2 || display[current_view] != view)
5808                 maximize_view(view);
5809         report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5810         return REQ_NONE;
5813 static enum request
5814 status_enter(struct view *view, struct line *line)
5816         struct status *status = line->data;
5817         const char *oldpath = status ? status->old.name : NULL;
5818         /* Diffs for unmerged entries are empty when passing the new
5819          * path, so leave it empty. */
5820         const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5821         const char *info;
5822         enum open_flags split;
5823         struct view *stage = VIEW(REQ_VIEW_STAGE);
5825         if (line->type == LINE_STAT_NONE ||
5826             (!status && line[1].type == LINE_STAT_NONE)) {
5827                 report("No file to diff");
5828                 return REQ_NONE;
5829         }
5831         switch (line->type) {
5832         case LINE_STAT_STAGED:
5833                 if (is_initial_commit()) {
5834                         const char *no_head_diff_argv[] = {
5835                                 "git", "diff", "--no-color", "--patch-with-stat",
5836                                         "--", "/dev/null", newpath, NULL
5837                         };
5839                         if (!prepare_update(stage, no_head_diff_argv, opt_cdup))
5840                                 return status_load_error(view, stage, newpath);
5841                 } else {
5842                         const char *index_show_argv[] = {
5843                                 "git", "diff-index", "--root", "--patch-with-stat",
5844                                         "-C", "-M", "--cached", "HEAD", "--",
5845                                         oldpath, newpath, NULL
5846                         };
5848                         if (!prepare_update(stage, index_show_argv, opt_cdup))
5849                                 return status_load_error(view, stage, newpath);
5850                 }
5852                 if (status)
5853                         info = "Staged changes to %s";
5854                 else
5855                         info = "Staged changes";
5856                 break;
5858         case LINE_STAT_UNSTAGED:
5859         {
5860                 const char *files_show_argv[] = {
5861                         "git", "diff-files", "--root", "--patch-with-stat",
5862                                 "-C", "-M", "--", oldpath, newpath, NULL
5863                 };
5865                 if (!prepare_update(stage, files_show_argv, opt_cdup))
5866                         return status_load_error(view, stage, newpath);
5867                 if (status)
5868                         info = "Unstaged changes to %s";
5869                 else
5870                         info = "Unstaged changes";
5871                 break;
5872         }
5873         case LINE_STAT_UNTRACKED:
5874                 if (!newpath) {
5875                         report("No file to show");
5876                         return REQ_NONE;
5877                 }
5879                 if (!suffixcmp(status->new.name, -1, "/")) {
5880                         report("Cannot display a directory");
5881                         return REQ_NONE;
5882                 }
5884                 if (!prepare_update_file(stage, newpath))
5885                         return status_load_error(view, stage, newpath);
5886                 info = "Untracked file %s";
5887                 break;
5889         case LINE_STAT_HEAD:
5890                 return REQ_NONE;
5892         default:
5893                 die("line type %d not handled in switch", line->type);
5894         }
5896         split = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
5897         open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5898         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5899                 if (status) {
5900                         stage_status = *status;
5901                 } else {
5902                         memset(&stage_status, 0, sizeof(stage_status));
5903                 }
5905                 stage_line_type = line->type;
5906                 stage_chunks = 0;
5907                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5908         }
5910         return REQ_NONE;
5913 static bool
5914 status_exists(struct status *status, enum line_type type)
5916         struct view *view = VIEW(REQ_VIEW_STATUS);
5917         unsigned long lineno;
5919         for (lineno = 0; lineno < view->lines; lineno++) {
5920                 struct line *line = &view->line[lineno];
5921                 struct status *pos = line->data;
5923                 if (line->type != type)
5924                         continue;
5925                 if (!pos && (!status || !status->status) && line[1].data) {
5926                         select_view_line(view, lineno);
5927                         return TRUE;
5928                 }
5929                 if (pos && !strcmp(status->new.name, pos->new.name)) {
5930                         select_view_line(view, lineno);
5931                         return TRUE;
5932                 }
5933         }
5935         return FALSE;
5939 static bool
5940 status_update_prepare(struct io *io, enum line_type type)
5942         const char *staged_argv[] = {
5943                 "git", "update-index", "-z", "--index-info", NULL
5944         };
5945         const char *others_argv[] = {
5946                 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5947         };
5949         switch (type) {
5950         case LINE_STAT_STAGED:
5951                 return io_run(io, IO_WR, opt_cdup, staged_argv);
5953         case LINE_STAT_UNSTAGED:
5954         case LINE_STAT_UNTRACKED:
5955                 return io_run(io, IO_WR, opt_cdup, others_argv);
5957         default:
5958                 die("line type %d not handled in switch", type);
5959                 return FALSE;
5960         }
5963 static bool
5964 status_update_write(struct io *io, struct status *status, enum line_type type)
5966         char buf[SIZEOF_STR];
5967         size_t bufsize = 0;
5969         switch (type) {
5970         case LINE_STAT_STAGED:
5971                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
5972                                         status->old.mode,
5973                                         status->old.rev,
5974                                         status->old.name, 0))
5975                         return FALSE;
5976                 break;
5978         case LINE_STAT_UNSTAGED:
5979         case LINE_STAT_UNTRACKED:
5980                 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
5981                         return FALSE;
5982                 break;
5984         default:
5985                 die("line type %d not handled in switch", type);
5986         }
5988         return io_write(io, buf, bufsize);
5991 static bool
5992 status_update_file(struct status *status, enum line_type type)
5994         struct io io;
5995         bool result;
5997         if (!status_update_prepare(&io, type))
5998                 return FALSE;
6000         result = status_update_write(&io, status, type);
6001         return io_done(&io) && result;
6004 static bool
6005 status_update_files(struct view *view, struct line *line)
6007         char buf[sizeof(view->ref)];
6008         struct io io;
6009         bool result = TRUE;
6010         struct line *pos = view->line + view->lines;
6011         int files = 0;
6012         int file, done;
6013         int cursor_y = -1, cursor_x = -1;
6015         if (!status_update_prepare(&io, line->type))
6016                 return FALSE;
6018         for (pos = line; pos < view->line + view->lines && pos->data; pos++)
6019                 files++;
6021         string_copy(buf, view->ref);
6022         getsyx(cursor_y, cursor_x);
6023         for (file = 0, done = 5; result && file < files; line++, file++) {
6024                 int almost_done = file * 100 / files;
6026                 if (almost_done > done) {
6027                         done = almost_done;
6028                         string_format(view->ref, "updating file %u of %u (%d%% done)",
6029                                       file, files, done);
6030                         update_view_title(view);
6031                         setsyx(cursor_y, cursor_x);
6032                         doupdate();
6033                 }
6034                 result = status_update_write(&io, line->data, line->type);
6035         }
6036         string_copy(view->ref, buf);
6038         return io_done(&io) && result;
6041 static bool
6042 status_update(struct view *view)
6044         struct line *line = &view->line[view->lineno];
6046         assert(view->lines);
6048         if (!line->data) {
6049                 /* This should work even for the "On branch" line. */
6050                 if (line < view->line + view->lines && !line[1].data) {
6051                         report("Nothing to update");
6052                         return FALSE;
6053                 }
6055                 if (!status_update_files(view, line + 1)) {
6056                         report("Failed to update file status");
6057                         return FALSE;
6058                 }
6060         } else if (!status_update_file(line->data, line->type)) {
6061                 report("Failed to update file status");
6062                 return FALSE;
6063         }
6065         return TRUE;
6068 static bool
6069 status_revert(struct status *status, enum line_type type, bool has_none)
6071         if (!status || type != LINE_STAT_UNSTAGED) {
6072                 if (type == LINE_STAT_STAGED) {
6073                         report("Cannot revert changes to staged files");
6074                 } else if (type == LINE_STAT_UNTRACKED) {
6075                         report("Cannot revert changes to untracked files");
6076                 } else if (has_none) {
6077                         report("Nothing to revert");
6078                 } else {
6079                         report("Cannot revert changes to multiple files");
6080                 }
6082         } else if (prompt_yesno("Are you sure you want to revert changes?")) {
6083                 char mode[10] = "100644";
6084                 const char *reset_argv[] = {
6085                         "git", "update-index", "--cacheinfo", mode,
6086                                 status->old.rev, status->old.name, NULL
6087                 };
6088                 const char *checkout_argv[] = {
6089                         "git", "checkout", "--", status->old.name, NULL
6090                 };
6092                 if (status->status == 'U') {
6093                         string_format(mode, "%5o", status->old.mode);
6095                         if (status->old.mode == 0 && status->new.mode == 0) {
6096                                 reset_argv[2] = "--force-remove";
6097                                 reset_argv[3] = status->old.name;
6098                                 reset_argv[4] = NULL;
6099                         }
6101                         if (!io_run_fg(reset_argv, opt_cdup))
6102                                 return FALSE;
6103                         if (status->old.mode == 0 && status->new.mode == 0)
6104                                 return TRUE;
6105                 }
6107                 return io_run_fg(checkout_argv, opt_cdup);
6108         }
6110         return FALSE;
6113 static enum request
6114 status_request(struct view *view, enum request request, struct line *line)
6116         struct status *status = line->data;
6118         switch (request) {
6119         case REQ_STATUS_UPDATE:
6120                 if (!status_update(view))
6121                         return REQ_NONE;
6122                 break;
6124         case REQ_STATUS_REVERT:
6125                 if (!status_revert(status, line->type, status_has_none(view, line)))
6126                         return REQ_NONE;
6127                 break;
6129         case REQ_STATUS_MERGE:
6130                 if (!status || status->status != 'U') {
6131                         report("Merging only possible for files with unmerged status ('U').");
6132                         return REQ_NONE;
6133                 }
6134                 open_mergetool(status->new.name);
6135                 break;
6137         case REQ_EDIT:
6138                 if (!status)
6139                         return request;
6140                 if (status->status == 'D') {
6141                         report("File has been deleted.");
6142                         return REQ_NONE;
6143                 }
6145                 open_editor(status->new.name);
6146                 break;
6148         case REQ_VIEW_BLAME:
6149                 if (status)
6150                         opt_ref[0] = 0;
6151                 return request;
6153         case REQ_ENTER:
6154                 /* After returning the status view has been split to
6155                  * show the stage view. No further reloading is
6156                  * necessary. */
6157                 return status_enter(view, line);
6159         case REQ_REFRESH:
6160                 /* Simply reload the view. */
6161                 break;
6163         default:
6164                 return request;
6165         }
6167         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
6169         return REQ_NONE;
6172 static void
6173 status_select(struct view *view, struct line *line)
6175         struct status *status = line->data;
6176         char file[SIZEOF_STR] = "all files";
6177         const char *text;
6178         const char *key;
6180         if (status && !string_format(file, "'%s'", status->new.name))
6181                 return;
6183         if (!status && line[1].type == LINE_STAT_NONE)
6184                 line++;
6186         switch (line->type) {
6187         case LINE_STAT_STAGED:
6188                 text = "Press %s to unstage %s for commit";
6189                 break;
6191         case LINE_STAT_UNSTAGED:
6192                 text = "Press %s to stage %s for commit";
6193                 break;
6195         case LINE_STAT_UNTRACKED:
6196                 text = "Press %s to stage %s for addition";
6197                 break;
6199         case LINE_STAT_HEAD:
6200         case LINE_STAT_NONE:
6201                 text = "Nothing to update";
6202                 break;
6204         default:
6205                 die("line type %d not handled in switch", line->type);
6206         }
6208         if (status && status->status == 'U') {
6209                 text = "Press %s to resolve conflict in %s";
6210                 key = get_key(KEYMAP_STATUS, REQ_STATUS_MERGE);
6212         } else {
6213                 key = get_key(KEYMAP_STATUS, REQ_STATUS_UPDATE);
6214         }
6216         string_format(view->ref, text, key, file);
6217         if (status)
6218                 string_copy(opt_file, status->new.name);
6221 static bool
6222 status_grep(struct view *view, struct line *line)
6224         struct status *status = line->data;
6226         if (status) {
6227                 const char buf[2] = { status->status, 0 };
6228                 const char *text[] = { status->new.name, buf, NULL };
6230                 return grep_text(view, text);
6231         }
6233         return FALSE;
6236 static struct view_ops status_ops = {
6237         "file",
6238         NULL,
6239         status_open,
6240         NULL,
6241         status_draw,
6242         status_request,
6243         status_grep,
6244         status_select,
6245 };
6248 static bool
6249 stage_diff_write(struct io *io, struct line *line, struct line *end)
6251         while (line < end) {
6252                 if (!io_write(io, line->data, strlen(line->data)) ||
6253                     !io_write(io, "\n", 1))
6254                         return FALSE;
6255                 line++;
6256                 if (line->type == LINE_DIFF_CHUNK ||
6257                     line->type == LINE_DIFF_HEADER)
6258                         break;
6259         }
6261         return TRUE;
6264 static struct line *
6265 stage_diff_find(struct view *view, struct line *line, enum line_type type)
6267         for (; view->line < line; line--)
6268                 if (line->type == type)
6269                         return line;
6271         return NULL;
6274 static bool
6275 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
6277         const char *apply_argv[SIZEOF_ARG] = {
6278                 "git", "apply", "--whitespace=nowarn", NULL
6279         };
6280         struct line *diff_hdr;
6281         struct io io;
6282         int argc = 3;
6284         diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
6285         if (!diff_hdr)
6286                 return FALSE;
6288         if (!revert)
6289                 apply_argv[argc++] = "--cached";
6290         if (revert || stage_line_type == LINE_STAT_STAGED)
6291                 apply_argv[argc++] = "-R";
6292         apply_argv[argc++] = "-";
6293         apply_argv[argc++] = NULL;
6294         if (!io_run(&io, IO_WR, opt_cdup, apply_argv))
6295                 return FALSE;
6297         if (!stage_diff_write(&io, diff_hdr, chunk) ||
6298             !stage_diff_write(&io, chunk, view->line + view->lines))
6299                 chunk = NULL;
6301         io_done(&io);
6302         io_run_bg(update_index_argv);
6304         return chunk ? TRUE : FALSE;
6307 static bool
6308 stage_update(struct view *view, struct line *line)
6310         struct line *chunk = NULL;
6312         if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
6313                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6315         if (chunk) {
6316                 if (!stage_apply_chunk(view, chunk, FALSE)) {
6317                         report("Failed to apply chunk");
6318                         return FALSE;
6319                 }
6321         } else if (!stage_status.status) {
6322                 view = VIEW(REQ_VIEW_STATUS);
6324                 for (line = view->line; line < view->line + view->lines; line++)
6325                         if (line->type == stage_line_type)
6326                                 break;
6328                 if (!status_update_files(view, line + 1)) {
6329                         report("Failed to update files");
6330                         return FALSE;
6331                 }
6333         } else if (!status_update_file(&stage_status, stage_line_type)) {
6334                 report("Failed to update file");
6335                 return FALSE;
6336         }
6338         return TRUE;
6341 static bool
6342 stage_revert(struct view *view, struct line *line)
6344         struct line *chunk = NULL;
6346         if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
6347                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6349         if (chunk) {
6350                 if (!prompt_yesno("Are you sure you want to revert changes?"))
6351                         return FALSE;
6353                 if (!stage_apply_chunk(view, chunk, TRUE)) {
6354                         report("Failed to revert chunk");
6355                         return FALSE;
6356                 }
6357                 return TRUE;
6359         } else {
6360                 return status_revert(stage_status.status ? &stage_status : NULL,
6361                                      stage_line_type, FALSE);
6362         }
6366 static void
6367 stage_next(struct view *view, struct line *line)
6369         int i;
6371         if (!stage_chunks) {
6372                 for (line = view->line; line < view->line + view->lines; line++) {
6373                         if (line->type != LINE_DIFF_CHUNK)
6374                                 continue;
6376                         if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
6377                                 report("Allocation failure");
6378                                 return;
6379                         }
6381                         stage_chunk[stage_chunks++] = line - view->line;
6382                 }
6383         }
6385         for (i = 0; i < stage_chunks; i++) {
6386                 if (stage_chunk[i] > view->lineno) {
6387                         do_scroll_view(view, stage_chunk[i] - view->lineno);
6388                         report("Chunk %d of %d", i + 1, stage_chunks);
6389                         return;
6390                 }
6391         }
6393         report("No next chunk found");
6396 static enum request
6397 stage_request(struct view *view, enum request request, struct line *line)
6399         switch (request) {
6400         case REQ_STATUS_UPDATE:
6401                 if (!stage_update(view, line))
6402                         return REQ_NONE;
6403                 break;
6405         case REQ_STATUS_REVERT:
6406                 if (!stage_revert(view, line))
6407                         return REQ_NONE;
6408                 break;
6410         case REQ_STAGE_NEXT:
6411                 if (stage_line_type == LINE_STAT_UNTRACKED) {
6412                         report("File is untracked; press %s to add",
6413                                get_key(KEYMAP_STAGE, REQ_STATUS_UPDATE));
6414                         return REQ_NONE;
6415                 }
6416                 stage_next(view, line);
6417                 return REQ_NONE;
6419         case REQ_EDIT:
6420                 if (!stage_status.new.name[0])
6421                         return request;
6422                 if (stage_status.status == 'D') {
6423                         report("File has been deleted.");
6424                         return REQ_NONE;
6425                 }
6427                 open_editor(stage_status.new.name);
6428                 break;
6430         case REQ_REFRESH:
6431                 /* Reload everything ... */
6432                 break;
6434         case REQ_VIEW_BLAME:
6435                 if (stage_status.new.name[0]) {
6436                         string_copy(opt_file, stage_status.new.name);
6437                         opt_ref[0] = 0;
6438                 }
6439                 return request;
6441         case REQ_ENTER:
6442                 return pager_request(view, request, line);
6444         default:
6445                 return request;
6446         }
6448         VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
6449         open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
6451         /* Check whether the staged entry still exists, and close the
6452          * stage view if it doesn't. */
6453         if (!status_exists(&stage_status, stage_line_type)) {
6454                 status_restore(VIEW(REQ_VIEW_STATUS));
6455                 return REQ_VIEW_CLOSE;
6456         }
6458         if (stage_line_type == LINE_STAT_UNTRACKED) {
6459                 if (!suffixcmp(stage_status.new.name, -1, "/")) {
6460                         report("Cannot display a directory");
6461                         return REQ_NONE;
6462                 }
6464                 if (!prepare_update_file(view, stage_status.new.name)) {
6465                         report("Failed to open file: %s", strerror(errno));
6466                         return REQ_NONE;
6467                 }
6468         }
6469         open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
6471         return REQ_NONE;
6474 static struct view_ops stage_ops = {
6475         "line",
6476         NULL,
6477         NULL,
6478         pager_read,
6479         pager_draw,
6480         stage_request,
6481         pager_grep,
6482         pager_select,
6483 };
6486 /*
6487  * Revision graph
6488  */
6490 struct commit {
6491         char id[SIZEOF_REV];            /* SHA1 ID. */
6492         char title[128];                /* First line of the commit message. */
6493         const char *author;             /* Author of the commit. */
6494         struct time time;               /* Date from the author ident. */
6495         struct ref_list *refs;          /* Repository references. */
6496         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
6497         size_t graph_size;              /* The width of the graph array. */
6498         bool has_parents;               /* Rewritten --parents seen. */
6499 };
6501 /* Size of rev graph with no  "padding" columns */
6502 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6504 struct rev_graph {
6505         struct rev_graph *prev, *next, *parents;
6506         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
6507         size_t size;
6508         struct commit *commit;
6509         size_t pos;
6510         unsigned int boundary:1;
6511 };
6513 /* Parents of the commit being visualized. */
6514 static struct rev_graph graph_parents[4];
6516 /* The current stack of revisions on the graph. */
6517 static struct rev_graph graph_stacks[4] = {
6518         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
6519         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
6520         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
6521         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
6522 };
6524 static inline bool
6525 graph_parent_is_merge(struct rev_graph *graph)
6527         return graph->parents->size > 1;
6530 static inline void
6531 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
6533         struct commit *commit = graph->commit;
6535         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
6536                 commit->graph[commit->graph_size++] = symbol;
6539 static void
6540 clear_rev_graph(struct rev_graph *graph)
6542         graph->boundary = 0;
6543         graph->size = graph->pos = 0;
6544         graph->commit = NULL;
6545         memset(graph->parents, 0, sizeof(*graph->parents));
6548 static void
6549 done_rev_graph(struct rev_graph *graph)
6551         if (graph_parent_is_merge(graph) &&
6552             graph->pos < graph->size - 1 &&
6553             graph->next->size == graph->size + graph->parents->size - 1) {
6554                 size_t i = graph->pos + graph->parents->size - 1;
6556                 graph->commit->graph_size = i * 2;
6557                 while (i < graph->next->size - 1) {
6558                         append_to_rev_graph(graph, ' ');
6559                         append_to_rev_graph(graph, '\\');
6560                         i++;
6561                 }
6562         }
6564         clear_rev_graph(graph);
6567 static void
6568 push_rev_graph(struct rev_graph *graph, const char *parent)
6570         int i;
6572         /* "Collapse" duplicate parents lines.
6573          *
6574          * FIXME: This needs to also update update the drawn graph but
6575          * for now it just serves as a method for pruning graph lines. */
6576         for (i = 0; i < graph->size; i++)
6577                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
6578                         return;
6580         if (graph->size < SIZEOF_REVITEMS) {
6581                 string_copy_rev(graph->rev[graph->size++], parent);
6582         }
6585 static chtype
6586 get_rev_graph_symbol(struct rev_graph *graph)
6588         chtype symbol;
6590         if (graph->boundary)
6591                 symbol = REVGRAPH_BOUND;
6592         else if (graph->parents->size == 0)
6593                 symbol = REVGRAPH_INIT;
6594         else if (graph_parent_is_merge(graph))
6595                 symbol = REVGRAPH_MERGE;
6596         else if (graph->pos >= graph->size)
6597                 symbol = REVGRAPH_BRANCH;
6598         else
6599                 symbol = REVGRAPH_COMMIT;
6601         return symbol;
6604 static void
6605 draw_rev_graph(struct rev_graph *graph)
6607         struct rev_filler {
6608                 chtype separator, line;
6609         };
6610         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
6611         static struct rev_filler fillers[] = {
6612                 { ' ',  '|' },
6613                 { '`',  '.' },
6614                 { '\'', ' ' },
6615                 { '/',  ' ' },
6616         };
6617         chtype symbol = get_rev_graph_symbol(graph);
6618         struct rev_filler *filler;
6619         size_t i;
6621         fillers[DEFAULT].line = opt_line_graphics ? ACS_VLINE : '|';
6622         filler = &fillers[DEFAULT];
6624         for (i = 0; i < graph->pos; i++) {
6625                 append_to_rev_graph(graph, filler->line);
6626                 if (graph_parent_is_merge(graph->prev) &&
6627                     graph->prev->pos == i)
6628                         filler = &fillers[RSHARP];
6630                 append_to_rev_graph(graph, filler->separator);
6631         }
6633         /* Place the symbol for this revision. */
6634         append_to_rev_graph(graph, symbol);
6636         if (graph->prev->size > graph->size)
6637                 filler = &fillers[RDIAG];
6638         else
6639                 filler = &fillers[DEFAULT];
6641         i++;
6643         for (; i < graph->size; i++) {
6644                 append_to_rev_graph(graph, filler->separator);
6645                 append_to_rev_graph(graph, filler->line);
6646                 if (graph_parent_is_merge(graph->prev) &&
6647                     i < graph->prev->pos + graph->parents->size)
6648                         filler = &fillers[RSHARP];
6649                 if (graph->prev->size > graph->size)
6650                         filler = &fillers[LDIAG];
6651         }
6653         if (graph->prev->size > graph->size) {
6654                 append_to_rev_graph(graph, filler->separator);
6655                 if (filler->line != ' ')
6656                         append_to_rev_graph(graph, filler->line);
6657         }
6660 /* Prepare the next rev graph */
6661 static void
6662 prepare_rev_graph(struct rev_graph *graph)
6664         size_t i;
6666         /* First, traverse all lines of revisions up to the active one. */
6667         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6668                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6669                         break;
6671                 push_rev_graph(graph->next, graph->rev[graph->pos]);
6672         }
6674         /* Interleave the new revision parent(s). */
6675         for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6676                 push_rev_graph(graph->next, graph->parents->rev[i]);
6678         /* Lastly, put any remaining revisions. */
6679         for (i = graph->pos + 1; i < graph->size; i++)
6680                 push_rev_graph(graph->next, graph->rev[i]);
6683 static void
6684 update_rev_graph(struct view *view, struct rev_graph *graph)
6686         /* If this is the finalizing update ... */
6687         if (graph->commit)
6688                 prepare_rev_graph(graph);
6690         /* Graph visualization needs a one rev look-ahead,
6691          * so the first update doesn't visualize anything. */
6692         if (!graph->prev->commit)
6693                 return;
6695         if (view->lines > 2)
6696                 view->line[view->lines - 3].dirty = 1;
6697         if (view->lines > 1)
6698                 view->line[view->lines - 2].dirty = 1;
6699         draw_rev_graph(graph->prev);
6700         done_rev_graph(graph->prev->prev);
6704 /*
6705  * Main view backend
6706  */
6708 static const char *main_argv[SIZEOF_ARG] = {
6709         "git", "log", "--no-color", "--pretty=raw", "--parents",
6710                 "--topo-order", "%(diffargs)", "%(revargs)",
6711                 "--", "%(fileargs)", NULL
6712 };
6714 static bool
6715 main_draw(struct view *view, struct line *line, unsigned int lineno)
6717         struct commit *commit = line->data;
6719         if (!commit->author)
6720                 return FALSE;
6722         if (opt_date && draw_date(view, &commit->time))
6723                 return TRUE;
6725         if (opt_author && draw_author(view, commit->author))
6726                 return TRUE;
6728         if (opt_rev_graph && commit->graph_size &&
6729             draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6730                 return TRUE;
6732         if (opt_show_refs && commit->refs) {
6733                 size_t i;
6735                 for (i = 0; i < commit->refs->size; i++) {
6736                         struct ref *ref = commit->refs->refs[i];
6737                         enum line_type type;
6739                         if (ref->head)
6740                                 type = LINE_MAIN_HEAD;
6741                         else if (ref->ltag)
6742                                 type = LINE_MAIN_LOCAL_TAG;
6743                         else if (ref->tag)
6744                                 type = LINE_MAIN_TAG;
6745                         else if (ref->tracked)
6746                                 type = LINE_MAIN_TRACKED;
6747                         else if (ref->remote)
6748                                 type = LINE_MAIN_REMOTE;
6749                         else
6750                                 type = LINE_MAIN_REF;
6752                         if (draw_text(view, type, "[", TRUE) ||
6753                             draw_text(view, type, ref->name, TRUE) ||
6754                             draw_text(view, type, "]", TRUE))
6755                                 return TRUE;
6757                         if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6758                                 return TRUE;
6759                 }
6760         }
6762         draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6763         return TRUE;
6766 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6767 static bool
6768 main_read(struct view *view, char *line)
6770         static struct rev_graph *graph = graph_stacks;
6771         enum line_type type;
6772         struct commit *commit;
6774         if (!line) {
6775                 int i;
6777                 if (!view->lines && !view->prev)
6778                         die("No revisions match the given arguments.");
6779                 if (view->lines > 0) {
6780                         commit = view->line[view->lines - 1].data;
6781                         view->line[view->lines - 1].dirty = 1;
6782                         if (!commit->author) {
6783                                 view->lines--;
6784                                 free(commit);
6785                                 graph->commit = NULL;
6786                         }
6787                 }
6788                 update_rev_graph(view, graph);
6790                 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6791                         clear_rev_graph(&graph_stacks[i]);
6792                 return TRUE;
6793         }
6795         type = get_line_type(line);
6796         if (type == LINE_COMMIT) {
6797                 commit = calloc(1, sizeof(struct commit));
6798                 if (!commit)
6799                         return FALSE;
6801                 line += STRING_SIZE("commit ");
6802                 if (*line == '-') {
6803                         graph->boundary = 1;
6804                         line++;
6805                 }
6807                 string_copy_rev(commit->id, line);
6808                 commit->refs = get_ref_list(commit->id);
6809                 graph->commit = commit;
6810                 add_line_data(view, commit, LINE_MAIN_COMMIT);
6812                 while ((line = strchr(line, ' '))) {
6813                         line++;
6814                         push_rev_graph(graph->parents, line);
6815                         commit->has_parents = TRUE;
6816                 }
6817                 return TRUE;
6818         }
6820         if (!view->lines)
6821                 return TRUE;
6822         commit = view->line[view->lines - 1].data;
6824         switch (type) {
6825         case LINE_PARENT:
6826                 if (commit->has_parents)
6827                         break;
6828                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6829                 break;
6831         case LINE_AUTHOR:
6832                 parse_author_line(line + STRING_SIZE("author "),
6833                                   &commit->author, &commit->time);
6834                 update_rev_graph(view, graph);
6835                 graph = graph->next;
6836                 break;
6838         default:
6839                 /* Fill in the commit title if it has not already been set. */
6840                 if (commit->title[0])
6841                         break;
6843                 /* Require titles to start with a non-space character at the
6844                  * offset used by git log. */
6845                 if (strncmp(line, "    ", 4))
6846                         break;
6847                 line += 4;
6848                 /* Well, if the title starts with a whitespace character,
6849                  * try to be forgiving.  Otherwise we end up with no title. */
6850                 while (isspace(*line))
6851                         line++;
6852                 if (*line == '\0')
6853                         break;
6854                 /* FIXME: More graceful handling of titles; append "..." to
6855                  * shortened titles, etc. */
6857                 string_expand(commit->title, sizeof(commit->title), line, 1);
6858                 view->line[view->lines - 1].dirty = 1;
6859         }
6861         return TRUE;
6864 static enum request
6865 main_request(struct view *view, enum request request, struct line *line)
6867         enum open_flags flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
6869         switch (request) {
6870         case REQ_ENTER:
6871                 open_view(view, REQ_VIEW_DIFF, flags);
6872                 break;
6873         case REQ_REFRESH:
6874                 load_refs();
6875                 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6876                 break;
6877         default:
6878                 return request;
6879         }
6881         return REQ_NONE;
6884 static bool
6885 grep_refs(struct ref_list *list, regex_t *regex)
6887         regmatch_t pmatch;
6888         size_t i;
6890         if (!opt_show_refs || !list)
6891                 return FALSE;
6893         for (i = 0; i < list->size; i++) {
6894                 if (regexec(regex, list->refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6895                         return TRUE;
6896         }
6898         return FALSE;
6901 static bool
6902 main_grep(struct view *view, struct line *line)
6904         struct commit *commit = line->data;
6905         const char *text[] = {
6906                 commit->title,
6907                 opt_author ? commit->author : "",
6908                 mkdate(&commit->time, opt_date),
6909                 NULL
6910         };
6912         return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6915 static void
6916 main_select(struct view *view, struct line *line)
6918         struct commit *commit = line->data;
6920         string_copy_rev(view->ref, commit->id);
6921         string_copy_rev(ref_commit, view->ref);
6924 static struct view_ops main_ops = {
6925         "commit",
6926         main_argv,
6927         NULL,
6928         main_read,
6929         main_draw,
6930         main_request,
6931         main_grep,
6932         main_select,
6933 };
6936 /*
6937  * Status management
6938  */
6940 /* Whether or not the curses interface has been initialized. */
6941 static bool cursed = FALSE;
6943 /* Terminal hacks and workarounds. */
6944 static bool use_scroll_redrawwin;
6945 static bool use_scroll_status_wclear;
6947 /* The status window is used for polling keystrokes. */
6948 static WINDOW *status_win;
6950 /* Reading from the prompt? */
6951 static bool input_mode = FALSE;
6953 static bool status_empty = FALSE;
6955 /* Update status and title window. */
6956 static void
6957 report(const char *msg, ...)
6959         struct view *view = display[current_view];
6961         if (input_mode)
6962                 return;
6964         if (!view) {
6965                 char buf[SIZEOF_STR];
6966                 va_list args;
6968                 va_start(args, msg);
6969                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
6970                         buf[sizeof(buf) - 1] = 0;
6971                         buf[sizeof(buf) - 2] = '.';
6972                         buf[sizeof(buf) - 3] = '.';
6973                         buf[sizeof(buf) - 4] = '.';
6974                 }
6975                 va_end(args);
6976                 die("%s", buf);
6977         }
6979         if (!status_empty || *msg) {
6980                 va_list args;
6982                 va_start(args, msg);
6984                 wmove(status_win, 0, 0);
6985                 if (view->has_scrolled && use_scroll_status_wclear)
6986                         wclear(status_win);
6987                 if (*msg) {
6988                         vwprintw(status_win, msg, args);
6989                         status_empty = FALSE;
6990                 } else {
6991                         status_empty = TRUE;
6992                 }
6993                 wclrtoeol(status_win);
6994                 wnoutrefresh(status_win);
6996                 va_end(args);
6997         }
6999         update_view_title(view);
7002 static void
7003 init_display(void)
7005         const char *term;
7006         int x, y;
7008         /* Initialize the curses library */
7009         if (isatty(STDIN_FILENO)) {
7010                 cursed = !!initscr();
7011                 opt_tty = stdin;
7012         } else {
7013                 /* Leave stdin and stdout alone when acting as a pager. */
7014                 opt_tty = fopen("/dev/tty", "r+");
7015                 if (!opt_tty)
7016                         die("Failed to open /dev/tty");
7017                 cursed = !!newterm(NULL, opt_tty, opt_tty);
7018         }
7020         if (!cursed)
7021                 die("Failed to initialize curses");
7023         nonl();         /* Disable conversion and detect newlines from input. */
7024         cbreak();       /* Take input chars one at a time, no wait for \n */
7025         noecho();       /* Don't echo input */
7026         leaveok(stdscr, FALSE);
7028         if (has_colors())
7029                 init_colors();
7031         getmaxyx(stdscr, y, x);
7032         status_win = newwin(1, 0, y - 1, 0);
7033         if (!status_win)
7034                 die("Failed to create status window");
7036         /* Enable keyboard mapping */
7037         keypad(status_win, TRUE);
7038         wbkgdset(status_win, get_line_attr(LINE_STATUS));
7040         TABSIZE = opt_tab_size;
7042         term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
7043         if (term && !strcmp(term, "gnome-terminal")) {
7044                 /* In the gnome-terminal-emulator, the message from
7045                  * scrolling up one line when impossible followed by
7046                  * scrolling down one line causes corruption of the
7047                  * status line. This is fixed by calling wclear. */
7048                 use_scroll_status_wclear = TRUE;
7049                 use_scroll_redrawwin = FALSE;
7051         } else if (term && !strcmp(term, "xrvt-xpm")) {
7052                 /* No problems with full optimizations in xrvt-(unicode)
7053                  * and aterm. */
7054                 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
7056         } else {
7057                 /* When scrolling in (u)xterm the last line in the
7058                  * scrolling direction will update slowly. */
7059                 use_scroll_redrawwin = TRUE;
7060                 use_scroll_status_wclear = FALSE;
7061         }
7064 static int
7065 get_input(int prompt_position)
7067         struct view *view;
7068         int i, key, cursor_y, cursor_x;
7070         if (prompt_position)
7071                 input_mode = TRUE;
7073         while (TRUE) {
7074                 bool loading = FALSE;
7076                 foreach_view (view, i) {
7077                         update_view(view);
7078                         if (view_is_displayed(view) && view->has_scrolled &&
7079                             use_scroll_redrawwin)
7080                                 redrawwin(view->win);
7081                         view->has_scrolled = FALSE;
7082                         if (view->pipe)
7083                                 loading = TRUE;
7084                 }
7086                 /* Update the cursor position. */
7087                 if (prompt_position) {
7088                         getbegyx(status_win, cursor_y, cursor_x);
7089                         cursor_x = prompt_position;
7090                 } else {
7091                         view = display[current_view];
7092                         getbegyx(view->win, cursor_y, cursor_x);
7093                         cursor_x = view->width - 1;
7094                         cursor_y += view->lineno - view->offset;
7095                 }
7096                 setsyx(cursor_y, cursor_x);
7098                 /* Refresh, accept single keystroke of input */
7099                 doupdate();
7100                 nodelay(status_win, loading);
7101                 key = wgetch(status_win);
7103                 /* wgetch() with nodelay() enabled returns ERR when
7104                  * there's no input. */
7105                 if (key == ERR) {
7107                 } else if (key == KEY_RESIZE) {
7108                         int height, width;
7110                         getmaxyx(stdscr, height, width);
7112                         wresize(status_win, 1, width);
7113                         mvwin(status_win, height - 1, 0);
7114                         wnoutrefresh(status_win);
7115                         resize_display();
7116                         redraw_display(TRUE);
7118                 } else {
7119                         input_mode = FALSE;
7120                         return key;
7121                 }
7122         }
7125 static char *
7126 prompt_input(const char *prompt, input_handler handler, void *data)
7128         enum input_status status = INPUT_OK;
7129         static char buf[SIZEOF_STR];
7130         size_t pos = 0;
7132         buf[pos] = 0;
7134         while (status == INPUT_OK || status == INPUT_SKIP) {
7135                 int key;
7137                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
7138                 wclrtoeol(status_win);
7140                 key = get_input(pos + 1);
7141                 switch (key) {
7142                 case KEY_RETURN:
7143                 case KEY_ENTER:
7144                 case '\n':
7145                         status = pos ? INPUT_STOP : INPUT_CANCEL;
7146                         break;
7148                 case KEY_BACKSPACE:
7149                         if (pos > 0)
7150                                 buf[--pos] = 0;
7151                         else
7152                                 status = INPUT_CANCEL;
7153                         break;
7155                 case KEY_ESC:
7156                         status = INPUT_CANCEL;
7157                         break;
7159                 default:
7160                         if (pos >= sizeof(buf)) {
7161                                 report("Input string too long");
7162                                 return NULL;
7163                         }
7165                         status = handler(data, buf, key);
7166                         if (status == INPUT_OK)
7167                                 buf[pos++] = (char) key;
7168                 }
7169         }
7171         /* Clear the status window */
7172         status_empty = FALSE;
7173         report("");
7175         if (status == INPUT_CANCEL)
7176                 return NULL;
7178         buf[pos++] = 0;
7180         return buf;
7183 static enum input_status
7184 prompt_yesno_handler(void *data, char *buf, int c)
7186         if (c == 'y' || c == 'Y')
7187                 return INPUT_STOP;
7188         if (c == 'n' || c == 'N')
7189                 return INPUT_CANCEL;
7190         return INPUT_SKIP;
7193 static bool
7194 prompt_yesno(const char *prompt)
7196         char prompt2[SIZEOF_STR];
7198         if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
7199                 return FALSE;
7201         return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
7204 static enum input_status
7205 read_prompt_handler(void *data, char *buf, int c)
7207         return isprint(c) ? INPUT_OK : INPUT_SKIP;
7210 static char *
7211 read_prompt(const char *prompt)
7213         return prompt_input(prompt, read_prompt_handler, NULL);
7216 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected)
7218         enum input_status status = INPUT_OK;
7219         int size = 0;
7221         while (items[size].text)
7222                 size++;
7224         while (status == INPUT_OK) {
7225                 const struct menu_item *item = &items[*selected];
7226                 int key;
7227                 int i;
7229                 mvwprintw(status_win, 0, 0, "%s (%d of %d) ",
7230                           prompt, *selected + 1, size);
7231                 if (item->hotkey)
7232                         wprintw(status_win, "[%c] ", (char) item->hotkey);
7233                 wprintw(status_win, "%s", item->text);
7234                 wclrtoeol(status_win);
7236                 key = get_input(COLS - 1);
7237                 switch (key) {
7238                 case KEY_RETURN:
7239                 case KEY_ENTER:
7240                 case '\n':
7241                         status = INPUT_STOP;
7242                         break;
7244                 case KEY_LEFT:
7245                 case KEY_UP:
7246                         *selected = *selected - 1;
7247                         if (*selected < 0)
7248                                 *selected = size - 1;
7249                         break;
7251                 case KEY_RIGHT:
7252                 case KEY_DOWN:
7253                         *selected = (*selected + 1) % size;
7254                         break;
7256                 case KEY_ESC:
7257                         status = INPUT_CANCEL;
7258                         break;
7260                 default:
7261                         for (i = 0; items[i].text; i++)
7262                                 if (items[i].hotkey == key) {
7263                                         *selected = i;
7264                                         status = INPUT_STOP;
7265                                         break;
7266                                 }
7267                 }
7268         }
7270         /* Clear the status window */
7271         status_empty = FALSE;
7272         report("");
7274         return status != INPUT_CANCEL;
7277 /*
7278  * Repository properties
7279  */
7281 static struct ref **refs = NULL;
7282 static size_t refs_size = 0;
7283 static struct ref *refs_head = NULL;
7285 static struct ref_list **ref_lists = NULL;
7286 static size_t ref_lists_size = 0;
7288 DEFINE_ALLOCATOR(realloc_refs, struct ref *, 256)
7289 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
7290 DEFINE_ALLOCATOR(realloc_ref_lists, struct ref_list *, 8)
7292 static int
7293 compare_refs(const void *ref1_, const void *ref2_)
7295         const struct ref *ref1 = *(const struct ref **)ref1_;
7296         const struct ref *ref2 = *(const struct ref **)ref2_;
7298         if (ref1->tag != ref2->tag)
7299                 return ref2->tag - ref1->tag;
7300         if (ref1->ltag != ref2->ltag)
7301                 return ref2->ltag - ref2->ltag;
7302         if (ref1->head != ref2->head)
7303                 return ref2->head - ref1->head;
7304         if (ref1->tracked != ref2->tracked)
7305                 return ref2->tracked - ref1->tracked;
7306         if (ref1->remote != ref2->remote)
7307                 return ref2->remote - ref1->remote;
7308         return strcmp(ref1->name, ref2->name);
7311 static void
7312 foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data)
7314         size_t i;
7316         for (i = 0; i < refs_size; i++)
7317                 if (!visitor(data, refs[i]))
7318                         break;
7321 static struct ref *
7322 get_ref_head()
7324         return refs_head;
7327 static struct ref_list *
7328 get_ref_list(const char *id)
7330         struct ref_list *list;
7331         size_t i;
7333         for (i = 0; i < ref_lists_size; i++)
7334                 if (!strcmp(id, ref_lists[i]->id))
7335                         return ref_lists[i];
7337         if (!realloc_ref_lists(&ref_lists, ref_lists_size, 1))
7338                 return NULL;
7339         list = calloc(1, sizeof(*list));
7340         if (!list)
7341                 return NULL;
7343         for (i = 0; i < refs_size; i++) {
7344                 if (!strcmp(id, refs[i]->id) &&
7345                     realloc_refs_list(&list->refs, list->size, 1))
7346                         list->refs[list->size++] = refs[i];
7347         }
7349         if (!list->refs) {
7350                 free(list);
7351                 return NULL;
7352         }
7354         qsort(list->refs, list->size, sizeof(*list->refs), compare_refs);
7355         ref_lists[ref_lists_size++] = list;
7356         return list;
7359 static int
7360 read_ref(char *id, size_t idlen, char *name, size_t namelen)
7362         struct ref *ref = NULL;
7363         bool tag = FALSE;
7364         bool ltag = FALSE;
7365         bool remote = FALSE;
7366         bool tracked = FALSE;
7367         bool head = FALSE;
7368         int from = 0, to = refs_size - 1;
7370         if (!prefixcmp(name, "refs/tags/")) {
7371                 if (!suffixcmp(name, namelen, "^{}")) {
7372                         namelen -= 3;
7373                         name[namelen] = 0;
7374                 } else {
7375                         ltag = TRUE;
7376                 }
7378                 tag = TRUE;
7379                 namelen -= STRING_SIZE("refs/tags/");
7380                 name    += STRING_SIZE("refs/tags/");
7382         } else if (!prefixcmp(name, "refs/remotes/")) {
7383                 remote = TRUE;
7384                 namelen -= STRING_SIZE("refs/remotes/");
7385                 name    += STRING_SIZE("refs/remotes/");
7386                 tracked  = !strcmp(opt_remote, name);
7388         } else if (!prefixcmp(name, "refs/heads/")) {
7389                 namelen -= STRING_SIZE("refs/heads/");
7390                 name    += STRING_SIZE("refs/heads/");
7391                 if (!strncmp(opt_head, name, namelen))
7392                         return OK;
7394         } else if (!strcmp(name, "HEAD")) {
7395                 head     = TRUE;
7396                 if (*opt_head) {
7397                         namelen  = strlen(opt_head);
7398                         name     = opt_head;
7399                 }
7400         }
7402         /* If we are reloading or it's an annotated tag, replace the
7403          * previous SHA1 with the resolved commit id; relies on the fact
7404          * git-ls-remote lists the commit id of an annotated tag right
7405          * before the commit id it points to. */
7406         while (from <= to) {
7407                 size_t pos = (to + from) / 2;
7408                 int cmp = strcmp(name, refs[pos]->name);
7410                 if (!cmp) {
7411                         ref = refs[pos];
7412                         break;
7413                 }
7415                 if (cmp < 0)
7416                         to = pos - 1;
7417                 else
7418                         from = pos + 1;
7419         }
7421         if (!ref) {
7422                 if (!realloc_refs(&refs, refs_size, 1))
7423                         return ERR;
7424                 ref = calloc(1, sizeof(*ref) + namelen);
7425                 if (!ref)
7426                         return ERR;
7427                 memmove(refs + from + 1, refs + from,
7428                         (refs_size - from) * sizeof(*refs));
7429                 refs[from] = ref;
7430                 strncpy(ref->name, name, namelen);
7431                 refs_size++;
7432         }
7434         ref->head = head;
7435         ref->tag = tag;
7436         ref->ltag = ltag;
7437         ref->remote = remote;
7438         ref->tracked = tracked;
7439         string_copy_rev(ref->id, id);
7441         if (head)
7442                 refs_head = ref;
7443         return OK;
7446 static int
7447 load_refs(void)
7449         const char *head_argv[] = {
7450                 "git", "symbolic-ref", "HEAD", NULL
7451         };
7452         static const char *ls_remote_argv[SIZEOF_ARG] = {
7453                 "git", "ls-remote", opt_git_dir, NULL
7454         };
7455         static bool init = FALSE;
7456         size_t i;
7458         if (!init) {
7459                 if (!argv_from_env(ls_remote_argv, "TIG_LS_REMOTE"))
7460                         die("TIG_LS_REMOTE contains too many arguments");
7461                 init = TRUE;
7462         }
7464         if (!*opt_git_dir)
7465                 return OK;
7467         if (io_run_buf(head_argv, opt_head, sizeof(opt_head)) &&
7468             !prefixcmp(opt_head, "refs/heads/")) {
7469                 char *offset = opt_head + STRING_SIZE("refs/heads/");
7471                 memmove(opt_head, offset, strlen(offset) + 1);
7472         }
7474         refs_head = NULL;
7475         for (i = 0; i < refs_size; i++)
7476                 refs[i]->id[0] = 0;
7478         if (io_run_load(ls_remote_argv, "\t", read_ref) == ERR)
7479                 return ERR;
7481         /* Update the ref lists to reflect changes. */
7482         for (i = 0; i < ref_lists_size; i++) {
7483                 struct ref_list *list = ref_lists[i];
7484                 size_t old, new;
7486                 for (old = new = 0; old < list->size; old++)
7487                         if (!strcmp(list->id, list->refs[old]->id))
7488                                 list->refs[new++] = list->refs[old];
7489                 list->size = new;
7490         }
7492         return OK;
7495 static void
7496 set_remote_branch(const char *name, const char *value, size_t valuelen)
7498         if (!strcmp(name, ".remote")) {
7499                 string_ncopy(opt_remote, value, valuelen);
7501         } else if (*opt_remote && !strcmp(name, ".merge")) {
7502                 size_t from = strlen(opt_remote);
7504                 if (!prefixcmp(value, "refs/heads/"))
7505                         value += STRING_SIZE("refs/heads/");
7507                 if (!string_format_from(opt_remote, &from, "/%s", value))
7508                         opt_remote[0] = 0;
7509         }
7512 static void
7513 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
7515         const char *argv[SIZEOF_ARG] = { name, "=" };
7516         int argc = 1 + (cmd == option_set_command);
7517         int error = ERR;
7519         if (!argv_from_string(argv, &argc, value))
7520                 config_msg = "Too many option arguments";
7521         else
7522                 error = cmd(argc, argv);
7524         if (error == ERR)
7525                 warn("Option 'tig.%s': %s", name, config_msg);
7528 static bool
7529 set_environment_variable(const char *name, const char *value)
7531         size_t len = strlen(name) + 1 + strlen(value) + 1;
7532         char *env = malloc(len);
7534         if (env &&
7535             string_nformat(env, len, NULL, "%s=%s", name, value) &&
7536             putenv(env) == 0)
7537                 return TRUE;
7538         free(env);
7539         return FALSE;
7542 static void
7543 set_work_tree(const char *value)
7545         char cwd[SIZEOF_STR];
7547         if (!getcwd(cwd, sizeof(cwd)))
7548                 die("Failed to get cwd path: %s", strerror(errno));
7549         if (chdir(opt_git_dir) < 0)
7550                 die("Failed to chdir(%s): %s", strerror(errno));
7551         if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7552                 die("Failed to get git path: %s", strerror(errno));
7553         if (chdir(cwd) < 0)
7554                 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7555         if (chdir(value) < 0)
7556                 die("Failed to chdir(%s): %s", value, strerror(errno));
7557         if (!getcwd(cwd, sizeof(cwd)))
7558                 die("Failed to get cwd path: %s", strerror(errno));
7559         if (!set_environment_variable("GIT_WORK_TREE", cwd))
7560                 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7561         if (!set_environment_variable("GIT_DIR", opt_git_dir))
7562                 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7563         opt_is_inside_work_tree = TRUE;
7566 static int
7567 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
7569         if (!strcmp(name, "i18n.commitencoding"))
7570                 string_ncopy(opt_encoding, value, valuelen);
7572         else if (!strcmp(name, "core.editor"))
7573                 string_ncopy(opt_editor, value, valuelen);
7575         else if (!strcmp(name, "core.worktree"))
7576                 set_work_tree(value);
7578         else if (!prefixcmp(name, "tig.color."))
7579                 set_repo_config_option(name + 10, value, option_color_command);
7581         else if (!prefixcmp(name, "tig.bind."))
7582                 set_repo_config_option(name + 9, value, option_bind_command);
7584         else if (!prefixcmp(name, "tig."))
7585                 set_repo_config_option(name + 4, value, option_set_command);
7587         else if (*opt_head && !prefixcmp(name, "branch.") &&
7588                  !strncmp(name + 7, opt_head, strlen(opt_head)))
7589                 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7591         return OK;
7594 static int
7595 load_git_config(void)
7597         const char *config_list_argv[] = { "git", "config", "--list", NULL };
7599         return io_run_load(config_list_argv, "=", read_repo_config_option);
7602 static int
7603 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
7605         if (!opt_git_dir[0]) {
7606                 string_ncopy(opt_git_dir, name, namelen);
7608         } else if (opt_is_inside_work_tree == -1) {
7609                 /* This can be 3 different values depending on the
7610                  * version of git being used. If git-rev-parse does not
7611                  * understand --is-inside-work-tree it will simply echo
7612                  * the option else either "true" or "false" is printed.
7613                  * Default to true for the unknown case. */
7614                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7616         } else if (*name == '.') {
7617                 string_ncopy(opt_cdup, name, namelen);
7619         } else {
7620                 string_ncopy(opt_prefix, name, namelen);
7621         }
7623         return OK;
7626 static int
7627 load_repo_info(void)
7629         const char *rev_parse_argv[] = {
7630                 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7631                         "--show-cdup", "--show-prefix", NULL
7632         };
7634         return io_run_load(rev_parse_argv, "=", read_repo_info);
7638 /*
7639  * Main
7640  */
7642 static const char usage[] =
7643 "tig " TIG_VERSION " (" __DATE__ ")\n"
7644 "\n"
7645 "Usage: tig        [options] [revs] [--] [paths]\n"
7646 "   or: tig show   [options] [revs] [--] [paths]\n"
7647 "   or: tig blame  [rev] path\n"
7648 "   or: tig status\n"
7649 "   or: tig <      [git command output]\n"
7650 "\n"
7651 "Options:\n"
7652 "  -v, --version   Show version and exit\n"
7653 "  -h, --help      Show help message and exit";
7655 static void __NORETURN
7656 quit(int sig)
7658         /* XXX: Restore tty modes and let the OS cleanup the rest! */
7659         if (cursed)
7660                 endwin();
7661         exit(0);
7664 static void __NORETURN
7665 die(const char *err, ...)
7667         va_list args;
7669         endwin();
7671         va_start(args, err);
7672         fputs("tig: ", stderr);
7673         vfprintf(stderr, err, args);
7674         fputs("\n", stderr);
7675         va_end(args);
7677         exit(1);
7680 static void
7681 warn(const char *msg, ...)
7683         va_list args;
7685         va_start(args, msg);
7686         fputs("tig warning: ", stderr);
7687         vfprintf(stderr, msg, args);
7688         fputs("\n", stderr);
7689         va_end(args);
7692 static const char ***filter_args;
7694 static int
7695 read_filter_args(char *name, size_t namelen, char *value, size_t valuelen)
7697         return argv_append(filter_args, name) ? OK : ERR;
7700 static void
7701 filter_rev_parse(const char ***args, const char *arg1, const char *arg2, const char *argv[])
7703         const char *rev_parse_argv[SIZEOF_ARG] = { "git", "rev-parse", arg1, arg2 };
7704         const char **all_argv = NULL;
7706         filter_args = args;
7707         if (!argv_append_array(&all_argv, rev_parse_argv) ||
7708             !argv_append_array(&all_argv, argv) ||
7709             !io_run_load(all_argv, "\n", read_filter_args) == ERR)
7710                 die("Failed to split arguments");
7711         argv_free(all_argv);
7712         free(all_argv);
7715 static void
7716 filter_options(const char *argv[])
7718         filter_rev_parse(&opt_file_args, "--no-revs", "--no-flags", argv);
7719         filter_rev_parse(&opt_diff_args, "--no-revs", "--flags", argv);
7720         filter_rev_parse(&opt_rev_args, "--symbolic", "--revs-only", argv);
7723 static enum request
7724 parse_options(int argc, const char *argv[])
7726         enum request request = REQ_VIEW_MAIN;
7727         const char *subcommand;
7728         bool seen_dashdash = FALSE;
7729         const char **filter_argv = NULL;
7730         int i;
7732         if (!isatty(STDIN_FILENO)) {
7733                 io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
7734                 return REQ_VIEW_PAGER;
7735         }
7737         if (argc <= 1)
7738                 return REQ_VIEW_MAIN;
7740         subcommand = argv[1];
7741         if (!strcmp(subcommand, "status")) {
7742                 if (argc > 2)
7743                         warn("ignoring arguments after `%s'", subcommand);
7744                 return REQ_VIEW_STATUS;
7746         } else if (!strcmp(subcommand, "blame")) {
7747                 if (argc <= 2 || argc > 4)
7748                         die("invalid number of options to blame\n\n%s", usage);
7750                 i = 2;
7751                 if (argc == 4) {
7752                         string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7753                         i++;
7754                 }
7756                 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7757                 return REQ_VIEW_BLAME;
7759         } else if (!strcmp(subcommand, "show")) {
7760                 request = REQ_VIEW_DIFF;
7762         } else {
7763                 subcommand = NULL;
7764         }
7766         for (i = 1 + !!subcommand; i < argc; i++) {
7767                 const char *opt = argv[i];
7769                 if (seen_dashdash) {
7770                         argv_append(&opt_file_args, opt);
7771                         continue;
7773                 } else if (!strcmp(opt, "--")) {
7774                         seen_dashdash = TRUE;
7775                         continue;
7777                 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7778                         printf("tig version %s\n", TIG_VERSION);
7779                         quit(0);
7781                 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7782                         printf("%s\n", usage);
7783                         quit(0);
7785                 } else if (!strcmp(opt, "--all")) {
7786                         argv_append(&opt_rev_args, opt);
7787                         continue;
7788                 }
7790                 if (!argv_append(&filter_argv, opt))
7791                         die("command too long");
7792         }
7794         if (filter_argv)
7795                 filter_options(filter_argv);
7797         return request;
7800 int
7801 main(int argc, const char *argv[])
7803         const char *codeset = "UTF-8";
7804         enum request request = parse_options(argc, argv);
7805         struct view *view;
7806         size_t i;
7808         signal(SIGINT, quit);
7809         signal(SIGPIPE, SIG_IGN);
7811         if (setlocale(LC_ALL, "")) {
7812                 codeset = nl_langinfo(CODESET);
7813         }
7815         if (load_repo_info() == ERR)
7816                 die("Failed to load repo info.");
7818         if (load_options() == ERR)
7819                 die("Failed to load user config.");
7821         if (load_git_config() == ERR)
7822                 die("Failed to load repo config.");
7824         /* Require a git repository unless when running in pager mode. */
7825         if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7826                 die("Not a git repository");
7828         if (*opt_encoding && strcmp(codeset, "UTF-8")) {
7829                 opt_iconv_in = iconv_open("UTF-8", opt_encoding);
7830                 if (opt_iconv_in == ICONV_NONE)
7831                         die("Failed to initialize character set conversion");
7832         }
7834         if (codeset && strcmp(codeset, "UTF-8")) {
7835                 opt_iconv_out = iconv_open(codeset, "UTF-8");
7836                 if (opt_iconv_out == ICONV_NONE)
7837                         die("Failed to initialize character set conversion");
7838         }
7840         if (load_refs() == ERR)
7841                 die("Failed to load refs.");
7843         foreach_view (view, i) {
7844                 if (getenv(view->cmd_env))
7845                         warn("Use of the %s environment variable is deprecated,"
7846                              " use options or TIG_DIFF_ARGS instead",
7847                              view->cmd_env);
7848                 if (!argv_from_env(view->ops->argv, view->cmd_env))
7849                         die("Too many arguments in the `%s` environment variable",
7850                             view->cmd_env);
7851         }
7853         init_display();
7855         while (view_driver(display[current_view], request)) {
7856                 int key = get_input(0);
7858                 view = display[current_view];
7859                 request = get_keybinding(view->keymap, key);
7861                 /* Some low-level request handling. This keeps access to
7862                  * status_win restricted. */
7863                 switch (request) {
7864                 case REQ_NONE:
7865                         report("Unknown key, press %s for help",
7866                                get_key(view->keymap, REQ_VIEW_HELP));
7867                         break;
7868                 case REQ_PROMPT:
7869                 {
7870                         char *cmd = read_prompt(":");
7872                         if (cmd && isdigit(*cmd)) {
7873                                 int lineno = view->lineno + 1;
7875                                 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7876                                         select_view_line(view, lineno - 1);
7877                                         report("");
7878                                 } else {
7879                                         report("Unable to parse '%s' as a line number", cmd);
7880                                 }
7882                         } else if (cmd) {
7883                                 struct view *next = VIEW(REQ_VIEW_PAGER);
7884                                 const char *argv[SIZEOF_ARG] = { "git" };
7885                                 int argc = 1;
7887                                 /* When running random commands, initially show the
7888                                  * command in the title. However, it maybe later be
7889                                  * overwritten if a commit line is selected. */
7890                                 string_ncopy(next->ref, cmd, strlen(cmd));
7892                                 if (!argv_from_string(argv, &argc, cmd)) {
7893                                         report("Too many arguments");
7894                                 } else if (!prepare_update(next, argv, NULL)) {
7895                                         report("Failed to format command");
7896                                 } else {
7897                                         open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7898                                 }
7899                         }
7901                         request = REQ_NONE;
7902                         break;
7903                 }
7904                 case REQ_SEARCH:
7905                 case REQ_SEARCH_BACK:
7906                 {
7907                         const char *prompt = request == REQ_SEARCH ? "/" : "?";
7908                         char *search = read_prompt(prompt);
7910                         if (search)
7911                                 string_ncopy(opt_search, search, strlen(search));
7912                         else if (*opt_search)
7913                                 request = request == REQ_SEARCH ?
7914                                         REQ_FIND_NEXT :
7915                                         REQ_FIND_PREV;
7916                         else
7917                                 request = REQ_NONE;
7918                         break;
7919                 }
7920                 default:
7921                         break;
7922                 }
7923         }
7925         quit(0);
7927         return 0;