Code

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