Code

Fix segfault when starting tig in pager mode
[tig.git] / tig.c
1 /* Copyright (c) 2006-2010 Jonas Fonseca <fonseca@diku.dk>
2  *
3  * This program is free software; you can redistribute it and/or
4  * modify it under the terms of the GNU General Public License as
5  * published by the Free Software Foundation; either version 2 of
6  * the License, or (at your option) any later version.
7  *
8  * This program is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11  * GNU General Public License for more details.
12  */
14 #ifdef HAVE_CONFIG_H
15 #include "config.h"
16 #endif
18 #ifndef TIG_VERSION
19 #define TIG_VERSION "unknown-version"
20 #endif
22 #ifndef DEBUG
23 #define NDEBUG
24 #endif
26 #include <assert.h>
27 #include <errno.h>
28 #include <ctype.h>
29 #include <signal.h>
30 #include <stdarg.h>
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <sys/types.h>
35 #include <sys/wait.h>
36 #include <sys/stat.h>
37 #include <sys/select.h>
38 #include <unistd.h>
39 #include <sys/time.h>
40 #include <time.h>
41 #include <fcntl.h>
43 #include <regex.h>
45 #include <locale.h>
46 #include <langinfo.h>
47 #include <iconv.h>
49 /* ncurses(3): Must be defined to have extended wide-character functions. */
50 #define _XOPEN_SOURCE_EXTENDED
52 #ifdef HAVE_NCURSESW_NCURSES_H
53 #include <ncursesw/ncurses.h>
54 #else
55 #ifdef HAVE_NCURSES_NCURSES_H
56 #include <ncurses/ncurses.h>
57 #else
58 #include <ncurses.h>
59 #endif
60 #endif
62 #if __GNUC__ >= 3
63 #define __NORETURN __attribute__((__noreturn__))
64 #else
65 #define __NORETURN
66 #endif
68 static void __NORETURN die(const char *err, ...);
69 static void warn(const char *msg, ...);
70 static void report(const char *msg, ...);
72 #define ABS(x)          ((x) >= 0  ? (x) : -(x))
73 #define MIN(x, y)       ((x) < (y) ? (x) :  (y))
74 #define MAX(x, y)       ((x) > (y) ? (x) :  (y))
76 #define ARRAY_SIZE(x)   (sizeof(x) / sizeof(x[0]))
77 #define STRING_SIZE(x)  (sizeof(x) - 1)
79 #define SIZEOF_STR      1024    /* Default string size. */
80 #define SIZEOF_REF      256     /* Size of symbolic or SHA1 ID. */
81 #define SIZEOF_REV      41      /* Holds a SHA-1 and an ending NUL. */
82 #define SIZEOF_ARG      32      /* Default argument array size. */
84 /* Revision graph */
86 #define REVGRAPH_INIT   'I'
87 #define REVGRAPH_MERGE  'M'
88 #define REVGRAPH_BRANCH '+'
89 #define REVGRAPH_COMMIT '*'
90 #define REVGRAPH_BOUND  '^'
92 #define SIZEOF_REVGRAPH 19      /* Size of revision ancestry graphics. */
94 /* This color name can be used to refer to the default term colors. */
95 #define COLOR_DEFAULT   (-1)
97 #define ICONV_NONE      ((iconv_t) -1)
98 #ifndef ICONV_CONST
99 #define ICONV_CONST     /* nothing */
100 #endif
102 /* The format and size of the date column in the main view. */
103 #define DATE_FORMAT     "%Y-%m-%d %H:%M"
104 #define DATE_COLS       STRING_SIZE("2006-04-29 14:21 ")
105 #define DATE_SHORT_COLS STRING_SIZE("2006-04-29 ")
107 #define ID_COLS         8
108 #define AUTHOR_COLS     19
110 #define MIN_VIEW_HEIGHT 4
112 #define NULL_ID         "0000000000000000000000000000000000000000"
114 #define S_ISGITLINK(mode) (((mode) & S_IFMT) == 0160000)
116 /* Some ASCII-shorthands fitted into the ncurses namespace. */
117 #define KEY_TAB         '\t'
118 #define KEY_RETURN      '\r'
119 #define KEY_ESC         27
122 struct ref {
123         char id[SIZEOF_REV];    /* Commit SHA1 ID */
124         unsigned int head:1;    /* Is it the current HEAD? */
125         unsigned int tag:1;     /* Is it a tag? */
126         unsigned int ltag:1;    /* If so, is the tag local? */
127         unsigned int remote:1;  /* Is it a remote ref? */
128         unsigned int tracked:1; /* Is it the remote for the current HEAD? */
129         char name[1];           /* Ref name; tag or head names are shortened. */
130 };
132 struct ref_list {
133         char id[SIZEOF_REV];    /* Commit SHA1 ID */
134         size_t size;            /* Number of refs. */
135         struct ref **refs;      /* References for this ID. */
136 };
138 static struct ref *get_ref_head();
139 static struct ref_list *get_ref_list(const char *id);
140 static void foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data);
141 static int load_refs(void);
143 enum input_status {
144         INPUT_OK,
145         INPUT_SKIP,
146         INPUT_STOP,
147         INPUT_CANCEL
148 };
150 typedef enum input_status (*input_handler)(void *data, char *buf, int c);
152 static char *prompt_input(const char *prompt, input_handler handler, void *data);
153 static bool prompt_yesno(const char *prompt);
155 struct menu_item {
156         int hotkey;
157         const char *text;
158         void *data;
159 };
161 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected);
163 /*
164  * Allocation helpers ... Entering macro hell to never be seen again.
165  */
167 #define DEFINE_ALLOCATOR(name, type, chunk_size)                                \
168 static type *                                                                   \
169 name(type **mem, size_t size, size_t increase)                                  \
170 {                                                                               \
171         size_t num_chunks = (size + chunk_size - 1) / chunk_size;               \
172         size_t num_chunks_new = (size + increase + chunk_size - 1) / chunk_size;\
173         type *tmp = *mem;                                                       \
174                                                                                 \
175         if (mem == NULL || num_chunks != num_chunks_new) {                      \
176                 tmp = realloc(tmp, num_chunks_new * chunk_size * sizeof(type)); \
177                 if (tmp)                                                        \
178                         *mem = tmp;                                             \
179         }                                                                       \
180                                                                                 \
181         return tmp;                                                             \
184 /*
185  * String helpers
186  */
188 static inline void
189 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
191         if (srclen > dstlen - 1)
192                 srclen = dstlen - 1;
194         strncpy(dst, src, srclen);
195         dst[srclen] = 0;
198 /* Shorthands for safely copying into a fixed buffer. */
200 #define string_copy(dst, src) \
201         string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
203 #define string_ncopy(dst, src, srclen) \
204         string_ncopy_do(dst, sizeof(dst), src, srclen)
206 #define string_copy_rev(dst, src) \
207         string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
209 #define string_add(dst, from, src) \
210         string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
212 static size_t
213 string_expand(char *dst, size_t dstlen, const char *src, int tabsize)
215         size_t size, pos;
217         for (size = pos = 0; size < dstlen - 1 && src[pos]; pos++) {
218                 if (src[pos] == '\t') {
219                         size_t expanded = tabsize - (size % tabsize);
221                         if (expanded + size >= dstlen - 1)
222                                 expanded = dstlen - size - 1;
223                         memcpy(dst + size, "        ", expanded);
224                         size += expanded;
225                 } else {
226                         dst[size++] = src[pos];
227                 }
228         }
230         dst[size] = 0;
231         return pos;
234 static char *
235 chomp_string(char *name)
237         int namelen;
239         while (isspace(*name))
240                 name++;
242         namelen = strlen(name) - 1;
243         while (namelen > 0 && isspace(name[namelen]))
244                 name[namelen--] = 0;
246         return name;
249 static bool
250 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
252         va_list args;
253         size_t pos = bufpos ? *bufpos : 0;
255         va_start(args, fmt);
256         pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
257         va_end(args);
259         if (bufpos)
260                 *bufpos = pos;
262         return pos >= bufsize ? FALSE : TRUE;
265 #define string_format(buf, fmt, args...) \
266         string_nformat(buf, sizeof(buf), NULL, fmt, args)
268 #define string_format_from(buf, from, fmt, args...) \
269         string_nformat(buf, sizeof(buf), from, fmt, args)
271 static int
272 string_enum_compare(const char *str1, const char *str2, int len)
274         size_t i;
276 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
278         /* Diff-Header == DIFF_HEADER */
279         for (i = 0; i < len; i++) {
280                 if (toupper(str1[i]) == toupper(str2[i]))
281                         continue;
283                 if (string_enum_sep(str1[i]) &&
284                     string_enum_sep(str2[i]))
285                         continue;
287                 return str1[i] - str2[i];
288         }
290         return 0;
293 #define enum_equals(entry, str, len) \
294         ((entry).namelen == (len) && !string_enum_compare((entry).name, str, len))
296 struct enum_map {
297         const char *name;
298         int namelen;
299         int value;
300 };
302 #define ENUM_MAP(name, value) { name, STRING_SIZE(name), value }
304 static char *
305 enum_map_name(const char *name, size_t namelen)
307         static char buf[SIZEOF_STR];
308         int bufpos;
310         for (bufpos = 0; bufpos <= namelen; bufpos++) {
311                 buf[bufpos] = tolower(name[bufpos]);
312                 if (buf[bufpos] == '_')
313                         buf[bufpos] = '-';
314         }
316         buf[bufpos] = 0;
317         return buf;
320 #define enum_name(entry) enum_map_name((entry).name, (entry).namelen)
322 static bool
323 map_enum_do(const struct enum_map *map, size_t map_size, int *value, const char *name)
325         size_t namelen = strlen(name);
326         int i;
328         for (i = 0; i < map_size; i++)
329                 if (enum_equals(map[i], name, namelen)) {
330                         *value = map[i].value;
331                         return TRUE;
332                 }
334         return FALSE;
337 #define map_enum(attr, map, name) \
338         map_enum_do(map, ARRAY_SIZE(map), attr, name)
340 #define prefixcmp(str1, str2) \
341         strncmp(str1, str2, STRING_SIZE(str2))
343 static inline int
344 suffixcmp(const char *str, int slen, const char *suffix)
346         size_t len = slen >= 0 ? slen : strlen(str);
347         size_t suffixlen = strlen(suffix);
349         return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
353 /*
354  * Unicode / UTF-8 handling
355  *
356  * NOTE: Much of the following code for dealing with Unicode is derived from
357  * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
358  * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
359  */
361 static inline int
362 unicode_width(unsigned long c, int tab_size)
364         if (c >= 0x1100 &&
365            (c <= 0x115f                         /* Hangul Jamo */
366             || c == 0x2329
367             || c == 0x232a
368             || (c >= 0x2e80  && c <= 0xa4cf && c != 0x303f)
369                                                 /* CJK ... Yi */
370             || (c >= 0xac00  && c <= 0xd7a3)    /* Hangul Syllables */
371             || (c >= 0xf900  && c <= 0xfaff)    /* CJK Compatibility Ideographs */
372             || (c >= 0xfe30  && c <= 0xfe6f)    /* CJK Compatibility Forms */
373             || (c >= 0xff00  && c <= 0xff60)    /* Fullwidth Forms */
374             || (c >= 0xffe0  && c <= 0xffe6)
375             || (c >= 0x20000 && c <= 0x2fffd)
376             || (c >= 0x30000 && c <= 0x3fffd)))
377                 return 2;
379         if (c == '\t')
380                 return tab_size;
382         return 1;
385 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
386  * Illegal bytes are set one. */
387 static const unsigned char utf8_bytes[256] = {
388         1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
389         1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
390         1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
391         1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
392         1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
393         1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
394         2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2,
395         3,3,3,3,3,3,3,3, 3,3,3,3,3,3,3,3, 4,4,4,4,4,4,4,4, 5,5,5,5,6,6,1,1,
396 };
398 static inline unsigned char
399 utf8_char_length(const char *string, const char *end)
401         int c = *(unsigned char *) string;
403         return utf8_bytes[c];
406 /* Decode UTF-8 multi-byte representation into a Unicode character. */
407 static inline unsigned long
408 utf8_to_unicode(const char *string, size_t length)
410         unsigned long unicode;
412         switch (length) {
413         case 1:
414                 unicode  =   string[0];
415                 break;
416         case 2:
417                 unicode  =  (string[0] & 0x1f) << 6;
418                 unicode +=  (string[1] & 0x3f);
419                 break;
420         case 3:
421                 unicode  =  (string[0] & 0x0f) << 12;
422                 unicode += ((string[1] & 0x3f) << 6);
423                 unicode +=  (string[2] & 0x3f);
424                 break;
425         case 4:
426                 unicode  =  (string[0] & 0x0f) << 18;
427                 unicode += ((string[1] & 0x3f) << 12);
428                 unicode += ((string[2] & 0x3f) << 6);
429                 unicode +=  (string[3] & 0x3f);
430                 break;
431         case 5:
432                 unicode  =  (string[0] & 0x0f) << 24;
433                 unicode += ((string[1] & 0x3f) << 18);
434                 unicode += ((string[2] & 0x3f) << 12);
435                 unicode += ((string[3] & 0x3f) << 6);
436                 unicode +=  (string[4] & 0x3f);
437                 break;
438         case 6:
439                 unicode  =  (string[0] & 0x01) << 30;
440                 unicode += ((string[1] & 0x3f) << 24);
441                 unicode += ((string[2] & 0x3f) << 18);
442                 unicode += ((string[3] & 0x3f) << 12);
443                 unicode += ((string[4] & 0x3f) << 6);
444                 unicode +=  (string[5] & 0x3f);
445                 break;
446         default:
447                 return 0;
448         }
450         /* Invalid characters could return the special 0xfffd value but NUL
451          * should be just as good. */
452         return unicode > 0xffff ? 0 : unicode;
455 /* Calculates how much of string can be shown within the given maximum width
456  * and sets trimmed parameter to non-zero value if all of string could not be
457  * shown. If the reserve flag is TRUE, it will reserve at least one
458  * trailing character, which can be useful when drawing a delimiter.
459  *
460  * Returns the number of bytes to output from string to satisfy max_width. */
461 static size_t
462 utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve, int tab_size)
464         const char *string = *start;
465         const char *end = strchr(string, '\0');
466         unsigned char last_bytes = 0;
467         size_t last_ucwidth = 0;
469         *width = 0;
470         *trimmed = 0;
472         while (string < end) {
473                 unsigned char bytes = utf8_char_length(string, end);
474                 size_t ucwidth;
475                 unsigned long unicode;
477                 if (string + bytes > end)
478                         break;
480                 /* Change representation to figure out whether
481                  * it is a single- or double-width character. */
483                 unicode = utf8_to_unicode(string, bytes);
484                 /* FIXME: Graceful handling of invalid Unicode character. */
485                 if (!unicode)
486                         break;
488                 ucwidth = unicode_width(unicode, tab_size);
489                 if (skip > 0) {
490                         skip -= ucwidth <= skip ? ucwidth : skip;
491                         *start += bytes;
492                 }
493                 *width  += ucwidth;
494                 if (*width > max_width) {
495                         *trimmed = 1;
496                         *width -= ucwidth;
497                         if (reserve && *width == max_width) {
498                                 string -= last_bytes;
499                                 *width -= last_ucwidth;
500                         }
501                         break;
502                 }
504                 string  += bytes;
505                 last_bytes = ucwidth ? bytes : 0;
506                 last_ucwidth = ucwidth;
507         }
509         return string - *start;
513 #define DATE_INFO \
514         DATE_(NO), \
515         DATE_(DEFAULT), \
516         DATE_(LOCAL), \
517         DATE_(RELATIVE), \
518         DATE_(SHORT)
520 enum date {
521 #define DATE_(name) DATE_##name
522         DATE_INFO
523 #undef  DATE_
524 };
526 static const struct enum_map date_map[] = {
527 #define DATE_(name) ENUM_MAP(#name, DATE_##name)
528         DATE_INFO
529 #undef  DATE_
530 };
532 struct time {
533         time_t sec;
534         int tz;
535 };
537 static inline int timecmp(const struct time *t1, const struct time *t2)
539         return t1->sec - t2->sec;
542 static const char *
543 mkdate(const struct time *time, enum date date)
545         static char buf[DATE_COLS + 1];
546         static const struct enum_map reldate[] = {
547                 { "second", 1,                  60 * 2 },
548                 { "minute", 60,                 60 * 60 * 2 },
549                 { "hour",   60 * 60,            60 * 60 * 24 * 2 },
550                 { "day",    60 * 60 * 24,       60 * 60 * 24 * 7 * 2 },
551                 { "week",   60 * 60 * 24 * 7,   60 * 60 * 24 * 7 * 5 },
552                 { "month",  60 * 60 * 24 * 30,  60 * 60 * 24 * 30 * 12 },
553         };
554         struct tm tm;
556         if (!date || !time || !time->sec)
557                 return "";
559         if (date == DATE_RELATIVE) {
560                 struct timeval now;
561                 time_t date = time->sec + time->tz;
562                 time_t seconds;
563                 int i;
565                 gettimeofday(&now, NULL);
566                 seconds = now.tv_sec < date ? date - now.tv_sec : now.tv_sec - date;
567                 for (i = 0; i < ARRAY_SIZE(reldate); i++) {
568                         if (seconds >= reldate[i].value)
569                                 continue;
571                         seconds /= reldate[i].namelen;
572                         if (!string_format(buf, "%ld %s%s %s",
573                                            seconds, reldate[i].name,
574                                            seconds > 1 ? "s" : "",
575                                            now.tv_sec >= date ? "ago" : "ahead"))
576                                 break;
577                         return buf;
578                 }
579         }
581         if (date == DATE_LOCAL) {
582                 time_t date = time->sec + time->tz;
583                 localtime_r(&date, &tm);
584         }
585         else {
586                 gmtime_r(&time->sec, &tm);
587         }
588         return strftime(buf, sizeof(buf), DATE_FORMAT, &tm) ? buf : NULL;
592 #define AUTHOR_VALUES \
593         AUTHOR_(NO), \
594         AUTHOR_(FULL), \
595         AUTHOR_(ABBREVIATED)
597 enum author {
598 #define AUTHOR_(name) AUTHOR_##name
599         AUTHOR_VALUES,
600 #undef  AUTHOR_
601         AUTHOR_DEFAULT = AUTHOR_FULL
602 };
604 static const struct enum_map author_map[] = {
605 #define AUTHOR_(name) ENUM_MAP(#name, AUTHOR_##name)
606         AUTHOR_VALUES
607 #undef  AUTHOR_
608 };
610 static const char *
611 get_author_initials(const char *author)
613         static char initials[AUTHOR_COLS * 6 + 1];
614         size_t pos = 0;
615         const char *end = strchr(author, '\0');
617 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@' || (c) == '-')
619         memset(initials, 0, sizeof(initials));
620         while (author < end) {
621                 unsigned char bytes;
622                 size_t i;
624                 while (is_initial_sep(*author))
625                         author++;
627                 bytes = utf8_char_length(author, end);
628                 if (bytes < sizeof(initials) - 1 - pos) {
629                         while (bytes--) {
630                                 initials[pos++] = *author++;
631                         }
632                 }
634                 for (i = pos; author < end && !is_initial_sep(*author); author++) {
635                         if (i < sizeof(initials) - 1)
636                                 initials[i++] = *author;
637                 }
639                 initials[i++] = 0;
640         }
642         return initials;
646 static bool
647 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
649         int valuelen;
651         while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
652                 bool advance = cmd[valuelen] != 0;
654                 cmd[valuelen] = 0;
655                 argv[(*argc)++] = chomp_string(cmd);
656                 cmd = chomp_string(cmd + valuelen + advance);
657         }
659         if (*argc < SIZEOF_ARG)
660                 argv[*argc] = NULL;
661         return *argc < SIZEOF_ARG;
664 static bool
665 argv_from_env(const char **argv, const char *name)
667         char *env = argv ? getenv(name) : NULL;
668         int argc = 0;
670         if (env && *env)
671                 env = strdup(env);
672         return !env || argv_from_string(argv, &argc, env);
675 static void
676 argv_free(const char *argv[])
678         int argc;
680         if (!argv)
681                 return;
682         for (argc = 0; argv[argc]; argc++)
683                 free((void *) argv[argc]);
684         argv[0] = NULL;
687 static size_t
688 argv_size(const char **argv)
690         int argc = 0;
692         while (argv && argv[argc])
693                 argc++;
695         return argc;
698 DEFINE_ALLOCATOR(argv_realloc, const char *, SIZEOF_ARG)
700 static bool
701 argv_append(const char ***argv, const char *arg)
703         size_t argc = argv_size(*argv);
705         if (!argv_realloc(argv, argc, 2))
706                 return FALSE;
708         (*argv)[argc++] = strdup(arg);
709         (*argv)[argc] = NULL;
710         return TRUE;
713 static bool
714 argv_append_array(const char ***dst_argv, const char *src_argv[])
716         int i;
718         for (i = 0; src_argv && src_argv[i]; i++)
719                 if (!argv_append(dst_argv, src_argv[i]))
720                         return FALSE;
721         return TRUE;
724 static bool
725 argv_copy(const char ***dst, const char *src[])
727         int argc;
729         for (argc = 0; src[argc]; argc++)
730                 if (!argv_append(dst, src[argc]))
731                         return FALSE;
732         return TRUE;
736 /*
737  * Executing external commands.
738  */
740 enum io_type {
741         IO_FD,                  /* File descriptor based IO. */
742         IO_BG,                  /* Execute command in the background. */
743         IO_FG,                  /* Execute command with same std{in,out,err}. */
744         IO_RD,                  /* Read only fork+exec IO. */
745         IO_WR,                  /* Write only fork+exec IO. */
746         IO_AP,                  /* Append fork+exec output to file. */
747 };
749 struct io {
750         int pipe;               /* Pipe end for reading or writing. */
751         pid_t pid;              /* PID of spawned process. */
752         int error;              /* Error status. */
753         char *buf;              /* Read buffer. */
754         size_t bufalloc;        /* Allocated buffer size. */
755         size_t bufsize;         /* Buffer content size. */
756         char *bufpos;           /* Current buffer position. */
757         unsigned int eof:1;     /* Has end of file been reached. */
758 };
760 static void
761 io_init(struct io *io)
763         memset(io, 0, sizeof(*io));
764         io->pipe = -1;
767 static bool
768 io_open(struct io *io, const char *fmt, ...)
770         char name[SIZEOF_STR] = "";
771         bool fits;
772         va_list args;
774         io_init(io);
776         va_start(args, fmt);
777         fits = vsnprintf(name, sizeof(name), fmt, args) < sizeof(name);
778         va_end(args);
780         if (!fits) {
781                 io->error = ENAMETOOLONG;
782                 return FALSE;
783         }
784         io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
785         if (io->pipe == -1)
786                 io->error = errno;
787         return io->pipe != -1;
790 static bool
791 io_kill(struct io *io)
793         return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
796 static bool
797 io_done(struct io *io)
799         pid_t pid = io->pid;
801         if (io->pipe != -1)
802                 close(io->pipe);
803         free(io->buf);
804         io_init(io);
806         while (pid > 0) {
807                 int status;
808                 pid_t waiting = waitpid(pid, &status, 0);
810                 if (waiting < 0) {
811                         if (errno == EINTR)
812                                 continue;
813                         io->error = errno;
814                         return FALSE;
815                 }
817                 return waiting == pid &&
818                        !WIFSIGNALED(status) &&
819                        WIFEXITED(status) &&
820                        !WEXITSTATUS(status);
821         }
823         return TRUE;
826 static bool
827 io_run(struct io *io, enum io_type type, const char *dir, const char *argv[], ...)
829         int pipefds[2] = { -1, -1 };
830         va_list args;
832         io_init(io);
834         if ((type == IO_RD || type == IO_WR) && pipe(pipefds) < 0) {
835                 io->error = errno;
836                 return FALSE;
837         } else if (type == IO_AP) {
838                 va_start(args, argv);
839                 pipefds[1] = va_arg(args, int);
840                 va_end(args);
841         }
843         if ((io->pid = fork())) {
844                 if (io->pid == -1)
845                         io->error = errno;
846                 if (pipefds[!(type == IO_WR)] != -1)
847                         close(pipefds[!(type == IO_WR)]);
848                 if (io->pid != -1) {
849                         io->pipe = pipefds[!!(type == IO_WR)];
850                         return TRUE;
851                 }
853         } else {
854                 if (type != IO_FG) {
855                         int devnull = open("/dev/null", O_RDWR);
856                         int readfd  = type == IO_WR ? pipefds[0] : devnull;
857                         int writefd = (type == IO_RD || type == IO_AP)
858                                                         ? pipefds[1] : devnull;
860                         dup2(readfd,  STDIN_FILENO);
861                         dup2(writefd, STDOUT_FILENO);
862                         dup2(devnull, STDERR_FILENO);
864                         close(devnull);
865                         if (pipefds[0] != -1)
866                                 close(pipefds[0]);
867                         if (pipefds[1] != -1)
868                                 close(pipefds[1]);
869                 }
871                 if (dir && *dir && chdir(dir) == -1)
872                         exit(errno);
874                 execvp(argv[0], (char *const*) argv);
875                 exit(errno);
876         }
878         if (pipefds[!!(type == IO_WR)] != -1)
879                 close(pipefds[!!(type == IO_WR)]);
880         return FALSE;
883 static bool
884 io_complete(enum io_type type, const char **argv, const char *dir, int fd)
886         struct io io;
888         return io_run(&io, type, dir, argv, fd) && io_done(&io);
891 static bool
892 io_run_bg(const char **argv)
894         return io_complete(IO_BG, argv, NULL, -1);
897 static bool
898 io_run_fg(const char **argv, const char *dir)
900         return io_complete(IO_FG, argv, dir, -1);
903 static bool
904 io_run_append(const char **argv, int fd)
906         return io_complete(IO_AP, argv, NULL, fd);
909 static bool
910 io_eof(struct io *io)
912         return io->eof;
915 static int
916 io_error(struct io *io)
918         return io->error;
921 static char *
922 io_strerror(struct io *io)
924         return strerror(io->error);
927 static bool
928 io_can_read(struct io *io)
930         struct timeval tv = { 0, 500 };
931         fd_set fds;
933         FD_ZERO(&fds);
934         FD_SET(io->pipe, &fds);
936         return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
939 static ssize_t
940 io_read(struct io *io, void *buf, size_t bufsize)
942         do {
943                 ssize_t readsize = read(io->pipe, buf, bufsize);
945                 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
946                         continue;
947                 else if (readsize == -1)
948                         io->error = errno;
949                 else if (readsize == 0)
950                         io->eof = 1;
951                 return readsize;
952         } while (1);
955 DEFINE_ALLOCATOR(io_realloc_buf, char, BUFSIZ)
957 static char *
958 io_get(struct io *io, int c, bool can_read)
960         char *eol;
961         ssize_t readsize;
963         while (TRUE) {
964                 if (io->bufsize > 0) {
965                         eol = memchr(io->bufpos, c, io->bufsize);
966                         if (eol) {
967                                 char *line = io->bufpos;
969                                 *eol = 0;
970                                 io->bufpos = eol + 1;
971                                 io->bufsize -= io->bufpos - line;
972                                 return line;
973                         }
974                 }
976                 if (io_eof(io)) {
977                         if (io->bufsize) {
978                                 io->bufpos[io->bufsize] = 0;
979                                 io->bufsize = 0;
980                                 return io->bufpos;
981                         }
982                         return NULL;
983                 }
985                 if (!can_read)
986                         return NULL;
988                 if (io->bufsize > 0 && io->bufpos > io->buf)
989                         memmove(io->buf, io->bufpos, io->bufsize);
991                 if (io->bufalloc == io->bufsize) {
992                         if (!io_realloc_buf(&io->buf, io->bufalloc, BUFSIZ))
993                                 return NULL;
994                         io->bufalloc += BUFSIZ;
995                 }
997                 io->bufpos = io->buf;
998                 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
999                 if (io_error(io))
1000                         return NULL;
1001                 io->bufsize += readsize;
1002         }
1005 static bool
1006 io_write(struct io *io, const void *buf, size_t bufsize)
1008         size_t written = 0;
1010         while (!io_error(io) && written < bufsize) {
1011                 ssize_t size;
1013                 size = write(io->pipe, buf + written, bufsize - written);
1014                 if (size < 0 && (errno == EAGAIN || errno == EINTR))
1015                         continue;
1016                 else if (size == -1)
1017                         io->error = errno;
1018                 else
1019                         written += size;
1020         }
1022         return written == bufsize;
1025 static bool
1026 io_read_buf(struct io *io, char buf[], size_t bufsize)
1028         char *result = io_get(io, '\n', TRUE);
1030         if (result) {
1031                 result = chomp_string(result);
1032                 string_ncopy_do(buf, bufsize, result, strlen(result));
1033         }
1035         return io_done(io) && result;
1038 static bool
1039 io_run_buf(const char **argv, char buf[], size_t bufsize)
1041         struct io io;
1043         return io_run(&io, IO_RD, NULL, argv) && io_read_buf(&io, buf, bufsize);
1046 static int
1047 io_load(struct io *io, const char *separators,
1048         int (*read_property)(char *, size_t, char *, size_t))
1050         char *name;
1051         int state = OK;
1053         while (state == OK && (name = io_get(io, '\n', TRUE))) {
1054                 char *value;
1055                 size_t namelen;
1056                 size_t valuelen;
1058                 name = chomp_string(name);
1059                 namelen = strcspn(name, separators);
1061                 if (name[namelen]) {
1062                         name[namelen] = 0;
1063                         value = chomp_string(name + namelen + 1);
1064                         valuelen = strlen(value);
1066                 } else {
1067                         value = "";
1068                         valuelen = 0;
1069                 }
1071                 state = read_property(name, namelen, value, valuelen);
1072         }
1074         if (state != ERR && io_error(io))
1075                 state = ERR;
1076         io_done(io);
1078         return state;
1081 static int
1082 io_run_load(const char **argv, const char *separators,
1083             int (*read_property)(char *, size_t, char *, size_t))
1085         struct io io;
1087         if (!io_run(&io, IO_RD, NULL, argv))
1088                 return ERR;
1089         return io_load(&io, separators, read_property);
1093 /*
1094  * User requests
1095  */
1097 #define REQ_INFO \
1098         /* XXX: Keep the view request first and in sync with views[]. */ \
1099         REQ_GROUP("View switching") \
1100         REQ_(VIEW_MAIN,         "Show main view"), \
1101         REQ_(VIEW_DIFF,         "Show diff view"), \
1102         REQ_(VIEW_LOG,          "Show log view"), \
1103         REQ_(VIEW_TREE,         "Show tree view"), \
1104         REQ_(VIEW_BLOB,         "Show blob view"), \
1105         REQ_(VIEW_BLAME,        "Show blame view"), \
1106         REQ_(VIEW_BRANCH,       "Show branch view"), \
1107         REQ_(VIEW_HELP,         "Show help page"), \
1108         REQ_(VIEW_PAGER,        "Show pager view"), \
1109         REQ_(VIEW_STATUS,       "Show status view"), \
1110         REQ_(VIEW_STAGE,        "Show stage view"), \
1111         \
1112         REQ_GROUP("View manipulation") \
1113         REQ_(ENTER,             "Enter current line and scroll"), \
1114         REQ_(NEXT,              "Move to next"), \
1115         REQ_(PREVIOUS,          "Move to previous"), \
1116         REQ_(PARENT,            "Move to parent"), \
1117         REQ_(VIEW_NEXT,         "Move focus to next view"), \
1118         REQ_(REFRESH,           "Reload and refresh"), \
1119         REQ_(MAXIMIZE,          "Maximize the current view"), \
1120         REQ_(VIEW_CLOSE,        "Close the current view"), \
1121         REQ_(QUIT,              "Close all views and quit"), \
1122         \
1123         REQ_GROUP("View specific requests") \
1124         REQ_(STATUS_UPDATE,     "Update file status"), \
1125         REQ_(STATUS_REVERT,     "Revert file changes"), \
1126         REQ_(STATUS_MERGE,      "Merge file using external tool"), \
1127         REQ_(STAGE_NEXT,        "Find next chunk to stage"), \
1128         \
1129         REQ_GROUP("Cursor navigation") \
1130         REQ_(MOVE_UP,           "Move cursor one line up"), \
1131         REQ_(MOVE_DOWN,         "Move cursor one line down"), \
1132         REQ_(MOVE_PAGE_DOWN,    "Move cursor one page down"), \
1133         REQ_(MOVE_PAGE_UP,      "Move cursor one page up"), \
1134         REQ_(MOVE_FIRST_LINE,   "Move cursor to first line"), \
1135         REQ_(MOVE_LAST_LINE,    "Move cursor to last line"), \
1136         \
1137         REQ_GROUP("Scrolling") \
1138         REQ_(SCROLL_LEFT,       "Scroll two columns left"), \
1139         REQ_(SCROLL_RIGHT,      "Scroll two columns right"), \
1140         REQ_(SCROLL_LINE_UP,    "Scroll one line up"), \
1141         REQ_(SCROLL_LINE_DOWN,  "Scroll one line down"), \
1142         REQ_(SCROLL_PAGE_UP,    "Scroll one page up"), \
1143         REQ_(SCROLL_PAGE_DOWN,  "Scroll one page down"), \
1144         \
1145         REQ_GROUP("Searching") \
1146         REQ_(SEARCH,            "Search the view"), \
1147         REQ_(SEARCH_BACK,       "Search backwards in the view"), \
1148         REQ_(FIND_NEXT,         "Find next search match"), \
1149         REQ_(FIND_PREV,         "Find previous search match"), \
1150         \
1151         REQ_GROUP("Option manipulation") \
1152         REQ_(OPTIONS,           "Open option menu"), \
1153         REQ_(TOGGLE_LINENO,     "Toggle line numbers"), \
1154         REQ_(TOGGLE_DATE,       "Toggle date display"), \
1155         REQ_(TOGGLE_DATE_SHORT, "Toggle short (date-only) dates"), \
1156         REQ_(TOGGLE_AUTHOR,     "Toggle author display"), \
1157         REQ_(TOGGLE_REV_GRAPH,  "Toggle revision graph visualization"), \
1158         REQ_(TOGGLE_REFS,       "Toggle reference display (tags/branches)"), \
1159         REQ_(TOGGLE_SORT_ORDER, "Toggle ascending/descending sort order"), \
1160         REQ_(TOGGLE_SORT_FIELD, "Toggle field to sort by"), \
1161         \
1162         REQ_GROUP("Misc") \
1163         REQ_(PROMPT,            "Bring up the prompt"), \
1164         REQ_(SCREEN_REDRAW,     "Redraw the screen"), \
1165         REQ_(SHOW_VERSION,      "Show version information"), \
1166         REQ_(STOP_LOADING,      "Stop all loading views"), \
1167         REQ_(EDIT,              "Open in editor"), \
1168         REQ_(NONE,              "Do nothing")
1171 /* User action requests. */
1172 enum request {
1173 #define REQ_GROUP(help)
1174 #define REQ_(req, help) REQ_##req
1176         /* Offset all requests to avoid conflicts with ncurses getch values. */
1177         REQ_UNKNOWN = KEY_MAX + 1,
1178         REQ_OFFSET,
1179         REQ_INFO
1181 #undef  REQ_GROUP
1182 #undef  REQ_
1183 };
1185 struct request_info {
1186         enum request request;
1187         const char *name;
1188         int namelen;
1189         const char *help;
1190 };
1192 static const struct request_info req_info[] = {
1193 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
1194 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
1195         REQ_INFO
1196 #undef  REQ_GROUP
1197 #undef  REQ_
1198 };
1200 static enum request
1201 get_request(const char *name)
1203         int namelen = strlen(name);
1204         int i;
1206         for (i = 0; i < ARRAY_SIZE(req_info); i++)
1207                 if (enum_equals(req_info[i], name, namelen))
1208                         return req_info[i].request;
1210         return REQ_UNKNOWN;
1214 /*
1215  * Options
1216  */
1218 /* Option and state variables. */
1219 static enum date opt_date               = DATE_DEFAULT;
1220 static enum author opt_author           = AUTHOR_DEFAULT;
1221 static bool opt_line_number             = FALSE;
1222 static bool opt_line_graphics           = TRUE;
1223 static bool opt_rev_graph               = FALSE;
1224 static bool opt_show_refs               = TRUE;
1225 static int opt_num_interval             = 5;
1226 static double opt_hscroll               = 0.50;
1227 static double opt_scale_split_view      = 2.0 / 3.0;
1228 static int opt_tab_size                 = 8;
1229 static int opt_author_cols              = AUTHOR_COLS;
1230 static char opt_path[SIZEOF_STR]        = "";
1231 static char opt_file[SIZEOF_STR]        = "";
1232 static char opt_ref[SIZEOF_REF]         = "";
1233 static char opt_head[SIZEOF_REF]        = "";
1234 static char opt_remote[SIZEOF_REF]      = "";
1235 static char opt_encoding[20]            = "UTF-8";
1236 static iconv_t opt_iconv_in             = ICONV_NONE;
1237 static iconv_t opt_iconv_out            = ICONV_NONE;
1238 static char opt_search[SIZEOF_STR]      = "";
1239 static char opt_cdup[SIZEOF_STR]        = "";
1240 static char opt_prefix[SIZEOF_STR]      = "";
1241 static char opt_git_dir[SIZEOF_STR]     = "";
1242 static signed char opt_is_inside_work_tree      = -1; /* set to TRUE or FALSE */
1243 static char opt_editor[SIZEOF_STR]      = "";
1244 static FILE *opt_tty                    = NULL;
1245 static const char **opt_diff_args       = NULL;
1246 static const char **opt_rev_args        = NULL;
1247 static const char **opt_file_args       = NULL;
1249 #define is_initial_commit()     (!get_ref_head())
1250 #define is_head_commit(rev)     (!strcmp((rev), "HEAD") || (get_ref_head() && !strcmp(rev, get_ref_head()->id)))
1253 /*
1254  * Line-oriented content detection.
1255  */
1257 #define LINE_INFO \
1258 LINE(DIFF_HEADER,  "diff --git ",       COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1259 LINE(DIFF_CHUNK,   "@@",                COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1260 LINE(DIFF_ADD,     "+",                 COLOR_GREEN,    COLOR_DEFAULT,  0), \
1261 LINE(DIFF_DEL,     "-",                 COLOR_RED,      COLOR_DEFAULT,  0), \
1262 LINE(DIFF_INDEX,        "index ",         COLOR_BLUE,   COLOR_DEFAULT,  0), \
1263 LINE(DIFF_OLDMODE,      "old file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1264 LINE(DIFF_NEWMODE,      "new file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1265 LINE(DIFF_COPY_FROM,    "copy from",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
1266 LINE(DIFF_COPY_TO,      "copy to",        COLOR_YELLOW, COLOR_DEFAULT,  0), \
1267 LINE(DIFF_RENAME_FROM,  "rename from",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
1268 LINE(DIFF_RENAME_TO,    "rename to",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
1269 LINE(DIFF_SIMILARITY,   "similarity ",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
1270 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1271 LINE(DIFF_TREE,         "diff-tree ",     COLOR_BLUE,   COLOR_DEFAULT,  0), \
1272 LINE(PP_AUTHOR,    "Author: ",          COLOR_CYAN,     COLOR_DEFAULT,  0), \
1273 LINE(PP_COMMIT,    "Commit: ",          COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1274 LINE(PP_MERGE,     "Merge: ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
1275 LINE(PP_DATE,      "Date:   ",          COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1276 LINE(PP_ADATE,     "AuthorDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1277 LINE(PP_CDATE,     "CommitDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1278 LINE(PP_REFS,      "Refs: ",            COLOR_RED,      COLOR_DEFAULT,  0), \
1279 LINE(COMMIT,       "commit ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
1280 LINE(PARENT,       "parent ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
1281 LINE(TREE,         "tree ",             COLOR_BLUE,     COLOR_DEFAULT,  0), \
1282 LINE(AUTHOR,       "author ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
1283 LINE(COMMITTER,    "committer ",        COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1284 LINE(SIGNOFF,      "    Signed-off-by", COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1285 LINE(ACKED,        "    Acked-by",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1286 LINE(TESTED,       "    Tested-by",     COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1287 LINE(REVIEWED,     "    Reviewed-by",   COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1288 LINE(DEFAULT,      "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
1289 LINE(CURSOR,       "",                  COLOR_WHITE,    COLOR_GREEN,    A_BOLD), \
1290 LINE(STATUS,       "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
1291 LINE(DELIMITER,    "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1292 LINE(DATE,         "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
1293 LINE(MODE,         "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1294 LINE(LINE_NUMBER,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1295 LINE(TITLE_BLUR,   "",                  COLOR_WHITE,    COLOR_BLUE,     0), \
1296 LINE(TITLE_FOCUS,  "",                  COLOR_WHITE,    COLOR_BLUE,     A_BOLD), \
1297 LINE(MAIN_COMMIT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
1298 LINE(MAIN_TAG,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
1299 LINE(MAIN_LOCAL_TAG,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1300 LINE(MAIN_REMOTE,  "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1301 LINE(MAIN_TRACKED, "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_BOLD), \
1302 LINE(MAIN_REF,     "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1303 LINE(MAIN_HEAD,    "",                  COLOR_CYAN,     COLOR_DEFAULT,  A_BOLD), \
1304 LINE(MAIN_REVGRAPH,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1305 LINE(TREE_HEAD,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_BOLD), \
1306 LINE(TREE_DIR,     "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_NORMAL), \
1307 LINE(TREE_FILE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
1308 LINE(STAT_HEAD,    "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1309 LINE(STAT_SECTION, "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1310 LINE(STAT_NONE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
1311 LINE(STAT_STAGED,  "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1312 LINE(STAT_UNSTAGED,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1313 LINE(STAT_UNTRACKED,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1314 LINE(HELP_KEYMAP,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1315 LINE(HELP_GROUP,   "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
1316 LINE(BLAME_ID,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0)
1318 enum line_type {
1319 #define LINE(type, line, fg, bg, attr) \
1320         LINE_##type
1321         LINE_INFO,
1322         LINE_NONE
1323 #undef  LINE
1324 };
1326 struct line_info {
1327         const char *name;       /* Option name. */
1328         int namelen;            /* Size of option name. */
1329         const char *line;       /* The start of line to match. */
1330         int linelen;            /* Size of string to match. */
1331         int fg, bg, attr;       /* Color and text attributes for the lines. */
1332 };
1334 static struct line_info line_info[] = {
1335 #define LINE(type, line, fg, bg, attr) \
1336         { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1337         LINE_INFO
1338 #undef  LINE
1339 };
1341 static enum line_type
1342 get_line_type(const char *line)
1344         int linelen = strlen(line);
1345         enum line_type type;
1347         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1348                 /* Case insensitive search matches Signed-off-by lines better. */
1349                 if (linelen >= line_info[type].linelen &&
1350                     !strncasecmp(line_info[type].line, line, line_info[type].linelen))
1351                         return type;
1353         return LINE_DEFAULT;
1356 static inline int
1357 get_line_attr(enum line_type type)
1359         assert(type < ARRAY_SIZE(line_info));
1360         return COLOR_PAIR(type) | line_info[type].attr;
1363 static struct line_info *
1364 get_line_info(const char *name)
1366         size_t namelen = strlen(name);
1367         enum line_type type;
1369         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1370                 if (enum_equals(line_info[type], name, namelen))
1371                         return &line_info[type];
1373         return NULL;
1376 static void
1377 init_colors(void)
1379         int default_bg = line_info[LINE_DEFAULT].bg;
1380         int default_fg = line_info[LINE_DEFAULT].fg;
1381         enum line_type type;
1383         start_color();
1385         if (assume_default_colors(default_fg, default_bg) == ERR) {
1386                 default_bg = COLOR_BLACK;
1387                 default_fg = COLOR_WHITE;
1388         }
1390         for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1391                 struct line_info *info = &line_info[type];
1392                 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1393                 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1395                 init_pair(type, fg, bg);
1396         }
1399 struct line {
1400         enum line_type type;
1402         /* State flags */
1403         unsigned int selected:1;
1404         unsigned int dirty:1;
1405         unsigned int cleareol:1;
1406         unsigned int other:16;
1408         void *data;             /* User data */
1409 };
1412 /*
1413  * Keys
1414  */
1416 struct keybinding {
1417         int alias;
1418         enum request request;
1419 };
1421 static struct keybinding default_keybindings[] = {
1422         /* View switching */
1423         { 'm',          REQ_VIEW_MAIN },
1424         { 'd',          REQ_VIEW_DIFF },
1425         { 'l',          REQ_VIEW_LOG },
1426         { 't',          REQ_VIEW_TREE },
1427         { 'f',          REQ_VIEW_BLOB },
1428         { 'B',          REQ_VIEW_BLAME },
1429         { 'H',          REQ_VIEW_BRANCH },
1430         { 'p',          REQ_VIEW_PAGER },
1431         { 'h',          REQ_VIEW_HELP },
1432         { 'S',          REQ_VIEW_STATUS },
1433         { 'c',          REQ_VIEW_STAGE },
1435         /* View manipulation */
1436         { 'q',          REQ_VIEW_CLOSE },
1437         { KEY_TAB,      REQ_VIEW_NEXT },
1438         { KEY_RETURN,   REQ_ENTER },
1439         { KEY_UP,       REQ_PREVIOUS },
1440         { KEY_DOWN,     REQ_NEXT },
1441         { 'R',          REQ_REFRESH },
1442         { KEY_F(5),     REQ_REFRESH },
1443         { 'O',          REQ_MAXIMIZE },
1445         /* Cursor navigation */
1446         { 'k',          REQ_MOVE_UP },
1447         { 'j',          REQ_MOVE_DOWN },
1448         { KEY_HOME,     REQ_MOVE_FIRST_LINE },
1449         { KEY_END,      REQ_MOVE_LAST_LINE },
1450         { KEY_NPAGE,    REQ_MOVE_PAGE_DOWN },
1451         { ' ',          REQ_MOVE_PAGE_DOWN },
1452         { KEY_PPAGE,    REQ_MOVE_PAGE_UP },
1453         { 'b',          REQ_MOVE_PAGE_UP },
1454         { '-',          REQ_MOVE_PAGE_UP },
1456         /* Scrolling */
1457         { KEY_LEFT,     REQ_SCROLL_LEFT },
1458         { KEY_RIGHT,    REQ_SCROLL_RIGHT },
1459         { KEY_IC,       REQ_SCROLL_LINE_UP },
1460         { KEY_DC,       REQ_SCROLL_LINE_DOWN },
1461         { 'w',          REQ_SCROLL_PAGE_UP },
1462         { 's',          REQ_SCROLL_PAGE_DOWN },
1464         /* Searching */
1465         { '/',          REQ_SEARCH },
1466         { '?',          REQ_SEARCH_BACK },
1467         { 'n',          REQ_FIND_NEXT },
1468         { 'N',          REQ_FIND_PREV },
1470         /* Misc */
1471         { 'Q',          REQ_QUIT },
1472         { 'z',          REQ_STOP_LOADING },
1473         { 'v',          REQ_SHOW_VERSION },
1474         { 'r',          REQ_SCREEN_REDRAW },
1475         { 'o',          REQ_OPTIONS },
1476         { '.',          REQ_TOGGLE_LINENO },
1477         { 'D',          REQ_TOGGLE_DATE },
1478         { 'A',          REQ_TOGGLE_AUTHOR },
1479         { 'g',          REQ_TOGGLE_REV_GRAPH },
1480         { 'F',          REQ_TOGGLE_REFS },
1481         { 'I',          REQ_TOGGLE_SORT_ORDER },
1482         { 'i',          REQ_TOGGLE_SORT_FIELD },
1483         { ':',          REQ_PROMPT },
1484         { 'u',          REQ_STATUS_UPDATE },
1485         { '!',          REQ_STATUS_REVERT },
1486         { 'M',          REQ_STATUS_MERGE },
1487         { '@',          REQ_STAGE_NEXT },
1488         { ',',          REQ_PARENT },
1489         { 'e',          REQ_EDIT },
1490 };
1492 #define KEYMAP_INFO \
1493         KEYMAP_(GENERIC), \
1494         KEYMAP_(MAIN), \
1495         KEYMAP_(DIFF), \
1496         KEYMAP_(LOG), \
1497         KEYMAP_(TREE), \
1498         KEYMAP_(BLOB), \
1499         KEYMAP_(BLAME), \
1500         KEYMAP_(BRANCH), \
1501         KEYMAP_(PAGER), \
1502         KEYMAP_(HELP), \
1503         KEYMAP_(STATUS), \
1504         KEYMAP_(STAGE)
1506 enum keymap {
1507 #define KEYMAP_(name) KEYMAP_##name
1508         KEYMAP_INFO
1509 #undef  KEYMAP_
1510 };
1512 static const struct enum_map keymap_table[] = {
1513 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1514         KEYMAP_INFO
1515 #undef  KEYMAP_
1516 };
1518 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1520 struct keybinding_table {
1521         struct keybinding *data;
1522         size_t size;
1523 };
1525 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1527 static void
1528 add_keybinding(enum keymap keymap, enum request request, int key)
1530         struct keybinding_table *table = &keybindings[keymap];
1531         size_t i;
1533         for (i = 0; i < keybindings[keymap].size; i++) {
1534                 if (keybindings[keymap].data[i].alias == key) {
1535                         keybindings[keymap].data[i].request = request;
1536                         return;
1537                 }
1538         }
1540         table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1541         if (!table->data)
1542                 die("Failed to allocate keybinding");
1543         table->data[table->size].alias = key;
1544         table->data[table->size++].request = request;
1546         if (request == REQ_NONE && keymap == KEYMAP_GENERIC) {
1547                 int i;
1549                 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1550                         if (default_keybindings[i].alias == key)
1551                                 default_keybindings[i].request = REQ_NONE;
1552         }
1555 /* Looks for a key binding first in the given map, then in the generic map, and
1556  * lastly in the default keybindings. */
1557 static enum request
1558 get_keybinding(enum keymap keymap, int key)
1560         size_t i;
1562         for (i = 0; i < keybindings[keymap].size; i++)
1563                 if (keybindings[keymap].data[i].alias == key)
1564                         return keybindings[keymap].data[i].request;
1566         for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1567                 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1568                         return keybindings[KEYMAP_GENERIC].data[i].request;
1570         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1571                 if (default_keybindings[i].alias == key)
1572                         return default_keybindings[i].request;
1574         return (enum request) key;
1578 struct key {
1579         const char *name;
1580         int value;
1581 };
1583 static const struct key key_table[] = {
1584         { "Enter",      KEY_RETURN },
1585         { "Space",      ' ' },
1586         { "Backspace",  KEY_BACKSPACE },
1587         { "Tab",        KEY_TAB },
1588         { "Escape",     KEY_ESC },
1589         { "Left",       KEY_LEFT },
1590         { "Right",      KEY_RIGHT },
1591         { "Up",         KEY_UP },
1592         { "Down",       KEY_DOWN },
1593         { "Insert",     KEY_IC },
1594         { "Delete",     KEY_DC },
1595         { "Hash",       '#' },
1596         { "Home",       KEY_HOME },
1597         { "End",        KEY_END },
1598         { "PageUp",     KEY_PPAGE },
1599         { "PageDown",   KEY_NPAGE },
1600         { "F1",         KEY_F(1) },
1601         { "F2",         KEY_F(2) },
1602         { "F3",         KEY_F(3) },
1603         { "F4",         KEY_F(4) },
1604         { "F5",         KEY_F(5) },
1605         { "F6",         KEY_F(6) },
1606         { "F7",         KEY_F(7) },
1607         { "F8",         KEY_F(8) },
1608         { "F9",         KEY_F(9) },
1609         { "F10",        KEY_F(10) },
1610         { "F11",        KEY_F(11) },
1611         { "F12",        KEY_F(12) },
1612 };
1614 static int
1615 get_key_value(const char *name)
1617         int i;
1619         for (i = 0; i < ARRAY_SIZE(key_table); i++)
1620                 if (!strcasecmp(key_table[i].name, name))
1621                         return key_table[i].value;
1623         if (strlen(name) == 1 && isprint(*name))
1624                 return (int) *name;
1626         return ERR;
1629 static const char *
1630 get_key_name(int key_value)
1632         static char key_char[] = "'X'";
1633         const char *seq = NULL;
1634         int key;
1636         for (key = 0; key < ARRAY_SIZE(key_table); key++)
1637                 if (key_table[key].value == key_value)
1638                         seq = key_table[key].name;
1640         if (seq == NULL &&
1641             key_value < 127 &&
1642             isprint(key_value)) {
1643                 key_char[1] = (char) key_value;
1644                 seq = key_char;
1645         }
1647         return seq ? seq : "(no key)";
1650 static bool
1651 append_key(char *buf, size_t *pos, const struct keybinding *keybinding)
1653         const char *sep = *pos > 0 ? ", " : "";
1654         const char *keyname = get_key_name(keybinding->alias);
1656         return string_nformat(buf, BUFSIZ, pos, "%s%s", sep, keyname);
1659 static bool
1660 append_keymap_request_keys(char *buf, size_t *pos, enum request request,
1661                            enum keymap keymap, bool all)
1663         int i;
1665         for (i = 0; i < keybindings[keymap].size; i++) {
1666                 if (keybindings[keymap].data[i].request == request) {
1667                         if (!append_key(buf, pos, &keybindings[keymap].data[i]))
1668                                 return FALSE;
1669                         if (!all)
1670                                 break;
1671                 }
1672         }
1674         return TRUE;
1677 #define get_key(keymap, request) get_keys(keymap, request, FALSE)
1679 static const char *
1680 get_keys(enum keymap keymap, enum request request, bool all)
1682         static char buf[BUFSIZ];
1683         size_t pos = 0;
1684         int i;
1686         buf[pos] = 0;
1688         if (!append_keymap_request_keys(buf, &pos, request, keymap, all))
1689                 return "Too many keybindings!";
1690         if (pos > 0 && !all)
1691                 return buf;
1693         if (keymap != KEYMAP_GENERIC) {
1694                 /* Only the generic keymap includes the default keybindings when
1695                  * listing all keys. */
1696                 if (all)
1697                         return buf;
1699                 if (!append_keymap_request_keys(buf, &pos, request, KEYMAP_GENERIC, all))
1700                         return "Too many keybindings!";
1701                 if (pos)
1702                         return buf;
1703         }
1705         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1706                 if (default_keybindings[i].request == request) {
1707                         if (!append_key(buf, &pos, &default_keybindings[i]))
1708                                 return "Too many keybindings!";
1709                         if (!all)
1710                                 return buf;
1711                 }
1712         }
1714         return buf;
1717 struct run_request {
1718         enum keymap keymap;
1719         int key;
1720         const char **argv;
1721 };
1723 static struct run_request *run_request;
1724 static size_t run_requests;
1726 DEFINE_ALLOCATOR(realloc_run_requests, struct run_request, 8)
1728 static enum request
1729 add_run_request(enum keymap keymap, int key, const char **argv)
1731         struct run_request *req;
1733         if (!realloc_run_requests(&run_request, run_requests, 1))
1734                 return REQ_NONE;
1736         req = &run_request[run_requests];
1737         req->keymap = keymap;
1738         req->key = key;
1739         req->argv = NULL;
1741         if (!argv_copy(&req->argv, argv))
1742                 return REQ_NONE;
1744         return REQ_NONE + ++run_requests;
1747 static struct run_request *
1748 get_run_request(enum request request)
1750         if (request <= REQ_NONE)
1751                 return NULL;
1752         return &run_request[request - REQ_NONE - 1];
1755 static void
1756 add_builtin_run_requests(void)
1758         const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1759         const char *checkout[] = { "git", "checkout", "%(branch)", NULL };
1760         const char *commit[] = { "git", "commit", NULL };
1761         const char *gc[] = { "git", "gc", NULL };
1762         struct run_request reqs[] = {
1763                 { KEYMAP_MAIN,    'C', cherry_pick },
1764                 { KEYMAP_STATUS,  'C', commit },
1765                 { KEYMAP_BRANCH,  'C', checkout },
1766                 { KEYMAP_GENERIC, 'G', gc },
1767         };
1768         int i;
1770         for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1771                 enum request req = get_keybinding(reqs[i].keymap, reqs[i].key);
1773                 if (req != reqs[i].key)
1774                         continue;
1775                 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argv);
1776                 if (req != REQ_NONE)
1777                         add_keybinding(reqs[i].keymap, req, reqs[i].key);
1778         }
1781 /*
1782  * User config file handling.
1783  */
1785 static int   config_lineno;
1786 static bool  config_errors;
1787 static const char *config_msg;
1789 static const struct enum_map color_map[] = {
1790 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1791         COLOR_MAP(DEFAULT),
1792         COLOR_MAP(BLACK),
1793         COLOR_MAP(BLUE),
1794         COLOR_MAP(CYAN),
1795         COLOR_MAP(GREEN),
1796         COLOR_MAP(MAGENTA),
1797         COLOR_MAP(RED),
1798         COLOR_MAP(WHITE),
1799         COLOR_MAP(YELLOW),
1800 };
1802 static const struct enum_map attr_map[] = {
1803 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1804         ATTR_MAP(NORMAL),
1805         ATTR_MAP(BLINK),
1806         ATTR_MAP(BOLD),
1807         ATTR_MAP(DIM),
1808         ATTR_MAP(REVERSE),
1809         ATTR_MAP(STANDOUT),
1810         ATTR_MAP(UNDERLINE),
1811 };
1813 #define set_attribute(attr, name)       map_enum(attr, attr_map, name)
1815 static int parse_step(double *opt, const char *arg)
1817         *opt = atoi(arg);
1818         if (!strchr(arg, '%'))
1819                 return OK;
1821         /* "Shift down" so 100% and 1 does not conflict. */
1822         *opt = (*opt - 1) / 100;
1823         if (*opt >= 1.0) {
1824                 *opt = 0.99;
1825                 config_msg = "Step value larger than 100%";
1826                 return ERR;
1827         }
1828         if (*opt < 0.0) {
1829                 *opt = 1;
1830                 config_msg = "Invalid step value";
1831                 return ERR;
1832         }
1833         return OK;
1836 static int
1837 parse_int(int *opt, const char *arg, int min, int max)
1839         int value = atoi(arg);
1841         if (min <= value && value <= max) {
1842                 *opt = value;
1843                 return OK;
1844         }
1846         config_msg = "Integer value out of bound";
1847         return ERR;
1850 static bool
1851 set_color(int *color, const char *name)
1853         if (map_enum(color, color_map, name))
1854                 return TRUE;
1855         if (!prefixcmp(name, "color"))
1856                 return parse_int(color, name + 5, 0, 255) == OK;
1857         return FALSE;
1860 /* Wants: object fgcolor bgcolor [attribute] */
1861 static int
1862 option_color_command(int argc, const char *argv[])
1864         struct line_info *info;
1866         if (argc < 3) {
1867                 config_msg = "Wrong number of arguments given to color command";
1868                 return ERR;
1869         }
1871         info = get_line_info(argv[0]);
1872         if (!info) {
1873                 static const struct enum_map obsolete[] = {
1874                         ENUM_MAP("main-delim",  LINE_DELIMITER),
1875                         ENUM_MAP("main-date",   LINE_DATE),
1876                         ENUM_MAP("main-author", LINE_AUTHOR),
1877                 };
1878                 int index;
1880                 if (!map_enum(&index, obsolete, argv[0])) {
1881                         config_msg = "Unknown color name";
1882                         return ERR;
1883                 }
1884                 info = &line_info[index];
1885         }
1887         if (!set_color(&info->fg, argv[1]) ||
1888             !set_color(&info->bg, argv[2])) {
1889                 config_msg = "Unknown color";
1890                 return ERR;
1891         }
1893         info->attr = 0;
1894         while (argc-- > 3) {
1895                 int attr;
1897                 if (!set_attribute(&attr, argv[argc])) {
1898                         config_msg = "Unknown attribute";
1899                         return ERR;
1900                 }
1901                 info->attr |= attr;
1902         }
1904         return OK;
1907 static int parse_bool(bool *opt, const char *arg)
1909         *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1910                 ? TRUE : FALSE;
1911         return OK;
1914 static int parse_enum_do(unsigned int *opt, const char *arg,
1915                          const struct enum_map *map, size_t map_size)
1917         bool is_true;
1919         assert(map_size > 1);
1921         if (map_enum_do(map, map_size, (int *) opt, arg))
1922                 return OK;
1924         if (parse_bool(&is_true, arg) != OK)
1925                 return ERR;
1927         *opt = is_true ? map[1].value : map[0].value;
1928         return OK;
1931 #define parse_enum(opt, arg, map) \
1932         parse_enum_do(opt, arg, map, ARRAY_SIZE(map))
1934 static int
1935 parse_string(char *opt, const char *arg, size_t optsize)
1937         int arglen = strlen(arg);
1939         switch (arg[0]) {
1940         case '\"':
1941         case '\'':
1942                 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1943                         config_msg = "Unmatched quotation";
1944                         return ERR;
1945                 }
1946                 arg += 1; arglen -= 2;
1947         default:
1948                 string_ncopy_do(opt, optsize, arg, arglen);
1949                 return OK;
1950         }
1953 /* Wants: name = value */
1954 static int
1955 option_set_command(int argc, const char *argv[])
1957         if (argc != 3) {
1958                 config_msg = "Wrong number of arguments given to set command";
1959                 return ERR;
1960         }
1962         if (strcmp(argv[1], "=")) {
1963                 config_msg = "No value assigned";
1964                 return ERR;
1965         }
1967         if (!strcmp(argv[0], "show-author"))
1968                 return parse_enum(&opt_author, argv[2], author_map);
1970         if (!strcmp(argv[0], "show-date"))
1971                 return parse_enum(&opt_date, argv[2], date_map);
1973         if (!strcmp(argv[0], "show-rev-graph"))
1974                 return parse_bool(&opt_rev_graph, argv[2]);
1976         if (!strcmp(argv[0], "show-refs"))
1977                 return parse_bool(&opt_show_refs, argv[2]);
1979         if (!strcmp(argv[0], "show-line-numbers"))
1980                 return parse_bool(&opt_line_number, argv[2]);
1982         if (!strcmp(argv[0], "line-graphics"))
1983                 return parse_bool(&opt_line_graphics, argv[2]);
1985         if (!strcmp(argv[0], "line-number-interval"))
1986                 return parse_int(&opt_num_interval, argv[2], 1, 1024);
1988         if (!strcmp(argv[0], "author-width"))
1989                 return parse_int(&opt_author_cols, argv[2], 0, 1024);
1991         if (!strcmp(argv[0], "horizontal-scroll"))
1992                 return parse_step(&opt_hscroll, argv[2]);
1994         if (!strcmp(argv[0], "split-view-height"))
1995                 return parse_step(&opt_scale_split_view, argv[2]);
1997         if (!strcmp(argv[0], "tab-size"))
1998                 return parse_int(&opt_tab_size, argv[2], 1, 1024);
2000         if (!strcmp(argv[0], "commit-encoding"))
2001                 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
2003         config_msg = "Unknown variable name";
2004         return ERR;
2007 /* Wants: mode request key */
2008 static int
2009 option_bind_command(int argc, const char *argv[])
2011         enum request request;
2012         int keymap = -1;
2013         int key;
2015         if (argc < 3) {
2016                 config_msg = "Wrong number of arguments given to bind command";
2017                 return ERR;
2018         }
2020         if (!set_keymap(&keymap, argv[0])) {
2021                 config_msg = "Unknown key map";
2022                 return ERR;
2023         }
2025         key = get_key_value(argv[1]);
2026         if (key == ERR) {
2027                 config_msg = "Unknown key";
2028                 return ERR;
2029         }
2031         request = get_request(argv[2]);
2032         if (request == REQ_UNKNOWN) {
2033                 static const struct enum_map obsolete[] = {
2034                         ENUM_MAP("cherry-pick",         REQ_NONE),
2035                         ENUM_MAP("screen-resize",       REQ_NONE),
2036                         ENUM_MAP("tree-parent",         REQ_PARENT),
2037                 };
2038                 int alias;
2040                 if (map_enum(&alias, obsolete, argv[2])) {
2041                         if (alias != REQ_NONE)
2042                                 add_keybinding(keymap, alias, key);
2043                         config_msg = "Obsolete request name";
2044                         return ERR;
2045                 }
2046         }
2047         if (request == REQ_UNKNOWN && *argv[2]++ == '!')
2048                 request = add_run_request(keymap, key, argv + 2);
2049         if (request == REQ_UNKNOWN) {
2050                 config_msg = "Unknown request name";
2051                 return ERR;
2052         }
2054         add_keybinding(keymap, request, key);
2056         return OK;
2059 static int
2060 set_option(const char *opt, char *value)
2062         const char *argv[SIZEOF_ARG];
2063         int argc = 0;
2065         if (!argv_from_string(argv, &argc, value)) {
2066                 config_msg = "Too many option arguments";
2067                 return ERR;
2068         }
2070         if (!strcmp(opt, "color"))
2071                 return option_color_command(argc, argv);
2073         if (!strcmp(opt, "set"))
2074                 return option_set_command(argc, argv);
2076         if (!strcmp(opt, "bind"))
2077                 return option_bind_command(argc, argv);
2079         config_msg = "Unknown option command";
2080         return ERR;
2083 static int
2084 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
2086         int status = OK;
2088         config_lineno++;
2089         config_msg = "Internal error";
2091         /* Check for comment markers, since read_properties() will
2092          * only ensure opt and value are split at first " \t". */
2093         optlen = strcspn(opt, "#");
2094         if (optlen == 0)
2095                 return OK;
2097         if (opt[optlen] != 0) {
2098                 config_msg = "No option value";
2099                 status = ERR;
2101         }  else {
2102                 /* Look for comment endings in the value. */
2103                 size_t len = strcspn(value, "#");
2105                 if (len < valuelen) {
2106                         valuelen = len;
2107                         value[valuelen] = 0;
2108                 }
2110                 status = set_option(opt, value);
2111         }
2113         if (status == ERR) {
2114                 warn("Error on line %d, near '%.*s': %s",
2115                      config_lineno, (int) optlen, opt, config_msg);
2116                 config_errors = TRUE;
2117         }
2119         /* Always keep going if errors are encountered. */
2120         return OK;
2123 static void
2124 load_option_file(const char *path)
2126         struct io io;
2128         /* It's OK that the file doesn't exist. */
2129         if (!io_open(&io, "%s", path))
2130                 return;
2132         config_lineno = 0;
2133         config_errors = FALSE;
2135         if (io_load(&io, " \t", read_option) == ERR ||
2136             config_errors == TRUE)
2137                 warn("Errors while loading %s.", path);
2140 static int
2141 load_options(void)
2143         const char *home = getenv("HOME");
2144         const char *tigrc_user = getenv("TIGRC_USER");
2145         const char *tigrc_system = getenv("TIGRC_SYSTEM");
2146         const char *tig_diff_opts = getenv("TIG_DIFF_OPTS");
2147         char buf[SIZEOF_STR];
2149         if (!tigrc_system)
2150                 tigrc_system = SYSCONFDIR "/tigrc";
2151         load_option_file(tigrc_system);
2153         if (!tigrc_user) {
2154                 if (!home || !string_format(buf, "%s/.tigrc", home))
2155                         return ERR;
2156                 tigrc_user = buf;
2157         }
2158         load_option_file(tigrc_user);
2160         /* Add _after_ loading config files to avoid adding run requests
2161          * that conflict with keybindings. */
2162         add_builtin_run_requests();
2164         if (!opt_diff_args && tig_diff_opts && *tig_diff_opts) {
2165                 static const char *diff_opts[SIZEOF_ARG] = { NULL };
2166                 int argc = 0;
2168                 if (!string_format(buf, "%s", tig_diff_opts) ||
2169                     !argv_from_string(diff_opts, &argc, buf))
2170                         die("TIG_DIFF_OPTS contains too many arguments");
2171                 else if (!argv_copy(&opt_diff_args, diff_opts))
2172                         die("Failed to format TIG_DIFF_OPTS arguments");
2173         }
2175         return OK;
2179 /*
2180  * The viewer
2181  */
2183 struct view;
2184 struct view_ops;
2186 /* The display array of active views and the index of the current view. */
2187 static struct view *display[2];
2188 static unsigned int current_view;
2190 #define foreach_displayed_view(view, i) \
2191         for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
2193 #define displayed_views()       (display[1] != NULL ? 2 : 1)
2195 /* Current head and commit ID */
2196 static char ref_blob[SIZEOF_REF]        = "";
2197 static char ref_commit[SIZEOF_REF]      = "HEAD";
2198 static char ref_head[SIZEOF_REF]        = "HEAD";
2199 static char ref_branch[SIZEOF_REF]      = "";
2201 enum view_type {
2202         VIEW_MAIN,
2203         VIEW_DIFF,
2204         VIEW_LOG,
2205         VIEW_TREE,
2206         VIEW_BLOB,
2207         VIEW_BLAME,
2208         VIEW_BRANCH,
2209         VIEW_HELP,
2210         VIEW_PAGER,
2211         VIEW_STATUS,
2212         VIEW_STAGE,
2213 };
2215 struct view {
2216         enum view_type type;    /* View type */
2217         const char *name;       /* View name */
2218         const char *cmd_env;    /* Command line set via environment */
2219         const char *id;         /* Points to either of ref_{head,commit,blob} */
2221         struct view_ops *ops;   /* View operations */
2223         enum keymap keymap;     /* What keymap does this view have */
2224         bool git_dir;           /* Whether the view requires a git directory. */
2226         char ref[SIZEOF_REF];   /* Hovered commit reference */
2227         char vid[SIZEOF_REF];   /* View ID. Set to id member when updating. */
2229         int height, width;      /* The width and height of the main window */
2230         WINDOW *win;            /* The main window */
2231         WINDOW *title;          /* The title window living below the main window */
2233         /* Navigation */
2234         unsigned long offset;   /* Offset of the window top */
2235         unsigned long yoffset;  /* Offset from the window side. */
2236         unsigned long lineno;   /* Current line number */
2237         unsigned long p_offset; /* Previous offset of the window top */
2238         unsigned long p_yoffset;/* Previous offset from the window side */
2239         unsigned long p_lineno; /* Previous current line number */
2240         bool p_restore;         /* Should the previous position be restored. */
2242         /* Searching */
2243         char grep[SIZEOF_STR];  /* Search string */
2244         regex_t *regex;         /* Pre-compiled regexp */
2246         /* If non-NULL, points to the view that opened this view. If this view
2247          * is closed tig will switch back to the parent view. */
2248         struct view *parent;
2249         struct view *prev;
2251         /* Buffering */
2252         size_t lines;           /* Total number of lines */
2253         struct line *line;      /* Line index */
2254         unsigned int digits;    /* Number of digits in the lines member. */
2256         /* Drawing */
2257         struct line *curline;   /* Line currently being drawn. */
2258         enum line_type curtype; /* Attribute currently used for drawing. */
2259         unsigned long col;      /* Column when drawing. */
2260         bool has_scrolled;      /* View was scrolled. */
2262         /* Loading */
2263         const char **argv;      /* Shell command arguments. */
2264         const char *dir;        /* Directory from which to execute. */
2265         struct io io;
2266         struct io *pipe;
2267         time_t start_time;
2268         time_t update_secs;
2269 };
2271 struct view_ops {
2272         /* What type of content being displayed. Used in the title bar. */
2273         const char *type;
2274         /* Default command arguments. */
2275         const char **argv;
2276         /* Open and reads in all view content. */
2277         bool (*open)(struct view *view);
2278         /* Read one line; updates view->line. */
2279         bool (*read)(struct view *view, char *data);
2280         /* Draw one line; @lineno must be < view->height. */
2281         bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
2282         /* Depending on view handle a special requests. */
2283         enum request (*request)(struct view *view, enum request request, struct line *line);
2284         /* Search for regexp in a line. */
2285         bool (*grep)(struct view *view, struct line *line);
2286         /* Select line */
2287         void (*select)(struct view *view, struct line *line);
2288         /* Prepare view for loading */
2289         bool (*prepare)(struct view *view);
2290 };
2292 static struct view_ops blame_ops;
2293 static struct view_ops blob_ops;
2294 static struct view_ops diff_ops;
2295 static struct view_ops help_ops;
2296 static struct view_ops log_ops;
2297 static struct view_ops main_ops;
2298 static struct view_ops pager_ops;
2299 static struct view_ops stage_ops;
2300 static struct view_ops status_ops;
2301 static struct view_ops tree_ops;
2302 static struct view_ops branch_ops;
2304 #define VIEW_STR(type, name, env, ref, ops, map, git) \
2305         { type, name, #env, ref, ops, map, git }
2307 #define VIEW_(id, name, ops, git, ref) \
2308         VIEW_STR(VIEW_##id, name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
2310 static struct view views[] = {
2311         VIEW_(MAIN,   "main",   &main_ops,   TRUE,  ref_head),
2312         VIEW_(DIFF,   "diff",   &diff_ops,   TRUE,  ref_commit),
2313         VIEW_(LOG,    "log",    &log_ops,    TRUE,  ref_head),
2314         VIEW_(TREE,   "tree",   &tree_ops,   TRUE,  ref_commit),
2315         VIEW_(BLOB,   "blob",   &blob_ops,   TRUE,  ref_blob),
2316         VIEW_(BLAME,  "blame",  &blame_ops,  TRUE,  ref_commit),
2317         VIEW_(BRANCH, "branch", &branch_ops, TRUE,  ref_head),
2318         VIEW_(HELP,   "help",   &help_ops,   FALSE, ""),
2319         VIEW_(PAGER,  "pager",  &pager_ops,  FALSE, ""),
2320         VIEW_(STATUS, "status", &status_ops, TRUE,  ""),
2321         VIEW_(STAGE,  "stage",  &stage_ops,  TRUE,  ""),
2322 };
2324 #define VIEW(req)       (&views[(req) - REQ_OFFSET - 1])
2326 #define foreach_view(view, i) \
2327         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2329 #define view_is_displayed(view) \
2330         (view == display[0] || view == display[1])
2332 static enum request
2333 view_request(struct view *view, enum request request)
2335         if (!view || !view->lines)
2336                 return request;
2337         return view->ops->request(view, request, &view->line[view->lineno]);
2341 /*
2342  * View drawing.
2343  */
2345 static inline void
2346 set_view_attr(struct view *view, enum line_type type)
2348         if (!view->curline->selected && view->curtype != type) {
2349                 (void) wattrset(view->win, get_line_attr(type));
2350                 wchgat(view->win, -1, 0, type, NULL);
2351                 view->curtype = type;
2352         }
2355 static int
2356 draw_chars(struct view *view, enum line_type type, const char *string,
2357            int max_len, bool use_tilde)
2359         static char out_buffer[BUFSIZ * 2];
2360         int len = 0;
2361         int col = 0;
2362         int trimmed = FALSE;
2363         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2365         if (max_len <= 0)
2366                 return 0;
2368         len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde, opt_tab_size);
2370         set_view_attr(view, type);
2371         if (len > 0) {
2372                 if (opt_iconv_out != ICONV_NONE) {
2373                         ICONV_CONST char *inbuf = (ICONV_CONST char *) string;
2374                         size_t inlen = len + 1;
2376                         char *outbuf = out_buffer;
2377                         size_t outlen = sizeof(out_buffer);
2379                         size_t ret;
2381                         ret = iconv(opt_iconv_out, &inbuf, &inlen, &outbuf, &outlen);
2382                         if (ret != (size_t) -1) {
2383                                 string = out_buffer;
2384                                 len = sizeof(out_buffer) - outlen;
2385                         }
2386                 }
2388                 waddnstr(view->win, string, len);
2389         }
2390         if (trimmed && use_tilde) {
2391                 set_view_attr(view, LINE_DELIMITER);
2392                 waddch(view->win, '~');
2393                 col++;
2394         }
2396         return col;
2399 static int
2400 draw_space(struct view *view, enum line_type type, int max, int spaces)
2402         static char space[] = "                    ";
2403         int col = 0;
2405         spaces = MIN(max, spaces);
2407         while (spaces > 0) {
2408                 int len = MIN(spaces, sizeof(space) - 1);
2410                 col += draw_chars(view, type, space, len, FALSE);
2411                 spaces -= len;
2412         }
2414         return col;
2417 static bool
2418 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
2420         char text[SIZEOF_STR];
2422         do {
2423                 size_t pos = string_expand(text, sizeof(text), string, opt_tab_size);
2425                 view->col += draw_chars(view, type, text, view->width + view->yoffset - view->col, trim);
2426                 string += pos;
2427         } while (*string && view->width + view->yoffset > view->col);
2429         return view->width + view->yoffset <= view->col;
2432 static bool
2433 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
2435         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2436         int max = view->width + view->yoffset - view->col;
2437         int i;
2439         if (max < size)
2440                 size = max;
2442         set_view_attr(view, type);
2443         /* Using waddch() instead of waddnstr() ensures that
2444          * they'll be rendered correctly for the cursor line. */
2445         for (i = skip; i < size; i++)
2446                 waddch(view->win, graphic[i]);
2448         view->col += size;
2449         if (size < max && skip <= size)
2450                 waddch(view->win, ' ');
2451         view->col++;
2453         return view->width + view->yoffset <= view->col;
2456 static bool
2457 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
2459         int max = MIN(view->width + view->yoffset - view->col, len);
2460         int col;
2462         if (text)
2463                 col = draw_chars(view, type, text, max - 1, trim);
2464         else
2465                 col = draw_space(view, type, max - 1, max - 1);
2467         view->col += col;
2468         view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
2469         return view->width + view->yoffset <= view->col;
2472 static bool
2473 draw_date(struct view *view, struct time *time)
2475         const char *date = mkdate(time, opt_date);
2476         int cols = opt_date == DATE_SHORT ? DATE_SHORT_COLS : DATE_COLS;
2478         return draw_field(view, LINE_DATE, date, cols, FALSE);
2481 static bool
2482 draw_author(struct view *view, const char *author)
2484         bool trim = opt_author_cols == 0 || opt_author_cols > 5;
2485         bool abbreviate = opt_author == AUTHOR_ABBREVIATED || !trim;
2487         if (abbreviate && author)
2488                 author = get_author_initials(author);
2490         return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2493 static bool
2494 draw_mode(struct view *view, mode_t mode)
2496         const char *str;
2498         if (S_ISDIR(mode))
2499                 str = "drwxr-xr-x";
2500         else if (S_ISLNK(mode))
2501                 str = "lrwxrwxrwx";
2502         else if (S_ISGITLINK(mode))
2503                 str = "m---------";
2504         else if (S_ISREG(mode) && mode & S_IXUSR)
2505                 str = "-rwxr-xr-x";
2506         else if (S_ISREG(mode))
2507                 str = "-rw-r--r--";
2508         else
2509                 str = "----------";
2511         return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2514 static bool
2515 draw_lineno(struct view *view, unsigned int lineno)
2517         char number[10];
2518         int digits3 = view->digits < 3 ? 3 : view->digits;
2519         int max = MIN(view->width + view->yoffset - view->col, digits3);
2520         char *text = NULL;
2521         chtype separator = opt_line_graphics ? ACS_VLINE : '|';
2523         lineno += view->offset + 1;
2524         if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2525                 static char fmt[] = "%1ld";
2527                 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2528                 if (string_format(number, fmt, lineno))
2529                         text = number;
2530         }
2531         if (text)
2532                 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2533         else
2534                 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2535         return draw_graphic(view, LINE_DEFAULT, &separator, 1);
2538 static bool
2539 draw_view_line(struct view *view, unsigned int lineno)
2541         struct line *line;
2542         bool selected = (view->offset + lineno == view->lineno);
2544         assert(view_is_displayed(view));
2546         if (view->offset + lineno >= view->lines)
2547                 return FALSE;
2549         line = &view->line[view->offset + lineno];
2551         wmove(view->win, lineno, 0);
2552         if (line->cleareol)
2553                 wclrtoeol(view->win);
2554         view->col = 0;
2555         view->curline = line;
2556         view->curtype = LINE_NONE;
2557         line->selected = FALSE;
2558         line->dirty = line->cleareol = 0;
2560         if (selected) {
2561                 set_view_attr(view, LINE_CURSOR);
2562                 line->selected = TRUE;
2563                 view->ops->select(view, line);
2564         }
2566         return view->ops->draw(view, line, lineno);
2569 static void
2570 redraw_view_dirty(struct view *view)
2572         bool dirty = FALSE;
2573         int lineno;
2575         for (lineno = 0; lineno < view->height; lineno++) {
2576                 if (view->offset + lineno >= view->lines)
2577                         break;
2578                 if (!view->line[view->offset + lineno].dirty)
2579                         continue;
2580                 dirty = TRUE;
2581                 if (!draw_view_line(view, lineno))
2582                         break;
2583         }
2585         if (!dirty)
2586                 return;
2587         wnoutrefresh(view->win);
2590 static void
2591 redraw_view_from(struct view *view, int lineno)
2593         assert(0 <= lineno && lineno < view->height);
2595         for (; lineno < view->height; lineno++) {
2596                 if (!draw_view_line(view, lineno))
2597                         break;
2598         }
2600         wnoutrefresh(view->win);
2603 static void
2604 redraw_view(struct view *view)
2606         werase(view->win);
2607         redraw_view_from(view, 0);
2611 static void
2612 update_view_title(struct view *view)
2614         char buf[SIZEOF_STR];
2615         char state[SIZEOF_STR];
2616         size_t bufpos = 0, statelen = 0;
2618         assert(view_is_displayed(view));
2620         if (view->type != VIEW_STATUS && view->lines) {
2621                 unsigned int view_lines = view->offset + view->height;
2622                 unsigned int lines = view->lines
2623                                    ? MIN(view_lines, view->lines) * 100 / view->lines
2624                                    : 0;
2626                 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2627                                    view->ops->type,
2628                                    view->lineno + 1,
2629                                    view->lines,
2630                                    lines);
2632         }
2634         if (view->pipe) {
2635                 time_t secs = time(NULL) - view->start_time;
2637                 /* Three git seconds are a long time ... */
2638                 if (secs > 2)
2639                         string_format_from(state, &statelen, " loading %lds", secs);
2640         }
2642         string_format_from(buf, &bufpos, "[%s]", view->name);
2643         if (*view->ref && bufpos < view->width) {
2644                 size_t refsize = strlen(view->ref);
2645                 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2647                 if (minsize < view->width)
2648                         refsize = view->width - minsize + 7;
2649                 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2650         }
2652         if (statelen && bufpos < view->width) {
2653                 string_format_from(buf, &bufpos, "%s", state);
2654         }
2656         if (view == display[current_view])
2657                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2658         else
2659                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2661         mvwaddnstr(view->title, 0, 0, buf, bufpos);
2662         wclrtoeol(view->title);
2663         wnoutrefresh(view->title);
2666 static int
2667 apply_step(double step, int value)
2669         if (step >= 1)
2670                 return (int) step;
2671         value *= step + 0.01;
2672         return value ? value : 1;
2675 static void
2676 resize_display(void)
2678         int offset, i;
2679         struct view *base = display[0];
2680         struct view *view = display[1] ? display[1] : display[0];
2682         /* Setup window dimensions */
2684         getmaxyx(stdscr, base->height, base->width);
2686         /* Make room for the status window. */
2687         base->height -= 1;
2689         if (view != base) {
2690                 /* Horizontal split. */
2691                 view->width   = base->width;
2692                 view->height  = apply_step(opt_scale_split_view, base->height);
2693                 view->height  = MAX(view->height, MIN_VIEW_HEIGHT);
2694                 view->height  = MIN(view->height, base->height - MIN_VIEW_HEIGHT);
2695                 base->height -= view->height;
2697                 /* Make room for the title bar. */
2698                 view->height -= 1;
2699         }
2701         /* Make room for the title bar. */
2702         base->height -= 1;
2704         offset = 0;
2706         foreach_displayed_view (view, i) {
2707                 if (!view->win) {
2708                         view->win = newwin(view->height, 0, offset, 0);
2709                         if (!view->win)
2710                                 die("Failed to create %s view", view->name);
2712                         scrollok(view->win, FALSE);
2714                         view->title = newwin(1, 0, offset + view->height, 0);
2715                         if (!view->title)
2716                                 die("Failed to create title window");
2718                 } else {
2719                         wresize(view->win, view->height, view->width);
2720                         mvwin(view->win,   offset, 0);
2721                         mvwin(view->title, offset + view->height, 0);
2722                 }
2724                 offset += view->height + 1;
2725         }
2728 static void
2729 redraw_display(bool clear)
2731         struct view *view;
2732         int i;
2734         foreach_displayed_view (view, i) {
2735                 if (clear)
2736                         wclear(view->win);
2737                 redraw_view(view);
2738                 update_view_title(view);
2739         }
2743 /*
2744  * Option management
2745  */
2747 static void
2748 toggle_enum_option_do(unsigned int *opt, const char *help,
2749                       const struct enum_map *map, size_t size)
2751         *opt = (*opt + 1) % size;
2752         redraw_display(FALSE);
2753         report("Displaying %s %s", enum_name(map[*opt]), help);
2756 #define toggle_enum_option(opt, help, map) \
2757         toggle_enum_option_do(opt, help, map, ARRAY_SIZE(map))
2759 #define toggle_date() toggle_enum_option(&opt_date, "dates", date_map)
2760 #define toggle_author() toggle_enum_option(&opt_author, "author names", author_map)
2762 static void
2763 toggle_view_option(bool *option, const char *help)
2765         *option = !*option;
2766         redraw_display(FALSE);
2767         report("%sabling %s", *option ? "En" : "Dis", help);
2770 static void
2771 open_option_menu(void)
2773         const struct menu_item menu[] = {
2774                 { '.', "line numbers", &opt_line_number },
2775                 { 'D', "date display", &opt_date },
2776                 { 'A', "author display", &opt_author },
2777                 { 'g', "revision graph display", &opt_rev_graph },
2778                 { 'F', "reference display", &opt_show_refs },
2779                 { 0 }
2780         };
2781         int selected = 0;
2783         if (prompt_menu("Toggle option", menu, &selected)) {
2784                 if (menu[selected].data == &opt_date)
2785                         toggle_date();
2786                 else if (menu[selected].data == &opt_author)
2787                         toggle_author();
2788                 else
2789                         toggle_view_option(menu[selected].data, menu[selected].text);
2790         }
2793 static void
2794 maximize_view(struct view *view)
2796         memset(display, 0, sizeof(display));
2797         current_view = 0;
2798         display[current_view] = view;
2799         resize_display();
2800         redraw_display(FALSE);
2801         report("");
2805 /*
2806  * Navigation
2807  */
2809 static bool
2810 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2812         if (lineno >= view->lines)
2813                 lineno = view->lines > 0 ? view->lines - 1 : 0;
2815         if (offset > lineno || offset + view->height <= lineno) {
2816                 unsigned long half = view->height / 2;
2818                 if (lineno > half)
2819                         offset = lineno - half;
2820                 else
2821                         offset = 0;
2822         }
2824         if (offset != view->offset || lineno != view->lineno) {
2825                 view->offset = offset;
2826                 view->lineno = lineno;
2827                 return TRUE;
2828         }
2830         return FALSE;
2833 /* Scrolling backend */
2834 static void
2835 do_scroll_view(struct view *view, int lines)
2837         bool redraw_current_line = FALSE;
2839         /* The rendering expects the new offset. */
2840         view->offset += lines;
2842         assert(0 <= view->offset && view->offset < view->lines);
2843         assert(lines);
2845         /* Move current line into the view. */
2846         if (view->lineno < view->offset) {
2847                 view->lineno = view->offset;
2848                 redraw_current_line = TRUE;
2849         } else if (view->lineno >= view->offset + view->height) {
2850                 view->lineno = view->offset + view->height - 1;
2851                 redraw_current_line = TRUE;
2852         }
2854         assert(view->offset <= view->lineno && view->lineno < view->lines);
2856         /* Redraw the whole screen if scrolling is pointless. */
2857         if (view->height < ABS(lines)) {
2858                 redraw_view(view);
2860         } else {
2861                 int line = lines > 0 ? view->height - lines : 0;
2862                 int end = line + ABS(lines);
2864                 scrollok(view->win, TRUE);
2865                 wscrl(view->win, lines);
2866                 scrollok(view->win, FALSE);
2868                 while (line < end && draw_view_line(view, line))
2869                         line++;
2871                 if (redraw_current_line)
2872                         draw_view_line(view, view->lineno - view->offset);
2873                 wnoutrefresh(view->win);
2874         }
2876         view->has_scrolled = TRUE;
2877         report("");
2880 /* Scroll frontend */
2881 static void
2882 scroll_view(struct view *view, enum request request)
2884         int lines = 1;
2886         assert(view_is_displayed(view));
2888         switch (request) {
2889         case REQ_SCROLL_LEFT:
2890                 if (view->yoffset == 0) {
2891                         report("Cannot scroll beyond the first column");
2892                         return;
2893                 }
2894                 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2895                         view->yoffset = 0;
2896                 else
2897                         view->yoffset -= apply_step(opt_hscroll, view->width);
2898                 redraw_view_from(view, 0);
2899                 report("");
2900                 return;
2901         case REQ_SCROLL_RIGHT:
2902                 view->yoffset += apply_step(opt_hscroll, view->width);
2903                 redraw_view(view);
2904                 report("");
2905                 return;
2906         case REQ_SCROLL_PAGE_DOWN:
2907                 lines = view->height;
2908         case REQ_SCROLL_LINE_DOWN:
2909                 if (view->offset + lines > view->lines)
2910                         lines = view->lines - view->offset;
2912                 if (lines == 0 || view->offset + view->height >= view->lines) {
2913                         report("Cannot scroll beyond the last line");
2914                         return;
2915                 }
2916                 break;
2918         case REQ_SCROLL_PAGE_UP:
2919                 lines = view->height;
2920         case REQ_SCROLL_LINE_UP:
2921                 if (lines > view->offset)
2922                         lines = view->offset;
2924                 if (lines == 0) {
2925                         report("Cannot scroll beyond the first line");
2926                         return;
2927                 }
2929                 lines = -lines;
2930                 break;
2932         default:
2933                 die("request %d not handled in switch", request);
2934         }
2936         do_scroll_view(view, lines);
2939 /* Cursor moving */
2940 static void
2941 move_view(struct view *view, enum request request)
2943         int scroll_steps = 0;
2944         int steps;
2946         switch (request) {
2947         case REQ_MOVE_FIRST_LINE:
2948                 steps = -view->lineno;
2949                 break;
2951         case REQ_MOVE_LAST_LINE:
2952                 steps = view->lines - view->lineno - 1;
2953                 break;
2955         case REQ_MOVE_PAGE_UP:
2956                 steps = view->height > view->lineno
2957                       ? -view->lineno : -view->height;
2958                 break;
2960         case REQ_MOVE_PAGE_DOWN:
2961                 steps = view->lineno + view->height >= view->lines
2962                       ? view->lines - view->lineno - 1 : view->height;
2963                 break;
2965         case REQ_MOVE_UP:
2966                 steps = -1;
2967                 break;
2969         case REQ_MOVE_DOWN:
2970                 steps = 1;
2971                 break;
2973         default:
2974                 die("request %d not handled in switch", request);
2975         }
2977         if (steps <= 0 && view->lineno == 0) {
2978                 report("Cannot move beyond the first line");
2979                 return;
2981         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2982                 report("Cannot move beyond the last line");
2983                 return;
2984         }
2986         /* Move the current line */
2987         view->lineno += steps;
2988         assert(0 <= view->lineno && view->lineno < view->lines);
2990         /* Check whether the view needs to be scrolled */
2991         if (view->lineno < view->offset ||
2992             view->lineno >= view->offset + view->height) {
2993                 scroll_steps = steps;
2994                 if (steps < 0 && -steps > view->offset) {
2995                         scroll_steps = -view->offset;
2997                 } else if (steps > 0) {
2998                         if (view->lineno == view->lines - 1 &&
2999                             view->lines > view->height) {
3000                                 scroll_steps = view->lines - view->offset - 1;
3001                                 if (scroll_steps >= view->height)
3002                                         scroll_steps -= view->height - 1;
3003                         }
3004                 }
3005         }
3007         if (!view_is_displayed(view)) {
3008                 view->offset += scroll_steps;
3009                 assert(0 <= view->offset && view->offset < view->lines);
3010                 view->ops->select(view, &view->line[view->lineno]);
3011                 return;
3012         }
3014         /* Repaint the old "current" line if we be scrolling */
3015         if (ABS(steps) < view->height)
3016                 draw_view_line(view, view->lineno - steps - view->offset);
3018         if (scroll_steps) {
3019                 do_scroll_view(view, scroll_steps);
3020                 return;
3021         }
3023         /* Draw the current line */
3024         draw_view_line(view, view->lineno - view->offset);
3026         wnoutrefresh(view->win);
3027         report("");
3031 /*
3032  * Searching
3033  */
3035 static void search_view(struct view *view, enum request request);
3037 static bool
3038 grep_text(struct view *view, const char *text[])
3040         regmatch_t pmatch;
3041         size_t i;
3043         for (i = 0; text[i]; i++)
3044                 if (*text[i] &&
3045                     regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
3046                         return TRUE;
3047         return FALSE;
3050 static void
3051 select_view_line(struct view *view, unsigned long lineno)
3053         unsigned long old_lineno = view->lineno;
3054         unsigned long old_offset = view->offset;
3056         if (goto_view_line(view, view->offset, lineno)) {
3057                 if (view_is_displayed(view)) {
3058                         if (old_offset != view->offset) {
3059                                 redraw_view(view);
3060                         } else {
3061                                 draw_view_line(view, old_lineno - view->offset);
3062                                 draw_view_line(view, view->lineno - view->offset);
3063                                 wnoutrefresh(view->win);
3064                         }
3065                 } else {
3066                         view->ops->select(view, &view->line[view->lineno]);
3067                 }
3068         }
3071 static void
3072 find_next(struct view *view, enum request request)
3074         unsigned long lineno = view->lineno;
3075         int direction;
3077         if (!*view->grep) {
3078                 if (!*opt_search)
3079                         report("No previous search");
3080                 else
3081                         search_view(view, request);
3082                 return;
3083         }
3085         switch (request) {
3086         case REQ_SEARCH:
3087         case REQ_FIND_NEXT:
3088                 direction = 1;
3089                 break;
3091         case REQ_SEARCH_BACK:
3092         case REQ_FIND_PREV:
3093                 direction = -1;
3094                 break;
3096         default:
3097                 return;
3098         }
3100         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
3101                 lineno += direction;
3103         /* Note, lineno is unsigned long so will wrap around in which case it
3104          * will become bigger than view->lines. */
3105         for (; lineno < view->lines; lineno += direction) {
3106                 if (view->ops->grep(view, &view->line[lineno])) {
3107                         select_view_line(view, lineno);
3108                         report("Line %ld matches '%s'", lineno + 1, view->grep);
3109                         return;
3110                 }
3111         }
3113         report("No match found for '%s'", view->grep);
3116 static void
3117 search_view(struct view *view, enum request request)
3119         int regex_err;
3121         if (view->regex) {
3122                 regfree(view->regex);
3123                 *view->grep = 0;
3124         } else {
3125                 view->regex = calloc(1, sizeof(*view->regex));
3126                 if (!view->regex)
3127                         return;
3128         }
3130         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
3131         if (regex_err != 0) {
3132                 char buf[SIZEOF_STR] = "unknown error";
3134                 regerror(regex_err, view->regex, buf, sizeof(buf));
3135                 report("Search failed: %s", buf);
3136                 return;
3137         }
3139         string_copy(view->grep, opt_search);
3141         find_next(view, request);
3144 /*
3145  * Incremental updating
3146  */
3148 static void
3149 reset_view(struct view *view)
3151         int i;
3153         for (i = 0; i < view->lines; i++)
3154                 free(view->line[i].data);
3155         free(view->line);
3157         view->p_offset = view->offset;
3158         view->p_yoffset = view->yoffset;
3159         view->p_lineno = view->lineno;
3161         view->line = NULL;
3162         view->offset = 0;
3163         view->yoffset = 0;
3164         view->lines  = 0;
3165         view->lineno = 0;
3166         view->vid[0] = 0;
3167         view->update_secs = 0;
3170 static const char *
3171 format_arg(const char *name)
3173         static struct {
3174                 const char *name;
3175                 size_t namelen;
3176                 const char *value;
3177                 const char *value_if_empty;
3178         } vars[] = {
3179 #define FORMAT_VAR(name, value, value_if_empty) \
3180         { name, STRING_SIZE(name), value, value_if_empty }
3181                 FORMAT_VAR("%(directory)",      opt_path,       ""),
3182                 FORMAT_VAR("%(file)",           opt_file,       ""),
3183                 FORMAT_VAR("%(ref)",            opt_ref,        "HEAD"),
3184                 FORMAT_VAR("%(head)",           ref_head,       ""),
3185                 FORMAT_VAR("%(commit)",         ref_commit,     ""),
3186                 FORMAT_VAR("%(blob)",           ref_blob,       ""),
3187                 FORMAT_VAR("%(branch)",         ref_branch,     ""),
3188         };
3189         int i;
3191         for (i = 0; i < ARRAY_SIZE(vars); i++)
3192                 if (!strncmp(name, vars[i].name, vars[i].namelen))
3193                         return *vars[i].value ? vars[i].value : vars[i].value_if_empty;
3195         report("Unknown replacement: `%s`", name);
3196         return NULL;
3199 static bool
3200 format_argv(const char ***dst_argv, const char *src_argv[], bool replace)
3202         char buf[SIZEOF_STR];
3203         int argc;
3205         argv_free(*dst_argv);
3207         for (argc = 0; src_argv[argc]; argc++) {
3208                 const char *arg = src_argv[argc];
3209                 size_t bufpos = 0;
3211                 if (!strcmp(arg, "%(fileargs)")) {
3212                         if (!argv_append_array(dst_argv, opt_file_args))
3213                                 break;
3214                         continue;
3216                 } else if (!strcmp(arg, "%(diffargs)")) {
3217                         if (!argv_append_array(dst_argv, opt_diff_args))
3218                                 break;
3219                         continue;
3221                 } else if (!strcmp(arg, "%(revargs)")) {
3222                         if (!argv_append_array(dst_argv, opt_rev_args))
3223                                 break;
3224                         continue;
3225                 }
3227                 while (arg) {
3228                         char *next = strstr(arg, "%(");
3229                         int len = next - arg;
3230                         const char *value;
3232                         if (!next || !replace) {
3233                                 len = strlen(arg);
3234                                 value = "";
3236                         } else {
3237                                 value = format_arg(next);
3239                                 if (!value) {
3240                                         return FALSE;
3241                                 }
3242                         }
3244                         if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
3245                                 return FALSE;
3247                         arg = next && replace ? strchr(next, ')') + 1 : NULL;
3248                 }
3250                 if (!argv_append(dst_argv, buf))
3251                         break;
3252         }
3254         return src_argv[argc] == NULL;
3257 static bool
3258 restore_view_position(struct view *view)
3260         if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
3261                 return FALSE;
3263         /* Changing the view position cancels the restoring. */
3264         /* FIXME: Changing back to the first line is not detected. */
3265         if (view->offset != 0 || view->lineno != 0) {
3266                 view->p_restore = FALSE;
3267                 return FALSE;
3268         }
3270         if (goto_view_line(view, view->p_offset, view->p_lineno) &&
3271             view_is_displayed(view))
3272                 werase(view->win);
3274         view->yoffset = view->p_yoffset;
3275         view->p_restore = FALSE;
3277         return TRUE;
3280 static void
3281 end_update(struct view *view, bool force)
3283         if (!view->pipe)
3284                 return;
3285         while (!view->ops->read(view, NULL))
3286                 if (!force)
3287                         return;
3288         if (force)
3289                 io_kill(view->pipe);
3290         io_done(view->pipe);
3291         view->pipe = NULL;
3294 static void
3295 setup_update(struct view *view, const char *vid)
3297         reset_view(view);
3298         string_copy_rev(view->vid, vid);
3299         view->pipe = &view->io;
3300         view->start_time = time(NULL);
3303 static bool
3304 prepare_io(struct view *view, const char *dir, const char *argv[], bool replace)
3306         view->dir = dir;
3307         return format_argv(&view->argv, argv, replace);
3310 static bool
3311 prepare_update(struct view *view, const char *argv[], const char *dir)
3313         if (view->pipe)
3314                 end_update(view, TRUE);
3315         return prepare_io(view, dir, argv, FALSE);
3318 static bool
3319 start_update(struct view *view, const char **argv, const char *dir)
3321         if (view->pipe)
3322                 io_done(view->pipe);
3323         return prepare_io(view, dir, argv, FALSE) &&
3324                io_run(&view->io, IO_RD, dir, view->argv);
3327 static bool
3328 prepare_update_file(struct view *view, const char *name)
3330         if (view->pipe)
3331                 end_update(view, TRUE);
3332         argv_free(view->argv);
3333         return io_open(&view->io, "%s/%s", opt_cdup[0] ? opt_cdup : ".", name);
3336 static bool
3337 begin_update(struct view *view, bool refresh)
3339         if (view->pipe)
3340                 end_update(view, TRUE);
3342         if (!refresh) {
3343                 if (view->ops->prepare) {
3344                         if (!view->ops->prepare(view))
3345                                 return FALSE;
3346                 } else if (!prepare_io(view, NULL, view->ops->argv, TRUE)) {
3347                         return FALSE;
3348                 }
3350                 /* Put the current ref_* value to the view title ref
3351                  * member. This is needed by the blob view. Most other
3352                  * views sets it automatically after loading because the
3353                  * first line is a commit line. */
3354                 string_copy_rev(view->ref, view->id);
3355         }
3357         if (view->argv && view->argv[0] &&
3358             !io_run(&view->io, IO_RD, view->dir, view->argv))
3359                 return FALSE;
3361         setup_update(view, view->id);
3363         return TRUE;
3366 static bool
3367 update_view(struct view *view)
3369         char out_buffer[BUFSIZ * 2];
3370         char *line;
3371         /* Clear the view and redraw everything since the tree sorting
3372          * might have rearranged things. */
3373         bool redraw = view->lines == 0;
3374         bool can_read = TRUE;
3376         if (!view->pipe)
3377                 return TRUE;
3379         if (!io_can_read(view->pipe)) {
3380                 if (view->lines == 0 && view_is_displayed(view)) {
3381                         time_t secs = time(NULL) - view->start_time;
3383                         if (secs > 1 && secs > view->update_secs) {
3384                                 if (view->update_secs == 0)
3385                                         redraw_view(view);
3386                                 update_view_title(view);
3387                                 view->update_secs = secs;
3388                         }
3389                 }
3390                 return TRUE;
3391         }
3393         for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
3394                 if (opt_iconv_in != ICONV_NONE) {
3395                         ICONV_CONST char *inbuf = line;
3396                         size_t inlen = strlen(line) + 1;
3398                         char *outbuf = out_buffer;
3399                         size_t outlen = sizeof(out_buffer);
3401                         size_t ret;
3403                         ret = iconv(opt_iconv_in, &inbuf, &inlen, &outbuf, &outlen);
3404                         if (ret != (size_t) -1)
3405                                 line = out_buffer;
3406                 }
3408                 if (!view->ops->read(view, line)) {
3409                         report("Allocation failure");
3410                         end_update(view, TRUE);
3411                         return FALSE;
3412                 }
3413         }
3415         {
3416                 unsigned long lines = view->lines;
3417                 int digits;
3419                 for (digits = 0; lines; digits++)
3420                         lines /= 10;
3422                 /* Keep the displayed view in sync with line number scaling. */
3423                 if (digits != view->digits) {
3424                         view->digits = digits;
3425                         if (opt_line_number || view->type == VIEW_BLAME)
3426                                 redraw = TRUE;
3427                 }
3428         }
3430         if (io_error(view->pipe)) {
3431                 report("Failed to read: %s", io_strerror(view->pipe));
3432                 end_update(view, TRUE);
3434         } else if (io_eof(view->pipe)) {
3435                 if (view_is_displayed(view))
3436                         report("");
3437                 end_update(view, FALSE);
3438         }
3440         if (restore_view_position(view))
3441                 redraw = TRUE;
3443         if (!view_is_displayed(view))
3444                 return TRUE;
3446         if (redraw)
3447                 redraw_view_from(view, 0);
3448         else
3449                 redraw_view_dirty(view);
3451         /* Update the title _after_ the redraw so that if the redraw picks up a
3452          * commit reference in view->ref it'll be available here. */
3453         update_view_title(view);
3454         return TRUE;
3457 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
3459 static struct line *
3460 add_line_data(struct view *view, void *data, enum line_type type)
3462         struct line *line;
3464         if (!realloc_lines(&view->line, view->lines, 1))
3465                 return NULL;
3467         line = &view->line[view->lines++];
3468         memset(line, 0, sizeof(*line));
3469         line->type = type;
3470         line->data = data;
3471         line->dirty = 1;
3473         return line;
3476 static struct line *
3477 add_line_text(struct view *view, const char *text, enum line_type type)
3479         char *data = text ? strdup(text) : NULL;
3481         return data ? add_line_data(view, data, type) : NULL;
3484 static struct line *
3485 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
3487         char buf[SIZEOF_STR];
3488         va_list args;
3490         va_start(args, fmt);
3491         if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
3492                 buf[0] = 0;
3493         va_end(args);
3495         return buf[0] ? add_line_text(view, buf, type) : NULL;
3498 /*
3499  * View opening
3500  */
3502 enum open_flags {
3503         OPEN_DEFAULT = 0,       /* Use default view switching. */
3504         OPEN_SPLIT = 1,         /* Split current view. */
3505         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
3506         OPEN_REFRESH = 16,      /* Refresh view using previous command. */
3507         OPEN_PREPARED = 32,     /* Open already prepared command. */
3508 };
3510 static void
3511 open_view(struct view *prev, enum request request, enum open_flags flags)
3513         bool split = !!(flags & OPEN_SPLIT);
3514         bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
3515         bool nomaximize = !!(flags & OPEN_REFRESH);
3516         struct view *view = VIEW(request);
3517         int nviews = displayed_views();
3518         struct view *base_view = display[0];
3520         if (view == prev && nviews == 1 && !reload) {
3521                 report("Already in %s view", view->name);
3522                 return;
3523         }
3525         if (view->git_dir && !opt_git_dir[0]) {
3526                 report("The %s view is disabled in pager view", view->name);
3527                 return;
3528         }
3530         if (split) {
3531                 display[1] = view;
3532                 current_view = 1;
3533                 view->parent = prev;
3534         } else if (!nomaximize) {
3535                 /* Maximize the current view. */
3536                 memset(display, 0, sizeof(display));
3537                 current_view = 0;
3538                 display[current_view] = view;
3539         }
3541         /* No prev signals that this is the first loaded view. */
3542         if (prev && view != prev) {
3543                 view->prev = prev;
3544         }
3546         /* Resize the view when switching between split- and full-screen,
3547          * or when switching between two different full-screen views. */
3548         if (nviews != displayed_views() ||
3549             (nviews == 1 && base_view != display[0]))
3550                 resize_display();
3552         if (view->ops->open) {
3553                 if (view->pipe)
3554                         end_update(view, TRUE);
3555                 if (!view->ops->open(view)) {
3556                         report("Failed to load %s view", view->name);
3557                         return;
3558                 }
3559                 restore_view_position(view);
3561         } else if ((reload || strcmp(view->vid, view->id)) &&
3562                    !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3563                 report("Failed to load %s view", view->name);
3564                 return;
3565         }
3567         if (split && prev->lineno - prev->offset >= prev->height) {
3568                 /* Take the title line into account. */
3569                 int lines = prev->lineno - prev->offset - prev->height + 1;
3571                 /* Scroll the view that was split if the current line is
3572                  * outside the new limited view. */
3573                 do_scroll_view(prev, lines);
3574         }
3576         if (prev && view != prev && split && view_is_displayed(prev)) {
3577                 /* "Blur" the previous view. */
3578                 update_view_title(prev);
3579         }
3581         if (view->pipe && view->lines == 0) {
3582                 /* Clear the old view and let the incremental updating refill
3583                  * the screen. */
3584                 werase(view->win);
3585                 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3586                 report("");
3587         } else if (view_is_displayed(view)) {
3588                 redraw_view(view);
3589                 report("");
3590         }
3593 static void
3594 open_external_viewer(const char *argv[], const char *dir)
3596         def_prog_mode();           /* save current tty modes */
3597         endwin();                  /* restore original tty modes */
3598         io_run_fg(argv, dir);
3599         fprintf(stderr, "Press Enter to continue");
3600         getc(opt_tty);
3601         reset_prog_mode();
3602         redraw_display(TRUE);
3605 static void
3606 open_mergetool(const char *file)
3608         const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3610         open_external_viewer(mergetool_argv, opt_cdup);
3613 static void
3614 open_editor(const char *file)
3616         const char *editor_argv[] = { "vi", file, NULL };
3617         const char *editor;
3619         editor = getenv("GIT_EDITOR");
3620         if (!editor && *opt_editor)
3621                 editor = opt_editor;
3622         if (!editor)
3623                 editor = getenv("VISUAL");
3624         if (!editor)
3625                 editor = getenv("EDITOR");
3626         if (!editor)
3627                 editor = "vi";
3629         editor_argv[0] = editor;
3630         open_external_viewer(editor_argv, opt_cdup);
3633 static void
3634 open_run_request(enum request request)
3636         struct run_request *req = get_run_request(request);
3637         const char **argv = NULL;
3639         if (!req) {
3640                 report("Unknown run request");
3641                 return;
3642         }
3644         if (format_argv(&argv, req->argv, TRUE))
3645                 open_external_viewer(argv, NULL);
3646         if (argv)
3647                 argv_free(argv);
3648         free(argv);
3651 /*
3652  * User request switch noodle
3653  */
3655 static int
3656 view_driver(struct view *view, enum request request)
3658         int i;
3660         if (request == REQ_NONE)
3661                 return TRUE;
3663         if (request > REQ_NONE) {
3664                 open_run_request(request);
3665                 view_request(view, REQ_REFRESH);
3666                 return TRUE;
3667         }
3669         request = view_request(view, request);
3670         if (request == REQ_NONE)
3671                 return TRUE;
3673         switch (request) {
3674         case REQ_MOVE_UP:
3675         case REQ_MOVE_DOWN:
3676         case REQ_MOVE_PAGE_UP:
3677         case REQ_MOVE_PAGE_DOWN:
3678         case REQ_MOVE_FIRST_LINE:
3679         case REQ_MOVE_LAST_LINE:
3680                 move_view(view, request);
3681                 break;
3683         case REQ_SCROLL_LEFT:
3684         case REQ_SCROLL_RIGHT:
3685         case REQ_SCROLL_LINE_DOWN:
3686         case REQ_SCROLL_LINE_UP:
3687         case REQ_SCROLL_PAGE_DOWN:
3688         case REQ_SCROLL_PAGE_UP:
3689                 scroll_view(view, request);
3690                 break;
3692         case REQ_VIEW_BLAME:
3693                 if (!opt_file[0]) {
3694                         report("No file chosen, press %s to open tree view",
3695                                get_key(view->keymap, REQ_VIEW_TREE));
3696                         break;
3697                 }
3698                 open_view(view, request, OPEN_DEFAULT);
3699                 break;
3701         case REQ_VIEW_BLOB:
3702                 if (!ref_blob[0]) {
3703                         report("No file chosen, press %s to open tree view",
3704                                get_key(view->keymap, REQ_VIEW_TREE));
3705                         break;
3706                 }
3707                 open_view(view, request, OPEN_DEFAULT);
3708                 break;
3710         case REQ_VIEW_PAGER:
3711                 if (view == NULL) {
3712                         if (!io_open(&VIEW(REQ_VIEW_PAGER)->io, ""))
3713                                 die("Failed to open stdin");
3714                         open_view(view, request, OPEN_PREPARED);
3715                         break;
3716                 }
3718                 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3719                         report("No pager content, press %s to run command from prompt",
3720                                get_key(view->keymap, REQ_PROMPT));
3721                         break;
3722                 }
3723                 open_view(view, request, OPEN_DEFAULT);
3724                 break;
3726         case REQ_VIEW_STAGE:
3727                 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3728                         report("No stage content, press %s to open the status view and choose file",
3729                                get_key(view->keymap, REQ_VIEW_STATUS));
3730                         break;
3731                 }
3732                 open_view(view, request, OPEN_DEFAULT);
3733                 break;
3735         case REQ_VIEW_STATUS:
3736                 if (opt_is_inside_work_tree == FALSE) {
3737                         report("The status view requires a working tree");
3738                         break;
3739                 }
3740                 open_view(view, request, OPEN_DEFAULT);
3741                 break;
3743         case REQ_VIEW_MAIN:
3744         case REQ_VIEW_DIFF:
3745         case REQ_VIEW_LOG:
3746         case REQ_VIEW_TREE:
3747         case REQ_VIEW_HELP:
3748         case REQ_VIEW_BRANCH:
3749                 open_view(view, request, OPEN_DEFAULT);
3750                 break;
3752         case REQ_NEXT:
3753         case REQ_PREVIOUS:
3754                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3756                 if (view->parent) {
3757                         int line;
3759                         view = view->parent;
3760                         line = view->lineno;
3761                         move_view(view, request);
3762                         if (view_is_displayed(view))
3763                                 update_view_title(view);
3764                         if (line != view->lineno)
3765                                 view_request(view, REQ_ENTER);
3766                 } else {
3767                         move_view(view, request);
3768                 }
3769                 break;
3771         case REQ_VIEW_NEXT:
3772         {
3773                 int nviews = displayed_views();
3774                 int next_view = (current_view + 1) % nviews;
3776                 if (next_view == current_view) {
3777                         report("Only one view is displayed");
3778                         break;
3779                 }
3781                 current_view = next_view;
3782                 /* Blur out the title of the previous view. */
3783                 update_view_title(view);
3784                 report("");
3785                 break;
3786         }
3787         case REQ_REFRESH:
3788                 report("Refreshing is not yet supported for the %s view", view->name);
3789                 break;
3791         case REQ_MAXIMIZE:
3792                 if (displayed_views() == 2)
3793                         maximize_view(view);
3794                 break;
3796         case REQ_OPTIONS:
3797                 open_option_menu();
3798                 break;
3800         case REQ_TOGGLE_LINENO:
3801                 toggle_view_option(&opt_line_number, "line numbers");
3802                 break;
3804         case REQ_TOGGLE_DATE:
3805                 toggle_date();
3806                 break;
3808         case REQ_TOGGLE_AUTHOR:
3809                 toggle_author();
3810                 break;
3812         case REQ_TOGGLE_REV_GRAPH:
3813                 toggle_view_option(&opt_rev_graph, "revision graph display");
3814                 break;
3816         case REQ_TOGGLE_REFS:
3817                 toggle_view_option(&opt_show_refs, "reference display");
3818                 break;
3820         case REQ_TOGGLE_SORT_FIELD:
3821         case REQ_TOGGLE_SORT_ORDER:
3822                 report("Sorting is not yet supported for the %s view", view->name);
3823                 break;
3825         case REQ_SEARCH:
3826         case REQ_SEARCH_BACK:
3827                 search_view(view, request);
3828                 break;
3830         case REQ_FIND_NEXT:
3831         case REQ_FIND_PREV:
3832                 find_next(view, request);
3833                 break;
3835         case REQ_STOP_LOADING:
3836                 foreach_view(view, i) {
3837                         if (view->pipe)
3838                                 report("Stopped loading the %s view", view->name),
3839                         end_update(view, TRUE);
3840                 }
3841                 break;
3843         case REQ_SHOW_VERSION:
3844                 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3845                 return TRUE;
3847         case REQ_SCREEN_REDRAW:
3848                 redraw_display(TRUE);
3849                 break;
3851         case REQ_EDIT:
3852                 report("Nothing to edit");
3853                 break;
3855         case REQ_ENTER:
3856                 report("Nothing to enter");
3857                 break;
3859         case REQ_VIEW_CLOSE:
3860                 /* XXX: Mark closed views by letting view->prev point to the
3861                  * view itself. Parents to closed view should never be
3862                  * followed. */
3863                 if (view->prev && view->prev != view) {
3864                         maximize_view(view->prev);
3865                         view->prev = view;
3866                         break;
3867                 }
3868                 /* Fall-through */
3869         case REQ_QUIT:
3870                 return FALSE;
3872         default:
3873                 report("Unknown key, press %s for help",
3874                        get_key(view->keymap, REQ_VIEW_HELP));
3875                 return TRUE;
3876         }
3878         return TRUE;
3882 /*
3883  * View backend utilities
3884  */
3886 enum sort_field {
3887         ORDERBY_NAME,
3888         ORDERBY_DATE,
3889         ORDERBY_AUTHOR,
3890 };
3892 struct sort_state {
3893         const enum sort_field *fields;
3894         size_t size, current;
3895         bool reverse;
3896 };
3898 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3899 #define get_sort_field(state) ((state).fields[(state).current])
3900 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3902 static void
3903 sort_view(struct view *view, enum request request, struct sort_state *state,
3904           int (*compare)(const void *, const void *))
3906         switch (request) {
3907         case REQ_TOGGLE_SORT_FIELD:
3908                 state->current = (state->current + 1) % state->size;
3909                 break;
3911         case REQ_TOGGLE_SORT_ORDER:
3912                 state->reverse = !state->reverse;
3913                 break;
3914         default:
3915                 die("Not a sort request");
3916         }
3918         qsort(view->line, view->lines, sizeof(*view->line), compare);
3919         redraw_view(view);
3922 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3924 /* Small author cache to reduce memory consumption. It uses binary
3925  * search to lookup or find place to position new entries. No entries
3926  * are ever freed. */
3927 static const char *
3928 get_author(const char *name)
3930         static const char **authors;
3931         static size_t authors_size;
3932         int from = 0, to = authors_size - 1;
3934         while (from <= to) {
3935                 size_t pos = (to + from) / 2;
3936                 int cmp = strcmp(name, authors[pos]);
3938                 if (!cmp)
3939                         return authors[pos];
3941                 if (cmp < 0)
3942                         to = pos - 1;
3943                 else
3944                         from = pos + 1;
3945         }
3947         if (!realloc_authors(&authors, authors_size, 1))
3948                 return NULL;
3949         name = strdup(name);
3950         if (!name)
3951                 return NULL;
3953         memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3954         authors[from] = name;
3955         authors_size++;
3957         return name;
3960 static void
3961 parse_timesec(struct time *time, const char *sec)
3963         time->sec = (time_t) atol(sec);
3966 static void
3967 parse_timezone(struct time *time, const char *zone)
3969         long tz;
3971         tz  = ('0' - zone[1]) * 60 * 60 * 10;
3972         tz += ('0' - zone[2]) * 60 * 60;
3973         tz += ('0' - zone[3]) * 60 * 10;
3974         tz += ('0' - zone[4]) * 60;
3976         if (zone[0] == '-')
3977                 tz = -tz;
3979         time->tz = tz;
3980         time->sec -= tz;
3983 /* Parse author lines where the name may be empty:
3984  *      author  <email@address.tld> 1138474660 +0100
3985  */
3986 static void
3987 parse_author_line(char *ident, const char **author, struct time *time)
3989         char *nameend = strchr(ident, '<');
3990         char *emailend = strchr(ident, '>');
3992         if (nameend && emailend)
3993                 *nameend = *emailend = 0;
3994         ident = chomp_string(ident);
3995         if (!*ident) {
3996                 if (nameend)
3997                         ident = chomp_string(nameend + 1);
3998                 if (!*ident)
3999                         ident = "Unknown";
4000         }
4002         *author = get_author(ident);
4004         /* Parse epoch and timezone */
4005         if (emailend && emailend[1] == ' ') {
4006                 char *secs = emailend + 2;
4007                 char *zone = strchr(secs, ' ');
4009                 parse_timesec(time, secs);
4011                 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
4012                         parse_timezone(time, zone + 1);
4013         }
4016 /*
4017  * Pager backend
4018  */
4020 static bool
4021 pager_draw(struct view *view, struct line *line, unsigned int lineno)
4023         if (opt_line_number && draw_lineno(view, lineno))
4024                 return TRUE;
4026         draw_text(view, line->type, line->data, TRUE);
4027         return TRUE;
4030 static bool
4031 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
4033         const char *describe_argv[] = { "git", "describe", commit_id, NULL };
4034         char ref[SIZEOF_STR];
4036         if (!io_run_buf(describe_argv, ref, sizeof(ref)) || !*ref)
4037                 return TRUE;
4039         /* This is the only fatal call, since it can "corrupt" the buffer. */
4040         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
4041                 return FALSE;
4043         return TRUE;
4046 static void
4047 add_pager_refs(struct view *view, struct line *line)
4049         char buf[SIZEOF_STR];
4050         char *commit_id = (char *)line->data + STRING_SIZE("commit ");
4051         struct ref_list *list;
4052         size_t bufpos = 0, i;
4053         const char *sep = "Refs: ";
4054         bool is_tag = FALSE;
4056         assert(line->type == LINE_COMMIT);
4058         list = get_ref_list(commit_id);
4059         if (!list) {
4060                 if (view->type == VIEW_DIFF)
4061                         goto try_add_describe_ref;
4062                 return;
4063         }
4065         for (i = 0; i < list->size; i++) {
4066                 struct ref *ref = list->refs[i];
4067                 const char *fmt = ref->tag    ? "%s[%s]" :
4068                                   ref->remote ? "%s<%s>" : "%s%s";
4070                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
4071                         return;
4072                 sep = ", ";
4073                 if (ref->tag)
4074                         is_tag = TRUE;
4075         }
4077         if (!is_tag && view->type == VIEW_DIFF) {
4078 try_add_describe_ref:
4079                 /* Add <tag>-g<commit_id> "fake" reference. */
4080                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
4081                         return;
4082         }
4084         if (bufpos == 0)
4085                 return;
4087         add_line_text(view, buf, LINE_PP_REFS);
4090 static bool
4091 pager_read(struct view *view, char *data)
4093         struct line *line;
4095         if (!data)
4096                 return TRUE;
4098         line = add_line_text(view, data, get_line_type(data));
4099         if (!line)
4100                 return FALSE;
4102         if (line->type == LINE_COMMIT &&
4103             (view->type == VIEW_DIFF ||
4104              view->type == VIEW_LOG))
4105                 add_pager_refs(view, line);
4107         return TRUE;
4110 static enum request
4111 pager_request(struct view *view, enum request request, struct line *line)
4113         int split = 0;
4115         if (request != REQ_ENTER)
4116                 return request;
4118         if (line->type == LINE_COMMIT &&
4119            (view->type == VIEW_LOG ||
4120             view->type == VIEW_PAGER)) {
4121                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
4122                 split = 1;
4123         }
4125         /* Always scroll the view even if it was split. That way
4126          * you can use Enter to scroll through the log view and
4127          * split open each commit diff. */
4128         scroll_view(view, REQ_SCROLL_LINE_DOWN);
4130         /* FIXME: A minor workaround. Scrolling the view will call report("")
4131          * but if we are scrolling a non-current view this won't properly
4132          * update the view title. */
4133         if (split)
4134                 update_view_title(view);
4136         return REQ_NONE;
4139 static bool
4140 pager_grep(struct view *view, struct line *line)
4142         const char *text[] = { line->data, NULL };
4144         return grep_text(view, text);
4147 static void
4148 pager_select(struct view *view, struct line *line)
4150         if (line->type == LINE_COMMIT) {
4151                 char *text = (char *)line->data + STRING_SIZE("commit ");
4153                 if (view->type != VIEW_PAGER)
4154                         string_copy_rev(view->ref, text);
4155                 string_copy_rev(ref_commit, text);
4156         }
4159 static struct view_ops pager_ops = {
4160         "line",
4161         NULL,
4162         NULL,
4163         pager_read,
4164         pager_draw,
4165         pager_request,
4166         pager_grep,
4167         pager_select,
4168 };
4170 static const char *log_argv[SIZEOF_ARG] = {
4171         "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
4172 };
4174 static enum request
4175 log_request(struct view *view, enum request request, struct line *line)
4177         switch (request) {
4178         case REQ_REFRESH:
4179                 load_refs();
4180                 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
4181                 return REQ_NONE;
4182         default:
4183                 return pager_request(view, request, line);
4184         }
4187 static struct view_ops log_ops = {
4188         "line",
4189         log_argv,
4190         NULL,
4191         pager_read,
4192         pager_draw,
4193         log_request,
4194         pager_grep,
4195         pager_select,
4196 };
4198 static const char *diff_argv[SIZEOF_ARG] = {
4199         "git", "show", "--pretty=fuller", "--no-color", "--root",
4200                 "--patch-with-stat", "--find-copies-harder", "-C",
4201                 "%(diffargs)", "%(commit)", "--", "%(fileargs)", NULL
4202 };
4204 static bool
4205 diff_read(struct view *view, char *data)
4207         if (!data) {
4208                 /* Fall back to retry if no diff will be shown. */
4209                 if (view->lines == 0 && opt_file_args) {
4210                         int pos = argv_size(view->argv)
4211                                 - argv_size(opt_file_args) - 1;
4213                         if (pos > 0 && !strcmp(view->argv[pos], "--")) {
4214                                 for (; view->argv[pos]; pos++) {
4215                                         free((void *) view->argv[pos]);
4216                                         view->argv[pos] = NULL;
4217                                 }
4219                                 if (view->pipe)
4220                                         io_done(view->pipe);
4221                                 if (io_run(&view->io, IO_RD, view->dir, view->argv))
4222                                         return FALSE;
4223                         }
4224                 }
4225                 return TRUE;
4226         }
4228         return pager_read(view, data);
4231 static struct view_ops diff_ops = {
4232         "line",
4233         diff_argv,
4234         NULL,
4235         diff_read,
4236         pager_draw,
4237         pager_request,
4238         pager_grep,
4239         pager_select,
4240 };
4242 /*
4243  * Help backend
4244  */
4246 static bool help_keymap_hidden[ARRAY_SIZE(keymap_table)];
4248 static bool
4249 help_open_keymap_title(struct view *view, enum keymap keymap)
4251         struct line *line;
4253         line = add_line_format(view, LINE_HELP_KEYMAP, "[%c] %s bindings",
4254                                help_keymap_hidden[keymap] ? '+' : '-',
4255                                enum_name(keymap_table[keymap]));
4256         if (line)
4257                 line->other = keymap;
4259         return help_keymap_hidden[keymap];
4262 static void
4263 help_open_keymap(struct view *view, enum keymap keymap)
4265         const char *group = NULL;
4266         char buf[SIZEOF_STR];
4267         size_t bufpos;
4268         bool add_title = TRUE;
4269         int i;
4271         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
4272                 const char *key = NULL;
4274                 if (req_info[i].request == REQ_NONE)
4275                         continue;
4277                 if (!req_info[i].request) {
4278                         group = req_info[i].help;
4279                         continue;
4280                 }
4282                 key = get_keys(keymap, req_info[i].request, TRUE);
4283                 if (!key || !*key)
4284                         continue;
4286                 if (add_title && help_open_keymap_title(view, keymap))
4287                         return;
4288                 add_title = FALSE;
4290                 if (group) {
4291                         add_line_text(view, group, LINE_HELP_GROUP);
4292                         group = NULL;
4293                 }
4295                 add_line_format(view, LINE_DEFAULT, "    %-25s %-20s %s", key,
4296                                 enum_name(req_info[i]), req_info[i].help);
4297         }
4299         group = "External commands:";
4301         for (i = 0; i < run_requests; i++) {
4302                 struct run_request *req = get_run_request(REQ_NONE + i + 1);
4303                 const char *key;
4304                 int argc;
4306                 if (!req || req->keymap != keymap)
4307                         continue;
4309                 key = get_key_name(req->key);
4310                 if (!*key)
4311                         key = "(no key defined)";
4313                 if (add_title && help_open_keymap_title(view, keymap))
4314                         return;
4315                 if (group) {
4316                         add_line_text(view, group, LINE_HELP_GROUP);
4317                         group = NULL;
4318                 }
4320                 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
4321                         if (!string_format_from(buf, &bufpos, "%s%s",
4322                                                 argc ? " " : "", req->argv[argc]))
4323                                 return;
4325                 add_line_format(view, LINE_DEFAULT, "    %-25s `%s`", key, buf);
4326         }
4329 static bool
4330 help_open(struct view *view)
4332         enum keymap keymap;
4334         reset_view(view);
4335         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
4336         add_line_text(view, "", LINE_DEFAULT);
4338         for (keymap = 0; keymap < ARRAY_SIZE(keymap_table); keymap++)
4339                 help_open_keymap(view, keymap);
4341         return TRUE;
4344 static enum request
4345 help_request(struct view *view, enum request request, struct line *line)
4347         switch (request) {
4348         case REQ_ENTER:
4349                 if (line->type == LINE_HELP_KEYMAP) {
4350                         help_keymap_hidden[line->other] =
4351                                 !help_keymap_hidden[line->other];
4352                         view->p_restore = TRUE;
4353                         open_view(view, REQ_VIEW_HELP, OPEN_REFRESH);
4354                 }
4356                 return REQ_NONE;
4357         default:
4358                 return pager_request(view, request, line);
4359         }
4362 static struct view_ops help_ops = {
4363         "line",
4364         NULL,
4365         help_open,
4366         NULL,
4367         pager_draw,
4368         help_request,
4369         pager_grep,
4370         pager_select,
4371 };
4374 /*
4375  * Tree backend
4376  */
4378 struct tree_stack_entry {
4379         struct tree_stack_entry *prev;  /* Entry below this in the stack */
4380         unsigned long lineno;           /* Line number to restore */
4381         char *name;                     /* Position of name in opt_path */
4382 };
4384 /* The top of the path stack. */
4385 static struct tree_stack_entry *tree_stack = NULL;
4386 unsigned long tree_lineno = 0;
4388 static void
4389 pop_tree_stack_entry(void)
4391         struct tree_stack_entry *entry = tree_stack;
4393         tree_lineno = entry->lineno;
4394         entry->name[0] = 0;
4395         tree_stack = entry->prev;
4396         free(entry);
4399 static void
4400 push_tree_stack_entry(const char *name, unsigned long lineno)
4402         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
4403         size_t pathlen = strlen(opt_path);
4405         if (!entry)
4406                 return;
4408         entry->prev = tree_stack;
4409         entry->name = opt_path + pathlen;
4410         tree_stack = entry;
4412         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
4413                 pop_tree_stack_entry();
4414                 return;
4415         }
4417         /* Move the current line to the first tree entry. */
4418         tree_lineno = 1;
4419         entry->lineno = lineno;
4422 /* Parse output from git-ls-tree(1):
4423  *
4424  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4425  */
4427 #define SIZEOF_TREE_ATTR \
4428         STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4430 #define SIZEOF_TREE_MODE \
4431         STRING_SIZE("100644 ")
4433 #define TREE_ID_OFFSET \
4434         STRING_SIZE("100644 blob ")
4436 struct tree_entry {
4437         char id[SIZEOF_REV];
4438         mode_t mode;
4439         struct time time;               /* Date from the author ident. */
4440         const char *author;             /* Author of the commit. */
4441         char name[1];
4442 };
4444 static const char *
4445 tree_path(const struct line *line)
4447         return ((struct tree_entry *) line->data)->name;
4450 static int
4451 tree_compare_entry(const struct line *line1, const struct line *line2)
4453         if (line1->type != line2->type)
4454                 return line1->type == LINE_TREE_DIR ? -1 : 1;
4455         return strcmp(tree_path(line1), tree_path(line2));
4458 static const enum sort_field tree_sort_fields[] = {
4459         ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4460 };
4461 static struct sort_state tree_sort_state = SORT_STATE(tree_sort_fields);
4463 static int
4464 tree_compare(const void *l1, const void *l2)
4466         const struct line *line1 = (const struct line *) l1;
4467         const struct line *line2 = (const struct line *) l2;
4468         const struct tree_entry *entry1 = ((const struct line *) l1)->data;
4469         const struct tree_entry *entry2 = ((const struct line *) l2)->data;
4471         if (line1->type == LINE_TREE_HEAD)
4472                 return -1;
4473         if (line2->type == LINE_TREE_HEAD)
4474                 return 1;
4476         switch (get_sort_field(tree_sort_state)) {
4477         case ORDERBY_DATE:
4478                 return sort_order(tree_sort_state, timecmp(&entry1->time, &entry2->time));
4480         case ORDERBY_AUTHOR:
4481                 return sort_order(tree_sort_state, strcmp(entry1->author, entry2->author));
4483         case ORDERBY_NAME:
4484         default:
4485                 return sort_order(tree_sort_state, tree_compare_entry(line1, line2));
4486         }
4490 static struct line *
4491 tree_entry(struct view *view, enum line_type type, const char *path,
4492            const char *mode, const char *id)
4494         struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
4495         struct line *line = entry ? add_line_data(view, entry, type) : NULL;
4497         if (!entry || !line) {
4498                 free(entry);
4499                 return NULL;
4500         }
4502         strncpy(entry->name, path, strlen(path));
4503         if (mode)
4504                 entry->mode = strtoul(mode, NULL, 8);
4505         if (id)
4506                 string_copy_rev(entry->id, id);
4508         return line;
4511 static bool
4512 tree_read_date(struct view *view, char *text, bool *read_date)
4514         static const char *author_name;
4515         static struct time author_time;
4517         if (!text && *read_date) {
4518                 *read_date = FALSE;
4519                 return TRUE;
4521         } else if (!text) {
4522                 char *path = *opt_path ? opt_path : ".";
4523                 /* Find next entry to process */
4524                 const char *log_file[] = {
4525                         "git", "log", "--no-color", "--pretty=raw",
4526                                 "--cc", "--raw", view->id, "--", path, NULL
4527                 };
4529                 if (!view->lines) {
4530                         tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
4531                         report("Tree is empty");
4532                         return TRUE;
4533                 }
4535                 if (!start_update(view, log_file, opt_cdup)) {
4536                         report("Failed to load tree data");
4537                         return TRUE;
4538                 }
4540                 *read_date = TRUE;
4541                 return FALSE;
4543         } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
4544                 parse_author_line(text + STRING_SIZE("author "),
4545                                   &author_name, &author_time);
4547         } else if (*text == ':') {
4548                 char *pos;
4549                 size_t annotated = 1;
4550                 size_t i;
4552                 pos = strchr(text, '\t');
4553                 if (!pos)
4554                         return TRUE;
4555                 text = pos + 1;
4556                 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
4557                         text += strlen(opt_path);
4558                 pos = strchr(text, '/');
4559                 if (pos)
4560                         *pos = 0;
4562                 for (i = 1; i < view->lines; i++) {
4563                         struct line *line = &view->line[i];
4564                         struct tree_entry *entry = line->data;
4566                         annotated += !!entry->author;
4567                         if (entry->author || strcmp(entry->name, text))
4568                                 continue;
4570                         entry->author = author_name;
4571                         entry->time = author_time;
4572                         line->dirty = 1;
4573                         break;
4574                 }
4576                 if (annotated == view->lines)
4577                         io_kill(view->pipe);
4578         }
4579         return TRUE;
4582 static bool
4583 tree_read(struct view *view, char *text)
4585         static bool read_date = FALSE;
4586         struct tree_entry *data;
4587         struct line *entry, *line;
4588         enum line_type type;
4589         size_t textlen = text ? strlen(text) : 0;
4590         char *path = text + SIZEOF_TREE_ATTR;
4592         if (read_date || !text)
4593                 return tree_read_date(view, text, &read_date);
4595         if (textlen <= SIZEOF_TREE_ATTR)
4596                 return FALSE;
4597         if (view->lines == 0 &&
4598             !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
4599                 return FALSE;
4601         /* Strip the path part ... */
4602         if (*opt_path) {
4603                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
4604                 size_t striplen = strlen(opt_path);
4606                 if (pathlen > striplen)
4607                         memmove(path, path + striplen,
4608                                 pathlen - striplen + 1);
4610                 /* Insert "link" to parent directory. */
4611                 if (view->lines == 1 &&
4612                     !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
4613                         return FALSE;
4614         }
4616         type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
4617         entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
4618         if (!entry)
4619                 return FALSE;
4620         data = entry->data;
4622         /* Skip "Directory ..." and ".." line. */
4623         for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
4624                 if (tree_compare_entry(line, entry) <= 0)
4625                         continue;
4627                 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4629                 line->data = data;
4630                 line->type = type;
4631                 for (; line <= entry; line++)
4632                         line->dirty = line->cleareol = 1;
4633                 return TRUE;
4634         }
4636         if (tree_lineno > view->lineno) {
4637                 view->lineno = tree_lineno;
4638                 tree_lineno = 0;
4639         }
4641         return TRUE;
4644 static bool
4645 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4647         struct tree_entry *entry = line->data;
4649         if (line->type == LINE_TREE_HEAD) {
4650                 if (draw_text(view, line->type, "Directory path /", TRUE))
4651                         return TRUE;
4652         } else {
4653                 if (draw_mode(view, entry->mode))
4654                         return TRUE;
4656                 if (opt_author && draw_author(view, entry->author))
4657                         return TRUE;
4659                 if (opt_date && draw_date(view, &entry->time))
4660                         return TRUE;
4661         }
4662         if (draw_text(view, line->type, entry->name, TRUE))
4663                 return TRUE;
4664         return TRUE;
4667 static void
4668 open_blob_editor(const char *id)
4670         const char *blob_argv[] = { "git", "cat-file", "blob", id, NULL };
4671         char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4672         int fd = mkstemp(file);
4674         if (fd == -1)
4675                 report("Failed to create temporary file");
4676         else if (!io_run_append(blob_argv, fd))
4677                 report("Failed to save blob data to file");
4678         else
4679                 open_editor(file);
4680         if (fd != -1)
4681                 unlink(file);
4684 static enum request
4685 tree_request(struct view *view, enum request request, struct line *line)
4687         enum open_flags flags;
4688         struct tree_entry *entry = line->data;
4690         switch (request) {
4691         case REQ_VIEW_BLAME:
4692                 if (line->type != LINE_TREE_FILE) {
4693                         report("Blame only supported for files");
4694                         return REQ_NONE;
4695                 }
4697                 string_copy(opt_ref, view->vid);
4698                 return request;
4700         case REQ_EDIT:
4701                 if (line->type != LINE_TREE_FILE) {
4702                         report("Edit only supported for files");
4703                 } else if (!is_head_commit(view->vid)) {
4704                         open_blob_editor(entry->id);
4705                 } else {
4706                         open_editor(opt_file);
4707                 }
4708                 return REQ_NONE;
4710         case REQ_TOGGLE_SORT_FIELD:
4711         case REQ_TOGGLE_SORT_ORDER:
4712                 sort_view(view, request, &tree_sort_state, tree_compare);
4713                 return REQ_NONE;
4715         case REQ_PARENT:
4716                 if (!*opt_path) {
4717                         /* quit view if at top of tree */
4718                         return REQ_VIEW_CLOSE;
4719                 }
4720                 /* fake 'cd  ..' */
4721                 line = &view->line[1];
4722                 break;
4724         case REQ_ENTER:
4725                 break;
4727         default:
4728                 return request;
4729         }
4731         /* Cleanup the stack if the tree view is at a different tree. */
4732         while (!*opt_path && tree_stack)
4733                 pop_tree_stack_entry();
4735         switch (line->type) {
4736         case LINE_TREE_DIR:
4737                 /* Depending on whether it is a subdirectory or parent link
4738                  * mangle the path buffer. */
4739                 if (line == &view->line[1] && *opt_path) {
4740                         pop_tree_stack_entry();
4742                 } else {
4743                         const char *basename = tree_path(line);
4745                         push_tree_stack_entry(basename, view->lineno);
4746                 }
4748                 /* Trees and subtrees share the same ID, so they are not not
4749                  * unique like blobs. */
4750                 flags = OPEN_RELOAD;
4751                 request = REQ_VIEW_TREE;
4752                 break;
4754         case LINE_TREE_FILE:
4755                 flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
4756                 request = REQ_VIEW_BLOB;
4757                 break;
4759         default:
4760                 return REQ_NONE;
4761         }
4763         open_view(view, request, flags);
4764         if (request == REQ_VIEW_TREE)
4765                 view->lineno = tree_lineno;
4767         return REQ_NONE;
4770 static bool
4771 tree_grep(struct view *view, struct line *line)
4773         struct tree_entry *entry = line->data;
4774         const char *text[] = {
4775                 entry->name,
4776                 opt_author ? entry->author : "",
4777                 mkdate(&entry->time, opt_date),
4778                 NULL
4779         };
4781         return grep_text(view, text);
4784 static void
4785 tree_select(struct view *view, struct line *line)
4787         struct tree_entry *entry = line->data;
4789         if (line->type == LINE_TREE_FILE) {
4790                 string_copy_rev(ref_blob, entry->id);
4791                 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4793         } else if (line->type != LINE_TREE_DIR) {
4794                 return;
4795         }
4797         string_copy_rev(view->ref, entry->id);
4800 static bool
4801 tree_prepare(struct view *view)
4803         if (view->lines == 0 && opt_prefix[0]) {
4804                 char *pos = opt_prefix;
4806                 while (pos && *pos) {
4807                         char *end = strchr(pos, '/');
4809                         if (end)
4810                                 *end = 0;
4811                         push_tree_stack_entry(pos, 0);
4812                         pos = end;
4813                         if (end) {
4814                                 *end = '/';
4815                                 pos++;
4816                         }
4817                 }
4819         } else if (strcmp(view->vid, view->id)) {
4820                 opt_path[0] = 0;
4821         }
4823         return prepare_io(view, opt_cdup, view->ops->argv, TRUE);
4826 static const char *tree_argv[SIZEOF_ARG] = {
4827         "git", "ls-tree", "%(commit)", "%(directory)", NULL
4828 };
4830 static struct view_ops tree_ops = {
4831         "file",
4832         tree_argv,
4833         NULL,
4834         tree_read,
4835         tree_draw,
4836         tree_request,
4837         tree_grep,
4838         tree_select,
4839         tree_prepare,
4840 };
4842 static bool
4843 blob_read(struct view *view, char *line)
4845         if (!line)
4846                 return TRUE;
4847         return add_line_text(view, line, LINE_DEFAULT) != NULL;
4850 static enum request
4851 blob_request(struct view *view, enum request request, struct line *line)
4853         switch (request) {
4854         case REQ_EDIT:
4855                 open_blob_editor(view->vid);
4856                 return REQ_NONE;
4857         default:
4858                 return pager_request(view, request, line);
4859         }
4862 static const char *blob_argv[SIZEOF_ARG] = {
4863         "git", "cat-file", "blob", "%(blob)", NULL
4864 };
4866 static struct view_ops blob_ops = {
4867         "line",
4868         blob_argv,
4869         NULL,
4870         blob_read,
4871         pager_draw,
4872         blob_request,
4873         pager_grep,
4874         pager_select,
4875 };
4877 /*
4878  * Blame backend
4879  *
4880  * Loading the blame view is a two phase job:
4881  *
4882  *  1. File content is read either using opt_file from the
4883  *     filesystem or using git-cat-file.
4884  *  2. Then blame information is incrementally added by
4885  *     reading output from git-blame.
4886  */
4888 struct blame_commit {
4889         char id[SIZEOF_REV];            /* SHA1 ID. */
4890         char title[128];                /* First line of the commit message. */
4891         const char *author;             /* Author of the commit. */
4892         struct time time;               /* Date from the author ident. */
4893         char filename[128];             /* Name of file. */
4894         char parent_id[SIZEOF_REV];     /* Parent/previous SHA1 ID. */
4895         char parent_filename[128];      /* Parent/previous name of file. */
4896 };
4898 struct blame {
4899         struct blame_commit *commit;
4900         unsigned long lineno;
4901         char text[1];
4902 };
4904 static bool
4905 blame_open(struct view *view)
4907         char path[SIZEOF_STR];
4908         size_t i;
4910         if (!view->prev && *opt_prefix) {
4911                 string_copy(path, opt_file);
4912                 if (!string_format(opt_file, "%s%s", opt_prefix, path))
4913                         return FALSE;
4914         }
4916         if (*opt_ref || !io_open(&view->io, "%s%s", opt_cdup, opt_file)) {
4917                 const char *blame_cat_file_argv[] = {
4918                         "git", "cat-file", "blob", path, NULL
4919                 };
4921                 if (!string_format(path, "%s:%s", opt_ref, opt_file) ||
4922                     !start_update(view, blame_cat_file_argv, opt_cdup))
4923                         return FALSE;
4924         }
4926         /* First pass: remove multiple references to the same commit. */
4927         for (i = 0; i < view->lines; i++) {
4928                 struct blame *blame = view->line[i].data;
4930                 if (blame->commit && blame->commit->id[0])
4931                         blame->commit->id[0] = 0;
4932                 else
4933                         blame->commit = NULL;
4934         }
4936         /* Second pass: free existing references. */
4937         for (i = 0; i < view->lines; i++) {
4938                 struct blame *blame = view->line[i].data;
4940                 if (blame->commit)
4941                         free(blame->commit);
4942         }
4944         setup_update(view, opt_file);
4945         string_format(view->ref, "%s ...", opt_file);
4947         return TRUE;
4950 static struct blame_commit *
4951 get_blame_commit(struct view *view, const char *id)
4953         size_t i;
4955         for (i = 0; i < view->lines; i++) {
4956                 struct blame *blame = view->line[i].data;
4958                 if (!blame->commit)
4959                         continue;
4961                 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4962                         return blame->commit;
4963         }
4965         {
4966                 struct blame_commit *commit = calloc(1, sizeof(*commit));
4968                 if (commit)
4969                         string_ncopy(commit->id, id, SIZEOF_REV);
4970                 return commit;
4971         }
4974 static bool
4975 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4977         const char *pos = *posref;
4979         *posref = NULL;
4980         pos = strchr(pos + 1, ' ');
4981         if (!pos || !isdigit(pos[1]))
4982                 return FALSE;
4983         *number = atoi(pos + 1);
4984         if (*number < min || *number > max)
4985                 return FALSE;
4987         *posref = pos;
4988         return TRUE;
4991 static struct blame_commit *
4992 parse_blame_commit(struct view *view, const char *text, int *blamed)
4994         struct blame_commit *commit;
4995         struct blame *blame;
4996         const char *pos = text + SIZEOF_REV - 2;
4997         size_t orig_lineno = 0;
4998         size_t lineno;
4999         size_t group;
5001         if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
5002                 return NULL;
5004         if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
5005             !parse_number(&pos, &lineno, 1, view->lines) ||
5006             !parse_number(&pos, &group, 1, view->lines - lineno + 1))
5007                 return NULL;
5009         commit = get_blame_commit(view, text);
5010         if (!commit)
5011                 return NULL;
5013         *blamed += group;
5014         while (group--) {
5015                 struct line *line = &view->line[lineno + group - 1];
5017                 blame = line->data;
5018                 blame->commit = commit;
5019                 blame->lineno = orig_lineno + group - 1;
5020                 line->dirty = 1;
5021         }
5023         return commit;
5026 static bool
5027 blame_read_file(struct view *view, const char *line, bool *read_file)
5029         if (!line) {
5030                 const char *blame_argv[] = {
5031                         "git", "blame", "--incremental",
5032                                 *opt_ref ? opt_ref : "--incremental", "--", opt_file, NULL
5033                 };
5035                 if (view->lines == 0 && !view->prev)
5036                         die("No blame exist for %s", view->vid);
5038                 if (view->lines == 0 || !start_update(view, blame_argv, opt_cdup)) {
5039                         report("Failed to load blame data");
5040                         return TRUE;
5041                 }
5043                 *read_file = FALSE;
5044                 return FALSE;
5046         } else {
5047                 size_t linelen = strlen(line);
5048                 struct blame *blame = malloc(sizeof(*blame) + linelen);
5050                 if (!blame)
5051                         return FALSE;
5053                 blame->commit = NULL;
5054                 strncpy(blame->text, line, linelen);
5055                 blame->text[linelen] = 0;
5056                 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
5057         }
5060 static bool
5061 match_blame_header(const char *name, char **line)
5063         size_t namelen = strlen(name);
5064         bool matched = !strncmp(name, *line, namelen);
5066         if (matched)
5067                 *line += namelen;
5069         return matched;
5072 static bool
5073 blame_read(struct view *view, char *line)
5075         static struct blame_commit *commit = NULL;
5076         static int blamed = 0;
5077         static bool read_file = TRUE;
5079         if (read_file)
5080                 return blame_read_file(view, line, &read_file);
5082         if (!line) {
5083                 /* Reset all! */
5084                 commit = NULL;
5085                 blamed = 0;
5086                 read_file = TRUE;
5087                 string_format(view->ref, "%s", view->vid);
5088                 if (view_is_displayed(view)) {
5089                         update_view_title(view);
5090                         redraw_view_from(view, 0);
5091                 }
5092                 return TRUE;
5093         }
5095         if (!commit) {
5096                 commit = parse_blame_commit(view, line, &blamed);
5097                 string_format(view->ref, "%s %2d%%", view->vid,
5098                               view->lines ? blamed * 100 / view->lines : 0);
5100         } else if (match_blame_header("author ", &line)) {
5101                 commit->author = get_author(line);
5103         } else if (match_blame_header("author-time ", &line)) {
5104                 parse_timesec(&commit->time, line);
5106         } else if (match_blame_header("author-tz ", &line)) {
5107                 parse_timezone(&commit->time, line);
5109         } else if (match_blame_header("summary ", &line)) {
5110                 string_ncopy(commit->title, line, strlen(line));
5112         } else if (match_blame_header("previous ", &line)) {
5113                 if (strlen(line) <= SIZEOF_REV)
5114                         return FALSE;
5115                 string_copy_rev(commit->parent_id, line);
5116                 line += SIZEOF_REV;
5117                 string_ncopy(commit->parent_filename, line, strlen(line));
5119         } else if (match_blame_header("filename ", &line)) {
5120                 string_ncopy(commit->filename, line, strlen(line));
5121                 commit = NULL;
5122         }
5124         return TRUE;
5127 static bool
5128 blame_draw(struct view *view, struct line *line, unsigned int lineno)
5130         struct blame *blame = line->data;
5131         struct time *time = NULL;
5132         const char *id = NULL, *author = NULL;
5134         if (blame->commit && *blame->commit->filename) {
5135                 id = blame->commit->id;
5136                 author = blame->commit->author;
5137                 time = &blame->commit->time;
5138         }
5140         if (opt_date && draw_date(view, time))
5141                 return TRUE;
5143         if (opt_author && draw_author(view, author))
5144                 return TRUE;
5146         if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
5147                 return TRUE;
5149         if (draw_lineno(view, lineno))
5150                 return TRUE;
5152         draw_text(view, LINE_DEFAULT, blame->text, TRUE);
5153         return TRUE;
5156 static bool
5157 check_blame_commit(struct blame *blame, bool check_null_id)
5159         if (!blame->commit)
5160                 report("Commit data not loaded yet");
5161         else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
5162                 report("No commit exist for the selected line");
5163         else
5164                 return TRUE;
5165         return FALSE;
5168 static void
5169 setup_blame_parent_line(struct view *view, struct blame *blame)
5171         char from[SIZEOF_REF + SIZEOF_STR];
5172         char to[SIZEOF_REF + SIZEOF_STR];
5173         const char *diff_tree_argv[] = {
5174                 "git", "diff", "--no-textconv", "--no-extdiff", "--no-color",
5175                         "-U0", from, to, "--", NULL
5176         };
5177         struct io io;
5178         int parent_lineno = -1;
5179         int blamed_lineno = -1;
5180         char *line;
5182         if (!string_format(from, "%s:%s", opt_ref, opt_file) ||
5183             !string_format(to, "%s:%s", blame->commit->id, blame->commit->filename) ||
5184             !io_run(&io, IO_RD, NULL, diff_tree_argv))
5185                 return;
5187         while ((line = io_get(&io, '\n', TRUE))) {
5188                 if (*line == '@') {
5189                         char *pos = strchr(line, '+');
5191                         parent_lineno = atoi(line + 4);
5192                         if (pos)
5193                                 blamed_lineno = atoi(pos + 1);
5195                 } else if (*line == '+' && parent_lineno != -1) {
5196                         if (blame->lineno == blamed_lineno - 1 &&
5197                             !strcmp(blame->text, line + 1)) {
5198                                 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
5199                                 break;
5200                         }
5201                         blamed_lineno++;
5202                 }
5203         }
5205         io_done(&io);
5208 static enum request
5209 blame_request(struct view *view, enum request request, struct line *line)
5211         enum open_flags flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
5212         struct blame *blame = line->data;
5214         switch (request) {
5215         case REQ_VIEW_BLAME:
5216                 if (check_blame_commit(blame, TRUE)) {
5217                         string_copy(opt_ref, blame->commit->id);
5218                         string_copy(opt_file, blame->commit->filename);
5219                         if (blame->lineno)
5220                                 view->lineno = blame->lineno;
5221                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5222                 }
5223                 break;
5225         case REQ_PARENT:
5226                 if (!check_blame_commit(blame, TRUE))
5227                         break;
5228                 if (!*blame->commit->parent_id) {
5229                         report("The selected commit has no parents");
5230                 } else {
5231                         string_copy_rev(opt_ref, blame->commit->parent_id);
5232                         string_copy(opt_file, blame->commit->parent_filename);
5233                         setup_blame_parent_line(view, blame);
5234                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5235                 }
5236                 break;
5238         case REQ_ENTER:
5239                 if (!check_blame_commit(blame, FALSE))
5240                         break;
5242                 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
5243                     !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
5244                         break;
5246                 if (!strcmp(blame->commit->id, NULL_ID)) {
5247                         struct view *diff = VIEW(REQ_VIEW_DIFF);
5248                         const char *diff_index_argv[] = {
5249                                 "git", "diff-index", "--root", "--patch-with-stat",
5250                                         "-C", "-M", "HEAD", "--", view->vid, NULL
5251                         };
5253                         if (!*blame->commit->parent_id) {
5254                                 diff_index_argv[1] = "diff";
5255                                 diff_index_argv[2] = "--no-color";
5256                                 diff_index_argv[6] = "--";
5257                                 diff_index_argv[7] = "/dev/null";
5258                         }
5260                         if (!prepare_update(diff, diff_index_argv, NULL)) {
5261                                 report("Failed to allocate diff command");
5262                                 break;
5263                         }
5264                         flags |= OPEN_PREPARED;
5265                 }
5267                 open_view(view, REQ_VIEW_DIFF, flags);
5268                 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
5269                         string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
5270                 break;
5272         default:
5273                 return request;
5274         }
5276         return REQ_NONE;
5279 static bool
5280 blame_grep(struct view *view, struct line *line)
5282         struct blame *blame = line->data;
5283         struct blame_commit *commit = blame->commit;
5284         const char *text[] = {
5285                 blame->text,
5286                 commit ? commit->title : "",
5287                 commit ? commit->id : "",
5288                 commit && opt_author ? commit->author : "",
5289                 commit ? mkdate(&commit->time, opt_date) : "",
5290                 NULL
5291         };
5293         return grep_text(view, text);
5296 static void
5297 blame_select(struct view *view, struct line *line)
5299         struct blame *blame = line->data;
5300         struct blame_commit *commit = blame->commit;
5302         if (!commit)
5303                 return;
5305         if (!strcmp(commit->id, NULL_ID))
5306                 string_ncopy(ref_commit, "HEAD", 4);
5307         else
5308                 string_copy_rev(ref_commit, commit->id);
5311 static struct view_ops blame_ops = {
5312         "line",
5313         NULL,
5314         blame_open,
5315         blame_read,
5316         blame_draw,
5317         blame_request,
5318         blame_grep,
5319         blame_select,
5320 };
5322 /*
5323  * Branch backend
5324  */
5326 struct branch {
5327         const char *author;             /* Author of the last commit. */
5328         struct time time;               /* Date of the last activity. */
5329         const struct ref *ref;          /* Name and commit ID information. */
5330 };
5332 static const struct ref branch_all;
5334 static const enum sort_field branch_sort_fields[] = {
5335         ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
5336 };
5337 static struct sort_state branch_sort_state = SORT_STATE(branch_sort_fields);
5339 static int
5340 branch_compare(const void *l1, const void *l2)
5342         const struct branch *branch1 = ((const struct line *) l1)->data;
5343         const struct branch *branch2 = ((const struct line *) l2)->data;
5345         switch (get_sort_field(branch_sort_state)) {
5346         case ORDERBY_DATE:
5347                 return sort_order(branch_sort_state, timecmp(&branch1->time, &branch2->time));
5349         case ORDERBY_AUTHOR:
5350                 return sort_order(branch_sort_state, strcmp(branch1->author, branch2->author));
5352         case ORDERBY_NAME:
5353         default:
5354                 return sort_order(branch_sort_state, strcmp(branch1->ref->name, branch2->ref->name));
5355         }
5358 static bool
5359 branch_draw(struct view *view, struct line *line, unsigned int lineno)
5361         struct branch *branch = line->data;
5362         enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
5364         if (opt_date && draw_date(view, &branch->time))
5365                 return TRUE;
5367         if (opt_author && draw_author(view, branch->author))
5368                 return TRUE;
5370         draw_text(view, type, branch->ref == &branch_all ? "All branches" : branch->ref->name, TRUE);
5371         return TRUE;
5374 static enum request
5375 branch_request(struct view *view, enum request request, struct line *line)
5377         struct branch *branch = line->data;
5379         switch (request) {
5380         case REQ_REFRESH:
5381                 load_refs();
5382                 open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
5383                 return REQ_NONE;
5385         case REQ_TOGGLE_SORT_FIELD:
5386         case REQ_TOGGLE_SORT_ORDER:
5387                 sort_view(view, request, &branch_sort_state, branch_compare);
5388                 return REQ_NONE;
5390         case REQ_ENTER:
5391         {
5392                 const struct ref *ref = branch->ref;
5393                 const char *all_branches_argv[] = {
5394                         "git", "log", "--no-color", "--pretty=raw", "--parents",
5395                               "--topo-order",
5396                               ref == &branch_all ? "--all" : ref->name, NULL
5397                 };
5398                 struct view *main_view = VIEW(REQ_VIEW_MAIN);
5400                 if (!prepare_update(main_view, all_branches_argv, NULL))
5401                         report("Failed to load view of all branches");
5402                 else
5403                         open_view(view, REQ_VIEW_MAIN, OPEN_PREPARED | OPEN_SPLIT);
5404                 return REQ_NONE;
5405         }
5406         default:
5407                 return request;
5408         }
5411 static bool
5412 branch_read(struct view *view, char *line)
5414         static char id[SIZEOF_REV];
5415         struct branch *reference;
5416         size_t i;
5418         if (!line)
5419                 return TRUE;
5421         switch (get_line_type(line)) {
5422         case LINE_COMMIT:
5423                 string_copy_rev(id, line + STRING_SIZE("commit "));
5424                 return TRUE;
5426         case LINE_AUTHOR:
5427                 for (i = 0, reference = NULL; i < view->lines; i++) {
5428                         struct branch *branch = view->line[i].data;
5430                         if (strcmp(branch->ref->id, id))
5431                                 continue;
5433                         view->line[i].dirty = TRUE;
5434                         if (reference) {
5435                                 branch->author = reference->author;
5436                                 branch->time = reference->time;
5437                                 continue;
5438                         }
5440                         parse_author_line(line + STRING_SIZE("author "),
5441                                           &branch->author, &branch->time);
5442                         reference = branch;
5443                 }
5444                 return TRUE;
5446         default:
5447                 return TRUE;
5448         }
5452 static bool
5453 branch_open_visitor(void *data, const struct ref *ref)
5455         struct view *view = data;
5456         struct branch *branch;
5458         if (ref->tag || ref->ltag || ref->remote)
5459                 return TRUE;
5461         branch = calloc(1, sizeof(*branch));
5462         if (!branch)
5463                 return FALSE;
5465         branch->ref = ref;
5466         return !!add_line_data(view, branch, LINE_DEFAULT);
5469 static bool
5470 branch_open(struct view *view)
5472         const char *branch_log[] = {
5473                 "git", "log", "--no-color", "--pretty=raw",
5474                         "--simplify-by-decoration", "--all", NULL
5475         };
5477         if (!start_update(view, branch_log, NULL)) {
5478                 report("Failed to load branch data");
5479                 return TRUE;
5480         }
5482         setup_update(view, view->id);
5483         branch_open_visitor(view, &branch_all);
5484         foreach_ref(branch_open_visitor, view);
5485         view->p_restore = TRUE;
5487         return TRUE;
5490 static bool
5491 branch_grep(struct view *view, struct line *line)
5493         struct branch *branch = line->data;
5494         const char *text[] = {
5495                 branch->ref->name,
5496                 branch->author,
5497                 NULL
5498         };
5500         return grep_text(view, text);
5503 static void
5504 branch_select(struct view *view, struct line *line)
5506         struct branch *branch = line->data;
5508         string_copy_rev(view->ref, branch->ref->id);
5509         string_copy_rev(ref_commit, branch->ref->id);
5510         string_copy_rev(ref_head, branch->ref->id);
5511         string_copy_rev(ref_branch, branch->ref->name);
5514 static struct view_ops branch_ops = {
5515         "branch",
5516         NULL,
5517         branch_open,
5518         branch_read,
5519         branch_draw,
5520         branch_request,
5521         branch_grep,
5522         branch_select,
5523 };
5525 /*
5526  * Status backend
5527  */
5529 struct status {
5530         char status;
5531         struct {
5532                 mode_t mode;
5533                 char rev[SIZEOF_REV];
5534                 char name[SIZEOF_STR];
5535         } old;
5536         struct {
5537                 mode_t mode;
5538                 char rev[SIZEOF_REV];
5539                 char name[SIZEOF_STR];
5540         } new;
5541 };
5543 static char status_onbranch[SIZEOF_STR];
5544 static struct status stage_status;
5545 static enum line_type stage_line_type;
5546 static size_t stage_chunks;
5547 static int *stage_chunk;
5549 DEFINE_ALLOCATOR(realloc_ints, int, 32)
5551 /* This should work even for the "On branch" line. */
5552 static inline bool
5553 status_has_none(struct view *view, struct line *line)
5555         return line < view->line + view->lines && !line[1].data;
5558 /* Get fields from the diff line:
5559  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5560  */
5561 static inline bool
5562 status_get_diff(struct status *file, const char *buf, size_t bufsize)
5564         const char *old_mode = buf +  1;
5565         const char *new_mode = buf +  8;
5566         const char *old_rev  = buf + 15;
5567         const char *new_rev  = buf + 56;
5568         const char *status   = buf + 97;
5570         if (bufsize < 98 ||
5571             old_mode[-1] != ':' ||
5572             new_mode[-1] != ' ' ||
5573             old_rev[-1]  != ' ' ||
5574             new_rev[-1]  != ' ' ||
5575             status[-1]   != ' ')
5576                 return FALSE;
5578         file->status = *status;
5580         string_copy_rev(file->old.rev, old_rev);
5581         string_copy_rev(file->new.rev, new_rev);
5583         file->old.mode = strtoul(old_mode, NULL, 8);
5584         file->new.mode = strtoul(new_mode, NULL, 8);
5586         file->old.name[0] = file->new.name[0] = 0;
5588         return TRUE;
5591 static bool
5592 status_run(struct view *view, const char *argv[], char status, enum line_type type)
5594         struct status *unmerged = NULL;
5595         char *buf;
5596         struct io io;
5598         if (!io_run(&io, IO_RD, opt_cdup, argv))
5599                 return FALSE;
5601         add_line_data(view, NULL, type);
5603         while ((buf = io_get(&io, 0, TRUE))) {
5604                 struct status *file = unmerged;
5606                 if (!file) {
5607                         file = calloc(1, sizeof(*file));
5608                         if (!file || !add_line_data(view, file, type))
5609                                 goto error_out;
5610                 }
5612                 /* Parse diff info part. */
5613                 if (status) {
5614                         file->status = status;
5615                         if (status == 'A')
5616                                 string_copy(file->old.rev, NULL_ID);
5618                 } else if (!file->status || file == unmerged) {
5619                         if (!status_get_diff(file, buf, strlen(buf)))
5620                                 goto error_out;
5622                         buf = io_get(&io, 0, TRUE);
5623                         if (!buf)
5624                                 break;
5626                         /* Collapse all modified entries that follow an
5627                          * associated unmerged entry. */
5628                         if (unmerged == file) {
5629                                 unmerged->status = 'U';
5630                                 unmerged = NULL;
5631                         } else if (file->status == 'U') {
5632                                 unmerged = file;
5633                         }
5634                 }
5636                 /* Grab the old name for rename/copy. */
5637                 if (!*file->old.name &&
5638                     (file->status == 'R' || file->status == 'C')) {
5639                         string_ncopy(file->old.name, buf, strlen(buf));
5641                         buf = io_get(&io, 0, TRUE);
5642                         if (!buf)
5643                                 break;
5644                 }
5646                 /* git-ls-files just delivers a NUL separated list of
5647                  * file names similar to the second half of the
5648                  * git-diff-* output. */
5649                 string_ncopy(file->new.name, buf, strlen(buf));
5650                 if (!*file->old.name)
5651                         string_copy(file->old.name, file->new.name);
5652                 file = NULL;
5653         }
5655         if (io_error(&io)) {
5656 error_out:
5657                 io_done(&io);
5658                 return FALSE;
5659         }
5661         if (!view->line[view->lines - 1].data)
5662                 add_line_data(view, NULL, LINE_STAT_NONE);
5664         io_done(&io);
5665         return TRUE;
5668 /* Don't show unmerged entries in the staged section. */
5669 static const char *status_diff_index_argv[] = {
5670         "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5671                              "--cached", "-M", "HEAD", NULL
5672 };
5674 static const char *status_diff_files_argv[] = {
5675         "git", "diff-files", "-z", NULL
5676 };
5678 static const char *status_list_other_argv[] = {
5679         "git", "ls-files", "-z", "--others", "--exclude-standard", opt_prefix, NULL
5680 };
5682 static const char *status_list_no_head_argv[] = {
5683         "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5684 };
5686 static const char *update_index_argv[] = {
5687         "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5688 };
5690 /* Restore the previous line number to stay in the context or select a
5691  * line with something that can be updated. */
5692 static void
5693 status_restore(struct view *view)
5695         if (view->p_lineno >= view->lines)
5696                 view->p_lineno = view->lines - 1;
5697         while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
5698                 view->p_lineno++;
5699         while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
5700                 view->p_lineno--;
5702         /* If the above fails, always skip the "On branch" line. */
5703         if (view->p_lineno < view->lines)
5704                 view->lineno = view->p_lineno;
5705         else
5706                 view->lineno = 1;
5708         if (view->lineno < view->offset)
5709                 view->offset = view->lineno;
5710         else if (view->offset + view->height <= view->lineno)
5711                 view->offset = view->lineno - view->height + 1;
5713         view->p_restore = FALSE;
5716 static void
5717 status_update_onbranch(void)
5719         static const char *paths[][2] = {
5720                 { "rebase-apply/rebasing",      "Rebasing" },
5721                 { "rebase-apply/applying",      "Applying mailbox" },
5722                 { "rebase-apply/",              "Rebasing mailbox" },
5723                 { "rebase-merge/interactive",   "Interactive rebase" },
5724                 { "rebase-merge/",              "Rebase merge" },
5725                 { "MERGE_HEAD",                 "Merging" },
5726                 { "BISECT_LOG",                 "Bisecting" },
5727                 { "HEAD",                       "On branch" },
5728         };
5729         char buf[SIZEOF_STR];
5730         struct stat stat;
5731         int i;
5733         if (is_initial_commit()) {
5734                 string_copy(status_onbranch, "Initial commit");
5735                 return;
5736         }
5738         for (i = 0; i < ARRAY_SIZE(paths); i++) {
5739                 char *head = opt_head;
5741                 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5742                     lstat(buf, &stat) < 0)
5743                         continue;
5745                 if (!*opt_head) {
5746                         struct io io;
5748                         if (io_open(&io, "%s/rebase-merge/head-name", opt_git_dir) &&
5749                             io_read_buf(&io, buf, sizeof(buf))) {
5750                                 head = buf;
5751                                 if (!prefixcmp(head, "refs/heads/"))
5752                                         head += STRING_SIZE("refs/heads/");
5753                         }
5754                 }
5756                 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5757                         string_copy(status_onbranch, opt_head);
5758                 return;
5759         }
5761         string_copy(status_onbranch, "Not currently on any branch");
5764 /* First parse staged info using git-diff-index(1), then parse unstaged
5765  * info using git-diff-files(1), and finally untracked files using
5766  * git-ls-files(1). */
5767 static bool
5768 status_open(struct view *view)
5770         reset_view(view);
5772         add_line_data(view, NULL, LINE_STAT_HEAD);
5773         status_update_onbranch();
5775         io_run_bg(update_index_argv);
5777         if (is_initial_commit()) {
5778                 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5779                         return FALSE;
5780         } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5781                 return FALSE;
5782         }
5784         if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5785             !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5786                 return FALSE;
5788         /* Restore the exact position or use the specialized restore
5789          * mode? */
5790         if (!view->p_restore)
5791                 status_restore(view);
5792         return TRUE;
5795 static bool
5796 status_draw(struct view *view, struct line *line, unsigned int lineno)
5798         struct status *status = line->data;
5799         enum line_type type;
5800         const char *text;
5802         if (!status) {
5803                 switch (line->type) {
5804                 case LINE_STAT_STAGED:
5805                         type = LINE_STAT_SECTION;
5806                         text = "Changes to be committed:";
5807                         break;
5809                 case LINE_STAT_UNSTAGED:
5810                         type = LINE_STAT_SECTION;
5811                         text = "Changed but not updated:";
5812                         break;
5814                 case LINE_STAT_UNTRACKED:
5815                         type = LINE_STAT_SECTION;
5816                         text = "Untracked files:";
5817                         break;
5819                 case LINE_STAT_NONE:
5820                         type = LINE_DEFAULT;
5821                         text = "  (no files)";
5822                         break;
5824                 case LINE_STAT_HEAD:
5825                         type = LINE_STAT_HEAD;
5826                         text = status_onbranch;
5827                         break;
5829                 default:
5830                         return FALSE;
5831                 }
5832         } else {
5833                 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5835                 buf[0] = status->status;
5836                 if (draw_text(view, line->type, buf, TRUE))
5837                         return TRUE;
5838                 type = LINE_DEFAULT;
5839                 text = status->new.name;
5840         }
5842         draw_text(view, type, text, TRUE);
5843         return TRUE;
5846 static enum request
5847 status_load_error(struct view *view, struct view *stage, const char *path)
5849         if (displayed_views() == 2 || display[current_view] != view)
5850                 maximize_view(view);
5851         report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5852         return REQ_NONE;
5855 static enum request
5856 status_enter(struct view *view, struct line *line)
5858         struct status *status = line->data;
5859         const char *oldpath = status ? status->old.name : NULL;
5860         /* Diffs for unmerged entries are empty when passing the new
5861          * path, so leave it empty. */
5862         const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5863         const char *info;
5864         enum open_flags split;
5865         struct view *stage = VIEW(REQ_VIEW_STAGE);
5867         if (line->type == LINE_STAT_NONE ||
5868             (!status && line[1].type == LINE_STAT_NONE)) {
5869                 report("No file to diff");
5870                 return REQ_NONE;
5871         }
5873         switch (line->type) {
5874         case LINE_STAT_STAGED:
5875                 if (is_initial_commit()) {
5876                         const char *no_head_diff_argv[] = {
5877                                 "git", "diff", "--no-color", "--patch-with-stat",
5878                                         "--", "/dev/null", newpath, NULL
5879                         };
5881                         if (!prepare_update(stage, no_head_diff_argv, opt_cdup))
5882                                 return status_load_error(view, stage, newpath);
5883                 } else {
5884                         const char *index_show_argv[] = {
5885                                 "git", "diff-index", "--root", "--patch-with-stat",
5886                                         "-C", "-M", "--cached", "HEAD", "--",
5887                                         oldpath, newpath, NULL
5888                         };
5890                         if (!prepare_update(stage, index_show_argv, opt_cdup))
5891                                 return status_load_error(view, stage, newpath);
5892                 }
5894                 if (status)
5895                         info = "Staged changes to %s";
5896                 else
5897                         info = "Staged changes";
5898                 break;
5900         case LINE_STAT_UNSTAGED:
5901         {
5902                 const char *files_show_argv[] = {
5903                         "git", "diff-files", "--root", "--patch-with-stat",
5904                                 "-C", "-M", "--", oldpath, newpath, NULL
5905                 };
5907                 if (!prepare_update(stage, files_show_argv, opt_cdup))
5908                         return status_load_error(view, stage, newpath);
5909                 if (status)
5910                         info = "Unstaged changes to %s";
5911                 else
5912                         info = "Unstaged changes";
5913                 break;
5914         }
5915         case LINE_STAT_UNTRACKED:
5916                 if (!newpath) {
5917                         report("No file to show");
5918                         return REQ_NONE;
5919                 }
5921                 if (!suffixcmp(status->new.name, -1, "/")) {
5922                         report("Cannot display a directory");
5923                         return REQ_NONE;
5924                 }
5926                 if (!prepare_update_file(stage, newpath))
5927                         return status_load_error(view, stage, newpath);
5928                 info = "Untracked file %s";
5929                 break;
5931         case LINE_STAT_HEAD:
5932                 return REQ_NONE;
5934         default:
5935                 die("line type %d not handled in switch", line->type);
5936         }
5938         split = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
5939         open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5940         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5941                 if (status) {
5942                         stage_status = *status;
5943                 } else {
5944                         memset(&stage_status, 0, sizeof(stage_status));
5945                 }
5947                 stage_line_type = line->type;
5948                 stage_chunks = 0;
5949                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5950         }
5952         return REQ_NONE;
5955 static bool
5956 status_exists(struct status *status, enum line_type type)
5958         struct view *view = VIEW(REQ_VIEW_STATUS);
5959         unsigned long lineno;
5961         for (lineno = 0; lineno < view->lines; lineno++) {
5962                 struct line *line = &view->line[lineno];
5963                 struct status *pos = line->data;
5965                 if (line->type != type)
5966                         continue;
5967                 if (!pos && (!status || !status->status) && line[1].data) {
5968                         select_view_line(view, lineno);
5969                         return TRUE;
5970                 }
5971                 if (pos && !strcmp(status->new.name, pos->new.name)) {
5972                         select_view_line(view, lineno);
5973                         return TRUE;
5974                 }
5975         }
5977         return FALSE;
5981 static bool
5982 status_update_prepare(struct io *io, enum line_type type)
5984         const char *staged_argv[] = {
5985                 "git", "update-index", "-z", "--index-info", NULL
5986         };
5987         const char *others_argv[] = {
5988                 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5989         };
5991         switch (type) {
5992         case LINE_STAT_STAGED:
5993                 return io_run(io, IO_WR, opt_cdup, staged_argv);
5995         case LINE_STAT_UNSTAGED:
5996         case LINE_STAT_UNTRACKED:
5997                 return io_run(io, IO_WR, opt_cdup, others_argv);
5999         default:
6000                 die("line type %d not handled in switch", type);
6001                 return FALSE;
6002         }
6005 static bool
6006 status_update_write(struct io *io, struct status *status, enum line_type type)
6008         char buf[SIZEOF_STR];
6009         size_t bufsize = 0;
6011         switch (type) {
6012         case LINE_STAT_STAGED:
6013                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
6014                                         status->old.mode,
6015                                         status->old.rev,
6016                                         status->old.name, 0))
6017                         return FALSE;
6018                 break;
6020         case LINE_STAT_UNSTAGED:
6021         case LINE_STAT_UNTRACKED:
6022                 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
6023                         return FALSE;
6024                 break;
6026         default:
6027                 die("line type %d not handled in switch", type);
6028         }
6030         return io_write(io, buf, bufsize);
6033 static bool
6034 status_update_file(struct status *status, enum line_type type)
6036         struct io io;
6037         bool result;
6039         if (!status_update_prepare(&io, type))
6040                 return FALSE;
6042         result = status_update_write(&io, status, type);
6043         return io_done(&io) && result;
6046 static bool
6047 status_update_files(struct view *view, struct line *line)
6049         char buf[sizeof(view->ref)];
6050         struct io io;
6051         bool result = TRUE;
6052         struct line *pos = view->line + view->lines;
6053         int files = 0;
6054         int file, done;
6055         int cursor_y = -1, cursor_x = -1;
6057         if (!status_update_prepare(&io, line->type))
6058                 return FALSE;
6060         for (pos = line; pos < view->line + view->lines && pos->data; pos++)
6061                 files++;
6063         string_copy(buf, view->ref);
6064         getsyx(cursor_y, cursor_x);
6065         for (file = 0, done = 5; result && file < files; line++, file++) {
6066                 int almost_done = file * 100 / files;
6068                 if (almost_done > done) {
6069                         done = almost_done;
6070                         string_format(view->ref, "updating file %u of %u (%d%% done)",
6071                                       file, files, done);
6072                         update_view_title(view);
6073                         setsyx(cursor_y, cursor_x);
6074                         doupdate();
6075                 }
6076                 result = status_update_write(&io, line->data, line->type);
6077         }
6078         string_copy(view->ref, buf);
6080         return io_done(&io) && result;
6083 static bool
6084 status_update(struct view *view)
6086         struct line *line = &view->line[view->lineno];
6088         assert(view->lines);
6090         if (!line->data) {
6091                 /* This should work even for the "On branch" line. */
6092                 if (line < view->line + view->lines && !line[1].data) {
6093                         report("Nothing to update");
6094                         return FALSE;
6095                 }
6097                 if (!status_update_files(view, line + 1)) {
6098                         report("Failed to update file status");
6099                         return FALSE;
6100                 }
6102         } else if (!status_update_file(line->data, line->type)) {
6103                 report("Failed to update file status");
6104                 return FALSE;
6105         }
6107         return TRUE;
6110 static bool
6111 status_revert(struct status *status, enum line_type type, bool has_none)
6113         if (!status || type != LINE_STAT_UNSTAGED) {
6114                 if (type == LINE_STAT_STAGED) {
6115                         report("Cannot revert changes to staged files");
6116                 } else if (type == LINE_STAT_UNTRACKED) {
6117                         report("Cannot revert changes to untracked files");
6118                 } else if (has_none) {
6119                         report("Nothing to revert");
6120                 } else {
6121                         report("Cannot revert changes to multiple files");
6122                 }
6124         } else if (prompt_yesno("Are you sure you want to revert changes?")) {
6125                 char mode[10] = "100644";
6126                 const char *reset_argv[] = {
6127                         "git", "update-index", "--cacheinfo", mode,
6128                                 status->old.rev, status->old.name, NULL
6129                 };
6130                 const char *checkout_argv[] = {
6131                         "git", "checkout", "--", status->old.name, NULL
6132                 };
6134                 if (status->status == 'U') {
6135                         string_format(mode, "%5o", status->old.mode);
6137                         if (status->old.mode == 0 && status->new.mode == 0) {
6138                                 reset_argv[2] = "--force-remove";
6139                                 reset_argv[3] = status->old.name;
6140                                 reset_argv[4] = NULL;
6141                         }
6143                         if (!io_run_fg(reset_argv, opt_cdup))
6144                                 return FALSE;
6145                         if (status->old.mode == 0 && status->new.mode == 0)
6146                                 return TRUE;
6147                 }
6149                 return io_run_fg(checkout_argv, opt_cdup);
6150         }
6152         return FALSE;
6155 static enum request
6156 status_request(struct view *view, enum request request, struct line *line)
6158         struct status *status = line->data;
6160         switch (request) {
6161         case REQ_STATUS_UPDATE:
6162                 if (!status_update(view))
6163                         return REQ_NONE;
6164                 break;
6166         case REQ_STATUS_REVERT:
6167                 if (!status_revert(status, line->type, status_has_none(view, line)))
6168                         return REQ_NONE;
6169                 break;
6171         case REQ_STATUS_MERGE:
6172                 if (!status || status->status != 'U') {
6173                         report("Merging only possible for files with unmerged status ('U').");
6174                         return REQ_NONE;
6175                 }
6176                 open_mergetool(status->new.name);
6177                 break;
6179         case REQ_EDIT:
6180                 if (!status)
6181                         return request;
6182                 if (status->status == 'D') {
6183                         report("File has been deleted.");
6184                         return REQ_NONE;
6185                 }
6187                 open_editor(status->new.name);
6188                 break;
6190         case REQ_VIEW_BLAME:
6191                 if (status)
6192                         opt_ref[0] = 0;
6193                 return request;
6195         case REQ_ENTER:
6196                 /* After returning the status view has been split to
6197                  * show the stage view. No further reloading is
6198                  * necessary. */
6199                 return status_enter(view, line);
6201         case REQ_REFRESH:
6202                 /* Simply reload the view. */
6203                 break;
6205         default:
6206                 return request;
6207         }
6209         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
6211         return REQ_NONE;
6214 static void
6215 status_select(struct view *view, struct line *line)
6217         struct status *status = line->data;
6218         char file[SIZEOF_STR] = "all files";
6219         const char *text;
6220         const char *key;
6222         if (status && !string_format(file, "'%s'", status->new.name))
6223                 return;
6225         if (!status && line[1].type == LINE_STAT_NONE)
6226                 line++;
6228         switch (line->type) {
6229         case LINE_STAT_STAGED:
6230                 text = "Press %s to unstage %s for commit";
6231                 break;
6233         case LINE_STAT_UNSTAGED:
6234                 text = "Press %s to stage %s for commit";
6235                 break;
6237         case LINE_STAT_UNTRACKED:
6238                 text = "Press %s to stage %s for addition";
6239                 break;
6241         case LINE_STAT_HEAD:
6242         case LINE_STAT_NONE:
6243                 text = "Nothing to update";
6244                 break;
6246         default:
6247                 die("line type %d not handled in switch", line->type);
6248         }
6250         if (status && status->status == 'U') {
6251                 text = "Press %s to resolve conflict in %s";
6252                 key = get_key(KEYMAP_STATUS, REQ_STATUS_MERGE);
6254         } else {
6255                 key = get_key(KEYMAP_STATUS, REQ_STATUS_UPDATE);
6256         }
6258         string_format(view->ref, text, key, file);
6259         if (status)
6260                 string_copy(opt_file, status->new.name);
6263 static bool
6264 status_grep(struct view *view, struct line *line)
6266         struct status *status = line->data;
6268         if (status) {
6269                 const char buf[2] = { status->status, 0 };
6270                 const char *text[] = { status->new.name, buf, NULL };
6272                 return grep_text(view, text);
6273         }
6275         return FALSE;
6278 static struct view_ops status_ops = {
6279         "file",
6280         NULL,
6281         status_open,
6282         NULL,
6283         status_draw,
6284         status_request,
6285         status_grep,
6286         status_select,
6287 };
6290 static bool
6291 stage_diff_write(struct io *io, struct line *line, struct line *end)
6293         while (line < end) {
6294                 if (!io_write(io, line->data, strlen(line->data)) ||
6295                     !io_write(io, "\n", 1))
6296                         return FALSE;
6297                 line++;
6298                 if (line->type == LINE_DIFF_CHUNK ||
6299                     line->type == LINE_DIFF_HEADER)
6300                         break;
6301         }
6303         return TRUE;
6306 static struct line *
6307 stage_diff_find(struct view *view, struct line *line, enum line_type type)
6309         for (; view->line < line; line--)
6310                 if (line->type == type)
6311                         return line;
6313         return NULL;
6316 static bool
6317 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
6319         const char *apply_argv[SIZEOF_ARG] = {
6320                 "git", "apply", "--whitespace=nowarn", NULL
6321         };
6322         struct line *diff_hdr;
6323         struct io io;
6324         int argc = 3;
6326         diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
6327         if (!diff_hdr)
6328                 return FALSE;
6330         if (!revert)
6331                 apply_argv[argc++] = "--cached";
6332         if (revert || stage_line_type == LINE_STAT_STAGED)
6333                 apply_argv[argc++] = "-R";
6334         apply_argv[argc++] = "-";
6335         apply_argv[argc++] = NULL;
6336         if (!io_run(&io, IO_WR, opt_cdup, apply_argv))
6337                 return FALSE;
6339         if (!stage_diff_write(&io, diff_hdr, chunk) ||
6340             !stage_diff_write(&io, chunk, view->line + view->lines))
6341                 chunk = NULL;
6343         io_done(&io);
6344         io_run_bg(update_index_argv);
6346         return chunk ? TRUE : FALSE;
6349 static bool
6350 stage_update(struct view *view, struct line *line)
6352         struct line *chunk = NULL;
6354         if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
6355                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6357         if (chunk) {
6358                 if (!stage_apply_chunk(view, chunk, FALSE)) {
6359                         report("Failed to apply chunk");
6360                         return FALSE;
6361                 }
6363         } else if (!stage_status.status) {
6364                 view = VIEW(REQ_VIEW_STATUS);
6366                 for (line = view->line; line < view->line + view->lines; line++)
6367                         if (line->type == stage_line_type)
6368                                 break;
6370                 if (!status_update_files(view, line + 1)) {
6371                         report("Failed to update files");
6372                         return FALSE;
6373                 }
6375         } else if (!status_update_file(&stage_status, stage_line_type)) {
6376                 report("Failed to update file");
6377                 return FALSE;
6378         }
6380         return TRUE;
6383 static bool
6384 stage_revert(struct view *view, struct line *line)
6386         struct line *chunk = NULL;
6388         if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
6389                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6391         if (chunk) {
6392                 if (!prompt_yesno("Are you sure you want to revert changes?"))
6393                         return FALSE;
6395                 if (!stage_apply_chunk(view, chunk, TRUE)) {
6396                         report("Failed to revert chunk");
6397                         return FALSE;
6398                 }
6399                 return TRUE;
6401         } else {
6402                 return status_revert(stage_status.status ? &stage_status : NULL,
6403                                      stage_line_type, FALSE);
6404         }
6408 static void
6409 stage_next(struct view *view, struct line *line)
6411         int i;
6413         if (!stage_chunks) {
6414                 for (line = view->line; line < view->line + view->lines; line++) {
6415                         if (line->type != LINE_DIFF_CHUNK)
6416                                 continue;
6418                         if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
6419                                 report("Allocation failure");
6420                                 return;
6421                         }
6423                         stage_chunk[stage_chunks++] = line - view->line;
6424                 }
6425         }
6427         for (i = 0; i < stage_chunks; i++) {
6428                 if (stage_chunk[i] > view->lineno) {
6429                         do_scroll_view(view, stage_chunk[i] - view->lineno);
6430                         report("Chunk %d of %d", i + 1, stage_chunks);
6431                         return;
6432                 }
6433         }
6435         report("No next chunk found");
6438 static enum request
6439 stage_request(struct view *view, enum request request, struct line *line)
6441         switch (request) {
6442         case REQ_STATUS_UPDATE:
6443                 if (!stage_update(view, line))
6444                         return REQ_NONE;
6445                 break;
6447         case REQ_STATUS_REVERT:
6448                 if (!stage_revert(view, line))
6449                         return REQ_NONE;
6450                 break;
6452         case REQ_STAGE_NEXT:
6453                 if (stage_line_type == LINE_STAT_UNTRACKED) {
6454                         report("File is untracked; press %s to add",
6455                                get_key(KEYMAP_STAGE, REQ_STATUS_UPDATE));
6456                         return REQ_NONE;
6457                 }
6458                 stage_next(view, line);
6459                 return REQ_NONE;
6461         case REQ_EDIT:
6462                 if (!stage_status.new.name[0])
6463                         return request;
6464                 if (stage_status.status == 'D') {
6465                         report("File has been deleted.");
6466                         return REQ_NONE;
6467                 }
6469                 open_editor(stage_status.new.name);
6470                 break;
6472         case REQ_REFRESH:
6473                 /* Reload everything ... */
6474                 break;
6476         case REQ_VIEW_BLAME:
6477                 if (stage_status.new.name[0]) {
6478                         string_copy(opt_file, stage_status.new.name);
6479                         opt_ref[0] = 0;
6480                 }
6481                 return request;
6483         case REQ_ENTER:
6484                 return pager_request(view, request, line);
6486         default:
6487                 return request;
6488         }
6490         VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
6491         open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
6493         /* Check whether the staged entry still exists, and close the
6494          * stage view if it doesn't. */
6495         if (!status_exists(&stage_status, stage_line_type)) {
6496                 status_restore(VIEW(REQ_VIEW_STATUS));
6497                 return REQ_VIEW_CLOSE;
6498         }
6500         if (stage_line_type == LINE_STAT_UNTRACKED) {
6501                 if (!suffixcmp(stage_status.new.name, -1, "/")) {
6502                         report("Cannot display a directory");
6503                         return REQ_NONE;
6504                 }
6506                 if (!prepare_update_file(view, stage_status.new.name)) {
6507                         report("Failed to open file: %s", strerror(errno));
6508                         return REQ_NONE;
6509                 }
6510         }
6511         open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
6513         return REQ_NONE;
6516 static struct view_ops stage_ops = {
6517         "line",
6518         NULL,
6519         NULL,
6520         pager_read,
6521         pager_draw,
6522         stage_request,
6523         pager_grep,
6524         pager_select,
6525 };
6528 /*
6529  * Revision graph
6530  */
6532 struct commit {
6533         char id[SIZEOF_REV];            /* SHA1 ID. */
6534         char title[128];                /* First line of the commit message. */
6535         const char *author;             /* Author of the commit. */
6536         struct time time;               /* Date from the author ident. */
6537         struct ref_list *refs;          /* Repository references. */
6538         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
6539         size_t graph_size;              /* The width of the graph array. */
6540         bool has_parents;               /* Rewritten --parents seen. */
6541 };
6543 /* Size of rev graph with no  "padding" columns */
6544 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6546 struct rev_graph {
6547         struct rev_graph *prev, *next, *parents;
6548         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
6549         size_t size;
6550         struct commit *commit;
6551         size_t pos;
6552         unsigned int boundary:1;
6553 };
6555 /* Parents of the commit being visualized. */
6556 static struct rev_graph graph_parents[4];
6558 /* The current stack of revisions on the graph. */
6559 static struct rev_graph graph_stacks[4] = {
6560         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
6561         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
6562         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
6563         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
6564 };
6566 static inline bool
6567 graph_parent_is_merge(struct rev_graph *graph)
6569         return graph->parents->size > 1;
6572 static inline void
6573 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
6575         struct commit *commit = graph->commit;
6577         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
6578                 commit->graph[commit->graph_size++] = symbol;
6581 static void
6582 clear_rev_graph(struct rev_graph *graph)
6584         graph->boundary = 0;
6585         graph->size = graph->pos = 0;
6586         graph->commit = NULL;
6587         memset(graph->parents, 0, sizeof(*graph->parents));
6590 static void
6591 done_rev_graph(struct rev_graph *graph)
6593         if (graph_parent_is_merge(graph) &&
6594             graph->pos < graph->size - 1 &&
6595             graph->next->size == graph->size + graph->parents->size - 1) {
6596                 size_t i = graph->pos + graph->parents->size - 1;
6598                 graph->commit->graph_size = i * 2;
6599                 while (i < graph->next->size - 1) {
6600                         append_to_rev_graph(graph, ' ');
6601                         append_to_rev_graph(graph, '\\');
6602                         i++;
6603                 }
6604         }
6606         clear_rev_graph(graph);
6609 static void
6610 push_rev_graph(struct rev_graph *graph, const char *parent)
6612         int i;
6614         /* "Collapse" duplicate parents lines.
6615          *
6616          * FIXME: This needs to also update update the drawn graph but
6617          * for now it just serves as a method for pruning graph lines. */
6618         for (i = 0; i < graph->size; i++)
6619                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
6620                         return;
6622         if (graph->size < SIZEOF_REVITEMS) {
6623                 string_copy_rev(graph->rev[graph->size++], parent);
6624         }
6627 static chtype
6628 get_rev_graph_symbol(struct rev_graph *graph)
6630         chtype symbol;
6632         if (graph->boundary)
6633                 symbol = REVGRAPH_BOUND;
6634         else if (graph->parents->size == 0)
6635                 symbol = REVGRAPH_INIT;
6636         else if (graph_parent_is_merge(graph))
6637                 symbol = REVGRAPH_MERGE;
6638         else if (graph->pos >= graph->size)
6639                 symbol = REVGRAPH_BRANCH;
6640         else
6641                 symbol = REVGRAPH_COMMIT;
6643         return symbol;
6646 static void
6647 draw_rev_graph(struct rev_graph *graph)
6649         struct rev_filler {
6650                 chtype separator, line;
6651         };
6652         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
6653         static struct rev_filler fillers[] = {
6654                 { ' ',  '|' },
6655                 { '`',  '.' },
6656                 { '\'', ' ' },
6657                 { '/',  ' ' },
6658         };
6659         chtype symbol = get_rev_graph_symbol(graph);
6660         struct rev_filler *filler;
6661         size_t i;
6663         fillers[DEFAULT].line = opt_line_graphics ? ACS_VLINE : '|';
6664         filler = &fillers[DEFAULT];
6666         for (i = 0; i < graph->pos; i++) {
6667                 append_to_rev_graph(graph, filler->line);
6668                 if (graph_parent_is_merge(graph->prev) &&
6669                     graph->prev->pos == i)
6670                         filler = &fillers[RSHARP];
6672                 append_to_rev_graph(graph, filler->separator);
6673         }
6675         /* Place the symbol for this revision. */
6676         append_to_rev_graph(graph, symbol);
6678         if (graph->prev->size > graph->size)
6679                 filler = &fillers[RDIAG];
6680         else
6681                 filler = &fillers[DEFAULT];
6683         i++;
6685         for (; i < graph->size; i++) {
6686                 append_to_rev_graph(graph, filler->separator);
6687                 append_to_rev_graph(graph, filler->line);
6688                 if (graph_parent_is_merge(graph->prev) &&
6689                     i < graph->prev->pos + graph->parents->size)
6690                         filler = &fillers[RSHARP];
6691                 if (graph->prev->size > graph->size)
6692                         filler = &fillers[LDIAG];
6693         }
6695         if (graph->prev->size > graph->size) {
6696                 append_to_rev_graph(graph, filler->separator);
6697                 if (filler->line != ' ')
6698                         append_to_rev_graph(graph, filler->line);
6699         }
6702 /* Prepare the next rev graph */
6703 static void
6704 prepare_rev_graph(struct rev_graph *graph)
6706         size_t i;
6708         /* First, traverse all lines of revisions up to the active one. */
6709         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6710                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6711                         break;
6713                 push_rev_graph(graph->next, graph->rev[graph->pos]);
6714         }
6716         /* Interleave the new revision parent(s). */
6717         for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6718                 push_rev_graph(graph->next, graph->parents->rev[i]);
6720         /* Lastly, put any remaining revisions. */
6721         for (i = graph->pos + 1; i < graph->size; i++)
6722                 push_rev_graph(graph->next, graph->rev[i]);
6725 static void
6726 update_rev_graph(struct view *view, struct rev_graph *graph)
6728         /* If this is the finalizing update ... */
6729         if (graph->commit)
6730                 prepare_rev_graph(graph);
6732         /* Graph visualization needs a one rev look-ahead,
6733          * so the first update doesn't visualize anything. */
6734         if (!graph->prev->commit)
6735                 return;
6737         if (view->lines > 2)
6738                 view->line[view->lines - 3].dirty = 1;
6739         if (view->lines > 1)
6740                 view->line[view->lines - 2].dirty = 1;
6741         draw_rev_graph(graph->prev);
6742         done_rev_graph(graph->prev->prev);
6746 /*
6747  * Main view backend
6748  */
6750 static const char *main_argv[SIZEOF_ARG] = {
6751         "git", "log", "--no-color", "--pretty=raw", "--parents",
6752                 "--topo-order", "%(diffargs)", "%(revargs)",
6753                 "--", "%(fileargs)", NULL
6754 };
6756 static bool
6757 main_draw(struct view *view, struct line *line, unsigned int lineno)
6759         struct commit *commit = line->data;
6761         if (!commit->author)
6762                 return FALSE;
6764         if (opt_date && draw_date(view, &commit->time))
6765                 return TRUE;
6767         if (opt_author && draw_author(view, commit->author))
6768                 return TRUE;
6770         if (opt_rev_graph && commit->graph_size &&
6771             draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6772                 return TRUE;
6774         if (opt_show_refs && commit->refs) {
6775                 size_t i;
6777                 for (i = 0; i < commit->refs->size; i++) {
6778                         struct ref *ref = commit->refs->refs[i];
6779                         enum line_type type;
6781                         if (ref->head)
6782                                 type = LINE_MAIN_HEAD;
6783                         else if (ref->ltag)
6784                                 type = LINE_MAIN_LOCAL_TAG;
6785                         else if (ref->tag)
6786                                 type = LINE_MAIN_TAG;
6787                         else if (ref->tracked)
6788                                 type = LINE_MAIN_TRACKED;
6789                         else if (ref->remote)
6790                                 type = LINE_MAIN_REMOTE;
6791                         else
6792                                 type = LINE_MAIN_REF;
6794                         if (draw_text(view, type, "[", TRUE) ||
6795                             draw_text(view, type, ref->name, TRUE) ||
6796                             draw_text(view, type, "]", TRUE))
6797                                 return TRUE;
6799                         if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6800                                 return TRUE;
6801                 }
6802         }
6804         draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6805         return TRUE;
6808 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6809 static bool
6810 main_read(struct view *view, char *line)
6812         static struct rev_graph *graph = graph_stacks;
6813         enum line_type type;
6814         struct commit *commit;
6816         if (!line) {
6817                 int i;
6819                 if (!view->lines && !view->prev)
6820                         die("No revisions match the given arguments.");
6821                 if (view->lines > 0) {
6822                         commit = view->line[view->lines - 1].data;
6823                         view->line[view->lines - 1].dirty = 1;
6824                         if (!commit->author) {
6825                                 view->lines--;
6826                                 free(commit);
6827                                 graph->commit = NULL;
6828                         }
6829                 }
6830                 update_rev_graph(view, graph);
6832                 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6833                         clear_rev_graph(&graph_stacks[i]);
6834                 return TRUE;
6835         }
6837         type = get_line_type(line);
6838         if (type == LINE_COMMIT) {
6839                 commit = calloc(1, sizeof(struct commit));
6840                 if (!commit)
6841                         return FALSE;
6843                 line += STRING_SIZE("commit ");
6844                 if (*line == '-') {
6845                         graph->boundary = 1;
6846                         line++;
6847                 }
6849                 string_copy_rev(commit->id, line);
6850                 commit->refs = get_ref_list(commit->id);
6851                 graph->commit = commit;
6852                 add_line_data(view, commit, LINE_MAIN_COMMIT);
6854                 while ((line = strchr(line, ' '))) {
6855                         line++;
6856                         push_rev_graph(graph->parents, line);
6857                         commit->has_parents = TRUE;
6858                 }
6859                 return TRUE;
6860         }
6862         if (!view->lines)
6863                 return TRUE;
6864         commit = view->line[view->lines - 1].data;
6866         switch (type) {
6867         case LINE_PARENT:
6868                 if (commit->has_parents)
6869                         break;
6870                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6871                 break;
6873         case LINE_AUTHOR:
6874                 parse_author_line(line + STRING_SIZE("author "),
6875                                   &commit->author, &commit->time);
6876                 update_rev_graph(view, graph);
6877                 graph = graph->next;
6878                 break;
6880         default:
6881                 /* Fill in the commit title if it has not already been set. */
6882                 if (commit->title[0])
6883                         break;
6885                 /* Require titles to start with a non-space character at the
6886                  * offset used by git log. */
6887                 if (strncmp(line, "    ", 4))
6888                         break;
6889                 line += 4;
6890                 /* Well, if the title starts with a whitespace character,
6891                  * try to be forgiving.  Otherwise we end up with no title. */
6892                 while (isspace(*line))
6893                         line++;
6894                 if (*line == '\0')
6895                         break;
6896                 /* FIXME: More graceful handling of titles; append "..." to
6897                  * shortened titles, etc. */
6899                 string_expand(commit->title, sizeof(commit->title), line, 1);
6900                 view->line[view->lines - 1].dirty = 1;
6901         }
6903         return TRUE;
6906 static enum request
6907 main_request(struct view *view, enum request request, struct line *line)
6909         enum open_flags flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
6911         switch (request) {
6912         case REQ_ENTER:
6913                 open_view(view, REQ_VIEW_DIFF, flags);
6914                 break;
6915         case REQ_REFRESH:
6916                 load_refs();
6917                 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6918                 break;
6919         default:
6920                 return request;
6921         }
6923         return REQ_NONE;
6926 static bool
6927 grep_refs(struct ref_list *list, regex_t *regex)
6929         regmatch_t pmatch;
6930         size_t i;
6932         if (!opt_show_refs || !list)
6933                 return FALSE;
6935         for (i = 0; i < list->size; i++) {
6936                 if (regexec(regex, list->refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6937                         return TRUE;
6938         }
6940         return FALSE;
6943 static bool
6944 main_grep(struct view *view, struct line *line)
6946         struct commit *commit = line->data;
6947         const char *text[] = {
6948                 commit->title,
6949                 opt_author ? commit->author : "",
6950                 mkdate(&commit->time, opt_date),
6951                 NULL
6952         };
6954         return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6957 static void
6958 main_select(struct view *view, struct line *line)
6960         struct commit *commit = line->data;
6962         string_copy_rev(view->ref, commit->id);
6963         string_copy_rev(ref_commit, view->ref);
6966 static struct view_ops main_ops = {
6967         "commit",
6968         main_argv,
6969         NULL,
6970         main_read,
6971         main_draw,
6972         main_request,
6973         main_grep,
6974         main_select,
6975 };
6978 /*
6979  * Status management
6980  */
6982 /* Whether or not the curses interface has been initialized. */
6983 static bool cursed = FALSE;
6985 /* Terminal hacks and workarounds. */
6986 static bool use_scroll_redrawwin;
6987 static bool use_scroll_status_wclear;
6989 /* The status window is used for polling keystrokes. */
6990 static WINDOW *status_win;
6992 /* Reading from the prompt? */
6993 static bool input_mode = FALSE;
6995 static bool status_empty = FALSE;
6997 /* Update status and title window. */
6998 static void
6999 report(const char *msg, ...)
7001         struct view *view = display[current_view];
7003         if (input_mode)
7004                 return;
7006         if (!view) {
7007                 char buf[SIZEOF_STR];
7008                 va_list args;
7010                 va_start(args, msg);
7011                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
7012                         buf[sizeof(buf) - 1] = 0;
7013                         buf[sizeof(buf) - 2] = '.';
7014                         buf[sizeof(buf) - 3] = '.';
7015                         buf[sizeof(buf) - 4] = '.';
7016                 }
7017                 va_end(args);
7018                 die("%s", buf);
7019         }
7021         if (!status_empty || *msg) {
7022                 va_list args;
7024                 va_start(args, msg);
7026                 wmove(status_win, 0, 0);
7027                 if (view->has_scrolled && use_scroll_status_wclear)
7028                         wclear(status_win);
7029                 if (*msg) {
7030                         vwprintw(status_win, msg, args);
7031                         status_empty = FALSE;
7032                 } else {
7033                         status_empty = TRUE;
7034                 }
7035                 wclrtoeol(status_win);
7036                 wnoutrefresh(status_win);
7038                 va_end(args);
7039         }
7041         update_view_title(view);
7044 static void
7045 init_display(void)
7047         const char *term;
7048         int x, y;
7050         /* Initialize the curses library */
7051         if (isatty(STDIN_FILENO)) {
7052                 cursed = !!initscr();
7053                 opt_tty = stdin;
7054         } else {
7055                 /* Leave stdin and stdout alone when acting as a pager. */
7056                 opt_tty = fopen("/dev/tty", "r+");
7057                 if (!opt_tty)
7058                         die("Failed to open /dev/tty");
7059                 cursed = !!newterm(NULL, opt_tty, opt_tty);
7060         }
7062         if (!cursed)
7063                 die("Failed to initialize curses");
7065         nonl();         /* Disable conversion and detect newlines from input. */
7066         cbreak();       /* Take input chars one at a time, no wait for \n */
7067         noecho();       /* Don't echo input */
7068         leaveok(stdscr, FALSE);
7070         if (has_colors())
7071                 init_colors();
7073         getmaxyx(stdscr, y, x);
7074         status_win = newwin(1, 0, y - 1, 0);
7075         if (!status_win)
7076                 die("Failed to create status window");
7078         /* Enable keyboard mapping */
7079         keypad(status_win, TRUE);
7080         wbkgdset(status_win, get_line_attr(LINE_STATUS));
7082         TABSIZE = opt_tab_size;
7084         term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
7085         if (term && !strcmp(term, "gnome-terminal")) {
7086                 /* In the gnome-terminal-emulator, the message from
7087                  * scrolling up one line when impossible followed by
7088                  * scrolling down one line causes corruption of the
7089                  * status line. This is fixed by calling wclear. */
7090                 use_scroll_status_wclear = TRUE;
7091                 use_scroll_redrawwin = FALSE;
7093         } else if (term && !strcmp(term, "xrvt-xpm")) {
7094                 /* No problems with full optimizations in xrvt-(unicode)
7095                  * and aterm. */
7096                 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
7098         } else {
7099                 /* When scrolling in (u)xterm the last line in the
7100                  * scrolling direction will update slowly. */
7101                 use_scroll_redrawwin = TRUE;
7102                 use_scroll_status_wclear = FALSE;
7103         }
7106 static int
7107 get_input(int prompt_position)
7109         struct view *view;
7110         int i, key, cursor_y, cursor_x;
7112         if (prompt_position)
7113                 input_mode = TRUE;
7115         while (TRUE) {
7116                 bool loading = FALSE;
7118                 foreach_view (view, i) {
7119                         update_view(view);
7120                         if (view_is_displayed(view) && view->has_scrolled &&
7121                             use_scroll_redrawwin)
7122                                 redrawwin(view->win);
7123                         view->has_scrolled = FALSE;
7124                         if (view->pipe)
7125                                 loading = TRUE;
7126                 }
7128                 /* Update the cursor position. */
7129                 if (prompt_position) {
7130                         getbegyx(status_win, cursor_y, cursor_x);
7131                         cursor_x = prompt_position;
7132                 } else {
7133                         view = display[current_view];
7134                         getbegyx(view->win, cursor_y, cursor_x);
7135                         cursor_x = view->width - 1;
7136                         cursor_y += view->lineno - view->offset;
7137                 }
7138                 setsyx(cursor_y, cursor_x);
7140                 /* Refresh, accept single keystroke of input */
7141                 doupdate();
7142                 nodelay(status_win, loading);
7143                 key = wgetch(status_win);
7145                 /* wgetch() with nodelay() enabled returns ERR when
7146                  * there's no input. */
7147                 if (key == ERR) {
7149                 } else if (key == KEY_RESIZE) {
7150                         int height, width;
7152                         getmaxyx(stdscr, height, width);
7154                         wresize(status_win, 1, width);
7155                         mvwin(status_win, height - 1, 0);
7156                         wnoutrefresh(status_win);
7157                         resize_display();
7158                         redraw_display(TRUE);
7160                 } else {
7161                         input_mode = FALSE;
7162                         return key;
7163                 }
7164         }
7167 static char *
7168 prompt_input(const char *prompt, input_handler handler, void *data)
7170         enum input_status status = INPUT_OK;
7171         static char buf[SIZEOF_STR];
7172         size_t pos = 0;
7174         buf[pos] = 0;
7176         while (status == INPUT_OK || status == INPUT_SKIP) {
7177                 int key;
7179                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
7180                 wclrtoeol(status_win);
7182                 key = get_input(pos + 1);
7183                 switch (key) {
7184                 case KEY_RETURN:
7185                 case KEY_ENTER:
7186                 case '\n':
7187                         status = pos ? INPUT_STOP : INPUT_CANCEL;
7188                         break;
7190                 case KEY_BACKSPACE:
7191                         if (pos > 0)
7192                                 buf[--pos] = 0;
7193                         else
7194                                 status = INPUT_CANCEL;
7195                         break;
7197                 case KEY_ESC:
7198                         status = INPUT_CANCEL;
7199                         break;
7201                 default:
7202                         if (pos >= sizeof(buf)) {
7203                                 report("Input string too long");
7204                                 return NULL;
7205                         }
7207                         status = handler(data, buf, key);
7208                         if (status == INPUT_OK)
7209                                 buf[pos++] = (char) key;
7210                 }
7211         }
7213         /* Clear the status window */
7214         status_empty = FALSE;
7215         report("");
7217         if (status == INPUT_CANCEL)
7218                 return NULL;
7220         buf[pos++] = 0;
7222         return buf;
7225 static enum input_status
7226 prompt_yesno_handler(void *data, char *buf, int c)
7228         if (c == 'y' || c == 'Y')
7229                 return INPUT_STOP;
7230         if (c == 'n' || c == 'N')
7231                 return INPUT_CANCEL;
7232         return INPUT_SKIP;
7235 static bool
7236 prompt_yesno(const char *prompt)
7238         char prompt2[SIZEOF_STR];
7240         if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
7241                 return FALSE;
7243         return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
7246 static enum input_status
7247 read_prompt_handler(void *data, char *buf, int c)
7249         return isprint(c) ? INPUT_OK : INPUT_SKIP;
7252 static char *
7253 read_prompt(const char *prompt)
7255         return prompt_input(prompt, read_prompt_handler, NULL);
7258 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected)
7260         enum input_status status = INPUT_OK;
7261         int size = 0;
7263         while (items[size].text)
7264                 size++;
7266         while (status == INPUT_OK) {
7267                 const struct menu_item *item = &items[*selected];
7268                 int key;
7269                 int i;
7271                 mvwprintw(status_win, 0, 0, "%s (%d of %d) ",
7272                           prompt, *selected + 1, size);
7273                 if (item->hotkey)
7274                         wprintw(status_win, "[%c] ", (char) item->hotkey);
7275                 wprintw(status_win, "%s", item->text);
7276                 wclrtoeol(status_win);
7278                 key = get_input(COLS - 1);
7279                 switch (key) {
7280                 case KEY_RETURN:
7281                 case KEY_ENTER:
7282                 case '\n':
7283                         status = INPUT_STOP;
7284                         break;
7286                 case KEY_LEFT:
7287                 case KEY_UP:
7288                         *selected = *selected - 1;
7289                         if (*selected < 0)
7290                                 *selected = size - 1;
7291                         break;
7293                 case KEY_RIGHT:
7294                 case KEY_DOWN:
7295                         *selected = (*selected + 1) % size;
7296                         break;
7298                 case KEY_ESC:
7299                         status = INPUT_CANCEL;
7300                         break;
7302                 default:
7303                         for (i = 0; items[i].text; i++)
7304                                 if (items[i].hotkey == key) {
7305                                         *selected = i;
7306                                         status = INPUT_STOP;
7307                                         break;
7308                                 }
7309                 }
7310         }
7312         /* Clear the status window */
7313         status_empty = FALSE;
7314         report("");
7316         return status != INPUT_CANCEL;
7319 /*
7320  * Repository properties
7321  */
7323 static struct ref **refs = NULL;
7324 static size_t refs_size = 0;
7325 static struct ref *refs_head = NULL;
7327 static struct ref_list **ref_lists = NULL;
7328 static size_t ref_lists_size = 0;
7330 DEFINE_ALLOCATOR(realloc_refs, struct ref *, 256)
7331 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
7332 DEFINE_ALLOCATOR(realloc_ref_lists, struct ref_list *, 8)
7334 static int
7335 compare_refs(const void *ref1_, const void *ref2_)
7337         const struct ref *ref1 = *(const struct ref **)ref1_;
7338         const struct ref *ref2 = *(const struct ref **)ref2_;
7340         if (ref1->tag != ref2->tag)
7341                 return ref2->tag - ref1->tag;
7342         if (ref1->ltag != ref2->ltag)
7343                 return ref2->ltag - ref2->ltag;
7344         if (ref1->head != ref2->head)
7345                 return ref2->head - ref1->head;
7346         if (ref1->tracked != ref2->tracked)
7347                 return ref2->tracked - ref1->tracked;
7348         if (ref1->remote != ref2->remote)
7349                 return ref2->remote - ref1->remote;
7350         return strcmp(ref1->name, ref2->name);
7353 static void
7354 foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data)
7356         size_t i;
7358         for (i = 0; i < refs_size; i++)
7359                 if (!visitor(data, refs[i]))
7360                         break;
7363 static struct ref *
7364 get_ref_head()
7366         return refs_head;
7369 static struct ref_list *
7370 get_ref_list(const char *id)
7372         struct ref_list *list;
7373         size_t i;
7375         for (i = 0; i < ref_lists_size; i++)
7376                 if (!strcmp(id, ref_lists[i]->id))
7377                         return ref_lists[i];
7379         if (!realloc_ref_lists(&ref_lists, ref_lists_size, 1))
7380                 return NULL;
7381         list = calloc(1, sizeof(*list));
7382         if (!list)
7383                 return NULL;
7385         for (i = 0; i < refs_size; i++) {
7386                 if (!strcmp(id, refs[i]->id) &&
7387                     realloc_refs_list(&list->refs, list->size, 1))
7388                         list->refs[list->size++] = refs[i];
7389         }
7391         if (!list->refs) {
7392                 free(list);
7393                 return NULL;
7394         }
7396         qsort(list->refs, list->size, sizeof(*list->refs), compare_refs);
7397         ref_lists[ref_lists_size++] = list;
7398         return list;
7401 static int
7402 read_ref(char *id, size_t idlen, char *name, size_t namelen)
7404         struct ref *ref = NULL;
7405         bool tag = FALSE;
7406         bool ltag = FALSE;
7407         bool remote = FALSE;
7408         bool tracked = FALSE;
7409         bool head = FALSE;
7410         int from = 0, to = refs_size - 1;
7412         if (!prefixcmp(name, "refs/tags/")) {
7413                 if (!suffixcmp(name, namelen, "^{}")) {
7414                         namelen -= 3;
7415                         name[namelen] = 0;
7416                 } else {
7417                         ltag = TRUE;
7418                 }
7420                 tag = TRUE;
7421                 namelen -= STRING_SIZE("refs/tags/");
7422                 name    += STRING_SIZE("refs/tags/");
7424         } else if (!prefixcmp(name, "refs/remotes/")) {
7425                 remote = TRUE;
7426                 namelen -= STRING_SIZE("refs/remotes/");
7427                 name    += STRING_SIZE("refs/remotes/");
7428                 tracked  = !strcmp(opt_remote, name);
7430         } else if (!prefixcmp(name, "refs/heads/")) {
7431                 namelen -= STRING_SIZE("refs/heads/");
7432                 name    += STRING_SIZE("refs/heads/");
7433                 if (!strncmp(opt_head, name, namelen))
7434                         return OK;
7436         } else if (!strcmp(name, "HEAD")) {
7437                 head     = TRUE;
7438                 if (*opt_head) {
7439                         namelen  = strlen(opt_head);
7440                         name     = opt_head;
7441                 }
7442         }
7444         /* If we are reloading or it's an annotated tag, replace the
7445          * previous SHA1 with the resolved commit id; relies on the fact
7446          * git-ls-remote lists the commit id of an annotated tag right
7447          * before the commit id it points to. */
7448         while (from <= to) {
7449                 size_t pos = (to + from) / 2;
7450                 int cmp = strcmp(name, refs[pos]->name);
7452                 if (!cmp) {
7453                         ref = refs[pos];
7454                         break;
7455                 }
7457                 if (cmp < 0)
7458                         to = pos - 1;
7459                 else
7460                         from = pos + 1;
7461         }
7463         if (!ref) {
7464                 if (!realloc_refs(&refs, refs_size, 1))
7465                         return ERR;
7466                 ref = calloc(1, sizeof(*ref) + namelen);
7467                 if (!ref)
7468                         return ERR;
7469                 memmove(refs + from + 1, refs + from,
7470                         (refs_size - from) * sizeof(*refs));
7471                 refs[from] = ref;
7472                 strncpy(ref->name, name, namelen);
7473                 refs_size++;
7474         }
7476         ref->head = head;
7477         ref->tag = tag;
7478         ref->ltag = ltag;
7479         ref->remote = remote;
7480         ref->tracked = tracked;
7481         string_copy_rev(ref->id, id);
7483         if (head)
7484                 refs_head = ref;
7485         return OK;
7488 static int
7489 load_refs(void)
7491         const char *head_argv[] = {
7492                 "git", "symbolic-ref", "HEAD", NULL
7493         };
7494         static const char *ls_remote_argv[SIZEOF_ARG] = {
7495                 "git", "ls-remote", opt_git_dir, NULL
7496         };
7497         static bool init = FALSE;
7498         size_t i;
7500         if (!init) {
7501                 if (!argv_from_env(ls_remote_argv, "TIG_LS_REMOTE"))
7502                         die("TIG_LS_REMOTE contains too many arguments");
7503                 init = TRUE;
7504         }
7506         if (!*opt_git_dir)
7507                 return OK;
7509         if (io_run_buf(head_argv, opt_head, sizeof(opt_head)) &&
7510             !prefixcmp(opt_head, "refs/heads/")) {
7511                 char *offset = opt_head + STRING_SIZE("refs/heads/");
7513                 memmove(opt_head, offset, strlen(offset) + 1);
7514         }
7516         refs_head = NULL;
7517         for (i = 0; i < refs_size; i++)
7518                 refs[i]->id[0] = 0;
7520         if (io_run_load(ls_remote_argv, "\t", read_ref) == ERR)
7521                 return ERR;
7523         /* Update the ref lists to reflect changes. */
7524         for (i = 0; i < ref_lists_size; i++) {
7525                 struct ref_list *list = ref_lists[i];
7526                 size_t old, new;
7528                 for (old = new = 0; old < list->size; old++)
7529                         if (!strcmp(list->id, list->refs[old]->id))
7530                                 list->refs[new++] = list->refs[old];
7531                 list->size = new;
7532         }
7534         return OK;
7537 static void
7538 set_remote_branch(const char *name, const char *value, size_t valuelen)
7540         if (!strcmp(name, ".remote")) {
7541                 string_ncopy(opt_remote, value, valuelen);
7543         } else if (*opt_remote && !strcmp(name, ".merge")) {
7544                 size_t from = strlen(opt_remote);
7546                 if (!prefixcmp(value, "refs/heads/"))
7547                         value += STRING_SIZE("refs/heads/");
7549                 if (!string_format_from(opt_remote, &from, "/%s", value))
7550                         opt_remote[0] = 0;
7551         }
7554 static void
7555 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
7557         const char *argv[SIZEOF_ARG] = { name, "=" };
7558         int argc = 1 + (cmd == option_set_command);
7559         int error = ERR;
7561         if (!argv_from_string(argv, &argc, value))
7562                 config_msg = "Too many option arguments";
7563         else
7564                 error = cmd(argc, argv);
7566         if (error == ERR)
7567                 warn("Option 'tig.%s': %s", name, config_msg);
7570 static bool
7571 set_environment_variable(const char *name, const char *value)
7573         size_t len = strlen(name) + 1 + strlen(value) + 1;
7574         char *env = malloc(len);
7576         if (env &&
7577             string_nformat(env, len, NULL, "%s=%s", name, value) &&
7578             putenv(env) == 0)
7579                 return TRUE;
7580         free(env);
7581         return FALSE;
7584 static void
7585 set_work_tree(const char *value)
7587         char cwd[SIZEOF_STR];
7589         if (!getcwd(cwd, sizeof(cwd)))
7590                 die("Failed to get cwd path: %s", strerror(errno));
7591         if (chdir(opt_git_dir) < 0)
7592                 die("Failed to chdir(%s): %s", strerror(errno));
7593         if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7594                 die("Failed to get git path: %s", strerror(errno));
7595         if (chdir(cwd) < 0)
7596                 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7597         if (chdir(value) < 0)
7598                 die("Failed to chdir(%s): %s", value, strerror(errno));
7599         if (!getcwd(cwd, sizeof(cwd)))
7600                 die("Failed to get cwd path: %s", strerror(errno));
7601         if (!set_environment_variable("GIT_WORK_TREE", cwd))
7602                 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7603         if (!set_environment_variable("GIT_DIR", opt_git_dir))
7604                 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7605         opt_is_inside_work_tree = TRUE;
7608 static int
7609 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
7611         if (!strcmp(name, "i18n.commitencoding"))
7612                 string_ncopy(opt_encoding, value, valuelen);
7614         else if (!strcmp(name, "core.editor"))
7615                 string_ncopy(opt_editor, value, valuelen);
7617         else if (!strcmp(name, "core.worktree"))
7618                 set_work_tree(value);
7620         else if (!prefixcmp(name, "tig.color."))
7621                 set_repo_config_option(name + 10, value, option_color_command);
7623         else if (!prefixcmp(name, "tig.bind."))
7624                 set_repo_config_option(name + 9, value, option_bind_command);
7626         else if (!prefixcmp(name, "tig."))
7627                 set_repo_config_option(name + 4, value, option_set_command);
7629         else if (*opt_head && !prefixcmp(name, "branch.") &&
7630                  !strncmp(name + 7, opt_head, strlen(opt_head)))
7631                 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7633         return OK;
7636 static int
7637 load_git_config(void)
7639         const char *config_list_argv[] = { "git", "config", "--list", NULL };
7641         return io_run_load(config_list_argv, "=", read_repo_config_option);
7644 static int
7645 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
7647         if (!opt_git_dir[0]) {
7648                 string_ncopy(opt_git_dir, name, namelen);
7650         } else if (opt_is_inside_work_tree == -1) {
7651                 /* This can be 3 different values depending on the
7652                  * version of git being used. If git-rev-parse does not
7653                  * understand --is-inside-work-tree it will simply echo
7654                  * the option else either "true" or "false" is printed.
7655                  * Default to true for the unknown case. */
7656                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7658         } else if (*name == '.') {
7659                 string_ncopy(opt_cdup, name, namelen);
7661         } else {
7662                 string_ncopy(opt_prefix, name, namelen);
7663         }
7665         return OK;
7668 static int
7669 load_repo_info(void)
7671         const char *rev_parse_argv[] = {
7672                 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7673                         "--show-cdup", "--show-prefix", NULL
7674         };
7676         return io_run_load(rev_parse_argv, "=", read_repo_info);
7680 /*
7681  * Main
7682  */
7684 static const char usage[] =
7685 "tig " TIG_VERSION " (" __DATE__ ")\n"
7686 "\n"
7687 "Usage: tig        [options] [revs] [--] [paths]\n"
7688 "   or: tig show   [options] [revs] [--] [paths]\n"
7689 "   or: tig blame  [rev] path\n"
7690 "   or: tig status\n"
7691 "   or: tig <      [git command output]\n"
7692 "\n"
7693 "Options:\n"
7694 "  -v, --version   Show version and exit\n"
7695 "  -h, --help      Show help message and exit";
7697 static void __NORETURN
7698 quit(int sig)
7700         /* XXX: Restore tty modes and let the OS cleanup the rest! */
7701         if (cursed)
7702                 endwin();
7703         exit(0);
7706 static void __NORETURN
7707 die(const char *err, ...)
7709         va_list args;
7711         endwin();
7713         va_start(args, err);
7714         fputs("tig: ", stderr);
7715         vfprintf(stderr, err, args);
7716         fputs("\n", stderr);
7717         va_end(args);
7719         exit(1);
7722 static void
7723 warn(const char *msg, ...)
7725         va_list args;
7727         va_start(args, msg);
7728         fputs("tig warning: ", stderr);
7729         vfprintf(stderr, msg, args);
7730         fputs("\n", stderr);
7731         va_end(args);
7734 static const char ***filter_args;
7736 static int
7737 read_filter_args(char *name, size_t namelen, char *value, size_t valuelen)
7739         return argv_append(filter_args, name) ? OK : ERR;
7742 static void
7743 filter_rev_parse(const char ***args, const char *arg1, const char *arg2, const char *argv[])
7745         const char *rev_parse_argv[SIZEOF_ARG] = { "git", "rev-parse", arg1, arg2 };
7746         const char **all_argv = NULL;
7748         filter_args = args;
7749         if (!argv_append_array(&all_argv, rev_parse_argv) ||
7750             !argv_append_array(&all_argv, argv) ||
7751             !io_run_load(all_argv, "\n", read_filter_args) == ERR)
7752                 die("Failed to split arguments");
7753         argv_free(all_argv);
7754         free(all_argv);
7757 static void
7758 filter_options(const char *argv[])
7760         filter_rev_parse(&opt_file_args, "--no-revs", "--no-flags", argv);
7761         filter_rev_parse(&opt_diff_args, "--no-revs", "--flags", argv);
7762         filter_rev_parse(&opt_rev_args, "--symbolic", "--revs-only", argv);
7765 static enum request
7766 parse_options(int argc, const char *argv[])
7768         enum request request = REQ_VIEW_MAIN;
7769         const char *subcommand;
7770         bool seen_dashdash = FALSE;
7771         const char **filter_argv = NULL;
7772         int i;
7774         if (!isatty(STDIN_FILENO))
7775                 return REQ_VIEW_PAGER;
7777         if (argc <= 1)
7778                 return REQ_VIEW_MAIN;
7780         subcommand = argv[1];
7781         if (!strcmp(subcommand, "status")) {
7782                 if (argc > 2)
7783                         warn("ignoring arguments after `%s'", subcommand);
7784                 return REQ_VIEW_STATUS;
7786         } else if (!strcmp(subcommand, "blame")) {
7787                 if (argc <= 2 || argc > 4)
7788                         die("invalid number of options to blame\n\n%s", usage);
7790                 i = 2;
7791                 if (argc == 4) {
7792                         string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7793                         i++;
7794                 }
7796                 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7797                 return REQ_VIEW_BLAME;
7799         } else if (!strcmp(subcommand, "show")) {
7800                 request = REQ_VIEW_DIFF;
7802         } else {
7803                 subcommand = NULL;
7804         }
7806         for (i = 1 + !!subcommand; i < argc; i++) {
7807                 const char *opt = argv[i];
7809                 if (seen_dashdash) {
7810                         argv_append(&opt_file_args, opt);
7811                         continue;
7813                 } else if (!strcmp(opt, "--")) {
7814                         seen_dashdash = TRUE;
7815                         continue;
7817                 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7818                         printf("tig version %s\n", TIG_VERSION);
7819                         quit(0);
7821                 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7822                         printf("%s\n", usage);
7823                         quit(0);
7825                 } else if (!strcmp(opt, "--all")) {
7826                         argv_append(&opt_rev_args, opt);
7827                         continue;
7828                 }
7830                 if (!argv_append(&filter_argv, opt))
7831                         die("command too long");
7832         }
7834         if (filter_argv)
7835                 filter_options(filter_argv);
7837         return request;
7840 int
7841 main(int argc, const char *argv[])
7843         const char *codeset = "UTF-8";
7844         enum request request = parse_options(argc, argv);
7845         struct view *view;
7846         size_t i;
7848         signal(SIGINT, quit);
7849         signal(SIGPIPE, SIG_IGN);
7851         if (setlocale(LC_ALL, "")) {
7852                 codeset = nl_langinfo(CODESET);
7853         }
7855         if (load_repo_info() == ERR)
7856                 die("Failed to load repo info.");
7858         if (load_options() == ERR)
7859                 die("Failed to load user config.");
7861         if (load_git_config() == ERR)
7862                 die("Failed to load repo config.");
7864         /* Require a git repository unless when running in pager mode. */
7865         if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7866                 die("Not a git repository");
7868         if (*opt_encoding && strcmp(codeset, "UTF-8")) {
7869                 opt_iconv_in = iconv_open("UTF-8", opt_encoding);
7870                 if (opt_iconv_in == ICONV_NONE)
7871                         die("Failed to initialize character set conversion");
7872         }
7874         if (codeset && strcmp(codeset, "UTF-8")) {
7875                 opt_iconv_out = iconv_open(codeset, "UTF-8");
7876                 if (opt_iconv_out == ICONV_NONE)
7877                         die("Failed to initialize character set conversion");
7878         }
7880         if (load_refs() == ERR)
7881                 die("Failed to load refs.");
7883         foreach_view (view, i) {
7884                 if (getenv(view->cmd_env))
7885                         warn("Use of the %s environment variable is deprecated,"
7886                              " use options or TIG_DIFF_ARGS instead",
7887                              view->cmd_env);
7888                 if (!argv_from_env(view->ops->argv, view->cmd_env))
7889                         die("Too many arguments in the `%s` environment variable",
7890                             view->cmd_env);
7891         }
7893         init_display();
7895         while (view_driver(display[current_view], request)) {
7896                 int key = get_input(0);
7898                 view = display[current_view];
7899                 request = get_keybinding(view->keymap, key);
7901                 /* Some low-level request handling. This keeps access to
7902                  * status_win restricted. */
7903                 switch (request) {
7904                 case REQ_NONE:
7905                         report("Unknown key, press %s for help",
7906                                get_key(view->keymap, REQ_VIEW_HELP));
7907                         break;
7908                 case REQ_PROMPT:
7909                 {
7910                         char *cmd = read_prompt(":");
7912                         if (cmd && isdigit(*cmd)) {
7913                                 int lineno = view->lineno + 1;
7915                                 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7916                                         select_view_line(view, lineno - 1);
7917                                         report("");
7918                                 } else {
7919                                         report("Unable to parse '%s' as a line number", cmd);
7920                                 }
7922                         } else if (cmd) {
7923                                 struct view *next = VIEW(REQ_VIEW_PAGER);
7924                                 const char *argv[SIZEOF_ARG] = { "git" };
7925                                 int argc = 1;
7927                                 /* When running random commands, initially show the
7928                                  * command in the title. However, it maybe later be
7929                                  * overwritten if a commit line is selected. */
7930                                 string_ncopy(next->ref, cmd, strlen(cmd));
7932                                 if (!argv_from_string(argv, &argc, cmd)) {
7933                                         report("Too many arguments");
7934                                 } else if (!prepare_update(next, argv, NULL)) {
7935                                         report("Failed to format command");
7936                                 } else {
7937                                         open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7938                                 }
7939                         }
7941                         request = REQ_NONE;
7942                         break;
7943                 }
7944                 case REQ_SEARCH:
7945                 case REQ_SEARCH_BACK:
7946                 {
7947                         const char *prompt = request == REQ_SEARCH ? "/" : "?";
7948                         char *search = read_prompt(prompt);
7950                         if (search)
7951                                 string_ncopy(opt_search, search, strlen(search));
7952                         else if (*opt_search)
7953                                 request = request == REQ_SEARCH ?
7954                                         REQ_FIND_NEXT :
7955                                         REQ_FIND_PREV;
7956                         else
7957                                 request = REQ_NONE;
7958                         break;
7959                 }
7960                 default:
7961                         break;
7962                 }
7963         }
7965         quit(0);
7967         return 0;