Code

Allow tig to parse control-modified chars in tigrc
[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) == 2 && name[0] == '^' && isprint(*name))
1624                 return (int)name[1] & 0x1f;
1625         if (strlen(name) == 1 && isprint(*name))
1626                 return (int) *name;
1627         return ERR;
1630 static const char *
1631 get_key_name(int key_value)
1633         static char key_char[] = "'X'\0";
1634         const char *seq = NULL;
1635         int key;
1637         for (key = 0; key < ARRAY_SIZE(key_table); key++)
1638                 if (key_table[key].value == key_value)
1639                         seq = key_table[key].name;
1641         if (seq == NULL && key_value < 0x7f) {
1642                 char *s = key_char + 1;
1644                 if (key_value >= 0x20) {
1645                         *s++ = key_value;
1646                 } else {
1647                         *s++ = '^';
1648                         *s++ = 0x40 | (key_value & 0x1f);
1649                 }
1650                 *s++ = '\'';
1651                 *s++ = '\0';
1652                 seq = key_char;
1653         }
1655         return seq ? seq : "(no key)";
1658 static bool
1659 append_key(char *buf, size_t *pos, const struct keybinding *keybinding)
1661         const char *sep = *pos > 0 ? ", " : "";
1662         const char *keyname = get_key_name(keybinding->alias);
1664         return string_nformat(buf, BUFSIZ, pos, "%s%s", sep, keyname);
1667 static bool
1668 append_keymap_request_keys(char *buf, size_t *pos, enum request request,
1669                            enum keymap keymap, bool all)
1671         int i;
1673         for (i = 0; i < keybindings[keymap].size; i++) {
1674                 if (keybindings[keymap].data[i].request == request) {
1675                         if (!append_key(buf, pos, &keybindings[keymap].data[i]))
1676                                 return FALSE;
1677                         if (!all)
1678                                 break;
1679                 }
1680         }
1682         return TRUE;
1685 #define get_key(keymap, request) get_keys(keymap, request, FALSE)
1687 static const char *
1688 get_keys(enum keymap keymap, enum request request, bool all)
1690         static char buf[BUFSIZ];
1691         size_t pos = 0;
1692         int i;
1694         buf[pos] = 0;
1696         if (!append_keymap_request_keys(buf, &pos, request, keymap, all))
1697                 return "Too many keybindings!";
1698         if (pos > 0 && !all)
1699                 return buf;
1701         if (keymap != KEYMAP_GENERIC) {
1702                 /* Only the generic keymap includes the default keybindings when
1703                  * listing all keys. */
1704                 if (all)
1705                         return buf;
1707                 if (!append_keymap_request_keys(buf, &pos, request, KEYMAP_GENERIC, all))
1708                         return "Too many keybindings!";
1709                 if (pos)
1710                         return buf;
1711         }
1713         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1714                 if (default_keybindings[i].request == request) {
1715                         if (!append_key(buf, &pos, &default_keybindings[i]))
1716                                 return "Too many keybindings!";
1717                         if (!all)
1718                                 return buf;
1719                 }
1720         }
1722         return buf;
1725 struct run_request {
1726         enum keymap keymap;
1727         int key;
1728         const char **argv;
1729 };
1731 static struct run_request *run_request;
1732 static size_t run_requests;
1734 DEFINE_ALLOCATOR(realloc_run_requests, struct run_request, 8)
1736 static enum request
1737 add_run_request(enum keymap keymap, int key, const char **argv)
1739         struct run_request *req;
1741         if (!realloc_run_requests(&run_request, run_requests, 1))
1742                 return REQ_NONE;
1744         req = &run_request[run_requests];
1745         req->keymap = keymap;
1746         req->key = key;
1747         req->argv = NULL;
1749         if (!argv_copy(&req->argv, argv))
1750                 return REQ_NONE;
1752         return REQ_NONE + ++run_requests;
1755 static struct run_request *
1756 get_run_request(enum request request)
1758         if (request <= REQ_NONE)
1759                 return NULL;
1760         return &run_request[request - REQ_NONE - 1];
1763 static void
1764 add_builtin_run_requests(void)
1766         const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1767         const char *checkout[] = { "git", "checkout", "%(branch)", NULL };
1768         const char *commit[] = { "git", "commit", NULL };
1769         const char *gc[] = { "git", "gc", NULL };
1770         struct run_request reqs[] = {
1771                 { KEYMAP_MAIN,    'C', cherry_pick },
1772                 { KEYMAP_STATUS,  'C', commit },
1773                 { KEYMAP_BRANCH,  'C', checkout },
1774                 { KEYMAP_GENERIC, 'G', gc },
1775         };
1776         int i;
1778         for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1779                 enum request req = get_keybinding(reqs[i].keymap, reqs[i].key);
1781                 if (req != reqs[i].key)
1782                         continue;
1783                 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argv);
1784                 if (req != REQ_NONE)
1785                         add_keybinding(reqs[i].keymap, req, reqs[i].key);
1786         }
1789 /*
1790  * User config file handling.
1791  */
1793 static int   config_lineno;
1794 static bool  config_errors;
1795 static const char *config_msg;
1797 static const struct enum_map color_map[] = {
1798 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1799         COLOR_MAP(DEFAULT),
1800         COLOR_MAP(BLACK),
1801         COLOR_MAP(BLUE),
1802         COLOR_MAP(CYAN),
1803         COLOR_MAP(GREEN),
1804         COLOR_MAP(MAGENTA),
1805         COLOR_MAP(RED),
1806         COLOR_MAP(WHITE),
1807         COLOR_MAP(YELLOW),
1808 };
1810 static const struct enum_map attr_map[] = {
1811 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1812         ATTR_MAP(NORMAL),
1813         ATTR_MAP(BLINK),
1814         ATTR_MAP(BOLD),
1815         ATTR_MAP(DIM),
1816         ATTR_MAP(REVERSE),
1817         ATTR_MAP(STANDOUT),
1818         ATTR_MAP(UNDERLINE),
1819 };
1821 #define set_attribute(attr, name)       map_enum(attr, attr_map, name)
1823 static int parse_step(double *opt, const char *arg)
1825         *opt = atoi(arg);
1826         if (!strchr(arg, '%'))
1827                 return OK;
1829         /* "Shift down" so 100% and 1 does not conflict. */
1830         *opt = (*opt - 1) / 100;
1831         if (*opt >= 1.0) {
1832                 *opt = 0.99;
1833                 config_msg = "Step value larger than 100%";
1834                 return ERR;
1835         }
1836         if (*opt < 0.0) {
1837                 *opt = 1;
1838                 config_msg = "Invalid step value";
1839                 return ERR;
1840         }
1841         return OK;
1844 static int
1845 parse_int(int *opt, const char *arg, int min, int max)
1847         int value = atoi(arg);
1849         if (min <= value && value <= max) {
1850                 *opt = value;
1851                 return OK;
1852         }
1854         config_msg = "Integer value out of bound";
1855         return ERR;
1858 static bool
1859 set_color(int *color, const char *name)
1861         if (map_enum(color, color_map, name))
1862                 return TRUE;
1863         if (!prefixcmp(name, "color"))
1864                 return parse_int(color, name + 5, 0, 255) == OK;
1865         return FALSE;
1868 /* Wants: object fgcolor bgcolor [attribute] */
1869 static int
1870 option_color_command(int argc, const char *argv[])
1872         struct line_info *info;
1874         if (argc < 3) {
1875                 config_msg = "Wrong number of arguments given to color command";
1876                 return ERR;
1877         }
1879         info = get_line_info(argv[0]);
1880         if (!info) {
1881                 static const struct enum_map obsolete[] = {
1882                         ENUM_MAP("main-delim",  LINE_DELIMITER),
1883                         ENUM_MAP("main-date",   LINE_DATE),
1884                         ENUM_MAP("main-author", LINE_AUTHOR),
1885                 };
1886                 int index;
1888                 if (!map_enum(&index, obsolete, argv[0])) {
1889                         config_msg = "Unknown color name";
1890                         return ERR;
1891                 }
1892                 info = &line_info[index];
1893         }
1895         if (!set_color(&info->fg, argv[1]) ||
1896             !set_color(&info->bg, argv[2])) {
1897                 config_msg = "Unknown color";
1898                 return ERR;
1899         }
1901         info->attr = 0;
1902         while (argc-- > 3) {
1903                 int attr;
1905                 if (!set_attribute(&attr, argv[argc])) {
1906                         config_msg = "Unknown attribute";
1907                         return ERR;
1908                 }
1909                 info->attr |= attr;
1910         }
1912         return OK;
1915 static int parse_bool(bool *opt, const char *arg)
1917         *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1918                 ? TRUE : FALSE;
1919         return OK;
1922 static int parse_enum_do(unsigned int *opt, const char *arg,
1923                          const struct enum_map *map, size_t map_size)
1925         bool is_true;
1927         assert(map_size > 1);
1929         if (map_enum_do(map, map_size, (int *) opt, arg))
1930                 return OK;
1932         if (parse_bool(&is_true, arg) != OK)
1933                 return ERR;
1935         *opt = is_true ? map[1].value : map[0].value;
1936         return OK;
1939 #define parse_enum(opt, arg, map) \
1940         parse_enum_do(opt, arg, map, ARRAY_SIZE(map))
1942 static int
1943 parse_string(char *opt, const char *arg, size_t optsize)
1945         int arglen = strlen(arg);
1947         switch (arg[0]) {
1948         case '\"':
1949         case '\'':
1950                 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1951                         config_msg = "Unmatched quotation";
1952                         return ERR;
1953                 }
1954                 arg += 1; arglen -= 2;
1955         default:
1956                 string_ncopy_do(opt, optsize, arg, arglen);
1957                 return OK;
1958         }
1961 /* Wants: name = value */
1962 static int
1963 option_set_command(int argc, const char *argv[])
1965         if (argc != 3) {
1966                 config_msg = "Wrong number of arguments given to set command";
1967                 return ERR;
1968         }
1970         if (strcmp(argv[1], "=")) {
1971                 config_msg = "No value assigned";
1972                 return ERR;
1973         }
1975         if (!strcmp(argv[0], "show-author"))
1976                 return parse_enum(&opt_author, argv[2], author_map);
1978         if (!strcmp(argv[0], "show-date"))
1979                 return parse_enum(&opt_date, argv[2], date_map);
1981         if (!strcmp(argv[0], "show-rev-graph"))
1982                 return parse_bool(&opt_rev_graph, argv[2]);
1984         if (!strcmp(argv[0], "show-refs"))
1985                 return parse_bool(&opt_show_refs, argv[2]);
1987         if (!strcmp(argv[0], "show-line-numbers"))
1988                 return parse_bool(&opt_line_number, argv[2]);
1990         if (!strcmp(argv[0], "line-graphics"))
1991                 return parse_bool(&opt_line_graphics, argv[2]);
1993         if (!strcmp(argv[0], "line-number-interval"))
1994                 return parse_int(&opt_num_interval, argv[2], 1, 1024);
1996         if (!strcmp(argv[0], "author-width"))
1997                 return parse_int(&opt_author_cols, argv[2], 0, 1024);
1999         if (!strcmp(argv[0], "horizontal-scroll"))
2000                 return parse_step(&opt_hscroll, argv[2]);
2002         if (!strcmp(argv[0], "split-view-height"))
2003                 return parse_step(&opt_scale_split_view, argv[2]);
2005         if (!strcmp(argv[0], "tab-size"))
2006                 return parse_int(&opt_tab_size, argv[2], 1, 1024);
2008         if (!strcmp(argv[0], "commit-encoding"))
2009                 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
2011         config_msg = "Unknown variable name";
2012         return ERR;
2015 /* Wants: mode request key */
2016 static int
2017 option_bind_command(int argc, const char *argv[])
2019         enum request request;
2020         int keymap = -1;
2021         int key;
2023         if (argc < 3) {
2024                 config_msg = "Wrong number of arguments given to bind command";
2025                 return ERR;
2026         }
2028         if (!set_keymap(&keymap, argv[0])) {
2029                 config_msg = "Unknown key map";
2030                 return ERR;
2031         }
2033         key = get_key_value(argv[1]);
2034         if (key == ERR) {
2035                 config_msg = "Unknown key";
2036                 return ERR;
2037         }
2039         request = get_request(argv[2]);
2040         if (request == REQ_UNKNOWN) {
2041                 static const struct enum_map obsolete[] = {
2042                         ENUM_MAP("cherry-pick",         REQ_NONE),
2043                         ENUM_MAP("screen-resize",       REQ_NONE),
2044                         ENUM_MAP("tree-parent",         REQ_PARENT),
2045                 };
2046                 int alias;
2048                 if (map_enum(&alias, obsolete, argv[2])) {
2049                         if (alias != REQ_NONE)
2050                                 add_keybinding(keymap, alias, key);
2051                         config_msg = "Obsolete request name";
2052                         return ERR;
2053                 }
2054         }
2055         if (request == REQ_UNKNOWN && *argv[2]++ == '!')
2056                 request = add_run_request(keymap, key, argv + 2);
2057         if (request == REQ_UNKNOWN) {
2058                 config_msg = "Unknown request name";
2059                 return ERR;
2060         }
2062         add_keybinding(keymap, request, key);
2064         return OK;
2067 static int
2068 set_option(const char *opt, char *value)
2070         const char *argv[SIZEOF_ARG];
2071         int argc = 0;
2073         if (!argv_from_string(argv, &argc, value)) {
2074                 config_msg = "Too many option arguments";
2075                 return ERR;
2076         }
2078         if (!strcmp(opt, "color"))
2079                 return option_color_command(argc, argv);
2081         if (!strcmp(opt, "set"))
2082                 return option_set_command(argc, argv);
2084         if (!strcmp(opt, "bind"))
2085                 return option_bind_command(argc, argv);
2087         config_msg = "Unknown option command";
2088         return ERR;
2091 static int
2092 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
2094         int status = OK;
2096         config_lineno++;
2097         config_msg = "Internal error";
2099         /* Check for comment markers, since read_properties() will
2100          * only ensure opt and value are split at first " \t". */
2101         optlen = strcspn(opt, "#");
2102         if (optlen == 0)
2103                 return OK;
2105         if (opt[optlen] != 0) {
2106                 config_msg = "No option value";
2107                 status = ERR;
2109         }  else {
2110                 /* Look for comment endings in the value. */
2111                 size_t len = strcspn(value, "#");
2113                 if (len < valuelen) {
2114                         valuelen = len;
2115                         value[valuelen] = 0;
2116                 }
2118                 status = set_option(opt, value);
2119         }
2121         if (status == ERR) {
2122                 warn("Error on line %d, near '%.*s': %s",
2123                      config_lineno, (int) optlen, opt, config_msg);
2124                 config_errors = TRUE;
2125         }
2127         /* Always keep going if errors are encountered. */
2128         return OK;
2131 static void
2132 load_option_file(const char *path)
2134         struct io io;
2136         /* It's OK that the file doesn't exist. */
2137         if (!io_open(&io, "%s", path))
2138                 return;
2140         config_lineno = 0;
2141         config_errors = FALSE;
2143         if (io_load(&io, " \t", read_option) == ERR ||
2144             config_errors == TRUE)
2145                 warn("Errors while loading %s.", path);
2148 static int
2149 load_options(void)
2151         const char *home = getenv("HOME");
2152         const char *tigrc_user = getenv("TIGRC_USER");
2153         const char *tigrc_system = getenv("TIGRC_SYSTEM");
2154         const char *tig_diff_opts = getenv("TIG_DIFF_OPTS");
2155         char buf[SIZEOF_STR];
2157         if (!tigrc_system)
2158                 tigrc_system = SYSCONFDIR "/tigrc";
2159         load_option_file(tigrc_system);
2161         if (!tigrc_user) {
2162                 if (!home || !string_format(buf, "%s/.tigrc", home))
2163                         return ERR;
2164                 tigrc_user = buf;
2165         }
2166         load_option_file(tigrc_user);
2168         /* Add _after_ loading config files to avoid adding run requests
2169          * that conflict with keybindings. */
2170         add_builtin_run_requests();
2172         if (!opt_diff_args && tig_diff_opts && *tig_diff_opts) {
2173                 static const char *diff_opts[SIZEOF_ARG] = { NULL };
2174                 int argc = 0;
2176                 if (!string_format(buf, "%s", tig_diff_opts) ||
2177                     !argv_from_string(diff_opts, &argc, buf))
2178                         die("TIG_DIFF_OPTS contains too many arguments");
2179                 else if (!argv_copy(&opt_diff_args, diff_opts))
2180                         die("Failed to format TIG_DIFF_OPTS arguments");
2181         }
2183         return OK;
2187 /*
2188  * The viewer
2189  */
2191 struct view;
2192 struct view_ops;
2194 /* The display array of active views and the index of the current view. */
2195 static struct view *display[2];
2196 static unsigned int current_view;
2198 #define foreach_displayed_view(view, i) \
2199         for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
2201 #define displayed_views()       (display[1] != NULL ? 2 : 1)
2203 /* Current head and commit ID */
2204 static char ref_blob[SIZEOF_REF]        = "";
2205 static char ref_commit[SIZEOF_REF]      = "HEAD";
2206 static char ref_head[SIZEOF_REF]        = "HEAD";
2207 static char ref_branch[SIZEOF_REF]      = "";
2209 enum view_type {
2210         VIEW_MAIN,
2211         VIEW_DIFF,
2212         VIEW_LOG,
2213         VIEW_TREE,
2214         VIEW_BLOB,
2215         VIEW_BLAME,
2216         VIEW_BRANCH,
2217         VIEW_HELP,
2218         VIEW_PAGER,
2219         VIEW_STATUS,
2220         VIEW_STAGE,
2221 };
2223 struct view {
2224         enum view_type type;    /* View type */
2225         const char *name;       /* View name */
2226         const char *cmd_env;    /* Command line set via environment */
2227         const char *id;         /* Points to either of ref_{head,commit,blob} */
2229         struct view_ops *ops;   /* View operations */
2231         enum keymap keymap;     /* What keymap does this view have */
2232         bool git_dir;           /* Whether the view requires a git directory. */
2234         char ref[SIZEOF_REF];   /* Hovered commit reference */
2235         char vid[SIZEOF_REF];   /* View ID. Set to id member when updating. */
2237         int height, width;      /* The width and height of the main window */
2238         WINDOW *win;            /* The main window */
2239         WINDOW *title;          /* The title window living below the main window */
2241         /* Navigation */
2242         unsigned long offset;   /* Offset of the window top */
2243         unsigned long yoffset;  /* Offset from the window side. */
2244         unsigned long lineno;   /* Current line number */
2245         unsigned long p_offset; /* Previous offset of the window top */
2246         unsigned long p_yoffset;/* Previous offset from the window side */
2247         unsigned long p_lineno; /* Previous current line number */
2248         bool p_restore;         /* Should the previous position be restored. */
2250         /* Searching */
2251         char grep[SIZEOF_STR];  /* Search string */
2252         regex_t *regex;         /* Pre-compiled regexp */
2254         /* If non-NULL, points to the view that opened this view. If this view
2255          * is closed tig will switch back to the parent view. */
2256         struct view *parent;
2257         struct view *prev;
2259         /* Buffering */
2260         size_t lines;           /* Total number of lines */
2261         struct line *line;      /* Line index */
2262         unsigned int digits;    /* Number of digits in the lines member. */
2264         /* Drawing */
2265         struct line *curline;   /* Line currently being drawn. */
2266         enum line_type curtype; /* Attribute currently used for drawing. */
2267         unsigned long col;      /* Column when drawing. */
2268         bool has_scrolled;      /* View was scrolled. */
2270         /* Loading */
2271         const char **argv;      /* Shell command arguments. */
2272         const char *dir;        /* Directory from which to execute. */
2273         struct io io;
2274         struct io *pipe;
2275         time_t start_time;
2276         time_t update_secs;
2277 };
2279 struct view_ops {
2280         /* What type of content being displayed. Used in the title bar. */
2281         const char *type;
2282         /* Default command arguments. */
2283         const char **argv;
2284         /* Open and reads in all view content. */
2285         bool (*open)(struct view *view);
2286         /* Read one line; updates view->line. */
2287         bool (*read)(struct view *view, char *data);
2288         /* Draw one line; @lineno must be < view->height. */
2289         bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
2290         /* Depending on view handle a special requests. */
2291         enum request (*request)(struct view *view, enum request request, struct line *line);
2292         /* Search for regexp in a line. */
2293         bool (*grep)(struct view *view, struct line *line);
2294         /* Select line */
2295         void (*select)(struct view *view, struct line *line);
2296         /* Prepare view for loading */
2297         bool (*prepare)(struct view *view);
2298 };
2300 static struct view_ops blame_ops;
2301 static struct view_ops blob_ops;
2302 static struct view_ops diff_ops;
2303 static struct view_ops help_ops;
2304 static struct view_ops log_ops;
2305 static struct view_ops main_ops;
2306 static struct view_ops pager_ops;
2307 static struct view_ops stage_ops;
2308 static struct view_ops status_ops;
2309 static struct view_ops tree_ops;
2310 static struct view_ops branch_ops;
2312 #define VIEW_STR(type, name, env, ref, ops, map, git) \
2313         { type, name, #env, ref, ops, map, git }
2315 #define VIEW_(id, name, ops, git, ref) \
2316         VIEW_STR(VIEW_##id, name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
2318 static struct view views[] = {
2319         VIEW_(MAIN,   "main",   &main_ops,   TRUE,  ref_head),
2320         VIEW_(DIFF,   "diff",   &diff_ops,   TRUE,  ref_commit),
2321         VIEW_(LOG,    "log",    &log_ops,    TRUE,  ref_head),
2322         VIEW_(TREE,   "tree",   &tree_ops,   TRUE,  ref_commit),
2323         VIEW_(BLOB,   "blob",   &blob_ops,   TRUE,  ref_blob),
2324         VIEW_(BLAME,  "blame",  &blame_ops,  TRUE,  ref_commit),
2325         VIEW_(BRANCH, "branch", &branch_ops, TRUE,  ref_head),
2326         VIEW_(HELP,   "help",   &help_ops,   FALSE, ""),
2327         VIEW_(PAGER,  "pager",  &pager_ops,  FALSE, ""),
2328         VIEW_(STATUS, "status", &status_ops, TRUE,  ""),
2329         VIEW_(STAGE,  "stage",  &stage_ops,  TRUE,  ""),
2330 };
2332 #define VIEW(req)       (&views[(req) - REQ_OFFSET - 1])
2334 #define foreach_view(view, i) \
2335         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2337 #define view_is_displayed(view) \
2338         (view == display[0] || view == display[1])
2340 static enum request
2341 view_request(struct view *view, enum request request)
2343         if (!view || !view->lines)
2344                 return request;
2345         return view->ops->request(view, request, &view->line[view->lineno]);
2349 /*
2350  * View drawing.
2351  */
2353 static inline void
2354 set_view_attr(struct view *view, enum line_type type)
2356         if (!view->curline->selected && view->curtype != type) {
2357                 (void) wattrset(view->win, get_line_attr(type));
2358                 wchgat(view->win, -1, 0, type, NULL);
2359                 view->curtype = type;
2360         }
2363 static int
2364 draw_chars(struct view *view, enum line_type type, const char *string,
2365            int max_len, bool use_tilde)
2367         static char out_buffer[BUFSIZ * 2];
2368         int len = 0;
2369         int col = 0;
2370         int trimmed = FALSE;
2371         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2373         if (max_len <= 0)
2374                 return 0;
2376         len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde, opt_tab_size);
2378         set_view_attr(view, type);
2379         if (len > 0) {
2380                 if (opt_iconv_out != ICONV_NONE) {
2381                         ICONV_CONST char *inbuf = (ICONV_CONST char *) string;
2382                         size_t inlen = len + 1;
2384                         char *outbuf = out_buffer;
2385                         size_t outlen = sizeof(out_buffer);
2387                         size_t ret;
2389                         ret = iconv(opt_iconv_out, &inbuf, &inlen, &outbuf, &outlen);
2390                         if (ret != (size_t) -1) {
2391                                 string = out_buffer;
2392                                 len = sizeof(out_buffer) - outlen;
2393                         }
2394                 }
2396                 waddnstr(view->win, string, len);
2397         }
2398         if (trimmed && use_tilde) {
2399                 set_view_attr(view, LINE_DELIMITER);
2400                 waddch(view->win, '~');
2401                 col++;
2402         }
2404         return col;
2407 static int
2408 draw_space(struct view *view, enum line_type type, int max, int spaces)
2410         static char space[] = "                    ";
2411         int col = 0;
2413         spaces = MIN(max, spaces);
2415         while (spaces > 0) {
2416                 int len = MIN(spaces, sizeof(space) - 1);
2418                 col += draw_chars(view, type, space, len, FALSE);
2419                 spaces -= len;
2420         }
2422         return col;
2425 static bool
2426 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
2428         char text[SIZEOF_STR];
2430         do {
2431                 size_t pos = string_expand(text, sizeof(text), string, opt_tab_size);
2433                 view->col += draw_chars(view, type, text, view->width + view->yoffset - view->col, trim);
2434                 string += pos;
2435         } while (*string && view->width + view->yoffset > view->col);
2437         return view->width + view->yoffset <= view->col;
2440 static bool
2441 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
2443         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2444         int max = view->width + view->yoffset - view->col;
2445         int i;
2447         if (max < size)
2448                 size = max;
2450         set_view_attr(view, type);
2451         /* Using waddch() instead of waddnstr() ensures that
2452          * they'll be rendered correctly for the cursor line. */
2453         for (i = skip; i < size; i++)
2454                 waddch(view->win, graphic[i]);
2456         view->col += size;
2457         if (size < max && skip <= size)
2458                 waddch(view->win, ' ');
2459         view->col++;
2461         return view->width + view->yoffset <= view->col;
2464 static bool
2465 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
2467         int max = MIN(view->width + view->yoffset - view->col, len);
2468         int col;
2470         if (text)
2471                 col = draw_chars(view, type, text, max - 1, trim);
2472         else
2473                 col = draw_space(view, type, max - 1, max - 1);
2475         view->col += col;
2476         view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
2477         return view->width + view->yoffset <= view->col;
2480 static bool
2481 draw_date(struct view *view, struct time *time)
2483         const char *date = mkdate(time, opt_date);
2484         int cols = opt_date == DATE_SHORT ? DATE_SHORT_COLS : DATE_COLS;
2486         return draw_field(view, LINE_DATE, date, cols, FALSE);
2489 static bool
2490 draw_author(struct view *view, const char *author)
2492         bool trim = opt_author_cols == 0 || opt_author_cols > 5;
2493         bool abbreviate = opt_author == AUTHOR_ABBREVIATED || !trim;
2495         if (abbreviate && author)
2496                 author = get_author_initials(author);
2498         return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2501 static bool
2502 draw_mode(struct view *view, mode_t mode)
2504         const char *str;
2506         if (S_ISDIR(mode))
2507                 str = "drwxr-xr-x";
2508         else if (S_ISLNK(mode))
2509                 str = "lrwxrwxrwx";
2510         else if (S_ISGITLINK(mode))
2511                 str = "m---------";
2512         else if (S_ISREG(mode) && mode & S_IXUSR)
2513                 str = "-rwxr-xr-x";
2514         else if (S_ISREG(mode))
2515                 str = "-rw-r--r--";
2516         else
2517                 str = "----------";
2519         return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2522 static bool
2523 draw_lineno(struct view *view, unsigned int lineno)
2525         char number[10];
2526         int digits3 = view->digits < 3 ? 3 : view->digits;
2527         int max = MIN(view->width + view->yoffset - view->col, digits3);
2528         char *text = NULL;
2529         chtype separator = opt_line_graphics ? ACS_VLINE : '|';
2531         lineno += view->offset + 1;
2532         if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2533                 static char fmt[] = "%1ld";
2535                 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2536                 if (string_format(number, fmt, lineno))
2537                         text = number;
2538         }
2539         if (text)
2540                 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2541         else
2542                 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2543         return draw_graphic(view, LINE_DEFAULT, &separator, 1);
2546 static bool
2547 draw_view_line(struct view *view, unsigned int lineno)
2549         struct line *line;
2550         bool selected = (view->offset + lineno == view->lineno);
2552         assert(view_is_displayed(view));
2554         if (view->offset + lineno >= view->lines)
2555                 return FALSE;
2557         line = &view->line[view->offset + lineno];
2559         wmove(view->win, lineno, 0);
2560         if (line->cleareol)
2561                 wclrtoeol(view->win);
2562         view->col = 0;
2563         view->curline = line;
2564         view->curtype = LINE_NONE;
2565         line->selected = FALSE;
2566         line->dirty = line->cleareol = 0;
2568         if (selected) {
2569                 set_view_attr(view, LINE_CURSOR);
2570                 line->selected = TRUE;
2571                 view->ops->select(view, line);
2572         }
2574         return view->ops->draw(view, line, lineno);
2577 static void
2578 redraw_view_dirty(struct view *view)
2580         bool dirty = FALSE;
2581         int lineno;
2583         for (lineno = 0; lineno < view->height; lineno++) {
2584                 if (view->offset + lineno >= view->lines)
2585                         break;
2586                 if (!view->line[view->offset + lineno].dirty)
2587                         continue;
2588                 dirty = TRUE;
2589                 if (!draw_view_line(view, lineno))
2590                         break;
2591         }
2593         if (!dirty)
2594                 return;
2595         wnoutrefresh(view->win);
2598 static void
2599 redraw_view_from(struct view *view, int lineno)
2601         assert(0 <= lineno && lineno < view->height);
2603         for (; lineno < view->height; lineno++) {
2604                 if (!draw_view_line(view, lineno))
2605                         break;
2606         }
2608         wnoutrefresh(view->win);
2611 static void
2612 redraw_view(struct view *view)
2614         werase(view->win);
2615         redraw_view_from(view, 0);
2619 static void
2620 update_view_title(struct view *view)
2622         char buf[SIZEOF_STR];
2623         char state[SIZEOF_STR];
2624         size_t bufpos = 0, statelen = 0;
2626         assert(view_is_displayed(view));
2628         if (view->type != VIEW_STATUS && view->lines) {
2629                 unsigned int view_lines = view->offset + view->height;
2630                 unsigned int lines = view->lines
2631                                    ? MIN(view_lines, view->lines) * 100 / view->lines
2632                                    : 0;
2634                 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2635                                    view->ops->type,
2636                                    view->lineno + 1,
2637                                    view->lines,
2638                                    lines);
2640         }
2642         if (view->pipe) {
2643                 time_t secs = time(NULL) - view->start_time;
2645                 /* Three git seconds are a long time ... */
2646                 if (secs > 2)
2647                         string_format_from(state, &statelen, " loading %lds", secs);
2648         }
2650         string_format_from(buf, &bufpos, "[%s]", view->name);
2651         if (*view->ref && bufpos < view->width) {
2652                 size_t refsize = strlen(view->ref);
2653                 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2655                 if (minsize < view->width)
2656                         refsize = view->width - minsize + 7;
2657                 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2658         }
2660         if (statelen && bufpos < view->width) {
2661                 string_format_from(buf, &bufpos, "%s", state);
2662         }
2664         if (view == display[current_view])
2665                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2666         else
2667                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2669         mvwaddnstr(view->title, 0, 0, buf, bufpos);
2670         wclrtoeol(view->title);
2671         wnoutrefresh(view->title);
2674 static int
2675 apply_step(double step, int value)
2677         if (step >= 1)
2678                 return (int) step;
2679         value *= step + 0.01;
2680         return value ? value : 1;
2683 static void
2684 resize_display(void)
2686         int offset, i;
2687         struct view *base = display[0];
2688         struct view *view = display[1] ? display[1] : display[0];
2690         /* Setup window dimensions */
2692         getmaxyx(stdscr, base->height, base->width);
2694         /* Make room for the status window. */
2695         base->height -= 1;
2697         if (view != base) {
2698                 /* Horizontal split. */
2699                 view->width   = base->width;
2700                 view->height  = apply_step(opt_scale_split_view, base->height);
2701                 view->height  = MAX(view->height, MIN_VIEW_HEIGHT);
2702                 view->height  = MIN(view->height, base->height - MIN_VIEW_HEIGHT);
2703                 base->height -= view->height;
2705                 /* Make room for the title bar. */
2706                 view->height -= 1;
2707         }
2709         /* Make room for the title bar. */
2710         base->height -= 1;
2712         offset = 0;
2714         foreach_displayed_view (view, i) {
2715                 if (!view->win) {
2716                         view->win = newwin(view->height, 0, offset, 0);
2717                         if (!view->win)
2718                                 die("Failed to create %s view", view->name);
2720                         scrollok(view->win, FALSE);
2722                         view->title = newwin(1, 0, offset + view->height, 0);
2723                         if (!view->title)
2724                                 die("Failed to create title window");
2726                 } else {
2727                         wresize(view->win, view->height, view->width);
2728                         mvwin(view->win,   offset, 0);
2729                         mvwin(view->title, offset + view->height, 0);
2730                 }
2732                 offset += view->height + 1;
2733         }
2736 static void
2737 redraw_display(bool clear)
2739         struct view *view;
2740         int i;
2742         foreach_displayed_view (view, i) {
2743                 if (clear)
2744                         wclear(view->win);
2745                 redraw_view(view);
2746                 update_view_title(view);
2747         }
2751 /*
2752  * Option management
2753  */
2755 static void
2756 toggle_enum_option_do(unsigned int *opt, const char *help,
2757                       const struct enum_map *map, size_t size)
2759         *opt = (*opt + 1) % size;
2760         redraw_display(FALSE);
2761         report("Displaying %s %s", enum_name(map[*opt]), help);
2764 #define toggle_enum_option(opt, help, map) \
2765         toggle_enum_option_do(opt, help, map, ARRAY_SIZE(map))
2767 #define toggle_date() toggle_enum_option(&opt_date, "dates", date_map)
2768 #define toggle_author() toggle_enum_option(&opt_author, "author names", author_map)
2770 static void
2771 toggle_view_option(bool *option, const char *help)
2773         *option = !*option;
2774         redraw_display(FALSE);
2775         report("%sabling %s", *option ? "En" : "Dis", help);
2778 static void
2779 open_option_menu(void)
2781         const struct menu_item menu[] = {
2782                 { '.', "line numbers", &opt_line_number },
2783                 { 'D', "date display", &opt_date },
2784                 { 'A', "author display", &opt_author },
2785                 { 'g', "revision graph display", &opt_rev_graph },
2786                 { 'F', "reference display", &opt_show_refs },
2787                 { 0 }
2788         };
2789         int selected = 0;
2791         if (prompt_menu("Toggle option", menu, &selected)) {
2792                 if (menu[selected].data == &opt_date)
2793                         toggle_date();
2794                 else if (menu[selected].data == &opt_author)
2795                         toggle_author();
2796                 else
2797                         toggle_view_option(menu[selected].data, menu[selected].text);
2798         }
2801 static void
2802 maximize_view(struct view *view)
2804         memset(display, 0, sizeof(display));
2805         current_view = 0;
2806         display[current_view] = view;
2807         resize_display();
2808         redraw_display(FALSE);
2809         report("");
2813 /*
2814  * Navigation
2815  */
2817 static bool
2818 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2820         if (lineno >= view->lines)
2821                 lineno = view->lines > 0 ? view->lines - 1 : 0;
2823         if (offset > lineno || offset + view->height <= lineno) {
2824                 unsigned long half = view->height / 2;
2826                 if (lineno > half)
2827                         offset = lineno - half;
2828                 else
2829                         offset = 0;
2830         }
2832         if (offset != view->offset || lineno != view->lineno) {
2833                 view->offset = offset;
2834                 view->lineno = lineno;
2835                 return TRUE;
2836         }
2838         return FALSE;
2841 /* Scrolling backend */
2842 static void
2843 do_scroll_view(struct view *view, int lines)
2845         bool redraw_current_line = FALSE;
2847         /* The rendering expects the new offset. */
2848         view->offset += lines;
2850         assert(0 <= view->offset && view->offset < view->lines);
2851         assert(lines);
2853         /* Move current line into the view. */
2854         if (view->lineno < view->offset) {
2855                 view->lineno = view->offset;
2856                 redraw_current_line = TRUE;
2857         } else if (view->lineno >= view->offset + view->height) {
2858                 view->lineno = view->offset + view->height - 1;
2859                 redraw_current_line = TRUE;
2860         }
2862         assert(view->offset <= view->lineno && view->lineno < view->lines);
2864         /* Redraw the whole screen if scrolling is pointless. */
2865         if (view->height < ABS(lines)) {
2866                 redraw_view(view);
2868         } else {
2869                 int line = lines > 0 ? view->height - lines : 0;
2870                 int end = line + ABS(lines);
2872                 scrollok(view->win, TRUE);
2873                 wscrl(view->win, lines);
2874                 scrollok(view->win, FALSE);
2876                 while (line < end && draw_view_line(view, line))
2877                         line++;
2879                 if (redraw_current_line)
2880                         draw_view_line(view, view->lineno - view->offset);
2881                 wnoutrefresh(view->win);
2882         }
2884         view->has_scrolled = TRUE;
2885         report("");
2888 /* Scroll frontend */
2889 static void
2890 scroll_view(struct view *view, enum request request)
2892         int lines = 1;
2894         assert(view_is_displayed(view));
2896         switch (request) {
2897         case REQ_SCROLL_LEFT:
2898                 if (view->yoffset == 0) {
2899                         report("Cannot scroll beyond the first column");
2900                         return;
2901                 }
2902                 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2903                         view->yoffset = 0;
2904                 else
2905                         view->yoffset -= apply_step(opt_hscroll, view->width);
2906                 redraw_view_from(view, 0);
2907                 report("");
2908                 return;
2909         case REQ_SCROLL_RIGHT:
2910                 view->yoffset += apply_step(opt_hscroll, view->width);
2911                 redraw_view(view);
2912                 report("");
2913                 return;
2914         case REQ_SCROLL_PAGE_DOWN:
2915                 lines = view->height;
2916         case REQ_SCROLL_LINE_DOWN:
2917                 if (view->offset + lines > view->lines)
2918                         lines = view->lines - view->offset;
2920                 if (lines == 0 || view->offset + view->height >= view->lines) {
2921                         report("Cannot scroll beyond the last line");
2922                         return;
2923                 }
2924                 break;
2926         case REQ_SCROLL_PAGE_UP:
2927                 lines = view->height;
2928         case REQ_SCROLL_LINE_UP:
2929                 if (lines > view->offset)
2930                         lines = view->offset;
2932                 if (lines == 0) {
2933                         report("Cannot scroll beyond the first line");
2934                         return;
2935                 }
2937                 lines = -lines;
2938                 break;
2940         default:
2941                 die("request %d not handled in switch", request);
2942         }
2944         do_scroll_view(view, lines);
2947 /* Cursor moving */
2948 static void
2949 move_view(struct view *view, enum request request)
2951         int scroll_steps = 0;
2952         int steps;
2954         switch (request) {
2955         case REQ_MOVE_FIRST_LINE:
2956                 steps = -view->lineno;
2957                 break;
2959         case REQ_MOVE_LAST_LINE:
2960                 steps = view->lines - view->lineno - 1;
2961                 break;
2963         case REQ_MOVE_PAGE_UP:
2964                 steps = view->height > view->lineno
2965                       ? -view->lineno : -view->height;
2966                 break;
2968         case REQ_MOVE_PAGE_DOWN:
2969                 steps = view->lineno + view->height >= view->lines
2970                       ? view->lines - view->lineno - 1 : view->height;
2971                 break;
2973         case REQ_MOVE_UP:
2974                 steps = -1;
2975                 break;
2977         case REQ_MOVE_DOWN:
2978                 steps = 1;
2979                 break;
2981         default:
2982                 die("request %d not handled in switch", request);
2983         }
2985         if (steps <= 0 && view->lineno == 0) {
2986                 report("Cannot move beyond the first line");
2987                 return;
2989         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2990                 report("Cannot move beyond the last line");
2991                 return;
2992         }
2994         /* Move the current line */
2995         view->lineno += steps;
2996         assert(0 <= view->lineno && view->lineno < view->lines);
2998         /* Check whether the view needs to be scrolled */
2999         if (view->lineno < view->offset ||
3000             view->lineno >= view->offset + view->height) {
3001                 scroll_steps = steps;
3002                 if (steps < 0 && -steps > view->offset) {
3003                         scroll_steps = -view->offset;
3005                 } else if (steps > 0) {
3006                         if (view->lineno == view->lines - 1 &&
3007                             view->lines > view->height) {
3008                                 scroll_steps = view->lines - view->offset - 1;
3009                                 if (scroll_steps >= view->height)
3010                                         scroll_steps -= view->height - 1;
3011                         }
3012                 }
3013         }
3015         if (!view_is_displayed(view)) {
3016                 view->offset += scroll_steps;
3017                 assert(0 <= view->offset && view->offset < view->lines);
3018                 view->ops->select(view, &view->line[view->lineno]);
3019                 return;
3020         }
3022         /* Repaint the old "current" line if we be scrolling */
3023         if (ABS(steps) < view->height)
3024                 draw_view_line(view, view->lineno - steps - view->offset);
3026         if (scroll_steps) {
3027                 do_scroll_view(view, scroll_steps);
3028                 return;
3029         }
3031         /* Draw the current line */
3032         draw_view_line(view, view->lineno - view->offset);
3034         wnoutrefresh(view->win);
3035         report("");
3039 /*
3040  * Searching
3041  */
3043 static void search_view(struct view *view, enum request request);
3045 static bool
3046 grep_text(struct view *view, const char *text[])
3048         regmatch_t pmatch;
3049         size_t i;
3051         for (i = 0; text[i]; i++)
3052                 if (*text[i] &&
3053                     regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
3054                         return TRUE;
3055         return FALSE;
3058 static void
3059 select_view_line(struct view *view, unsigned long lineno)
3061         unsigned long old_lineno = view->lineno;
3062         unsigned long old_offset = view->offset;
3064         if (goto_view_line(view, view->offset, lineno)) {
3065                 if (view_is_displayed(view)) {
3066                         if (old_offset != view->offset) {
3067                                 redraw_view(view);
3068                         } else {
3069                                 draw_view_line(view, old_lineno - view->offset);
3070                                 draw_view_line(view, view->lineno - view->offset);
3071                                 wnoutrefresh(view->win);
3072                         }
3073                 } else {
3074                         view->ops->select(view, &view->line[view->lineno]);
3075                 }
3076         }
3079 static void
3080 find_next(struct view *view, enum request request)
3082         unsigned long lineno = view->lineno;
3083         int direction;
3085         if (!*view->grep) {
3086                 if (!*opt_search)
3087                         report("No previous search");
3088                 else
3089                         search_view(view, request);
3090                 return;
3091         }
3093         switch (request) {
3094         case REQ_SEARCH:
3095         case REQ_FIND_NEXT:
3096                 direction = 1;
3097                 break;
3099         case REQ_SEARCH_BACK:
3100         case REQ_FIND_PREV:
3101                 direction = -1;
3102                 break;
3104         default:
3105                 return;
3106         }
3108         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
3109                 lineno += direction;
3111         /* Note, lineno is unsigned long so will wrap around in which case it
3112          * will become bigger than view->lines. */
3113         for (; lineno < view->lines; lineno += direction) {
3114                 if (view->ops->grep(view, &view->line[lineno])) {
3115                         select_view_line(view, lineno);
3116                         report("Line %ld matches '%s'", lineno + 1, view->grep);
3117                         return;
3118                 }
3119         }
3121         report("No match found for '%s'", view->grep);
3124 static void
3125 search_view(struct view *view, enum request request)
3127         int regex_err;
3129         if (view->regex) {
3130                 regfree(view->regex);
3131                 *view->grep = 0;
3132         } else {
3133                 view->regex = calloc(1, sizeof(*view->regex));
3134                 if (!view->regex)
3135                         return;
3136         }
3138         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
3139         if (regex_err != 0) {
3140                 char buf[SIZEOF_STR] = "unknown error";
3142                 regerror(regex_err, view->regex, buf, sizeof(buf));
3143                 report("Search failed: %s", buf);
3144                 return;
3145         }
3147         string_copy(view->grep, opt_search);
3149         find_next(view, request);
3152 /*
3153  * Incremental updating
3154  */
3156 static void
3157 reset_view(struct view *view)
3159         int i;
3161         for (i = 0; i < view->lines; i++)
3162                 free(view->line[i].data);
3163         free(view->line);
3165         view->p_offset = view->offset;
3166         view->p_yoffset = view->yoffset;
3167         view->p_lineno = view->lineno;
3169         view->line = NULL;
3170         view->offset = 0;
3171         view->yoffset = 0;
3172         view->lines  = 0;
3173         view->lineno = 0;
3174         view->vid[0] = 0;
3175         view->update_secs = 0;
3178 static const char *
3179 format_arg(const char *name)
3181         static struct {
3182                 const char *name;
3183                 size_t namelen;
3184                 const char *value;
3185                 const char *value_if_empty;
3186         } vars[] = {
3187 #define FORMAT_VAR(name, value, value_if_empty) \
3188         { name, STRING_SIZE(name), value, value_if_empty }
3189                 FORMAT_VAR("%(directory)",      opt_path,       ""),
3190                 FORMAT_VAR("%(file)",           opt_file,       ""),
3191                 FORMAT_VAR("%(ref)",            opt_ref,        "HEAD"),
3192                 FORMAT_VAR("%(head)",           ref_head,       ""),
3193                 FORMAT_VAR("%(commit)",         ref_commit,     ""),
3194                 FORMAT_VAR("%(blob)",           ref_blob,       ""),
3195                 FORMAT_VAR("%(branch)",         ref_branch,     ""),
3196         };
3197         int i;
3199         for (i = 0; i < ARRAY_SIZE(vars); i++)
3200                 if (!strncmp(name, vars[i].name, vars[i].namelen))
3201                         return *vars[i].value ? vars[i].value : vars[i].value_if_empty;
3203         report("Unknown replacement: `%s`", name);
3204         return NULL;
3207 static bool
3208 format_argv(const char ***dst_argv, const char *src_argv[], bool replace)
3210         char buf[SIZEOF_STR];
3211         int argc;
3213         argv_free(*dst_argv);
3215         for (argc = 0; src_argv[argc]; argc++) {
3216                 const char *arg = src_argv[argc];
3217                 size_t bufpos = 0;
3219                 if (!strcmp(arg, "%(fileargs)")) {
3220                         if (!argv_append_array(dst_argv, opt_file_args))
3221                                 break;
3222                         continue;
3224                 } else if (!strcmp(arg, "%(diffargs)")) {
3225                         if (!argv_append_array(dst_argv, opt_diff_args))
3226                                 break;
3227                         continue;
3229                 } else if (!strcmp(arg, "%(revargs)")) {
3230                         if (!argv_append_array(dst_argv, opt_rev_args))
3231                                 break;
3232                         continue;
3233                 }
3235                 while (arg) {
3236                         char *next = strstr(arg, "%(");
3237                         int len = next - arg;
3238                         const char *value;
3240                         if (!next || !replace) {
3241                                 len = strlen(arg);
3242                                 value = "";
3244                         } else {
3245                                 value = format_arg(next);
3247                                 if (!value) {
3248                                         return FALSE;
3249                                 }
3250                         }
3252                         if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
3253                                 return FALSE;
3255                         arg = next && replace ? strchr(next, ')') + 1 : NULL;
3256                 }
3258                 if (!argv_append(dst_argv, buf))
3259                         break;
3260         }
3262         return src_argv[argc] == NULL;
3265 static bool
3266 restore_view_position(struct view *view)
3268         if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
3269                 return FALSE;
3271         /* Changing the view position cancels the restoring. */
3272         /* FIXME: Changing back to the first line is not detected. */
3273         if (view->offset != 0 || view->lineno != 0) {
3274                 view->p_restore = FALSE;
3275                 return FALSE;
3276         }
3278         if (goto_view_line(view, view->p_offset, view->p_lineno) &&
3279             view_is_displayed(view))
3280                 werase(view->win);
3282         view->yoffset = view->p_yoffset;
3283         view->p_restore = FALSE;
3285         return TRUE;
3288 static void
3289 end_update(struct view *view, bool force)
3291         if (!view->pipe)
3292                 return;
3293         while (!view->ops->read(view, NULL))
3294                 if (!force)
3295                         return;
3296         if (force)
3297                 io_kill(view->pipe);
3298         io_done(view->pipe);
3299         view->pipe = NULL;
3302 static void
3303 setup_update(struct view *view, const char *vid)
3305         reset_view(view);
3306         string_copy_rev(view->vid, vid);
3307         view->pipe = &view->io;
3308         view->start_time = time(NULL);
3311 static bool
3312 prepare_io(struct view *view, const char *dir, const char *argv[], bool replace)
3314         view->dir = dir;
3315         return format_argv(&view->argv, argv, replace);
3318 static bool
3319 prepare_update(struct view *view, const char *argv[], const char *dir)
3321         if (view->pipe)
3322                 end_update(view, TRUE);
3323         return prepare_io(view, dir, argv, FALSE);
3326 static bool
3327 start_update(struct view *view, const char **argv, const char *dir)
3329         if (view->pipe)
3330                 io_done(view->pipe);
3331         return prepare_io(view, dir, argv, FALSE) &&
3332                io_run(&view->io, IO_RD, dir, view->argv);
3335 static bool
3336 prepare_update_file(struct view *view, const char *name)
3338         if (view->pipe)
3339                 end_update(view, TRUE);
3340         argv_free(view->argv);
3341         return io_open(&view->io, "%s/%s", opt_cdup[0] ? opt_cdup : ".", name);
3344 static bool
3345 begin_update(struct view *view, bool refresh)
3347         if (view->pipe)
3348                 end_update(view, TRUE);
3350         if (!refresh) {
3351                 if (view->ops->prepare) {
3352                         if (!view->ops->prepare(view))
3353                                 return FALSE;
3354                 } else if (!prepare_io(view, NULL, view->ops->argv, TRUE)) {
3355                         return FALSE;
3356                 }
3358                 /* Put the current ref_* value to the view title ref
3359                  * member. This is needed by the blob view. Most other
3360                  * views sets it automatically after loading because the
3361                  * first line is a commit line. */
3362                 string_copy_rev(view->ref, view->id);
3363         }
3365         if (view->argv && view->argv[0] &&
3366             !io_run(&view->io, IO_RD, view->dir, view->argv))
3367                 return FALSE;
3369         setup_update(view, view->id);
3371         return TRUE;
3374 static bool
3375 update_view(struct view *view)
3377         char out_buffer[BUFSIZ * 2];
3378         char *line;
3379         /* Clear the view and redraw everything since the tree sorting
3380          * might have rearranged things. */
3381         bool redraw = view->lines == 0;
3382         bool can_read = TRUE;
3384         if (!view->pipe)
3385                 return TRUE;
3387         if (!io_can_read(view->pipe)) {
3388                 if (view->lines == 0 && view_is_displayed(view)) {
3389                         time_t secs = time(NULL) - view->start_time;
3391                         if (secs > 1 && secs > view->update_secs) {
3392                                 if (view->update_secs == 0)
3393                                         redraw_view(view);
3394                                 update_view_title(view);
3395                                 view->update_secs = secs;
3396                         }
3397                 }
3398                 return TRUE;
3399         }
3401         for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
3402                 if (opt_iconv_in != ICONV_NONE) {
3403                         ICONV_CONST char *inbuf = line;
3404                         size_t inlen = strlen(line) + 1;
3406                         char *outbuf = out_buffer;
3407                         size_t outlen = sizeof(out_buffer);
3409                         size_t ret;
3411                         ret = iconv(opt_iconv_in, &inbuf, &inlen, &outbuf, &outlen);
3412                         if (ret != (size_t) -1)
3413                                 line = out_buffer;
3414                 }
3416                 if (!view->ops->read(view, line)) {
3417                         report("Allocation failure");
3418                         end_update(view, TRUE);
3419                         return FALSE;
3420                 }
3421         }
3423         {
3424                 unsigned long lines = view->lines;
3425                 int digits;
3427                 for (digits = 0; lines; digits++)
3428                         lines /= 10;
3430                 /* Keep the displayed view in sync with line number scaling. */
3431                 if (digits != view->digits) {
3432                         view->digits = digits;
3433                         if (opt_line_number || view->type == VIEW_BLAME)
3434                                 redraw = TRUE;
3435                 }
3436         }
3438         if (io_error(view->pipe)) {
3439                 report("Failed to read: %s", io_strerror(view->pipe));
3440                 end_update(view, TRUE);
3442         } else if (io_eof(view->pipe)) {
3443                 if (view_is_displayed(view))
3444                         report("");
3445                 end_update(view, FALSE);
3446         }
3448         if (restore_view_position(view))
3449                 redraw = TRUE;
3451         if (!view_is_displayed(view))
3452                 return TRUE;
3454         if (redraw)
3455                 redraw_view_from(view, 0);
3456         else
3457                 redraw_view_dirty(view);
3459         /* Update the title _after_ the redraw so that if the redraw picks up a
3460          * commit reference in view->ref it'll be available here. */
3461         update_view_title(view);
3462         return TRUE;
3465 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
3467 static struct line *
3468 add_line_data(struct view *view, void *data, enum line_type type)
3470         struct line *line;
3472         if (!realloc_lines(&view->line, view->lines, 1))
3473                 return NULL;
3475         line = &view->line[view->lines++];
3476         memset(line, 0, sizeof(*line));
3477         line->type = type;
3478         line->data = data;
3479         line->dirty = 1;
3481         return line;
3484 static struct line *
3485 add_line_text(struct view *view, const char *text, enum line_type type)
3487         char *data = text ? strdup(text) : NULL;
3489         return data ? add_line_data(view, data, type) : NULL;
3492 static struct line *
3493 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
3495         char buf[SIZEOF_STR];
3496         va_list args;
3498         va_start(args, fmt);
3499         if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
3500                 buf[0] = 0;
3501         va_end(args);
3503         return buf[0] ? add_line_text(view, buf, type) : NULL;
3506 /*
3507  * View opening
3508  */
3510 enum open_flags {
3511         OPEN_DEFAULT = 0,       /* Use default view switching. */
3512         OPEN_SPLIT = 1,         /* Split current view. */
3513         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
3514         OPEN_REFRESH = 16,      /* Refresh view using previous command. */
3515         OPEN_PREPARED = 32,     /* Open already prepared command. */
3516 };
3518 static void
3519 open_view(struct view *prev, enum request request, enum open_flags flags)
3521         bool split = !!(flags & OPEN_SPLIT);
3522         bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
3523         bool nomaximize = !!(flags & OPEN_REFRESH);
3524         struct view *view = VIEW(request);
3525         int nviews = displayed_views();
3526         struct view *base_view = display[0];
3528         if (view == prev && nviews == 1 && !reload) {
3529                 report("Already in %s view", view->name);
3530                 return;
3531         }
3533         if (view->git_dir && !opt_git_dir[0]) {
3534                 report("The %s view is disabled in pager view", view->name);
3535                 return;
3536         }
3538         if (split) {
3539                 display[1] = view;
3540                 current_view = 1;
3541                 view->parent = prev;
3542         } else if (!nomaximize) {
3543                 /* Maximize the current view. */
3544                 memset(display, 0, sizeof(display));
3545                 current_view = 0;
3546                 display[current_view] = view;
3547         }
3549         /* No prev signals that this is the first loaded view. */
3550         if (prev && view != prev) {
3551                 view->prev = prev;
3552         }
3554         /* Resize the view when switching between split- and full-screen,
3555          * or when switching between two different full-screen views. */
3556         if (nviews != displayed_views() ||
3557             (nviews == 1 && base_view != display[0]))
3558                 resize_display();
3560         if (view->ops->open) {
3561                 if (view->pipe)
3562                         end_update(view, TRUE);
3563                 if (!view->ops->open(view)) {
3564                         report("Failed to load %s view", view->name);
3565                         return;
3566                 }
3567                 restore_view_position(view);
3569         } else if ((reload || strcmp(view->vid, view->id)) &&
3570                    !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3571                 report("Failed to load %s view", view->name);
3572                 return;
3573         }
3575         if (split && prev->lineno - prev->offset >= prev->height) {
3576                 /* Take the title line into account. */
3577                 int lines = prev->lineno - prev->offset - prev->height + 1;
3579                 /* Scroll the view that was split if the current line is
3580                  * outside the new limited view. */
3581                 do_scroll_view(prev, lines);
3582         }
3584         if (prev && view != prev && split && view_is_displayed(prev)) {
3585                 /* "Blur" the previous view. */
3586                 update_view_title(prev);
3587         }
3589         if (view->pipe && view->lines == 0) {
3590                 /* Clear the old view and let the incremental updating refill
3591                  * the screen. */
3592                 werase(view->win);
3593                 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3594                 report("");
3595         } else if (view_is_displayed(view)) {
3596                 redraw_view(view);
3597                 report("");
3598         }
3601 static void
3602 open_external_viewer(const char *argv[], const char *dir)
3604         def_prog_mode();           /* save current tty modes */
3605         endwin();                  /* restore original tty modes */
3606         io_run_fg(argv, dir);
3607         fprintf(stderr, "Press Enter to continue");
3608         getc(opt_tty);
3609         reset_prog_mode();
3610         redraw_display(TRUE);
3613 static void
3614 open_mergetool(const char *file)
3616         const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3618         open_external_viewer(mergetool_argv, opt_cdup);
3621 static void
3622 open_editor(const char *file)
3624         const char *editor_argv[] = { "vi", file, NULL };
3625         const char *editor;
3627         editor = getenv("GIT_EDITOR");
3628         if (!editor && *opt_editor)
3629                 editor = opt_editor;
3630         if (!editor)
3631                 editor = getenv("VISUAL");
3632         if (!editor)
3633                 editor = getenv("EDITOR");
3634         if (!editor)
3635                 editor = "vi";
3637         editor_argv[0] = editor;
3638         open_external_viewer(editor_argv, opt_cdup);
3641 static void
3642 open_run_request(enum request request)
3644         struct run_request *req = get_run_request(request);
3645         const char **argv = NULL;
3647         if (!req) {
3648                 report("Unknown run request");
3649                 return;
3650         }
3652         if (format_argv(&argv, req->argv, TRUE))
3653                 open_external_viewer(argv, NULL);
3654         if (argv)
3655                 argv_free(argv);
3656         free(argv);
3659 /*
3660  * User request switch noodle
3661  */
3663 static int
3664 view_driver(struct view *view, enum request request)
3666         int i;
3668         if (request == REQ_NONE)
3669                 return TRUE;
3671         if (request > REQ_NONE) {
3672                 open_run_request(request);
3673                 view_request(view, REQ_REFRESH);
3674                 return TRUE;
3675         }
3677         request = view_request(view, request);
3678         if (request == REQ_NONE)
3679                 return TRUE;
3681         switch (request) {
3682         case REQ_MOVE_UP:
3683         case REQ_MOVE_DOWN:
3684         case REQ_MOVE_PAGE_UP:
3685         case REQ_MOVE_PAGE_DOWN:
3686         case REQ_MOVE_FIRST_LINE:
3687         case REQ_MOVE_LAST_LINE:
3688                 move_view(view, request);
3689                 break;
3691         case REQ_SCROLL_LEFT:
3692         case REQ_SCROLL_RIGHT:
3693         case REQ_SCROLL_LINE_DOWN:
3694         case REQ_SCROLL_LINE_UP:
3695         case REQ_SCROLL_PAGE_DOWN:
3696         case REQ_SCROLL_PAGE_UP:
3697                 scroll_view(view, request);
3698                 break;
3700         case REQ_VIEW_BLAME:
3701                 if (!opt_file[0]) {
3702                         report("No file chosen, press %s to open tree view",
3703                                get_key(view->keymap, REQ_VIEW_TREE));
3704                         break;
3705                 }
3706                 open_view(view, request, OPEN_DEFAULT);
3707                 break;
3709         case REQ_VIEW_BLOB:
3710                 if (!ref_blob[0]) {
3711                         report("No file chosen, press %s to open tree view",
3712                                get_key(view->keymap, REQ_VIEW_TREE));
3713                         break;
3714                 }
3715                 open_view(view, request, OPEN_DEFAULT);
3716                 break;
3718         case REQ_VIEW_PAGER:
3719                 if (view == NULL) {
3720                         if (!io_open(&VIEW(REQ_VIEW_PAGER)->io, ""))
3721                                 die("Failed to open stdin");
3722                         open_view(view, request, OPEN_PREPARED);
3723                         break;
3724                 }
3726                 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3727                         report("No pager content, press %s to run command from prompt",
3728                                get_key(view->keymap, REQ_PROMPT));
3729                         break;
3730                 }
3731                 open_view(view, request, OPEN_DEFAULT);
3732                 break;
3734         case REQ_VIEW_STAGE:
3735                 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3736                         report("No stage content, press %s to open the status view and choose file",
3737                                get_key(view->keymap, REQ_VIEW_STATUS));
3738                         break;
3739                 }
3740                 open_view(view, request, OPEN_DEFAULT);
3741                 break;
3743         case REQ_VIEW_STATUS:
3744                 if (opt_is_inside_work_tree == FALSE) {
3745                         report("The status view requires a working tree");
3746                         break;
3747                 }
3748                 open_view(view, request, OPEN_DEFAULT);
3749                 break;
3751         case REQ_VIEW_MAIN:
3752         case REQ_VIEW_DIFF:
3753         case REQ_VIEW_LOG:
3754         case REQ_VIEW_TREE:
3755         case REQ_VIEW_HELP:
3756         case REQ_VIEW_BRANCH:
3757                 open_view(view, request, OPEN_DEFAULT);
3758                 break;
3760         case REQ_NEXT:
3761         case REQ_PREVIOUS:
3762                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3764                 if (view->parent) {
3765                         int line;
3767                         view = view->parent;
3768                         line = view->lineno;
3769                         move_view(view, request);
3770                         if (view_is_displayed(view))
3771                                 update_view_title(view);
3772                         if (line != view->lineno)
3773                                 view_request(view, REQ_ENTER);
3774                 } else {
3775                         move_view(view, request);
3776                 }
3777                 break;
3779         case REQ_VIEW_NEXT:
3780         {
3781                 int nviews = displayed_views();
3782                 int next_view = (current_view + 1) % nviews;
3784                 if (next_view == current_view) {
3785                         report("Only one view is displayed");
3786                         break;
3787                 }
3789                 current_view = next_view;
3790                 /* Blur out the title of the previous view. */
3791                 update_view_title(view);
3792                 report("");
3793                 break;
3794         }
3795         case REQ_REFRESH:
3796                 report("Refreshing is not yet supported for the %s view", view->name);
3797                 break;
3799         case REQ_MAXIMIZE:
3800                 if (displayed_views() == 2)
3801                         maximize_view(view);
3802                 break;
3804         case REQ_OPTIONS:
3805                 open_option_menu();
3806                 break;
3808         case REQ_TOGGLE_LINENO:
3809                 toggle_view_option(&opt_line_number, "line numbers");
3810                 break;
3812         case REQ_TOGGLE_DATE:
3813                 toggle_date();
3814                 break;
3816         case REQ_TOGGLE_AUTHOR:
3817                 toggle_author();
3818                 break;
3820         case REQ_TOGGLE_REV_GRAPH:
3821                 toggle_view_option(&opt_rev_graph, "revision graph display");
3822                 break;
3824         case REQ_TOGGLE_REFS:
3825                 toggle_view_option(&opt_show_refs, "reference display");
3826                 break;
3828         case REQ_TOGGLE_SORT_FIELD:
3829         case REQ_TOGGLE_SORT_ORDER:
3830                 report("Sorting is not yet supported for the %s view", view->name);
3831                 break;
3833         case REQ_SEARCH:
3834         case REQ_SEARCH_BACK:
3835                 search_view(view, request);
3836                 break;
3838         case REQ_FIND_NEXT:
3839         case REQ_FIND_PREV:
3840                 find_next(view, request);
3841                 break;
3843         case REQ_STOP_LOADING:
3844                 foreach_view(view, i) {
3845                         if (view->pipe)
3846                                 report("Stopped loading the %s view", view->name),
3847                         end_update(view, TRUE);
3848                 }
3849                 break;
3851         case REQ_SHOW_VERSION:
3852                 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3853                 return TRUE;
3855         case REQ_SCREEN_REDRAW:
3856                 redraw_display(TRUE);
3857                 break;
3859         case REQ_EDIT:
3860                 report("Nothing to edit");
3861                 break;
3863         case REQ_ENTER:
3864                 report("Nothing to enter");
3865                 break;
3867         case REQ_VIEW_CLOSE:
3868                 /* XXX: Mark closed views by letting view->prev point to the
3869                  * view itself. Parents to closed view should never be
3870                  * followed. */
3871                 if (view->prev && view->prev != view) {
3872                         maximize_view(view->prev);
3873                         view->prev = view;
3874                         break;
3875                 }
3876                 /* Fall-through */
3877         case REQ_QUIT:
3878                 return FALSE;
3880         default:
3881                 report("Unknown key, press %s for help",
3882                        get_key(view->keymap, REQ_VIEW_HELP));
3883                 return TRUE;
3884         }
3886         return TRUE;
3890 /*
3891  * View backend utilities
3892  */
3894 enum sort_field {
3895         ORDERBY_NAME,
3896         ORDERBY_DATE,
3897         ORDERBY_AUTHOR,
3898 };
3900 struct sort_state {
3901         const enum sort_field *fields;
3902         size_t size, current;
3903         bool reverse;
3904 };
3906 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3907 #define get_sort_field(state) ((state).fields[(state).current])
3908 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3910 static void
3911 sort_view(struct view *view, enum request request, struct sort_state *state,
3912           int (*compare)(const void *, const void *))
3914         switch (request) {
3915         case REQ_TOGGLE_SORT_FIELD:
3916                 state->current = (state->current + 1) % state->size;
3917                 break;
3919         case REQ_TOGGLE_SORT_ORDER:
3920                 state->reverse = !state->reverse;
3921                 break;
3922         default:
3923                 die("Not a sort request");
3924         }
3926         qsort(view->line, view->lines, sizeof(*view->line), compare);
3927         redraw_view(view);
3930 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3932 /* Small author cache to reduce memory consumption. It uses binary
3933  * search to lookup or find place to position new entries. No entries
3934  * are ever freed. */
3935 static const char *
3936 get_author(const char *name)
3938         static const char **authors;
3939         static size_t authors_size;
3940         int from = 0, to = authors_size - 1;
3942         while (from <= to) {
3943                 size_t pos = (to + from) / 2;
3944                 int cmp = strcmp(name, authors[pos]);
3946                 if (!cmp)
3947                         return authors[pos];
3949                 if (cmp < 0)
3950                         to = pos - 1;
3951                 else
3952                         from = pos + 1;
3953         }
3955         if (!realloc_authors(&authors, authors_size, 1))
3956                 return NULL;
3957         name = strdup(name);
3958         if (!name)
3959                 return NULL;
3961         memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3962         authors[from] = name;
3963         authors_size++;
3965         return name;
3968 static void
3969 parse_timesec(struct time *time, const char *sec)
3971         time->sec = (time_t) atol(sec);
3974 static void
3975 parse_timezone(struct time *time, const char *zone)
3977         long tz;
3979         tz  = ('0' - zone[1]) * 60 * 60 * 10;
3980         tz += ('0' - zone[2]) * 60 * 60;
3981         tz += ('0' - zone[3]) * 60 * 10;
3982         tz += ('0' - zone[4]) * 60;
3984         if (zone[0] == '-')
3985                 tz = -tz;
3987         time->tz = tz;
3988         time->sec -= tz;
3991 /* Parse author lines where the name may be empty:
3992  *      author  <email@address.tld> 1138474660 +0100
3993  */
3994 static void
3995 parse_author_line(char *ident, const char **author, struct time *time)
3997         char *nameend = strchr(ident, '<');
3998         char *emailend = strchr(ident, '>');
4000         if (nameend && emailend)
4001                 *nameend = *emailend = 0;
4002         ident = chomp_string(ident);
4003         if (!*ident) {
4004                 if (nameend)
4005                         ident = chomp_string(nameend + 1);
4006                 if (!*ident)
4007                         ident = "Unknown";
4008         }
4010         *author = get_author(ident);
4012         /* Parse epoch and timezone */
4013         if (emailend && emailend[1] == ' ') {
4014                 char *secs = emailend + 2;
4015                 char *zone = strchr(secs, ' ');
4017                 parse_timesec(time, secs);
4019                 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
4020                         parse_timezone(time, zone + 1);
4021         }
4024 /*
4025  * Pager backend
4026  */
4028 static bool
4029 pager_draw(struct view *view, struct line *line, unsigned int lineno)
4031         if (opt_line_number && draw_lineno(view, lineno))
4032                 return TRUE;
4034         draw_text(view, line->type, line->data, TRUE);
4035         return TRUE;
4038 static bool
4039 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
4041         const char *describe_argv[] = { "git", "describe", commit_id, NULL };
4042         char ref[SIZEOF_STR];
4044         if (!io_run_buf(describe_argv, ref, sizeof(ref)) || !*ref)
4045                 return TRUE;
4047         /* This is the only fatal call, since it can "corrupt" the buffer. */
4048         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
4049                 return FALSE;
4051         return TRUE;
4054 static void
4055 add_pager_refs(struct view *view, struct line *line)
4057         char buf[SIZEOF_STR];
4058         char *commit_id = (char *)line->data + STRING_SIZE("commit ");
4059         struct ref_list *list;
4060         size_t bufpos = 0, i;
4061         const char *sep = "Refs: ";
4062         bool is_tag = FALSE;
4064         assert(line->type == LINE_COMMIT);
4066         list = get_ref_list(commit_id);
4067         if (!list) {
4068                 if (view->type == VIEW_DIFF)
4069                         goto try_add_describe_ref;
4070                 return;
4071         }
4073         for (i = 0; i < list->size; i++) {
4074                 struct ref *ref = list->refs[i];
4075                 const char *fmt = ref->tag    ? "%s[%s]" :
4076                                   ref->remote ? "%s<%s>" : "%s%s";
4078                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
4079                         return;
4080                 sep = ", ";
4081                 if (ref->tag)
4082                         is_tag = TRUE;
4083         }
4085         if (!is_tag && view->type == VIEW_DIFF) {
4086 try_add_describe_ref:
4087                 /* Add <tag>-g<commit_id> "fake" reference. */
4088                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
4089                         return;
4090         }
4092         if (bufpos == 0)
4093                 return;
4095         add_line_text(view, buf, LINE_PP_REFS);
4098 static bool
4099 pager_read(struct view *view, char *data)
4101         struct line *line;
4103         if (!data)
4104                 return TRUE;
4106         line = add_line_text(view, data, get_line_type(data));
4107         if (!line)
4108                 return FALSE;
4110         if (line->type == LINE_COMMIT &&
4111             (view->type == VIEW_DIFF ||
4112              view->type == VIEW_LOG))
4113                 add_pager_refs(view, line);
4115         return TRUE;
4118 static enum request
4119 pager_request(struct view *view, enum request request, struct line *line)
4121         int split = 0;
4123         if (request != REQ_ENTER)
4124                 return request;
4126         if (line->type == LINE_COMMIT &&
4127            (view->type == VIEW_LOG ||
4128             view->type == VIEW_PAGER)) {
4129                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
4130                 split = 1;
4131         }
4133         /* Always scroll the view even if it was split. That way
4134          * you can use Enter to scroll through the log view and
4135          * split open each commit diff. */
4136         scroll_view(view, REQ_SCROLL_LINE_DOWN);
4138         /* FIXME: A minor workaround. Scrolling the view will call report("")
4139          * but if we are scrolling a non-current view this won't properly
4140          * update the view title. */
4141         if (split)
4142                 update_view_title(view);
4144         return REQ_NONE;
4147 static bool
4148 pager_grep(struct view *view, struct line *line)
4150         const char *text[] = { line->data, NULL };
4152         return grep_text(view, text);
4155 static void
4156 pager_select(struct view *view, struct line *line)
4158         if (line->type == LINE_COMMIT) {
4159                 char *text = (char *)line->data + STRING_SIZE("commit ");
4161                 if (view->type != VIEW_PAGER)
4162                         string_copy_rev(view->ref, text);
4163                 string_copy_rev(ref_commit, text);
4164         }
4167 static struct view_ops pager_ops = {
4168         "line",
4169         NULL,
4170         NULL,
4171         pager_read,
4172         pager_draw,
4173         pager_request,
4174         pager_grep,
4175         pager_select,
4176 };
4178 static const char *log_argv[SIZEOF_ARG] = {
4179         "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
4180 };
4182 static enum request
4183 log_request(struct view *view, enum request request, struct line *line)
4185         switch (request) {
4186         case REQ_REFRESH:
4187                 load_refs();
4188                 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
4189                 return REQ_NONE;
4190         default:
4191                 return pager_request(view, request, line);
4192         }
4195 static struct view_ops log_ops = {
4196         "line",
4197         log_argv,
4198         NULL,
4199         pager_read,
4200         pager_draw,
4201         log_request,
4202         pager_grep,
4203         pager_select,
4204 };
4206 static const char *diff_argv[SIZEOF_ARG] = {
4207         "git", "show", "--pretty=fuller", "--no-color", "--root",
4208                 "--patch-with-stat", "--find-copies-harder", "-C",
4209                 "%(diffargs)", "%(commit)", "--", "%(fileargs)", NULL
4210 };
4212 static bool
4213 diff_read(struct view *view, char *data)
4215         if (!data) {
4216                 /* Fall back to retry if no diff will be shown. */
4217                 if (view->lines == 0 && opt_file_args) {
4218                         int pos = argv_size(view->argv)
4219                                 - argv_size(opt_file_args) - 1;
4221                         if (pos > 0 && !strcmp(view->argv[pos], "--")) {
4222                                 for (; view->argv[pos]; pos++) {
4223                                         free((void *) view->argv[pos]);
4224                                         view->argv[pos] = NULL;
4225                                 }
4227                                 if (view->pipe)
4228                                         io_done(view->pipe);
4229                                 if (io_run(&view->io, IO_RD, view->dir, view->argv))
4230                                         return FALSE;
4231                         }
4232                 }
4233                 return TRUE;
4234         }
4236         return pager_read(view, data);
4239 static struct view_ops diff_ops = {
4240         "line",
4241         diff_argv,
4242         NULL,
4243         diff_read,
4244         pager_draw,
4245         pager_request,
4246         pager_grep,
4247         pager_select,
4248 };
4250 /*
4251  * Help backend
4252  */
4254 static bool help_keymap_hidden[ARRAY_SIZE(keymap_table)];
4256 static bool
4257 help_open_keymap_title(struct view *view, enum keymap keymap)
4259         struct line *line;
4261         line = add_line_format(view, LINE_HELP_KEYMAP, "[%c] %s bindings",
4262                                help_keymap_hidden[keymap] ? '+' : '-',
4263                                enum_name(keymap_table[keymap]));
4264         if (line)
4265                 line->other = keymap;
4267         return help_keymap_hidden[keymap];
4270 static void
4271 help_open_keymap(struct view *view, enum keymap keymap)
4273         const char *group = NULL;
4274         char buf[SIZEOF_STR];
4275         size_t bufpos;
4276         bool add_title = TRUE;
4277         int i;
4279         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
4280                 const char *key = NULL;
4282                 if (req_info[i].request == REQ_NONE)
4283                         continue;
4285                 if (!req_info[i].request) {
4286                         group = req_info[i].help;
4287                         continue;
4288                 }
4290                 key = get_keys(keymap, req_info[i].request, TRUE);
4291                 if (!key || !*key)
4292                         continue;
4294                 if (add_title && help_open_keymap_title(view, keymap))
4295                         return;
4296                 add_title = FALSE;
4298                 if (group) {
4299                         add_line_text(view, group, LINE_HELP_GROUP);
4300                         group = NULL;
4301                 }
4303                 add_line_format(view, LINE_DEFAULT, "    %-25s %-20s %s", key,
4304                                 enum_name(req_info[i]), req_info[i].help);
4305         }
4307         group = "External commands:";
4309         for (i = 0; i < run_requests; i++) {
4310                 struct run_request *req = get_run_request(REQ_NONE + i + 1);
4311                 const char *key;
4312                 int argc;
4314                 if (!req || req->keymap != keymap)
4315                         continue;
4317                 key = get_key_name(req->key);
4318                 if (!*key)
4319                         key = "(no key defined)";
4321                 if (add_title && help_open_keymap_title(view, keymap))
4322                         return;
4323                 if (group) {
4324                         add_line_text(view, group, LINE_HELP_GROUP);
4325                         group = NULL;
4326                 }
4328                 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
4329                         if (!string_format_from(buf, &bufpos, "%s%s",
4330                                                 argc ? " " : "", req->argv[argc]))
4331                                 return;
4333                 add_line_format(view, LINE_DEFAULT, "    %-25s `%s`", key, buf);
4334         }
4337 static bool
4338 help_open(struct view *view)
4340         enum keymap keymap;
4342         reset_view(view);
4343         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
4344         add_line_text(view, "", LINE_DEFAULT);
4346         for (keymap = 0; keymap < ARRAY_SIZE(keymap_table); keymap++)
4347                 help_open_keymap(view, keymap);
4349         return TRUE;
4352 static enum request
4353 help_request(struct view *view, enum request request, struct line *line)
4355         switch (request) {
4356         case REQ_ENTER:
4357                 if (line->type == LINE_HELP_KEYMAP) {
4358                         help_keymap_hidden[line->other] =
4359                                 !help_keymap_hidden[line->other];
4360                         view->p_restore = TRUE;
4361                         open_view(view, REQ_VIEW_HELP, OPEN_REFRESH);
4362                 }
4364                 return REQ_NONE;
4365         default:
4366                 return pager_request(view, request, line);
4367         }
4370 static struct view_ops help_ops = {
4371         "line",
4372         NULL,
4373         help_open,
4374         NULL,
4375         pager_draw,
4376         help_request,
4377         pager_grep,
4378         pager_select,
4379 };
4382 /*
4383  * Tree backend
4384  */
4386 struct tree_stack_entry {
4387         struct tree_stack_entry *prev;  /* Entry below this in the stack */
4388         unsigned long lineno;           /* Line number to restore */
4389         char *name;                     /* Position of name in opt_path */
4390 };
4392 /* The top of the path stack. */
4393 static struct tree_stack_entry *tree_stack = NULL;
4394 unsigned long tree_lineno = 0;
4396 static void
4397 pop_tree_stack_entry(void)
4399         struct tree_stack_entry *entry = tree_stack;
4401         tree_lineno = entry->lineno;
4402         entry->name[0] = 0;
4403         tree_stack = entry->prev;
4404         free(entry);
4407 static void
4408 push_tree_stack_entry(const char *name, unsigned long lineno)
4410         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
4411         size_t pathlen = strlen(opt_path);
4413         if (!entry)
4414                 return;
4416         entry->prev = tree_stack;
4417         entry->name = opt_path + pathlen;
4418         tree_stack = entry;
4420         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
4421                 pop_tree_stack_entry();
4422                 return;
4423         }
4425         /* Move the current line to the first tree entry. */
4426         tree_lineno = 1;
4427         entry->lineno = lineno;
4430 /* Parse output from git-ls-tree(1):
4431  *
4432  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4433  */
4435 #define SIZEOF_TREE_ATTR \
4436         STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4438 #define SIZEOF_TREE_MODE \
4439         STRING_SIZE("100644 ")
4441 #define TREE_ID_OFFSET \
4442         STRING_SIZE("100644 blob ")
4444 struct tree_entry {
4445         char id[SIZEOF_REV];
4446         mode_t mode;
4447         struct time time;               /* Date from the author ident. */
4448         const char *author;             /* Author of the commit. */
4449         char name[1];
4450 };
4452 static const char *
4453 tree_path(const struct line *line)
4455         return ((struct tree_entry *) line->data)->name;
4458 static int
4459 tree_compare_entry(const struct line *line1, const struct line *line2)
4461         if (line1->type != line2->type)
4462                 return line1->type == LINE_TREE_DIR ? -1 : 1;
4463         return strcmp(tree_path(line1), tree_path(line2));
4466 static const enum sort_field tree_sort_fields[] = {
4467         ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4468 };
4469 static struct sort_state tree_sort_state = SORT_STATE(tree_sort_fields);
4471 static int
4472 tree_compare(const void *l1, const void *l2)
4474         const struct line *line1 = (const struct line *) l1;
4475         const struct line *line2 = (const struct line *) l2;
4476         const struct tree_entry *entry1 = ((const struct line *) l1)->data;
4477         const struct tree_entry *entry2 = ((const struct line *) l2)->data;
4479         if (line1->type == LINE_TREE_HEAD)
4480                 return -1;
4481         if (line2->type == LINE_TREE_HEAD)
4482                 return 1;
4484         switch (get_sort_field(tree_sort_state)) {
4485         case ORDERBY_DATE:
4486                 return sort_order(tree_sort_state, timecmp(&entry1->time, &entry2->time));
4488         case ORDERBY_AUTHOR:
4489                 return sort_order(tree_sort_state, strcmp(entry1->author, entry2->author));
4491         case ORDERBY_NAME:
4492         default:
4493                 return sort_order(tree_sort_state, tree_compare_entry(line1, line2));
4494         }
4498 static struct line *
4499 tree_entry(struct view *view, enum line_type type, const char *path,
4500            const char *mode, const char *id)
4502         struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
4503         struct line *line = entry ? add_line_data(view, entry, type) : NULL;
4505         if (!entry || !line) {
4506                 free(entry);
4507                 return NULL;
4508         }
4510         strncpy(entry->name, path, strlen(path));
4511         if (mode)
4512                 entry->mode = strtoul(mode, NULL, 8);
4513         if (id)
4514                 string_copy_rev(entry->id, id);
4516         return line;
4519 static bool
4520 tree_read_date(struct view *view, char *text, bool *read_date)
4522         static const char *author_name;
4523         static struct time author_time;
4525         if (!text && *read_date) {
4526                 *read_date = FALSE;
4527                 return TRUE;
4529         } else if (!text) {
4530                 char *path = *opt_path ? opt_path : ".";
4531                 /* Find next entry to process */
4532                 const char *log_file[] = {
4533                         "git", "log", "--no-color", "--pretty=raw",
4534                                 "--cc", "--raw", view->id, "--", path, NULL
4535                 };
4537                 if (!view->lines) {
4538                         tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
4539                         report("Tree is empty");
4540                         return TRUE;
4541                 }
4543                 if (!start_update(view, log_file, opt_cdup)) {
4544                         report("Failed to load tree data");
4545                         return TRUE;
4546                 }
4548                 *read_date = TRUE;
4549                 return FALSE;
4551         } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
4552                 parse_author_line(text + STRING_SIZE("author "),
4553                                   &author_name, &author_time);
4555         } else if (*text == ':') {
4556                 char *pos;
4557                 size_t annotated = 1;
4558                 size_t i;
4560                 pos = strchr(text, '\t');
4561                 if (!pos)
4562                         return TRUE;
4563                 text = pos + 1;
4564                 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
4565                         text += strlen(opt_path);
4566                 pos = strchr(text, '/');
4567                 if (pos)
4568                         *pos = 0;
4570                 for (i = 1; i < view->lines; i++) {
4571                         struct line *line = &view->line[i];
4572                         struct tree_entry *entry = line->data;
4574                         annotated += !!entry->author;
4575                         if (entry->author || strcmp(entry->name, text))
4576                                 continue;
4578                         entry->author = author_name;
4579                         entry->time = author_time;
4580                         line->dirty = 1;
4581                         break;
4582                 }
4584                 if (annotated == view->lines)
4585                         io_kill(view->pipe);
4586         }
4587         return TRUE;
4590 static bool
4591 tree_read(struct view *view, char *text)
4593         static bool read_date = FALSE;
4594         struct tree_entry *data;
4595         struct line *entry, *line;
4596         enum line_type type;
4597         size_t textlen = text ? strlen(text) : 0;
4598         char *path = text + SIZEOF_TREE_ATTR;
4600         if (read_date || !text)
4601                 return tree_read_date(view, text, &read_date);
4603         if (textlen <= SIZEOF_TREE_ATTR)
4604                 return FALSE;
4605         if (view->lines == 0 &&
4606             !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
4607                 return FALSE;
4609         /* Strip the path part ... */
4610         if (*opt_path) {
4611                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
4612                 size_t striplen = strlen(opt_path);
4614                 if (pathlen > striplen)
4615                         memmove(path, path + striplen,
4616                                 pathlen - striplen + 1);
4618                 /* Insert "link" to parent directory. */
4619                 if (view->lines == 1 &&
4620                     !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
4621                         return FALSE;
4622         }
4624         type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
4625         entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
4626         if (!entry)
4627                 return FALSE;
4628         data = entry->data;
4630         /* Skip "Directory ..." and ".." line. */
4631         for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
4632                 if (tree_compare_entry(line, entry) <= 0)
4633                         continue;
4635                 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4637                 line->data = data;
4638                 line->type = type;
4639                 for (; line <= entry; line++)
4640                         line->dirty = line->cleareol = 1;
4641                 return TRUE;
4642         }
4644         if (tree_lineno > view->lineno) {
4645                 view->lineno = tree_lineno;
4646                 tree_lineno = 0;
4647         }
4649         return TRUE;
4652 static bool
4653 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4655         struct tree_entry *entry = line->data;
4657         if (line->type == LINE_TREE_HEAD) {
4658                 if (draw_text(view, line->type, "Directory path /", TRUE))
4659                         return TRUE;
4660         } else {
4661                 if (draw_mode(view, entry->mode))
4662                         return TRUE;
4664                 if (opt_author && draw_author(view, entry->author))
4665                         return TRUE;
4667                 if (opt_date && draw_date(view, &entry->time))
4668                         return TRUE;
4669         }
4670         if (draw_text(view, line->type, entry->name, TRUE))
4671                 return TRUE;
4672         return TRUE;
4675 static void
4676 open_blob_editor(const char *id)
4678         const char *blob_argv[] = { "git", "cat-file", "blob", id, NULL };
4679         char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4680         int fd = mkstemp(file);
4682         if (fd == -1)
4683                 report("Failed to create temporary file");
4684         else if (!io_run_append(blob_argv, fd))
4685                 report("Failed to save blob data to file");
4686         else
4687                 open_editor(file);
4688         if (fd != -1)
4689                 unlink(file);
4692 static enum request
4693 tree_request(struct view *view, enum request request, struct line *line)
4695         enum open_flags flags;
4696         struct tree_entry *entry = line->data;
4698         switch (request) {
4699         case REQ_VIEW_BLAME:
4700                 if (line->type != LINE_TREE_FILE) {
4701                         report("Blame only supported for files");
4702                         return REQ_NONE;
4703                 }
4705                 string_copy(opt_ref, view->vid);
4706                 return request;
4708         case REQ_EDIT:
4709                 if (line->type != LINE_TREE_FILE) {
4710                         report("Edit only supported for files");
4711                 } else if (!is_head_commit(view->vid)) {
4712                         open_blob_editor(entry->id);
4713                 } else {
4714                         open_editor(opt_file);
4715                 }
4716                 return REQ_NONE;
4718         case REQ_TOGGLE_SORT_FIELD:
4719         case REQ_TOGGLE_SORT_ORDER:
4720                 sort_view(view, request, &tree_sort_state, tree_compare);
4721                 return REQ_NONE;
4723         case REQ_PARENT:
4724                 if (!*opt_path) {
4725                         /* quit view if at top of tree */
4726                         return REQ_VIEW_CLOSE;
4727                 }
4728                 /* fake 'cd  ..' */
4729                 line = &view->line[1];
4730                 break;
4732         case REQ_ENTER:
4733                 break;
4735         default:
4736                 return request;
4737         }
4739         /* Cleanup the stack if the tree view is at a different tree. */
4740         while (!*opt_path && tree_stack)
4741                 pop_tree_stack_entry();
4743         switch (line->type) {
4744         case LINE_TREE_DIR:
4745                 /* Depending on whether it is a subdirectory or parent link
4746                  * mangle the path buffer. */
4747                 if (line == &view->line[1] && *opt_path) {
4748                         pop_tree_stack_entry();
4750                 } else {
4751                         const char *basename = tree_path(line);
4753                         push_tree_stack_entry(basename, view->lineno);
4754                 }
4756                 /* Trees and subtrees share the same ID, so they are not not
4757                  * unique like blobs. */
4758                 flags = OPEN_RELOAD;
4759                 request = REQ_VIEW_TREE;
4760                 break;
4762         case LINE_TREE_FILE:
4763                 flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
4764                 request = REQ_VIEW_BLOB;
4765                 break;
4767         default:
4768                 return REQ_NONE;
4769         }
4771         open_view(view, request, flags);
4772         if (request == REQ_VIEW_TREE)
4773                 view->lineno = tree_lineno;
4775         return REQ_NONE;
4778 static bool
4779 tree_grep(struct view *view, struct line *line)
4781         struct tree_entry *entry = line->data;
4782         const char *text[] = {
4783                 entry->name,
4784                 opt_author ? entry->author : "",
4785                 mkdate(&entry->time, opt_date),
4786                 NULL
4787         };
4789         return grep_text(view, text);
4792 static void
4793 tree_select(struct view *view, struct line *line)
4795         struct tree_entry *entry = line->data;
4797         if (line->type == LINE_TREE_FILE) {
4798                 string_copy_rev(ref_blob, entry->id);
4799                 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4801         } else if (line->type != LINE_TREE_DIR) {
4802                 return;
4803         }
4805         string_copy_rev(view->ref, entry->id);
4808 static bool
4809 tree_prepare(struct view *view)
4811         if (view->lines == 0 && opt_prefix[0]) {
4812                 char *pos = opt_prefix;
4814                 while (pos && *pos) {
4815                         char *end = strchr(pos, '/');
4817                         if (end)
4818                                 *end = 0;
4819                         push_tree_stack_entry(pos, 0);
4820                         pos = end;
4821                         if (end) {
4822                                 *end = '/';
4823                                 pos++;
4824                         }
4825                 }
4827         } else if (strcmp(view->vid, view->id)) {
4828                 opt_path[0] = 0;
4829         }
4831         return prepare_io(view, opt_cdup, view->ops->argv, TRUE);
4834 static const char *tree_argv[SIZEOF_ARG] = {
4835         "git", "ls-tree", "%(commit)", "%(directory)", NULL
4836 };
4838 static struct view_ops tree_ops = {
4839         "file",
4840         tree_argv,
4841         NULL,
4842         tree_read,
4843         tree_draw,
4844         tree_request,
4845         tree_grep,
4846         tree_select,
4847         tree_prepare,
4848 };
4850 static bool
4851 blob_read(struct view *view, char *line)
4853         if (!line)
4854                 return TRUE;
4855         return add_line_text(view, line, LINE_DEFAULT) != NULL;
4858 static enum request
4859 blob_request(struct view *view, enum request request, struct line *line)
4861         switch (request) {
4862         case REQ_EDIT:
4863                 open_blob_editor(view->vid);
4864                 return REQ_NONE;
4865         default:
4866                 return pager_request(view, request, line);
4867         }
4870 static const char *blob_argv[SIZEOF_ARG] = {
4871         "git", "cat-file", "blob", "%(blob)", NULL
4872 };
4874 static struct view_ops blob_ops = {
4875         "line",
4876         blob_argv,
4877         NULL,
4878         blob_read,
4879         pager_draw,
4880         blob_request,
4881         pager_grep,
4882         pager_select,
4883 };
4885 /*
4886  * Blame backend
4887  *
4888  * Loading the blame view is a two phase job:
4889  *
4890  *  1. File content is read either using opt_file from the
4891  *     filesystem or using git-cat-file.
4892  *  2. Then blame information is incrementally added by
4893  *     reading output from git-blame.
4894  */
4896 struct blame_commit {
4897         char id[SIZEOF_REV];            /* SHA1 ID. */
4898         char title[128];                /* First line of the commit message. */
4899         const char *author;             /* Author of the commit. */
4900         struct time time;               /* Date from the author ident. */
4901         char filename[128];             /* Name of file. */
4902         char parent_id[SIZEOF_REV];     /* Parent/previous SHA1 ID. */
4903         char parent_filename[128];      /* Parent/previous name of file. */
4904 };
4906 struct blame {
4907         struct blame_commit *commit;
4908         unsigned long lineno;
4909         char text[1];
4910 };
4912 static bool
4913 blame_open(struct view *view)
4915         char path[SIZEOF_STR];
4916         size_t i;
4918         if (!view->prev && *opt_prefix) {
4919                 string_copy(path, opt_file);
4920                 if (!string_format(opt_file, "%s%s", opt_prefix, path))
4921                         return FALSE;
4922         }
4924         if (*opt_ref || !io_open(&view->io, "%s%s", opt_cdup, opt_file)) {
4925                 const char *blame_cat_file_argv[] = {
4926                         "git", "cat-file", "blob", path, NULL
4927                 };
4929                 if (!string_format(path, "%s:%s", opt_ref, opt_file) ||
4930                     !start_update(view, blame_cat_file_argv, opt_cdup))
4931                         return FALSE;
4932         }
4934         /* First pass: remove multiple references to the same commit. */
4935         for (i = 0; i < view->lines; i++) {
4936                 struct blame *blame = view->line[i].data;
4938                 if (blame->commit && blame->commit->id[0])
4939                         blame->commit->id[0] = 0;
4940                 else
4941                         blame->commit = NULL;
4942         }
4944         /* Second pass: free existing references. */
4945         for (i = 0; i < view->lines; i++) {
4946                 struct blame *blame = view->line[i].data;
4948                 if (blame->commit)
4949                         free(blame->commit);
4950         }
4952         setup_update(view, opt_file);
4953         string_format(view->ref, "%s ...", opt_file);
4955         return TRUE;
4958 static struct blame_commit *
4959 get_blame_commit(struct view *view, const char *id)
4961         size_t i;
4963         for (i = 0; i < view->lines; i++) {
4964                 struct blame *blame = view->line[i].data;
4966                 if (!blame->commit)
4967                         continue;
4969                 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4970                         return blame->commit;
4971         }
4973         {
4974                 struct blame_commit *commit = calloc(1, sizeof(*commit));
4976                 if (commit)
4977                         string_ncopy(commit->id, id, SIZEOF_REV);
4978                 return commit;
4979         }
4982 static bool
4983 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4985         const char *pos = *posref;
4987         *posref = NULL;
4988         pos = strchr(pos + 1, ' ');
4989         if (!pos || !isdigit(pos[1]))
4990                 return FALSE;
4991         *number = atoi(pos + 1);
4992         if (*number < min || *number > max)
4993                 return FALSE;
4995         *posref = pos;
4996         return TRUE;
4999 static struct blame_commit *
5000 parse_blame_commit(struct view *view, const char *text, int *blamed)
5002         struct blame_commit *commit;
5003         struct blame *blame;
5004         const char *pos = text + SIZEOF_REV - 2;
5005         size_t orig_lineno = 0;
5006         size_t lineno;
5007         size_t group;
5009         if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
5010                 return NULL;
5012         if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
5013             !parse_number(&pos, &lineno, 1, view->lines) ||
5014             !parse_number(&pos, &group, 1, view->lines - lineno + 1))
5015                 return NULL;
5017         commit = get_blame_commit(view, text);
5018         if (!commit)
5019                 return NULL;
5021         *blamed += group;
5022         while (group--) {
5023                 struct line *line = &view->line[lineno + group - 1];
5025                 blame = line->data;
5026                 blame->commit = commit;
5027                 blame->lineno = orig_lineno + group - 1;
5028                 line->dirty = 1;
5029         }
5031         return commit;
5034 static bool
5035 blame_read_file(struct view *view, const char *line, bool *read_file)
5037         if (!line) {
5038                 const char *blame_argv[] = {
5039                         "git", "blame", "--incremental",
5040                                 *opt_ref ? opt_ref : "--incremental", "--", opt_file, NULL
5041                 };
5043                 if (view->lines == 0 && !view->prev)
5044                         die("No blame exist for %s", view->vid);
5046                 if (view->lines == 0 || !start_update(view, blame_argv, opt_cdup)) {
5047                         report("Failed to load blame data");
5048                         return TRUE;
5049                 }
5051                 *read_file = FALSE;
5052                 return FALSE;
5054         } else {
5055                 size_t linelen = strlen(line);
5056                 struct blame *blame = malloc(sizeof(*blame) + linelen);
5058                 if (!blame)
5059                         return FALSE;
5061                 blame->commit = NULL;
5062                 strncpy(blame->text, line, linelen);
5063                 blame->text[linelen] = 0;
5064                 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
5065         }
5068 static bool
5069 match_blame_header(const char *name, char **line)
5071         size_t namelen = strlen(name);
5072         bool matched = !strncmp(name, *line, namelen);
5074         if (matched)
5075                 *line += namelen;
5077         return matched;
5080 static bool
5081 blame_read(struct view *view, char *line)
5083         static struct blame_commit *commit = NULL;
5084         static int blamed = 0;
5085         static bool read_file = TRUE;
5087         if (read_file)
5088                 return blame_read_file(view, line, &read_file);
5090         if (!line) {
5091                 /* Reset all! */
5092                 commit = NULL;
5093                 blamed = 0;
5094                 read_file = TRUE;
5095                 string_format(view->ref, "%s", view->vid);
5096                 if (view_is_displayed(view)) {
5097                         update_view_title(view);
5098                         redraw_view_from(view, 0);
5099                 }
5100                 return TRUE;
5101         }
5103         if (!commit) {
5104                 commit = parse_blame_commit(view, line, &blamed);
5105                 string_format(view->ref, "%s %2d%%", view->vid,
5106                               view->lines ? blamed * 100 / view->lines : 0);
5108         } else if (match_blame_header("author ", &line)) {
5109                 commit->author = get_author(line);
5111         } else if (match_blame_header("author-time ", &line)) {
5112                 parse_timesec(&commit->time, line);
5114         } else if (match_blame_header("author-tz ", &line)) {
5115                 parse_timezone(&commit->time, line);
5117         } else if (match_blame_header("summary ", &line)) {
5118                 string_ncopy(commit->title, line, strlen(line));
5120         } else if (match_blame_header("previous ", &line)) {
5121                 if (strlen(line) <= SIZEOF_REV)
5122                         return FALSE;
5123                 string_copy_rev(commit->parent_id, line);
5124                 line += SIZEOF_REV;
5125                 string_ncopy(commit->parent_filename, line, strlen(line));
5127         } else if (match_blame_header("filename ", &line)) {
5128                 string_ncopy(commit->filename, line, strlen(line));
5129                 commit = NULL;
5130         }
5132         return TRUE;
5135 static bool
5136 blame_draw(struct view *view, struct line *line, unsigned int lineno)
5138         struct blame *blame = line->data;
5139         struct time *time = NULL;
5140         const char *id = NULL, *author = NULL;
5142         if (blame->commit && *blame->commit->filename) {
5143                 id = blame->commit->id;
5144                 author = blame->commit->author;
5145                 time = &blame->commit->time;
5146         }
5148         if (opt_date && draw_date(view, time))
5149                 return TRUE;
5151         if (opt_author && draw_author(view, author))
5152                 return TRUE;
5154         if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
5155                 return TRUE;
5157         if (draw_lineno(view, lineno))
5158                 return TRUE;
5160         draw_text(view, LINE_DEFAULT, blame->text, TRUE);
5161         return TRUE;
5164 static bool
5165 check_blame_commit(struct blame *blame, bool check_null_id)
5167         if (!blame->commit)
5168                 report("Commit data not loaded yet");
5169         else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
5170                 report("No commit exist for the selected line");
5171         else
5172                 return TRUE;
5173         return FALSE;
5176 static void
5177 setup_blame_parent_line(struct view *view, struct blame *blame)
5179         char from[SIZEOF_REF + SIZEOF_STR];
5180         char to[SIZEOF_REF + SIZEOF_STR];
5181         const char *diff_tree_argv[] = {
5182                 "git", "diff", "--no-textconv", "--no-extdiff", "--no-color",
5183                         "-U0", from, to, "--", NULL
5184         };
5185         struct io io;
5186         int parent_lineno = -1;
5187         int blamed_lineno = -1;
5188         char *line;
5190         if (!string_format(from, "%s:%s", opt_ref, opt_file) ||
5191             !string_format(to, "%s:%s", blame->commit->id, blame->commit->filename) ||
5192             !io_run(&io, IO_RD, NULL, diff_tree_argv))
5193                 return;
5195         while ((line = io_get(&io, '\n', TRUE))) {
5196                 if (*line == '@') {
5197                         char *pos = strchr(line, '+');
5199                         parent_lineno = atoi(line + 4);
5200                         if (pos)
5201                                 blamed_lineno = atoi(pos + 1);
5203                 } else if (*line == '+' && parent_lineno != -1) {
5204                         if (blame->lineno == blamed_lineno - 1 &&
5205                             !strcmp(blame->text, line + 1)) {
5206                                 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
5207                                 break;
5208                         }
5209                         blamed_lineno++;
5210                 }
5211         }
5213         io_done(&io);
5216 static enum request
5217 blame_request(struct view *view, enum request request, struct line *line)
5219         enum open_flags flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
5220         struct blame *blame = line->data;
5222         switch (request) {
5223         case REQ_VIEW_BLAME:
5224                 if (check_blame_commit(blame, TRUE)) {
5225                         string_copy(opt_ref, blame->commit->id);
5226                         string_copy(opt_file, blame->commit->filename);
5227                         if (blame->lineno)
5228                                 view->lineno = blame->lineno;
5229                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5230                 }
5231                 break;
5233         case REQ_PARENT:
5234                 if (!check_blame_commit(blame, TRUE))
5235                         break;
5236                 if (!*blame->commit->parent_id) {
5237                         report("The selected commit has no parents");
5238                 } else {
5239                         string_copy_rev(opt_ref, blame->commit->parent_id);
5240                         string_copy(opt_file, blame->commit->parent_filename);
5241                         setup_blame_parent_line(view, blame);
5242                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5243                 }
5244                 break;
5246         case REQ_ENTER:
5247                 if (!check_blame_commit(blame, FALSE))
5248                         break;
5250                 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
5251                     !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
5252                         break;
5254                 if (!strcmp(blame->commit->id, NULL_ID)) {
5255                         struct view *diff = VIEW(REQ_VIEW_DIFF);
5256                         const char *diff_index_argv[] = {
5257                                 "git", "diff-index", "--root", "--patch-with-stat",
5258                                         "-C", "-M", "HEAD", "--", view->vid, NULL
5259                         };
5261                         if (!*blame->commit->parent_id) {
5262                                 diff_index_argv[1] = "diff";
5263                                 diff_index_argv[2] = "--no-color";
5264                                 diff_index_argv[6] = "--";
5265                                 diff_index_argv[7] = "/dev/null";
5266                         }
5268                         if (!prepare_update(diff, diff_index_argv, NULL)) {
5269                                 report("Failed to allocate diff command");
5270                                 break;
5271                         }
5272                         flags |= OPEN_PREPARED;
5273                 }
5275                 open_view(view, REQ_VIEW_DIFF, flags);
5276                 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
5277                         string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
5278                 break;
5280         default:
5281                 return request;
5282         }
5284         return REQ_NONE;
5287 static bool
5288 blame_grep(struct view *view, struct line *line)
5290         struct blame *blame = line->data;
5291         struct blame_commit *commit = blame->commit;
5292         const char *text[] = {
5293                 blame->text,
5294                 commit ? commit->title : "",
5295                 commit ? commit->id : "",
5296                 commit && opt_author ? commit->author : "",
5297                 commit ? mkdate(&commit->time, opt_date) : "",
5298                 NULL
5299         };
5301         return grep_text(view, text);
5304 static void
5305 blame_select(struct view *view, struct line *line)
5307         struct blame *blame = line->data;
5308         struct blame_commit *commit = blame->commit;
5310         if (!commit)
5311                 return;
5313         if (!strcmp(commit->id, NULL_ID))
5314                 string_ncopy(ref_commit, "HEAD", 4);
5315         else
5316                 string_copy_rev(ref_commit, commit->id);
5319 static struct view_ops blame_ops = {
5320         "line",
5321         NULL,
5322         blame_open,
5323         blame_read,
5324         blame_draw,
5325         blame_request,
5326         blame_grep,
5327         blame_select,
5328 };
5330 /*
5331  * Branch backend
5332  */
5334 struct branch {
5335         const char *author;             /* Author of the last commit. */
5336         struct time time;               /* Date of the last activity. */
5337         const struct ref *ref;          /* Name and commit ID information. */
5338 };
5340 static const struct ref branch_all;
5342 static const enum sort_field branch_sort_fields[] = {
5343         ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
5344 };
5345 static struct sort_state branch_sort_state = SORT_STATE(branch_sort_fields);
5347 static int
5348 branch_compare(const void *l1, const void *l2)
5350         const struct branch *branch1 = ((const struct line *) l1)->data;
5351         const struct branch *branch2 = ((const struct line *) l2)->data;
5353         switch (get_sort_field(branch_sort_state)) {
5354         case ORDERBY_DATE:
5355                 return sort_order(branch_sort_state, timecmp(&branch1->time, &branch2->time));
5357         case ORDERBY_AUTHOR:
5358                 return sort_order(branch_sort_state, strcmp(branch1->author, branch2->author));
5360         case ORDERBY_NAME:
5361         default:
5362                 return sort_order(branch_sort_state, strcmp(branch1->ref->name, branch2->ref->name));
5363         }
5366 static bool
5367 branch_draw(struct view *view, struct line *line, unsigned int lineno)
5369         struct branch *branch = line->data;
5370         enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
5372         if (opt_date && draw_date(view, &branch->time))
5373                 return TRUE;
5375         if (opt_author && draw_author(view, branch->author))
5376                 return TRUE;
5378         draw_text(view, type, branch->ref == &branch_all ? "All branches" : branch->ref->name, TRUE);
5379         return TRUE;
5382 static enum request
5383 branch_request(struct view *view, enum request request, struct line *line)
5385         struct branch *branch = line->data;
5387         switch (request) {
5388         case REQ_REFRESH:
5389                 load_refs();
5390                 open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
5391                 return REQ_NONE;
5393         case REQ_TOGGLE_SORT_FIELD:
5394         case REQ_TOGGLE_SORT_ORDER:
5395                 sort_view(view, request, &branch_sort_state, branch_compare);
5396                 return REQ_NONE;
5398         case REQ_ENTER:
5399         {
5400                 const struct ref *ref = branch->ref;
5401                 const char *all_branches_argv[] = {
5402                         "git", "log", "--no-color", "--pretty=raw", "--parents",
5403                               "--topo-order",
5404                               ref == &branch_all ? "--all" : ref->name, NULL
5405                 };
5406                 struct view *main_view = VIEW(REQ_VIEW_MAIN);
5408                 if (!prepare_update(main_view, all_branches_argv, NULL))
5409                         report("Failed to load view of all branches");
5410                 else
5411                         open_view(view, REQ_VIEW_MAIN, OPEN_PREPARED | OPEN_SPLIT);
5412                 return REQ_NONE;
5413         }
5414         default:
5415                 return request;
5416         }
5419 static bool
5420 branch_read(struct view *view, char *line)
5422         static char id[SIZEOF_REV];
5423         struct branch *reference;
5424         size_t i;
5426         if (!line)
5427                 return TRUE;
5429         switch (get_line_type(line)) {
5430         case LINE_COMMIT:
5431                 string_copy_rev(id, line + STRING_SIZE("commit "));
5432                 return TRUE;
5434         case LINE_AUTHOR:
5435                 for (i = 0, reference = NULL; i < view->lines; i++) {
5436                         struct branch *branch = view->line[i].data;
5438                         if (strcmp(branch->ref->id, id))
5439                                 continue;
5441                         view->line[i].dirty = TRUE;
5442                         if (reference) {
5443                                 branch->author = reference->author;
5444                                 branch->time = reference->time;
5445                                 continue;
5446                         }
5448                         parse_author_line(line + STRING_SIZE("author "),
5449                                           &branch->author, &branch->time);
5450                         reference = branch;
5451                 }
5452                 return TRUE;
5454         default:
5455                 return TRUE;
5456         }
5460 static bool
5461 branch_open_visitor(void *data, const struct ref *ref)
5463         struct view *view = data;
5464         struct branch *branch;
5466         if (ref->tag || ref->ltag || ref->remote)
5467                 return TRUE;
5469         branch = calloc(1, sizeof(*branch));
5470         if (!branch)
5471                 return FALSE;
5473         branch->ref = ref;
5474         return !!add_line_data(view, branch, LINE_DEFAULT);
5477 static bool
5478 branch_open(struct view *view)
5480         const char *branch_log[] = {
5481                 "git", "log", "--no-color", "--pretty=raw",
5482                         "--simplify-by-decoration", "--all", NULL
5483         };
5485         if (!start_update(view, branch_log, NULL)) {
5486                 report("Failed to load branch data");
5487                 return TRUE;
5488         }
5490         setup_update(view, view->id);
5491         branch_open_visitor(view, &branch_all);
5492         foreach_ref(branch_open_visitor, view);
5493         view->p_restore = TRUE;
5495         return TRUE;
5498 static bool
5499 branch_grep(struct view *view, struct line *line)
5501         struct branch *branch = line->data;
5502         const char *text[] = {
5503                 branch->ref->name,
5504                 branch->author,
5505                 NULL
5506         };
5508         return grep_text(view, text);
5511 static void
5512 branch_select(struct view *view, struct line *line)
5514         struct branch *branch = line->data;
5516         string_copy_rev(view->ref, branch->ref->id);
5517         string_copy_rev(ref_commit, branch->ref->id);
5518         string_copy_rev(ref_head, branch->ref->id);
5519         string_copy_rev(ref_branch, branch->ref->name);
5522 static struct view_ops branch_ops = {
5523         "branch",
5524         NULL,
5525         branch_open,
5526         branch_read,
5527         branch_draw,
5528         branch_request,
5529         branch_grep,
5530         branch_select,
5531 };
5533 /*
5534  * Status backend
5535  */
5537 struct status {
5538         char status;
5539         struct {
5540                 mode_t mode;
5541                 char rev[SIZEOF_REV];
5542                 char name[SIZEOF_STR];
5543         } old;
5544         struct {
5545                 mode_t mode;
5546                 char rev[SIZEOF_REV];
5547                 char name[SIZEOF_STR];
5548         } new;
5549 };
5551 static char status_onbranch[SIZEOF_STR];
5552 static struct status stage_status;
5553 static enum line_type stage_line_type;
5554 static size_t stage_chunks;
5555 static int *stage_chunk;
5557 DEFINE_ALLOCATOR(realloc_ints, int, 32)
5559 /* This should work even for the "On branch" line. */
5560 static inline bool
5561 status_has_none(struct view *view, struct line *line)
5563         return line < view->line + view->lines && !line[1].data;
5566 /* Get fields from the diff line:
5567  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5568  */
5569 static inline bool
5570 status_get_diff(struct status *file, const char *buf, size_t bufsize)
5572         const char *old_mode = buf +  1;
5573         const char *new_mode = buf +  8;
5574         const char *old_rev  = buf + 15;
5575         const char *new_rev  = buf + 56;
5576         const char *status   = buf + 97;
5578         if (bufsize < 98 ||
5579             old_mode[-1] != ':' ||
5580             new_mode[-1] != ' ' ||
5581             old_rev[-1]  != ' ' ||
5582             new_rev[-1]  != ' ' ||
5583             status[-1]   != ' ')
5584                 return FALSE;
5586         file->status = *status;
5588         string_copy_rev(file->old.rev, old_rev);
5589         string_copy_rev(file->new.rev, new_rev);
5591         file->old.mode = strtoul(old_mode, NULL, 8);
5592         file->new.mode = strtoul(new_mode, NULL, 8);
5594         file->old.name[0] = file->new.name[0] = 0;
5596         return TRUE;
5599 static bool
5600 status_run(struct view *view, const char *argv[], char status, enum line_type type)
5602         struct status *unmerged = NULL;
5603         char *buf;
5604         struct io io;
5606         if (!io_run(&io, IO_RD, opt_cdup, argv))
5607                 return FALSE;
5609         add_line_data(view, NULL, type);
5611         while ((buf = io_get(&io, 0, TRUE))) {
5612                 struct status *file = unmerged;
5614                 if (!file) {
5615                         file = calloc(1, sizeof(*file));
5616                         if (!file || !add_line_data(view, file, type))
5617                                 goto error_out;
5618                 }
5620                 /* Parse diff info part. */
5621                 if (status) {
5622                         file->status = status;
5623                         if (status == 'A')
5624                                 string_copy(file->old.rev, NULL_ID);
5626                 } else if (!file->status || file == unmerged) {
5627                         if (!status_get_diff(file, buf, strlen(buf)))
5628                                 goto error_out;
5630                         buf = io_get(&io, 0, TRUE);
5631                         if (!buf)
5632                                 break;
5634                         /* Collapse all modified entries that follow an
5635                          * associated unmerged entry. */
5636                         if (unmerged == file) {
5637                                 unmerged->status = 'U';
5638                                 unmerged = NULL;
5639                         } else if (file->status == 'U') {
5640                                 unmerged = file;
5641                         }
5642                 }
5644                 /* Grab the old name for rename/copy. */
5645                 if (!*file->old.name &&
5646                     (file->status == 'R' || file->status == 'C')) {
5647                         string_ncopy(file->old.name, buf, strlen(buf));
5649                         buf = io_get(&io, 0, TRUE);
5650                         if (!buf)
5651                                 break;
5652                 }
5654                 /* git-ls-files just delivers a NUL separated list of
5655                  * file names similar to the second half of the
5656                  * git-diff-* output. */
5657                 string_ncopy(file->new.name, buf, strlen(buf));
5658                 if (!*file->old.name)
5659                         string_copy(file->old.name, file->new.name);
5660                 file = NULL;
5661         }
5663         if (io_error(&io)) {
5664 error_out:
5665                 io_done(&io);
5666                 return FALSE;
5667         }
5669         if (!view->line[view->lines - 1].data)
5670                 add_line_data(view, NULL, LINE_STAT_NONE);
5672         io_done(&io);
5673         return TRUE;
5676 /* Don't show unmerged entries in the staged section. */
5677 static const char *status_diff_index_argv[] = {
5678         "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5679                              "--cached", "-M", "HEAD", NULL
5680 };
5682 static const char *status_diff_files_argv[] = {
5683         "git", "diff-files", "-z", NULL
5684 };
5686 static const char *status_list_other_argv[] = {
5687         "git", "ls-files", "-z", "--others", "--exclude-standard", opt_prefix, NULL
5688 };
5690 static const char *status_list_no_head_argv[] = {
5691         "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5692 };
5694 static const char *update_index_argv[] = {
5695         "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5696 };
5698 /* Restore the previous line number to stay in the context or select a
5699  * line with something that can be updated. */
5700 static void
5701 status_restore(struct view *view)
5703         if (view->p_lineno >= view->lines)
5704                 view->p_lineno = view->lines - 1;
5705         while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
5706                 view->p_lineno++;
5707         while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
5708                 view->p_lineno--;
5710         /* If the above fails, always skip the "On branch" line. */
5711         if (view->p_lineno < view->lines)
5712                 view->lineno = view->p_lineno;
5713         else
5714                 view->lineno = 1;
5716         if (view->lineno < view->offset)
5717                 view->offset = view->lineno;
5718         else if (view->offset + view->height <= view->lineno)
5719                 view->offset = view->lineno - view->height + 1;
5721         view->p_restore = FALSE;
5724 static void
5725 status_update_onbranch(void)
5727         static const char *paths[][2] = {
5728                 { "rebase-apply/rebasing",      "Rebasing" },
5729                 { "rebase-apply/applying",      "Applying mailbox" },
5730                 { "rebase-apply/",              "Rebasing mailbox" },
5731                 { "rebase-merge/interactive",   "Interactive rebase" },
5732                 { "rebase-merge/",              "Rebase merge" },
5733                 { "MERGE_HEAD",                 "Merging" },
5734                 { "BISECT_LOG",                 "Bisecting" },
5735                 { "HEAD",                       "On branch" },
5736         };
5737         char buf[SIZEOF_STR];
5738         struct stat stat;
5739         int i;
5741         if (is_initial_commit()) {
5742                 string_copy(status_onbranch, "Initial commit");
5743                 return;
5744         }
5746         for (i = 0; i < ARRAY_SIZE(paths); i++) {
5747                 char *head = opt_head;
5749                 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5750                     lstat(buf, &stat) < 0)
5751                         continue;
5753                 if (!*opt_head) {
5754                         struct io io;
5756                         if (io_open(&io, "%s/rebase-merge/head-name", opt_git_dir) &&
5757                             io_read_buf(&io, buf, sizeof(buf))) {
5758                                 head = buf;
5759                                 if (!prefixcmp(head, "refs/heads/"))
5760                                         head += STRING_SIZE("refs/heads/");
5761                         }
5762                 }
5764                 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5765                         string_copy(status_onbranch, opt_head);
5766                 return;
5767         }
5769         string_copy(status_onbranch, "Not currently on any branch");
5772 /* First parse staged info using git-diff-index(1), then parse unstaged
5773  * info using git-diff-files(1), and finally untracked files using
5774  * git-ls-files(1). */
5775 static bool
5776 status_open(struct view *view)
5778         reset_view(view);
5780         add_line_data(view, NULL, LINE_STAT_HEAD);
5781         status_update_onbranch();
5783         io_run_bg(update_index_argv);
5785         if (is_initial_commit()) {
5786                 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5787                         return FALSE;
5788         } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5789                 return FALSE;
5790         }
5792         if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5793             !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5794                 return FALSE;
5796         /* Restore the exact position or use the specialized restore
5797          * mode? */
5798         if (!view->p_restore)
5799                 status_restore(view);
5800         return TRUE;
5803 static bool
5804 status_draw(struct view *view, struct line *line, unsigned int lineno)
5806         struct status *status = line->data;
5807         enum line_type type;
5808         const char *text;
5810         if (!status) {
5811                 switch (line->type) {
5812                 case LINE_STAT_STAGED:
5813                         type = LINE_STAT_SECTION;
5814                         text = "Changes to be committed:";
5815                         break;
5817                 case LINE_STAT_UNSTAGED:
5818                         type = LINE_STAT_SECTION;
5819                         text = "Changed but not updated:";
5820                         break;
5822                 case LINE_STAT_UNTRACKED:
5823                         type = LINE_STAT_SECTION;
5824                         text = "Untracked files:";
5825                         break;
5827                 case LINE_STAT_NONE:
5828                         type = LINE_DEFAULT;
5829                         text = "  (no files)";
5830                         break;
5832                 case LINE_STAT_HEAD:
5833                         type = LINE_STAT_HEAD;
5834                         text = status_onbranch;
5835                         break;
5837                 default:
5838                         return FALSE;
5839                 }
5840         } else {
5841                 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5843                 buf[0] = status->status;
5844                 if (draw_text(view, line->type, buf, TRUE))
5845                         return TRUE;
5846                 type = LINE_DEFAULT;
5847                 text = status->new.name;
5848         }
5850         draw_text(view, type, text, TRUE);
5851         return TRUE;
5854 static enum request
5855 status_load_error(struct view *view, struct view *stage, const char *path)
5857         if (displayed_views() == 2 || display[current_view] != view)
5858                 maximize_view(view);
5859         report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5860         return REQ_NONE;
5863 static enum request
5864 status_enter(struct view *view, struct line *line)
5866         struct status *status = line->data;
5867         const char *oldpath = status ? status->old.name : NULL;
5868         /* Diffs for unmerged entries are empty when passing the new
5869          * path, so leave it empty. */
5870         const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5871         const char *info;
5872         enum open_flags split;
5873         struct view *stage = VIEW(REQ_VIEW_STAGE);
5875         if (line->type == LINE_STAT_NONE ||
5876             (!status && line[1].type == LINE_STAT_NONE)) {
5877                 report("No file to diff");
5878                 return REQ_NONE;
5879         }
5881         switch (line->type) {
5882         case LINE_STAT_STAGED:
5883                 if (is_initial_commit()) {
5884                         const char *no_head_diff_argv[] = {
5885                                 "git", "diff", "--no-color", "--patch-with-stat",
5886                                         "--", "/dev/null", newpath, NULL
5887                         };
5889                         if (!prepare_update(stage, no_head_diff_argv, opt_cdup))
5890                                 return status_load_error(view, stage, newpath);
5891                 } else {
5892                         const char *index_show_argv[] = {
5893                                 "git", "diff-index", "--root", "--patch-with-stat",
5894                                         "-C", "-M", "--cached", "HEAD", "--",
5895                                         oldpath, newpath, NULL
5896                         };
5898                         if (!prepare_update(stage, index_show_argv, opt_cdup))
5899                                 return status_load_error(view, stage, newpath);
5900                 }
5902                 if (status)
5903                         info = "Staged changes to %s";
5904                 else
5905                         info = "Staged changes";
5906                 break;
5908         case LINE_STAT_UNSTAGED:
5909         {
5910                 const char *files_show_argv[] = {
5911                         "git", "diff-files", "--root", "--patch-with-stat",
5912                                 "-C", "-M", "--", oldpath, newpath, NULL
5913                 };
5915                 if (!prepare_update(stage, files_show_argv, opt_cdup))
5916                         return status_load_error(view, stage, newpath);
5917                 if (status)
5918                         info = "Unstaged changes to %s";
5919                 else
5920                         info = "Unstaged changes";
5921                 break;
5922         }
5923         case LINE_STAT_UNTRACKED:
5924                 if (!newpath) {
5925                         report("No file to show");
5926                         return REQ_NONE;
5927                 }
5929                 if (!suffixcmp(status->new.name, -1, "/")) {
5930                         report("Cannot display a directory");
5931                         return REQ_NONE;
5932                 }
5934                 if (!prepare_update_file(stage, newpath))
5935                         return status_load_error(view, stage, newpath);
5936                 info = "Untracked file %s";
5937                 break;
5939         case LINE_STAT_HEAD:
5940                 return REQ_NONE;
5942         default:
5943                 die("line type %d not handled in switch", line->type);
5944         }
5946         split = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
5947         open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5948         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5949                 if (status) {
5950                         stage_status = *status;
5951                 } else {
5952                         memset(&stage_status, 0, sizeof(stage_status));
5953                 }
5955                 stage_line_type = line->type;
5956                 stage_chunks = 0;
5957                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5958         }
5960         return REQ_NONE;
5963 static bool
5964 status_exists(struct status *status, enum line_type type)
5966         struct view *view = VIEW(REQ_VIEW_STATUS);
5967         unsigned long lineno;
5969         for (lineno = 0; lineno < view->lines; lineno++) {
5970                 struct line *line = &view->line[lineno];
5971                 struct status *pos = line->data;
5973                 if (line->type != type)
5974                         continue;
5975                 if (!pos && (!status || !status->status) && line[1].data) {
5976                         select_view_line(view, lineno);
5977                         return TRUE;
5978                 }
5979                 if (pos && !strcmp(status->new.name, pos->new.name)) {
5980                         select_view_line(view, lineno);
5981                         return TRUE;
5982                 }
5983         }
5985         return FALSE;
5989 static bool
5990 status_update_prepare(struct io *io, enum line_type type)
5992         const char *staged_argv[] = {
5993                 "git", "update-index", "-z", "--index-info", NULL
5994         };
5995         const char *others_argv[] = {
5996                 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
5997         };
5999         switch (type) {
6000         case LINE_STAT_STAGED:
6001                 return io_run(io, IO_WR, opt_cdup, staged_argv);
6003         case LINE_STAT_UNSTAGED:
6004         case LINE_STAT_UNTRACKED:
6005                 return io_run(io, IO_WR, opt_cdup, others_argv);
6007         default:
6008                 die("line type %d not handled in switch", type);
6009                 return FALSE;
6010         }
6013 static bool
6014 status_update_write(struct io *io, struct status *status, enum line_type type)
6016         char buf[SIZEOF_STR];
6017         size_t bufsize = 0;
6019         switch (type) {
6020         case LINE_STAT_STAGED:
6021                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
6022                                         status->old.mode,
6023                                         status->old.rev,
6024                                         status->old.name, 0))
6025                         return FALSE;
6026                 break;
6028         case LINE_STAT_UNSTAGED:
6029         case LINE_STAT_UNTRACKED:
6030                 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
6031                         return FALSE;
6032                 break;
6034         default:
6035                 die("line type %d not handled in switch", type);
6036         }
6038         return io_write(io, buf, bufsize);
6041 static bool
6042 status_update_file(struct status *status, enum line_type type)
6044         struct io io;
6045         bool result;
6047         if (!status_update_prepare(&io, type))
6048                 return FALSE;
6050         result = status_update_write(&io, status, type);
6051         return io_done(&io) && result;
6054 static bool
6055 status_update_files(struct view *view, struct line *line)
6057         char buf[sizeof(view->ref)];
6058         struct io io;
6059         bool result = TRUE;
6060         struct line *pos = view->line + view->lines;
6061         int files = 0;
6062         int file, done;
6063         int cursor_y = -1, cursor_x = -1;
6065         if (!status_update_prepare(&io, line->type))
6066                 return FALSE;
6068         for (pos = line; pos < view->line + view->lines && pos->data; pos++)
6069                 files++;
6071         string_copy(buf, view->ref);
6072         getsyx(cursor_y, cursor_x);
6073         for (file = 0, done = 5; result && file < files; line++, file++) {
6074                 int almost_done = file * 100 / files;
6076                 if (almost_done > done) {
6077                         done = almost_done;
6078                         string_format(view->ref, "updating file %u of %u (%d%% done)",
6079                                       file, files, done);
6080                         update_view_title(view);
6081                         setsyx(cursor_y, cursor_x);
6082                         doupdate();
6083                 }
6084                 result = status_update_write(&io, line->data, line->type);
6085         }
6086         string_copy(view->ref, buf);
6088         return io_done(&io) && result;
6091 static bool
6092 status_update(struct view *view)
6094         struct line *line = &view->line[view->lineno];
6096         assert(view->lines);
6098         if (!line->data) {
6099                 /* This should work even for the "On branch" line. */
6100                 if (line < view->line + view->lines && !line[1].data) {
6101                         report("Nothing to update");
6102                         return FALSE;
6103                 }
6105                 if (!status_update_files(view, line + 1)) {
6106                         report("Failed to update file status");
6107                         return FALSE;
6108                 }
6110         } else if (!status_update_file(line->data, line->type)) {
6111                 report("Failed to update file status");
6112                 return FALSE;
6113         }
6115         return TRUE;
6118 static bool
6119 status_revert(struct status *status, enum line_type type, bool has_none)
6121         if (!status || type != LINE_STAT_UNSTAGED) {
6122                 if (type == LINE_STAT_STAGED) {
6123                         report("Cannot revert changes to staged files");
6124                 } else if (type == LINE_STAT_UNTRACKED) {
6125                         report("Cannot revert changes to untracked files");
6126                 } else if (has_none) {
6127                         report("Nothing to revert");
6128                 } else {
6129                         report("Cannot revert changes to multiple files");
6130                 }
6132         } else if (prompt_yesno("Are you sure you want to revert changes?")) {
6133                 char mode[10] = "100644";
6134                 const char *reset_argv[] = {
6135                         "git", "update-index", "--cacheinfo", mode,
6136                                 status->old.rev, status->old.name, NULL
6137                 };
6138                 const char *checkout_argv[] = {
6139                         "git", "checkout", "--", status->old.name, NULL
6140                 };
6142                 if (status->status == 'U') {
6143                         string_format(mode, "%5o", status->old.mode);
6145                         if (status->old.mode == 0 && status->new.mode == 0) {
6146                                 reset_argv[2] = "--force-remove";
6147                                 reset_argv[3] = status->old.name;
6148                                 reset_argv[4] = NULL;
6149                         }
6151                         if (!io_run_fg(reset_argv, opt_cdup))
6152                                 return FALSE;
6153                         if (status->old.mode == 0 && status->new.mode == 0)
6154                                 return TRUE;
6155                 }
6157                 return io_run_fg(checkout_argv, opt_cdup);
6158         }
6160         return FALSE;
6163 static enum request
6164 status_request(struct view *view, enum request request, struct line *line)
6166         struct status *status = line->data;
6168         switch (request) {
6169         case REQ_STATUS_UPDATE:
6170                 if (!status_update(view))
6171                         return REQ_NONE;
6172                 break;
6174         case REQ_STATUS_REVERT:
6175                 if (!status_revert(status, line->type, status_has_none(view, line)))
6176                         return REQ_NONE;
6177                 break;
6179         case REQ_STATUS_MERGE:
6180                 if (!status || status->status != 'U') {
6181                         report("Merging only possible for files with unmerged status ('U').");
6182                         return REQ_NONE;
6183                 }
6184                 open_mergetool(status->new.name);
6185                 break;
6187         case REQ_EDIT:
6188                 if (!status)
6189                         return request;
6190                 if (status->status == 'D') {
6191                         report("File has been deleted.");
6192                         return REQ_NONE;
6193                 }
6195                 open_editor(status->new.name);
6196                 break;
6198         case REQ_VIEW_BLAME:
6199                 if (status)
6200                         opt_ref[0] = 0;
6201                 return request;
6203         case REQ_ENTER:
6204                 /* After returning the status view has been split to
6205                  * show the stage view. No further reloading is
6206                  * necessary. */
6207                 return status_enter(view, line);
6209         case REQ_REFRESH:
6210                 /* Simply reload the view. */
6211                 break;
6213         default:
6214                 return request;
6215         }
6217         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
6219         return REQ_NONE;
6222 static void
6223 status_select(struct view *view, struct line *line)
6225         struct status *status = line->data;
6226         char file[SIZEOF_STR] = "all files";
6227         const char *text;
6228         const char *key;
6230         if (status && !string_format(file, "'%s'", status->new.name))
6231                 return;
6233         if (!status && line[1].type == LINE_STAT_NONE)
6234                 line++;
6236         switch (line->type) {
6237         case LINE_STAT_STAGED:
6238                 text = "Press %s to unstage %s for commit";
6239                 break;
6241         case LINE_STAT_UNSTAGED:
6242                 text = "Press %s to stage %s for commit";
6243                 break;
6245         case LINE_STAT_UNTRACKED:
6246                 text = "Press %s to stage %s for addition";
6247                 break;
6249         case LINE_STAT_HEAD:
6250         case LINE_STAT_NONE:
6251                 text = "Nothing to update";
6252                 break;
6254         default:
6255                 die("line type %d not handled in switch", line->type);
6256         }
6258         if (status && status->status == 'U') {
6259                 text = "Press %s to resolve conflict in %s";
6260                 key = get_key(KEYMAP_STATUS, REQ_STATUS_MERGE);
6262         } else {
6263                 key = get_key(KEYMAP_STATUS, REQ_STATUS_UPDATE);
6264         }
6266         string_format(view->ref, text, key, file);
6267         if (status)
6268                 string_copy(opt_file, status->new.name);
6271 static bool
6272 status_grep(struct view *view, struct line *line)
6274         struct status *status = line->data;
6276         if (status) {
6277                 const char buf[2] = { status->status, 0 };
6278                 const char *text[] = { status->new.name, buf, NULL };
6280                 return grep_text(view, text);
6281         }
6283         return FALSE;
6286 static struct view_ops status_ops = {
6287         "file",
6288         NULL,
6289         status_open,
6290         NULL,
6291         status_draw,
6292         status_request,
6293         status_grep,
6294         status_select,
6295 };
6298 static bool
6299 stage_diff_write(struct io *io, struct line *line, struct line *end)
6301         while (line < end) {
6302                 if (!io_write(io, line->data, strlen(line->data)) ||
6303                     !io_write(io, "\n", 1))
6304                         return FALSE;
6305                 line++;
6306                 if (line->type == LINE_DIFF_CHUNK ||
6307                     line->type == LINE_DIFF_HEADER)
6308                         break;
6309         }
6311         return TRUE;
6314 static struct line *
6315 stage_diff_find(struct view *view, struct line *line, enum line_type type)
6317         for (; view->line < line; line--)
6318                 if (line->type == type)
6319                         return line;
6321         return NULL;
6324 static bool
6325 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
6327         const char *apply_argv[SIZEOF_ARG] = {
6328                 "git", "apply", "--whitespace=nowarn", NULL
6329         };
6330         struct line *diff_hdr;
6331         struct io io;
6332         int argc = 3;
6334         diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
6335         if (!diff_hdr)
6336                 return FALSE;
6338         if (!revert)
6339                 apply_argv[argc++] = "--cached";
6340         if (revert || stage_line_type == LINE_STAT_STAGED)
6341                 apply_argv[argc++] = "-R";
6342         apply_argv[argc++] = "-";
6343         apply_argv[argc++] = NULL;
6344         if (!io_run(&io, IO_WR, opt_cdup, apply_argv))
6345                 return FALSE;
6347         if (!stage_diff_write(&io, diff_hdr, chunk) ||
6348             !stage_diff_write(&io, chunk, view->line + view->lines))
6349                 chunk = NULL;
6351         io_done(&io);
6352         io_run_bg(update_index_argv);
6354         return chunk ? TRUE : FALSE;
6357 static bool
6358 stage_update(struct view *view, struct line *line)
6360         struct line *chunk = NULL;
6362         if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
6363                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6365         if (chunk) {
6366                 if (!stage_apply_chunk(view, chunk, FALSE)) {
6367                         report("Failed to apply chunk");
6368                         return FALSE;
6369                 }
6371         } else if (!stage_status.status) {
6372                 view = VIEW(REQ_VIEW_STATUS);
6374                 for (line = view->line; line < view->line + view->lines; line++)
6375                         if (line->type == stage_line_type)
6376                                 break;
6378                 if (!status_update_files(view, line + 1)) {
6379                         report("Failed to update files");
6380                         return FALSE;
6381                 }
6383         } else if (!status_update_file(&stage_status, stage_line_type)) {
6384                 report("Failed to update file");
6385                 return FALSE;
6386         }
6388         return TRUE;
6391 static bool
6392 stage_revert(struct view *view, struct line *line)
6394         struct line *chunk = NULL;
6396         if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
6397                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6399         if (chunk) {
6400                 if (!prompt_yesno("Are you sure you want to revert changes?"))
6401                         return FALSE;
6403                 if (!stage_apply_chunk(view, chunk, TRUE)) {
6404                         report("Failed to revert chunk");
6405                         return FALSE;
6406                 }
6407                 return TRUE;
6409         } else {
6410                 return status_revert(stage_status.status ? &stage_status : NULL,
6411                                      stage_line_type, FALSE);
6412         }
6416 static void
6417 stage_next(struct view *view, struct line *line)
6419         int i;
6421         if (!stage_chunks) {
6422                 for (line = view->line; line < view->line + view->lines; line++) {
6423                         if (line->type != LINE_DIFF_CHUNK)
6424                                 continue;
6426                         if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
6427                                 report("Allocation failure");
6428                                 return;
6429                         }
6431                         stage_chunk[stage_chunks++] = line - view->line;
6432                 }
6433         }
6435         for (i = 0; i < stage_chunks; i++) {
6436                 if (stage_chunk[i] > view->lineno) {
6437                         do_scroll_view(view, stage_chunk[i] - view->lineno);
6438                         report("Chunk %d of %d", i + 1, stage_chunks);
6439                         return;
6440                 }
6441         }
6443         report("No next chunk found");
6446 static enum request
6447 stage_request(struct view *view, enum request request, struct line *line)
6449         switch (request) {
6450         case REQ_STATUS_UPDATE:
6451                 if (!stage_update(view, line))
6452                         return REQ_NONE;
6453                 break;
6455         case REQ_STATUS_REVERT:
6456                 if (!stage_revert(view, line))
6457                         return REQ_NONE;
6458                 break;
6460         case REQ_STAGE_NEXT:
6461                 if (stage_line_type == LINE_STAT_UNTRACKED) {
6462                         report("File is untracked; press %s to add",
6463                                get_key(KEYMAP_STAGE, REQ_STATUS_UPDATE));
6464                         return REQ_NONE;
6465                 }
6466                 stage_next(view, line);
6467                 return REQ_NONE;
6469         case REQ_EDIT:
6470                 if (!stage_status.new.name[0])
6471                         return request;
6472                 if (stage_status.status == 'D') {
6473                         report("File has been deleted.");
6474                         return REQ_NONE;
6475                 }
6477                 open_editor(stage_status.new.name);
6478                 break;
6480         case REQ_REFRESH:
6481                 /* Reload everything ... */
6482                 break;
6484         case REQ_VIEW_BLAME:
6485                 if (stage_status.new.name[0]) {
6486                         string_copy(opt_file, stage_status.new.name);
6487                         opt_ref[0] = 0;
6488                 }
6489                 return request;
6491         case REQ_ENTER:
6492                 return pager_request(view, request, line);
6494         default:
6495                 return request;
6496         }
6498         VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
6499         open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
6501         /* Check whether the staged entry still exists, and close the
6502          * stage view if it doesn't. */
6503         if (!status_exists(&stage_status, stage_line_type)) {
6504                 status_restore(VIEW(REQ_VIEW_STATUS));
6505                 return REQ_VIEW_CLOSE;
6506         }
6508         if (stage_line_type == LINE_STAT_UNTRACKED) {
6509                 if (!suffixcmp(stage_status.new.name, -1, "/")) {
6510                         report("Cannot display a directory");
6511                         return REQ_NONE;
6512                 }
6514                 if (!prepare_update_file(view, stage_status.new.name)) {
6515                         report("Failed to open file: %s", strerror(errno));
6516                         return REQ_NONE;
6517                 }
6518         }
6519         open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
6521         return REQ_NONE;
6524 static struct view_ops stage_ops = {
6525         "line",
6526         NULL,
6527         NULL,
6528         pager_read,
6529         pager_draw,
6530         stage_request,
6531         pager_grep,
6532         pager_select,
6533 };
6536 /*
6537  * Revision graph
6538  */
6540 struct commit {
6541         char id[SIZEOF_REV];            /* SHA1 ID. */
6542         char title[128];                /* First line of the commit message. */
6543         const char *author;             /* Author of the commit. */
6544         struct time time;               /* Date from the author ident. */
6545         struct ref_list *refs;          /* Repository references. */
6546         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
6547         size_t graph_size;              /* The width of the graph array. */
6548         bool has_parents;               /* Rewritten --parents seen. */
6549 };
6551 /* Size of rev graph with no  "padding" columns */
6552 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6554 struct rev_graph {
6555         struct rev_graph *prev, *next, *parents;
6556         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
6557         size_t size;
6558         struct commit *commit;
6559         size_t pos;
6560         unsigned int boundary:1;
6561 };
6563 /* Parents of the commit being visualized. */
6564 static struct rev_graph graph_parents[4];
6566 /* The current stack of revisions on the graph. */
6567 static struct rev_graph graph_stacks[4] = {
6568         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
6569         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
6570         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
6571         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
6572 };
6574 static inline bool
6575 graph_parent_is_merge(struct rev_graph *graph)
6577         return graph->parents->size > 1;
6580 static inline void
6581 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
6583         struct commit *commit = graph->commit;
6585         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
6586                 commit->graph[commit->graph_size++] = symbol;
6589 static void
6590 clear_rev_graph(struct rev_graph *graph)
6592         graph->boundary = 0;
6593         graph->size = graph->pos = 0;
6594         graph->commit = NULL;
6595         memset(graph->parents, 0, sizeof(*graph->parents));
6598 static void
6599 done_rev_graph(struct rev_graph *graph)
6601         if (graph_parent_is_merge(graph) &&
6602             graph->pos < graph->size - 1 &&
6603             graph->next->size == graph->size + graph->parents->size - 1) {
6604                 size_t i = graph->pos + graph->parents->size - 1;
6606                 graph->commit->graph_size = i * 2;
6607                 while (i < graph->next->size - 1) {
6608                         append_to_rev_graph(graph, ' ');
6609                         append_to_rev_graph(graph, '\\');
6610                         i++;
6611                 }
6612         }
6614         clear_rev_graph(graph);
6617 static void
6618 push_rev_graph(struct rev_graph *graph, const char *parent)
6620         int i;
6622         /* "Collapse" duplicate parents lines.
6623          *
6624          * FIXME: This needs to also update update the drawn graph but
6625          * for now it just serves as a method for pruning graph lines. */
6626         for (i = 0; i < graph->size; i++)
6627                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
6628                         return;
6630         if (graph->size < SIZEOF_REVITEMS) {
6631                 string_copy_rev(graph->rev[graph->size++], parent);
6632         }
6635 static chtype
6636 get_rev_graph_symbol(struct rev_graph *graph)
6638         chtype symbol;
6640         if (graph->boundary)
6641                 symbol = REVGRAPH_BOUND;
6642         else if (graph->parents->size == 0)
6643                 symbol = REVGRAPH_INIT;
6644         else if (graph_parent_is_merge(graph))
6645                 symbol = REVGRAPH_MERGE;
6646         else if (graph->pos >= graph->size)
6647                 symbol = REVGRAPH_BRANCH;
6648         else
6649                 symbol = REVGRAPH_COMMIT;
6651         return symbol;
6654 static void
6655 draw_rev_graph(struct rev_graph *graph)
6657         struct rev_filler {
6658                 chtype separator, line;
6659         };
6660         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
6661         static struct rev_filler fillers[] = {
6662                 { ' ',  '|' },
6663                 { '`',  '.' },
6664                 { '\'', ' ' },
6665                 { '/',  ' ' },
6666         };
6667         chtype symbol = get_rev_graph_symbol(graph);
6668         struct rev_filler *filler;
6669         size_t i;
6671         fillers[DEFAULT].line = opt_line_graphics ? ACS_VLINE : '|';
6672         filler = &fillers[DEFAULT];
6674         for (i = 0; i < graph->pos; i++) {
6675                 append_to_rev_graph(graph, filler->line);
6676                 if (graph_parent_is_merge(graph->prev) &&
6677                     graph->prev->pos == i)
6678                         filler = &fillers[RSHARP];
6680                 append_to_rev_graph(graph, filler->separator);
6681         }
6683         /* Place the symbol for this revision. */
6684         append_to_rev_graph(graph, symbol);
6686         if (graph->prev->size > graph->size)
6687                 filler = &fillers[RDIAG];
6688         else
6689                 filler = &fillers[DEFAULT];
6691         i++;
6693         for (; i < graph->size; i++) {
6694                 append_to_rev_graph(graph, filler->separator);
6695                 append_to_rev_graph(graph, filler->line);
6696                 if (graph_parent_is_merge(graph->prev) &&
6697                     i < graph->prev->pos + graph->parents->size)
6698                         filler = &fillers[RSHARP];
6699                 if (graph->prev->size > graph->size)
6700                         filler = &fillers[LDIAG];
6701         }
6703         if (graph->prev->size > graph->size) {
6704                 append_to_rev_graph(graph, filler->separator);
6705                 if (filler->line != ' ')
6706                         append_to_rev_graph(graph, filler->line);
6707         }
6710 /* Prepare the next rev graph */
6711 static void
6712 prepare_rev_graph(struct rev_graph *graph)
6714         size_t i;
6716         /* First, traverse all lines of revisions up to the active one. */
6717         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6718                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6719                         break;
6721                 push_rev_graph(graph->next, graph->rev[graph->pos]);
6722         }
6724         /* Interleave the new revision parent(s). */
6725         for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6726                 push_rev_graph(graph->next, graph->parents->rev[i]);
6728         /* Lastly, put any remaining revisions. */
6729         for (i = graph->pos + 1; i < graph->size; i++)
6730                 push_rev_graph(graph->next, graph->rev[i]);
6733 static void
6734 update_rev_graph(struct view *view, struct rev_graph *graph)
6736         /* If this is the finalizing update ... */
6737         if (graph->commit)
6738                 prepare_rev_graph(graph);
6740         /* Graph visualization needs a one rev look-ahead,
6741          * so the first update doesn't visualize anything. */
6742         if (!graph->prev->commit)
6743                 return;
6745         if (view->lines > 2)
6746                 view->line[view->lines - 3].dirty = 1;
6747         if (view->lines > 1)
6748                 view->line[view->lines - 2].dirty = 1;
6749         draw_rev_graph(graph->prev);
6750         done_rev_graph(graph->prev->prev);
6754 /*
6755  * Main view backend
6756  */
6758 static const char *main_argv[SIZEOF_ARG] = {
6759         "git", "log", "--no-color", "--pretty=raw", "--parents",
6760                 "--topo-order", "%(diffargs)", "%(revargs)",
6761                 "--", "%(fileargs)", NULL
6762 };
6764 static bool
6765 main_draw(struct view *view, struct line *line, unsigned int lineno)
6767         struct commit *commit = line->data;
6769         if (!commit->author)
6770                 return FALSE;
6772         if (opt_date && draw_date(view, &commit->time))
6773                 return TRUE;
6775         if (opt_author && draw_author(view, commit->author))
6776                 return TRUE;
6778         if (opt_rev_graph && commit->graph_size &&
6779             draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6780                 return TRUE;
6782         if (opt_show_refs && commit->refs) {
6783                 size_t i;
6785                 for (i = 0; i < commit->refs->size; i++) {
6786                         struct ref *ref = commit->refs->refs[i];
6787                         enum line_type type;
6789                         if (ref->head)
6790                                 type = LINE_MAIN_HEAD;
6791                         else if (ref->ltag)
6792                                 type = LINE_MAIN_LOCAL_TAG;
6793                         else if (ref->tag)
6794                                 type = LINE_MAIN_TAG;
6795                         else if (ref->tracked)
6796                                 type = LINE_MAIN_TRACKED;
6797                         else if (ref->remote)
6798                                 type = LINE_MAIN_REMOTE;
6799                         else
6800                                 type = LINE_MAIN_REF;
6802                         if (draw_text(view, type, "[", TRUE) ||
6803                             draw_text(view, type, ref->name, TRUE) ||
6804                             draw_text(view, type, "]", TRUE))
6805                                 return TRUE;
6807                         if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6808                                 return TRUE;
6809                 }
6810         }
6812         draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6813         return TRUE;
6816 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6817 static bool
6818 main_read(struct view *view, char *line)
6820         static struct rev_graph *graph = graph_stacks;
6821         enum line_type type;
6822         struct commit *commit;
6824         if (!line) {
6825                 int i;
6827                 if (!view->lines && !view->prev)
6828                         die("No revisions match the given arguments.");
6829                 if (view->lines > 0) {
6830                         commit = view->line[view->lines - 1].data;
6831                         view->line[view->lines - 1].dirty = 1;
6832                         if (!commit->author) {
6833                                 view->lines--;
6834                                 free(commit);
6835                                 graph->commit = NULL;
6836                         }
6837                 }
6838                 update_rev_graph(view, graph);
6840                 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6841                         clear_rev_graph(&graph_stacks[i]);
6842                 return TRUE;
6843         }
6845         type = get_line_type(line);
6846         if (type == LINE_COMMIT) {
6847                 commit = calloc(1, sizeof(struct commit));
6848                 if (!commit)
6849                         return FALSE;
6851                 line += STRING_SIZE("commit ");
6852                 if (*line == '-') {
6853                         graph->boundary = 1;
6854                         line++;
6855                 }
6857                 string_copy_rev(commit->id, line);
6858                 commit->refs = get_ref_list(commit->id);
6859                 graph->commit = commit;
6860                 add_line_data(view, commit, LINE_MAIN_COMMIT);
6862                 while ((line = strchr(line, ' '))) {
6863                         line++;
6864                         push_rev_graph(graph->parents, line);
6865                         commit->has_parents = TRUE;
6866                 }
6867                 return TRUE;
6868         }
6870         if (!view->lines)
6871                 return TRUE;
6872         commit = view->line[view->lines - 1].data;
6874         switch (type) {
6875         case LINE_PARENT:
6876                 if (commit->has_parents)
6877                         break;
6878                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6879                 break;
6881         case LINE_AUTHOR:
6882                 parse_author_line(line + STRING_SIZE("author "),
6883                                   &commit->author, &commit->time);
6884                 update_rev_graph(view, graph);
6885                 graph = graph->next;
6886                 break;
6888         default:
6889                 /* Fill in the commit title if it has not already been set. */
6890                 if (commit->title[0])
6891                         break;
6893                 /* Require titles to start with a non-space character at the
6894                  * offset used by git log. */
6895                 if (strncmp(line, "    ", 4))
6896                         break;
6897                 line += 4;
6898                 /* Well, if the title starts with a whitespace character,
6899                  * try to be forgiving.  Otherwise we end up with no title. */
6900                 while (isspace(*line))
6901                         line++;
6902                 if (*line == '\0')
6903                         break;
6904                 /* FIXME: More graceful handling of titles; append "..." to
6905                  * shortened titles, etc. */
6907                 string_expand(commit->title, sizeof(commit->title), line, 1);
6908                 view->line[view->lines - 1].dirty = 1;
6909         }
6911         return TRUE;
6914 static enum request
6915 main_request(struct view *view, enum request request, struct line *line)
6917         enum open_flags flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
6919         switch (request) {
6920         case REQ_ENTER:
6921                 open_view(view, REQ_VIEW_DIFF, flags);
6922                 break;
6923         case REQ_REFRESH:
6924                 load_refs();
6925                 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6926                 break;
6927         default:
6928                 return request;
6929         }
6931         return REQ_NONE;
6934 static bool
6935 grep_refs(struct ref_list *list, regex_t *regex)
6937         regmatch_t pmatch;
6938         size_t i;
6940         if (!opt_show_refs || !list)
6941                 return FALSE;
6943         for (i = 0; i < list->size; i++) {
6944                 if (regexec(regex, list->refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6945                         return TRUE;
6946         }
6948         return FALSE;
6951 static bool
6952 main_grep(struct view *view, struct line *line)
6954         struct commit *commit = line->data;
6955         const char *text[] = {
6956                 commit->title,
6957                 opt_author ? commit->author : "",
6958                 mkdate(&commit->time, opt_date),
6959                 NULL
6960         };
6962         return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6965 static void
6966 main_select(struct view *view, struct line *line)
6968         struct commit *commit = line->data;
6970         string_copy_rev(view->ref, commit->id);
6971         string_copy_rev(ref_commit, view->ref);
6974 static struct view_ops main_ops = {
6975         "commit",
6976         main_argv,
6977         NULL,
6978         main_read,
6979         main_draw,
6980         main_request,
6981         main_grep,
6982         main_select,
6983 };
6986 /*
6987  * Status management
6988  */
6990 /* Whether or not the curses interface has been initialized. */
6991 static bool cursed = FALSE;
6993 /* Terminal hacks and workarounds. */
6994 static bool use_scroll_redrawwin;
6995 static bool use_scroll_status_wclear;
6997 /* The status window is used for polling keystrokes. */
6998 static WINDOW *status_win;
7000 /* Reading from the prompt? */
7001 static bool input_mode = FALSE;
7003 static bool status_empty = FALSE;
7005 /* Update status and title window. */
7006 static void
7007 report(const char *msg, ...)
7009         struct view *view = display[current_view];
7011         if (input_mode)
7012                 return;
7014         if (!view) {
7015                 char buf[SIZEOF_STR];
7016                 va_list args;
7018                 va_start(args, msg);
7019                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
7020                         buf[sizeof(buf) - 1] = 0;
7021                         buf[sizeof(buf) - 2] = '.';
7022                         buf[sizeof(buf) - 3] = '.';
7023                         buf[sizeof(buf) - 4] = '.';
7024                 }
7025                 va_end(args);
7026                 die("%s", buf);
7027         }
7029         if (!status_empty || *msg) {
7030                 va_list args;
7032                 va_start(args, msg);
7034                 wmove(status_win, 0, 0);
7035                 if (view->has_scrolled && use_scroll_status_wclear)
7036                         wclear(status_win);
7037                 if (*msg) {
7038                         vwprintw(status_win, msg, args);
7039                         status_empty = FALSE;
7040                 } else {
7041                         status_empty = TRUE;
7042                 }
7043                 wclrtoeol(status_win);
7044                 wnoutrefresh(status_win);
7046                 va_end(args);
7047         }
7049         update_view_title(view);
7052 static void
7053 init_display(void)
7055         const char *term;
7056         int x, y;
7058         /* Initialize the curses library */
7059         if (isatty(STDIN_FILENO)) {
7060                 cursed = !!initscr();
7061                 opt_tty = stdin;
7062         } else {
7063                 /* Leave stdin and stdout alone when acting as a pager. */
7064                 opt_tty = fopen("/dev/tty", "r+");
7065                 if (!opt_tty)
7066                         die("Failed to open /dev/tty");
7067                 cursed = !!newterm(NULL, opt_tty, opt_tty);
7068         }
7070         if (!cursed)
7071                 die("Failed to initialize curses");
7073         nonl();         /* Disable conversion and detect newlines from input. */
7074         cbreak();       /* Take input chars one at a time, no wait for \n */
7075         noecho();       /* Don't echo input */
7076         leaveok(stdscr, FALSE);
7078         if (has_colors())
7079                 init_colors();
7081         getmaxyx(stdscr, y, x);
7082         status_win = newwin(1, 0, y - 1, 0);
7083         if (!status_win)
7084                 die("Failed to create status window");
7086         /* Enable keyboard mapping */
7087         keypad(status_win, TRUE);
7088         wbkgdset(status_win, get_line_attr(LINE_STATUS));
7090         TABSIZE = opt_tab_size;
7092         term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
7093         if (term && !strcmp(term, "gnome-terminal")) {
7094                 /* In the gnome-terminal-emulator, the message from
7095                  * scrolling up one line when impossible followed by
7096                  * scrolling down one line causes corruption of the
7097                  * status line. This is fixed by calling wclear. */
7098                 use_scroll_status_wclear = TRUE;
7099                 use_scroll_redrawwin = FALSE;
7101         } else if (term && !strcmp(term, "xrvt-xpm")) {
7102                 /* No problems with full optimizations in xrvt-(unicode)
7103                  * and aterm. */
7104                 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
7106         } else {
7107                 /* When scrolling in (u)xterm the last line in the
7108                  * scrolling direction will update slowly. */
7109                 use_scroll_redrawwin = TRUE;
7110                 use_scroll_status_wclear = FALSE;
7111         }
7114 static int
7115 get_input(int prompt_position)
7117         struct view *view;
7118         int i, key, cursor_y, cursor_x;
7120         if (prompt_position)
7121                 input_mode = TRUE;
7123         while (TRUE) {
7124                 bool loading = FALSE;
7126                 foreach_view (view, i) {
7127                         update_view(view);
7128                         if (view_is_displayed(view) && view->has_scrolled &&
7129                             use_scroll_redrawwin)
7130                                 redrawwin(view->win);
7131                         view->has_scrolled = FALSE;
7132                         if (view->pipe)
7133                                 loading = TRUE;
7134                 }
7136                 /* Update the cursor position. */
7137                 if (prompt_position) {
7138                         getbegyx(status_win, cursor_y, cursor_x);
7139                         cursor_x = prompt_position;
7140                 } else {
7141                         view = display[current_view];
7142                         getbegyx(view->win, cursor_y, cursor_x);
7143                         cursor_x = view->width - 1;
7144                         cursor_y += view->lineno - view->offset;
7145                 }
7146                 setsyx(cursor_y, cursor_x);
7148                 /* Refresh, accept single keystroke of input */
7149                 doupdate();
7150                 nodelay(status_win, loading);
7151                 key = wgetch(status_win);
7153                 /* wgetch() with nodelay() enabled returns ERR when
7154                  * there's no input. */
7155                 if (key == ERR) {
7157                 } else if (key == KEY_RESIZE) {
7158                         int height, width;
7160                         getmaxyx(stdscr, height, width);
7162                         wresize(status_win, 1, width);
7163                         mvwin(status_win, height - 1, 0);
7164                         wnoutrefresh(status_win);
7165                         resize_display();
7166                         redraw_display(TRUE);
7168                 } else {
7169                         input_mode = FALSE;
7170                         return key;
7171                 }
7172         }
7175 static char *
7176 prompt_input(const char *prompt, input_handler handler, void *data)
7178         enum input_status status = INPUT_OK;
7179         static char buf[SIZEOF_STR];
7180         size_t pos = 0;
7182         buf[pos] = 0;
7184         while (status == INPUT_OK || status == INPUT_SKIP) {
7185                 int key;
7187                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
7188                 wclrtoeol(status_win);
7190                 key = get_input(pos + 1);
7191                 switch (key) {
7192                 case KEY_RETURN:
7193                 case KEY_ENTER:
7194                 case '\n':
7195                         status = pos ? INPUT_STOP : INPUT_CANCEL;
7196                         break;
7198                 case KEY_BACKSPACE:
7199                         if (pos > 0)
7200                                 buf[--pos] = 0;
7201                         else
7202                                 status = INPUT_CANCEL;
7203                         break;
7205                 case KEY_ESC:
7206                         status = INPUT_CANCEL;
7207                         break;
7209                 default:
7210                         if (pos >= sizeof(buf)) {
7211                                 report("Input string too long");
7212                                 return NULL;
7213                         }
7215                         status = handler(data, buf, key);
7216                         if (status == INPUT_OK)
7217                                 buf[pos++] = (char) key;
7218                 }
7219         }
7221         /* Clear the status window */
7222         status_empty = FALSE;
7223         report("");
7225         if (status == INPUT_CANCEL)
7226                 return NULL;
7228         buf[pos++] = 0;
7230         return buf;
7233 static enum input_status
7234 prompt_yesno_handler(void *data, char *buf, int c)
7236         if (c == 'y' || c == 'Y')
7237                 return INPUT_STOP;
7238         if (c == 'n' || c == 'N')
7239                 return INPUT_CANCEL;
7240         return INPUT_SKIP;
7243 static bool
7244 prompt_yesno(const char *prompt)
7246         char prompt2[SIZEOF_STR];
7248         if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
7249                 return FALSE;
7251         return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
7254 static enum input_status
7255 read_prompt_handler(void *data, char *buf, int c)
7257         return isprint(c) ? INPUT_OK : INPUT_SKIP;
7260 static char *
7261 read_prompt(const char *prompt)
7263         return prompt_input(prompt, read_prompt_handler, NULL);
7266 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected)
7268         enum input_status status = INPUT_OK;
7269         int size = 0;
7271         while (items[size].text)
7272                 size++;
7274         while (status == INPUT_OK) {
7275                 const struct menu_item *item = &items[*selected];
7276                 int key;
7277                 int i;
7279                 mvwprintw(status_win, 0, 0, "%s (%d of %d) ",
7280                           prompt, *selected + 1, size);
7281                 if (item->hotkey)
7282                         wprintw(status_win, "[%c] ", (char) item->hotkey);
7283                 wprintw(status_win, "%s", item->text);
7284                 wclrtoeol(status_win);
7286                 key = get_input(COLS - 1);
7287                 switch (key) {
7288                 case KEY_RETURN:
7289                 case KEY_ENTER:
7290                 case '\n':
7291                         status = INPUT_STOP;
7292                         break;
7294                 case KEY_LEFT:
7295                 case KEY_UP:
7296                         *selected = *selected - 1;
7297                         if (*selected < 0)
7298                                 *selected = size - 1;
7299                         break;
7301                 case KEY_RIGHT:
7302                 case KEY_DOWN:
7303                         *selected = (*selected + 1) % size;
7304                         break;
7306                 case KEY_ESC:
7307                         status = INPUT_CANCEL;
7308                         break;
7310                 default:
7311                         for (i = 0; items[i].text; i++)
7312                                 if (items[i].hotkey == key) {
7313                                         *selected = i;
7314                                         status = INPUT_STOP;
7315                                         break;
7316                                 }
7317                 }
7318         }
7320         /* Clear the status window */
7321         status_empty = FALSE;
7322         report("");
7324         return status != INPUT_CANCEL;
7327 /*
7328  * Repository properties
7329  */
7331 static struct ref **refs = NULL;
7332 static size_t refs_size = 0;
7333 static struct ref *refs_head = NULL;
7335 static struct ref_list **ref_lists = NULL;
7336 static size_t ref_lists_size = 0;
7338 DEFINE_ALLOCATOR(realloc_refs, struct ref *, 256)
7339 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
7340 DEFINE_ALLOCATOR(realloc_ref_lists, struct ref_list *, 8)
7342 static int
7343 compare_refs(const void *ref1_, const void *ref2_)
7345         const struct ref *ref1 = *(const struct ref **)ref1_;
7346         const struct ref *ref2 = *(const struct ref **)ref2_;
7348         if (ref1->tag != ref2->tag)
7349                 return ref2->tag - ref1->tag;
7350         if (ref1->ltag != ref2->ltag)
7351                 return ref2->ltag - ref2->ltag;
7352         if (ref1->head != ref2->head)
7353                 return ref2->head - ref1->head;
7354         if (ref1->tracked != ref2->tracked)
7355                 return ref2->tracked - ref1->tracked;
7356         if (ref1->remote != ref2->remote)
7357                 return ref2->remote - ref1->remote;
7358         return strcmp(ref1->name, ref2->name);
7361 static void
7362 foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data)
7364         size_t i;
7366         for (i = 0; i < refs_size; i++)
7367                 if (!visitor(data, refs[i]))
7368                         break;
7371 static struct ref *
7372 get_ref_head()
7374         return refs_head;
7377 static struct ref_list *
7378 get_ref_list(const char *id)
7380         struct ref_list *list;
7381         size_t i;
7383         for (i = 0; i < ref_lists_size; i++)
7384                 if (!strcmp(id, ref_lists[i]->id))
7385                         return ref_lists[i];
7387         if (!realloc_ref_lists(&ref_lists, ref_lists_size, 1))
7388                 return NULL;
7389         list = calloc(1, sizeof(*list));
7390         if (!list)
7391                 return NULL;
7393         for (i = 0; i < refs_size; i++) {
7394                 if (!strcmp(id, refs[i]->id) &&
7395                     realloc_refs_list(&list->refs, list->size, 1))
7396                         list->refs[list->size++] = refs[i];
7397         }
7399         if (!list->refs) {
7400                 free(list);
7401                 return NULL;
7402         }
7404         qsort(list->refs, list->size, sizeof(*list->refs), compare_refs);
7405         ref_lists[ref_lists_size++] = list;
7406         return list;
7409 static int
7410 read_ref(char *id, size_t idlen, char *name, size_t namelen)
7412         struct ref *ref = NULL;
7413         bool tag = FALSE;
7414         bool ltag = FALSE;
7415         bool remote = FALSE;
7416         bool tracked = FALSE;
7417         bool head = FALSE;
7418         int from = 0, to = refs_size - 1;
7420         if (!prefixcmp(name, "refs/tags/")) {
7421                 if (!suffixcmp(name, namelen, "^{}")) {
7422                         namelen -= 3;
7423                         name[namelen] = 0;
7424                 } else {
7425                         ltag = TRUE;
7426                 }
7428                 tag = TRUE;
7429                 namelen -= STRING_SIZE("refs/tags/");
7430                 name    += STRING_SIZE("refs/tags/");
7432         } else if (!prefixcmp(name, "refs/remotes/")) {
7433                 remote = TRUE;
7434                 namelen -= STRING_SIZE("refs/remotes/");
7435                 name    += STRING_SIZE("refs/remotes/");
7436                 tracked  = !strcmp(opt_remote, name);
7438         } else if (!prefixcmp(name, "refs/heads/")) {
7439                 namelen -= STRING_SIZE("refs/heads/");
7440                 name    += STRING_SIZE("refs/heads/");
7441                 if (!strncmp(opt_head, name, namelen))
7442                         return OK;
7444         } else if (!strcmp(name, "HEAD")) {
7445                 head     = TRUE;
7446                 if (*opt_head) {
7447                         namelen  = strlen(opt_head);
7448                         name     = opt_head;
7449                 }
7450         }
7452         /* If we are reloading or it's an annotated tag, replace the
7453          * previous SHA1 with the resolved commit id; relies on the fact
7454          * git-ls-remote lists the commit id of an annotated tag right
7455          * before the commit id it points to. */
7456         while (from <= to) {
7457                 size_t pos = (to + from) / 2;
7458                 int cmp = strcmp(name, refs[pos]->name);
7460                 if (!cmp) {
7461                         ref = refs[pos];
7462                         break;
7463                 }
7465                 if (cmp < 0)
7466                         to = pos - 1;
7467                 else
7468                         from = pos + 1;
7469         }
7471         if (!ref) {
7472                 if (!realloc_refs(&refs, refs_size, 1))
7473                         return ERR;
7474                 ref = calloc(1, sizeof(*ref) + namelen);
7475                 if (!ref)
7476                         return ERR;
7477                 memmove(refs + from + 1, refs + from,
7478                         (refs_size - from) * sizeof(*refs));
7479                 refs[from] = ref;
7480                 strncpy(ref->name, name, namelen);
7481                 refs_size++;
7482         }
7484         ref->head = head;
7485         ref->tag = tag;
7486         ref->ltag = ltag;
7487         ref->remote = remote;
7488         ref->tracked = tracked;
7489         string_copy_rev(ref->id, id);
7491         if (head)
7492                 refs_head = ref;
7493         return OK;
7496 static int
7497 load_refs(void)
7499         const char *head_argv[] = {
7500                 "git", "symbolic-ref", "HEAD", NULL
7501         };
7502         static const char *ls_remote_argv[SIZEOF_ARG] = {
7503                 "git", "ls-remote", opt_git_dir, NULL
7504         };
7505         static bool init = FALSE;
7506         size_t i;
7508         if (!init) {
7509                 if (!argv_from_env(ls_remote_argv, "TIG_LS_REMOTE"))
7510                         die("TIG_LS_REMOTE contains too many arguments");
7511                 init = TRUE;
7512         }
7514         if (!*opt_git_dir)
7515                 return OK;
7517         if (io_run_buf(head_argv, opt_head, sizeof(opt_head)) &&
7518             !prefixcmp(opt_head, "refs/heads/")) {
7519                 char *offset = opt_head + STRING_SIZE("refs/heads/");
7521                 memmove(opt_head, offset, strlen(offset) + 1);
7522         }
7524         refs_head = NULL;
7525         for (i = 0; i < refs_size; i++)
7526                 refs[i]->id[0] = 0;
7528         if (io_run_load(ls_remote_argv, "\t", read_ref) == ERR)
7529                 return ERR;
7531         /* Update the ref lists to reflect changes. */
7532         for (i = 0; i < ref_lists_size; i++) {
7533                 struct ref_list *list = ref_lists[i];
7534                 size_t old, new;
7536                 for (old = new = 0; old < list->size; old++)
7537                         if (!strcmp(list->id, list->refs[old]->id))
7538                                 list->refs[new++] = list->refs[old];
7539                 list->size = new;
7540         }
7542         return OK;
7545 static void
7546 set_remote_branch(const char *name, const char *value, size_t valuelen)
7548         if (!strcmp(name, ".remote")) {
7549                 string_ncopy(opt_remote, value, valuelen);
7551         } else if (*opt_remote && !strcmp(name, ".merge")) {
7552                 size_t from = strlen(opt_remote);
7554                 if (!prefixcmp(value, "refs/heads/"))
7555                         value += STRING_SIZE("refs/heads/");
7557                 if (!string_format_from(opt_remote, &from, "/%s", value))
7558                         opt_remote[0] = 0;
7559         }
7562 static void
7563 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
7565         const char *argv[SIZEOF_ARG] = { name, "=" };
7566         int argc = 1 + (cmd == option_set_command);
7567         int error = ERR;
7569         if (!argv_from_string(argv, &argc, value))
7570                 config_msg = "Too many option arguments";
7571         else
7572                 error = cmd(argc, argv);
7574         if (error == ERR)
7575                 warn("Option 'tig.%s': %s", name, config_msg);
7578 static bool
7579 set_environment_variable(const char *name, const char *value)
7581         size_t len = strlen(name) + 1 + strlen(value) + 1;
7582         char *env = malloc(len);
7584         if (env &&
7585             string_nformat(env, len, NULL, "%s=%s", name, value) &&
7586             putenv(env) == 0)
7587                 return TRUE;
7588         free(env);
7589         return FALSE;
7592 static void
7593 set_work_tree(const char *value)
7595         char cwd[SIZEOF_STR];
7597         if (!getcwd(cwd, sizeof(cwd)))
7598                 die("Failed to get cwd path: %s", strerror(errno));
7599         if (chdir(opt_git_dir) < 0)
7600                 die("Failed to chdir(%s): %s", strerror(errno));
7601         if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7602                 die("Failed to get git path: %s", strerror(errno));
7603         if (chdir(cwd) < 0)
7604                 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7605         if (chdir(value) < 0)
7606                 die("Failed to chdir(%s): %s", value, strerror(errno));
7607         if (!getcwd(cwd, sizeof(cwd)))
7608                 die("Failed to get cwd path: %s", strerror(errno));
7609         if (!set_environment_variable("GIT_WORK_TREE", cwd))
7610                 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7611         if (!set_environment_variable("GIT_DIR", opt_git_dir))
7612                 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7613         opt_is_inside_work_tree = TRUE;
7616 static int
7617 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
7619         if (!strcmp(name, "i18n.commitencoding"))
7620                 string_ncopy(opt_encoding, value, valuelen);
7622         else if (!strcmp(name, "core.editor"))
7623                 string_ncopy(opt_editor, value, valuelen);
7625         else if (!strcmp(name, "core.worktree"))
7626                 set_work_tree(value);
7628         else if (!prefixcmp(name, "tig.color."))
7629                 set_repo_config_option(name + 10, value, option_color_command);
7631         else if (!prefixcmp(name, "tig.bind."))
7632                 set_repo_config_option(name + 9, value, option_bind_command);
7634         else if (!prefixcmp(name, "tig."))
7635                 set_repo_config_option(name + 4, value, option_set_command);
7637         else if (*opt_head && !prefixcmp(name, "branch.") &&
7638                  !strncmp(name + 7, opt_head, strlen(opt_head)))
7639                 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7641         return OK;
7644 static int
7645 load_git_config(void)
7647         const char *config_list_argv[] = { "git", "config", "--list", NULL };
7649         return io_run_load(config_list_argv, "=", read_repo_config_option);
7652 static int
7653 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
7655         if (!opt_git_dir[0]) {
7656                 string_ncopy(opt_git_dir, name, namelen);
7658         } else if (opt_is_inside_work_tree == -1) {
7659                 /* This can be 3 different values depending on the
7660                  * version of git being used. If git-rev-parse does not
7661                  * understand --is-inside-work-tree it will simply echo
7662                  * the option else either "true" or "false" is printed.
7663                  * Default to true for the unknown case. */
7664                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7666         } else if (*name == '.') {
7667                 string_ncopy(opt_cdup, name, namelen);
7669         } else {
7670                 string_ncopy(opt_prefix, name, namelen);
7671         }
7673         return OK;
7676 static int
7677 load_repo_info(void)
7679         const char *rev_parse_argv[] = {
7680                 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7681                         "--show-cdup", "--show-prefix", NULL
7682         };
7684         return io_run_load(rev_parse_argv, "=", read_repo_info);
7688 /*
7689  * Main
7690  */
7692 static const char usage[] =
7693 "tig " TIG_VERSION " (" __DATE__ ")\n"
7694 "\n"
7695 "Usage: tig        [options] [revs] [--] [paths]\n"
7696 "   or: tig show   [options] [revs] [--] [paths]\n"
7697 "   or: tig blame  [rev] path\n"
7698 "   or: tig status\n"
7699 "   or: tig <      [git command output]\n"
7700 "\n"
7701 "Options:\n"
7702 "  -v, --version   Show version and exit\n"
7703 "  -h, --help      Show help message and exit";
7705 static void __NORETURN
7706 quit(int sig)
7708         /* XXX: Restore tty modes and let the OS cleanup the rest! */
7709         if (cursed)
7710                 endwin();
7711         exit(0);
7714 static void __NORETURN
7715 die(const char *err, ...)
7717         va_list args;
7719         endwin();
7721         va_start(args, err);
7722         fputs("tig: ", stderr);
7723         vfprintf(stderr, err, args);
7724         fputs("\n", stderr);
7725         va_end(args);
7727         exit(1);
7730 static void
7731 warn(const char *msg, ...)
7733         va_list args;
7735         va_start(args, msg);
7736         fputs("tig warning: ", stderr);
7737         vfprintf(stderr, msg, args);
7738         fputs("\n", stderr);
7739         va_end(args);
7742 static const char ***filter_args;
7744 static int
7745 read_filter_args(char *name, size_t namelen, char *value, size_t valuelen)
7747         return argv_append(filter_args, name) ? OK : ERR;
7750 static void
7751 filter_rev_parse(const char ***args, const char *arg1, const char *arg2, const char *argv[])
7753         const char *rev_parse_argv[SIZEOF_ARG] = { "git", "rev-parse", arg1, arg2 };
7754         const char **all_argv = NULL;
7756         filter_args = args;
7757         if (!argv_append_array(&all_argv, rev_parse_argv) ||
7758             !argv_append_array(&all_argv, argv) ||
7759             !io_run_load(all_argv, "\n", read_filter_args) == ERR)
7760                 die("Failed to split arguments");
7761         argv_free(all_argv);
7762         free(all_argv);
7765 static void
7766 filter_options(const char *argv[])
7768         filter_rev_parse(&opt_file_args, "--no-revs", "--no-flags", argv);
7769         filter_rev_parse(&opt_diff_args, "--no-revs", "--flags", argv);
7770         filter_rev_parse(&opt_rev_args, "--symbolic", "--revs-only", argv);
7773 static enum request
7774 parse_options(int argc, const char *argv[])
7776         enum request request = REQ_VIEW_MAIN;
7777         const char *subcommand;
7778         bool seen_dashdash = FALSE;
7779         const char **filter_argv = NULL;
7780         int i;
7782         if (!isatty(STDIN_FILENO))
7783                 return REQ_VIEW_PAGER;
7785         if (argc <= 1)
7786                 return REQ_VIEW_MAIN;
7788         subcommand = argv[1];
7789         if (!strcmp(subcommand, "status")) {
7790                 if (argc > 2)
7791                         warn("ignoring arguments after `%s'", subcommand);
7792                 return REQ_VIEW_STATUS;
7794         } else if (!strcmp(subcommand, "blame")) {
7795                 if (argc <= 2 || argc > 4)
7796                         die("invalid number of options to blame\n\n%s", usage);
7798                 i = 2;
7799                 if (argc == 4) {
7800                         string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7801                         i++;
7802                 }
7804                 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7805                 return REQ_VIEW_BLAME;
7807         } else if (!strcmp(subcommand, "show")) {
7808                 request = REQ_VIEW_DIFF;
7810         } else {
7811                 subcommand = NULL;
7812         }
7814         for (i = 1 + !!subcommand; i < argc; i++) {
7815                 const char *opt = argv[i];
7817                 if (seen_dashdash) {
7818                         argv_append(&opt_file_args, opt);
7819                         continue;
7821                 } else if (!strcmp(opt, "--")) {
7822                         seen_dashdash = TRUE;
7823                         continue;
7825                 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7826                         printf("tig version %s\n", TIG_VERSION);
7827                         quit(0);
7829                 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7830                         printf("%s\n", usage);
7831                         quit(0);
7833                 } else if (!strcmp(opt, "--all")) {
7834                         argv_append(&opt_rev_args, opt);
7835                         continue;
7836                 }
7838                 if (!argv_append(&filter_argv, opt))
7839                         die("command too long");
7840         }
7842         if (filter_argv)
7843                 filter_options(filter_argv);
7845         return request;
7848 int
7849 main(int argc, const char *argv[])
7851         const char *codeset = "UTF-8";
7852         enum request request = parse_options(argc, argv);
7853         struct view *view;
7854         size_t i;
7856         signal(SIGINT, quit);
7857         signal(SIGPIPE, SIG_IGN);
7859         if (setlocale(LC_ALL, "")) {
7860                 codeset = nl_langinfo(CODESET);
7861         }
7863         if (load_repo_info() == ERR)
7864                 die("Failed to load repo info.");
7866         if (load_options() == ERR)
7867                 die("Failed to load user config.");
7869         if (load_git_config() == ERR)
7870                 die("Failed to load repo config.");
7872         /* Require a git repository unless when running in pager mode. */
7873         if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7874                 die("Not a git repository");
7876         if (*opt_encoding && strcmp(codeset, "UTF-8")) {
7877                 opt_iconv_in = iconv_open("UTF-8", opt_encoding);
7878                 if (opt_iconv_in == ICONV_NONE)
7879                         die("Failed to initialize character set conversion");
7880         }
7882         if (codeset && strcmp(codeset, "UTF-8")) {
7883                 opt_iconv_out = iconv_open(codeset, "UTF-8");
7884                 if (opt_iconv_out == ICONV_NONE)
7885                         die("Failed to initialize character set conversion");
7886         }
7888         if (load_refs() == ERR)
7889                 die("Failed to load refs.");
7891         foreach_view (view, i) {
7892                 if (getenv(view->cmd_env))
7893                         warn("Use of the %s environment variable is deprecated,"
7894                              " use options or TIG_DIFF_ARGS instead",
7895                              view->cmd_env);
7896                 if (!argv_from_env(view->ops->argv, view->cmd_env))
7897                         die("Too many arguments in the `%s` environment variable",
7898                             view->cmd_env);
7899         }
7901         init_display();
7903         while (view_driver(display[current_view], request)) {
7904                 int key = get_input(0);
7906                 view = display[current_view];
7907                 request = get_keybinding(view->keymap, key);
7909                 /* Some low-level request handling. This keeps access to
7910                  * status_win restricted. */
7911                 switch (request) {
7912                 case REQ_NONE:
7913                         report("Unknown key, press %s for help",
7914                                get_key(view->keymap, REQ_VIEW_HELP));
7915                         break;
7916                 case REQ_PROMPT:
7917                 {
7918                         char *cmd = read_prompt(":");
7920                         if (cmd && isdigit(*cmd)) {
7921                                 int lineno = view->lineno + 1;
7923                                 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7924                                         select_view_line(view, lineno - 1);
7925                                         report("");
7926                                 } else {
7927                                         report("Unable to parse '%s' as a line number", cmd);
7928                                 }
7930                         } else if (cmd) {
7931                                 struct view *next = VIEW(REQ_VIEW_PAGER);
7932                                 const char *argv[SIZEOF_ARG] = { "git" };
7933                                 int argc = 1;
7935                                 /* When running random commands, initially show the
7936                                  * command in the title. However, it maybe later be
7937                                  * overwritten if a commit line is selected. */
7938                                 string_ncopy(next->ref, cmd, strlen(cmd));
7940                                 if (!argv_from_string(argv, &argc, cmd)) {
7941                                         report("Too many arguments");
7942                                 } else if (!prepare_update(next, argv, NULL)) {
7943                                         report("Failed to format command");
7944                                 } else {
7945                                         open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7946                                 }
7947                         }
7949                         request = REQ_NONE;
7950                         break;
7951                 }
7952                 case REQ_SEARCH:
7953                 case REQ_SEARCH_BACK:
7954                 {
7955                         const char *prompt = request == REQ_SEARCH ? "/" : "?";
7956                         char *search = read_prompt(prompt);
7958                         if (search)
7959                                 string_ncopy(opt_search, search, strlen(search));
7960                         else if (*opt_search)
7961                                 request = request == REQ_SEARCH ?
7962                                         REQ_FIND_NEXT :
7963                                         REQ_FIND_PREV;
7964                         else
7965                                 request = REQ_NONE;
7966                         break;
7967                 }
7968                 default:
7969                         break;
7970                 }
7971         }
7973         quit(0);
7975         return 0;