Code

Refactor option parsing error handling to use error codes
[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_CTL(x)      ((x) & 0x1f) /* KEY_CTL(A) == ^A == \1 */
118 #define KEY_TAB         '\t'
119 #define KEY_RETURN      '\r'
120 #define KEY_ESC         27
123 struct ref {
124         char id[SIZEOF_REV];    /* Commit SHA1 ID */
125         unsigned int head:1;    /* Is it the current HEAD? */
126         unsigned int tag:1;     /* Is it a tag? */
127         unsigned int ltag:1;    /* If so, is the tag local? */
128         unsigned int remote:1;  /* Is it a remote ref? */
129         unsigned int tracked:1; /* Is it the remote for the current HEAD? */
130         char name[1];           /* Ref name; tag or head names are shortened. */
131 };
133 struct ref_list {
134         char id[SIZEOF_REV];    /* Commit SHA1 ID */
135         size_t size;            /* Number of refs. */
136         struct ref **refs;      /* References for this ID. */
137 };
139 static struct ref *get_ref_head();
140 static struct ref_list *get_ref_list(const char *id);
141 static void foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data);
142 static int load_refs(void);
144 enum input_status {
145         INPUT_OK,
146         INPUT_SKIP,
147         INPUT_STOP,
148         INPUT_CANCEL
149 };
151 typedef enum input_status (*input_handler)(void *data, char *buf, int c);
153 static char *prompt_input(const char *prompt, input_handler handler, void *data);
154 static bool prompt_yesno(const char *prompt);
156 struct menu_item {
157         int hotkey;
158         const char *text;
159         void *data;
160 };
162 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected);
164 /*
165  * Allocation helpers ... Entering macro hell to never be seen again.
166  */
168 #define DEFINE_ALLOCATOR(name, type, chunk_size)                                \
169 static type *                                                                   \
170 name(type **mem, size_t size, size_t increase)                                  \
171 {                                                                               \
172         size_t num_chunks = (size + chunk_size - 1) / chunk_size;               \
173         size_t num_chunks_new = (size + increase + chunk_size - 1) / chunk_size;\
174         type *tmp = *mem;                                                       \
175                                                                                 \
176         if (mem == NULL || num_chunks != num_chunks_new) {                      \
177                 tmp = realloc(tmp, num_chunks_new * chunk_size * sizeof(type)); \
178                 if (tmp)                                                        \
179                         *mem = tmp;                                             \
180         }                                                                       \
181                                                                                 \
182         return tmp;                                                             \
185 /*
186  * String helpers
187  */
189 static inline void
190 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
192         if (srclen > dstlen - 1)
193                 srclen = dstlen - 1;
195         strncpy(dst, src, srclen);
196         dst[srclen] = 0;
199 /* Shorthands for safely copying into a fixed buffer. */
201 #define string_copy(dst, src) \
202         string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
204 #define string_ncopy(dst, src, srclen) \
205         string_ncopy_do(dst, sizeof(dst), src, srclen)
207 #define string_copy_rev(dst, src) \
208         string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
210 #define string_add(dst, from, src) \
211         string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
213 static size_t
214 string_expand(char *dst, size_t dstlen, const char *src, int tabsize)
216         size_t size, pos;
218         for (size = pos = 0; size < dstlen - 1 && src[pos]; pos++) {
219                 if (src[pos] == '\t') {
220                         size_t expanded = tabsize - (size % tabsize);
222                         if (expanded + size >= dstlen - 1)
223                                 expanded = dstlen - size - 1;
224                         memcpy(dst + size, "        ", expanded);
225                         size += expanded;
226                 } else {
227                         dst[size++] = src[pos];
228                 }
229         }
231         dst[size] = 0;
232         return pos;
235 static char *
236 chomp_string(char *name)
238         int namelen;
240         while (isspace(*name))
241                 name++;
243         namelen = strlen(name) - 1;
244         while (namelen > 0 && isspace(name[namelen]))
245                 name[namelen--] = 0;
247         return name;
250 static bool
251 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
253         va_list args;
254         size_t pos = bufpos ? *bufpos : 0;
256         va_start(args, fmt);
257         pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
258         va_end(args);
260         if (bufpos)
261                 *bufpos = pos;
263         return pos >= bufsize ? FALSE : TRUE;
266 #define string_format(buf, fmt, args...) \
267         string_nformat(buf, sizeof(buf), NULL, fmt, args)
269 #define string_format_from(buf, from, fmt, args...) \
270         string_nformat(buf, sizeof(buf), from, fmt, args)
272 static int
273 string_enum_compare(const char *str1, const char *str2, int len)
275         size_t i;
277 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
279         /* Diff-Header == DIFF_HEADER */
280         for (i = 0; i < len; i++) {
281                 if (toupper(str1[i]) == toupper(str2[i]))
282                         continue;
284                 if (string_enum_sep(str1[i]) &&
285                     string_enum_sep(str2[i]))
286                         continue;
288                 return str1[i] - str2[i];
289         }
291         return 0;
294 #define enum_equals(entry, str, len) \
295         ((entry).namelen == (len) && !string_enum_compare((entry).name, str, len))
297 struct enum_map {
298         const char *name;
299         int namelen;
300         int value;
301 };
303 #define ENUM_MAP(name, value) { name, STRING_SIZE(name), value }
305 static char *
306 enum_map_name(const char *name, size_t namelen)
308         static char buf[SIZEOF_STR];
309         int bufpos;
311         for (bufpos = 0; bufpos <= namelen; bufpos++) {
312                 buf[bufpos] = tolower(name[bufpos]);
313                 if (buf[bufpos] == '_')
314                         buf[bufpos] = '-';
315         }
317         buf[bufpos] = 0;
318         return buf;
321 #define enum_name(entry) enum_map_name((entry).name, (entry).namelen)
323 static bool
324 map_enum_do(const struct enum_map *map, size_t map_size, int *value, const char *name)
326         size_t namelen = strlen(name);
327         int i;
329         for (i = 0; i < map_size; i++)
330                 if (enum_equals(map[i], name, namelen)) {
331                         *value = map[i].value;
332                         return TRUE;
333                 }
335         return FALSE;
338 #define map_enum(attr, map, name) \
339         map_enum_do(map, ARRAY_SIZE(map), attr, name)
341 #define prefixcmp(str1, str2) \
342         strncmp(str1, str2, STRING_SIZE(str2))
344 static inline int
345 suffixcmp(const char *str, int slen, const char *suffix)
347         size_t len = slen >= 0 ? slen : strlen(str);
348         size_t suffixlen = strlen(suffix);
350         return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
354 /*
355  * Unicode / UTF-8 handling
356  *
357  * NOTE: Much of the following code for dealing with Unicode is derived from
358  * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
359  * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
360  */
362 static inline int
363 unicode_width(unsigned long c, int tab_size)
365         if (c >= 0x1100 &&
366            (c <= 0x115f                         /* Hangul Jamo */
367             || c == 0x2329
368             || c == 0x232a
369             || (c >= 0x2e80  && c <= 0xa4cf && c != 0x303f)
370                                                 /* CJK ... Yi */
371             || (c >= 0xac00  && c <= 0xd7a3)    /* Hangul Syllables */
372             || (c >= 0xf900  && c <= 0xfaff)    /* CJK Compatibility Ideographs */
373             || (c >= 0xfe30  && c <= 0xfe6f)    /* CJK Compatibility Forms */
374             || (c >= 0xff00  && c <= 0xff60)    /* Fullwidth Forms */
375             || (c >= 0xffe0  && c <= 0xffe6)
376             || (c >= 0x20000 && c <= 0x2fffd)
377             || (c >= 0x30000 && c <= 0x3fffd)))
378                 return 2;
380         if (c == '\t')
381                 return tab_size;
383         return 1;
386 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
387  * Illegal bytes are set one. */
388 static const unsigned char utf8_bytes[256] = {
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         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,
395         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,
396         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,
397 };
399 static inline unsigned char
400 utf8_char_length(const char *string, const char *end)
402         int c = *(unsigned char *) string;
404         return utf8_bytes[c];
407 /* Decode UTF-8 multi-byte representation into a Unicode character. */
408 static inline unsigned long
409 utf8_to_unicode(const char *string, size_t length)
411         unsigned long unicode;
413         switch (length) {
414         case 1:
415                 unicode  =   string[0];
416                 break;
417         case 2:
418                 unicode  =  (string[0] & 0x1f) << 6;
419                 unicode +=  (string[1] & 0x3f);
420                 break;
421         case 3:
422                 unicode  =  (string[0] & 0x0f) << 12;
423                 unicode += ((string[1] & 0x3f) << 6);
424                 unicode +=  (string[2] & 0x3f);
425                 break;
426         case 4:
427                 unicode  =  (string[0] & 0x0f) << 18;
428                 unicode += ((string[1] & 0x3f) << 12);
429                 unicode += ((string[2] & 0x3f) << 6);
430                 unicode +=  (string[3] & 0x3f);
431                 break;
432         case 5:
433                 unicode  =  (string[0] & 0x0f) << 24;
434                 unicode += ((string[1] & 0x3f) << 18);
435                 unicode += ((string[2] & 0x3f) << 12);
436                 unicode += ((string[3] & 0x3f) << 6);
437                 unicode +=  (string[4] & 0x3f);
438                 break;
439         case 6:
440                 unicode  =  (string[0] & 0x01) << 30;
441                 unicode += ((string[1] & 0x3f) << 24);
442                 unicode += ((string[2] & 0x3f) << 18);
443                 unicode += ((string[3] & 0x3f) << 12);
444                 unicode += ((string[4] & 0x3f) << 6);
445                 unicode +=  (string[5] & 0x3f);
446                 break;
447         default:
448                 return 0;
449         }
451         /* Invalid characters could return the special 0xfffd value but NUL
452          * should be just as good. */
453         return unicode > 0xffff ? 0 : unicode;
456 /* Calculates how much of string can be shown within the given maximum width
457  * and sets trimmed parameter to non-zero value if all of string could not be
458  * shown. If the reserve flag is TRUE, it will reserve at least one
459  * trailing character, which can be useful when drawing a delimiter.
460  *
461  * Returns the number of bytes to output from string to satisfy max_width. */
462 static size_t
463 utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve, int tab_size)
465         const char *string = *start;
466         const char *end = strchr(string, '\0');
467         unsigned char last_bytes = 0;
468         size_t last_ucwidth = 0;
470         *width = 0;
471         *trimmed = 0;
473         while (string < end) {
474                 unsigned char bytes = utf8_char_length(string, end);
475                 size_t ucwidth;
476                 unsigned long unicode;
478                 if (string + bytes > end)
479                         break;
481                 /* Change representation to figure out whether
482                  * it is a single- or double-width character. */
484                 unicode = utf8_to_unicode(string, bytes);
485                 /* FIXME: Graceful handling of invalid Unicode character. */
486                 if (!unicode)
487                         break;
489                 ucwidth = unicode_width(unicode, tab_size);
490                 if (skip > 0) {
491                         skip -= ucwidth <= skip ? ucwidth : skip;
492                         *start += bytes;
493                 }
494                 *width  += ucwidth;
495                 if (*width > max_width) {
496                         *trimmed = 1;
497                         *width -= ucwidth;
498                         if (reserve && *width == max_width) {
499                                 string -= last_bytes;
500                                 *width -= last_ucwidth;
501                         }
502                         break;
503                 }
505                 string  += bytes;
506                 last_bytes = ucwidth ? bytes : 0;
507                 last_ucwidth = ucwidth;
508         }
510         return string - *start;
514 #define DATE_INFO \
515         DATE_(NO), \
516         DATE_(DEFAULT), \
517         DATE_(LOCAL), \
518         DATE_(RELATIVE), \
519         DATE_(SHORT)
521 enum date {
522 #define DATE_(name) DATE_##name
523         DATE_INFO
524 #undef  DATE_
525 };
527 static const struct enum_map date_map[] = {
528 #define DATE_(name) ENUM_MAP(#name, DATE_##name)
529         DATE_INFO
530 #undef  DATE_
531 };
533 struct time {
534         time_t sec;
535         int tz;
536 };
538 static inline int timecmp(const struct time *t1, const struct time *t2)
540         return t1->sec - t2->sec;
543 static const char *
544 mkdate(const struct time *time, enum date date)
546         static char buf[DATE_COLS + 1];
547         static const struct enum_map reldate[] = {
548                 { "second", 1,                  60 * 2 },
549                 { "minute", 60,                 60 * 60 * 2 },
550                 { "hour",   60 * 60,            60 * 60 * 24 * 2 },
551                 { "day",    60 * 60 * 24,       60 * 60 * 24 * 7 * 2 },
552                 { "week",   60 * 60 * 24 * 7,   60 * 60 * 24 * 7 * 5 },
553                 { "month",  60 * 60 * 24 * 30,  60 * 60 * 24 * 30 * 12 },
554         };
555         struct tm tm;
557         if (!date || !time || !time->sec)
558                 return "";
560         if (date == DATE_RELATIVE) {
561                 struct timeval now;
562                 time_t date = time->sec + time->tz;
563                 time_t seconds;
564                 int i;
566                 gettimeofday(&now, NULL);
567                 seconds = now.tv_sec < date ? date - now.tv_sec : now.tv_sec - date;
568                 for (i = 0; i < ARRAY_SIZE(reldate); i++) {
569                         if (seconds >= reldate[i].value)
570                                 continue;
572                         seconds /= reldate[i].namelen;
573                         if (!string_format(buf, "%ld %s%s %s",
574                                            seconds, reldate[i].name,
575                                            seconds > 1 ? "s" : "",
576                                            now.tv_sec >= date ? "ago" : "ahead"))
577                                 break;
578                         return buf;
579                 }
580         }
582         if (date == DATE_LOCAL) {
583                 time_t date = time->sec + time->tz;
584                 localtime_r(&date, &tm);
585         }
586         else {
587                 gmtime_r(&time->sec, &tm);
588         }
589         return strftime(buf, sizeof(buf), DATE_FORMAT, &tm) ? buf : NULL;
593 #define AUTHOR_VALUES \
594         AUTHOR_(NO), \
595         AUTHOR_(FULL), \
596         AUTHOR_(ABBREVIATED)
598 enum author {
599 #define AUTHOR_(name) AUTHOR_##name
600         AUTHOR_VALUES,
601 #undef  AUTHOR_
602         AUTHOR_DEFAULT = AUTHOR_FULL
603 };
605 static const struct enum_map author_map[] = {
606 #define AUTHOR_(name) ENUM_MAP(#name, AUTHOR_##name)
607         AUTHOR_VALUES
608 #undef  AUTHOR_
609 };
611 static const char *
612 get_author_initials(const char *author)
614         static char initials[AUTHOR_COLS * 6 + 1];
615         size_t pos = 0;
616         const char *end = strchr(author, '\0');
618 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@' || (c) == '-')
620         memset(initials, 0, sizeof(initials));
621         while (author < end) {
622                 unsigned char bytes;
623                 size_t i;
625                 while (is_initial_sep(*author))
626                         author++;
628                 bytes = utf8_char_length(author, end);
629                 if (bytes < sizeof(initials) - 1 - pos) {
630                         while (bytes--) {
631                                 initials[pos++] = *author++;
632                         }
633                 }
635                 for (i = pos; author < end && !is_initial_sep(*author); author++) {
636                         if (i < sizeof(initials) - 1)
637                                 initials[i++] = *author;
638                 }
640                 initials[i++] = 0;
641         }
643         return initials;
647 static bool
648 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
650         int valuelen;
652         while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
653                 bool advance = cmd[valuelen] != 0;
655                 cmd[valuelen] = 0;
656                 argv[(*argc)++] = chomp_string(cmd);
657                 cmd = chomp_string(cmd + valuelen + advance);
658         }
660         if (*argc < SIZEOF_ARG)
661                 argv[*argc] = NULL;
662         return *argc < SIZEOF_ARG;
665 static bool
666 argv_from_env(const char **argv, const char *name)
668         char *env = argv ? getenv(name) : NULL;
669         int argc = 0;
671         if (env && *env)
672                 env = strdup(env);
673         return !env || argv_from_string(argv, &argc, env);
676 static void
677 argv_free(const char *argv[])
679         int argc;
681         if (!argv)
682                 return;
683         for (argc = 0; argv[argc]; argc++)
684                 free((void *) argv[argc]);
685         argv[0] = NULL;
688 static size_t
689 argv_size(const char **argv)
691         int argc = 0;
693         while (argv && argv[argc])
694                 argc++;
696         return argc;
699 DEFINE_ALLOCATOR(argv_realloc, const char *, SIZEOF_ARG)
701 static bool
702 argv_append(const char ***argv, const char *arg)
704         size_t argc = argv_size(*argv);
706         if (!argv_realloc(argv, argc, 2))
707                 return FALSE;
709         (*argv)[argc++] = strdup(arg);
710         (*argv)[argc] = NULL;
711         return TRUE;
714 static bool
715 argv_append_array(const char ***dst_argv, const char *src_argv[])
717         int i;
719         for (i = 0; src_argv && src_argv[i]; i++)
720                 if (!argv_append(dst_argv, src_argv[i]))
721                         return FALSE;
722         return TRUE;
725 static bool
726 argv_copy(const char ***dst, const char *src[])
728         int argc;
730         for (argc = 0; src[argc]; argc++)
731                 if (!argv_append(dst, src[argc]))
732                         return FALSE;
733         return TRUE;
737 /*
738  * Executing external commands.
739  */
741 enum io_type {
742         IO_FD,                  /* File descriptor based IO. */
743         IO_BG,                  /* Execute command in the background. */
744         IO_FG,                  /* Execute command with same std{in,out,err}. */
745         IO_RD,                  /* Read only fork+exec IO. */
746         IO_WR,                  /* Write only fork+exec IO. */
747         IO_AP,                  /* Append fork+exec output to file. */
748 };
750 struct io {
751         int pipe;               /* Pipe end for reading or writing. */
752         pid_t pid;              /* PID of spawned process. */
753         int error;              /* Error status. */
754         char *buf;              /* Read buffer. */
755         size_t bufalloc;        /* Allocated buffer size. */
756         size_t bufsize;         /* Buffer content size. */
757         char *bufpos;           /* Current buffer position. */
758         unsigned int eof:1;     /* Has end of file been reached. */
759 };
761 static void
762 io_init(struct io *io)
764         memset(io, 0, sizeof(*io));
765         io->pipe = -1;
768 static bool
769 io_open(struct io *io, const char *fmt, ...)
771         char name[SIZEOF_STR] = "";
772         bool fits;
773         va_list args;
775         io_init(io);
777         va_start(args, fmt);
778         fits = vsnprintf(name, sizeof(name), fmt, args) < sizeof(name);
779         va_end(args);
781         if (!fits) {
782                 io->error = ENAMETOOLONG;
783                 return FALSE;
784         }
785         io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
786         if (io->pipe == -1)
787                 io->error = errno;
788         return io->pipe != -1;
791 static bool
792 io_kill(struct io *io)
794         return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
797 static bool
798 io_done(struct io *io)
800         pid_t pid = io->pid;
802         if (io->pipe != -1)
803                 close(io->pipe);
804         free(io->buf);
805         io_init(io);
807         while (pid > 0) {
808                 int status;
809                 pid_t waiting = waitpid(pid, &status, 0);
811                 if (waiting < 0) {
812                         if (errno == EINTR)
813                                 continue;
814                         io->error = errno;
815                         return FALSE;
816                 }
818                 return waiting == pid &&
819                        !WIFSIGNALED(status) &&
820                        WIFEXITED(status) &&
821                        !WEXITSTATUS(status);
822         }
824         return TRUE;
827 static bool
828 io_run(struct io *io, enum io_type type, const char *dir, const char *argv[], ...)
830         int pipefds[2] = { -1, -1 };
831         va_list args;
833         io_init(io);
835         if ((type == IO_RD || type == IO_WR) && pipe(pipefds) < 0) {
836                 io->error = errno;
837                 return FALSE;
838         } else if (type == IO_AP) {
839                 va_start(args, argv);
840                 pipefds[1] = va_arg(args, int);
841                 va_end(args);
842         }
844         if ((io->pid = fork())) {
845                 if (io->pid == -1)
846                         io->error = errno;
847                 if (pipefds[!(type == IO_WR)] != -1)
848                         close(pipefds[!(type == IO_WR)]);
849                 if (io->pid != -1) {
850                         io->pipe = pipefds[!!(type == IO_WR)];
851                         return TRUE;
852                 }
854         } else {
855                 if (type != IO_FG) {
856                         int devnull = open("/dev/null", O_RDWR);
857                         int readfd  = type == IO_WR ? pipefds[0] : devnull;
858                         int writefd = (type == IO_RD || type == IO_AP)
859                                                         ? pipefds[1] : devnull;
861                         dup2(readfd,  STDIN_FILENO);
862                         dup2(writefd, STDOUT_FILENO);
863                         dup2(devnull, STDERR_FILENO);
865                         close(devnull);
866                         if (pipefds[0] != -1)
867                                 close(pipefds[0]);
868                         if (pipefds[1] != -1)
869                                 close(pipefds[1]);
870                 }
872                 if (dir && *dir && chdir(dir) == -1)
873                         exit(errno);
875                 execvp(argv[0], (char *const*) argv);
876                 exit(errno);
877         }
879         if (pipefds[!!(type == IO_WR)] != -1)
880                 close(pipefds[!!(type == IO_WR)]);
881         return FALSE;
884 static bool
885 io_complete(enum io_type type, const char **argv, const char *dir, int fd)
887         struct io io;
889         return io_run(&io, type, dir, argv, fd) && io_done(&io);
892 static bool
893 io_run_bg(const char **argv)
895         return io_complete(IO_BG, argv, NULL, -1);
898 static bool
899 io_run_fg(const char **argv, const char *dir)
901         return io_complete(IO_FG, argv, dir, -1);
904 static bool
905 io_run_append(const char **argv, int fd)
907         return io_complete(IO_AP, argv, NULL, fd);
910 static bool
911 io_eof(struct io *io)
913         return io->eof;
916 static int
917 io_error(struct io *io)
919         return io->error;
922 static char *
923 io_strerror(struct io *io)
925         return strerror(io->error);
928 static bool
929 io_can_read(struct io *io)
931         struct timeval tv = { 0, 500 };
932         fd_set fds;
934         FD_ZERO(&fds);
935         FD_SET(io->pipe, &fds);
937         return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
940 static ssize_t
941 io_read(struct io *io, void *buf, size_t bufsize)
943         do {
944                 ssize_t readsize = read(io->pipe, buf, bufsize);
946                 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
947                         continue;
948                 else if (readsize == -1)
949                         io->error = errno;
950                 else if (readsize == 0)
951                         io->eof = 1;
952                 return readsize;
953         } while (1);
956 DEFINE_ALLOCATOR(io_realloc_buf, char, BUFSIZ)
958 static char *
959 io_get(struct io *io, int c, bool can_read)
961         char *eol;
962         ssize_t readsize;
964         while (TRUE) {
965                 if (io->bufsize > 0) {
966                         eol = memchr(io->bufpos, c, io->bufsize);
967                         if (eol) {
968                                 char *line = io->bufpos;
970                                 *eol = 0;
971                                 io->bufpos = eol + 1;
972                                 io->bufsize -= io->bufpos - line;
973                                 return line;
974                         }
975                 }
977                 if (io_eof(io)) {
978                         if (io->bufsize) {
979                                 io->bufpos[io->bufsize] = 0;
980                                 io->bufsize = 0;
981                                 return io->bufpos;
982                         }
983                         return NULL;
984                 }
986                 if (!can_read)
987                         return NULL;
989                 if (io->bufsize > 0 && io->bufpos > io->buf)
990                         memmove(io->buf, io->bufpos, io->bufsize);
992                 if (io->bufalloc == io->bufsize) {
993                         if (!io_realloc_buf(&io->buf, io->bufalloc, BUFSIZ))
994                                 return NULL;
995                         io->bufalloc += BUFSIZ;
996                 }
998                 io->bufpos = io->buf;
999                 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
1000                 if (io_error(io))
1001                         return NULL;
1002                 io->bufsize += readsize;
1003         }
1006 static bool
1007 io_write(struct io *io, const void *buf, size_t bufsize)
1009         size_t written = 0;
1011         while (!io_error(io) && written < bufsize) {
1012                 ssize_t size;
1014                 size = write(io->pipe, buf + written, bufsize - written);
1015                 if (size < 0 && (errno == EAGAIN || errno == EINTR))
1016                         continue;
1017                 else if (size == -1)
1018                         io->error = errno;
1019                 else
1020                         written += size;
1021         }
1023         return written == bufsize;
1026 static bool
1027 io_read_buf(struct io *io, char buf[], size_t bufsize)
1029         char *result = io_get(io, '\n', TRUE);
1031         if (result) {
1032                 result = chomp_string(result);
1033                 string_ncopy_do(buf, bufsize, result, strlen(result));
1034         }
1036         return io_done(io) && result;
1039 static bool
1040 io_run_buf(const char **argv, char buf[], size_t bufsize)
1042         struct io io;
1044         return io_run(&io, IO_RD, NULL, argv) && io_read_buf(&io, buf, bufsize);
1047 typedef int (*io_read_fn)(char *, size_t, char *, size_t, void *data);
1049 static int
1050 io_load(struct io *io, const char *separators,
1051         io_read_fn read_property, void *data)
1053         char *name;
1054         int state = OK;
1056         while (state == OK && (name = io_get(io, '\n', TRUE))) {
1057                 char *value;
1058                 size_t namelen;
1059                 size_t valuelen;
1061                 name = chomp_string(name);
1062                 namelen = strcspn(name, separators);
1064                 if (name[namelen]) {
1065                         name[namelen] = 0;
1066                         value = chomp_string(name + namelen + 1);
1067                         valuelen = strlen(value);
1069                 } else {
1070                         value = "";
1071                         valuelen = 0;
1072                 }
1074                 state = read_property(name, namelen, value, valuelen, data);
1075         }
1077         if (state != ERR && io_error(io))
1078                 state = ERR;
1079         io_done(io);
1081         return state;
1084 static int
1085 io_run_load(const char **argv, const char *separators,
1086             io_read_fn read_property, void *data)
1088         struct io io;
1090         if (!io_run(&io, IO_RD, NULL, argv))
1091                 return ERR;
1092         return io_load(&io, separators, read_property, data);
1096 /*
1097  * User requests
1098  */
1100 #define REQ_INFO \
1101         /* XXX: Keep the view request first and in sync with views[]. */ \
1102         REQ_GROUP("View switching") \
1103         REQ_(VIEW_MAIN,         "Show main view"), \
1104         REQ_(VIEW_DIFF,         "Show diff view"), \
1105         REQ_(VIEW_LOG,          "Show log view"), \
1106         REQ_(VIEW_TREE,         "Show tree view"), \
1107         REQ_(VIEW_BLOB,         "Show blob view"), \
1108         REQ_(VIEW_BLAME,        "Show blame view"), \
1109         REQ_(VIEW_BRANCH,       "Show branch view"), \
1110         REQ_(VIEW_HELP,         "Show help page"), \
1111         REQ_(VIEW_PAGER,        "Show pager view"), \
1112         REQ_(VIEW_STATUS,       "Show status view"), \
1113         REQ_(VIEW_STAGE,        "Show stage view"), \
1114         \
1115         REQ_GROUP("View manipulation") \
1116         REQ_(ENTER,             "Enter current line and scroll"), \
1117         REQ_(NEXT,              "Move to next"), \
1118         REQ_(PREVIOUS,          "Move to previous"), \
1119         REQ_(PARENT,            "Move to parent"), \
1120         REQ_(VIEW_NEXT,         "Move focus to next view"), \
1121         REQ_(REFRESH,           "Reload and refresh"), \
1122         REQ_(MAXIMIZE,          "Maximize the current view"), \
1123         REQ_(VIEW_CLOSE,        "Close the current view"), \
1124         REQ_(QUIT,              "Close all views and quit"), \
1125         \
1126         REQ_GROUP("View specific requests") \
1127         REQ_(STATUS_UPDATE,     "Update file status"), \
1128         REQ_(STATUS_REVERT,     "Revert file changes"), \
1129         REQ_(STATUS_MERGE,      "Merge file using external tool"), \
1130         REQ_(STAGE_NEXT,        "Find next chunk to stage"), \
1131         \
1132         REQ_GROUP("Cursor navigation") \
1133         REQ_(MOVE_UP,           "Move cursor one line up"), \
1134         REQ_(MOVE_DOWN,         "Move cursor one line down"), \
1135         REQ_(MOVE_PAGE_DOWN,    "Move cursor one page down"), \
1136         REQ_(MOVE_PAGE_UP,      "Move cursor one page up"), \
1137         REQ_(MOVE_FIRST_LINE,   "Move cursor to first line"), \
1138         REQ_(MOVE_LAST_LINE,    "Move cursor to last line"), \
1139         \
1140         REQ_GROUP("Scrolling") \
1141         REQ_(SCROLL_FIRST_COL,  "Scroll to the first line columns"), \
1142         REQ_(SCROLL_LEFT,       "Scroll two columns left"), \
1143         REQ_(SCROLL_RIGHT,      "Scroll two columns right"), \
1144         REQ_(SCROLL_LINE_UP,    "Scroll one line up"), \
1145         REQ_(SCROLL_LINE_DOWN,  "Scroll one line down"), \
1146         REQ_(SCROLL_PAGE_UP,    "Scroll one page up"), \
1147         REQ_(SCROLL_PAGE_DOWN,  "Scroll one page down"), \
1148         \
1149         REQ_GROUP("Searching") \
1150         REQ_(SEARCH,            "Search the view"), \
1151         REQ_(SEARCH_BACK,       "Search backwards in the view"), \
1152         REQ_(FIND_NEXT,         "Find next search match"), \
1153         REQ_(FIND_PREV,         "Find previous search match"), \
1154         \
1155         REQ_GROUP("Option manipulation") \
1156         REQ_(OPTIONS,           "Open option menu"), \
1157         REQ_(TOGGLE_LINENO,     "Toggle line numbers"), \
1158         REQ_(TOGGLE_DATE,       "Toggle date display"), \
1159         REQ_(TOGGLE_AUTHOR,     "Toggle author display"), \
1160         REQ_(TOGGLE_REV_GRAPH,  "Toggle revision graph visualization"), \
1161         REQ_(TOGGLE_REFS,       "Toggle reference display (tags/branches)"), \
1162         REQ_(TOGGLE_SORT_ORDER, "Toggle ascending/descending sort order"), \
1163         REQ_(TOGGLE_SORT_FIELD, "Toggle field to sort by"), \
1164         \
1165         REQ_GROUP("Misc") \
1166         REQ_(PROMPT,            "Bring up the prompt"), \
1167         REQ_(SCREEN_REDRAW,     "Redraw the screen"), \
1168         REQ_(SHOW_VERSION,      "Show version information"), \
1169         REQ_(STOP_LOADING,      "Stop all loading views"), \
1170         REQ_(EDIT,              "Open in editor"), \
1171         REQ_(NONE,              "Do nothing")
1174 /* User action requests. */
1175 enum request {
1176 #define REQ_GROUP(help)
1177 #define REQ_(req, help) REQ_##req
1179         /* Offset all requests to avoid conflicts with ncurses getch values. */
1180         REQ_UNKNOWN = KEY_MAX + 1,
1181         REQ_OFFSET,
1182         REQ_INFO
1184 #undef  REQ_GROUP
1185 #undef  REQ_
1186 };
1188 struct request_info {
1189         enum request request;
1190         const char *name;
1191         int namelen;
1192         const char *help;
1193 };
1195 static const struct request_info req_info[] = {
1196 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
1197 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
1198         REQ_INFO
1199 #undef  REQ_GROUP
1200 #undef  REQ_
1201 };
1203 static enum request
1204 get_request(const char *name)
1206         int namelen = strlen(name);
1207         int i;
1209         for (i = 0; i < ARRAY_SIZE(req_info); i++)
1210                 if (enum_equals(req_info[i], name, namelen))
1211                         return req_info[i].request;
1213         return REQ_UNKNOWN;
1217 /*
1218  * Options
1219  */
1221 /* Option and state variables. */
1222 static enum date opt_date               = DATE_DEFAULT;
1223 static enum author opt_author           = AUTHOR_DEFAULT;
1224 static bool opt_line_number             = FALSE;
1225 static bool opt_line_graphics           = TRUE;
1226 static bool opt_rev_graph               = FALSE;
1227 static bool opt_show_refs               = TRUE;
1228 static bool opt_untracked_dirs_content  = TRUE;
1229 static int opt_num_interval             = 5;
1230 static double opt_hscroll               = 0.50;
1231 static double opt_scale_split_view      = 2.0 / 3.0;
1232 static int opt_tab_size                 = 8;
1233 static int opt_author_cols              = AUTHOR_COLS;
1234 static char opt_path[SIZEOF_STR]        = "";
1235 static char opt_file[SIZEOF_STR]        = "";
1236 static char opt_ref[SIZEOF_REF]         = "";
1237 static char opt_head[SIZEOF_REF]        = "";
1238 static char opt_remote[SIZEOF_REF]      = "";
1239 static char opt_encoding[20]            = "UTF-8";
1240 static iconv_t opt_iconv_in             = ICONV_NONE;
1241 static iconv_t opt_iconv_out            = ICONV_NONE;
1242 static char opt_search[SIZEOF_STR]      = "";
1243 static char opt_cdup[SIZEOF_STR]        = "";
1244 static char opt_prefix[SIZEOF_STR]      = "";
1245 static char opt_git_dir[SIZEOF_STR]     = "";
1246 static signed char opt_is_inside_work_tree      = -1; /* set to TRUE or FALSE */
1247 static char opt_editor[SIZEOF_STR]      = "";
1248 static FILE *opt_tty                    = NULL;
1249 static const char **opt_diff_argv       = NULL;
1250 static const char **opt_rev_argv        = NULL;
1251 static const char **opt_file_argv       = NULL;
1253 #define is_initial_commit()     (!get_ref_head())
1254 #define is_head_commit(rev)     (!strcmp((rev), "HEAD") || (get_ref_head() && !strcmp(rev, get_ref_head()->id)))
1257 /*
1258  * Line-oriented content detection.
1259  */
1261 #define LINE_INFO \
1262 LINE(DIFF_HEADER,  "diff --git ",       COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1263 LINE(DIFF_CHUNK,   "@@",                COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1264 LINE(DIFF_ADD,     "+",                 COLOR_GREEN,    COLOR_DEFAULT,  0), \
1265 LINE(DIFF_DEL,     "-",                 COLOR_RED,      COLOR_DEFAULT,  0), \
1266 LINE(DIFF_INDEX,        "index ",         COLOR_BLUE,   COLOR_DEFAULT,  0), \
1267 LINE(DIFF_OLDMODE,      "old file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1268 LINE(DIFF_NEWMODE,      "new file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1269 LINE(DIFF_COPY_FROM,    "copy from",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
1270 LINE(DIFF_COPY_TO,      "copy to",        COLOR_YELLOW, COLOR_DEFAULT,  0), \
1271 LINE(DIFF_RENAME_FROM,  "rename from",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
1272 LINE(DIFF_RENAME_TO,    "rename to",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
1273 LINE(DIFF_SIMILARITY,   "similarity ",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
1274 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1275 LINE(DIFF_TREE,         "diff-tree ",     COLOR_BLUE,   COLOR_DEFAULT,  0), \
1276 LINE(PP_AUTHOR,    "Author: ",          COLOR_CYAN,     COLOR_DEFAULT,  0), \
1277 LINE(PP_COMMIT,    "Commit: ",          COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1278 LINE(PP_MERGE,     "Merge: ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
1279 LINE(PP_DATE,      "Date:   ",          COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1280 LINE(PP_ADATE,     "AuthorDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1281 LINE(PP_CDATE,     "CommitDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1282 LINE(PP_REFS,      "Refs: ",            COLOR_RED,      COLOR_DEFAULT,  0), \
1283 LINE(COMMIT,       "commit ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
1284 LINE(PARENT,       "parent ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
1285 LINE(TREE,         "tree ",             COLOR_BLUE,     COLOR_DEFAULT,  0), \
1286 LINE(AUTHOR,       "author ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
1287 LINE(COMMITTER,    "committer ",        COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1288 LINE(SIGNOFF,      "    Signed-off-by", COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1289 LINE(ACKED,        "    Acked-by",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1290 LINE(TESTED,       "    Tested-by",     COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1291 LINE(REVIEWED,     "    Reviewed-by",   COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1292 LINE(DEFAULT,      "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
1293 LINE(CURSOR,       "",                  COLOR_WHITE,    COLOR_GREEN,    A_BOLD), \
1294 LINE(STATUS,       "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
1295 LINE(DELIMITER,    "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1296 LINE(DATE,         "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
1297 LINE(MODE,         "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1298 LINE(LINE_NUMBER,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1299 LINE(TITLE_BLUR,   "",                  COLOR_WHITE,    COLOR_BLUE,     0), \
1300 LINE(TITLE_FOCUS,  "",                  COLOR_WHITE,    COLOR_BLUE,     A_BOLD), \
1301 LINE(MAIN_COMMIT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
1302 LINE(MAIN_TAG,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
1303 LINE(MAIN_LOCAL_TAG,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1304 LINE(MAIN_REMOTE,  "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1305 LINE(MAIN_TRACKED, "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_BOLD), \
1306 LINE(MAIN_REF,     "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1307 LINE(MAIN_HEAD,    "",                  COLOR_CYAN,     COLOR_DEFAULT,  A_BOLD), \
1308 LINE(MAIN_REVGRAPH,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1309 LINE(TREE_HEAD,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_BOLD), \
1310 LINE(TREE_DIR,     "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_NORMAL), \
1311 LINE(TREE_FILE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
1312 LINE(STAT_HEAD,    "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1313 LINE(STAT_SECTION, "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1314 LINE(STAT_NONE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
1315 LINE(STAT_STAGED,  "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1316 LINE(STAT_UNSTAGED,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1317 LINE(STAT_UNTRACKED,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1318 LINE(HELP_KEYMAP,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1319 LINE(HELP_GROUP,   "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
1320 LINE(BLAME_ID,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0)
1322 enum line_type {
1323 #define LINE(type, line, fg, bg, attr) \
1324         LINE_##type
1325         LINE_INFO,
1326         LINE_NONE
1327 #undef  LINE
1328 };
1330 struct line_info {
1331         const char *name;       /* Option name. */
1332         int namelen;            /* Size of option name. */
1333         const char *line;       /* The start of line to match. */
1334         int linelen;            /* Size of string to match. */
1335         int fg, bg, attr;       /* Color and text attributes for the lines. */
1336 };
1338 static struct line_info line_info[] = {
1339 #define LINE(type, line, fg, bg, attr) \
1340         { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1341         LINE_INFO
1342 #undef  LINE
1343 };
1345 static enum line_type
1346 get_line_type(const char *line)
1348         int linelen = strlen(line);
1349         enum line_type type;
1351         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1352                 /* Case insensitive search matches Signed-off-by lines better. */
1353                 if (linelen >= line_info[type].linelen &&
1354                     !strncasecmp(line_info[type].line, line, line_info[type].linelen))
1355                         return type;
1357         return LINE_DEFAULT;
1360 static inline int
1361 get_line_attr(enum line_type type)
1363         assert(type < ARRAY_SIZE(line_info));
1364         return COLOR_PAIR(type) | line_info[type].attr;
1367 static struct line_info *
1368 get_line_info(const char *name)
1370         size_t namelen = strlen(name);
1371         enum line_type type;
1373         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1374                 if (enum_equals(line_info[type], name, namelen))
1375                         return &line_info[type];
1377         return NULL;
1380 static void
1381 init_colors(void)
1383         int default_bg = line_info[LINE_DEFAULT].bg;
1384         int default_fg = line_info[LINE_DEFAULT].fg;
1385         enum line_type type;
1387         start_color();
1389         if (assume_default_colors(default_fg, default_bg) == ERR) {
1390                 default_bg = COLOR_BLACK;
1391                 default_fg = COLOR_WHITE;
1392         }
1394         for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1395                 struct line_info *info = &line_info[type];
1396                 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1397                 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1399                 init_pair(type, fg, bg);
1400         }
1403 struct line {
1404         enum line_type type;
1406         /* State flags */
1407         unsigned int selected:1;
1408         unsigned int dirty:1;
1409         unsigned int cleareol:1;
1410         unsigned int other:16;
1412         void *data;             /* User data */
1413 };
1416 /*
1417  * Keys
1418  */
1420 struct keybinding {
1421         int alias;
1422         enum request request;
1423 };
1425 static struct keybinding default_keybindings[] = {
1426         /* View switching */
1427         { 'm',          REQ_VIEW_MAIN },
1428         { 'd',          REQ_VIEW_DIFF },
1429         { 'l',          REQ_VIEW_LOG },
1430         { 't',          REQ_VIEW_TREE },
1431         { 'f',          REQ_VIEW_BLOB },
1432         { 'B',          REQ_VIEW_BLAME },
1433         { 'H',          REQ_VIEW_BRANCH },
1434         { 'p',          REQ_VIEW_PAGER },
1435         { 'h',          REQ_VIEW_HELP },
1436         { 'S',          REQ_VIEW_STATUS },
1437         { 'c',          REQ_VIEW_STAGE },
1439         /* View manipulation */
1440         { 'q',          REQ_VIEW_CLOSE },
1441         { KEY_TAB,      REQ_VIEW_NEXT },
1442         { KEY_RETURN,   REQ_ENTER },
1443         { KEY_UP,       REQ_PREVIOUS },
1444         { KEY_CTL('P'), REQ_PREVIOUS },
1445         { KEY_DOWN,     REQ_NEXT },
1446         { KEY_CTL('N'), REQ_NEXT },
1447         { 'R',          REQ_REFRESH },
1448         { KEY_F(5),     REQ_REFRESH },
1449         { 'O',          REQ_MAXIMIZE },
1451         /* Cursor navigation */
1452         { 'k',          REQ_MOVE_UP },
1453         { 'j',          REQ_MOVE_DOWN },
1454         { KEY_HOME,     REQ_MOVE_FIRST_LINE },
1455         { KEY_END,      REQ_MOVE_LAST_LINE },
1456         { KEY_NPAGE,    REQ_MOVE_PAGE_DOWN },
1457         { KEY_CTL('D'), REQ_MOVE_PAGE_DOWN },
1458         { ' ',          REQ_MOVE_PAGE_DOWN },
1459         { KEY_PPAGE,    REQ_MOVE_PAGE_UP },
1460         { KEY_CTL('U'), REQ_MOVE_PAGE_UP },
1461         { 'b',          REQ_MOVE_PAGE_UP },
1462         { '-',          REQ_MOVE_PAGE_UP },
1464         /* Scrolling */
1465         { '|',          REQ_SCROLL_FIRST_COL },
1466         { KEY_LEFT,     REQ_SCROLL_LEFT },
1467         { KEY_RIGHT,    REQ_SCROLL_RIGHT },
1468         { KEY_IC,       REQ_SCROLL_LINE_UP },
1469         { KEY_CTL('Y'), REQ_SCROLL_LINE_UP },
1470         { KEY_DC,       REQ_SCROLL_LINE_DOWN },
1471         { KEY_CTL('E'), REQ_SCROLL_LINE_DOWN },
1472         { 'w',          REQ_SCROLL_PAGE_UP },
1473         { 's',          REQ_SCROLL_PAGE_DOWN },
1475         /* Searching */
1476         { '/',          REQ_SEARCH },
1477         { '?',          REQ_SEARCH_BACK },
1478         { 'n',          REQ_FIND_NEXT },
1479         { 'N',          REQ_FIND_PREV },
1481         /* Misc */
1482         { 'Q',          REQ_QUIT },
1483         { 'z',          REQ_STOP_LOADING },
1484         { 'v',          REQ_SHOW_VERSION },
1485         { 'r',          REQ_SCREEN_REDRAW },
1486         { KEY_CTL('L'), REQ_SCREEN_REDRAW },
1487         { 'o',          REQ_OPTIONS },
1488         { '.',          REQ_TOGGLE_LINENO },
1489         { 'D',          REQ_TOGGLE_DATE },
1490         { 'A',          REQ_TOGGLE_AUTHOR },
1491         { 'g',          REQ_TOGGLE_REV_GRAPH },
1492         { 'F',          REQ_TOGGLE_REFS },
1493         { 'I',          REQ_TOGGLE_SORT_ORDER },
1494         { 'i',          REQ_TOGGLE_SORT_FIELD },
1495         { ':',          REQ_PROMPT },
1496         { 'u',          REQ_STATUS_UPDATE },
1497         { '!',          REQ_STATUS_REVERT },
1498         { 'M',          REQ_STATUS_MERGE },
1499         { '@',          REQ_STAGE_NEXT },
1500         { ',',          REQ_PARENT },
1501         { 'e',          REQ_EDIT },
1502 };
1504 #define KEYMAP_INFO \
1505         KEYMAP_(GENERIC), \
1506         KEYMAP_(MAIN), \
1507         KEYMAP_(DIFF), \
1508         KEYMAP_(LOG), \
1509         KEYMAP_(TREE), \
1510         KEYMAP_(BLOB), \
1511         KEYMAP_(BLAME), \
1512         KEYMAP_(BRANCH), \
1513         KEYMAP_(PAGER), \
1514         KEYMAP_(HELP), \
1515         KEYMAP_(STATUS), \
1516         KEYMAP_(STAGE)
1518 enum keymap {
1519 #define KEYMAP_(name) KEYMAP_##name
1520         KEYMAP_INFO
1521 #undef  KEYMAP_
1522 };
1524 static const struct enum_map keymap_table[] = {
1525 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1526         KEYMAP_INFO
1527 #undef  KEYMAP_
1528 };
1530 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1532 struct keybinding_table {
1533         struct keybinding *data;
1534         size_t size;
1535 };
1537 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1539 static void
1540 add_keybinding(enum keymap keymap, enum request request, int key)
1542         struct keybinding_table *table = &keybindings[keymap];
1543         size_t i;
1545         for (i = 0; i < keybindings[keymap].size; i++) {
1546                 if (keybindings[keymap].data[i].alias == key) {
1547                         keybindings[keymap].data[i].request = request;
1548                         return;
1549                 }
1550         }
1552         table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1553         if (!table->data)
1554                 die("Failed to allocate keybinding");
1555         table->data[table->size].alias = key;
1556         table->data[table->size++].request = request;
1558         if (request == REQ_NONE && keymap == KEYMAP_GENERIC) {
1559                 int i;
1561                 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1562                         if (default_keybindings[i].alias == key)
1563                                 default_keybindings[i].request = REQ_NONE;
1564         }
1567 /* Looks for a key binding first in the given map, then in the generic map, and
1568  * lastly in the default keybindings. */
1569 static enum request
1570 get_keybinding(enum keymap keymap, int key)
1572         size_t i;
1574         for (i = 0; i < keybindings[keymap].size; i++)
1575                 if (keybindings[keymap].data[i].alias == key)
1576                         return keybindings[keymap].data[i].request;
1578         for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1579                 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1580                         return keybindings[KEYMAP_GENERIC].data[i].request;
1582         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1583                 if (default_keybindings[i].alias == key)
1584                         return default_keybindings[i].request;
1586         return (enum request) key;
1590 struct key {
1591         const char *name;
1592         int value;
1593 };
1595 static const struct key key_table[] = {
1596         { "Enter",      KEY_RETURN },
1597         { "Space",      ' ' },
1598         { "Backspace",  KEY_BACKSPACE },
1599         { "Tab",        KEY_TAB },
1600         { "Escape",     KEY_ESC },
1601         { "Left",       KEY_LEFT },
1602         { "Right",      KEY_RIGHT },
1603         { "Up",         KEY_UP },
1604         { "Down",       KEY_DOWN },
1605         { "Insert",     KEY_IC },
1606         { "Delete",     KEY_DC },
1607         { "Hash",       '#' },
1608         { "Home",       KEY_HOME },
1609         { "End",        KEY_END },
1610         { "PageUp",     KEY_PPAGE },
1611         { "PageDown",   KEY_NPAGE },
1612         { "F1",         KEY_F(1) },
1613         { "F2",         KEY_F(2) },
1614         { "F3",         KEY_F(3) },
1615         { "F4",         KEY_F(4) },
1616         { "F5",         KEY_F(5) },
1617         { "F6",         KEY_F(6) },
1618         { "F7",         KEY_F(7) },
1619         { "F8",         KEY_F(8) },
1620         { "F9",         KEY_F(9) },
1621         { "F10",        KEY_F(10) },
1622         { "F11",        KEY_F(11) },
1623         { "F12",        KEY_F(12) },
1624 };
1626 static int
1627 get_key_value(const char *name)
1629         int i;
1631         for (i = 0; i < ARRAY_SIZE(key_table); i++)
1632                 if (!strcasecmp(key_table[i].name, name))
1633                         return key_table[i].value;
1635         if (strlen(name) == 2 && name[0] == '^' && isprint(*name))
1636                 return (int)name[1] & 0x1f;
1637         if (strlen(name) == 1 && isprint(*name))
1638                 return (int) *name;
1639         return ERR;
1642 static const char *
1643 get_key_name(int key_value)
1645         static char key_char[] = "'X'\0";
1646         const char *seq = NULL;
1647         int key;
1649         for (key = 0; key < ARRAY_SIZE(key_table); key++)
1650                 if (key_table[key].value == key_value)
1651                         seq = key_table[key].name;
1653         if (seq == NULL && key_value < 0x7f) {
1654                 char *s = key_char + 1;
1656                 if (key_value >= 0x20) {
1657                         *s++ = key_value;
1658                 } else {
1659                         *s++ = '^';
1660                         *s++ = 0x40 | (key_value & 0x1f);
1661                 }
1662                 *s++ = '\'';
1663                 *s++ = '\0';
1664                 seq = key_char;
1665         }
1667         return seq ? seq : "(no key)";
1670 static bool
1671 append_key(char *buf, size_t *pos, const struct keybinding *keybinding)
1673         const char *sep = *pos > 0 ? ", " : "";
1674         const char *keyname = get_key_name(keybinding->alias);
1676         return string_nformat(buf, BUFSIZ, pos, "%s%s", sep, keyname);
1679 static bool
1680 append_keymap_request_keys(char *buf, size_t *pos, enum request request,
1681                            enum keymap keymap, bool all)
1683         int i;
1685         for (i = 0; i < keybindings[keymap].size; i++) {
1686                 if (keybindings[keymap].data[i].request == request) {
1687                         if (!append_key(buf, pos, &keybindings[keymap].data[i]))
1688                                 return FALSE;
1689                         if (!all)
1690                                 break;
1691                 }
1692         }
1694         return TRUE;
1697 #define get_key(keymap, request) get_keys(keymap, request, FALSE)
1699 static const char *
1700 get_keys(enum keymap keymap, enum request request, bool all)
1702         static char buf[BUFSIZ];
1703         size_t pos = 0;
1704         int i;
1706         buf[pos] = 0;
1708         if (!append_keymap_request_keys(buf, &pos, request, keymap, all))
1709                 return "Too many keybindings!";
1710         if (pos > 0 && !all)
1711                 return buf;
1713         if (keymap != KEYMAP_GENERIC) {
1714                 /* Only the generic keymap includes the default keybindings when
1715                  * listing all keys. */
1716                 if (all)
1717                         return buf;
1719                 if (!append_keymap_request_keys(buf, &pos, request, KEYMAP_GENERIC, all))
1720                         return "Too many keybindings!";
1721                 if (pos)
1722                         return buf;
1723         }
1725         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1726                 if (default_keybindings[i].request == request) {
1727                         if (!append_key(buf, &pos, &default_keybindings[i]))
1728                                 return "Too many keybindings!";
1729                         if (!all)
1730                                 return buf;
1731                 }
1732         }
1734         return buf;
1737 struct run_request {
1738         enum keymap keymap;
1739         int key;
1740         const char **argv;
1741 };
1743 static struct run_request *run_request;
1744 static size_t run_requests;
1746 DEFINE_ALLOCATOR(realloc_run_requests, struct run_request, 8)
1748 static enum request
1749 add_run_request(enum keymap keymap, int key, const char **argv)
1751         struct run_request *req;
1753         if (!realloc_run_requests(&run_request, run_requests, 1))
1754                 return REQ_NONE;
1756         req = &run_request[run_requests];
1757         req->keymap = keymap;
1758         req->key = key;
1759         req->argv = NULL;
1761         if (!argv_copy(&req->argv, argv))
1762                 return REQ_NONE;
1764         return REQ_NONE + ++run_requests;
1767 static struct run_request *
1768 get_run_request(enum request request)
1770         if (request <= REQ_NONE)
1771                 return NULL;
1772         return &run_request[request - REQ_NONE - 1];
1775 static void
1776 add_builtin_run_requests(void)
1778         const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1779         const char *checkout[] = { "git", "checkout", "%(branch)", NULL };
1780         const char *commit[] = { "git", "commit", NULL };
1781         const char *gc[] = { "git", "gc", NULL };
1782         struct run_request reqs[] = {
1783                 { KEYMAP_MAIN,    'C', cherry_pick },
1784                 { KEYMAP_STATUS,  'C', commit },
1785                 { KEYMAP_BRANCH,  'C', checkout },
1786                 { KEYMAP_GENERIC, 'G', gc },
1787         };
1788         int i;
1790         for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1791                 enum request req = get_keybinding(reqs[i].keymap, reqs[i].key);
1793                 if (req != reqs[i].key)
1794                         continue;
1795                 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argv);
1796                 if (req != REQ_NONE)
1797                         add_keybinding(reqs[i].keymap, req, reqs[i].key);
1798         }
1801 /*
1802  * User config file handling.
1803  */
1805 #define OPT_ERR_INFO \
1806         OPT_ERR_(INTEGER_VALUE_OUT_OF_BOUND, "Integer value out of bound"), \
1807         OPT_ERR_(INVALID_STEP_VALUE, "Invalid step value"), \
1808         OPT_ERR_(NO_OPTION_VALUE, "No option value"), \
1809         OPT_ERR_(NO_VALUE_ASSIGNED, "No value assigned"), \
1810         OPT_ERR_(OBSOLETE_REQUEST_NAME, "Obsolete request name"), \
1811         OPT_ERR_(TOO_MANY_OPTION_ARGUMENTS, "Too many option arguments"), \
1812         OPT_ERR_(UNKNOWN_ATTRIBUTE, "Unknown attribute"), \
1813         OPT_ERR_(UNKNOWN_COLOR, "Unknown color"), \
1814         OPT_ERR_(UNKNOWN_COLOR_NAME, "Unknown color name"), \
1815         OPT_ERR_(UNKNOWN_KEY, "Unknown key"), \
1816         OPT_ERR_(UNKNOWN_KEY_MAP, "Unknown key map"), \
1817         OPT_ERR_(UNKNOWN_OPTION_COMMAND, "Unknown option command"), \
1818         OPT_ERR_(UNKNOWN_REQUEST_NAME, "Unknown request name"), \
1819         OPT_ERR_(UNKNOWN_VARIABLE_NAME, "Unknown variable name"), \
1820         OPT_ERR_(UNMATCHED_QUOTATION, "Unmatched quotation"), \
1821         OPT_ERR_(WRONG_NUMBER_OF_ARGUMENTS, "Wrong number of arguments"),
1823 enum option_code {
1824 #define OPT_ERR_(name, msg) OPT_ERR_ ## name
1825         OPT_ERR_INFO
1826 #undef  OPT_ERR_
1827         OPT_OK
1828 };
1830 static const char *option_errors[] = {
1831 #define OPT_ERR_(name, msg) msg
1832         OPT_ERR_INFO
1833 #undef  OPT_ERR_
1834 };
1836 static const struct enum_map color_map[] = {
1837 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1838         COLOR_MAP(DEFAULT),
1839         COLOR_MAP(BLACK),
1840         COLOR_MAP(BLUE),
1841         COLOR_MAP(CYAN),
1842         COLOR_MAP(GREEN),
1843         COLOR_MAP(MAGENTA),
1844         COLOR_MAP(RED),
1845         COLOR_MAP(WHITE),
1846         COLOR_MAP(YELLOW),
1847 };
1849 static const struct enum_map attr_map[] = {
1850 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1851         ATTR_MAP(NORMAL),
1852         ATTR_MAP(BLINK),
1853         ATTR_MAP(BOLD),
1854         ATTR_MAP(DIM),
1855         ATTR_MAP(REVERSE),
1856         ATTR_MAP(STANDOUT),
1857         ATTR_MAP(UNDERLINE),
1858 };
1860 #define set_attribute(attr, name)       map_enum(attr, attr_map, name)
1862 static enum option_code
1863 parse_step(double *opt, const char *arg)
1865         *opt = atoi(arg);
1866         if (!strchr(arg, '%'))
1867                 return OPT_OK;
1869         /* "Shift down" so 100% and 1 does not conflict. */
1870         *opt = (*opt - 1) / 100;
1871         if (*opt >= 1.0) {
1872                 *opt = 0.99;
1873                 return OPT_ERR_INVALID_STEP_VALUE;
1874         }
1875         if (*opt < 0.0) {
1876                 *opt = 1;
1877                 return OPT_ERR_INVALID_STEP_VALUE;
1878         }
1879         return OPT_OK;
1882 static enum option_code
1883 parse_int(int *opt, const char *arg, int min, int max)
1885         int value = atoi(arg);
1887         if (min <= value && value <= max) {
1888                 *opt = value;
1889                 return OPT_OK;
1890         }
1892         return OPT_ERR_INTEGER_VALUE_OUT_OF_BOUND;
1895 static bool
1896 set_color(int *color, const char *name)
1898         if (map_enum(color, color_map, name))
1899                 return TRUE;
1900         if (!prefixcmp(name, "color"))
1901                 return parse_int(color, name + 5, 0, 255) == OK;
1902         return FALSE;
1905 /* Wants: object fgcolor bgcolor [attribute] */
1906 static enum option_code
1907 option_color_command(int argc, const char *argv[])
1909         struct line_info *info;
1911         if (argc < 3)
1912                 return OPT_ERR_WRONG_NUMBER_OF_ARGUMENTS;
1914         info = get_line_info(argv[0]);
1915         if (!info) {
1916                 static const struct enum_map obsolete[] = {
1917                         ENUM_MAP("main-delim",  LINE_DELIMITER),
1918                         ENUM_MAP("main-date",   LINE_DATE),
1919                         ENUM_MAP("main-author", LINE_AUTHOR),
1920                 };
1921                 int index;
1923                 if (!map_enum(&index, obsolete, argv[0])) {
1924                         return OPT_ERR_UNKNOWN_COLOR_NAME;
1925                 }
1926                 info = &line_info[index];
1927         }
1929         if (!set_color(&info->fg, argv[1]) ||
1930             !set_color(&info->bg, argv[2])) {
1931                 return OPT_ERR_UNKNOWN_COLOR;
1932         }
1934         info->attr = 0;
1935         while (argc-- > 3) {
1936                 int attr;
1938                 if (!set_attribute(&attr, argv[argc])) {
1939                         return OPT_ERR_UNKNOWN_ATTRIBUTE;
1940                 }
1941                 info->attr |= attr;
1942         }
1944         return OPT_OK;
1947 static enum option_code
1948 parse_bool(bool *opt, const char *arg)
1950         *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1951                 ? TRUE : FALSE;
1952         return OPT_OK;
1955 static enum option_code
1956 parse_enum_do(unsigned int *opt, const char *arg,
1957               const struct enum_map *map, size_t map_size)
1959         bool is_true;
1961         assert(map_size > 1);
1963         if (map_enum_do(map, map_size, (int *) opt, arg))
1964                 return OPT_OK;
1966         parse_bool(&is_true, arg);
1967         *opt = is_true ? map[1].value : map[0].value;
1968         return OPT_OK;
1971 #define parse_enum(opt, arg, map) \
1972         parse_enum_do(opt, arg, map, ARRAY_SIZE(map))
1974 static enum option_code
1975 parse_string(char *opt, const char *arg, size_t optsize)
1977         int arglen = strlen(arg);
1979         switch (arg[0]) {
1980         case '\"':
1981         case '\'':
1982                 if (arglen == 1 || arg[arglen - 1] != arg[0])
1983                         return OPT_ERR_UNMATCHED_QUOTATION;
1984                 arg += 1; arglen -= 2;
1985         default:
1986                 string_ncopy_do(opt, optsize, arg, arglen);
1987                 return OPT_OK;
1988         }
1991 /* Wants: name = value */
1992 static enum option_code
1993 option_set_command(int argc, const char *argv[])
1995         if (argc != 3)
1996                 return OPT_ERR_WRONG_NUMBER_OF_ARGUMENTS;
1998         if (strcmp(argv[1], "="))
1999                 return OPT_ERR_NO_VALUE_ASSIGNED;
2001         if (!strcmp(argv[0], "show-author"))
2002                 return parse_enum(&opt_author, argv[2], author_map);
2004         if (!strcmp(argv[0], "show-date"))
2005                 return parse_enum(&opt_date, argv[2], date_map);
2007         if (!strcmp(argv[0], "show-rev-graph"))
2008                 return parse_bool(&opt_rev_graph, argv[2]);
2010         if (!strcmp(argv[0], "show-refs"))
2011                 return parse_bool(&opt_show_refs, argv[2]);
2013         if (!strcmp(argv[0], "show-line-numbers"))
2014                 return parse_bool(&opt_line_number, argv[2]);
2016         if (!strcmp(argv[0], "line-graphics"))
2017                 return parse_bool(&opt_line_graphics, argv[2]);
2019         if (!strcmp(argv[0], "line-number-interval"))
2020                 return parse_int(&opt_num_interval, argv[2], 1, 1024);
2022         if (!strcmp(argv[0], "author-width"))
2023                 return parse_int(&opt_author_cols, argv[2], 0, 1024);
2025         if (!strcmp(argv[0], "horizontal-scroll"))
2026                 return parse_step(&opt_hscroll, argv[2]);
2028         if (!strcmp(argv[0], "split-view-height"))
2029                 return parse_step(&opt_scale_split_view, argv[2]);
2031         if (!strcmp(argv[0], "tab-size"))
2032                 return parse_int(&opt_tab_size, argv[2], 1, 1024);
2034         if (!strcmp(argv[0], "commit-encoding"))
2035                 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
2037         if (!strcmp(argv[0], "status-untracked-dirs"))
2038                 return parse_bool(&opt_untracked_dirs_content, argv[2]);
2040         return OPT_ERR_UNKNOWN_VARIABLE_NAME;
2043 /* Wants: mode request key */
2044 static enum option_code
2045 option_bind_command(int argc, const char *argv[])
2047         enum request request;
2048         int keymap = -1;
2049         int key;
2051         if (argc < 3)
2052                 return OPT_ERR_WRONG_NUMBER_OF_ARGUMENTS;
2054         if (!set_keymap(&keymap, argv[0]))
2055                 return OPT_ERR_UNKNOWN_KEY_MAP;
2057         key = get_key_value(argv[1]);
2058         if (key == ERR)
2059                 return OPT_ERR_UNKNOWN_KEY;
2061         request = get_request(argv[2]);
2062         if (request == REQ_UNKNOWN) {
2063                 static const struct enum_map obsolete[] = {
2064                         ENUM_MAP("cherry-pick",         REQ_NONE),
2065                         ENUM_MAP("screen-resize",       REQ_NONE),
2066                         ENUM_MAP("tree-parent",         REQ_PARENT),
2067                 };
2068                 int alias;
2070                 if (map_enum(&alias, obsolete, argv[2])) {
2071                         if (alias != REQ_NONE)
2072                                 add_keybinding(keymap, alias, key);
2073                         return OPT_ERR_OBSOLETE_REQUEST_NAME;
2074                 }
2075         }
2076         if (request == REQ_UNKNOWN && *argv[2]++ == '!')
2077                 request = add_run_request(keymap, key, argv + 2);
2078         if (request == REQ_UNKNOWN)
2079                 return OPT_ERR_UNKNOWN_REQUEST_NAME;
2081         add_keybinding(keymap, request, key);
2083         return OPT_OK;
2086 static enum option_code
2087 set_option(const char *opt, char *value)
2089         const char *argv[SIZEOF_ARG];
2090         int argc = 0;
2092         if (!argv_from_string(argv, &argc, value))
2093                 return OPT_ERR_TOO_MANY_OPTION_ARGUMENTS;
2095         if (!strcmp(opt, "color"))
2096                 return option_color_command(argc, argv);
2098         if (!strcmp(opt, "set"))
2099                 return option_set_command(argc, argv);
2101         if (!strcmp(opt, "bind"))
2102                 return option_bind_command(argc, argv);
2104         return OPT_ERR_UNKNOWN_OPTION_COMMAND;
2107 struct config_state {
2108         int lineno;
2109         bool errors;
2110 };
2112 static int
2113 read_option(char *opt, size_t optlen, char *value, size_t valuelen, void *data)
2115         struct config_state *config = data;
2116         enum option_code status = OPT_ERR_NO_OPTION_VALUE;
2118         config->lineno++;
2120         /* Check for comment markers, since read_properties() will
2121          * only ensure opt and value are split at first " \t". */
2122         optlen = strcspn(opt, "#");
2123         if (optlen == 0)
2124                 return OK;
2126         if (opt[optlen] == 0) {
2127                 /* Look for comment endings in the value. */
2128                 size_t len = strcspn(value, "#");
2130                 if (len < valuelen) {
2131                         valuelen = len;
2132                         value[valuelen] = 0;
2133                 }
2135                 status = set_option(opt, value);
2136         }
2138         if (status != OPT_OK) {
2139                 warn("Error on line %d, near '%.*s': %s",
2140                      config->lineno, (int) optlen, opt, option_errors[status]);
2141                 config->errors = TRUE;
2142         }
2144         /* Always keep going if errors are encountered. */
2145         return OK;
2148 static void
2149 load_option_file(const char *path)
2151         struct config_state config = { 0, FALSE };
2152         struct io io;
2154         /* It's OK that the file doesn't exist. */
2155         if (!io_open(&io, "%s", path))
2156                 return;
2158         if (io_load(&io, " \t", read_option, &config) == ERR ||
2159             config.errors == TRUE)
2160                 warn("Errors while loading %s.", path);
2163 static int
2164 load_options(void)
2166         const char *home = getenv("HOME");
2167         const char *tigrc_user = getenv("TIGRC_USER");
2168         const char *tigrc_system = getenv("TIGRC_SYSTEM");
2169         const char *tig_diff_opts = getenv("TIG_DIFF_OPTS");
2170         char buf[SIZEOF_STR];
2172         if (!tigrc_system)
2173                 tigrc_system = SYSCONFDIR "/tigrc";
2174         load_option_file(tigrc_system);
2176         if (!tigrc_user) {
2177                 if (!home || !string_format(buf, "%s/.tigrc", home))
2178                         return ERR;
2179                 tigrc_user = buf;
2180         }
2181         load_option_file(tigrc_user);
2183         /* Add _after_ loading config files to avoid adding run requests
2184          * that conflict with keybindings. */
2185         add_builtin_run_requests();
2187         if (!opt_diff_argv && tig_diff_opts && *tig_diff_opts) {
2188                 static const char *diff_opts[SIZEOF_ARG] = { NULL };
2189                 int argc = 0;
2191                 if (!string_format(buf, "%s", tig_diff_opts) ||
2192                     !argv_from_string(diff_opts, &argc, buf))
2193                         die("TIG_DIFF_OPTS contains too many arguments");
2194                 else if (!argv_copy(&opt_diff_argv, diff_opts))
2195                         die("Failed to format TIG_DIFF_OPTS arguments");
2196         }
2198         return OK;
2202 /*
2203  * The viewer
2204  */
2206 struct view;
2207 struct view_ops;
2209 /* The display array of active views and the index of the current view. */
2210 static struct view *display[2];
2211 static unsigned int current_view;
2213 #define foreach_displayed_view(view, i) \
2214         for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
2216 #define displayed_views()       (display[1] != NULL ? 2 : 1)
2218 /* Current head and commit ID */
2219 static char ref_blob[SIZEOF_REF]        = "";
2220 static char ref_commit[SIZEOF_REF]      = "HEAD";
2221 static char ref_head[SIZEOF_REF]        = "HEAD";
2222 static char ref_branch[SIZEOF_REF]      = "";
2224 enum view_type {
2225         VIEW_MAIN,
2226         VIEW_DIFF,
2227         VIEW_LOG,
2228         VIEW_TREE,
2229         VIEW_BLOB,
2230         VIEW_BLAME,
2231         VIEW_BRANCH,
2232         VIEW_HELP,
2233         VIEW_PAGER,
2234         VIEW_STATUS,
2235         VIEW_STAGE,
2236 };
2238 struct view {
2239         enum view_type type;    /* View type */
2240         const char *name;       /* View name */
2241         const char *cmd_env;    /* Command line set via environment */
2242         const char *id;         /* Points to either of ref_{head,commit,blob} */
2244         struct view_ops *ops;   /* View operations */
2246         enum keymap keymap;     /* What keymap does this view have */
2247         bool git_dir;           /* Whether the view requires a git directory. */
2249         char ref[SIZEOF_REF];   /* Hovered commit reference */
2250         char vid[SIZEOF_REF];   /* View ID. Set to id member when updating. */
2252         int height, width;      /* The width and height of the main window */
2253         WINDOW *win;            /* The main window */
2254         WINDOW *title;          /* The title window living below the main window */
2256         /* Navigation */
2257         unsigned long offset;   /* Offset of the window top */
2258         unsigned long yoffset;  /* Offset from the window side. */
2259         unsigned long lineno;   /* Current line number */
2260         unsigned long p_offset; /* Previous offset of the window top */
2261         unsigned long p_yoffset;/* Previous offset from the window side */
2262         unsigned long p_lineno; /* Previous current line number */
2263         bool p_restore;         /* Should the previous position be restored. */
2265         /* Searching */
2266         char grep[SIZEOF_STR];  /* Search string */
2267         regex_t *regex;         /* Pre-compiled regexp */
2269         /* If non-NULL, points to the view that opened this view. If this view
2270          * is closed tig will switch back to the parent view. */
2271         struct view *parent;
2272         struct view *prev;
2274         /* Buffering */
2275         size_t lines;           /* Total number of lines */
2276         struct line *line;      /* Line index */
2277         unsigned int digits;    /* Number of digits in the lines member. */
2279         /* Drawing */
2280         struct line *curline;   /* Line currently being drawn. */
2281         enum line_type curtype; /* Attribute currently used for drawing. */
2282         unsigned long col;      /* Column when drawing. */
2283         bool has_scrolled;      /* View was scrolled. */
2285         /* Loading */
2286         const char **argv;      /* Shell command arguments. */
2287         const char *dir;        /* Directory from which to execute. */
2288         struct io io;
2289         struct io *pipe;
2290         time_t start_time;
2291         time_t update_secs;
2292 };
2294 struct view_ops {
2295         /* What type of content being displayed. Used in the title bar. */
2296         const char *type;
2297         /* Default command arguments. */
2298         const char **argv;
2299         /* Open and reads in all view content. */
2300         bool (*open)(struct view *view);
2301         /* Read one line; updates view->line. */
2302         bool (*read)(struct view *view, char *data);
2303         /* Draw one line; @lineno must be < view->height. */
2304         bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
2305         /* Depending on view handle a special requests. */
2306         enum request (*request)(struct view *view, enum request request, struct line *line);
2307         /* Search for regexp in a line. */
2308         bool (*grep)(struct view *view, struct line *line);
2309         /* Select line */
2310         void (*select)(struct view *view, struct line *line);
2311         /* Prepare view for loading */
2312         bool (*prepare)(struct view *view);
2313 };
2315 static struct view_ops blame_ops;
2316 static struct view_ops blob_ops;
2317 static struct view_ops diff_ops;
2318 static struct view_ops help_ops;
2319 static struct view_ops log_ops;
2320 static struct view_ops main_ops;
2321 static struct view_ops pager_ops;
2322 static struct view_ops stage_ops;
2323 static struct view_ops status_ops;
2324 static struct view_ops tree_ops;
2325 static struct view_ops branch_ops;
2327 #define VIEW_STR(type, name, env, ref, ops, map, git) \
2328         { type, name, #env, ref, ops, map, git }
2330 #define VIEW_(id, name, ops, git, ref) \
2331         VIEW_STR(VIEW_##id, name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
2333 static struct view views[] = {
2334         VIEW_(MAIN,   "main",   &main_ops,   TRUE,  ref_head),
2335         VIEW_(DIFF,   "diff",   &diff_ops,   TRUE,  ref_commit),
2336         VIEW_(LOG,    "log",    &log_ops,    TRUE,  ref_head),
2337         VIEW_(TREE,   "tree",   &tree_ops,   TRUE,  ref_commit),
2338         VIEW_(BLOB,   "blob",   &blob_ops,   TRUE,  ref_blob),
2339         VIEW_(BLAME,  "blame",  &blame_ops,  TRUE,  ref_commit),
2340         VIEW_(BRANCH, "branch", &branch_ops, TRUE,  ref_head),
2341         VIEW_(HELP,   "help",   &help_ops,   FALSE, ""),
2342         VIEW_(PAGER,  "pager",  &pager_ops,  FALSE, ""),
2343         VIEW_(STATUS, "status", &status_ops, TRUE,  ""),
2344         VIEW_(STAGE,  "stage",  &stage_ops,  TRUE,  ""),
2345 };
2347 #define VIEW(req)       (&views[(req) - REQ_OFFSET - 1])
2349 #define foreach_view(view, i) \
2350         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2352 #define view_is_displayed(view) \
2353         (view == display[0] || view == display[1])
2355 static enum request
2356 view_request(struct view *view, enum request request)
2358         if (!view || !view->lines)
2359                 return request;
2360         return view->ops->request(view, request, &view->line[view->lineno]);
2364 /*
2365  * View drawing.
2366  */
2368 static inline void
2369 set_view_attr(struct view *view, enum line_type type)
2371         if (!view->curline->selected && view->curtype != type) {
2372                 (void) wattrset(view->win, get_line_attr(type));
2373                 wchgat(view->win, -1, 0, type, NULL);
2374                 view->curtype = type;
2375         }
2378 static int
2379 draw_chars(struct view *view, enum line_type type, const char *string,
2380            int max_len, bool use_tilde)
2382         static char out_buffer[BUFSIZ * 2];
2383         int len = 0;
2384         int col = 0;
2385         int trimmed = FALSE;
2386         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2388         if (max_len <= 0)
2389                 return 0;
2391         len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde, opt_tab_size);
2393         set_view_attr(view, type);
2394         if (len > 0) {
2395                 if (opt_iconv_out != ICONV_NONE) {
2396                         ICONV_CONST char *inbuf = (ICONV_CONST char *) string;
2397                         size_t inlen = len + 1;
2399                         char *outbuf = out_buffer;
2400                         size_t outlen = sizeof(out_buffer);
2402                         size_t ret;
2404                         ret = iconv(opt_iconv_out, &inbuf, &inlen, &outbuf, &outlen);
2405                         if (ret != (size_t) -1) {
2406                                 string = out_buffer;
2407                                 len = sizeof(out_buffer) - outlen;
2408                         }
2409                 }
2411                 waddnstr(view->win, string, len);
2413                 if (trimmed && use_tilde) {
2414                         set_view_attr(view, LINE_DELIMITER);
2415                         waddch(view->win, '~');
2416                         col++;
2417                 }
2418         }
2420         return col;
2423 static int
2424 draw_space(struct view *view, enum line_type type, int max, int spaces)
2426         static char space[] = "                    ";
2427         int col = 0;
2429         spaces = MIN(max, spaces);
2431         while (spaces > 0) {
2432                 int len = MIN(spaces, sizeof(space) - 1);
2434                 col += draw_chars(view, type, space, len, FALSE);
2435                 spaces -= len;
2436         }
2438         return col;
2441 static bool
2442 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
2444         char text[SIZEOF_STR];
2446         do {
2447                 size_t pos = string_expand(text, sizeof(text), string, opt_tab_size);
2449                 view->col += draw_chars(view, type, text, view->width + view->yoffset - view->col, trim);
2450                 string += pos;
2451         } while (*string && view->width + view->yoffset > view->col);
2453         return view->width + view->yoffset <= view->col;
2456 static bool
2457 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
2459         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2460         int max = view->width + view->yoffset - view->col;
2461         int i;
2463         if (max < size)
2464                 size = max;
2466         set_view_attr(view, type);
2467         /* Using waddch() instead of waddnstr() ensures that
2468          * they'll be rendered correctly for the cursor line. */
2469         for (i = skip; i < size; i++)
2470                 waddch(view->win, graphic[i]);
2472         view->col += size;
2473         if (size < max && skip <= size)
2474                 waddch(view->win, ' ');
2475         view->col++;
2477         return view->width + view->yoffset <= view->col;
2480 static bool
2481 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
2483         int max = MIN(view->width + view->yoffset - view->col, len);
2484         int col;
2486         if (text)
2487                 col = draw_chars(view, type, text, max - 1, trim);
2488         else
2489                 col = draw_space(view, type, max - 1, max - 1);
2491         view->col += col;
2492         view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
2493         return view->width + view->yoffset <= view->col;
2496 static bool
2497 draw_date(struct view *view, struct time *time)
2499         const char *date = mkdate(time, opt_date);
2500         int cols = opt_date == DATE_SHORT ? DATE_SHORT_COLS : DATE_COLS;
2502         return draw_field(view, LINE_DATE, date, cols, FALSE);
2505 static bool
2506 draw_author(struct view *view, const char *author)
2508         bool trim = opt_author_cols == 0 || opt_author_cols > 5;
2509         bool abbreviate = opt_author == AUTHOR_ABBREVIATED || !trim;
2511         if (abbreviate && author)
2512                 author = get_author_initials(author);
2514         return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2517 static bool
2518 draw_mode(struct view *view, mode_t mode)
2520         const char *str;
2522         if (S_ISDIR(mode))
2523                 str = "drwxr-xr-x";
2524         else if (S_ISLNK(mode))
2525                 str = "lrwxrwxrwx";
2526         else if (S_ISGITLINK(mode))
2527                 str = "m---------";
2528         else if (S_ISREG(mode) && mode & S_IXUSR)
2529                 str = "-rwxr-xr-x";
2530         else if (S_ISREG(mode))
2531                 str = "-rw-r--r--";
2532         else
2533                 str = "----------";
2535         return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2538 static bool
2539 draw_lineno(struct view *view, unsigned int lineno)
2541         char number[10];
2542         int digits3 = view->digits < 3 ? 3 : view->digits;
2543         int max = MIN(view->width + view->yoffset - view->col, digits3);
2544         char *text = NULL;
2545         chtype separator = opt_line_graphics ? ACS_VLINE : '|';
2547         lineno += view->offset + 1;
2548         if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2549                 static char fmt[] = "%1ld";
2551                 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2552                 if (string_format(number, fmt, lineno))
2553                         text = number;
2554         }
2555         if (text)
2556                 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2557         else
2558                 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2559         return draw_graphic(view, LINE_DEFAULT, &separator, 1);
2562 static bool
2563 draw_view_line(struct view *view, unsigned int lineno)
2565         struct line *line;
2566         bool selected = (view->offset + lineno == view->lineno);
2568         assert(view_is_displayed(view));
2570         if (view->offset + lineno >= view->lines)
2571                 return FALSE;
2573         line = &view->line[view->offset + lineno];
2575         wmove(view->win, lineno, 0);
2576         if (line->cleareol)
2577                 wclrtoeol(view->win);
2578         view->col = 0;
2579         view->curline = line;
2580         view->curtype = LINE_NONE;
2581         line->selected = FALSE;
2582         line->dirty = line->cleareol = 0;
2584         if (selected) {
2585                 set_view_attr(view, LINE_CURSOR);
2586                 line->selected = TRUE;
2587                 view->ops->select(view, line);
2588         }
2590         return view->ops->draw(view, line, lineno);
2593 static void
2594 redraw_view_dirty(struct view *view)
2596         bool dirty = FALSE;
2597         int lineno;
2599         for (lineno = 0; lineno < view->height; lineno++) {
2600                 if (view->offset + lineno >= view->lines)
2601                         break;
2602                 if (!view->line[view->offset + lineno].dirty)
2603                         continue;
2604                 dirty = TRUE;
2605                 if (!draw_view_line(view, lineno))
2606                         break;
2607         }
2609         if (!dirty)
2610                 return;
2611         wnoutrefresh(view->win);
2614 static void
2615 redraw_view_from(struct view *view, int lineno)
2617         assert(0 <= lineno && lineno < view->height);
2619         for (; lineno < view->height; lineno++) {
2620                 if (!draw_view_line(view, lineno))
2621                         break;
2622         }
2624         wnoutrefresh(view->win);
2627 static void
2628 redraw_view(struct view *view)
2630         werase(view->win);
2631         redraw_view_from(view, 0);
2635 static void
2636 update_view_title(struct view *view)
2638         char buf[SIZEOF_STR];
2639         char state[SIZEOF_STR];
2640         size_t bufpos = 0, statelen = 0;
2642         assert(view_is_displayed(view));
2644         if (view->type != VIEW_STATUS && view->lines) {
2645                 unsigned int view_lines = view->offset + view->height;
2646                 unsigned int lines = view->lines
2647                                    ? MIN(view_lines, view->lines) * 100 / view->lines
2648                                    : 0;
2650                 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2651                                    view->ops->type,
2652                                    view->lineno + 1,
2653                                    view->lines,
2654                                    lines);
2656         }
2658         if (view->pipe) {
2659                 time_t secs = time(NULL) - view->start_time;
2661                 /* Three git seconds are a long time ... */
2662                 if (secs > 2)
2663                         string_format_from(state, &statelen, " loading %lds", secs);
2664         }
2666         string_format_from(buf, &bufpos, "[%s]", view->name);
2667         if (*view->ref && bufpos < view->width) {
2668                 size_t refsize = strlen(view->ref);
2669                 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2671                 if (minsize < view->width)
2672                         refsize = view->width - minsize + 7;
2673                 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2674         }
2676         if (statelen && bufpos < view->width) {
2677                 string_format_from(buf, &bufpos, "%s", state);
2678         }
2680         if (view == display[current_view])
2681                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2682         else
2683                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2685         mvwaddnstr(view->title, 0, 0, buf, bufpos);
2686         wclrtoeol(view->title);
2687         wnoutrefresh(view->title);
2690 static int
2691 apply_step(double step, int value)
2693         if (step >= 1)
2694                 return (int) step;
2695         value *= step + 0.01;
2696         return value ? value : 1;
2699 static void
2700 resize_display(void)
2702         int offset, i;
2703         struct view *base = display[0];
2704         struct view *view = display[1] ? display[1] : display[0];
2706         /* Setup window dimensions */
2708         getmaxyx(stdscr, base->height, base->width);
2710         /* Make room for the status window. */
2711         base->height -= 1;
2713         if (view != base) {
2714                 /* Horizontal split. */
2715                 view->width   = base->width;
2716                 view->height  = apply_step(opt_scale_split_view, base->height);
2717                 view->height  = MAX(view->height, MIN_VIEW_HEIGHT);
2718                 view->height  = MIN(view->height, base->height - MIN_VIEW_HEIGHT);
2719                 base->height -= view->height;
2721                 /* Make room for the title bar. */
2722                 view->height -= 1;
2723         }
2725         /* Make room for the title bar. */
2726         base->height -= 1;
2728         offset = 0;
2730         foreach_displayed_view (view, i) {
2731                 if (!view->win) {
2732                         view->win = newwin(view->height, 0, offset, 0);
2733                         if (!view->win)
2734                                 die("Failed to create %s view", view->name);
2736                         scrollok(view->win, FALSE);
2738                         view->title = newwin(1, 0, offset + view->height, 0);
2739                         if (!view->title)
2740                                 die("Failed to create title window");
2742                 } else {
2743                         wresize(view->win, view->height, view->width);
2744                         mvwin(view->win,   offset, 0);
2745                         mvwin(view->title, offset + view->height, 0);
2746                 }
2748                 offset += view->height + 1;
2749         }
2752 static void
2753 redraw_display(bool clear)
2755         struct view *view;
2756         int i;
2758         foreach_displayed_view (view, i) {
2759                 if (clear)
2760                         wclear(view->win);
2761                 redraw_view(view);
2762                 update_view_title(view);
2763         }
2767 /*
2768  * Option management
2769  */
2771 static void
2772 toggle_enum_option_do(unsigned int *opt, const char *help,
2773                       const struct enum_map *map, size_t size)
2775         *opt = (*opt + 1) % size;
2776         redraw_display(FALSE);
2777         report("Displaying %s %s", enum_name(map[*opt]), help);
2780 #define toggle_enum_option(opt, help, map) \
2781         toggle_enum_option_do(opt, help, map, ARRAY_SIZE(map))
2783 #define toggle_date() toggle_enum_option(&opt_date, "dates", date_map)
2784 #define toggle_author() toggle_enum_option(&opt_author, "author names", author_map)
2786 static void
2787 toggle_view_option(bool *option, const char *help)
2789         *option = !*option;
2790         redraw_display(FALSE);
2791         report("%sabling %s", *option ? "En" : "Dis", help);
2794 static void
2795 open_option_menu(void)
2797         const struct menu_item menu[] = {
2798                 { '.', "line numbers", &opt_line_number },
2799                 { 'D', "date display", &opt_date },
2800                 { 'A', "author display", &opt_author },
2801                 { 'g', "revision graph display", &opt_rev_graph },
2802                 { 'F', "reference display", &opt_show_refs },
2803                 { 0 }
2804         };
2805         int selected = 0;
2807         if (prompt_menu("Toggle option", menu, &selected)) {
2808                 if (menu[selected].data == &opt_date)
2809                         toggle_date();
2810                 else if (menu[selected].data == &opt_author)
2811                         toggle_author();
2812                 else
2813                         toggle_view_option(menu[selected].data, menu[selected].text);
2814         }
2817 static void
2818 maximize_view(struct view *view)
2820         memset(display, 0, sizeof(display));
2821         current_view = 0;
2822         display[current_view] = view;
2823         resize_display();
2824         redraw_display(FALSE);
2825         report("");
2829 /*
2830  * Navigation
2831  */
2833 static bool
2834 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2836         if (lineno >= view->lines)
2837                 lineno = view->lines > 0 ? view->lines - 1 : 0;
2839         if (offset > lineno || offset + view->height <= lineno) {
2840                 unsigned long half = view->height / 2;
2842                 if (lineno > half)
2843                         offset = lineno - half;
2844                 else
2845                         offset = 0;
2846         }
2848         if (offset != view->offset || lineno != view->lineno) {
2849                 view->offset = offset;
2850                 view->lineno = lineno;
2851                 return TRUE;
2852         }
2854         return FALSE;
2857 /* Scrolling backend */
2858 static void
2859 do_scroll_view(struct view *view, int lines)
2861         bool redraw_current_line = FALSE;
2863         /* The rendering expects the new offset. */
2864         view->offset += lines;
2866         assert(0 <= view->offset && view->offset < view->lines);
2867         assert(lines);
2869         /* Move current line into the view. */
2870         if (view->lineno < view->offset) {
2871                 view->lineno = view->offset;
2872                 redraw_current_line = TRUE;
2873         } else if (view->lineno >= view->offset + view->height) {
2874                 view->lineno = view->offset + view->height - 1;
2875                 redraw_current_line = TRUE;
2876         }
2878         assert(view->offset <= view->lineno && view->lineno < view->lines);
2880         /* Redraw the whole screen if scrolling is pointless. */
2881         if (view->height < ABS(lines)) {
2882                 redraw_view(view);
2884         } else {
2885                 int line = lines > 0 ? view->height - lines : 0;
2886                 int end = line + ABS(lines);
2888                 scrollok(view->win, TRUE);
2889                 wscrl(view->win, lines);
2890                 scrollok(view->win, FALSE);
2892                 while (line < end && draw_view_line(view, line))
2893                         line++;
2895                 if (redraw_current_line)
2896                         draw_view_line(view, view->lineno - view->offset);
2897                 wnoutrefresh(view->win);
2898         }
2900         view->has_scrolled = TRUE;
2901         report("");
2904 /* Scroll frontend */
2905 static void
2906 scroll_view(struct view *view, enum request request)
2908         int lines = 1;
2910         assert(view_is_displayed(view));
2912         switch (request) {
2913         case REQ_SCROLL_FIRST_COL:
2914                 view->yoffset = 0;
2915                 redraw_view_from(view, 0);
2916                 report("");
2917                 return;
2918         case REQ_SCROLL_LEFT:
2919                 if (view->yoffset == 0) {
2920                         report("Cannot scroll beyond the first column");
2921                         return;
2922                 }
2923                 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2924                         view->yoffset = 0;
2925                 else
2926                         view->yoffset -= apply_step(opt_hscroll, view->width);
2927                 redraw_view_from(view, 0);
2928                 report("");
2929                 return;
2930         case REQ_SCROLL_RIGHT:
2931                 view->yoffset += apply_step(opt_hscroll, view->width);
2932                 redraw_view(view);
2933                 report("");
2934                 return;
2935         case REQ_SCROLL_PAGE_DOWN:
2936                 lines = view->height;
2937         case REQ_SCROLL_LINE_DOWN:
2938                 if (view->offset + lines > view->lines)
2939                         lines = view->lines - view->offset;
2941                 if (lines == 0 || view->offset + view->height >= view->lines) {
2942                         report("Cannot scroll beyond the last line");
2943                         return;
2944                 }
2945                 break;
2947         case REQ_SCROLL_PAGE_UP:
2948                 lines = view->height;
2949         case REQ_SCROLL_LINE_UP:
2950                 if (lines > view->offset)
2951                         lines = view->offset;
2953                 if (lines == 0) {
2954                         report("Cannot scroll beyond the first line");
2955                         return;
2956                 }
2958                 lines = -lines;
2959                 break;
2961         default:
2962                 die("request %d not handled in switch", request);
2963         }
2965         do_scroll_view(view, lines);
2968 /* Cursor moving */
2969 static void
2970 move_view(struct view *view, enum request request)
2972         int scroll_steps = 0;
2973         int steps;
2975         switch (request) {
2976         case REQ_MOVE_FIRST_LINE:
2977                 steps = -view->lineno;
2978                 break;
2980         case REQ_MOVE_LAST_LINE:
2981                 steps = view->lines - view->lineno - 1;
2982                 break;
2984         case REQ_MOVE_PAGE_UP:
2985                 steps = view->height > view->lineno
2986                       ? -view->lineno : -view->height;
2987                 break;
2989         case REQ_MOVE_PAGE_DOWN:
2990                 steps = view->lineno + view->height >= view->lines
2991                       ? view->lines - view->lineno - 1 : view->height;
2992                 break;
2994         case REQ_MOVE_UP:
2995                 steps = -1;
2996                 break;
2998         case REQ_MOVE_DOWN:
2999                 steps = 1;
3000                 break;
3002         default:
3003                 die("request %d not handled in switch", request);
3004         }
3006         if (steps <= 0 && view->lineno == 0) {
3007                 report("Cannot move beyond the first line");
3008                 return;
3010         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
3011                 report("Cannot move beyond the last line");
3012                 return;
3013         }
3015         /* Move the current line */
3016         view->lineno += steps;
3017         assert(0 <= view->lineno && view->lineno < view->lines);
3019         /* Check whether the view needs to be scrolled */
3020         if (view->lineno < view->offset ||
3021             view->lineno >= view->offset + view->height) {
3022                 scroll_steps = steps;
3023                 if (steps < 0 && -steps > view->offset) {
3024                         scroll_steps = -view->offset;
3026                 } else if (steps > 0) {
3027                         if (view->lineno == view->lines - 1 &&
3028                             view->lines > view->height) {
3029                                 scroll_steps = view->lines - view->offset - 1;
3030                                 if (scroll_steps >= view->height)
3031                                         scroll_steps -= view->height - 1;
3032                         }
3033                 }
3034         }
3036         if (!view_is_displayed(view)) {
3037                 view->offset += scroll_steps;
3038                 assert(0 <= view->offset && view->offset < view->lines);
3039                 view->ops->select(view, &view->line[view->lineno]);
3040                 return;
3041         }
3043         /* Repaint the old "current" line if we be scrolling */
3044         if (ABS(steps) < view->height)
3045                 draw_view_line(view, view->lineno - steps - view->offset);
3047         if (scroll_steps) {
3048                 do_scroll_view(view, scroll_steps);
3049                 return;
3050         }
3052         /* Draw the current line */
3053         draw_view_line(view, view->lineno - view->offset);
3055         wnoutrefresh(view->win);
3056         report("");
3060 /*
3061  * Searching
3062  */
3064 static void search_view(struct view *view, enum request request);
3066 static bool
3067 grep_text(struct view *view, const char *text[])
3069         regmatch_t pmatch;
3070         size_t i;
3072         for (i = 0; text[i]; i++)
3073                 if (*text[i] &&
3074                     regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
3075                         return TRUE;
3076         return FALSE;
3079 static void
3080 select_view_line(struct view *view, unsigned long lineno)
3082         unsigned long old_lineno = view->lineno;
3083         unsigned long old_offset = view->offset;
3085         if (goto_view_line(view, view->offset, lineno)) {
3086                 if (view_is_displayed(view)) {
3087                         if (old_offset != view->offset) {
3088                                 redraw_view(view);
3089                         } else {
3090                                 draw_view_line(view, old_lineno - view->offset);
3091                                 draw_view_line(view, view->lineno - view->offset);
3092                                 wnoutrefresh(view->win);
3093                         }
3094                 } else {
3095                         view->ops->select(view, &view->line[view->lineno]);
3096                 }
3097         }
3100 static void
3101 find_next(struct view *view, enum request request)
3103         unsigned long lineno = view->lineno;
3104         int direction;
3106         if (!*view->grep) {
3107                 if (!*opt_search)
3108                         report("No previous search");
3109                 else
3110                         search_view(view, request);
3111                 return;
3112         }
3114         switch (request) {
3115         case REQ_SEARCH:
3116         case REQ_FIND_NEXT:
3117                 direction = 1;
3118                 break;
3120         case REQ_SEARCH_BACK:
3121         case REQ_FIND_PREV:
3122                 direction = -1;
3123                 break;
3125         default:
3126                 return;
3127         }
3129         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
3130                 lineno += direction;
3132         /* Note, lineno is unsigned long so will wrap around in which case it
3133          * will become bigger than view->lines. */
3134         for (; lineno < view->lines; lineno += direction) {
3135                 if (view->ops->grep(view, &view->line[lineno])) {
3136                         select_view_line(view, lineno);
3137                         report("Line %ld matches '%s'", lineno + 1, view->grep);
3138                         return;
3139                 }
3140         }
3142         report("No match found for '%s'", view->grep);
3145 static void
3146 search_view(struct view *view, enum request request)
3148         int regex_err;
3150         if (view->regex) {
3151                 regfree(view->regex);
3152                 *view->grep = 0;
3153         } else {
3154                 view->regex = calloc(1, sizeof(*view->regex));
3155                 if (!view->regex)
3156                         return;
3157         }
3159         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
3160         if (regex_err != 0) {
3161                 char buf[SIZEOF_STR] = "unknown error";
3163                 regerror(regex_err, view->regex, buf, sizeof(buf));
3164                 report("Search failed: %s", buf);
3165                 return;
3166         }
3168         string_copy(view->grep, opt_search);
3170         find_next(view, request);
3173 /*
3174  * Incremental updating
3175  */
3177 static void
3178 reset_view(struct view *view)
3180         int i;
3182         for (i = 0; i < view->lines; i++)
3183                 free(view->line[i].data);
3184         free(view->line);
3186         view->p_offset = view->offset;
3187         view->p_yoffset = view->yoffset;
3188         view->p_lineno = view->lineno;
3190         view->line = NULL;
3191         view->offset = 0;
3192         view->yoffset = 0;
3193         view->lines  = 0;
3194         view->lineno = 0;
3195         view->vid[0] = 0;
3196         view->update_secs = 0;
3199 static const char *
3200 format_arg(const char *name)
3202         static struct {
3203                 const char *name;
3204                 size_t namelen;
3205                 const char *value;
3206                 const char *value_if_empty;
3207         } vars[] = {
3208 #define FORMAT_VAR(name, value, value_if_empty) \
3209         { name, STRING_SIZE(name), value, value_if_empty }
3210                 FORMAT_VAR("%(directory)",      opt_path,       ""),
3211                 FORMAT_VAR("%(file)",           opt_file,       ""),
3212                 FORMAT_VAR("%(ref)",            opt_ref,        "HEAD"),
3213                 FORMAT_VAR("%(head)",           ref_head,       ""),
3214                 FORMAT_VAR("%(commit)",         ref_commit,     ""),
3215                 FORMAT_VAR("%(blob)",           ref_blob,       ""),
3216                 FORMAT_VAR("%(branch)",         ref_branch,     ""),
3217         };
3218         int i;
3220         for (i = 0; i < ARRAY_SIZE(vars); i++)
3221                 if (!strncmp(name, vars[i].name, vars[i].namelen))
3222                         return *vars[i].value ? vars[i].value : vars[i].value_if_empty;
3224         report("Unknown replacement: `%s`", name);
3225         return NULL;
3228 static bool
3229 format_argv(const char ***dst_argv, const char *src_argv[], bool replace, bool first)
3231         char buf[SIZEOF_STR];
3232         int argc;
3234         argv_free(*dst_argv);
3236         for (argc = 0; src_argv[argc]; argc++) {
3237                 const char *arg = src_argv[argc];
3238                 size_t bufpos = 0;
3240                 if (!strcmp(arg, "%(fileargs)")) {
3241                         if (!argv_append_array(dst_argv, opt_file_argv))
3242                                 break;
3243                         continue;
3245                 } else if (!strcmp(arg, "%(diffargs)")) {
3246                         if (!argv_append_array(dst_argv, opt_diff_argv))
3247                                 break;
3248                         continue;
3250                 } else if (!strcmp(arg, "%(revargs)") ||
3251                            (first && !strcmp(arg, "%(commit)"))) {
3252                         if (!argv_append_array(dst_argv, opt_rev_argv))
3253                                 break;
3254                         continue;
3255                 }
3257                 while (arg) {
3258                         char *next = strstr(arg, "%(");
3259                         int len = next - arg;
3260                         const char *value;
3262                         if (!next || !replace) {
3263                                 len = strlen(arg);
3264                                 value = "";
3266                         } else {
3267                                 value = format_arg(next);
3269                                 if (!value) {
3270                                         return FALSE;
3271                                 }
3272                         }
3274                         if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
3275                                 return FALSE;
3277                         arg = next && replace ? strchr(next, ')') + 1 : NULL;
3278                 }
3280                 if (!argv_append(dst_argv, buf))
3281                         break;
3282         }
3284         return src_argv[argc] == NULL;
3287 static bool
3288 restore_view_position(struct view *view)
3290         if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
3291                 return FALSE;
3293         /* Changing the view position cancels the restoring. */
3294         /* FIXME: Changing back to the first line is not detected. */
3295         if (view->offset != 0 || view->lineno != 0) {
3296                 view->p_restore = FALSE;
3297                 return FALSE;
3298         }
3300         if (goto_view_line(view, view->p_offset, view->p_lineno) &&
3301             view_is_displayed(view))
3302                 werase(view->win);
3304         view->yoffset = view->p_yoffset;
3305         view->p_restore = FALSE;
3307         return TRUE;
3310 static void
3311 end_update(struct view *view, bool force)
3313         if (!view->pipe)
3314                 return;
3315         while (!view->ops->read(view, NULL))
3316                 if (!force)
3317                         return;
3318         if (force)
3319                 io_kill(view->pipe);
3320         io_done(view->pipe);
3321         view->pipe = NULL;
3324 static void
3325 setup_update(struct view *view, const char *vid)
3327         reset_view(view);
3328         string_copy_rev(view->vid, vid);
3329         view->pipe = &view->io;
3330         view->start_time = time(NULL);
3333 static bool
3334 prepare_io(struct view *view, const char *dir, const char *argv[], bool replace)
3336         view->dir = dir;
3337         return format_argv(&view->argv, argv, replace, !view->prev);
3340 static bool
3341 prepare_update(struct view *view, const char *argv[], const char *dir)
3343         if (view->pipe)
3344                 end_update(view, TRUE);
3345         return prepare_io(view, dir, argv, FALSE);
3348 static bool
3349 start_update(struct view *view, const char **argv, const char *dir)
3351         if (view->pipe)
3352                 io_done(view->pipe);
3353         return prepare_io(view, dir, argv, FALSE) &&
3354                io_run(&view->io, IO_RD, dir, view->argv);
3357 static bool
3358 prepare_update_file(struct view *view, const char *name)
3360         if (view->pipe)
3361                 end_update(view, TRUE);
3362         argv_free(view->argv);
3363         return io_open(&view->io, "%s/%s", opt_cdup[0] ? opt_cdup : ".", name);
3366 static bool
3367 begin_update(struct view *view, bool refresh)
3369         if (view->pipe)
3370                 end_update(view, TRUE);
3372         if (!refresh) {
3373                 if (view->ops->prepare) {
3374                         if (!view->ops->prepare(view))
3375                                 return FALSE;
3376                 } else if (!prepare_io(view, NULL, view->ops->argv, TRUE)) {
3377                         return FALSE;
3378                 }
3380                 /* Put the current ref_* value to the view title ref
3381                  * member. This is needed by the blob view. Most other
3382                  * views sets it automatically after loading because the
3383                  * first line is a commit line. */
3384                 string_copy_rev(view->ref, view->id);
3385         }
3387         if (view->argv && view->argv[0] &&
3388             !io_run(&view->io, IO_RD, view->dir, view->argv))
3389                 return FALSE;
3391         setup_update(view, view->id);
3393         return TRUE;
3396 static bool
3397 update_view(struct view *view)
3399         char out_buffer[BUFSIZ * 2];
3400         char *line;
3401         /* Clear the view and redraw everything since the tree sorting
3402          * might have rearranged things. */
3403         bool redraw = view->lines == 0;
3404         bool can_read = TRUE;
3406         if (!view->pipe)
3407                 return TRUE;
3409         if (!io_can_read(view->pipe)) {
3410                 if (view->lines == 0 && view_is_displayed(view)) {
3411                         time_t secs = time(NULL) - view->start_time;
3413                         if (secs > 1 && secs > view->update_secs) {
3414                                 if (view->update_secs == 0)
3415                                         redraw_view(view);
3416                                 update_view_title(view);
3417                                 view->update_secs = secs;
3418                         }
3419                 }
3420                 return TRUE;
3421         }
3423         for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
3424                 if (opt_iconv_in != ICONV_NONE) {
3425                         ICONV_CONST char *inbuf = line;
3426                         size_t inlen = strlen(line) + 1;
3428                         char *outbuf = out_buffer;
3429                         size_t outlen = sizeof(out_buffer);
3431                         size_t ret;
3433                         ret = iconv(opt_iconv_in, &inbuf, &inlen, &outbuf, &outlen);
3434                         if (ret != (size_t) -1)
3435                                 line = out_buffer;
3436                 }
3438                 if (!view->ops->read(view, line)) {
3439                         report("Allocation failure");
3440                         end_update(view, TRUE);
3441                         return FALSE;
3442                 }
3443         }
3445         {
3446                 unsigned long lines = view->lines;
3447                 int digits;
3449                 for (digits = 0; lines; digits++)
3450                         lines /= 10;
3452                 /* Keep the displayed view in sync with line number scaling. */
3453                 if (digits != view->digits) {
3454                         view->digits = digits;
3455                         if (opt_line_number || view->type == VIEW_BLAME)
3456                                 redraw = TRUE;
3457                 }
3458         }
3460         if (io_error(view->pipe)) {
3461                 report("Failed to read: %s", io_strerror(view->pipe));
3462                 end_update(view, TRUE);
3464         } else if (io_eof(view->pipe)) {
3465                 if (view_is_displayed(view))
3466                         report("");
3467                 end_update(view, FALSE);
3468         }
3470         if (restore_view_position(view))
3471                 redraw = TRUE;
3473         if (!view_is_displayed(view))
3474                 return TRUE;
3476         if (redraw)
3477                 redraw_view_from(view, 0);
3478         else
3479                 redraw_view_dirty(view);
3481         /* Update the title _after_ the redraw so that if the redraw picks up a
3482          * commit reference in view->ref it'll be available here. */
3483         update_view_title(view);
3484         return TRUE;
3487 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
3489 static struct line *
3490 add_line_data(struct view *view, void *data, enum line_type type)
3492         struct line *line;
3494         if (!realloc_lines(&view->line, view->lines, 1))
3495                 return NULL;
3497         line = &view->line[view->lines++];
3498         memset(line, 0, sizeof(*line));
3499         line->type = type;
3500         line->data = data;
3501         line->dirty = 1;
3503         return line;
3506 static struct line *
3507 add_line_text(struct view *view, const char *text, enum line_type type)
3509         char *data = text ? strdup(text) : NULL;
3511         return data ? add_line_data(view, data, type) : NULL;
3514 static struct line *
3515 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
3517         char buf[SIZEOF_STR];
3518         va_list args;
3520         va_start(args, fmt);
3521         if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
3522                 buf[0] = 0;
3523         va_end(args);
3525         return buf[0] ? add_line_text(view, buf, type) : NULL;
3528 /*
3529  * View opening
3530  */
3532 enum open_flags {
3533         OPEN_DEFAULT = 0,       /* Use default view switching. */
3534         OPEN_SPLIT = 1,         /* Split current view. */
3535         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
3536         OPEN_REFRESH = 16,      /* Refresh view using previous command. */
3537         OPEN_PREPARED = 32,     /* Open already prepared command. */
3538 };
3540 static void
3541 open_view(struct view *prev, enum request request, enum open_flags flags)
3543         bool split = !!(flags & OPEN_SPLIT);
3544         bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
3545         bool nomaximize = !!(flags & OPEN_REFRESH);
3546         struct view *view = VIEW(request);
3547         int nviews = displayed_views();
3548         struct view *base_view = display[0];
3550         if (view == prev && nviews == 1 && !reload) {
3551                 report("Already in %s view", view->name);
3552                 return;
3553         }
3555         if (view->git_dir && !opt_git_dir[0]) {
3556                 report("The %s view is disabled in pager view", view->name);
3557                 return;
3558         }
3560         if (split) {
3561                 display[1] = view;
3562                 current_view = 1;
3563                 view->parent = prev;
3564         } else if (!nomaximize) {
3565                 /* Maximize the current view. */
3566                 memset(display, 0, sizeof(display));
3567                 current_view = 0;
3568                 display[current_view] = view;
3569         }
3571         /* No prev signals that this is the first loaded view. */
3572         if (prev && view != prev) {
3573                 view->prev = prev;
3574         }
3576         /* Resize the view when switching between split- and full-screen,
3577          * or when switching between two different full-screen views. */
3578         if (nviews != displayed_views() ||
3579             (nviews == 1 && base_view != display[0]))
3580                 resize_display();
3582         if (view->ops->open) {
3583                 if (view->pipe)
3584                         end_update(view, TRUE);
3585                 if (!view->ops->open(view)) {
3586                         report("Failed to load %s view", view->name);
3587                         return;
3588                 }
3589                 restore_view_position(view);
3591         } else if ((reload || strcmp(view->vid, view->id)) &&
3592                    !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3593                 report("Failed to load %s view", view->name);
3594                 return;
3595         }
3597         if (split && prev->lineno - prev->offset >= prev->height) {
3598                 /* Take the title line into account. */
3599                 int lines = prev->lineno - prev->offset - prev->height + 1;
3601                 /* Scroll the view that was split if the current line is
3602                  * outside the new limited view. */
3603                 do_scroll_view(prev, lines);
3604         }
3606         if (prev && view != prev && split && view_is_displayed(prev)) {
3607                 /* "Blur" the previous view. */
3608                 update_view_title(prev);
3609         }
3611         if (view->pipe && view->lines == 0) {
3612                 /* Clear the old view and let the incremental updating refill
3613                  * the screen. */
3614                 werase(view->win);
3615                 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3616                 report("");
3617         } else if (view_is_displayed(view)) {
3618                 redraw_view(view);
3619                 report("");
3620         }
3623 static void
3624 open_external_viewer(const char *argv[], const char *dir)
3626         def_prog_mode();           /* save current tty modes */
3627         endwin();                  /* restore original tty modes */
3628         io_run_fg(argv, dir);
3629         fprintf(stderr, "Press Enter to continue");
3630         getc(opt_tty);
3631         reset_prog_mode();
3632         redraw_display(TRUE);
3635 static void
3636 open_mergetool(const char *file)
3638         const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3640         open_external_viewer(mergetool_argv, opt_cdup);
3643 static void
3644 open_editor(const char *file)
3646         const char *editor_argv[] = { "vi", file, NULL };
3647         const char *editor;
3649         editor = getenv("GIT_EDITOR");
3650         if (!editor && *opt_editor)
3651                 editor = opt_editor;
3652         if (!editor)
3653                 editor = getenv("VISUAL");
3654         if (!editor)
3655                 editor = getenv("EDITOR");
3656         if (!editor)
3657                 editor = "vi";
3659         editor_argv[0] = editor;
3660         open_external_viewer(editor_argv, opt_cdup);
3663 static void
3664 open_run_request(enum request request)
3666         struct run_request *req = get_run_request(request);
3667         const char **argv = NULL;
3669         if (!req) {
3670                 report("Unknown run request");
3671                 return;
3672         }
3674         if (format_argv(&argv, req->argv, TRUE, FALSE))
3675                 open_external_viewer(argv, NULL);
3676         if (argv)
3677                 argv_free(argv);
3678         free(argv);
3681 /*
3682  * User request switch noodle
3683  */
3685 static int
3686 view_driver(struct view *view, enum request request)
3688         int i;
3690         if (request == REQ_NONE)
3691                 return TRUE;
3693         if (request > REQ_NONE) {
3694                 open_run_request(request);
3695                 view_request(view, REQ_REFRESH);
3696                 return TRUE;
3697         }
3699         request = view_request(view, request);
3700         if (request == REQ_NONE)
3701                 return TRUE;
3703         switch (request) {
3704         case REQ_MOVE_UP:
3705         case REQ_MOVE_DOWN:
3706         case REQ_MOVE_PAGE_UP:
3707         case REQ_MOVE_PAGE_DOWN:
3708         case REQ_MOVE_FIRST_LINE:
3709         case REQ_MOVE_LAST_LINE:
3710                 move_view(view, request);
3711                 break;
3713         case REQ_SCROLL_FIRST_COL:
3714         case REQ_SCROLL_LEFT:
3715         case REQ_SCROLL_RIGHT:
3716         case REQ_SCROLL_LINE_DOWN:
3717         case REQ_SCROLL_LINE_UP:
3718         case REQ_SCROLL_PAGE_DOWN:
3719         case REQ_SCROLL_PAGE_UP:
3720                 scroll_view(view, request);
3721                 break;
3723         case REQ_VIEW_BLAME:
3724                 if (!opt_file[0]) {
3725                         report("No file chosen, press %s to open tree view",
3726                                get_key(view->keymap, REQ_VIEW_TREE));
3727                         break;
3728                 }
3729                 open_view(view, request, OPEN_DEFAULT);
3730                 break;
3732         case REQ_VIEW_BLOB:
3733                 if (!ref_blob[0]) {
3734                         report("No file chosen, press %s to open tree view",
3735                                get_key(view->keymap, REQ_VIEW_TREE));
3736                         break;
3737                 }
3738                 open_view(view, request, OPEN_DEFAULT);
3739                 break;
3741         case REQ_VIEW_PAGER:
3742                 if (view == NULL) {
3743                         if (!io_open(&VIEW(REQ_VIEW_PAGER)->io, ""))
3744                                 die("Failed to open stdin");
3745                         open_view(view, request, OPEN_PREPARED);
3746                         break;
3747                 }
3749                 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3750                         report("No pager content, press %s to run command from prompt",
3751                                get_key(view->keymap, REQ_PROMPT));
3752                         break;
3753                 }
3754                 open_view(view, request, OPEN_DEFAULT);
3755                 break;
3757         case REQ_VIEW_STAGE:
3758                 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3759                         report("No stage content, press %s to open the status view and choose file",
3760                                get_key(view->keymap, REQ_VIEW_STATUS));
3761                         break;
3762                 }
3763                 open_view(view, request, OPEN_DEFAULT);
3764                 break;
3766         case REQ_VIEW_STATUS:
3767                 if (opt_is_inside_work_tree == FALSE) {
3768                         report("The status view requires a working tree");
3769                         break;
3770                 }
3771                 open_view(view, request, OPEN_DEFAULT);
3772                 break;
3774         case REQ_VIEW_MAIN:
3775         case REQ_VIEW_DIFF:
3776         case REQ_VIEW_LOG:
3777         case REQ_VIEW_TREE:
3778         case REQ_VIEW_HELP:
3779         case REQ_VIEW_BRANCH:
3780                 open_view(view, request, OPEN_DEFAULT);
3781                 break;
3783         case REQ_NEXT:
3784         case REQ_PREVIOUS:
3785                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3787                 if (view->parent) {
3788                         int line;
3790                         view = view->parent;
3791                         line = view->lineno;
3792                         move_view(view, request);
3793                         if (view_is_displayed(view))
3794                                 update_view_title(view);
3795                         if (line != view->lineno)
3796                                 view_request(view, REQ_ENTER);
3797                 } else {
3798                         move_view(view, request);
3799                 }
3800                 break;
3802         case REQ_VIEW_NEXT:
3803         {
3804                 int nviews = displayed_views();
3805                 int next_view = (current_view + 1) % nviews;
3807                 if (next_view == current_view) {
3808                         report("Only one view is displayed");
3809                         break;
3810                 }
3812                 current_view = next_view;
3813                 /* Blur out the title of the previous view. */
3814                 update_view_title(view);
3815                 report("");
3816                 break;
3817         }
3818         case REQ_REFRESH:
3819                 report("Refreshing is not yet supported for the %s view", view->name);
3820                 break;
3822         case REQ_MAXIMIZE:
3823                 if (displayed_views() == 2)
3824                         maximize_view(view);
3825                 break;
3827         case REQ_OPTIONS:
3828                 open_option_menu();
3829                 break;
3831         case REQ_TOGGLE_LINENO:
3832                 toggle_view_option(&opt_line_number, "line numbers");
3833                 break;
3835         case REQ_TOGGLE_DATE:
3836                 toggle_date();
3837                 break;
3839         case REQ_TOGGLE_AUTHOR:
3840                 toggle_author();
3841                 break;
3843         case REQ_TOGGLE_REV_GRAPH:
3844                 toggle_view_option(&opt_rev_graph, "revision graph display");
3845                 break;
3847         case REQ_TOGGLE_REFS:
3848                 toggle_view_option(&opt_show_refs, "reference display");
3849                 break;
3851         case REQ_TOGGLE_SORT_FIELD:
3852         case REQ_TOGGLE_SORT_ORDER:
3853                 report("Sorting is not yet supported for the %s view", view->name);
3854                 break;
3856         case REQ_SEARCH:
3857         case REQ_SEARCH_BACK:
3858                 search_view(view, request);
3859                 break;
3861         case REQ_FIND_NEXT:
3862         case REQ_FIND_PREV:
3863                 find_next(view, request);
3864                 break;
3866         case REQ_STOP_LOADING:
3867                 foreach_view(view, i) {
3868                         if (view->pipe)
3869                                 report("Stopped loading the %s view", view->name),
3870                         end_update(view, TRUE);
3871                 }
3872                 break;
3874         case REQ_SHOW_VERSION:
3875                 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3876                 return TRUE;
3878         case REQ_SCREEN_REDRAW:
3879                 redraw_display(TRUE);
3880                 break;
3882         case REQ_EDIT:
3883                 report("Nothing to edit");
3884                 break;
3886         case REQ_ENTER:
3887                 report("Nothing to enter");
3888                 break;
3890         case REQ_VIEW_CLOSE:
3891                 /* XXX: Mark closed views by letting view->prev point to the
3892                  * view itself. Parents to closed view should never be
3893                  * followed. */
3894                 if (view->prev && view->prev != view) {
3895                         maximize_view(view->prev);
3896                         view->prev = view;
3897                         break;
3898                 }
3899                 /* Fall-through */
3900         case REQ_QUIT:
3901                 return FALSE;
3903         default:
3904                 report("Unknown key, press %s for help",
3905                        get_key(view->keymap, REQ_VIEW_HELP));
3906                 return TRUE;
3907         }
3909         return TRUE;
3913 /*
3914  * View backend utilities
3915  */
3917 enum sort_field {
3918         ORDERBY_NAME,
3919         ORDERBY_DATE,
3920         ORDERBY_AUTHOR,
3921 };
3923 struct sort_state {
3924         const enum sort_field *fields;
3925         size_t size, current;
3926         bool reverse;
3927 };
3929 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3930 #define get_sort_field(state) ((state).fields[(state).current])
3931 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3933 static void
3934 sort_view(struct view *view, enum request request, struct sort_state *state,
3935           int (*compare)(const void *, const void *))
3937         switch (request) {
3938         case REQ_TOGGLE_SORT_FIELD:
3939                 state->current = (state->current + 1) % state->size;
3940                 break;
3942         case REQ_TOGGLE_SORT_ORDER:
3943                 state->reverse = !state->reverse;
3944                 break;
3945         default:
3946                 die("Not a sort request");
3947         }
3949         qsort(view->line, view->lines, sizeof(*view->line), compare);
3950         redraw_view(view);
3953 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3955 /* Small author cache to reduce memory consumption. It uses binary
3956  * search to lookup or find place to position new entries. No entries
3957  * are ever freed. */
3958 static const char *
3959 get_author(const char *name)
3961         static const char **authors;
3962         static size_t authors_size;
3963         int from = 0, to = authors_size - 1;
3965         while (from <= to) {
3966                 size_t pos = (to + from) / 2;
3967                 int cmp = strcmp(name, authors[pos]);
3969                 if (!cmp)
3970                         return authors[pos];
3972                 if (cmp < 0)
3973                         to = pos - 1;
3974                 else
3975                         from = pos + 1;
3976         }
3978         if (!realloc_authors(&authors, authors_size, 1))
3979                 return NULL;
3980         name = strdup(name);
3981         if (!name)
3982                 return NULL;
3984         memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3985         authors[from] = name;
3986         authors_size++;
3988         return name;
3991 static void
3992 parse_timesec(struct time *time, const char *sec)
3994         time->sec = (time_t) atol(sec);
3997 static void
3998 parse_timezone(struct time *time, const char *zone)
4000         long tz;
4002         tz  = ('0' - zone[1]) * 60 * 60 * 10;
4003         tz += ('0' - zone[2]) * 60 * 60;
4004         tz += ('0' - zone[3]) * 60 * 10;
4005         tz += ('0' - zone[4]) * 60;
4007         if (zone[0] == '-')
4008                 tz = -tz;
4010         time->tz = tz;
4011         time->sec -= tz;
4014 /* Parse author lines where the name may be empty:
4015  *      author  <email@address.tld> 1138474660 +0100
4016  */
4017 static void
4018 parse_author_line(char *ident, const char **author, struct time *time)
4020         char *nameend = strchr(ident, '<');
4021         char *emailend = strchr(ident, '>');
4023         if (nameend && emailend)
4024                 *nameend = *emailend = 0;
4025         ident = chomp_string(ident);
4026         if (!*ident) {
4027                 if (nameend)
4028                         ident = chomp_string(nameend + 1);
4029                 if (!*ident)
4030                         ident = "Unknown";
4031         }
4033         *author = get_author(ident);
4035         /* Parse epoch and timezone */
4036         if (emailend && emailend[1] == ' ') {
4037                 char *secs = emailend + 2;
4038                 char *zone = strchr(secs, ' ');
4040                 parse_timesec(time, secs);
4042                 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
4043                         parse_timezone(time, zone + 1);
4044         }
4047 /*
4048  * Pager backend
4049  */
4051 static bool
4052 pager_draw(struct view *view, struct line *line, unsigned int lineno)
4054         if (opt_line_number && draw_lineno(view, lineno))
4055                 return TRUE;
4057         draw_text(view, line->type, line->data, TRUE);
4058         return TRUE;
4061 static bool
4062 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
4064         const char *describe_argv[] = { "git", "describe", commit_id, NULL };
4065         char ref[SIZEOF_STR];
4067         if (!io_run_buf(describe_argv, ref, sizeof(ref)) || !*ref)
4068                 return TRUE;
4070         /* This is the only fatal call, since it can "corrupt" the buffer. */
4071         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
4072                 return FALSE;
4074         return TRUE;
4077 static void
4078 add_pager_refs(struct view *view, struct line *line)
4080         char buf[SIZEOF_STR];
4081         char *commit_id = (char *)line->data + STRING_SIZE("commit ");
4082         struct ref_list *list;
4083         size_t bufpos = 0, i;
4084         const char *sep = "Refs: ";
4085         bool is_tag = FALSE;
4087         assert(line->type == LINE_COMMIT);
4089         list = get_ref_list(commit_id);
4090         if (!list) {
4091                 if (view->type == VIEW_DIFF)
4092                         goto try_add_describe_ref;
4093                 return;
4094         }
4096         for (i = 0; i < list->size; i++) {
4097                 struct ref *ref = list->refs[i];
4098                 const char *fmt = ref->tag    ? "%s[%s]" :
4099                                   ref->remote ? "%s<%s>" : "%s%s";
4101                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
4102                         return;
4103                 sep = ", ";
4104                 if (ref->tag)
4105                         is_tag = TRUE;
4106         }
4108         if (!is_tag && view->type == VIEW_DIFF) {
4109 try_add_describe_ref:
4110                 /* Add <tag>-g<commit_id> "fake" reference. */
4111                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
4112                         return;
4113         }
4115         if (bufpos == 0)
4116                 return;
4118         add_line_text(view, buf, LINE_PP_REFS);
4121 static bool
4122 pager_read(struct view *view, char *data)
4124         struct line *line;
4126         if (!data)
4127                 return TRUE;
4129         line = add_line_text(view, data, get_line_type(data));
4130         if (!line)
4131                 return FALSE;
4133         if (line->type == LINE_COMMIT &&
4134             (view->type == VIEW_DIFF ||
4135              view->type == VIEW_LOG))
4136                 add_pager_refs(view, line);
4138         return TRUE;
4141 static enum request
4142 pager_request(struct view *view, enum request request, struct line *line)
4144         int split = 0;
4146         if (request != REQ_ENTER)
4147                 return request;
4149         if (line->type == LINE_COMMIT &&
4150            (view->type == VIEW_LOG ||
4151             view->type == VIEW_PAGER)) {
4152                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
4153                 split = 1;
4154         }
4156         /* Always scroll the view even if it was split. That way
4157          * you can use Enter to scroll through the log view and
4158          * split open each commit diff. */
4159         scroll_view(view, REQ_SCROLL_LINE_DOWN);
4161         /* FIXME: A minor workaround. Scrolling the view will call report("")
4162          * but if we are scrolling a non-current view this won't properly
4163          * update the view title. */
4164         if (split)
4165                 update_view_title(view);
4167         return REQ_NONE;
4170 static bool
4171 pager_grep(struct view *view, struct line *line)
4173         const char *text[] = { line->data, NULL };
4175         return grep_text(view, text);
4178 static void
4179 pager_select(struct view *view, struct line *line)
4181         if (line->type == LINE_COMMIT) {
4182                 char *text = (char *)line->data + STRING_SIZE("commit ");
4184                 if (view->type != VIEW_PAGER)
4185                         string_copy_rev(view->ref, text);
4186                 string_copy_rev(ref_commit, text);
4187         }
4190 static struct view_ops pager_ops = {
4191         "line",
4192         NULL,
4193         NULL,
4194         pager_read,
4195         pager_draw,
4196         pager_request,
4197         pager_grep,
4198         pager_select,
4199 };
4201 static const char *log_argv[SIZEOF_ARG] = {
4202         "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
4203 };
4205 static enum request
4206 log_request(struct view *view, enum request request, struct line *line)
4208         switch (request) {
4209         case REQ_REFRESH:
4210                 load_refs();
4211                 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
4212                 return REQ_NONE;
4213         default:
4214                 return pager_request(view, request, line);
4215         }
4218 static struct view_ops log_ops = {
4219         "line",
4220         log_argv,
4221         NULL,
4222         pager_read,
4223         pager_draw,
4224         log_request,
4225         pager_grep,
4226         pager_select,
4227 };
4229 static const char *diff_argv[SIZEOF_ARG] = {
4230         "git", "show", "--pretty=fuller", "--no-color", "--root",
4231                 "--patch-with-stat", "--find-copies-harder", "-C",
4232                 "%(diffargs)", "%(commit)", "--", "%(fileargs)", NULL
4233 };
4235 static bool
4236 diff_read(struct view *view, char *data)
4238         if (!data) {
4239                 /* Fall back to retry if no diff will be shown. */
4240                 if (view->lines == 0 && opt_file_argv) {
4241                         int pos = argv_size(view->argv)
4242                                 - argv_size(opt_file_argv) - 1;
4244                         if (pos > 0 && !strcmp(view->argv[pos], "--")) {
4245                                 for (; view->argv[pos]; pos++) {
4246                                         free((void *) view->argv[pos]);
4247                                         view->argv[pos] = NULL;
4248                                 }
4250                                 if (view->pipe)
4251                                         io_done(view->pipe);
4252                                 if (io_run(&view->io, IO_RD, view->dir, view->argv))
4253                                         return FALSE;
4254                         }
4255                 }
4256                 return TRUE;
4257         }
4259         return pager_read(view, data);
4262 static struct view_ops diff_ops = {
4263         "line",
4264         diff_argv,
4265         NULL,
4266         diff_read,
4267         pager_draw,
4268         pager_request,
4269         pager_grep,
4270         pager_select,
4271 };
4273 /*
4274  * Help backend
4275  */
4277 static bool help_keymap_hidden[ARRAY_SIZE(keymap_table)];
4279 static bool
4280 help_open_keymap_title(struct view *view, enum keymap keymap)
4282         struct line *line;
4284         line = add_line_format(view, LINE_HELP_KEYMAP, "[%c] %s bindings",
4285                                help_keymap_hidden[keymap] ? '+' : '-',
4286                                enum_name(keymap_table[keymap]));
4287         if (line)
4288                 line->other = keymap;
4290         return help_keymap_hidden[keymap];
4293 static void
4294 help_open_keymap(struct view *view, enum keymap keymap)
4296         const char *group = NULL;
4297         char buf[SIZEOF_STR];
4298         size_t bufpos;
4299         bool add_title = TRUE;
4300         int i;
4302         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
4303                 const char *key = NULL;
4305                 if (req_info[i].request == REQ_NONE)
4306                         continue;
4308                 if (!req_info[i].request) {
4309                         group = req_info[i].help;
4310                         continue;
4311                 }
4313                 key = get_keys(keymap, req_info[i].request, TRUE);
4314                 if (!key || !*key)
4315                         continue;
4317                 if (add_title && help_open_keymap_title(view, keymap))
4318                         return;
4319                 add_title = FALSE;
4321                 if (group) {
4322                         add_line_text(view, group, LINE_HELP_GROUP);
4323                         group = NULL;
4324                 }
4326                 add_line_format(view, LINE_DEFAULT, "    %-25s %-20s %s", key,
4327                                 enum_name(req_info[i]), req_info[i].help);
4328         }
4330         group = "External commands:";
4332         for (i = 0; i < run_requests; i++) {
4333                 struct run_request *req = get_run_request(REQ_NONE + i + 1);
4334                 const char *key;
4335                 int argc;
4337                 if (!req || req->keymap != keymap)
4338                         continue;
4340                 key = get_key_name(req->key);
4341                 if (!*key)
4342                         key = "(no key defined)";
4344                 if (add_title && help_open_keymap_title(view, keymap))
4345                         return;
4346                 if (group) {
4347                         add_line_text(view, group, LINE_HELP_GROUP);
4348                         group = NULL;
4349                 }
4351                 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
4352                         if (!string_format_from(buf, &bufpos, "%s%s",
4353                                                 argc ? " " : "", req->argv[argc]))
4354                                 return;
4356                 add_line_format(view, LINE_DEFAULT, "    %-25s `%s`", key, buf);
4357         }
4360 static bool
4361 help_open(struct view *view)
4363         enum keymap keymap;
4365         reset_view(view);
4366         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
4367         add_line_text(view, "", LINE_DEFAULT);
4369         for (keymap = 0; keymap < ARRAY_SIZE(keymap_table); keymap++)
4370                 help_open_keymap(view, keymap);
4372         return TRUE;
4375 static enum request
4376 help_request(struct view *view, enum request request, struct line *line)
4378         switch (request) {
4379         case REQ_ENTER:
4380                 if (line->type == LINE_HELP_KEYMAP) {
4381                         help_keymap_hidden[line->other] =
4382                                 !help_keymap_hidden[line->other];
4383                         view->p_restore = TRUE;
4384                         open_view(view, REQ_VIEW_HELP, OPEN_REFRESH);
4385                 }
4387                 return REQ_NONE;
4388         default:
4389                 return pager_request(view, request, line);
4390         }
4393 static struct view_ops help_ops = {
4394         "line",
4395         NULL,
4396         help_open,
4397         NULL,
4398         pager_draw,
4399         help_request,
4400         pager_grep,
4401         pager_select,
4402 };
4405 /*
4406  * Tree backend
4407  */
4409 struct tree_stack_entry {
4410         struct tree_stack_entry *prev;  /* Entry below this in the stack */
4411         unsigned long lineno;           /* Line number to restore */
4412         char *name;                     /* Position of name in opt_path */
4413 };
4415 /* The top of the path stack. */
4416 static struct tree_stack_entry *tree_stack = NULL;
4417 unsigned long tree_lineno = 0;
4419 static void
4420 pop_tree_stack_entry(void)
4422         struct tree_stack_entry *entry = tree_stack;
4424         tree_lineno = entry->lineno;
4425         entry->name[0] = 0;
4426         tree_stack = entry->prev;
4427         free(entry);
4430 static void
4431 push_tree_stack_entry(const char *name, unsigned long lineno)
4433         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
4434         size_t pathlen = strlen(opt_path);
4436         if (!entry)
4437                 return;
4439         entry->prev = tree_stack;
4440         entry->name = opt_path + pathlen;
4441         tree_stack = entry;
4443         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
4444                 pop_tree_stack_entry();
4445                 return;
4446         }
4448         /* Move the current line to the first tree entry. */
4449         tree_lineno = 1;
4450         entry->lineno = lineno;
4453 /* Parse output from git-ls-tree(1):
4454  *
4455  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4456  */
4458 #define SIZEOF_TREE_ATTR \
4459         STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4461 #define SIZEOF_TREE_MODE \
4462         STRING_SIZE("100644 ")
4464 #define TREE_ID_OFFSET \
4465         STRING_SIZE("100644 blob ")
4467 struct tree_entry {
4468         char id[SIZEOF_REV];
4469         mode_t mode;
4470         struct time time;               /* Date from the author ident. */
4471         const char *author;             /* Author of the commit. */
4472         char name[1];
4473 };
4475 static const char *
4476 tree_path(const struct line *line)
4478         return ((struct tree_entry *) line->data)->name;
4481 static int
4482 tree_compare_entry(const struct line *line1, const struct line *line2)
4484         if (line1->type != line2->type)
4485                 return line1->type == LINE_TREE_DIR ? -1 : 1;
4486         return strcmp(tree_path(line1), tree_path(line2));
4489 static const enum sort_field tree_sort_fields[] = {
4490         ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4491 };
4492 static struct sort_state tree_sort_state = SORT_STATE(tree_sort_fields);
4494 static int
4495 tree_compare(const void *l1, const void *l2)
4497         const struct line *line1 = (const struct line *) l1;
4498         const struct line *line2 = (const struct line *) l2;
4499         const struct tree_entry *entry1 = ((const struct line *) l1)->data;
4500         const struct tree_entry *entry2 = ((const struct line *) l2)->data;
4502         if (line1->type == LINE_TREE_HEAD)
4503                 return -1;
4504         if (line2->type == LINE_TREE_HEAD)
4505                 return 1;
4507         switch (get_sort_field(tree_sort_state)) {
4508         case ORDERBY_DATE:
4509                 return sort_order(tree_sort_state, timecmp(&entry1->time, &entry2->time));
4511         case ORDERBY_AUTHOR:
4512                 return sort_order(tree_sort_state, strcmp(entry1->author, entry2->author));
4514         case ORDERBY_NAME:
4515         default:
4516                 return sort_order(tree_sort_state, tree_compare_entry(line1, line2));
4517         }
4521 static struct line *
4522 tree_entry(struct view *view, enum line_type type, const char *path,
4523            const char *mode, const char *id)
4525         struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
4526         struct line *line = entry ? add_line_data(view, entry, type) : NULL;
4528         if (!entry || !line) {
4529                 free(entry);
4530                 return NULL;
4531         }
4533         strncpy(entry->name, path, strlen(path));
4534         if (mode)
4535                 entry->mode = strtoul(mode, NULL, 8);
4536         if (id)
4537                 string_copy_rev(entry->id, id);
4539         return line;
4542 static bool
4543 tree_read_date(struct view *view, char *text, bool *read_date)
4545         static const char *author_name;
4546         static struct time author_time;
4548         if (!text && *read_date) {
4549                 *read_date = FALSE;
4550                 return TRUE;
4552         } else if (!text) {
4553                 char *path = *opt_path ? opt_path : ".";
4554                 /* Find next entry to process */
4555                 const char *log_file[] = {
4556                         "git", "log", "--no-color", "--pretty=raw",
4557                                 "--cc", "--raw", view->id, "--", path, NULL
4558                 };
4560                 if (!view->lines) {
4561                         tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
4562                         report("Tree is empty");
4563                         return TRUE;
4564                 }
4566                 if (!start_update(view, log_file, opt_cdup)) {
4567                         report("Failed to load tree data");
4568                         return TRUE;
4569                 }
4571                 *read_date = TRUE;
4572                 return FALSE;
4574         } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
4575                 parse_author_line(text + STRING_SIZE("author "),
4576                                   &author_name, &author_time);
4578         } else if (*text == ':') {
4579                 char *pos;
4580                 size_t annotated = 1;
4581                 size_t i;
4583                 pos = strchr(text, '\t');
4584                 if (!pos)
4585                         return TRUE;
4586                 text = pos + 1;
4587                 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
4588                         text += strlen(opt_path);
4589                 pos = strchr(text, '/');
4590                 if (pos)
4591                         *pos = 0;
4593                 for (i = 1; i < view->lines; i++) {
4594                         struct line *line = &view->line[i];
4595                         struct tree_entry *entry = line->data;
4597                         annotated += !!entry->author;
4598                         if (entry->author || strcmp(entry->name, text))
4599                                 continue;
4601                         entry->author = author_name;
4602                         entry->time = author_time;
4603                         line->dirty = 1;
4604                         break;
4605                 }
4607                 if (annotated == view->lines)
4608                         io_kill(view->pipe);
4609         }
4610         return TRUE;
4613 static bool
4614 tree_read(struct view *view, char *text)
4616         static bool read_date = FALSE;
4617         struct tree_entry *data;
4618         struct line *entry, *line;
4619         enum line_type type;
4620         size_t textlen = text ? strlen(text) : 0;
4621         char *path = text + SIZEOF_TREE_ATTR;
4623         if (read_date || !text)
4624                 return tree_read_date(view, text, &read_date);
4626         if (textlen <= SIZEOF_TREE_ATTR)
4627                 return FALSE;
4628         if (view->lines == 0 &&
4629             !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
4630                 return FALSE;
4632         /* Strip the path part ... */
4633         if (*opt_path) {
4634                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
4635                 size_t striplen = strlen(opt_path);
4637                 if (pathlen > striplen)
4638                         memmove(path, path + striplen,
4639                                 pathlen - striplen + 1);
4641                 /* Insert "link" to parent directory. */
4642                 if (view->lines == 1 &&
4643                     !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
4644                         return FALSE;
4645         }
4647         type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
4648         entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
4649         if (!entry)
4650                 return FALSE;
4651         data = entry->data;
4653         /* Skip "Directory ..." and ".." line. */
4654         for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
4655                 if (tree_compare_entry(line, entry) <= 0)
4656                         continue;
4658                 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4660                 line->data = data;
4661                 line->type = type;
4662                 for (; line <= entry; line++)
4663                         line->dirty = line->cleareol = 1;
4664                 return TRUE;
4665         }
4667         if (tree_lineno > view->lineno) {
4668                 view->lineno = tree_lineno;
4669                 tree_lineno = 0;
4670         }
4672         return TRUE;
4675 static bool
4676 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4678         struct tree_entry *entry = line->data;
4680         if (line->type == LINE_TREE_HEAD) {
4681                 if (draw_text(view, line->type, "Directory path /", TRUE))
4682                         return TRUE;
4683         } else {
4684                 if (draw_mode(view, entry->mode))
4685                         return TRUE;
4687                 if (opt_author && draw_author(view, entry->author))
4688                         return TRUE;
4690                 if (opt_date && draw_date(view, &entry->time))
4691                         return TRUE;
4692         }
4693         if (draw_text(view, line->type, entry->name, TRUE))
4694                 return TRUE;
4695         return TRUE;
4698 static void
4699 open_blob_editor(const char *id)
4701         const char *blob_argv[] = { "git", "cat-file", "blob", id, NULL };
4702         char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4703         int fd = mkstemp(file);
4705         if (fd == -1)
4706                 report("Failed to create temporary file");
4707         else if (!io_run_append(blob_argv, fd))
4708                 report("Failed to save blob data to file");
4709         else
4710                 open_editor(file);
4711         if (fd != -1)
4712                 unlink(file);
4715 static enum request
4716 tree_request(struct view *view, enum request request, struct line *line)
4718         enum open_flags flags;
4719         struct tree_entry *entry = line->data;
4721         switch (request) {
4722         case REQ_VIEW_BLAME:
4723                 if (line->type != LINE_TREE_FILE) {
4724                         report("Blame only supported for files");
4725                         return REQ_NONE;
4726                 }
4728                 string_copy(opt_ref, view->vid);
4729                 return request;
4731         case REQ_EDIT:
4732                 if (line->type != LINE_TREE_FILE) {
4733                         report("Edit only supported for files");
4734                 } else if (!is_head_commit(view->vid)) {
4735                         open_blob_editor(entry->id);
4736                 } else {
4737                         open_editor(opt_file);
4738                 }
4739                 return REQ_NONE;
4741         case REQ_TOGGLE_SORT_FIELD:
4742         case REQ_TOGGLE_SORT_ORDER:
4743                 sort_view(view, request, &tree_sort_state, tree_compare);
4744                 return REQ_NONE;
4746         case REQ_PARENT:
4747                 if (!*opt_path) {
4748                         /* quit view if at top of tree */
4749                         return REQ_VIEW_CLOSE;
4750                 }
4751                 /* fake 'cd  ..' */
4752                 line = &view->line[1];
4753                 break;
4755         case REQ_ENTER:
4756                 break;
4758         default:
4759                 return request;
4760         }
4762         /* Cleanup the stack if the tree view is at a different tree. */
4763         while (!*opt_path && tree_stack)
4764                 pop_tree_stack_entry();
4766         switch (line->type) {
4767         case LINE_TREE_DIR:
4768                 /* Depending on whether it is a subdirectory or parent link
4769                  * mangle the path buffer. */
4770                 if (line == &view->line[1] && *opt_path) {
4771                         pop_tree_stack_entry();
4773                 } else {
4774                         const char *basename = tree_path(line);
4776                         push_tree_stack_entry(basename, view->lineno);
4777                 }
4779                 /* Trees and subtrees share the same ID, so they are not not
4780                  * unique like blobs. */
4781                 flags = OPEN_RELOAD;
4782                 request = REQ_VIEW_TREE;
4783                 break;
4785         case LINE_TREE_FILE:
4786                 flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
4787                 request = REQ_VIEW_BLOB;
4788                 break;
4790         default:
4791                 return REQ_NONE;
4792         }
4794         open_view(view, request, flags);
4795         if (request == REQ_VIEW_TREE)
4796                 view->lineno = tree_lineno;
4798         return REQ_NONE;
4801 static bool
4802 tree_grep(struct view *view, struct line *line)
4804         struct tree_entry *entry = line->data;
4805         const char *text[] = {
4806                 entry->name,
4807                 opt_author ? entry->author : "",
4808                 mkdate(&entry->time, opt_date),
4809                 NULL
4810         };
4812         return grep_text(view, text);
4815 static void
4816 tree_select(struct view *view, struct line *line)
4818         struct tree_entry *entry = line->data;
4820         if (line->type == LINE_TREE_FILE) {
4821                 string_copy_rev(ref_blob, entry->id);
4822                 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4824         } else if (line->type != LINE_TREE_DIR) {
4825                 return;
4826         }
4828         string_copy_rev(view->ref, entry->id);
4831 static bool
4832 tree_prepare(struct view *view)
4834         if (view->lines == 0 && opt_prefix[0]) {
4835                 char *pos = opt_prefix;
4837                 while (pos && *pos) {
4838                         char *end = strchr(pos, '/');
4840                         if (end)
4841                                 *end = 0;
4842                         push_tree_stack_entry(pos, 0);
4843                         pos = end;
4844                         if (end) {
4845                                 *end = '/';
4846                                 pos++;
4847                         }
4848                 }
4850         } else if (strcmp(view->vid, view->id)) {
4851                 opt_path[0] = 0;
4852         }
4854         return prepare_io(view, opt_cdup, view->ops->argv, TRUE);
4857 static const char *tree_argv[SIZEOF_ARG] = {
4858         "git", "ls-tree", "%(commit)", "%(directory)", NULL
4859 };
4861 static struct view_ops tree_ops = {
4862         "file",
4863         tree_argv,
4864         NULL,
4865         tree_read,
4866         tree_draw,
4867         tree_request,
4868         tree_grep,
4869         tree_select,
4870         tree_prepare,
4871 };
4873 static bool
4874 blob_read(struct view *view, char *line)
4876         if (!line)
4877                 return TRUE;
4878         return add_line_text(view, line, LINE_DEFAULT) != NULL;
4881 static enum request
4882 blob_request(struct view *view, enum request request, struct line *line)
4884         switch (request) {
4885         case REQ_EDIT:
4886                 open_blob_editor(view->vid);
4887                 return REQ_NONE;
4888         default:
4889                 return pager_request(view, request, line);
4890         }
4893 static const char *blob_argv[SIZEOF_ARG] = {
4894         "git", "cat-file", "blob", "%(blob)", NULL
4895 };
4897 static struct view_ops blob_ops = {
4898         "line",
4899         blob_argv,
4900         NULL,
4901         blob_read,
4902         pager_draw,
4903         blob_request,
4904         pager_grep,
4905         pager_select,
4906 };
4908 /*
4909  * Blame backend
4910  *
4911  * Loading the blame view is a two phase job:
4912  *
4913  *  1. File content is read either using opt_file from the
4914  *     filesystem or using git-cat-file.
4915  *  2. Then blame information is incrementally added by
4916  *     reading output from git-blame.
4917  */
4919 struct blame_commit {
4920         char id[SIZEOF_REV];            /* SHA1 ID. */
4921         char title[128];                /* First line of the commit message. */
4922         const char *author;             /* Author of the commit. */
4923         struct time time;               /* Date from the author ident. */
4924         char filename[128];             /* Name of file. */
4925         char parent_id[SIZEOF_REV];     /* Parent/previous SHA1 ID. */
4926         char parent_filename[128];      /* Parent/previous name of file. */
4927 };
4929 struct blame {
4930         struct blame_commit *commit;
4931         unsigned long lineno;
4932         char text[1];
4933 };
4935 static bool
4936 blame_open(struct view *view)
4938         char path[SIZEOF_STR];
4939         size_t i;
4941         if (!view->prev && *opt_prefix) {
4942                 string_copy(path, opt_file);
4943                 if (!string_format(opt_file, "%s%s", opt_prefix, path))
4944                         return FALSE;
4945         }
4947         if (*opt_ref || !io_open(&view->io, "%s%s", opt_cdup, opt_file)) {
4948                 const char *blame_cat_file_argv[] = {
4949                         "git", "cat-file", "blob", path, NULL
4950                 };
4952                 if (!string_format(path, "%s:%s", opt_ref, opt_file) ||
4953                     !start_update(view, blame_cat_file_argv, opt_cdup))
4954                         return FALSE;
4955         }
4957         /* First pass: remove multiple references to the same commit. */
4958         for (i = 0; i < view->lines; i++) {
4959                 struct blame *blame = view->line[i].data;
4961                 if (blame->commit && blame->commit->id[0])
4962                         blame->commit->id[0] = 0;
4963                 else
4964                         blame->commit = NULL;
4965         }
4967         /* Second pass: free existing references. */
4968         for (i = 0; i < view->lines; i++) {
4969                 struct blame *blame = view->line[i].data;
4971                 if (blame->commit)
4972                         free(blame->commit);
4973         }
4975         setup_update(view, opt_file);
4976         string_format(view->ref, "%s ...", opt_file);
4978         return TRUE;
4981 static struct blame_commit *
4982 get_blame_commit(struct view *view, const char *id)
4984         size_t i;
4986         for (i = 0; i < view->lines; i++) {
4987                 struct blame *blame = view->line[i].data;
4989                 if (!blame->commit)
4990                         continue;
4992                 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4993                         return blame->commit;
4994         }
4996         {
4997                 struct blame_commit *commit = calloc(1, sizeof(*commit));
4999                 if (commit)
5000                         string_ncopy(commit->id, id, SIZEOF_REV);
5001                 return commit;
5002         }
5005 static bool
5006 parse_number(const char **posref, size_t *number, size_t min, size_t max)
5008         const char *pos = *posref;
5010         *posref = NULL;
5011         pos = strchr(pos + 1, ' ');
5012         if (!pos || !isdigit(pos[1]))
5013                 return FALSE;
5014         *number = atoi(pos + 1);
5015         if (*number < min || *number > max)
5016                 return FALSE;
5018         *posref = pos;
5019         return TRUE;
5022 static struct blame_commit *
5023 parse_blame_commit(struct view *view, const char *text, int *blamed)
5025         struct blame_commit *commit;
5026         struct blame *blame;
5027         const char *pos = text + SIZEOF_REV - 2;
5028         size_t orig_lineno = 0;
5029         size_t lineno;
5030         size_t group;
5032         if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
5033                 return NULL;
5035         if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
5036             !parse_number(&pos, &lineno, 1, view->lines) ||
5037             !parse_number(&pos, &group, 1, view->lines - lineno + 1))
5038                 return NULL;
5040         commit = get_blame_commit(view, text);
5041         if (!commit)
5042                 return NULL;
5044         *blamed += group;
5045         while (group--) {
5046                 struct line *line = &view->line[lineno + group - 1];
5048                 blame = line->data;
5049                 blame->commit = commit;
5050                 blame->lineno = orig_lineno + group - 1;
5051                 line->dirty = 1;
5052         }
5054         return commit;
5057 static bool
5058 blame_read_file(struct view *view, const char *line, bool *read_file)
5060         if (!line) {
5061                 const char *blame_argv[] = {
5062                         "git", "blame", "--incremental",
5063                                 *opt_ref ? opt_ref : "--incremental", "--", opt_file, NULL
5064                 };
5066                 if (view->lines == 0 && !view->prev)
5067                         die("No blame exist for %s", view->vid);
5069                 if (view->lines == 0 || !start_update(view, blame_argv, opt_cdup)) {
5070                         report("Failed to load blame data");
5071                         return TRUE;
5072                 }
5074                 *read_file = FALSE;
5075                 return FALSE;
5077         } else {
5078                 size_t linelen = strlen(line);
5079                 struct blame *blame = malloc(sizeof(*blame) + linelen);
5081                 if (!blame)
5082                         return FALSE;
5084                 blame->commit = NULL;
5085                 strncpy(blame->text, line, linelen);
5086                 blame->text[linelen] = 0;
5087                 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
5088         }
5091 static bool
5092 match_blame_header(const char *name, char **line)
5094         size_t namelen = strlen(name);
5095         bool matched = !strncmp(name, *line, namelen);
5097         if (matched)
5098                 *line += namelen;
5100         return matched;
5103 static bool
5104 blame_read(struct view *view, char *line)
5106         static struct blame_commit *commit = NULL;
5107         static int blamed = 0;
5108         static bool read_file = TRUE;
5110         if (read_file)
5111                 return blame_read_file(view, line, &read_file);
5113         if (!line) {
5114                 /* Reset all! */
5115                 commit = NULL;
5116                 blamed = 0;
5117                 read_file = TRUE;
5118                 string_format(view->ref, "%s", view->vid);
5119                 if (view_is_displayed(view)) {
5120                         update_view_title(view);
5121                         redraw_view_from(view, 0);
5122                 }
5123                 return TRUE;
5124         }
5126         if (!commit) {
5127                 commit = parse_blame_commit(view, line, &blamed);
5128                 string_format(view->ref, "%s %2d%%", view->vid,
5129                               view->lines ? blamed * 100 / view->lines : 0);
5131         } else if (match_blame_header("author ", &line)) {
5132                 commit->author = get_author(line);
5134         } else if (match_blame_header("author-time ", &line)) {
5135                 parse_timesec(&commit->time, line);
5137         } else if (match_blame_header("author-tz ", &line)) {
5138                 parse_timezone(&commit->time, line);
5140         } else if (match_blame_header("summary ", &line)) {
5141                 string_ncopy(commit->title, line, strlen(line));
5143         } else if (match_blame_header("previous ", &line)) {
5144                 if (strlen(line) <= SIZEOF_REV)
5145                         return FALSE;
5146                 string_copy_rev(commit->parent_id, line);
5147                 line += SIZEOF_REV;
5148                 string_ncopy(commit->parent_filename, line, strlen(line));
5150         } else if (match_blame_header("filename ", &line)) {
5151                 string_ncopy(commit->filename, line, strlen(line));
5152                 commit = NULL;
5153         }
5155         return TRUE;
5158 static bool
5159 blame_draw(struct view *view, struct line *line, unsigned int lineno)
5161         struct blame *blame = line->data;
5162         struct time *time = NULL;
5163         const char *id = NULL, *author = NULL;
5165         if (blame->commit && *blame->commit->filename) {
5166                 id = blame->commit->id;
5167                 author = blame->commit->author;
5168                 time = &blame->commit->time;
5169         }
5171         if (opt_date && draw_date(view, time))
5172                 return TRUE;
5174         if (opt_author && draw_author(view, author))
5175                 return TRUE;
5177         if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
5178                 return TRUE;
5180         if (draw_lineno(view, lineno))
5181                 return TRUE;
5183         draw_text(view, LINE_DEFAULT, blame->text, TRUE);
5184         return TRUE;
5187 static bool
5188 check_blame_commit(struct blame *blame, bool check_null_id)
5190         if (!blame->commit)
5191                 report("Commit data not loaded yet");
5192         else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
5193                 report("No commit exist for the selected line");
5194         else
5195                 return TRUE;
5196         return FALSE;
5199 static void
5200 setup_blame_parent_line(struct view *view, struct blame *blame)
5202         char from[SIZEOF_REF + SIZEOF_STR];
5203         char to[SIZEOF_REF + SIZEOF_STR];
5204         const char *diff_tree_argv[] = {
5205                 "git", "diff", "--no-textconv", "--no-extdiff", "--no-color",
5206                         "-U0", from, to, "--", NULL
5207         };
5208         struct io io;
5209         int parent_lineno = -1;
5210         int blamed_lineno = -1;
5211         char *line;
5213         if (!string_format(from, "%s:%s", opt_ref, opt_file) ||
5214             !string_format(to, "%s:%s", blame->commit->id, blame->commit->filename) ||
5215             !io_run(&io, IO_RD, NULL, diff_tree_argv))
5216                 return;
5218         while ((line = io_get(&io, '\n', TRUE))) {
5219                 if (*line == '@') {
5220                         char *pos = strchr(line, '+');
5222                         parent_lineno = atoi(line + 4);
5223                         if (pos)
5224                                 blamed_lineno = atoi(pos + 1);
5226                 } else if (*line == '+' && parent_lineno != -1) {
5227                         if (blame->lineno == blamed_lineno - 1 &&
5228                             !strcmp(blame->text, line + 1)) {
5229                                 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
5230                                 break;
5231                         }
5232                         blamed_lineno++;
5233                 }
5234         }
5236         io_done(&io);
5239 static enum request
5240 blame_request(struct view *view, enum request request, struct line *line)
5242         enum open_flags flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
5243         struct blame *blame = line->data;
5245         switch (request) {
5246         case REQ_VIEW_BLAME:
5247                 if (check_blame_commit(blame, TRUE)) {
5248                         string_copy(opt_ref, blame->commit->id);
5249                         string_copy(opt_file, blame->commit->filename);
5250                         if (blame->lineno)
5251                                 view->lineno = blame->lineno;
5252                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5253                 }
5254                 break;
5256         case REQ_PARENT:
5257                 if (!check_blame_commit(blame, TRUE))
5258                         break;
5259                 if (!*blame->commit->parent_id) {
5260                         report("The selected commit has no parents");
5261                 } else {
5262                         string_copy_rev(opt_ref, blame->commit->parent_id);
5263                         string_copy(opt_file, blame->commit->parent_filename);
5264                         setup_blame_parent_line(view, blame);
5265                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5266                 }
5267                 break;
5269         case REQ_ENTER:
5270                 if (!check_blame_commit(blame, FALSE))
5271                         break;
5273                 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
5274                     !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
5275                         break;
5277                 if (!strcmp(blame->commit->id, NULL_ID)) {
5278                         struct view *diff = VIEW(REQ_VIEW_DIFF);
5279                         const char *diff_index_argv[] = {
5280                                 "git", "diff-index", "--root", "--patch-with-stat",
5281                                         "-C", "-M", "HEAD", "--", view->vid, NULL
5282                         };
5284                         if (!*blame->commit->parent_id) {
5285                                 diff_index_argv[1] = "diff";
5286                                 diff_index_argv[2] = "--no-color";
5287                                 diff_index_argv[6] = "--";
5288                                 diff_index_argv[7] = "/dev/null";
5289                         }
5291                         if (!prepare_update(diff, diff_index_argv, NULL)) {
5292                                 report("Failed to allocate diff command");
5293                                 break;
5294                         }
5295                         flags |= OPEN_PREPARED;
5296                 }
5298                 open_view(view, REQ_VIEW_DIFF, flags);
5299                 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
5300                         string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
5301                 break;
5303         default:
5304                 return request;
5305         }
5307         return REQ_NONE;
5310 static bool
5311 blame_grep(struct view *view, struct line *line)
5313         struct blame *blame = line->data;
5314         struct blame_commit *commit = blame->commit;
5315         const char *text[] = {
5316                 blame->text,
5317                 commit ? commit->title : "",
5318                 commit ? commit->id : "",
5319                 commit && opt_author ? commit->author : "",
5320                 commit ? mkdate(&commit->time, opt_date) : "",
5321                 NULL
5322         };
5324         return grep_text(view, text);
5327 static void
5328 blame_select(struct view *view, struct line *line)
5330         struct blame *blame = line->data;
5331         struct blame_commit *commit = blame->commit;
5333         if (!commit)
5334                 return;
5336         if (!strcmp(commit->id, NULL_ID))
5337                 string_ncopy(ref_commit, "HEAD", 4);
5338         else
5339                 string_copy_rev(ref_commit, commit->id);
5342 static struct view_ops blame_ops = {
5343         "line",
5344         NULL,
5345         blame_open,
5346         blame_read,
5347         blame_draw,
5348         blame_request,
5349         blame_grep,
5350         blame_select,
5351 };
5353 /*
5354  * Branch backend
5355  */
5357 struct branch {
5358         const char *author;             /* Author of the last commit. */
5359         struct time time;               /* Date of the last activity. */
5360         const struct ref *ref;          /* Name and commit ID information. */
5361 };
5363 static const struct ref branch_all;
5365 static const enum sort_field branch_sort_fields[] = {
5366         ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
5367 };
5368 static struct sort_state branch_sort_state = SORT_STATE(branch_sort_fields);
5370 static int
5371 branch_compare(const void *l1, const void *l2)
5373         const struct branch *branch1 = ((const struct line *) l1)->data;
5374         const struct branch *branch2 = ((const struct line *) l2)->data;
5376         switch (get_sort_field(branch_sort_state)) {
5377         case ORDERBY_DATE:
5378                 return sort_order(branch_sort_state, timecmp(&branch1->time, &branch2->time));
5380         case ORDERBY_AUTHOR:
5381                 return sort_order(branch_sort_state, strcmp(branch1->author, branch2->author));
5383         case ORDERBY_NAME:
5384         default:
5385                 return sort_order(branch_sort_state, strcmp(branch1->ref->name, branch2->ref->name));
5386         }
5389 static bool
5390 branch_draw(struct view *view, struct line *line, unsigned int lineno)
5392         struct branch *branch = line->data;
5393         enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
5395         if (opt_date && draw_date(view, &branch->time))
5396                 return TRUE;
5398         if (opt_author && draw_author(view, branch->author))
5399                 return TRUE;
5401         draw_text(view, type, branch->ref == &branch_all ? "All branches" : branch->ref->name, TRUE);
5402         return TRUE;
5405 static enum request
5406 branch_request(struct view *view, enum request request, struct line *line)
5408         struct branch *branch = line->data;
5410         switch (request) {
5411         case REQ_REFRESH:
5412                 load_refs();
5413                 open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
5414                 return REQ_NONE;
5416         case REQ_TOGGLE_SORT_FIELD:
5417         case REQ_TOGGLE_SORT_ORDER:
5418                 sort_view(view, request, &branch_sort_state, branch_compare);
5419                 return REQ_NONE;
5421         case REQ_ENTER:
5422         {
5423                 const struct ref *ref = branch->ref;
5424                 const char *all_branches_argv[] = {
5425                         "git", "log", "--no-color", "--pretty=raw", "--parents",
5426                               "--topo-order",
5427                               ref == &branch_all ? "--all" : ref->name, NULL
5428                 };
5429                 struct view *main_view = VIEW(REQ_VIEW_MAIN);
5431                 if (!prepare_update(main_view, all_branches_argv, NULL))
5432                         report("Failed to load view of all branches");
5433                 else
5434                         open_view(view, REQ_VIEW_MAIN, OPEN_PREPARED | OPEN_SPLIT);
5435                 return REQ_NONE;
5436         }
5437         default:
5438                 return request;
5439         }
5442 static bool
5443 branch_read(struct view *view, char *line)
5445         static char id[SIZEOF_REV];
5446         struct branch *reference;
5447         size_t i;
5449         if (!line)
5450                 return TRUE;
5452         switch (get_line_type(line)) {
5453         case LINE_COMMIT:
5454                 string_copy_rev(id, line + STRING_SIZE("commit "));
5455                 return TRUE;
5457         case LINE_AUTHOR:
5458                 for (i = 0, reference = NULL; i < view->lines; i++) {
5459                         struct branch *branch = view->line[i].data;
5461                         if (strcmp(branch->ref->id, id))
5462                                 continue;
5464                         view->line[i].dirty = TRUE;
5465                         if (reference) {
5466                                 branch->author = reference->author;
5467                                 branch->time = reference->time;
5468                                 continue;
5469                         }
5471                         parse_author_line(line + STRING_SIZE("author "),
5472                                           &branch->author, &branch->time);
5473                         reference = branch;
5474                 }
5475                 return TRUE;
5477         default:
5478                 return TRUE;
5479         }
5483 static bool
5484 branch_open_visitor(void *data, const struct ref *ref)
5486         struct view *view = data;
5487         struct branch *branch;
5489         if (ref->tag || ref->ltag || ref->remote)
5490                 return TRUE;
5492         branch = calloc(1, sizeof(*branch));
5493         if (!branch)
5494                 return FALSE;
5496         branch->ref = ref;
5497         return !!add_line_data(view, branch, LINE_DEFAULT);
5500 static bool
5501 branch_open(struct view *view)
5503         const char *branch_log[] = {
5504                 "git", "log", "--no-color", "--pretty=raw",
5505                         "--simplify-by-decoration", "--all", NULL
5506         };
5508         if (!start_update(view, branch_log, NULL)) {
5509                 report("Failed to load branch data");
5510                 return TRUE;
5511         }
5513         setup_update(view, view->id);
5514         branch_open_visitor(view, &branch_all);
5515         foreach_ref(branch_open_visitor, view);
5516         view->p_restore = TRUE;
5518         return TRUE;
5521 static bool
5522 branch_grep(struct view *view, struct line *line)
5524         struct branch *branch = line->data;
5525         const char *text[] = {
5526                 branch->ref->name,
5527                 branch->author,
5528                 NULL
5529         };
5531         return grep_text(view, text);
5534 static void
5535 branch_select(struct view *view, struct line *line)
5537         struct branch *branch = line->data;
5539         string_copy_rev(view->ref, branch->ref->id);
5540         string_copy_rev(ref_commit, branch->ref->id);
5541         string_copy_rev(ref_head, branch->ref->id);
5542         string_copy_rev(ref_branch, branch->ref->name);
5545 static struct view_ops branch_ops = {
5546         "branch",
5547         NULL,
5548         branch_open,
5549         branch_read,
5550         branch_draw,
5551         branch_request,
5552         branch_grep,
5553         branch_select,
5554 };
5556 /*
5557  * Status backend
5558  */
5560 struct status {
5561         char status;
5562         struct {
5563                 mode_t mode;
5564                 char rev[SIZEOF_REV];
5565                 char name[SIZEOF_STR];
5566         } old;
5567         struct {
5568                 mode_t mode;
5569                 char rev[SIZEOF_REV];
5570                 char name[SIZEOF_STR];
5571         } new;
5572 };
5574 static char status_onbranch[SIZEOF_STR];
5575 static struct status stage_status;
5576 static enum line_type stage_line_type;
5577 static size_t stage_chunks;
5578 static int *stage_chunk;
5580 DEFINE_ALLOCATOR(realloc_ints, int, 32)
5582 /* This should work even for the "On branch" line. */
5583 static inline bool
5584 status_has_none(struct view *view, struct line *line)
5586         return line < view->line + view->lines && !line[1].data;
5589 /* Get fields from the diff line:
5590  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5591  */
5592 static inline bool
5593 status_get_diff(struct status *file, const char *buf, size_t bufsize)
5595         const char *old_mode = buf +  1;
5596         const char *new_mode = buf +  8;
5597         const char *old_rev  = buf + 15;
5598         const char *new_rev  = buf + 56;
5599         const char *status   = buf + 97;
5601         if (bufsize < 98 ||
5602             old_mode[-1] != ':' ||
5603             new_mode[-1] != ' ' ||
5604             old_rev[-1]  != ' ' ||
5605             new_rev[-1]  != ' ' ||
5606             status[-1]   != ' ')
5607                 return FALSE;
5609         file->status = *status;
5611         string_copy_rev(file->old.rev, old_rev);
5612         string_copy_rev(file->new.rev, new_rev);
5614         file->old.mode = strtoul(old_mode, NULL, 8);
5615         file->new.mode = strtoul(new_mode, NULL, 8);
5617         file->old.name[0] = file->new.name[0] = 0;
5619         return TRUE;
5622 static bool
5623 status_run(struct view *view, const char *argv[], char status, enum line_type type)
5625         struct status *unmerged = NULL;
5626         char *buf;
5627         struct io io;
5629         if (!io_run(&io, IO_RD, opt_cdup, argv))
5630                 return FALSE;
5632         add_line_data(view, NULL, type);
5634         while ((buf = io_get(&io, 0, TRUE))) {
5635                 struct status *file = unmerged;
5637                 if (!file) {
5638                         file = calloc(1, sizeof(*file));
5639                         if (!file || !add_line_data(view, file, type))
5640                                 goto error_out;
5641                 }
5643                 /* Parse diff info part. */
5644                 if (status) {
5645                         file->status = status;
5646                         if (status == 'A')
5647                                 string_copy(file->old.rev, NULL_ID);
5649                 } else if (!file->status || file == unmerged) {
5650                         if (!status_get_diff(file, buf, strlen(buf)))
5651                                 goto error_out;
5653                         buf = io_get(&io, 0, TRUE);
5654                         if (!buf)
5655                                 break;
5657                         /* Collapse all modified entries that follow an
5658                          * associated unmerged entry. */
5659                         if (unmerged == file) {
5660                                 unmerged->status = 'U';
5661                                 unmerged = NULL;
5662                         } else if (file->status == 'U') {
5663                                 unmerged = file;
5664                         }
5665                 }
5667                 /* Grab the old name for rename/copy. */
5668                 if (!*file->old.name &&
5669                     (file->status == 'R' || file->status == 'C')) {
5670                         string_ncopy(file->old.name, buf, strlen(buf));
5672                         buf = io_get(&io, 0, TRUE);
5673                         if (!buf)
5674                                 break;
5675                 }
5677                 /* git-ls-files just delivers a NUL separated list of
5678                  * file names similar to the second half of the
5679                  * git-diff-* output. */
5680                 string_ncopy(file->new.name, buf, strlen(buf));
5681                 if (!*file->old.name)
5682                         string_copy(file->old.name, file->new.name);
5683                 file = NULL;
5684         }
5686         if (io_error(&io)) {
5687 error_out:
5688                 io_done(&io);
5689                 return FALSE;
5690         }
5692         if (!view->line[view->lines - 1].data)
5693                 add_line_data(view, NULL, LINE_STAT_NONE);
5695         io_done(&io);
5696         return TRUE;
5699 /* Don't show unmerged entries in the staged section. */
5700 static const char *status_diff_index_argv[] = {
5701         "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5702                              "--cached", "-M", "HEAD", NULL
5703 };
5705 static const char *status_diff_files_argv[] = {
5706         "git", "diff-files", "-z", NULL
5707 };
5709 static const char *status_list_other_argv[] = {
5710         "git", "ls-files", "-z", "--others", "--exclude-standard", opt_prefix, NULL, NULL,
5711 };
5713 static const char *status_list_no_head_argv[] = {
5714         "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5715 };
5717 static const char *update_index_argv[] = {
5718         "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5719 };
5721 /* Restore the previous line number to stay in the context or select a
5722  * line with something that can be updated. */
5723 static void
5724 status_restore(struct view *view)
5726         if (view->p_lineno >= view->lines)
5727                 view->p_lineno = view->lines - 1;
5728         while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
5729                 view->p_lineno++;
5730         while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
5731                 view->p_lineno--;
5733         /* If the above fails, always skip the "On branch" line. */
5734         if (view->p_lineno < view->lines)
5735                 view->lineno = view->p_lineno;
5736         else
5737                 view->lineno = 1;
5739         if (view->lineno < view->offset)
5740                 view->offset = view->lineno;
5741         else if (view->offset + view->height <= view->lineno)
5742                 view->offset = view->lineno - view->height + 1;
5744         view->p_restore = FALSE;
5747 static void
5748 status_update_onbranch(void)
5750         static const char *paths[][2] = {
5751                 { "rebase-apply/rebasing",      "Rebasing" },
5752                 { "rebase-apply/applying",      "Applying mailbox" },
5753                 { "rebase-apply/",              "Rebasing mailbox" },
5754                 { "rebase-merge/interactive",   "Interactive rebase" },
5755                 { "rebase-merge/",              "Rebase merge" },
5756                 { "MERGE_HEAD",                 "Merging" },
5757                 { "BISECT_LOG",                 "Bisecting" },
5758                 { "HEAD",                       "On branch" },
5759         };
5760         char buf[SIZEOF_STR];
5761         struct stat stat;
5762         int i;
5764         if (is_initial_commit()) {
5765                 string_copy(status_onbranch, "Initial commit");
5766                 return;
5767         }
5769         for (i = 0; i < ARRAY_SIZE(paths); i++) {
5770                 char *head = opt_head;
5772                 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5773                     lstat(buf, &stat) < 0)
5774                         continue;
5776                 if (!*opt_head) {
5777                         struct io io;
5779                         if (io_open(&io, "%s/rebase-merge/head-name", opt_git_dir) &&
5780                             io_read_buf(&io, buf, sizeof(buf))) {
5781                                 head = buf;
5782                                 if (!prefixcmp(head, "refs/heads/"))
5783                                         head += STRING_SIZE("refs/heads/");
5784                         }
5785                 }
5787                 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5788                         string_copy(status_onbranch, opt_head);
5789                 return;
5790         }
5792         string_copy(status_onbranch, "Not currently on any branch");
5795 /* First parse staged info using git-diff-index(1), then parse unstaged
5796  * info using git-diff-files(1), and finally untracked files using
5797  * git-ls-files(1). */
5798 static bool
5799 status_open(struct view *view)
5801         reset_view(view);
5803         add_line_data(view, NULL, LINE_STAT_HEAD);
5804         status_update_onbranch();
5806         io_run_bg(update_index_argv);
5808         if (is_initial_commit()) {
5809                 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5810                         return FALSE;
5811         } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5812                 return FALSE;
5813         }
5815         if (!opt_untracked_dirs_content)
5816                 status_list_other_argv[ARRAY_SIZE(status_list_other_argv) - 2] = "--directory";
5818         if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5819             !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5820                 return FALSE;
5822         /* Restore the exact position or use the specialized restore
5823          * mode? */
5824         if (!view->p_restore)
5825                 status_restore(view);
5826         return TRUE;
5829 static bool
5830 status_draw(struct view *view, struct line *line, unsigned int lineno)
5832         struct status *status = line->data;
5833         enum line_type type;
5834         const char *text;
5836         if (!status) {
5837                 switch (line->type) {
5838                 case LINE_STAT_STAGED:
5839                         type = LINE_STAT_SECTION;
5840                         text = "Changes to be committed:";
5841                         break;
5843                 case LINE_STAT_UNSTAGED:
5844                         type = LINE_STAT_SECTION;
5845                         text = "Changed but not updated:";
5846                         break;
5848                 case LINE_STAT_UNTRACKED:
5849                         type = LINE_STAT_SECTION;
5850                         text = "Untracked files:";
5851                         break;
5853                 case LINE_STAT_NONE:
5854                         type = LINE_DEFAULT;
5855                         text = "  (no files)";
5856                         break;
5858                 case LINE_STAT_HEAD:
5859                         type = LINE_STAT_HEAD;
5860                         text = status_onbranch;
5861                         break;
5863                 default:
5864                         return FALSE;
5865                 }
5866         } else {
5867                 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5869                 buf[0] = status->status;
5870                 if (draw_text(view, line->type, buf, TRUE))
5871                         return TRUE;
5872                 type = LINE_DEFAULT;
5873                 text = status->new.name;
5874         }
5876         draw_text(view, type, text, TRUE);
5877         return TRUE;
5880 static enum request
5881 status_load_error(struct view *view, struct view *stage, const char *path)
5883         if (displayed_views() == 2 || display[current_view] != view)
5884                 maximize_view(view);
5885         report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5886         return REQ_NONE;
5889 static enum request
5890 status_enter(struct view *view, struct line *line)
5892         struct status *status = line->data;
5893         const char *oldpath = status ? status->old.name : NULL;
5894         /* Diffs for unmerged entries are empty when passing the new
5895          * path, so leave it empty. */
5896         const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5897         const char *info;
5898         enum open_flags split;
5899         struct view *stage = VIEW(REQ_VIEW_STAGE);
5901         if (line->type == LINE_STAT_NONE ||
5902             (!status && line[1].type == LINE_STAT_NONE)) {
5903                 report("No file to diff");
5904                 return REQ_NONE;
5905         }
5907         switch (line->type) {
5908         case LINE_STAT_STAGED:
5909                 if (is_initial_commit()) {
5910                         const char *no_head_diff_argv[] = {
5911                                 "git", "diff", "--no-color", "--patch-with-stat",
5912                                         "--", "/dev/null", newpath, NULL
5913                         };
5915                         if (!prepare_update(stage, no_head_diff_argv, opt_cdup))
5916                                 return status_load_error(view, stage, newpath);
5917                 } else {
5918                         const char *index_show_argv[] = {
5919                                 "git", "diff-index", "--root", "--patch-with-stat",
5920                                         "-C", "-M", "--cached", "HEAD", "--",
5921                                         oldpath, newpath, NULL
5922                         };
5924                         if (!prepare_update(stage, index_show_argv, opt_cdup))
5925                                 return status_load_error(view, stage, newpath);
5926                 }
5928                 if (status)
5929                         info = "Staged changes to %s";
5930                 else
5931                         info = "Staged changes";
5932                 break;
5934         case LINE_STAT_UNSTAGED:
5935         {
5936                 const char *files_show_argv[] = {
5937                         "git", "diff-files", "--root", "--patch-with-stat",
5938                                 "-C", "-M", "--", oldpath, newpath, NULL
5939                 };
5941                 if (!prepare_update(stage, files_show_argv, opt_cdup))
5942                         return status_load_error(view, stage, newpath);
5943                 if (status)
5944                         info = "Unstaged changes to %s";
5945                 else
5946                         info = "Unstaged changes";
5947                 break;
5948         }
5949         case LINE_STAT_UNTRACKED:
5950                 if (!newpath) {
5951                         report("No file to show");
5952                         return REQ_NONE;
5953                 }
5955                 if (!suffixcmp(status->new.name, -1, "/")) {
5956                         report("Cannot display a directory");
5957                         return REQ_NONE;
5958                 }
5960                 if (!prepare_update_file(stage, newpath))
5961                         return status_load_error(view, stage, newpath);
5962                 info = "Untracked file %s";
5963                 break;
5965         case LINE_STAT_HEAD:
5966                 return REQ_NONE;
5968         default:
5969                 die("line type %d not handled in switch", line->type);
5970         }
5972         split = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
5973         open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5974         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5975                 if (status) {
5976                         stage_status = *status;
5977                 } else {
5978                         memset(&stage_status, 0, sizeof(stage_status));
5979                 }
5981                 stage_line_type = line->type;
5982                 stage_chunks = 0;
5983                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5984         }
5986         return REQ_NONE;
5989 static bool
5990 status_exists(struct status *status, enum line_type type)
5992         struct view *view = VIEW(REQ_VIEW_STATUS);
5993         unsigned long lineno;
5995         for (lineno = 0; lineno < view->lines; lineno++) {
5996                 struct line *line = &view->line[lineno];
5997                 struct status *pos = line->data;
5999                 if (line->type != type)
6000                         continue;
6001                 if (!pos && (!status || !status->status) && line[1].data) {
6002                         select_view_line(view, lineno);
6003                         return TRUE;
6004                 }
6005                 if (pos && !strcmp(status->new.name, pos->new.name)) {
6006                         select_view_line(view, lineno);
6007                         return TRUE;
6008                 }
6009         }
6011         return FALSE;
6015 static bool
6016 status_update_prepare(struct io *io, enum line_type type)
6018         const char *staged_argv[] = {
6019                 "git", "update-index", "-z", "--index-info", NULL
6020         };
6021         const char *others_argv[] = {
6022                 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
6023         };
6025         switch (type) {
6026         case LINE_STAT_STAGED:
6027                 return io_run(io, IO_WR, opt_cdup, staged_argv);
6029         case LINE_STAT_UNSTAGED:
6030         case LINE_STAT_UNTRACKED:
6031                 return io_run(io, IO_WR, opt_cdup, others_argv);
6033         default:
6034                 die("line type %d not handled in switch", type);
6035                 return FALSE;
6036         }
6039 static bool
6040 status_update_write(struct io *io, struct status *status, enum line_type type)
6042         char buf[SIZEOF_STR];
6043         size_t bufsize = 0;
6045         switch (type) {
6046         case LINE_STAT_STAGED:
6047                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
6048                                         status->old.mode,
6049                                         status->old.rev,
6050                                         status->old.name, 0))
6051                         return FALSE;
6052                 break;
6054         case LINE_STAT_UNSTAGED:
6055         case LINE_STAT_UNTRACKED:
6056                 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
6057                         return FALSE;
6058                 break;
6060         default:
6061                 die("line type %d not handled in switch", type);
6062         }
6064         return io_write(io, buf, bufsize);
6067 static bool
6068 status_update_file(struct status *status, enum line_type type)
6070         struct io io;
6071         bool result;
6073         if (!status_update_prepare(&io, type))
6074                 return FALSE;
6076         result = status_update_write(&io, status, type);
6077         return io_done(&io) && result;
6080 static bool
6081 status_update_files(struct view *view, struct line *line)
6083         char buf[sizeof(view->ref)];
6084         struct io io;
6085         bool result = TRUE;
6086         struct line *pos = view->line + view->lines;
6087         int files = 0;
6088         int file, done;
6089         int cursor_y = -1, cursor_x = -1;
6091         if (!status_update_prepare(&io, line->type))
6092                 return FALSE;
6094         for (pos = line; pos < view->line + view->lines && pos->data; pos++)
6095                 files++;
6097         string_copy(buf, view->ref);
6098         getsyx(cursor_y, cursor_x);
6099         for (file = 0, done = 5; result && file < files; line++, file++) {
6100                 int almost_done = file * 100 / files;
6102                 if (almost_done > done) {
6103                         done = almost_done;
6104                         string_format(view->ref, "updating file %u of %u (%d%% done)",
6105                                       file, files, done);
6106                         update_view_title(view);
6107                         setsyx(cursor_y, cursor_x);
6108                         doupdate();
6109                 }
6110                 result = status_update_write(&io, line->data, line->type);
6111         }
6112         string_copy(view->ref, buf);
6114         return io_done(&io) && result;
6117 static bool
6118 status_update(struct view *view)
6120         struct line *line = &view->line[view->lineno];
6122         assert(view->lines);
6124         if (!line->data) {
6125                 /* This should work even for the "On branch" line. */
6126                 if (line < view->line + view->lines && !line[1].data) {
6127                         report("Nothing to update");
6128                         return FALSE;
6129                 }
6131                 if (!status_update_files(view, line + 1)) {
6132                         report("Failed to update file status");
6133                         return FALSE;
6134                 }
6136         } else if (!status_update_file(line->data, line->type)) {
6137                 report("Failed to update file status");
6138                 return FALSE;
6139         }
6141         return TRUE;
6144 static bool
6145 status_revert(struct status *status, enum line_type type, bool has_none)
6147         if (!status || type != LINE_STAT_UNSTAGED) {
6148                 if (type == LINE_STAT_STAGED) {
6149                         report("Cannot revert changes to staged files");
6150                 } else if (type == LINE_STAT_UNTRACKED) {
6151                         report("Cannot revert changes to untracked files");
6152                 } else if (has_none) {
6153                         report("Nothing to revert");
6154                 } else {
6155                         report("Cannot revert changes to multiple files");
6156                 }
6158         } else if (prompt_yesno("Are you sure you want to revert changes?")) {
6159                 char mode[10] = "100644";
6160                 const char *reset_argv[] = {
6161                         "git", "update-index", "--cacheinfo", mode,
6162                                 status->old.rev, status->old.name, NULL
6163                 };
6164                 const char *checkout_argv[] = {
6165                         "git", "checkout", "--", status->old.name, NULL
6166                 };
6168                 if (status->status == 'U') {
6169                         string_format(mode, "%5o", status->old.mode);
6171                         if (status->old.mode == 0 && status->new.mode == 0) {
6172                                 reset_argv[2] = "--force-remove";
6173                                 reset_argv[3] = status->old.name;
6174                                 reset_argv[4] = NULL;
6175                         }
6177                         if (!io_run_fg(reset_argv, opt_cdup))
6178                                 return FALSE;
6179                         if (status->old.mode == 0 && status->new.mode == 0)
6180                                 return TRUE;
6181                 }
6183                 return io_run_fg(checkout_argv, opt_cdup);
6184         }
6186         return FALSE;
6189 static enum request
6190 status_request(struct view *view, enum request request, struct line *line)
6192         struct status *status = line->data;
6194         switch (request) {
6195         case REQ_STATUS_UPDATE:
6196                 if (!status_update(view))
6197                         return REQ_NONE;
6198                 break;
6200         case REQ_STATUS_REVERT:
6201                 if (!status_revert(status, line->type, status_has_none(view, line)))
6202                         return REQ_NONE;
6203                 break;
6205         case REQ_STATUS_MERGE:
6206                 if (!status || status->status != 'U') {
6207                         report("Merging only possible for files with unmerged status ('U').");
6208                         return REQ_NONE;
6209                 }
6210                 open_mergetool(status->new.name);
6211                 break;
6213         case REQ_EDIT:
6214                 if (!status)
6215                         return request;
6216                 if (status->status == 'D') {
6217                         report("File has been deleted.");
6218                         return REQ_NONE;
6219                 }
6221                 open_editor(status->new.name);
6222                 break;
6224         case REQ_VIEW_BLAME:
6225                 if (status)
6226                         opt_ref[0] = 0;
6227                 return request;
6229         case REQ_ENTER:
6230                 /* After returning the status view has been split to
6231                  * show the stage view. No further reloading is
6232                  * necessary. */
6233                 return status_enter(view, line);
6235         case REQ_REFRESH:
6236                 /* Simply reload the view. */
6237                 break;
6239         default:
6240                 return request;
6241         }
6243         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
6245         return REQ_NONE;
6248 static void
6249 status_select(struct view *view, struct line *line)
6251         struct status *status = line->data;
6252         char file[SIZEOF_STR] = "all files";
6253         const char *text;
6254         const char *key;
6256         if (status && !string_format(file, "'%s'", status->new.name))
6257                 return;
6259         if (!status && line[1].type == LINE_STAT_NONE)
6260                 line++;
6262         switch (line->type) {
6263         case LINE_STAT_STAGED:
6264                 text = "Press %s to unstage %s for commit";
6265                 break;
6267         case LINE_STAT_UNSTAGED:
6268                 text = "Press %s to stage %s for commit";
6269                 break;
6271         case LINE_STAT_UNTRACKED:
6272                 text = "Press %s to stage %s for addition";
6273                 break;
6275         case LINE_STAT_HEAD:
6276         case LINE_STAT_NONE:
6277                 text = "Nothing to update";
6278                 break;
6280         default:
6281                 die("line type %d not handled in switch", line->type);
6282         }
6284         if (status && status->status == 'U') {
6285                 text = "Press %s to resolve conflict in %s";
6286                 key = get_key(KEYMAP_STATUS, REQ_STATUS_MERGE);
6288         } else {
6289                 key = get_key(KEYMAP_STATUS, REQ_STATUS_UPDATE);
6290         }
6292         string_format(view->ref, text, key, file);
6293         if (status)
6294                 string_copy(opt_file, status->new.name);
6297 static bool
6298 status_grep(struct view *view, struct line *line)
6300         struct status *status = line->data;
6302         if (status) {
6303                 const char buf[2] = { status->status, 0 };
6304                 const char *text[] = { status->new.name, buf, NULL };
6306                 return grep_text(view, text);
6307         }
6309         return FALSE;
6312 static struct view_ops status_ops = {
6313         "file",
6314         NULL,
6315         status_open,
6316         NULL,
6317         status_draw,
6318         status_request,
6319         status_grep,
6320         status_select,
6321 };
6324 static bool
6325 stage_diff_write(struct io *io, struct line *line, struct line *end)
6327         while (line < end) {
6328                 if (!io_write(io, line->data, strlen(line->data)) ||
6329                     !io_write(io, "\n", 1))
6330                         return FALSE;
6331                 line++;
6332                 if (line->type == LINE_DIFF_CHUNK ||
6333                     line->type == LINE_DIFF_HEADER)
6334                         break;
6335         }
6337         return TRUE;
6340 static struct line *
6341 stage_diff_find(struct view *view, struct line *line, enum line_type type)
6343         for (; view->line < line; line--)
6344                 if (line->type == type)
6345                         return line;
6347         return NULL;
6350 static bool
6351 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
6353         const char *apply_argv[SIZEOF_ARG] = {
6354                 "git", "apply", "--whitespace=nowarn", NULL
6355         };
6356         struct line *diff_hdr;
6357         struct io io;
6358         int argc = 3;
6360         diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
6361         if (!diff_hdr)
6362                 return FALSE;
6364         if (!revert)
6365                 apply_argv[argc++] = "--cached";
6366         if (revert || stage_line_type == LINE_STAT_STAGED)
6367                 apply_argv[argc++] = "-R";
6368         apply_argv[argc++] = "-";
6369         apply_argv[argc++] = NULL;
6370         if (!io_run(&io, IO_WR, opt_cdup, apply_argv))
6371                 return FALSE;
6373         if (!stage_diff_write(&io, diff_hdr, chunk) ||
6374             !stage_diff_write(&io, chunk, view->line + view->lines))
6375                 chunk = NULL;
6377         io_done(&io);
6378         io_run_bg(update_index_argv);
6380         return chunk ? TRUE : FALSE;
6383 static bool
6384 stage_update(struct view *view, struct line *line)
6386         struct line *chunk = NULL;
6388         if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
6389                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6391         if (chunk) {
6392                 if (!stage_apply_chunk(view, chunk, FALSE)) {
6393                         report("Failed to apply chunk");
6394                         return FALSE;
6395                 }
6397         } else if (!stage_status.status) {
6398                 view = VIEW(REQ_VIEW_STATUS);
6400                 for (line = view->line; line < view->line + view->lines; line++)
6401                         if (line->type == stage_line_type)
6402                                 break;
6404                 if (!status_update_files(view, line + 1)) {
6405                         report("Failed to update files");
6406                         return FALSE;
6407                 }
6409         } else if (!status_update_file(&stage_status, stage_line_type)) {
6410                 report("Failed to update file");
6411                 return FALSE;
6412         }
6414         return TRUE;
6417 static bool
6418 stage_revert(struct view *view, struct line *line)
6420         struct line *chunk = NULL;
6422         if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
6423                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6425         if (chunk) {
6426                 if (!prompt_yesno("Are you sure you want to revert changes?"))
6427                         return FALSE;
6429                 if (!stage_apply_chunk(view, chunk, TRUE)) {
6430                         report("Failed to revert chunk");
6431                         return FALSE;
6432                 }
6433                 return TRUE;
6435         } else {
6436                 return status_revert(stage_status.status ? &stage_status : NULL,
6437                                      stage_line_type, FALSE);
6438         }
6442 static void
6443 stage_next(struct view *view, struct line *line)
6445         int i;
6447         if (!stage_chunks) {
6448                 for (line = view->line; line < view->line + view->lines; line++) {
6449                         if (line->type != LINE_DIFF_CHUNK)
6450                                 continue;
6452                         if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
6453                                 report("Allocation failure");
6454                                 return;
6455                         }
6457                         stage_chunk[stage_chunks++] = line - view->line;
6458                 }
6459         }
6461         for (i = 0; i < stage_chunks; i++) {
6462                 if (stage_chunk[i] > view->lineno) {
6463                         do_scroll_view(view, stage_chunk[i] - view->lineno);
6464                         report("Chunk %d of %d", i + 1, stage_chunks);
6465                         return;
6466                 }
6467         }
6469         report("No next chunk found");
6472 static enum request
6473 stage_request(struct view *view, enum request request, struct line *line)
6475         switch (request) {
6476         case REQ_STATUS_UPDATE:
6477                 if (!stage_update(view, line))
6478                         return REQ_NONE;
6479                 break;
6481         case REQ_STATUS_REVERT:
6482                 if (!stage_revert(view, line))
6483                         return REQ_NONE;
6484                 break;
6486         case REQ_STAGE_NEXT:
6487                 if (stage_line_type == LINE_STAT_UNTRACKED) {
6488                         report("File is untracked; press %s to add",
6489                                get_key(KEYMAP_STAGE, REQ_STATUS_UPDATE));
6490                         return REQ_NONE;
6491                 }
6492                 stage_next(view, line);
6493                 return REQ_NONE;
6495         case REQ_EDIT:
6496                 if (!stage_status.new.name[0])
6497                         return request;
6498                 if (stage_status.status == 'D') {
6499                         report("File has been deleted.");
6500                         return REQ_NONE;
6501                 }
6503                 open_editor(stage_status.new.name);
6504                 break;
6506         case REQ_REFRESH:
6507                 /* Reload everything ... */
6508                 break;
6510         case REQ_VIEW_BLAME:
6511                 if (stage_status.new.name[0]) {
6512                         string_copy(opt_file, stage_status.new.name);
6513                         opt_ref[0] = 0;
6514                 }
6515                 return request;
6517         case REQ_ENTER:
6518                 return pager_request(view, request, line);
6520         default:
6521                 return request;
6522         }
6524         VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
6525         open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
6527         /* Check whether the staged entry still exists, and close the
6528          * stage view if it doesn't. */
6529         if (!status_exists(&stage_status, stage_line_type)) {
6530                 status_restore(VIEW(REQ_VIEW_STATUS));
6531                 return REQ_VIEW_CLOSE;
6532         }
6534         if (stage_line_type == LINE_STAT_UNTRACKED) {
6535                 if (!suffixcmp(stage_status.new.name, -1, "/")) {
6536                         report("Cannot display a directory");
6537                         return REQ_NONE;
6538                 }
6540                 if (!prepare_update_file(view, stage_status.new.name)) {
6541                         report("Failed to open file: %s", strerror(errno));
6542                         return REQ_NONE;
6543                 }
6544         }
6545         open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
6547         return REQ_NONE;
6550 static struct view_ops stage_ops = {
6551         "line",
6552         NULL,
6553         NULL,
6554         pager_read,
6555         pager_draw,
6556         stage_request,
6557         pager_grep,
6558         pager_select,
6559 };
6562 /*
6563  * Revision graph
6564  */
6566 struct commit {
6567         char id[SIZEOF_REV];            /* SHA1 ID. */
6568         char title[128];                /* First line of the commit message. */
6569         const char *author;             /* Author of the commit. */
6570         struct time time;               /* Date from the author ident. */
6571         struct ref_list *refs;          /* Repository references. */
6572         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
6573         size_t graph_size;              /* The width of the graph array. */
6574         bool has_parents;               /* Rewritten --parents seen. */
6575 };
6577 /* Size of rev graph with no  "padding" columns */
6578 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6580 struct rev_graph {
6581         struct rev_graph *prev, *next, *parents;
6582         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
6583         size_t size;
6584         struct commit *commit;
6585         size_t pos;
6586         unsigned int boundary:1;
6587 };
6589 /* Parents of the commit being visualized. */
6590 static struct rev_graph graph_parents[4];
6592 /* The current stack of revisions on the graph. */
6593 static struct rev_graph graph_stacks[4] = {
6594         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
6595         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
6596         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
6597         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
6598 };
6600 static inline bool
6601 graph_parent_is_merge(struct rev_graph *graph)
6603         return graph->parents->size > 1;
6606 static inline void
6607 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
6609         struct commit *commit = graph->commit;
6611         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
6612                 commit->graph[commit->graph_size++] = symbol;
6615 static void
6616 clear_rev_graph(struct rev_graph *graph)
6618         graph->boundary = 0;
6619         graph->size = graph->pos = 0;
6620         graph->commit = NULL;
6621         memset(graph->parents, 0, sizeof(*graph->parents));
6624 static void
6625 done_rev_graph(struct rev_graph *graph)
6627         if (graph_parent_is_merge(graph) &&
6628             graph->pos < graph->size - 1 &&
6629             graph->next->size == graph->size + graph->parents->size - 1) {
6630                 size_t i = graph->pos + graph->parents->size - 1;
6632                 graph->commit->graph_size = i * 2;
6633                 while (i < graph->next->size - 1) {
6634                         append_to_rev_graph(graph, ' ');
6635                         append_to_rev_graph(graph, '\\');
6636                         i++;
6637                 }
6638         }
6640         clear_rev_graph(graph);
6643 static void
6644 push_rev_graph(struct rev_graph *graph, const char *parent)
6646         int i;
6648         /* "Collapse" duplicate parents lines.
6649          *
6650          * FIXME: This needs to also update update the drawn graph but
6651          * for now it just serves as a method for pruning graph lines. */
6652         for (i = 0; i < graph->size; i++)
6653                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
6654                         return;
6656         if (graph->size < SIZEOF_REVITEMS) {
6657                 string_copy_rev(graph->rev[graph->size++], parent);
6658         }
6661 static chtype
6662 get_rev_graph_symbol(struct rev_graph *graph)
6664         chtype symbol;
6666         if (graph->boundary)
6667                 symbol = REVGRAPH_BOUND;
6668         else if (graph->parents->size == 0)
6669                 symbol = REVGRAPH_INIT;
6670         else if (graph_parent_is_merge(graph))
6671                 symbol = REVGRAPH_MERGE;
6672         else if (graph->pos >= graph->size)
6673                 symbol = REVGRAPH_BRANCH;
6674         else
6675                 symbol = REVGRAPH_COMMIT;
6677         return symbol;
6680 static void
6681 draw_rev_graph(struct rev_graph *graph)
6683         struct rev_filler {
6684                 chtype separator, line;
6685         };
6686         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
6687         static struct rev_filler fillers[] = {
6688                 { ' ',  '|' },
6689                 { '`',  '.' },
6690                 { '\'', ' ' },
6691                 { '/',  ' ' },
6692         };
6693         chtype symbol = get_rev_graph_symbol(graph);
6694         struct rev_filler *filler;
6695         size_t i;
6697         fillers[DEFAULT].line = opt_line_graphics ? ACS_VLINE : '|';
6698         filler = &fillers[DEFAULT];
6700         for (i = 0; i < graph->pos; i++) {
6701                 append_to_rev_graph(graph, filler->line);
6702                 if (graph_parent_is_merge(graph->prev) &&
6703                     graph->prev->pos == i)
6704                         filler = &fillers[RSHARP];
6706                 append_to_rev_graph(graph, filler->separator);
6707         }
6709         /* Place the symbol for this revision. */
6710         append_to_rev_graph(graph, symbol);
6712         if (graph->prev->size > graph->size)
6713                 filler = &fillers[RDIAG];
6714         else
6715                 filler = &fillers[DEFAULT];
6717         i++;
6719         for (; i < graph->size; i++) {
6720                 append_to_rev_graph(graph, filler->separator);
6721                 append_to_rev_graph(graph, filler->line);
6722                 if (graph_parent_is_merge(graph->prev) &&
6723                     i < graph->prev->pos + graph->parents->size)
6724                         filler = &fillers[RSHARP];
6725                 if (graph->prev->size > graph->size)
6726                         filler = &fillers[LDIAG];
6727         }
6729         if (graph->prev->size > graph->size) {
6730                 append_to_rev_graph(graph, filler->separator);
6731                 if (filler->line != ' ')
6732                         append_to_rev_graph(graph, filler->line);
6733         }
6736 /* Prepare the next rev graph */
6737 static void
6738 prepare_rev_graph(struct rev_graph *graph)
6740         size_t i;
6742         /* First, traverse all lines of revisions up to the active one. */
6743         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6744                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6745                         break;
6747                 push_rev_graph(graph->next, graph->rev[graph->pos]);
6748         }
6750         /* Interleave the new revision parent(s). */
6751         for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6752                 push_rev_graph(graph->next, graph->parents->rev[i]);
6754         /* Lastly, put any remaining revisions. */
6755         for (i = graph->pos + 1; i < graph->size; i++)
6756                 push_rev_graph(graph->next, graph->rev[i]);
6759 static void
6760 update_rev_graph(struct view *view, struct rev_graph *graph)
6762         /* If this is the finalizing update ... */
6763         if (graph->commit)
6764                 prepare_rev_graph(graph);
6766         /* Graph visualization needs a one rev look-ahead,
6767          * so the first update doesn't visualize anything. */
6768         if (!graph->prev->commit)
6769                 return;
6771         if (view->lines > 2)
6772                 view->line[view->lines - 3].dirty = 1;
6773         if (view->lines > 1)
6774                 view->line[view->lines - 2].dirty = 1;
6775         draw_rev_graph(graph->prev);
6776         done_rev_graph(graph->prev->prev);
6780 /*
6781  * Main view backend
6782  */
6784 static const char *main_argv[SIZEOF_ARG] = {
6785         "git", "log", "--no-color", "--pretty=raw", "--parents",
6786                 "--topo-order", "%(diffargs)", "%(revargs)",
6787                 "--", "%(fileargs)", NULL
6788 };
6790 static bool
6791 main_draw(struct view *view, struct line *line, unsigned int lineno)
6793         struct commit *commit = line->data;
6795         if (!commit->author)
6796                 return FALSE;
6798         if (opt_date && draw_date(view, &commit->time))
6799                 return TRUE;
6801         if (opt_author && draw_author(view, commit->author))
6802                 return TRUE;
6804         if (opt_rev_graph && commit->graph_size &&
6805             draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6806                 return TRUE;
6808         if (opt_show_refs && commit->refs) {
6809                 size_t i;
6811                 for (i = 0; i < commit->refs->size; i++) {
6812                         struct ref *ref = commit->refs->refs[i];
6813                         enum line_type type;
6815                         if (ref->head)
6816                                 type = LINE_MAIN_HEAD;
6817                         else if (ref->ltag)
6818                                 type = LINE_MAIN_LOCAL_TAG;
6819                         else if (ref->tag)
6820                                 type = LINE_MAIN_TAG;
6821                         else if (ref->tracked)
6822                                 type = LINE_MAIN_TRACKED;
6823                         else if (ref->remote)
6824                                 type = LINE_MAIN_REMOTE;
6825                         else
6826                                 type = LINE_MAIN_REF;
6828                         if (draw_text(view, type, "[", TRUE) ||
6829                             draw_text(view, type, ref->name, TRUE) ||
6830                             draw_text(view, type, "]", TRUE))
6831                                 return TRUE;
6833                         if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6834                                 return TRUE;
6835                 }
6836         }
6838         draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6839         return TRUE;
6842 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6843 static bool
6844 main_read(struct view *view, char *line)
6846         static struct rev_graph *graph = graph_stacks;
6847         enum line_type type;
6848         struct commit *commit;
6850         if (!line) {
6851                 int i;
6853                 if (!view->lines && !view->prev)
6854                         die("No revisions match the given arguments.");
6855                 if (view->lines > 0) {
6856                         commit = view->line[view->lines - 1].data;
6857                         view->line[view->lines - 1].dirty = 1;
6858                         if (!commit->author) {
6859                                 view->lines--;
6860                                 free(commit);
6861                                 graph->commit = NULL;
6862                         }
6863                 }
6864                 update_rev_graph(view, graph);
6866                 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6867                         clear_rev_graph(&graph_stacks[i]);
6868                 return TRUE;
6869         }
6871         type = get_line_type(line);
6872         if (type == LINE_COMMIT) {
6873                 commit = calloc(1, sizeof(struct commit));
6874                 if (!commit)
6875                         return FALSE;
6877                 line += STRING_SIZE("commit ");
6878                 if (*line == '-') {
6879                         graph->boundary = 1;
6880                         line++;
6881                 }
6883                 string_copy_rev(commit->id, line);
6884                 commit->refs = get_ref_list(commit->id);
6885                 graph->commit = commit;
6886                 add_line_data(view, commit, LINE_MAIN_COMMIT);
6888                 while ((line = strchr(line, ' '))) {
6889                         line++;
6890                         push_rev_graph(graph->parents, line);
6891                         commit->has_parents = TRUE;
6892                 }
6893                 return TRUE;
6894         }
6896         if (!view->lines)
6897                 return TRUE;
6898         commit = view->line[view->lines - 1].data;
6900         switch (type) {
6901         case LINE_PARENT:
6902                 if (commit->has_parents)
6903                         break;
6904                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6905                 break;
6907         case LINE_AUTHOR:
6908                 parse_author_line(line + STRING_SIZE("author "),
6909                                   &commit->author, &commit->time);
6910                 update_rev_graph(view, graph);
6911                 graph = graph->next;
6912                 break;
6914         default:
6915                 /* Fill in the commit title if it has not already been set. */
6916                 if (commit->title[0])
6917                         break;
6919                 /* Require titles to start with a non-space character at the
6920                  * offset used by git log. */
6921                 if (strncmp(line, "    ", 4))
6922                         break;
6923                 line += 4;
6924                 /* Well, if the title starts with a whitespace character,
6925                  * try to be forgiving.  Otherwise we end up with no title. */
6926                 while (isspace(*line))
6927                         line++;
6928                 if (*line == '\0')
6929                         break;
6930                 /* FIXME: More graceful handling of titles; append "..." to
6931                  * shortened titles, etc. */
6933                 string_expand(commit->title, sizeof(commit->title), line, 1);
6934                 view->line[view->lines - 1].dirty = 1;
6935         }
6937         return TRUE;
6940 static enum request
6941 main_request(struct view *view, enum request request, struct line *line)
6943         enum open_flags flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
6945         switch (request) {
6946         case REQ_ENTER:
6947                 if (view_is_displayed(view) && display[0] != view)
6948                         maximize_view(view);
6949                 open_view(view, REQ_VIEW_DIFF, flags);
6950                 break;
6951         case REQ_REFRESH:
6952                 load_refs();
6953                 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6954                 break;
6955         default:
6956                 return request;
6957         }
6959         return REQ_NONE;
6962 static bool
6963 grep_refs(struct ref_list *list, regex_t *regex)
6965         regmatch_t pmatch;
6966         size_t i;
6968         if (!opt_show_refs || !list)
6969                 return FALSE;
6971         for (i = 0; i < list->size; i++) {
6972                 if (regexec(regex, list->refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6973                         return TRUE;
6974         }
6976         return FALSE;
6979 static bool
6980 main_grep(struct view *view, struct line *line)
6982         struct commit *commit = line->data;
6983         const char *text[] = {
6984                 commit->title,
6985                 opt_author ? commit->author : "",
6986                 mkdate(&commit->time, opt_date),
6987                 NULL
6988         };
6990         return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6993 static void
6994 main_select(struct view *view, struct line *line)
6996         struct commit *commit = line->data;
6998         string_copy_rev(view->ref, commit->id);
6999         string_copy_rev(ref_commit, view->ref);
7002 static struct view_ops main_ops = {
7003         "commit",
7004         main_argv,
7005         NULL,
7006         main_read,
7007         main_draw,
7008         main_request,
7009         main_grep,
7010         main_select,
7011 };
7014 /*
7015  * Status management
7016  */
7018 /* Whether or not the curses interface has been initialized. */
7019 static bool cursed = FALSE;
7021 /* Terminal hacks and workarounds. */
7022 static bool use_scroll_redrawwin;
7023 static bool use_scroll_status_wclear;
7025 /* The status window is used for polling keystrokes. */
7026 static WINDOW *status_win;
7028 /* Reading from the prompt? */
7029 static bool input_mode = FALSE;
7031 static bool status_empty = FALSE;
7033 /* Update status and title window. */
7034 static void
7035 report(const char *msg, ...)
7037         struct view *view = display[current_view];
7039         if (input_mode)
7040                 return;
7042         if (!view) {
7043                 char buf[SIZEOF_STR];
7044                 va_list args;
7046                 va_start(args, msg);
7047                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
7048                         buf[sizeof(buf) - 1] = 0;
7049                         buf[sizeof(buf) - 2] = '.';
7050                         buf[sizeof(buf) - 3] = '.';
7051                         buf[sizeof(buf) - 4] = '.';
7052                 }
7053                 va_end(args);
7054                 die("%s", buf);
7055         }
7057         if (!status_empty || *msg) {
7058                 va_list args;
7060                 va_start(args, msg);
7062                 wmove(status_win, 0, 0);
7063                 if (view->has_scrolled && use_scroll_status_wclear)
7064                         wclear(status_win);
7065                 if (*msg) {
7066                         vwprintw(status_win, msg, args);
7067                         status_empty = FALSE;
7068                 } else {
7069                         status_empty = TRUE;
7070                 }
7071                 wclrtoeol(status_win);
7072                 wnoutrefresh(status_win);
7074                 va_end(args);
7075         }
7077         update_view_title(view);
7080 static void
7081 init_display(void)
7083         const char *term;
7084         int x, y;
7086         /* Initialize the curses library */
7087         if (isatty(STDIN_FILENO)) {
7088                 cursed = !!initscr();
7089                 opt_tty = stdin;
7090         } else {
7091                 /* Leave stdin and stdout alone when acting as a pager. */
7092                 opt_tty = fopen("/dev/tty", "r+");
7093                 if (!opt_tty)
7094                         die("Failed to open /dev/tty");
7095                 cursed = !!newterm(NULL, opt_tty, opt_tty);
7096         }
7098         if (!cursed)
7099                 die("Failed to initialize curses");
7101         nonl();         /* Disable conversion and detect newlines from input. */
7102         cbreak();       /* Take input chars one at a time, no wait for \n */
7103         noecho();       /* Don't echo input */
7104         leaveok(stdscr, FALSE);
7106         if (has_colors())
7107                 init_colors();
7109         getmaxyx(stdscr, y, x);
7110         status_win = newwin(1, 0, y - 1, 0);
7111         if (!status_win)
7112                 die("Failed to create status window");
7114         /* Enable keyboard mapping */
7115         keypad(status_win, TRUE);
7116         wbkgdset(status_win, get_line_attr(LINE_STATUS));
7118 #if defined(NCURSES_VERSION_PATCH) && (NCURSES_VERSION_PATCH >= 20080119)
7119         set_tabsize(opt_tab_size);
7120 #else
7121         TABSIZE = opt_tab_size;
7122 #endif
7124         term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
7125         if (term && !strcmp(term, "gnome-terminal")) {
7126                 /* In the gnome-terminal-emulator, the message from
7127                  * scrolling up one line when impossible followed by
7128                  * scrolling down one line causes corruption of the
7129                  * status line. This is fixed by calling wclear. */
7130                 use_scroll_status_wclear = TRUE;
7131                 use_scroll_redrawwin = FALSE;
7133         } else if (term && !strcmp(term, "xrvt-xpm")) {
7134                 /* No problems with full optimizations in xrvt-(unicode)
7135                  * and aterm. */
7136                 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
7138         } else {
7139                 /* When scrolling in (u)xterm the last line in the
7140                  * scrolling direction will update slowly. */
7141                 use_scroll_redrawwin = TRUE;
7142                 use_scroll_status_wclear = FALSE;
7143         }
7146 static int
7147 get_input(int prompt_position)
7149         struct view *view;
7150         int i, key, cursor_y, cursor_x;
7152         if (prompt_position)
7153                 input_mode = TRUE;
7155         while (TRUE) {
7156                 bool loading = FALSE;
7158                 foreach_view (view, i) {
7159                         update_view(view);
7160                         if (view_is_displayed(view) && view->has_scrolled &&
7161                             use_scroll_redrawwin)
7162                                 redrawwin(view->win);
7163                         view->has_scrolled = FALSE;
7164                         if (view->pipe)
7165                                 loading = TRUE;
7166                 }
7168                 /* Update the cursor position. */
7169                 if (prompt_position) {
7170                         getbegyx(status_win, cursor_y, cursor_x);
7171                         cursor_x = prompt_position;
7172                 } else {
7173                         view = display[current_view];
7174                         getbegyx(view->win, cursor_y, cursor_x);
7175                         cursor_x = view->width - 1;
7176                         cursor_y += view->lineno - view->offset;
7177                 }
7178                 setsyx(cursor_y, cursor_x);
7180                 /* Refresh, accept single keystroke of input */
7181                 doupdate();
7182                 nodelay(status_win, loading);
7183                 key = wgetch(status_win);
7185                 /* wgetch() with nodelay() enabled returns ERR when
7186                  * there's no input. */
7187                 if (key == ERR) {
7189                 } else if (key == KEY_RESIZE) {
7190                         int height, width;
7192                         getmaxyx(stdscr, height, width);
7194                         wresize(status_win, 1, width);
7195                         mvwin(status_win, height - 1, 0);
7196                         wnoutrefresh(status_win);
7197                         resize_display();
7198                         redraw_display(TRUE);
7200                 } else {
7201                         input_mode = FALSE;
7202                         return key;
7203                 }
7204         }
7207 static char *
7208 prompt_input(const char *prompt, input_handler handler, void *data)
7210         enum input_status status = INPUT_OK;
7211         static char buf[SIZEOF_STR];
7212         size_t pos = 0;
7214         buf[pos] = 0;
7216         while (status == INPUT_OK || status == INPUT_SKIP) {
7217                 int key;
7219                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
7220                 wclrtoeol(status_win);
7222                 key = get_input(pos + 1);
7223                 switch (key) {
7224                 case KEY_RETURN:
7225                 case KEY_ENTER:
7226                 case '\n':
7227                         status = pos ? INPUT_STOP : INPUT_CANCEL;
7228                         break;
7230                 case KEY_BACKSPACE:
7231                         if (pos > 0)
7232                                 buf[--pos] = 0;
7233                         else
7234                                 status = INPUT_CANCEL;
7235                         break;
7237                 case KEY_ESC:
7238                         status = INPUT_CANCEL;
7239                         break;
7241                 default:
7242                         if (pos >= sizeof(buf)) {
7243                                 report("Input string too long");
7244                                 return NULL;
7245                         }
7247                         status = handler(data, buf, key);
7248                         if (status == INPUT_OK)
7249                                 buf[pos++] = (char) key;
7250                 }
7251         }
7253         /* Clear the status window */
7254         status_empty = FALSE;
7255         report("");
7257         if (status == INPUT_CANCEL)
7258                 return NULL;
7260         buf[pos++] = 0;
7262         return buf;
7265 static enum input_status
7266 prompt_yesno_handler(void *data, char *buf, int c)
7268         if (c == 'y' || c == 'Y')
7269                 return INPUT_STOP;
7270         if (c == 'n' || c == 'N')
7271                 return INPUT_CANCEL;
7272         return INPUT_SKIP;
7275 static bool
7276 prompt_yesno(const char *prompt)
7278         char prompt2[SIZEOF_STR];
7280         if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
7281                 return FALSE;
7283         return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
7286 static enum input_status
7287 read_prompt_handler(void *data, char *buf, int c)
7289         return isprint(c) ? INPUT_OK : INPUT_SKIP;
7292 static char *
7293 read_prompt(const char *prompt)
7295         return prompt_input(prompt, read_prompt_handler, NULL);
7298 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected)
7300         enum input_status status = INPUT_OK;
7301         int size = 0;
7303         while (items[size].text)
7304                 size++;
7306         while (status == INPUT_OK) {
7307                 const struct menu_item *item = &items[*selected];
7308                 int key;
7309                 int i;
7311                 mvwprintw(status_win, 0, 0, "%s (%d of %d) ",
7312                           prompt, *selected + 1, size);
7313                 if (item->hotkey)
7314                         wprintw(status_win, "[%c] ", (char) item->hotkey);
7315                 wprintw(status_win, "%s", item->text);
7316                 wclrtoeol(status_win);
7318                 key = get_input(COLS - 1);
7319                 switch (key) {
7320                 case KEY_RETURN:
7321                 case KEY_ENTER:
7322                 case '\n':
7323                         status = INPUT_STOP;
7324                         break;
7326                 case KEY_LEFT:
7327                 case KEY_UP:
7328                         *selected = *selected - 1;
7329                         if (*selected < 0)
7330                                 *selected = size - 1;
7331                         break;
7333                 case KEY_RIGHT:
7334                 case KEY_DOWN:
7335                         *selected = (*selected + 1) % size;
7336                         break;
7338                 case KEY_ESC:
7339                         status = INPUT_CANCEL;
7340                         break;
7342                 default:
7343                         for (i = 0; items[i].text; i++)
7344                                 if (items[i].hotkey == key) {
7345                                         *selected = i;
7346                                         status = INPUT_STOP;
7347                                         break;
7348                                 }
7349                 }
7350         }
7352         /* Clear the status window */
7353         status_empty = FALSE;
7354         report("");
7356         return status != INPUT_CANCEL;
7359 /*
7360  * Repository properties
7361  */
7363 static struct ref **refs = NULL;
7364 static size_t refs_size = 0;
7365 static struct ref *refs_head = NULL;
7367 static struct ref_list **ref_lists = NULL;
7368 static size_t ref_lists_size = 0;
7370 DEFINE_ALLOCATOR(realloc_refs, struct ref *, 256)
7371 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
7372 DEFINE_ALLOCATOR(realloc_ref_lists, struct ref_list *, 8)
7374 static int
7375 compare_refs(const void *ref1_, const void *ref2_)
7377         const struct ref *ref1 = *(const struct ref **)ref1_;
7378         const struct ref *ref2 = *(const struct ref **)ref2_;
7380         if (ref1->tag != ref2->tag)
7381                 return ref2->tag - ref1->tag;
7382         if (ref1->ltag != ref2->ltag)
7383                 return ref2->ltag - ref2->ltag;
7384         if (ref1->head != ref2->head)
7385                 return ref2->head - ref1->head;
7386         if (ref1->tracked != ref2->tracked)
7387                 return ref2->tracked - ref1->tracked;
7388         if (ref1->remote != ref2->remote)
7389                 return ref2->remote - ref1->remote;
7390         return strcmp(ref1->name, ref2->name);
7393 static void
7394 foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data)
7396         size_t i;
7398         for (i = 0; i < refs_size; i++)
7399                 if (!visitor(data, refs[i]))
7400                         break;
7403 static struct ref *
7404 get_ref_head()
7406         return refs_head;
7409 static struct ref_list *
7410 get_ref_list(const char *id)
7412         struct ref_list *list;
7413         size_t i;
7415         for (i = 0; i < ref_lists_size; i++)
7416                 if (!strcmp(id, ref_lists[i]->id))
7417                         return ref_lists[i];
7419         if (!realloc_ref_lists(&ref_lists, ref_lists_size, 1))
7420                 return NULL;
7421         list = calloc(1, sizeof(*list));
7422         if (!list)
7423                 return NULL;
7425         for (i = 0; i < refs_size; i++) {
7426                 if (!strcmp(id, refs[i]->id) &&
7427                     realloc_refs_list(&list->refs, list->size, 1))
7428                         list->refs[list->size++] = refs[i];
7429         }
7431         if (!list->refs) {
7432                 free(list);
7433                 return NULL;
7434         }
7436         qsort(list->refs, list->size, sizeof(*list->refs), compare_refs);
7437         ref_lists[ref_lists_size++] = list;
7438         return list;
7441 static int
7442 read_ref(char *id, size_t idlen, char *name, size_t namelen, void *data)
7444         struct ref *ref = NULL;
7445         bool tag = FALSE;
7446         bool ltag = FALSE;
7447         bool remote = FALSE;
7448         bool tracked = FALSE;
7449         bool head = FALSE;
7450         int from = 0, to = refs_size - 1;
7452         if (!prefixcmp(name, "refs/tags/")) {
7453                 if (!suffixcmp(name, namelen, "^{}")) {
7454                         namelen -= 3;
7455                         name[namelen] = 0;
7456                 } else {
7457                         ltag = TRUE;
7458                 }
7460                 tag = TRUE;
7461                 namelen -= STRING_SIZE("refs/tags/");
7462                 name    += STRING_SIZE("refs/tags/");
7464         } else if (!prefixcmp(name, "refs/remotes/")) {
7465                 remote = TRUE;
7466                 namelen -= STRING_SIZE("refs/remotes/");
7467                 name    += STRING_SIZE("refs/remotes/");
7468                 tracked  = !strcmp(opt_remote, name);
7470         } else if (!prefixcmp(name, "refs/heads/")) {
7471                 namelen -= STRING_SIZE("refs/heads/");
7472                 name    += STRING_SIZE("refs/heads/");
7473                 if (!strncmp(opt_head, name, namelen))
7474                         return OK;
7476         } else if (!strcmp(name, "HEAD")) {
7477                 head     = TRUE;
7478                 if (*opt_head) {
7479                         namelen  = strlen(opt_head);
7480                         name     = opt_head;
7481                 }
7482         }
7484         /* If we are reloading or it's an annotated tag, replace the
7485          * previous SHA1 with the resolved commit id; relies on the fact
7486          * git-ls-remote lists the commit id of an annotated tag right
7487          * before the commit id it points to. */
7488         while (from <= to) {
7489                 size_t pos = (to + from) / 2;
7490                 int cmp = strcmp(name, refs[pos]->name);
7492                 if (!cmp) {
7493                         ref = refs[pos];
7494                         break;
7495                 }
7497                 if (cmp < 0)
7498                         to = pos - 1;
7499                 else
7500                         from = pos + 1;
7501         }
7503         if (!ref) {
7504                 if (!realloc_refs(&refs, refs_size, 1))
7505                         return ERR;
7506                 ref = calloc(1, sizeof(*ref) + namelen);
7507                 if (!ref)
7508                         return ERR;
7509                 memmove(refs + from + 1, refs + from,
7510                         (refs_size - from) * sizeof(*refs));
7511                 refs[from] = ref;
7512                 strncpy(ref->name, name, namelen);
7513                 refs_size++;
7514         }
7516         ref->head = head;
7517         ref->tag = tag;
7518         ref->ltag = ltag;
7519         ref->remote = remote;
7520         ref->tracked = tracked;
7521         string_copy_rev(ref->id, id);
7523         if (head)
7524                 refs_head = ref;
7525         return OK;
7528 static int
7529 load_refs(void)
7531         const char *head_argv[] = {
7532                 "git", "symbolic-ref", "HEAD", NULL
7533         };
7534         static const char *ls_remote_argv[SIZEOF_ARG] = {
7535                 "git", "ls-remote", opt_git_dir, NULL
7536         };
7537         static bool init = FALSE;
7538         size_t i;
7540         if (!init) {
7541                 if (!argv_from_env(ls_remote_argv, "TIG_LS_REMOTE"))
7542                         die("TIG_LS_REMOTE contains too many arguments");
7543                 init = TRUE;
7544         }
7546         if (!*opt_git_dir)
7547                 return OK;
7549         if (io_run_buf(head_argv, opt_head, sizeof(opt_head)) &&
7550             !prefixcmp(opt_head, "refs/heads/")) {
7551                 char *offset = opt_head + STRING_SIZE("refs/heads/");
7553                 memmove(opt_head, offset, strlen(offset) + 1);
7554         }
7556         refs_head = NULL;
7557         for (i = 0; i < refs_size; i++)
7558                 refs[i]->id[0] = 0;
7560         if (io_run_load(ls_remote_argv, "\t", read_ref, NULL) == ERR)
7561                 return ERR;
7563         /* Update the ref lists to reflect changes. */
7564         for (i = 0; i < ref_lists_size; i++) {
7565                 struct ref_list *list = ref_lists[i];
7566                 size_t old, new;
7568                 for (old = new = 0; old < list->size; old++)
7569                         if (!strcmp(list->id, list->refs[old]->id))
7570                                 list->refs[new++] = list->refs[old];
7571                 list->size = new;
7572         }
7574         return OK;
7577 static void
7578 set_remote_branch(const char *name, const char *value, size_t valuelen)
7580         if (!strcmp(name, ".remote")) {
7581                 string_ncopy(opt_remote, value, valuelen);
7583         } else if (*opt_remote && !strcmp(name, ".merge")) {
7584                 size_t from = strlen(opt_remote);
7586                 if (!prefixcmp(value, "refs/heads/"))
7587                         value += STRING_SIZE("refs/heads/");
7589                 if (!string_format_from(opt_remote, &from, "/%s", value))
7590                         opt_remote[0] = 0;
7591         }
7594 static void
7595 set_repo_config_option(char *name, char *value, enum option_code (*cmd)(int, const char **))
7597         const char *argv[SIZEOF_ARG] = { name, "=" };
7598         int argc = 1 + (cmd == option_set_command);
7599         enum option_code error;
7601         if (!argv_from_string(argv, &argc, value))
7602                 error = OPT_ERR_TOO_MANY_OPTION_ARGUMENTS;
7603         else
7604                 error = cmd(argc, argv);
7606         if (error != OPT_OK)
7607                 warn("Option 'tig.%s': %s", name, option_errors[error]);
7610 static bool
7611 set_environment_variable(const char *name, const char *value)
7613         size_t len = strlen(name) + 1 + strlen(value) + 1;
7614         char *env = malloc(len);
7616         if (env &&
7617             string_nformat(env, len, NULL, "%s=%s", name, value) &&
7618             putenv(env) == 0)
7619                 return TRUE;
7620         free(env);
7621         return FALSE;
7624 static void
7625 set_work_tree(const char *value)
7627         char cwd[SIZEOF_STR];
7629         if (!getcwd(cwd, sizeof(cwd)))
7630                 die("Failed to get cwd path: %s", strerror(errno));
7631         if (chdir(opt_git_dir) < 0)
7632                 die("Failed to chdir(%s): %s", strerror(errno));
7633         if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7634                 die("Failed to get git path: %s", strerror(errno));
7635         if (chdir(cwd) < 0)
7636                 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7637         if (chdir(value) < 0)
7638                 die("Failed to chdir(%s): %s", value, strerror(errno));
7639         if (!getcwd(cwd, sizeof(cwd)))
7640                 die("Failed to get cwd path: %s", strerror(errno));
7641         if (!set_environment_variable("GIT_WORK_TREE", cwd))
7642                 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7643         if (!set_environment_variable("GIT_DIR", opt_git_dir))
7644                 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7645         opt_is_inside_work_tree = TRUE;
7648 static int
7649 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen, void *data)
7651         if (!strcmp(name, "i18n.commitencoding"))
7652                 string_ncopy(opt_encoding, value, valuelen);
7654         else if (!strcmp(name, "core.editor"))
7655                 string_ncopy(opt_editor, value, valuelen);
7657         else if (!strcmp(name, "core.worktree"))
7658                 set_work_tree(value);
7660         else if (!prefixcmp(name, "tig.color."))
7661                 set_repo_config_option(name + 10, value, option_color_command);
7663         else if (!prefixcmp(name, "tig.bind."))
7664                 set_repo_config_option(name + 9, value, option_bind_command);
7666         else if (!prefixcmp(name, "tig."))
7667                 set_repo_config_option(name + 4, value, option_set_command);
7669         else if (*opt_head && !prefixcmp(name, "branch.") &&
7670                  !strncmp(name + 7, opt_head, strlen(opt_head)))
7671                 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7673         return OK;
7676 static int
7677 load_git_config(void)
7679         const char *config_list_argv[] = { "git", "config", "--list", NULL };
7681         return io_run_load(config_list_argv, "=", read_repo_config_option, NULL);
7684 static int
7685 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen, void *data)
7687         if (!opt_git_dir[0]) {
7688                 string_ncopy(opt_git_dir, name, namelen);
7690         } else if (opt_is_inside_work_tree == -1) {
7691                 /* This can be 3 different values depending on the
7692                  * version of git being used. If git-rev-parse does not
7693                  * understand --is-inside-work-tree it will simply echo
7694                  * the option else either "true" or "false" is printed.
7695                  * Default to true for the unknown case. */
7696                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7698         } else if (*name == '.') {
7699                 string_ncopy(opt_cdup, name, namelen);
7701         } else {
7702                 string_ncopy(opt_prefix, name, namelen);
7703         }
7705         return OK;
7708 static int
7709 load_repo_info(void)
7711         const char *rev_parse_argv[] = {
7712                 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7713                         "--show-cdup", "--show-prefix", NULL
7714         };
7716         return io_run_load(rev_parse_argv, "=", read_repo_info, NULL);
7720 /*
7721  * Main
7722  */
7724 static const char usage[] =
7725 "tig " TIG_VERSION " (" __DATE__ ")\n"
7726 "\n"
7727 "Usage: tig        [options] [revs] [--] [paths]\n"
7728 "   or: tig show   [options] [revs] [--] [paths]\n"
7729 "   or: tig blame  [rev] path\n"
7730 "   or: tig status\n"
7731 "   or: tig <      [git command output]\n"
7732 "\n"
7733 "Options:\n"
7734 "  -v, --version   Show version and exit\n"
7735 "  -h, --help      Show help message and exit";
7737 static void __NORETURN
7738 quit(int sig)
7740         /* XXX: Restore tty modes and let the OS cleanup the rest! */
7741         if (cursed)
7742                 endwin();
7743         exit(0);
7746 static void __NORETURN
7747 die(const char *err, ...)
7749         va_list args;
7751         endwin();
7753         va_start(args, err);
7754         fputs("tig: ", stderr);
7755         vfprintf(stderr, err, args);
7756         fputs("\n", stderr);
7757         va_end(args);
7759         exit(1);
7762 static void
7763 warn(const char *msg, ...)
7765         va_list args;
7767         va_start(args, msg);
7768         fputs("tig warning: ", stderr);
7769         vfprintf(stderr, msg, args);
7770         fputs("\n", stderr);
7771         va_end(args);
7774 static int
7775 read_filter_args(char *name, size_t namelen, char *value, size_t valuelen, void *data)
7777         const char ***filter_args = data;
7779         return argv_append(filter_args, name) ? OK : ERR;
7782 static void
7783 filter_rev_parse(const char ***args, const char *arg1, const char *arg2, const char *argv[])
7785         const char *rev_parse_argv[SIZEOF_ARG] = { "git", "rev-parse", arg1, arg2 };
7786         const char **all_argv = NULL;
7788         if (!argv_append_array(&all_argv, rev_parse_argv) ||
7789             !argv_append_array(&all_argv, argv) ||
7790             !io_run_load(all_argv, "\n", read_filter_args, args) == ERR)
7791                 die("Failed to split arguments");
7792         argv_free(all_argv);
7793         free(all_argv);
7796 static void
7797 filter_options(const char *argv[])
7799         filter_rev_parse(&opt_file_argv, "--no-revs", "--no-flags", argv);
7800         filter_rev_parse(&opt_diff_argv, "--no-revs", "--flags", argv);
7801         filter_rev_parse(&opt_rev_argv, "--symbolic", "--revs-only", argv);
7804 static enum request
7805 parse_options(int argc, const char *argv[])
7807         enum request request = REQ_VIEW_MAIN;
7808         const char *subcommand;
7809         bool seen_dashdash = FALSE;
7810         const char **filter_argv = NULL;
7811         int i;
7813         if (!isatty(STDIN_FILENO))
7814                 return REQ_VIEW_PAGER;
7816         if (argc <= 1)
7817                 return REQ_VIEW_MAIN;
7819         subcommand = argv[1];
7820         if (!strcmp(subcommand, "status")) {
7821                 if (argc > 2)
7822                         warn("ignoring arguments after `%s'", subcommand);
7823                 return REQ_VIEW_STATUS;
7825         } else if (!strcmp(subcommand, "blame")) {
7826                 if (argc <= 2 || argc > 4)
7827                         die("invalid number of options to blame\n\n%s", usage);
7829                 i = 2;
7830                 if (argc == 4) {
7831                         string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7832                         i++;
7833                 }
7835                 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7836                 return REQ_VIEW_BLAME;
7838         } else if (!strcmp(subcommand, "show")) {
7839                 request = REQ_VIEW_DIFF;
7841         } else {
7842                 subcommand = NULL;
7843         }
7845         for (i = 1 + !!subcommand; i < argc; i++) {
7846                 const char *opt = argv[i];
7848                 if (seen_dashdash) {
7849                         argv_append(&opt_file_argv, opt);
7850                         continue;
7852                 } else if (!strcmp(opt, "--")) {
7853                         seen_dashdash = TRUE;
7854                         continue;
7856                 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7857                         printf("tig version %s\n", TIG_VERSION);
7858                         quit(0);
7860                 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7861                         printf("%s\n", usage);
7862                         quit(0);
7864                 } else if (!strcmp(opt, "--all")) {
7865                         argv_append(&opt_rev_argv, opt);
7866                         continue;
7867                 }
7869                 if (!argv_append(&filter_argv, opt))
7870                         die("command too long");
7871         }
7873         if (filter_argv)
7874                 filter_options(filter_argv);
7876         return request;
7879 int
7880 main(int argc, const char *argv[])
7882         const char *codeset = "UTF-8";
7883         enum request request = parse_options(argc, argv);
7884         struct view *view;
7885         size_t i;
7887         signal(SIGINT, quit);
7888         signal(SIGPIPE, SIG_IGN);
7890         if (setlocale(LC_ALL, "")) {
7891                 codeset = nl_langinfo(CODESET);
7892         }
7894         if (load_repo_info() == ERR)
7895                 die("Failed to load repo info.");
7897         if (load_options() == ERR)
7898                 die("Failed to load user config.");
7900         if (load_git_config() == ERR)
7901                 die("Failed to load repo config.");
7903         /* Require a git repository unless when running in pager mode. */
7904         if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7905                 die("Not a git repository");
7907         if (*opt_encoding && strcmp(codeset, "UTF-8")) {
7908                 opt_iconv_in = iconv_open("UTF-8", opt_encoding);
7909                 if (opt_iconv_in == ICONV_NONE)
7910                         die("Failed to initialize character set conversion");
7911         }
7913         if (codeset && strcmp(codeset, "UTF-8")) {
7914                 opt_iconv_out = iconv_open(codeset, "UTF-8");
7915                 if (opt_iconv_out == ICONV_NONE)
7916                         die("Failed to initialize character set conversion");
7917         }
7919         if (load_refs() == ERR)
7920                 die("Failed to load refs.");
7922         foreach_view (view, i) {
7923                 if (getenv(view->cmd_env))
7924                         warn("Use of the %s environment variable is deprecated,"
7925                              " use options or TIG_DIFF_ARGS instead",
7926                              view->cmd_env);
7927                 if (!argv_from_env(view->ops->argv, view->cmd_env))
7928                         die("Too many arguments in the `%s` environment variable",
7929                             view->cmd_env);
7930         }
7932         init_display();
7934         while (view_driver(display[current_view], request)) {
7935                 int key = get_input(0);
7937                 view = display[current_view];
7938                 request = get_keybinding(view->keymap, key);
7940                 /* Some low-level request handling. This keeps access to
7941                  * status_win restricted. */
7942                 switch (request) {
7943                 case REQ_NONE:
7944                         report("Unknown key, press %s for help",
7945                                get_key(view->keymap, REQ_VIEW_HELP));
7946                         break;
7947                 case REQ_PROMPT:
7948                 {
7949                         char *cmd = read_prompt(":");
7951                         if (cmd && isdigit(*cmd)) {
7952                                 int lineno = view->lineno + 1;
7954                                 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7955                                         select_view_line(view, lineno - 1);
7956                                         report("");
7957                                 } else {
7958                                         report("Unable to parse '%s' as a line number", cmd);
7959                                 }
7961                         } else if (cmd) {
7962                                 struct view *next = VIEW(REQ_VIEW_PAGER);
7963                                 const char *argv[SIZEOF_ARG] = { "git" };
7964                                 int argc = 1;
7966                                 /* When running random commands, initially show the
7967                                  * command in the title. However, it maybe later be
7968                                  * overwritten if a commit line is selected. */
7969                                 string_ncopy(next->ref, cmd, strlen(cmd));
7971                                 if (!argv_from_string(argv, &argc, cmd)) {
7972                                         report("Too many arguments");
7973                                 } else if (!prepare_update(next, argv, NULL)) {
7974                                         report("Failed to format command");
7975                                 } else {
7976                                         open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7977                                 }
7978                         }
7980                         request = REQ_NONE;
7981                         break;
7982                 }
7983                 case REQ_SEARCH:
7984                 case REQ_SEARCH_BACK:
7985                 {
7986                         const char *prompt = request == REQ_SEARCH ? "/" : "?";
7987                         char *search = read_prompt(prompt);
7989                         if (search)
7990                                 string_ncopy(opt_search, search, strlen(search));
7991                         else if (*opt_search)
7992                                 request = request == REQ_SEARCH ?
7993                                         REQ_FIND_NEXT :
7994                                         REQ_FIND_PREV;
7995                         else
7996                                 request = REQ_NONE;
7997                         break;
7998                 }
7999                 default:
8000                         break;
8001                 }
8002         }
8004         quit(0);
8006         return 0;