Code

80c4d7a6c7994725092b0aec0dd644f3a5ea4df4
[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 static int
1048 io_load(struct io *io, const char *separators,
1049         int (*read_property)(char *, size_t, char *, size_t))
1051         char *name;
1052         int state = OK;
1054         while (state == OK && (name = io_get(io, '\n', TRUE))) {
1055                 char *value;
1056                 size_t namelen;
1057                 size_t valuelen;
1059                 name = chomp_string(name);
1060                 namelen = strcspn(name, separators);
1062                 if (name[namelen]) {
1063                         name[namelen] = 0;
1064                         value = chomp_string(name + namelen + 1);
1065                         valuelen = strlen(value);
1067                 } else {
1068                         value = "";
1069                         valuelen = 0;
1070                 }
1072                 state = read_property(name, namelen, value, valuelen);
1073         }
1075         if (state != ERR && io_error(io))
1076                 state = ERR;
1077         io_done(io);
1079         return state;
1082 static int
1083 io_run_load(const char **argv, const char *separators,
1084             int (*read_property)(char *, size_t, char *, size_t))
1086         struct io io;
1088         if (!io_run(&io, IO_RD, NULL, argv))
1089                 return ERR;
1090         return io_load(&io, separators, read_property);
1094 /*
1095  * User requests
1096  */
1098 #define REQ_INFO \
1099         /* XXX: Keep the view request first and in sync with views[]. */ \
1100         REQ_GROUP("View switching") \
1101         REQ_(VIEW_MAIN,         "Show main view"), \
1102         REQ_(VIEW_DIFF,         "Show diff view"), \
1103         REQ_(VIEW_LOG,          "Show log view"), \
1104         REQ_(VIEW_TREE,         "Show tree view"), \
1105         REQ_(VIEW_BLOB,         "Show blob view"), \
1106         REQ_(VIEW_BLAME,        "Show blame view"), \
1107         REQ_(VIEW_BRANCH,       "Show branch view"), \
1108         REQ_(VIEW_HELP,         "Show help page"), \
1109         REQ_(VIEW_PAGER,        "Show pager view"), \
1110         REQ_(VIEW_STATUS,       "Show status view"), \
1111         REQ_(VIEW_STAGE,        "Show stage view"), \
1112         \
1113         REQ_GROUP("View manipulation") \
1114         REQ_(ENTER,             "Enter current line and scroll"), \
1115         REQ_(NEXT,              "Move to next"), \
1116         REQ_(PREVIOUS,          "Move to previous"), \
1117         REQ_(PARENT,            "Move to parent"), \
1118         REQ_(VIEW_NEXT,         "Move focus to next view"), \
1119         REQ_(REFRESH,           "Reload and refresh"), \
1120         REQ_(MAXIMIZE,          "Maximize the current view"), \
1121         REQ_(VIEW_CLOSE,        "Close the current view"), \
1122         REQ_(QUIT,              "Close all views and quit"), \
1123         \
1124         REQ_GROUP("View specific requests") \
1125         REQ_(STATUS_UPDATE,     "Update file status"), \
1126         REQ_(STATUS_REVERT,     "Revert file changes"), \
1127         REQ_(STATUS_MERGE,      "Merge file using external tool"), \
1128         REQ_(STAGE_NEXT,        "Find next chunk to stage"), \
1129         \
1130         REQ_GROUP("Cursor navigation") \
1131         REQ_(MOVE_UP,           "Move cursor one line up"), \
1132         REQ_(MOVE_DOWN,         "Move cursor one line down"), \
1133         REQ_(MOVE_PAGE_DOWN,    "Move cursor one page down"), \
1134         REQ_(MOVE_PAGE_UP,      "Move cursor one page up"), \
1135         REQ_(MOVE_FIRST_LINE,   "Move cursor to first line"), \
1136         REQ_(MOVE_LAST_LINE,    "Move cursor to last line"), \
1137         \
1138         REQ_GROUP("Scrolling") \
1139         REQ_(SCROLL_FIRST_COL,  "Scroll to the first line columns"), \
1140         REQ_(SCROLL_LEFT,       "Scroll two columns left"), \
1141         REQ_(SCROLL_RIGHT,      "Scroll two columns right"), \
1142         REQ_(SCROLL_LINE_UP,    "Scroll one line up"), \
1143         REQ_(SCROLL_LINE_DOWN,  "Scroll one line down"), \
1144         REQ_(SCROLL_PAGE_UP,    "Scroll one page up"), \
1145         REQ_(SCROLL_PAGE_DOWN,  "Scroll one page down"), \
1146         \
1147         REQ_GROUP("Searching") \
1148         REQ_(SEARCH,            "Search the view"), \
1149         REQ_(SEARCH_BACK,       "Search backwards in the view"), \
1150         REQ_(FIND_NEXT,         "Find next search match"), \
1151         REQ_(FIND_PREV,         "Find previous search match"), \
1152         \
1153         REQ_GROUP("Option manipulation") \
1154         REQ_(OPTIONS,           "Open option menu"), \
1155         REQ_(TOGGLE_LINENO,     "Toggle line numbers"), \
1156         REQ_(TOGGLE_DATE,       "Toggle date display"), \
1157         REQ_(TOGGLE_AUTHOR,     "Toggle author display"), \
1158         REQ_(TOGGLE_REV_GRAPH,  "Toggle revision graph visualization"), \
1159         REQ_(TOGGLE_REFS,       "Toggle reference display (tags/branches)"), \
1160         REQ_(TOGGLE_SORT_ORDER, "Toggle ascending/descending sort order"), \
1161         REQ_(TOGGLE_SORT_FIELD, "Toggle field to sort by"), \
1162         \
1163         REQ_GROUP("Misc") \
1164         REQ_(PROMPT,            "Bring up the prompt"), \
1165         REQ_(SCREEN_REDRAW,     "Redraw the screen"), \
1166         REQ_(SHOW_VERSION,      "Show version information"), \
1167         REQ_(STOP_LOADING,      "Stop all loading views"), \
1168         REQ_(EDIT,              "Open in editor"), \
1169         REQ_(NONE,              "Do nothing")
1172 /* User action requests. */
1173 enum request {
1174 #define REQ_GROUP(help)
1175 #define REQ_(req, help) REQ_##req
1177         /* Offset all requests to avoid conflicts with ncurses getch values. */
1178         REQ_UNKNOWN = KEY_MAX + 1,
1179         REQ_OFFSET,
1180         REQ_INFO
1182 #undef  REQ_GROUP
1183 #undef  REQ_
1184 };
1186 struct request_info {
1187         enum request request;
1188         const char *name;
1189         int namelen;
1190         const char *help;
1191 };
1193 static const struct request_info req_info[] = {
1194 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
1195 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
1196         REQ_INFO
1197 #undef  REQ_GROUP
1198 #undef  REQ_
1199 };
1201 static enum request
1202 get_request(const char *name)
1204         int namelen = strlen(name);
1205         int i;
1207         for (i = 0; i < ARRAY_SIZE(req_info); i++)
1208                 if (enum_equals(req_info[i], name, namelen))
1209                         return req_info[i].request;
1211         return REQ_UNKNOWN;
1215 /*
1216  * Options
1217  */
1219 /* Option and state variables. */
1220 static enum date opt_date               = DATE_DEFAULT;
1221 static enum author opt_author           = AUTHOR_DEFAULT;
1222 static bool opt_line_number             = FALSE;
1223 static bool opt_line_graphics           = TRUE;
1224 static bool opt_rev_graph               = FALSE;
1225 static bool opt_show_refs               = TRUE;
1226 static bool opt_untracked_dirs_content  = TRUE;
1227 static int opt_num_interval             = 5;
1228 static double opt_hscroll               = 0.50;
1229 static double opt_scale_split_view      = 2.0 / 3.0;
1230 static int opt_tab_size                 = 8;
1231 static int opt_author_cols              = AUTHOR_COLS;
1232 static char opt_path[SIZEOF_STR]        = "";
1233 static char opt_file[SIZEOF_STR]        = "";
1234 static char opt_ref[SIZEOF_REF]         = "";
1235 static char opt_head[SIZEOF_REF]        = "";
1236 static char opt_remote[SIZEOF_REF]      = "";
1237 static char opt_encoding[20]            = "UTF-8";
1238 static iconv_t opt_iconv_in             = ICONV_NONE;
1239 static iconv_t opt_iconv_out            = ICONV_NONE;
1240 static char opt_search[SIZEOF_STR]      = "";
1241 static char opt_cdup[SIZEOF_STR]        = "";
1242 static char opt_prefix[SIZEOF_STR]      = "";
1243 static char opt_git_dir[SIZEOF_STR]     = "";
1244 static signed char opt_is_inside_work_tree      = -1; /* set to TRUE or FALSE */
1245 static char opt_editor[SIZEOF_STR]      = "";
1246 static FILE *opt_tty                    = NULL;
1247 static const char **opt_diff_args       = NULL;
1248 static const char **opt_rev_args        = NULL;
1249 static const char **opt_file_args       = NULL;
1251 #define is_initial_commit()     (!get_ref_head())
1252 #define is_head_commit(rev)     (!strcmp((rev), "HEAD") || (get_ref_head() && !strcmp(rev, get_ref_head()->id)))
1255 /*
1256  * Line-oriented content detection.
1257  */
1259 #define LINE_INFO \
1260 LINE(DIFF_HEADER,  "diff --git ",       COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1261 LINE(DIFF_CHUNK,   "@@",                COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1262 LINE(DIFF_ADD,     "+",                 COLOR_GREEN,    COLOR_DEFAULT,  0), \
1263 LINE(DIFF_DEL,     "-",                 COLOR_RED,      COLOR_DEFAULT,  0), \
1264 LINE(DIFF_INDEX,        "index ",         COLOR_BLUE,   COLOR_DEFAULT,  0), \
1265 LINE(DIFF_OLDMODE,      "old file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1266 LINE(DIFF_NEWMODE,      "new file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1267 LINE(DIFF_COPY_FROM,    "copy from",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
1268 LINE(DIFF_COPY_TO,      "copy to",        COLOR_YELLOW, COLOR_DEFAULT,  0), \
1269 LINE(DIFF_RENAME_FROM,  "rename from",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
1270 LINE(DIFF_RENAME_TO,    "rename to",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
1271 LINE(DIFF_SIMILARITY,   "similarity ",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
1272 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1273 LINE(DIFF_TREE,         "diff-tree ",     COLOR_BLUE,   COLOR_DEFAULT,  0), \
1274 LINE(PP_AUTHOR,    "Author: ",          COLOR_CYAN,     COLOR_DEFAULT,  0), \
1275 LINE(PP_COMMIT,    "Commit: ",          COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1276 LINE(PP_MERGE,     "Merge: ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
1277 LINE(PP_DATE,      "Date:   ",          COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1278 LINE(PP_ADATE,     "AuthorDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1279 LINE(PP_CDATE,     "CommitDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1280 LINE(PP_REFS,      "Refs: ",            COLOR_RED,      COLOR_DEFAULT,  0), \
1281 LINE(COMMIT,       "commit ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
1282 LINE(PARENT,       "parent ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
1283 LINE(TREE,         "tree ",             COLOR_BLUE,     COLOR_DEFAULT,  0), \
1284 LINE(AUTHOR,       "author ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
1285 LINE(COMMITTER,    "committer ",        COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1286 LINE(SIGNOFF,      "    Signed-off-by", COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1287 LINE(ACKED,        "    Acked-by",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1288 LINE(TESTED,       "    Tested-by",     COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1289 LINE(REVIEWED,     "    Reviewed-by",   COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1290 LINE(DEFAULT,      "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
1291 LINE(CURSOR,       "",                  COLOR_WHITE,    COLOR_GREEN,    A_BOLD), \
1292 LINE(STATUS,       "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
1293 LINE(DELIMITER,    "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1294 LINE(DATE,         "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
1295 LINE(MODE,         "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1296 LINE(LINE_NUMBER,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1297 LINE(TITLE_BLUR,   "",                  COLOR_WHITE,    COLOR_BLUE,     0), \
1298 LINE(TITLE_FOCUS,  "",                  COLOR_WHITE,    COLOR_BLUE,     A_BOLD), \
1299 LINE(MAIN_COMMIT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
1300 LINE(MAIN_TAG,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
1301 LINE(MAIN_LOCAL_TAG,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1302 LINE(MAIN_REMOTE,  "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1303 LINE(MAIN_TRACKED, "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_BOLD), \
1304 LINE(MAIN_REF,     "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1305 LINE(MAIN_HEAD,    "",                  COLOR_CYAN,     COLOR_DEFAULT,  A_BOLD), \
1306 LINE(MAIN_REVGRAPH,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1307 LINE(TREE_HEAD,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_BOLD), \
1308 LINE(TREE_DIR,     "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_NORMAL), \
1309 LINE(TREE_FILE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
1310 LINE(STAT_HEAD,    "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1311 LINE(STAT_SECTION, "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1312 LINE(STAT_NONE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
1313 LINE(STAT_STAGED,  "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1314 LINE(STAT_UNSTAGED,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1315 LINE(STAT_UNTRACKED,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1316 LINE(HELP_KEYMAP,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1317 LINE(HELP_GROUP,   "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
1318 LINE(BLAME_ID,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0)
1320 enum line_type {
1321 #define LINE(type, line, fg, bg, attr) \
1322         LINE_##type
1323         LINE_INFO,
1324         LINE_NONE
1325 #undef  LINE
1326 };
1328 struct line_info {
1329         const char *name;       /* Option name. */
1330         int namelen;            /* Size of option name. */
1331         const char *line;       /* The start of line to match. */
1332         int linelen;            /* Size of string to match. */
1333         int fg, bg, attr;       /* Color and text attributes for the lines. */
1334 };
1336 static struct line_info line_info[] = {
1337 #define LINE(type, line, fg, bg, attr) \
1338         { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1339         LINE_INFO
1340 #undef  LINE
1341 };
1343 static enum line_type
1344 get_line_type(const char *line)
1346         int linelen = strlen(line);
1347         enum line_type type;
1349         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1350                 /* Case insensitive search matches Signed-off-by lines better. */
1351                 if (linelen >= line_info[type].linelen &&
1352                     !strncasecmp(line_info[type].line, line, line_info[type].linelen))
1353                         return type;
1355         return LINE_DEFAULT;
1358 static inline int
1359 get_line_attr(enum line_type type)
1361         assert(type < ARRAY_SIZE(line_info));
1362         return COLOR_PAIR(type) | line_info[type].attr;
1365 static struct line_info *
1366 get_line_info(const char *name)
1368         size_t namelen = strlen(name);
1369         enum line_type type;
1371         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1372                 if (enum_equals(line_info[type], name, namelen))
1373                         return &line_info[type];
1375         return NULL;
1378 static void
1379 init_colors(void)
1381         int default_bg = line_info[LINE_DEFAULT].bg;
1382         int default_fg = line_info[LINE_DEFAULT].fg;
1383         enum line_type type;
1385         start_color();
1387         if (assume_default_colors(default_fg, default_bg) == ERR) {
1388                 default_bg = COLOR_BLACK;
1389                 default_fg = COLOR_WHITE;
1390         }
1392         for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1393                 struct line_info *info = &line_info[type];
1394                 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1395                 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1397                 init_pair(type, fg, bg);
1398         }
1401 struct line {
1402         enum line_type type;
1404         /* State flags */
1405         unsigned int selected:1;
1406         unsigned int dirty:1;
1407         unsigned int cleareol:1;
1408         unsigned int other:16;
1410         void *data;             /* User data */
1411 };
1414 /*
1415  * Keys
1416  */
1418 struct keybinding {
1419         int alias;
1420         enum request request;
1421 };
1423 static struct keybinding default_keybindings[] = {
1424         /* View switching */
1425         { 'm',          REQ_VIEW_MAIN },
1426         { 'd',          REQ_VIEW_DIFF },
1427         { 'l',          REQ_VIEW_LOG },
1428         { 't',          REQ_VIEW_TREE },
1429         { 'f',          REQ_VIEW_BLOB },
1430         { 'B',          REQ_VIEW_BLAME },
1431         { 'H',          REQ_VIEW_BRANCH },
1432         { 'p',          REQ_VIEW_PAGER },
1433         { 'h',          REQ_VIEW_HELP },
1434         { 'S',          REQ_VIEW_STATUS },
1435         { 'c',          REQ_VIEW_STAGE },
1437         /* View manipulation */
1438         { 'q',          REQ_VIEW_CLOSE },
1439         { KEY_TAB,      REQ_VIEW_NEXT },
1440         { KEY_RETURN,   REQ_ENTER },
1441         { KEY_UP,       REQ_PREVIOUS },
1442         { KEY_CTL('P'), REQ_PREVIOUS },
1443         { KEY_DOWN,     REQ_NEXT },
1444         { KEY_CTL('N'), REQ_NEXT },
1445         { 'R',          REQ_REFRESH },
1446         { KEY_F(5),     REQ_REFRESH },
1447         { 'O',          REQ_MAXIMIZE },
1449         /* Cursor navigation */
1450         { 'k',          REQ_MOVE_UP },
1451         { 'j',          REQ_MOVE_DOWN },
1452         { KEY_HOME,     REQ_MOVE_FIRST_LINE },
1453         { KEY_END,      REQ_MOVE_LAST_LINE },
1454         { KEY_NPAGE,    REQ_MOVE_PAGE_DOWN },
1455         { KEY_CTL('D'), REQ_MOVE_PAGE_DOWN },
1456         { ' ',          REQ_MOVE_PAGE_DOWN },
1457         { KEY_PPAGE,    REQ_MOVE_PAGE_UP },
1458         { KEY_CTL('U'), REQ_MOVE_PAGE_UP },
1459         { 'b',          REQ_MOVE_PAGE_UP },
1460         { '-',          REQ_MOVE_PAGE_UP },
1462         /* Scrolling */
1463         { '|',          REQ_SCROLL_FIRST_COL },
1464         { KEY_LEFT,     REQ_SCROLL_LEFT },
1465         { KEY_RIGHT,    REQ_SCROLL_RIGHT },
1466         { KEY_IC,       REQ_SCROLL_LINE_UP },
1467         { KEY_CTL('Y'), REQ_SCROLL_LINE_UP },
1468         { KEY_DC,       REQ_SCROLL_LINE_DOWN },
1469         { KEY_CTL('E'), REQ_SCROLL_LINE_DOWN },
1470         { 'w',          REQ_SCROLL_PAGE_UP },
1471         { 's',          REQ_SCROLL_PAGE_DOWN },
1473         /* Searching */
1474         { '/',          REQ_SEARCH },
1475         { '?',          REQ_SEARCH_BACK },
1476         { 'n',          REQ_FIND_NEXT },
1477         { 'N',          REQ_FIND_PREV },
1479         /* Misc */
1480         { 'Q',          REQ_QUIT },
1481         { 'z',          REQ_STOP_LOADING },
1482         { 'v',          REQ_SHOW_VERSION },
1483         { 'r',          REQ_SCREEN_REDRAW },
1484         { KEY_CTL('L'), REQ_SCREEN_REDRAW },
1485         { 'o',          REQ_OPTIONS },
1486         { '.',          REQ_TOGGLE_LINENO },
1487         { 'D',          REQ_TOGGLE_DATE },
1488         { 'A',          REQ_TOGGLE_AUTHOR },
1489         { 'g',          REQ_TOGGLE_REV_GRAPH },
1490         { 'F',          REQ_TOGGLE_REFS },
1491         { 'I',          REQ_TOGGLE_SORT_ORDER },
1492         { 'i',          REQ_TOGGLE_SORT_FIELD },
1493         { ':',          REQ_PROMPT },
1494         { 'u',          REQ_STATUS_UPDATE },
1495         { '!',          REQ_STATUS_REVERT },
1496         { 'M',          REQ_STATUS_MERGE },
1497         { '@',          REQ_STAGE_NEXT },
1498         { ',',          REQ_PARENT },
1499         { 'e',          REQ_EDIT },
1500 };
1502 #define KEYMAP_INFO \
1503         KEYMAP_(GENERIC), \
1504         KEYMAP_(MAIN), \
1505         KEYMAP_(DIFF), \
1506         KEYMAP_(LOG), \
1507         KEYMAP_(TREE), \
1508         KEYMAP_(BLOB), \
1509         KEYMAP_(BLAME), \
1510         KEYMAP_(BRANCH), \
1511         KEYMAP_(PAGER), \
1512         KEYMAP_(HELP), \
1513         KEYMAP_(STATUS), \
1514         KEYMAP_(STAGE)
1516 enum keymap {
1517 #define KEYMAP_(name) KEYMAP_##name
1518         KEYMAP_INFO
1519 #undef  KEYMAP_
1520 };
1522 static const struct enum_map keymap_table[] = {
1523 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1524         KEYMAP_INFO
1525 #undef  KEYMAP_
1526 };
1528 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1530 struct keybinding_table {
1531         struct keybinding *data;
1532         size_t size;
1533 };
1535 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1537 static void
1538 add_keybinding(enum keymap keymap, enum request request, int key)
1540         struct keybinding_table *table = &keybindings[keymap];
1541         size_t i;
1543         for (i = 0; i < keybindings[keymap].size; i++) {
1544                 if (keybindings[keymap].data[i].alias == key) {
1545                         keybindings[keymap].data[i].request = request;
1546                         return;
1547                 }
1548         }
1550         table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1551         if (!table->data)
1552                 die("Failed to allocate keybinding");
1553         table->data[table->size].alias = key;
1554         table->data[table->size++].request = request;
1556         if (request == REQ_NONE && keymap == KEYMAP_GENERIC) {
1557                 int i;
1559                 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1560                         if (default_keybindings[i].alias == key)
1561                                 default_keybindings[i].request = REQ_NONE;
1562         }
1565 /* Looks for a key binding first in the given map, then in the generic map, and
1566  * lastly in the default keybindings. */
1567 static enum request
1568 get_keybinding(enum keymap keymap, int key)
1570         size_t i;
1572         for (i = 0; i < keybindings[keymap].size; i++)
1573                 if (keybindings[keymap].data[i].alias == key)
1574                         return keybindings[keymap].data[i].request;
1576         for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1577                 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1578                         return keybindings[KEYMAP_GENERIC].data[i].request;
1580         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1581                 if (default_keybindings[i].alias == key)
1582                         return default_keybindings[i].request;
1584         return (enum request) key;
1588 struct key {
1589         const char *name;
1590         int value;
1591 };
1593 static const struct key key_table[] = {
1594         { "Enter",      KEY_RETURN },
1595         { "Space",      ' ' },
1596         { "Backspace",  KEY_BACKSPACE },
1597         { "Tab",        KEY_TAB },
1598         { "Escape",     KEY_ESC },
1599         { "Left",       KEY_LEFT },
1600         { "Right",      KEY_RIGHT },
1601         { "Up",         KEY_UP },
1602         { "Down",       KEY_DOWN },
1603         { "Insert",     KEY_IC },
1604         { "Delete",     KEY_DC },
1605         { "Hash",       '#' },
1606         { "Home",       KEY_HOME },
1607         { "End",        KEY_END },
1608         { "PageUp",     KEY_PPAGE },
1609         { "PageDown",   KEY_NPAGE },
1610         { "F1",         KEY_F(1) },
1611         { "F2",         KEY_F(2) },
1612         { "F3",         KEY_F(3) },
1613         { "F4",         KEY_F(4) },
1614         { "F5",         KEY_F(5) },
1615         { "F6",         KEY_F(6) },
1616         { "F7",         KEY_F(7) },
1617         { "F8",         KEY_F(8) },
1618         { "F9",         KEY_F(9) },
1619         { "F10",        KEY_F(10) },
1620         { "F11",        KEY_F(11) },
1621         { "F12",        KEY_F(12) },
1622 };
1624 static int
1625 get_key_value(const char *name)
1627         int i;
1629         for (i = 0; i < ARRAY_SIZE(key_table); i++)
1630                 if (!strcasecmp(key_table[i].name, name))
1631                         return key_table[i].value;
1633         if (strlen(name) == 2 && name[0] == '^' && isprint(*name))
1634                 return (int)name[1] & 0x1f;
1635         if (strlen(name) == 1 && isprint(*name))
1636                 return (int) *name;
1637         return ERR;
1640 static const char *
1641 get_key_name(int key_value)
1643         static char key_char[] = "'X'\0";
1644         const char *seq = NULL;
1645         int key;
1647         for (key = 0; key < ARRAY_SIZE(key_table); key++)
1648                 if (key_table[key].value == key_value)
1649                         seq = key_table[key].name;
1651         if (seq == NULL && key_value < 0x7f) {
1652                 char *s = key_char + 1;
1654                 if (key_value >= 0x20) {
1655                         *s++ = key_value;
1656                 } else {
1657                         *s++ = '^';
1658                         *s++ = 0x40 | (key_value & 0x1f);
1659                 }
1660                 *s++ = '\'';
1661                 *s++ = '\0';
1662                 seq = key_char;
1663         }
1665         return seq ? seq : "(no key)";
1668 static bool
1669 append_key(char *buf, size_t *pos, const struct keybinding *keybinding)
1671         const char *sep = *pos > 0 ? ", " : "";
1672         const char *keyname = get_key_name(keybinding->alias);
1674         return string_nformat(buf, BUFSIZ, pos, "%s%s", sep, keyname);
1677 static bool
1678 append_keymap_request_keys(char *buf, size_t *pos, enum request request,
1679                            enum keymap keymap, bool all)
1681         int i;
1683         for (i = 0; i < keybindings[keymap].size; i++) {
1684                 if (keybindings[keymap].data[i].request == request) {
1685                         if (!append_key(buf, pos, &keybindings[keymap].data[i]))
1686                                 return FALSE;
1687                         if (!all)
1688                                 break;
1689                 }
1690         }
1692         return TRUE;
1695 #define get_key(keymap, request) get_keys(keymap, request, FALSE)
1697 static const char *
1698 get_keys(enum keymap keymap, enum request request, bool all)
1700         static char buf[BUFSIZ];
1701         size_t pos = 0;
1702         int i;
1704         buf[pos] = 0;
1706         if (!append_keymap_request_keys(buf, &pos, request, keymap, all))
1707                 return "Too many keybindings!";
1708         if (pos > 0 && !all)
1709                 return buf;
1711         if (keymap != KEYMAP_GENERIC) {
1712                 /* Only the generic keymap includes the default keybindings when
1713                  * listing all keys. */
1714                 if (all)
1715                         return buf;
1717                 if (!append_keymap_request_keys(buf, &pos, request, KEYMAP_GENERIC, all))
1718                         return "Too many keybindings!";
1719                 if (pos)
1720                         return buf;
1721         }
1723         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1724                 if (default_keybindings[i].request == request) {
1725                         if (!append_key(buf, &pos, &default_keybindings[i]))
1726                                 return "Too many keybindings!";
1727                         if (!all)
1728                                 return buf;
1729                 }
1730         }
1732         return buf;
1735 struct run_request {
1736         enum keymap keymap;
1737         int key;
1738         const char **argv;
1739 };
1741 static struct run_request *run_request;
1742 static size_t run_requests;
1744 DEFINE_ALLOCATOR(realloc_run_requests, struct run_request, 8)
1746 static enum request
1747 add_run_request(enum keymap keymap, int key, const char **argv)
1749         struct run_request *req;
1751         if (!realloc_run_requests(&run_request, run_requests, 1))
1752                 return REQ_NONE;
1754         req = &run_request[run_requests];
1755         req->keymap = keymap;
1756         req->key = key;
1757         req->argv = NULL;
1759         if (!argv_copy(&req->argv, argv))
1760                 return REQ_NONE;
1762         return REQ_NONE + ++run_requests;
1765 static struct run_request *
1766 get_run_request(enum request request)
1768         if (request <= REQ_NONE)
1769                 return NULL;
1770         return &run_request[request - REQ_NONE - 1];
1773 static void
1774 add_builtin_run_requests(void)
1776         const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1777         const char *checkout[] = { "git", "checkout", "%(branch)", NULL };
1778         const char *commit[] = { "git", "commit", NULL };
1779         const char *gc[] = { "git", "gc", NULL };
1780         struct run_request reqs[] = {
1781                 { KEYMAP_MAIN,    'C', cherry_pick },
1782                 { KEYMAP_STATUS,  'C', commit },
1783                 { KEYMAP_BRANCH,  'C', checkout },
1784                 { KEYMAP_GENERIC, 'G', gc },
1785         };
1786         int i;
1788         for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1789                 enum request req = get_keybinding(reqs[i].keymap, reqs[i].key);
1791                 if (req != reqs[i].key)
1792                         continue;
1793                 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argv);
1794                 if (req != REQ_NONE)
1795                         add_keybinding(reqs[i].keymap, req, reqs[i].key);
1796         }
1799 /*
1800  * User config file handling.
1801  */
1803 static int   config_lineno;
1804 static bool  config_errors;
1805 static const char *config_msg;
1807 static const struct enum_map color_map[] = {
1808 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1809         COLOR_MAP(DEFAULT),
1810         COLOR_MAP(BLACK),
1811         COLOR_MAP(BLUE),
1812         COLOR_MAP(CYAN),
1813         COLOR_MAP(GREEN),
1814         COLOR_MAP(MAGENTA),
1815         COLOR_MAP(RED),
1816         COLOR_MAP(WHITE),
1817         COLOR_MAP(YELLOW),
1818 };
1820 static const struct enum_map attr_map[] = {
1821 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1822         ATTR_MAP(NORMAL),
1823         ATTR_MAP(BLINK),
1824         ATTR_MAP(BOLD),
1825         ATTR_MAP(DIM),
1826         ATTR_MAP(REVERSE),
1827         ATTR_MAP(STANDOUT),
1828         ATTR_MAP(UNDERLINE),
1829 };
1831 #define set_attribute(attr, name)       map_enum(attr, attr_map, name)
1833 static int parse_step(double *opt, const char *arg)
1835         *opt = atoi(arg);
1836         if (!strchr(arg, '%'))
1837                 return OK;
1839         /* "Shift down" so 100% and 1 does not conflict. */
1840         *opt = (*opt - 1) / 100;
1841         if (*opt >= 1.0) {
1842                 *opt = 0.99;
1843                 config_msg = "Step value larger than 100%";
1844                 return ERR;
1845         }
1846         if (*opt < 0.0) {
1847                 *opt = 1;
1848                 config_msg = "Invalid step value";
1849                 return ERR;
1850         }
1851         return OK;
1854 static int
1855 parse_int(int *opt, const char *arg, int min, int max)
1857         int value = atoi(arg);
1859         if (min <= value && value <= max) {
1860                 *opt = value;
1861                 return OK;
1862         }
1864         config_msg = "Integer value out of bound";
1865         return ERR;
1868 static bool
1869 set_color(int *color, const char *name)
1871         if (map_enum(color, color_map, name))
1872                 return TRUE;
1873         if (!prefixcmp(name, "color"))
1874                 return parse_int(color, name + 5, 0, 255) == OK;
1875         return FALSE;
1878 /* Wants: object fgcolor bgcolor [attribute] */
1879 static int
1880 option_color_command(int argc, const char *argv[])
1882         struct line_info *info;
1884         if (argc < 3) {
1885                 config_msg = "Wrong number of arguments given to color command";
1886                 return ERR;
1887         }
1889         info = get_line_info(argv[0]);
1890         if (!info) {
1891                 static const struct enum_map obsolete[] = {
1892                         ENUM_MAP("main-delim",  LINE_DELIMITER),
1893                         ENUM_MAP("main-date",   LINE_DATE),
1894                         ENUM_MAP("main-author", LINE_AUTHOR),
1895                 };
1896                 int index;
1898                 if (!map_enum(&index, obsolete, argv[0])) {
1899                         config_msg = "Unknown color name";
1900                         return ERR;
1901                 }
1902                 info = &line_info[index];
1903         }
1905         if (!set_color(&info->fg, argv[1]) ||
1906             !set_color(&info->bg, argv[2])) {
1907                 config_msg = "Unknown color";
1908                 return ERR;
1909         }
1911         info->attr = 0;
1912         while (argc-- > 3) {
1913                 int attr;
1915                 if (!set_attribute(&attr, argv[argc])) {
1916                         config_msg = "Unknown attribute";
1917                         return ERR;
1918                 }
1919                 info->attr |= attr;
1920         }
1922         return OK;
1925 static int parse_bool(bool *opt, const char *arg)
1927         *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1928                 ? TRUE : FALSE;
1929         return OK;
1932 static int parse_enum_do(unsigned int *opt, const char *arg,
1933                          const struct enum_map *map, size_t map_size)
1935         bool is_true;
1937         assert(map_size > 1);
1939         if (map_enum_do(map, map_size, (int *) opt, arg))
1940                 return OK;
1942         if (parse_bool(&is_true, arg) != OK)
1943                 return ERR;
1945         *opt = is_true ? map[1].value : map[0].value;
1946         return OK;
1949 #define parse_enum(opt, arg, map) \
1950         parse_enum_do(opt, arg, map, ARRAY_SIZE(map))
1952 static int
1953 parse_string(char *opt, const char *arg, size_t optsize)
1955         int arglen = strlen(arg);
1957         switch (arg[0]) {
1958         case '\"':
1959         case '\'':
1960                 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1961                         config_msg = "Unmatched quotation";
1962                         return ERR;
1963                 }
1964                 arg += 1; arglen -= 2;
1965         default:
1966                 string_ncopy_do(opt, optsize, arg, arglen);
1967                 return OK;
1968         }
1971 /* Wants: name = value */
1972 static int
1973 option_set_command(int argc, const char *argv[])
1975         if (argc != 3) {
1976                 config_msg = "Wrong number of arguments given to set command";
1977                 return ERR;
1978         }
1980         if (strcmp(argv[1], "=")) {
1981                 config_msg = "No value assigned";
1982                 return ERR;
1983         }
1985         if (!strcmp(argv[0], "show-author"))
1986                 return parse_enum(&opt_author, argv[2], author_map);
1988         if (!strcmp(argv[0], "show-date"))
1989                 return parse_enum(&opt_date, argv[2], date_map);
1991         if (!strcmp(argv[0], "show-rev-graph"))
1992                 return parse_bool(&opt_rev_graph, argv[2]);
1994         if (!strcmp(argv[0], "show-refs"))
1995                 return parse_bool(&opt_show_refs, argv[2]);
1997         if (!strcmp(argv[0], "show-line-numbers"))
1998                 return parse_bool(&opt_line_number, argv[2]);
2000         if (!strcmp(argv[0], "line-graphics"))
2001                 return parse_bool(&opt_line_graphics, argv[2]);
2003         if (!strcmp(argv[0], "line-number-interval"))
2004                 return parse_int(&opt_num_interval, argv[2], 1, 1024);
2006         if (!strcmp(argv[0], "author-width"))
2007                 return parse_int(&opt_author_cols, argv[2], 0, 1024);
2009         if (!strcmp(argv[0], "horizontal-scroll"))
2010                 return parse_step(&opt_hscroll, argv[2]);
2012         if (!strcmp(argv[0], "split-view-height"))
2013                 return parse_step(&opt_scale_split_view, argv[2]);
2015         if (!strcmp(argv[0], "tab-size"))
2016                 return parse_int(&opt_tab_size, argv[2], 1, 1024);
2018         if (!strcmp(argv[0], "commit-encoding"))
2019                 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
2021         if (!strcmp(argv[0], "status-untracked-dirs"))
2022                 return parse_bool(&opt_untracked_dirs_content, argv[2]);
2024         config_msg = "Unknown variable name";
2025         return ERR;
2028 /* Wants: mode request key */
2029 static int
2030 option_bind_command(int argc, const char *argv[])
2032         enum request request;
2033         int keymap = -1;
2034         int key;
2036         if (argc < 3) {
2037                 config_msg = "Wrong number of arguments given to bind command";
2038                 return ERR;
2039         }
2041         if (!set_keymap(&keymap, argv[0])) {
2042                 config_msg = "Unknown key map";
2043                 return ERR;
2044         }
2046         key = get_key_value(argv[1]);
2047         if (key == ERR) {
2048                 config_msg = "Unknown key";
2049                 return ERR;
2050         }
2052         request = get_request(argv[2]);
2053         if (request == REQ_UNKNOWN) {
2054                 static const struct enum_map obsolete[] = {
2055                         ENUM_MAP("cherry-pick",         REQ_NONE),
2056                         ENUM_MAP("screen-resize",       REQ_NONE),
2057                         ENUM_MAP("tree-parent",         REQ_PARENT),
2058                 };
2059                 int alias;
2061                 if (map_enum(&alias, obsolete, argv[2])) {
2062                         if (alias != REQ_NONE)
2063                                 add_keybinding(keymap, alias, key);
2064                         config_msg = "Obsolete request name";
2065                         return ERR;
2066                 }
2067         }
2068         if (request == REQ_UNKNOWN && *argv[2]++ == '!')
2069                 request = add_run_request(keymap, key, argv + 2);
2070         if (request == REQ_UNKNOWN) {
2071                 config_msg = "Unknown request name";
2072                 return ERR;
2073         }
2075         add_keybinding(keymap, request, key);
2077         return OK;
2080 static int
2081 set_option(const char *opt, char *value)
2083         const char *argv[SIZEOF_ARG];
2084         int argc = 0;
2086         if (!argv_from_string(argv, &argc, value)) {
2087                 config_msg = "Too many option arguments";
2088                 return ERR;
2089         }
2091         if (!strcmp(opt, "color"))
2092                 return option_color_command(argc, argv);
2094         if (!strcmp(opt, "set"))
2095                 return option_set_command(argc, argv);
2097         if (!strcmp(opt, "bind"))
2098                 return option_bind_command(argc, argv);
2100         config_msg = "Unknown option command";
2101         return ERR;
2104 static int
2105 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
2107         int status = OK;
2109         config_lineno++;
2110         config_msg = "Internal error";
2112         /* Check for comment markers, since read_properties() will
2113          * only ensure opt and value are split at first " \t". */
2114         optlen = strcspn(opt, "#");
2115         if (optlen == 0)
2116                 return OK;
2118         if (opt[optlen] != 0) {
2119                 config_msg = "No option value";
2120                 status = ERR;
2122         }  else {
2123                 /* Look for comment endings in the value. */
2124                 size_t len = strcspn(value, "#");
2126                 if (len < valuelen) {
2127                         valuelen = len;
2128                         value[valuelen] = 0;
2129                 }
2131                 status = set_option(opt, value);
2132         }
2134         if (status == ERR) {
2135                 warn("Error on line %d, near '%.*s': %s",
2136                      config_lineno, (int) optlen, opt, config_msg);
2137                 config_errors = TRUE;
2138         }
2140         /* Always keep going if errors are encountered. */
2141         return OK;
2144 static void
2145 load_option_file(const char *path)
2147         struct io io;
2149         /* It's OK that the file doesn't exist. */
2150         if (!io_open(&io, "%s", path))
2151                 return;
2153         config_lineno = 0;
2154         config_errors = FALSE;
2156         if (io_load(&io, " \t", read_option) == ERR ||
2157             config_errors == TRUE)
2158                 warn("Errors while loading %s.", path);
2161 static int
2162 load_options(void)
2164         const char *home = getenv("HOME");
2165         const char *tigrc_user = getenv("TIGRC_USER");
2166         const char *tigrc_system = getenv("TIGRC_SYSTEM");
2167         const char *tig_diff_opts = getenv("TIG_DIFF_OPTS");
2168         char buf[SIZEOF_STR];
2170         if (!tigrc_system)
2171                 tigrc_system = SYSCONFDIR "/tigrc";
2172         load_option_file(tigrc_system);
2174         if (!tigrc_user) {
2175                 if (!home || !string_format(buf, "%s/.tigrc", home))
2176                         return ERR;
2177                 tigrc_user = buf;
2178         }
2179         load_option_file(tigrc_user);
2181         /* Add _after_ loading config files to avoid adding run requests
2182          * that conflict with keybindings. */
2183         add_builtin_run_requests();
2185         if (!opt_diff_args && tig_diff_opts && *tig_diff_opts) {
2186                 static const char *diff_opts[SIZEOF_ARG] = { NULL };
2187                 int argc = 0;
2189                 if (!string_format(buf, "%s", tig_diff_opts) ||
2190                     !argv_from_string(diff_opts, &argc, buf))
2191                         die("TIG_DIFF_OPTS contains too many arguments");
2192                 else if (!argv_copy(&opt_diff_args, diff_opts))
2193                         die("Failed to format TIG_DIFF_OPTS arguments");
2194         }
2196         return OK;
2200 /*
2201  * The viewer
2202  */
2204 struct view;
2205 struct view_ops;
2207 /* The display array of active views and the index of the current view. */
2208 static struct view *display[2];
2209 static unsigned int current_view;
2211 #define foreach_displayed_view(view, i) \
2212         for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
2214 #define displayed_views()       (display[1] != NULL ? 2 : 1)
2216 /* Current head and commit ID */
2217 static char ref_blob[SIZEOF_REF]        = "";
2218 static char ref_commit[SIZEOF_REF]      = "HEAD";
2219 static char ref_head[SIZEOF_REF]        = "HEAD";
2220 static char ref_branch[SIZEOF_REF]      = "";
2222 enum view_type {
2223         VIEW_MAIN,
2224         VIEW_DIFF,
2225         VIEW_LOG,
2226         VIEW_TREE,
2227         VIEW_BLOB,
2228         VIEW_BLAME,
2229         VIEW_BRANCH,
2230         VIEW_HELP,
2231         VIEW_PAGER,
2232         VIEW_STATUS,
2233         VIEW_STAGE,
2234 };
2236 struct view {
2237         enum view_type type;    /* View type */
2238         const char *name;       /* View name */
2239         const char *cmd_env;    /* Command line set via environment */
2240         const char *id;         /* Points to either of ref_{head,commit,blob} */
2242         struct view_ops *ops;   /* View operations */
2244         enum keymap keymap;     /* What keymap does this view have */
2245         bool git_dir;           /* Whether the view requires a git directory. */
2247         char ref[SIZEOF_REF];   /* Hovered commit reference */
2248         char vid[SIZEOF_REF];   /* View ID. Set to id member when updating. */
2250         int height, width;      /* The width and height of the main window */
2251         WINDOW *win;            /* The main window */
2252         WINDOW *title;          /* The title window living below the main window */
2254         /* Navigation */
2255         unsigned long offset;   /* Offset of the window top */
2256         unsigned long yoffset;  /* Offset from the window side. */
2257         unsigned long lineno;   /* Current line number */
2258         unsigned long p_offset; /* Previous offset of the window top */
2259         unsigned long p_yoffset;/* Previous offset from the window side */
2260         unsigned long p_lineno; /* Previous current line number */
2261         bool p_restore;         /* Should the previous position be restored. */
2263         /* Searching */
2264         char grep[SIZEOF_STR];  /* Search string */
2265         regex_t *regex;         /* Pre-compiled regexp */
2267         /* If non-NULL, points to the view that opened this view. If this view
2268          * is closed tig will switch back to the parent view. */
2269         struct view *parent;
2270         struct view *prev;
2272         /* Buffering */
2273         size_t lines;           /* Total number of lines */
2274         struct line *line;      /* Line index */
2275         unsigned int digits;    /* Number of digits in the lines member. */
2277         /* Drawing */
2278         struct line *curline;   /* Line currently being drawn. */
2279         enum line_type curtype; /* Attribute currently used for drawing. */
2280         unsigned long col;      /* Column when drawing. */
2281         bool has_scrolled;      /* View was scrolled. */
2283         /* Loading */
2284         const char **argv;      /* Shell command arguments. */
2285         const char *dir;        /* Directory from which to execute. */
2286         struct io io;
2287         struct io *pipe;
2288         time_t start_time;
2289         time_t update_secs;
2290 };
2292 struct view_ops {
2293         /* What type of content being displayed. Used in the title bar. */
2294         const char *type;
2295         /* Default command arguments. */
2296         const char **argv;
2297         /* Open and reads in all view content. */
2298         bool (*open)(struct view *view);
2299         /* Read one line; updates view->line. */
2300         bool (*read)(struct view *view, char *data);
2301         /* Draw one line; @lineno must be < view->height. */
2302         bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
2303         /* Depending on view handle a special requests. */
2304         enum request (*request)(struct view *view, enum request request, struct line *line);
2305         /* Search for regexp in a line. */
2306         bool (*grep)(struct view *view, struct line *line);
2307         /* Select line */
2308         void (*select)(struct view *view, struct line *line);
2309         /* Prepare view for loading */
2310         bool (*prepare)(struct view *view);
2311 };
2313 static struct view_ops blame_ops;
2314 static struct view_ops blob_ops;
2315 static struct view_ops diff_ops;
2316 static struct view_ops help_ops;
2317 static struct view_ops log_ops;
2318 static struct view_ops main_ops;
2319 static struct view_ops pager_ops;
2320 static struct view_ops stage_ops;
2321 static struct view_ops status_ops;
2322 static struct view_ops tree_ops;
2323 static struct view_ops branch_ops;
2325 #define VIEW_STR(type, name, env, ref, ops, map, git) \
2326         { type, name, #env, ref, ops, map, git }
2328 #define VIEW_(id, name, ops, git, ref) \
2329         VIEW_STR(VIEW_##id, name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
2331 static struct view views[] = {
2332         VIEW_(MAIN,   "main",   &main_ops,   TRUE,  ref_head),
2333         VIEW_(DIFF,   "diff",   &diff_ops,   TRUE,  ref_commit),
2334         VIEW_(LOG,    "log",    &log_ops,    TRUE,  ref_head),
2335         VIEW_(TREE,   "tree",   &tree_ops,   TRUE,  ref_commit),
2336         VIEW_(BLOB,   "blob",   &blob_ops,   TRUE,  ref_blob),
2337         VIEW_(BLAME,  "blame",  &blame_ops,  TRUE,  ref_commit),
2338         VIEW_(BRANCH, "branch", &branch_ops, TRUE,  ref_head),
2339         VIEW_(HELP,   "help",   &help_ops,   FALSE, ""),
2340         VIEW_(PAGER,  "pager",  &pager_ops,  FALSE, ""),
2341         VIEW_(STATUS, "status", &status_ops, TRUE,  ""),
2342         VIEW_(STAGE,  "stage",  &stage_ops,  TRUE,  ""),
2343 };
2345 #define VIEW(req)       (&views[(req) - REQ_OFFSET - 1])
2347 #define foreach_view(view, i) \
2348         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2350 #define view_is_displayed(view) \
2351         (view == display[0] || view == display[1])
2353 static enum request
2354 view_request(struct view *view, enum request request)
2356         if (!view || !view->lines)
2357                 return request;
2358         return view->ops->request(view, request, &view->line[view->lineno]);
2362 /*
2363  * View drawing.
2364  */
2366 static inline void
2367 set_view_attr(struct view *view, enum line_type type)
2369         if (!view->curline->selected && view->curtype != type) {
2370                 (void) wattrset(view->win, get_line_attr(type));
2371                 wchgat(view->win, -1, 0, type, NULL);
2372                 view->curtype = type;
2373         }
2376 static int
2377 draw_chars(struct view *view, enum line_type type, const char *string,
2378            int max_len, bool use_tilde)
2380         static char out_buffer[BUFSIZ * 2];
2381         int len = 0;
2382         int col = 0;
2383         int trimmed = FALSE;
2384         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2386         if (max_len <= 0)
2387                 return 0;
2389         len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde, opt_tab_size);
2391         set_view_attr(view, type);
2392         if (len > 0) {
2393                 if (opt_iconv_out != ICONV_NONE) {
2394                         ICONV_CONST char *inbuf = (ICONV_CONST char *) string;
2395                         size_t inlen = len + 1;
2397                         char *outbuf = out_buffer;
2398                         size_t outlen = sizeof(out_buffer);
2400                         size_t ret;
2402                         ret = iconv(opt_iconv_out, &inbuf, &inlen, &outbuf, &outlen);
2403                         if (ret != (size_t) -1) {
2404                                 string = out_buffer;
2405                                 len = sizeof(out_buffer) - outlen;
2406                         }
2407                 }
2409                 waddnstr(view->win, string, len);
2411                 if (trimmed && use_tilde) {
2412                         set_view_attr(view, LINE_DELIMITER);
2413                         waddch(view->win, '~');
2414                         col++;
2415                 }
2416         }
2418         return col;
2421 static int
2422 draw_space(struct view *view, enum line_type type, int max, int spaces)
2424         static char space[] = "                    ";
2425         int col = 0;
2427         spaces = MIN(max, spaces);
2429         while (spaces > 0) {
2430                 int len = MIN(spaces, sizeof(space) - 1);
2432                 col += draw_chars(view, type, space, len, FALSE);
2433                 spaces -= len;
2434         }
2436         return col;
2439 static bool
2440 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
2442         char text[SIZEOF_STR];
2444         do {
2445                 size_t pos = string_expand(text, sizeof(text), string, opt_tab_size);
2447                 view->col += draw_chars(view, type, text, view->width + view->yoffset - view->col, trim);
2448                 string += pos;
2449         } while (*string && view->width + view->yoffset > view->col);
2451         return view->width + view->yoffset <= view->col;
2454 static bool
2455 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
2457         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2458         int max = view->width + view->yoffset - view->col;
2459         int i;
2461         if (max < size)
2462                 size = max;
2464         set_view_attr(view, type);
2465         /* Using waddch() instead of waddnstr() ensures that
2466          * they'll be rendered correctly for the cursor line. */
2467         for (i = skip; i < size; i++)
2468                 waddch(view->win, graphic[i]);
2470         view->col += size;
2471         if (size < max && skip <= size)
2472                 waddch(view->win, ' ');
2473         view->col++;
2475         return view->width + view->yoffset <= view->col;
2478 static bool
2479 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
2481         int max = MIN(view->width + view->yoffset - view->col, len);
2482         int col;
2484         if (text)
2485                 col = draw_chars(view, type, text, max - 1, trim);
2486         else
2487                 col = draw_space(view, type, max - 1, max - 1);
2489         view->col += col;
2490         view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
2491         return view->width + view->yoffset <= view->col;
2494 static bool
2495 draw_date(struct view *view, struct time *time)
2497         const char *date = mkdate(time, opt_date);
2498         int cols = opt_date == DATE_SHORT ? DATE_SHORT_COLS : DATE_COLS;
2500         return draw_field(view, LINE_DATE, date, cols, FALSE);
2503 static bool
2504 draw_author(struct view *view, const char *author)
2506         bool trim = opt_author_cols == 0 || opt_author_cols > 5;
2507         bool abbreviate = opt_author == AUTHOR_ABBREVIATED || !trim;
2509         if (abbreviate && author)
2510                 author = get_author_initials(author);
2512         return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2515 static bool
2516 draw_mode(struct view *view, mode_t mode)
2518         const char *str;
2520         if (S_ISDIR(mode))
2521                 str = "drwxr-xr-x";
2522         else if (S_ISLNK(mode))
2523                 str = "lrwxrwxrwx";
2524         else if (S_ISGITLINK(mode))
2525                 str = "m---------";
2526         else if (S_ISREG(mode) && mode & S_IXUSR)
2527                 str = "-rwxr-xr-x";
2528         else if (S_ISREG(mode))
2529                 str = "-rw-r--r--";
2530         else
2531                 str = "----------";
2533         return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2536 static bool
2537 draw_lineno(struct view *view, unsigned int lineno)
2539         char number[10];
2540         int digits3 = view->digits < 3 ? 3 : view->digits;
2541         int max = MIN(view->width + view->yoffset - view->col, digits3);
2542         char *text = NULL;
2543         chtype separator = opt_line_graphics ? ACS_VLINE : '|';
2545         lineno += view->offset + 1;
2546         if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2547                 static char fmt[] = "%1ld";
2549                 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2550                 if (string_format(number, fmt, lineno))
2551                         text = number;
2552         }
2553         if (text)
2554                 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2555         else
2556                 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2557         return draw_graphic(view, LINE_DEFAULT, &separator, 1);
2560 static bool
2561 draw_view_line(struct view *view, unsigned int lineno)
2563         struct line *line;
2564         bool selected = (view->offset + lineno == view->lineno);
2566         assert(view_is_displayed(view));
2568         if (view->offset + lineno >= view->lines)
2569                 return FALSE;
2571         line = &view->line[view->offset + lineno];
2573         wmove(view->win, lineno, 0);
2574         if (line->cleareol)
2575                 wclrtoeol(view->win);
2576         view->col = 0;
2577         view->curline = line;
2578         view->curtype = LINE_NONE;
2579         line->selected = FALSE;
2580         line->dirty = line->cleareol = 0;
2582         if (selected) {
2583                 set_view_attr(view, LINE_CURSOR);
2584                 line->selected = TRUE;
2585                 view->ops->select(view, line);
2586         }
2588         return view->ops->draw(view, line, lineno);
2591 static void
2592 redraw_view_dirty(struct view *view)
2594         bool dirty = FALSE;
2595         int lineno;
2597         for (lineno = 0; lineno < view->height; lineno++) {
2598                 if (view->offset + lineno >= view->lines)
2599                         break;
2600                 if (!view->line[view->offset + lineno].dirty)
2601                         continue;
2602                 dirty = TRUE;
2603                 if (!draw_view_line(view, lineno))
2604                         break;
2605         }
2607         if (!dirty)
2608                 return;
2609         wnoutrefresh(view->win);
2612 static void
2613 redraw_view_from(struct view *view, int lineno)
2615         assert(0 <= lineno && lineno < view->height);
2617         for (; lineno < view->height; lineno++) {
2618                 if (!draw_view_line(view, lineno))
2619                         break;
2620         }
2622         wnoutrefresh(view->win);
2625 static void
2626 redraw_view(struct view *view)
2628         werase(view->win);
2629         redraw_view_from(view, 0);
2633 static void
2634 update_view_title(struct view *view)
2636         char buf[SIZEOF_STR];
2637         char state[SIZEOF_STR];
2638         size_t bufpos = 0, statelen = 0;
2640         assert(view_is_displayed(view));
2642         if (view->type != VIEW_STATUS && view->lines) {
2643                 unsigned int view_lines = view->offset + view->height;
2644                 unsigned int lines = view->lines
2645                                    ? MIN(view_lines, view->lines) * 100 / view->lines
2646                                    : 0;
2648                 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2649                                    view->ops->type,
2650                                    view->lineno + 1,
2651                                    view->lines,
2652                                    lines);
2654         }
2656         if (view->pipe) {
2657                 time_t secs = time(NULL) - view->start_time;
2659                 /* Three git seconds are a long time ... */
2660                 if (secs > 2)
2661                         string_format_from(state, &statelen, " loading %lds", secs);
2662         }
2664         string_format_from(buf, &bufpos, "[%s]", view->name);
2665         if (*view->ref && bufpos < view->width) {
2666                 size_t refsize = strlen(view->ref);
2667                 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2669                 if (minsize < view->width)
2670                         refsize = view->width - minsize + 7;
2671                 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2672         }
2674         if (statelen && bufpos < view->width) {
2675                 string_format_from(buf, &bufpos, "%s", state);
2676         }
2678         if (view == display[current_view])
2679                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2680         else
2681                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2683         mvwaddnstr(view->title, 0, 0, buf, bufpos);
2684         wclrtoeol(view->title);
2685         wnoutrefresh(view->title);
2688 static int
2689 apply_step(double step, int value)
2691         if (step >= 1)
2692                 return (int) step;
2693         value *= step + 0.01;
2694         return value ? value : 1;
2697 static void
2698 resize_display(void)
2700         int offset, i;
2701         struct view *base = display[0];
2702         struct view *view = display[1] ? display[1] : display[0];
2704         /* Setup window dimensions */
2706         getmaxyx(stdscr, base->height, base->width);
2708         /* Make room for the status window. */
2709         base->height -= 1;
2711         if (view != base) {
2712                 /* Horizontal split. */
2713                 view->width   = base->width;
2714                 view->height  = apply_step(opt_scale_split_view, base->height);
2715                 view->height  = MAX(view->height, MIN_VIEW_HEIGHT);
2716                 view->height  = MIN(view->height, base->height - MIN_VIEW_HEIGHT);
2717                 base->height -= view->height;
2719                 /* Make room for the title bar. */
2720                 view->height -= 1;
2721         }
2723         /* Make room for the title bar. */
2724         base->height -= 1;
2726         offset = 0;
2728         foreach_displayed_view (view, i) {
2729                 if (!view->win) {
2730                         view->win = newwin(view->height, 0, offset, 0);
2731                         if (!view->win)
2732                                 die("Failed to create %s view", view->name);
2734                         scrollok(view->win, FALSE);
2736                         view->title = newwin(1, 0, offset + view->height, 0);
2737                         if (!view->title)
2738                                 die("Failed to create title window");
2740                 } else {
2741                         wresize(view->win, view->height, view->width);
2742                         mvwin(view->win,   offset, 0);
2743                         mvwin(view->title, offset + view->height, 0);
2744                 }
2746                 offset += view->height + 1;
2747         }
2750 static void
2751 redraw_display(bool clear)
2753         struct view *view;
2754         int i;
2756         foreach_displayed_view (view, i) {
2757                 if (clear)
2758                         wclear(view->win);
2759                 redraw_view(view);
2760                 update_view_title(view);
2761         }
2765 /*
2766  * Option management
2767  */
2769 static void
2770 toggle_enum_option_do(unsigned int *opt, const char *help,
2771                       const struct enum_map *map, size_t size)
2773         *opt = (*opt + 1) % size;
2774         redraw_display(FALSE);
2775         report("Displaying %s %s", enum_name(map[*opt]), help);
2778 #define toggle_enum_option(opt, help, map) \
2779         toggle_enum_option_do(opt, help, map, ARRAY_SIZE(map))
2781 #define toggle_date() toggle_enum_option(&opt_date, "dates", date_map)
2782 #define toggle_author() toggle_enum_option(&opt_author, "author names", author_map)
2784 static void
2785 toggle_view_option(bool *option, const char *help)
2787         *option = !*option;
2788         redraw_display(FALSE);
2789         report("%sabling %s", *option ? "En" : "Dis", help);
2792 static void
2793 open_option_menu(void)
2795         const struct menu_item menu[] = {
2796                 { '.', "line numbers", &opt_line_number },
2797                 { 'D', "date display", &opt_date },
2798                 { 'A', "author display", &opt_author },
2799                 { 'g', "revision graph display", &opt_rev_graph },
2800                 { 'F', "reference display", &opt_show_refs },
2801                 { 0 }
2802         };
2803         int selected = 0;
2805         if (prompt_menu("Toggle option", menu, &selected)) {
2806                 if (menu[selected].data == &opt_date)
2807                         toggle_date();
2808                 else if (menu[selected].data == &opt_author)
2809                         toggle_author();
2810                 else
2811                         toggle_view_option(menu[selected].data, menu[selected].text);
2812         }
2815 static void
2816 maximize_view(struct view *view)
2818         memset(display, 0, sizeof(display));
2819         current_view = 0;
2820         display[current_view] = view;
2821         resize_display();
2822         redraw_display(FALSE);
2823         report("");
2827 /*
2828  * Navigation
2829  */
2831 static bool
2832 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2834         if (lineno >= view->lines)
2835                 lineno = view->lines > 0 ? view->lines - 1 : 0;
2837         if (offset > lineno || offset + view->height <= lineno) {
2838                 unsigned long half = view->height / 2;
2840                 if (lineno > half)
2841                         offset = lineno - half;
2842                 else
2843                         offset = 0;
2844         }
2846         if (offset != view->offset || lineno != view->lineno) {
2847                 view->offset = offset;
2848                 view->lineno = lineno;
2849                 return TRUE;
2850         }
2852         return FALSE;
2855 /* Scrolling backend */
2856 static void
2857 do_scroll_view(struct view *view, int lines)
2859         bool redraw_current_line = FALSE;
2861         /* The rendering expects the new offset. */
2862         view->offset += lines;
2864         assert(0 <= view->offset && view->offset < view->lines);
2865         assert(lines);
2867         /* Move current line into the view. */
2868         if (view->lineno < view->offset) {
2869                 view->lineno = view->offset;
2870                 redraw_current_line = TRUE;
2871         } else if (view->lineno >= view->offset + view->height) {
2872                 view->lineno = view->offset + view->height - 1;
2873                 redraw_current_line = TRUE;
2874         }
2876         assert(view->offset <= view->lineno && view->lineno < view->lines);
2878         /* Redraw the whole screen if scrolling is pointless. */
2879         if (view->height < ABS(lines)) {
2880                 redraw_view(view);
2882         } else {
2883                 int line = lines > 0 ? view->height - lines : 0;
2884                 int end = line + ABS(lines);
2886                 scrollok(view->win, TRUE);
2887                 wscrl(view->win, lines);
2888                 scrollok(view->win, FALSE);
2890                 while (line < end && draw_view_line(view, line))
2891                         line++;
2893                 if (redraw_current_line)
2894                         draw_view_line(view, view->lineno - view->offset);
2895                 wnoutrefresh(view->win);
2896         }
2898         view->has_scrolled = TRUE;
2899         report("");
2902 /* Scroll frontend */
2903 static void
2904 scroll_view(struct view *view, enum request request)
2906         int lines = 1;
2908         assert(view_is_displayed(view));
2910         switch (request) {
2911         case REQ_SCROLL_FIRST_COL:
2912                 view->yoffset = 0;
2913                 redraw_view_from(view, 0);
2914                 report("");
2915                 return;
2916         case REQ_SCROLL_LEFT:
2917                 if (view->yoffset == 0) {
2918                         report("Cannot scroll beyond the first column");
2919                         return;
2920                 }
2921                 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2922                         view->yoffset = 0;
2923                 else
2924                         view->yoffset -= apply_step(opt_hscroll, view->width);
2925                 redraw_view_from(view, 0);
2926                 report("");
2927                 return;
2928         case REQ_SCROLL_RIGHT:
2929                 view->yoffset += apply_step(opt_hscroll, view->width);
2930                 redraw_view(view);
2931                 report("");
2932                 return;
2933         case REQ_SCROLL_PAGE_DOWN:
2934                 lines = view->height;
2935         case REQ_SCROLL_LINE_DOWN:
2936                 if (view->offset + lines > view->lines)
2937                         lines = view->lines - view->offset;
2939                 if (lines == 0 || view->offset + view->height >= view->lines) {
2940                         report("Cannot scroll beyond the last line");
2941                         return;
2942                 }
2943                 break;
2945         case REQ_SCROLL_PAGE_UP:
2946                 lines = view->height;
2947         case REQ_SCROLL_LINE_UP:
2948                 if (lines > view->offset)
2949                         lines = view->offset;
2951                 if (lines == 0) {
2952                         report("Cannot scroll beyond the first line");
2953                         return;
2954                 }
2956                 lines = -lines;
2957                 break;
2959         default:
2960                 die("request %d not handled in switch", request);
2961         }
2963         do_scroll_view(view, lines);
2966 /* Cursor moving */
2967 static void
2968 move_view(struct view *view, enum request request)
2970         int scroll_steps = 0;
2971         int steps;
2973         switch (request) {
2974         case REQ_MOVE_FIRST_LINE:
2975                 steps = -view->lineno;
2976                 break;
2978         case REQ_MOVE_LAST_LINE:
2979                 steps = view->lines - view->lineno - 1;
2980                 break;
2982         case REQ_MOVE_PAGE_UP:
2983                 steps = view->height > view->lineno
2984                       ? -view->lineno : -view->height;
2985                 break;
2987         case REQ_MOVE_PAGE_DOWN:
2988                 steps = view->lineno + view->height >= view->lines
2989                       ? view->lines - view->lineno - 1 : view->height;
2990                 break;
2992         case REQ_MOVE_UP:
2993                 steps = -1;
2994                 break;
2996         case REQ_MOVE_DOWN:
2997                 steps = 1;
2998                 break;
3000         default:
3001                 die("request %d not handled in switch", request);
3002         }
3004         if (steps <= 0 && view->lineno == 0) {
3005                 report("Cannot move beyond the first line");
3006                 return;
3008         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
3009                 report("Cannot move beyond the last line");
3010                 return;
3011         }
3013         /* Move the current line */
3014         view->lineno += steps;
3015         assert(0 <= view->lineno && view->lineno < view->lines);
3017         /* Check whether the view needs to be scrolled */
3018         if (view->lineno < view->offset ||
3019             view->lineno >= view->offset + view->height) {
3020                 scroll_steps = steps;
3021                 if (steps < 0 && -steps > view->offset) {
3022                         scroll_steps = -view->offset;
3024                 } else if (steps > 0) {
3025                         if (view->lineno == view->lines - 1 &&
3026                             view->lines > view->height) {
3027                                 scroll_steps = view->lines - view->offset - 1;
3028                                 if (scroll_steps >= view->height)
3029                                         scroll_steps -= view->height - 1;
3030                         }
3031                 }
3032         }
3034         if (!view_is_displayed(view)) {
3035                 view->offset += scroll_steps;
3036                 assert(0 <= view->offset && view->offset < view->lines);
3037                 view->ops->select(view, &view->line[view->lineno]);
3038                 return;
3039         }
3041         /* Repaint the old "current" line if we be scrolling */
3042         if (ABS(steps) < view->height)
3043                 draw_view_line(view, view->lineno - steps - view->offset);
3045         if (scroll_steps) {
3046                 do_scroll_view(view, scroll_steps);
3047                 return;
3048         }
3050         /* Draw the current line */
3051         draw_view_line(view, view->lineno - view->offset);
3053         wnoutrefresh(view->win);
3054         report("");
3058 /*
3059  * Searching
3060  */
3062 static void search_view(struct view *view, enum request request);
3064 static bool
3065 grep_text(struct view *view, const char *text[])
3067         regmatch_t pmatch;
3068         size_t i;
3070         for (i = 0; text[i]; i++)
3071                 if (*text[i] &&
3072                     regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
3073                         return TRUE;
3074         return FALSE;
3077 static void
3078 select_view_line(struct view *view, unsigned long lineno)
3080         unsigned long old_lineno = view->lineno;
3081         unsigned long old_offset = view->offset;
3083         if (goto_view_line(view, view->offset, lineno)) {
3084                 if (view_is_displayed(view)) {
3085                         if (old_offset != view->offset) {
3086                                 redraw_view(view);
3087                         } else {
3088                                 draw_view_line(view, old_lineno - view->offset);
3089                                 draw_view_line(view, view->lineno - view->offset);
3090                                 wnoutrefresh(view->win);
3091                         }
3092                 } else {
3093                         view->ops->select(view, &view->line[view->lineno]);
3094                 }
3095         }
3098 static void
3099 find_next(struct view *view, enum request request)
3101         unsigned long lineno = view->lineno;
3102         int direction;
3104         if (!*view->grep) {
3105                 if (!*opt_search)
3106                         report("No previous search");
3107                 else
3108                         search_view(view, request);
3109                 return;
3110         }
3112         switch (request) {
3113         case REQ_SEARCH:
3114         case REQ_FIND_NEXT:
3115                 direction = 1;
3116                 break;
3118         case REQ_SEARCH_BACK:
3119         case REQ_FIND_PREV:
3120                 direction = -1;
3121                 break;
3123         default:
3124                 return;
3125         }
3127         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
3128                 lineno += direction;
3130         /* Note, lineno is unsigned long so will wrap around in which case it
3131          * will become bigger than view->lines. */
3132         for (; lineno < view->lines; lineno += direction) {
3133                 if (view->ops->grep(view, &view->line[lineno])) {
3134                         select_view_line(view, lineno);
3135                         report("Line %ld matches '%s'", lineno + 1, view->grep);
3136                         return;
3137                 }
3138         }
3140         report("No match found for '%s'", view->grep);
3143 static void
3144 search_view(struct view *view, enum request request)
3146         int regex_err;
3148         if (view->regex) {
3149                 regfree(view->regex);
3150                 *view->grep = 0;
3151         } else {
3152                 view->regex = calloc(1, sizeof(*view->regex));
3153                 if (!view->regex)
3154                         return;
3155         }
3157         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
3158         if (regex_err != 0) {
3159                 char buf[SIZEOF_STR] = "unknown error";
3161                 regerror(regex_err, view->regex, buf, sizeof(buf));
3162                 report("Search failed: %s", buf);
3163                 return;
3164         }
3166         string_copy(view->grep, opt_search);
3168         find_next(view, request);
3171 /*
3172  * Incremental updating
3173  */
3175 static void
3176 reset_view(struct view *view)
3178         int i;
3180         for (i = 0; i < view->lines; i++)
3181                 free(view->line[i].data);
3182         free(view->line);
3184         view->p_offset = view->offset;
3185         view->p_yoffset = view->yoffset;
3186         view->p_lineno = view->lineno;
3188         view->line = NULL;
3189         view->offset = 0;
3190         view->yoffset = 0;
3191         view->lines  = 0;
3192         view->lineno = 0;
3193         view->vid[0] = 0;
3194         view->update_secs = 0;
3197 static const char *
3198 format_arg(const char *name)
3200         static struct {
3201                 const char *name;
3202                 size_t namelen;
3203                 const char *value;
3204                 const char *value_if_empty;
3205         } vars[] = {
3206 #define FORMAT_VAR(name, value, value_if_empty) \
3207         { name, STRING_SIZE(name), value, value_if_empty }
3208                 FORMAT_VAR("%(directory)",      opt_path,       ""),
3209                 FORMAT_VAR("%(file)",           opt_file,       ""),
3210                 FORMAT_VAR("%(ref)",            opt_ref,        "HEAD"),
3211                 FORMAT_VAR("%(head)",           ref_head,       ""),
3212                 FORMAT_VAR("%(commit)",         ref_commit,     ""),
3213                 FORMAT_VAR("%(blob)",           ref_blob,       ""),
3214                 FORMAT_VAR("%(branch)",         ref_branch,     ""),
3215         };
3216         int i;
3218         for (i = 0; i < ARRAY_SIZE(vars); i++)
3219                 if (!strncmp(name, vars[i].name, vars[i].namelen))
3220                         return *vars[i].value ? vars[i].value : vars[i].value_if_empty;
3222         report("Unknown replacement: `%s`", name);
3223         return NULL;
3226 static bool
3227 format_argv(const char ***dst_argv, const char *src_argv[], bool replace, bool first)
3229         char buf[SIZEOF_STR];
3230         int argc;
3232         argv_free(*dst_argv);
3234         for (argc = 0; src_argv[argc]; argc++) {
3235                 const char *arg = src_argv[argc];
3236                 size_t bufpos = 0;
3238                 if (!strcmp(arg, "%(fileargs)")) {
3239                         if (!argv_append_array(dst_argv, opt_file_args))
3240                                 break;
3241                         continue;
3243                 } else if (!strcmp(arg, "%(diffargs)")) {
3244                         if (!argv_append_array(dst_argv, opt_diff_args))
3245                                 break;
3246                         continue;
3248                 } else if (!strcmp(arg, "%(revargs)") ||
3249                            (first && !strcmp(arg, "%(commit)"))) {
3250                         if (!argv_append_array(dst_argv, opt_rev_args))
3251                                 break;
3252                         continue;
3253                 }
3255                 while (arg) {
3256                         char *next = strstr(arg, "%(");
3257                         int len = next - arg;
3258                         const char *value;
3260                         if (!next || !replace) {
3261                                 len = strlen(arg);
3262                                 value = "";
3264                         } else {
3265                                 value = format_arg(next);
3267                                 if (!value) {
3268                                         return FALSE;
3269                                 }
3270                         }
3272                         if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
3273                                 return FALSE;
3275                         arg = next && replace ? strchr(next, ')') + 1 : NULL;
3276                 }
3278                 if (!argv_append(dst_argv, buf))
3279                         break;
3280         }
3282         return src_argv[argc] == NULL;
3285 static bool
3286 restore_view_position(struct view *view)
3288         if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
3289                 return FALSE;
3291         /* Changing the view position cancels the restoring. */
3292         /* FIXME: Changing back to the first line is not detected. */
3293         if (view->offset != 0 || view->lineno != 0) {
3294                 view->p_restore = FALSE;
3295                 return FALSE;
3296         }
3298         if (goto_view_line(view, view->p_offset, view->p_lineno) &&
3299             view_is_displayed(view))
3300                 werase(view->win);
3302         view->yoffset = view->p_yoffset;
3303         view->p_restore = FALSE;
3305         return TRUE;
3308 static void
3309 end_update(struct view *view, bool force)
3311         if (!view->pipe)
3312                 return;
3313         while (!view->ops->read(view, NULL))
3314                 if (!force)
3315                         return;
3316         if (force)
3317                 io_kill(view->pipe);
3318         io_done(view->pipe);
3319         view->pipe = NULL;
3322 static void
3323 setup_update(struct view *view, const char *vid)
3325         reset_view(view);
3326         string_copy_rev(view->vid, vid);
3327         view->pipe = &view->io;
3328         view->start_time = time(NULL);
3331 static bool
3332 prepare_io(struct view *view, const char *dir, const char *argv[], bool replace)
3334         view->dir = dir;
3335         return format_argv(&view->argv, argv, replace, !view->prev);
3338 static bool
3339 prepare_update(struct view *view, const char *argv[], const char *dir)
3341         if (view->pipe)
3342                 end_update(view, TRUE);
3343         return prepare_io(view, dir, argv, FALSE);
3346 static bool
3347 start_update(struct view *view, const char **argv, const char *dir)
3349         if (view->pipe)
3350                 io_done(view->pipe);
3351         return prepare_io(view, dir, argv, FALSE) &&
3352                io_run(&view->io, IO_RD, dir, view->argv);
3355 static bool
3356 prepare_update_file(struct view *view, const char *name)
3358         if (view->pipe)
3359                 end_update(view, TRUE);
3360         argv_free(view->argv);
3361         return io_open(&view->io, "%s/%s", opt_cdup[0] ? opt_cdup : ".", name);
3364 static bool
3365 begin_update(struct view *view, bool refresh)
3367         if (view->pipe)
3368                 end_update(view, TRUE);
3370         if (!refresh) {
3371                 if (view->ops->prepare) {
3372                         if (!view->ops->prepare(view))
3373                                 return FALSE;
3374                 } else if (!prepare_io(view, NULL, view->ops->argv, TRUE)) {
3375                         return FALSE;
3376                 }
3378                 /* Put the current ref_* value to the view title ref
3379                  * member. This is needed by the blob view. Most other
3380                  * views sets it automatically after loading because the
3381                  * first line is a commit line. */
3382                 string_copy_rev(view->ref, view->id);
3383         }
3385         if (view->argv && view->argv[0] &&
3386             !io_run(&view->io, IO_RD, view->dir, view->argv))
3387                 return FALSE;
3389         setup_update(view, view->id);
3391         return TRUE;
3394 static bool
3395 update_view(struct view *view)
3397         char out_buffer[BUFSIZ * 2];
3398         char *line;
3399         /* Clear the view and redraw everything since the tree sorting
3400          * might have rearranged things. */
3401         bool redraw = view->lines == 0;
3402         bool can_read = TRUE;
3404         if (!view->pipe)
3405                 return TRUE;
3407         if (!io_can_read(view->pipe)) {
3408                 if (view->lines == 0 && view_is_displayed(view)) {
3409                         time_t secs = time(NULL) - view->start_time;
3411                         if (secs > 1 && secs > view->update_secs) {
3412                                 if (view->update_secs == 0)
3413                                         redraw_view(view);
3414                                 update_view_title(view);
3415                                 view->update_secs = secs;
3416                         }
3417                 }
3418                 return TRUE;
3419         }
3421         for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
3422                 if (opt_iconv_in != ICONV_NONE) {
3423                         ICONV_CONST char *inbuf = line;
3424                         size_t inlen = strlen(line) + 1;
3426                         char *outbuf = out_buffer;
3427                         size_t outlen = sizeof(out_buffer);
3429                         size_t ret;
3431                         ret = iconv(opt_iconv_in, &inbuf, &inlen, &outbuf, &outlen);
3432                         if (ret != (size_t) -1)
3433                                 line = out_buffer;
3434                 }
3436                 if (!view->ops->read(view, line)) {
3437                         report("Allocation failure");
3438                         end_update(view, TRUE);
3439                         return FALSE;
3440                 }
3441         }
3443         {
3444                 unsigned long lines = view->lines;
3445                 int digits;
3447                 for (digits = 0; lines; digits++)
3448                         lines /= 10;
3450                 /* Keep the displayed view in sync with line number scaling. */
3451                 if (digits != view->digits) {
3452                         view->digits = digits;
3453                         if (opt_line_number || view->type == VIEW_BLAME)
3454                                 redraw = TRUE;
3455                 }
3456         }
3458         if (io_error(view->pipe)) {
3459                 report("Failed to read: %s", io_strerror(view->pipe));
3460                 end_update(view, TRUE);
3462         } else if (io_eof(view->pipe)) {
3463                 if (view_is_displayed(view))
3464                         report("");
3465                 end_update(view, FALSE);
3466         }
3468         if (restore_view_position(view))
3469                 redraw = TRUE;
3471         if (!view_is_displayed(view))
3472                 return TRUE;
3474         if (redraw)
3475                 redraw_view_from(view, 0);
3476         else
3477                 redraw_view_dirty(view);
3479         /* Update the title _after_ the redraw so that if the redraw picks up a
3480          * commit reference in view->ref it'll be available here. */
3481         update_view_title(view);
3482         return TRUE;
3485 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
3487 static struct line *
3488 add_line_data(struct view *view, void *data, enum line_type type)
3490         struct line *line;
3492         if (!realloc_lines(&view->line, view->lines, 1))
3493                 return NULL;
3495         line = &view->line[view->lines++];
3496         memset(line, 0, sizeof(*line));
3497         line->type = type;
3498         line->data = data;
3499         line->dirty = 1;
3501         return line;
3504 static struct line *
3505 add_line_text(struct view *view, const char *text, enum line_type type)
3507         char *data = text ? strdup(text) : NULL;
3509         return data ? add_line_data(view, data, type) : NULL;
3512 static struct line *
3513 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
3515         char buf[SIZEOF_STR];
3516         va_list args;
3518         va_start(args, fmt);
3519         if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
3520                 buf[0] = 0;
3521         va_end(args);
3523         return buf[0] ? add_line_text(view, buf, type) : NULL;
3526 /*
3527  * View opening
3528  */
3530 enum open_flags {
3531         OPEN_DEFAULT = 0,       /* Use default view switching. */
3532         OPEN_SPLIT = 1,         /* Split current view. */
3533         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
3534         OPEN_REFRESH = 16,      /* Refresh view using previous command. */
3535         OPEN_PREPARED = 32,     /* Open already prepared command. */
3536 };
3538 static void
3539 open_view(struct view *prev, enum request request, enum open_flags flags)
3541         bool split = !!(flags & OPEN_SPLIT);
3542         bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
3543         bool nomaximize = !!(flags & OPEN_REFRESH);
3544         struct view *view = VIEW(request);
3545         int nviews = displayed_views();
3546         struct view *base_view = display[0];
3548         if (view == prev && nviews == 1 && !reload) {
3549                 report("Already in %s view", view->name);
3550                 return;
3551         }
3553         if (view->git_dir && !opt_git_dir[0]) {
3554                 report("The %s view is disabled in pager view", view->name);
3555                 return;
3556         }
3558         if (split) {
3559                 display[1] = view;
3560                 current_view = 1;
3561                 view->parent = prev;
3562         } else if (!nomaximize) {
3563                 /* Maximize the current view. */
3564                 memset(display, 0, sizeof(display));
3565                 current_view = 0;
3566                 display[current_view] = view;
3567         }
3569         /* No prev signals that this is the first loaded view. */
3570         if (prev && view != prev) {
3571                 view->prev = prev;
3572         }
3574         /* Resize the view when switching between split- and full-screen,
3575          * or when switching between two different full-screen views. */
3576         if (nviews != displayed_views() ||
3577             (nviews == 1 && base_view != display[0]))
3578                 resize_display();
3580         if (view->ops->open) {
3581                 if (view->pipe)
3582                         end_update(view, TRUE);
3583                 if (!view->ops->open(view)) {
3584                         report("Failed to load %s view", view->name);
3585                         return;
3586                 }
3587                 restore_view_position(view);
3589         } else if ((reload || strcmp(view->vid, view->id)) &&
3590                    !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3591                 report("Failed to load %s view", view->name);
3592                 return;
3593         }
3595         if (split && prev->lineno - prev->offset >= prev->height) {
3596                 /* Take the title line into account. */
3597                 int lines = prev->lineno - prev->offset - prev->height + 1;
3599                 /* Scroll the view that was split if the current line is
3600                  * outside the new limited view. */
3601                 do_scroll_view(prev, lines);
3602         }
3604         if (prev && view != prev && split && view_is_displayed(prev)) {
3605                 /* "Blur" the previous view. */
3606                 update_view_title(prev);
3607         }
3609         if (view->pipe && view->lines == 0) {
3610                 /* Clear the old view and let the incremental updating refill
3611                  * the screen. */
3612                 werase(view->win);
3613                 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3614                 report("");
3615         } else if (view_is_displayed(view)) {
3616                 redraw_view(view);
3617                 report("");
3618         }
3621 static void
3622 open_external_viewer(const char *argv[], const char *dir)
3624         def_prog_mode();           /* save current tty modes */
3625         endwin();                  /* restore original tty modes */
3626         io_run_fg(argv, dir);
3627         fprintf(stderr, "Press Enter to continue");
3628         getc(opt_tty);
3629         reset_prog_mode();
3630         redraw_display(TRUE);
3633 static void
3634 open_mergetool(const char *file)
3636         const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3638         open_external_viewer(mergetool_argv, opt_cdup);
3641 static void
3642 open_editor(const char *file)
3644         const char *editor_argv[] = { "vi", file, NULL };
3645         const char *editor;
3647         editor = getenv("GIT_EDITOR");
3648         if (!editor && *opt_editor)
3649                 editor = opt_editor;
3650         if (!editor)
3651                 editor = getenv("VISUAL");
3652         if (!editor)
3653                 editor = getenv("EDITOR");
3654         if (!editor)
3655                 editor = "vi";
3657         editor_argv[0] = editor;
3658         open_external_viewer(editor_argv, opt_cdup);
3661 static void
3662 open_run_request(enum request request)
3664         struct run_request *req = get_run_request(request);
3665         const char **argv = NULL;
3667         if (!req) {
3668                 report("Unknown run request");
3669                 return;
3670         }
3672         if (format_argv(&argv, req->argv, TRUE, FALSE))
3673                 open_external_viewer(argv, NULL);
3674         if (argv)
3675                 argv_free(argv);
3676         free(argv);
3679 /*
3680  * User request switch noodle
3681  */
3683 static int
3684 view_driver(struct view *view, enum request request)
3686         int i;
3688         if (request == REQ_NONE)
3689                 return TRUE;
3691         if (request > REQ_NONE) {
3692                 open_run_request(request);
3693                 view_request(view, REQ_REFRESH);
3694                 return TRUE;
3695         }
3697         request = view_request(view, request);
3698         if (request == REQ_NONE)
3699                 return TRUE;
3701         switch (request) {
3702         case REQ_MOVE_UP:
3703         case REQ_MOVE_DOWN:
3704         case REQ_MOVE_PAGE_UP:
3705         case REQ_MOVE_PAGE_DOWN:
3706         case REQ_MOVE_FIRST_LINE:
3707         case REQ_MOVE_LAST_LINE:
3708                 move_view(view, request);
3709                 break;
3711         case REQ_SCROLL_FIRST_COL:
3712         case REQ_SCROLL_LEFT:
3713         case REQ_SCROLL_RIGHT:
3714         case REQ_SCROLL_LINE_DOWN:
3715         case REQ_SCROLL_LINE_UP:
3716         case REQ_SCROLL_PAGE_DOWN:
3717         case REQ_SCROLL_PAGE_UP:
3718                 scroll_view(view, request);
3719                 break;
3721         case REQ_VIEW_BLAME:
3722                 if (!opt_file[0]) {
3723                         report("No file chosen, press %s to open tree view",
3724                                get_key(view->keymap, REQ_VIEW_TREE));
3725                         break;
3726                 }
3727                 open_view(view, request, OPEN_DEFAULT);
3728                 break;
3730         case REQ_VIEW_BLOB:
3731                 if (!ref_blob[0]) {
3732                         report("No file chosen, press %s to open tree view",
3733                                get_key(view->keymap, REQ_VIEW_TREE));
3734                         break;
3735                 }
3736                 open_view(view, request, OPEN_DEFAULT);
3737                 break;
3739         case REQ_VIEW_PAGER:
3740                 if (view == NULL) {
3741                         if (!io_open(&VIEW(REQ_VIEW_PAGER)->io, ""))
3742                                 die("Failed to open stdin");
3743                         open_view(view, request, OPEN_PREPARED);
3744                         break;
3745                 }
3747                 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3748                         report("No pager content, press %s to run command from prompt",
3749                                get_key(view->keymap, REQ_PROMPT));
3750                         break;
3751                 }
3752                 open_view(view, request, OPEN_DEFAULT);
3753                 break;
3755         case REQ_VIEW_STAGE:
3756                 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3757                         report("No stage content, press %s to open the status view and choose file",
3758                                get_key(view->keymap, REQ_VIEW_STATUS));
3759                         break;
3760                 }
3761                 open_view(view, request, OPEN_DEFAULT);
3762                 break;
3764         case REQ_VIEW_STATUS:
3765                 if (opt_is_inside_work_tree == FALSE) {
3766                         report("The status view requires a working tree");
3767                         break;
3768                 }
3769                 open_view(view, request, OPEN_DEFAULT);
3770                 break;
3772         case REQ_VIEW_MAIN:
3773         case REQ_VIEW_DIFF:
3774         case REQ_VIEW_LOG:
3775         case REQ_VIEW_TREE:
3776         case REQ_VIEW_HELP:
3777         case REQ_VIEW_BRANCH:
3778                 open_view(view, request, OPEN_DEFAULT);
3779                 break;
3781         case REQ_NEXT:
3782         case REQ_PREVIOUS:
3783                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3785                 if (view->parent) {
3786                         int line;
3788                         view = view->parent;
3789                         line = view->lineno;
3790                         move_view(view, request);
3791                         if (view_is_displayed(view))
3792                                 update_view_title(view);
3793                         if (line != view->lineno)
3794                                 view_request(view, REQ_ENTER);
3795                 } else {
3796                         move_view(view, request);
3797                 }
3798                 break;
3800         case REQ_VIEW_NEXT:
3801         {
3802                 int nviews = displayed_views();
3803                 int next_view = (current_view + 1) % nviews;
3805                 if (next_view == current_view) {
3806                         report("Only one view is displayed");
3807                         break;
3808                 }
3810                 current_view = next_view;
3811                 /* Blur out the title of the previous view. */
3812                 update_view_title(view);
3813                 report("");
3814                 break;
3815         }
3816         case REQ_REFRESH:
3817                 report("Refreshing is not yet supported for the %s view", view->name);
3818                 break;
3820         case REQ_MAXIMIZE:
3821                 if (displayed_views() == 2)
3822                         maximize_view(view);
3823                 break;
3825         case REQ_OPTIONS:
3826                 open_option_menu();
3827                 break;
3829         case REQ_TOGGLE_LINENO:
3830                 toggle_view_option(&opt_line_number, "line numbers");
3831                 break;
3833         case REQ_TOGGLE_DATE:
3834                 toggle_date();
3835                 break;
3837         case REQ_TOGGLE_AUTHOR:
3838                 toggle_author();
3839                 break;
3841         case REQ_TOGGLE_REV_GRAPH:
3842                 toggle_view_option(&opt_rev_graph, "revision graph display");
3843                 break;
3845         case REQ_TOGGLE_REFS:
3846                 toggle_view_option(&opt_show_refs, "reference display");
3847                 break;
3849         case REQ_TOGGLE_SORT_FIELD:
3850         case REQ_TOGGLE_SORT_ORDER:
3851                 report("Sorting is not yet supported for the %s view", view->name);
3852                 break;
3854         case REQ_SEARCH:
3855         case REQ_SEARCH_BACK:
3856                 search_view(view, request);
3857                 break;
3859         case REQ_FIND_NEXT:
3860         case REQ_FIND_PREV:
3861                 find_next(view, request);
3862                 break;
3864         case REQ_STOP_LOADING:
3865                 foreach_view(view, i) {
3866                         if (view->pipe)
3867                                 report("Stopped loading the %s view", view->name),
3868                         end_update(view, TRUE);
3869                 }
3870                 break;
3872         case REQ_SHOW_VERSION:
3873                 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3874                 return TRUE;
3876         case REQ_SCREEN_REDRAW:
3877                 redraw_display(TRUE);
3878                 break;
3880         case REQ_EDIT:
3881                 report("Nothing to edit");
3882                 break;
3884         case REQ_ENTER:
3885                 report("Nothing to enter");
3886                 break;
3888         case REQ_VIEW_CLOSE:
3889                 /* XXX: Mark closed views by letting view->prev point to the
3890                  * view itself. Parents to closed view should never be
3891                  * followed. */
3892                 if (view->prev && view->prev != view) {
3893                         maximize_view(view->prev);
3894                         view->prev = view;
3895                         break;
3896                 }
3897                 /* Fall-through */
3898         case REQ_QUIT:
3899                 return FALSE;
3901         default:
3902                 report("Unknown key, press %s for help",
3903                        get_key(view->keymap, REQ_VIEW_HELP));
3904                 return TRUE;
3905         }
3907         return TRUE;
3911 /*
3912  * View backend utilities
3913  */
3915 enum sort_field {
3916         ORDERBY_NAME,
3917         ORDERBY_DATE,
3918         ORDERBY_AUTHOR,
3919 };
3921 struct sort_state {
3922         const enum sort_field *fields;
3923         size_t size, current;
3924         bool reverse;
3925 };
3927 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3928 #define get_sort_field(state) ((state).fields[(state).current])
3929 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3931 static void
3932 sort_view(struct view *view, enum request request, struct sort_state *state,
3933           int (*compare)(const void *, const void *))
3935         switch (request) {
3936         case REQ_TOGGLE_SORT_FIELD:
3937                 state->current = (state->current + 1) % state->size;
3938                 break;
3940         case REQ_TOGGLE_SORT_ORDER:
3941                 state->reverse = !state->reverse;
3942                 break;
3943         default:
3944                 die("Not a sort request");
3945         }
3947         qsort(view->line, view->lines, sizeof(*view->line), compare);
3948         redraw_view(view);
3951 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3953 /* Small author cache to reduce memory consumption. It uses binary
3954  * search to lookup or find place to position new entries. No entries
3955  * are ever freed. */
3956 static const char *
3957 get_author(const char *name)
3959         static const char **authors;
3960         static size_t authors_size;
3961         int from = 0, to = authors_size - 1;
3963         while (from <= to) {
3964                 size_t pos = (to + from) / 2;
3965                 int cmp = strcmp(name, authors[pos]);
3967                 if (!cmp)
3968                         return authors[pos];
3970                 if (cmp < 0)
3971                         to = pos - 1;
3972                 else
3973                         from = pos + 1;
3974         }
3976         if (!realloc_authors(&authors, authors_size, 1))
3977                 return NULL;
3978         name = strdup(name);
3979         if (!name)
3980                 return NULL;
3982         memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3983         authors[from] = name;
3984         authors_size++;
3986         return name;
3989 static void
3990 parse_timesec(struct time *time, const char *sec)
3992         time->sec = (time_t) atol(sec);
3995 static void
3996 parse_timezone(struct time *time, const char *zone)
3998         long tz;
4000         tz  = ('0' - zone[1]) * 60 * 60 * 10;
4001         tz += ('0' - zone[2]) * 60 * 60;
4002         tz += ('0' - zone[3]) * 60 * 10;
4003         tz += ('0' - zone[4]) * 60;
4005         if (zone[0] == '-')
4006                 tz = -tz;
4008         time->tz = tz;
4009         time->sec -= tz;
4012 /* Parse author lines where the name may be empty:
4013  *      author  <email@address.tld> 1138474660 +0100
4014  */
4015 static void
4016 parse_author_line(char *ident, const char **author, struct time *time)
4018         char *nameend = strchr(ident, '<');
4019         char *emailend = strchr(ident, '>');
4021         if (nameend && emailend)
4022                 *nameend = *emailend = 0;
4023         ident = chomp_string(ident);
4024         if (!*ident) {
4025                 if (nameend)
4026                         ident = chomp_string(nameend + 1);
4027                 if (!*ident)
4028                         ident = "Unknown";
4029         }
4031         *author = get_author(ident);
4033         /* Parse epoch and timezone */
4034         if (emailend && emailend[1] == ' ') {
4035                 char *secs = emailend + 2;
4036                 char *zone = strchr(secs, ' ');
4038                 parse_timesec(time, secs);
4040                 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
4041                         parse_timezone(time, zone + 1);
4042         }
4045 /*
4046  * Pager backend
4047  */
4049 static bool
4050 pager_draw(struct view *view, struct line *line, unsigned int lineno)
4052         if (opt_line_number && draw_lineno(view, lineno))
4053                 return TRUE;
4055         draw_text(view, line->type, line->data, TRUE);
4056         return TRUE;
4059 static bool
4060 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
4062         const char *describe_argv[] = { "git", "describe", commit_id, NULL };
4063         char ref[SIZEOF_STR];
4065         if (!io_run_buf(describe_argv, ref, sizeof(ref)) || !*ref)
4066                 return TRUE;
4068         /* This is the only fatal call, since it can "corrupt" the buffer. */
4069         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
4070                 return FALSE;
4072         return TRUE;
4075 static void
4076 add_pager_refs(struct view *view, struct line *line)
4078         char buf[SIZEOF_STR];
4079         char *commit_id = (char *)line->data + STRING_SIZE("commit ");
4080         struct ref_list *list;
4081         size_t bufpos = 0, i;
4082         const char *sep = "Refs: ";
4083         bool is_tag = FALSE;
4085         assert(line->type == LINE_COMMIT);
4087         list = get_ref_list(commit_id);
4088         if (!list) {
4089                 if (view->type == VIEW_DIFF)
4090                         goto try_add_describe_ref;
4091                 return;
4092         }
4094         for (i = 0; i < list->size; i++) {
4095                 struct ref *ref = list->refs[i];
4096                 const char *fmt = ref->tag    ? "%s[%s]" :
4097                                   ref->remote ? "%s<%s>" : "%s%s";
4099                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
4100                         return;
4101                 sep = ", ";
4102                 if (ref->tag)
4103                         is_tag = TRUE;
4104         }
4106         if (!is_tag && view->type == VIEW_DIFF) {
4107 try_add_describe_ref:
4108                 /* Add <tag>-g<commit_id> "fake" reference. */
4109                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
4110                         return;
4111         }
4113         if (bufpos == 0)
4114                 return;
4116         add_line_text(view, buf, LINE_PP_REFS);
4119 static bool
4120 pager_read(struct view *view, char *data)
4122         struct line *line;
4124         if (!data)
4125                 return TRUE;
4127         line = add_line_text(view, data, get_line_type(data));
4128         if (!line)
4129                 return FALSE;
4131         if (line->type == LINE_COMMIT &&
4132             (view->type == VIEW_DIFF ||
4133              view->type == VIEW_LOG))
4134                 add_pager_refs(view, line);
4136         return TRUE;
4139 static enum request
4140 pager_request(struct view *view, enum request request, struct line *line)
4142         int split = 0;
4144         if (request != REQ_ENTER)
4145                 return request;
4147         if (line->type == LINE_COMMIT &&
4148            (view->type == VIEW_LOG ||
4149             view->type == VIEW_PAGER)) {
4150                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
4151                 split = 1;
4152         }
4154         /* Always scroll the view even if it was split. That way
4155          * you can use Enter to scroll through the log view and
4156          * split open each commit diff. */
4157         scroll_view(view, REQ_SCROLL_LINE_DOWN);
4159         /* FIXME: A minor workaround. Scrolling the view will call report("")
4160          * but if we are scrolling a non-current view this won't properly
4161          * update the view title. */
4162         if (split)
4163                 update_view_title(view);
4165         return REQ_NONE;
4168 static bool
4169 pager_grep(struct view *view, struct line *line)
4171         const char *text[] = { line->data, NULL };
4173         return grep_text(view, text);
4176 static void
4177 pager_select(struct view *view, struct line *line)
4179         if (line->type == LINE_COMMIT) {
4180                 char *text = (char *)line->data + STRING_SIZE("commit ");
4182                 if (view->type != VIEW_PAGER)
4183                         string_copy_rev(view->ref, text);
4184                 string_copy_rev(ref_commit, text);
4185         }
4188 static struct view_ops pager_ops = {
4189         "line",
4190         NULL,
4191         NULL,
4192         pager_read,
4193         pager_draw,
4194         pager_request,
4195         pager_grep,
4196         pager_select,
4197 };
4199 static const char *log_argv[SIZEOF_ARG] = {
4200         "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
4201 };
4203 static enum request
4204 log_request(struct view *view, enum request request, struct line *line)
4206         switch (request) {
4207         case REQ_REFRESH:
4208                 load_refs();
4209                 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
4210                 return REQ_NONE;
4211         default:
4212                 return pager_request(view, request, line);
4213         }
4216 static struct view_ops log_ops = {
4217         "line",
4218         log_argv,
4219         NULL,
4220         pager_read,
4221         pager_draw,
4222         log_request,
4223         pager_grep,
4224         pager_select,
4225 };
4227 static const char *diff_argv[SIZEOF_ARG] = {
4228         "git", "show", "--pretty=fuller", "--no-color", "--root",
4229                 "--patch-with-stat", "--find-copies-harder", "-C",
4230                 "%(diffargs)", "%(commit)", "--", "%(fileargs)", NULL
4231 };
4233 static bool
4234 diff_read(struct view *view, char *data)
4236         if (!data) {
4237                 /* Fall back to retry if no diff will be shown. */
4238                 if (view->lines == 0 && opt_file_args) {
4239                         int pos = argv_size(view->argv)
4240                                 - argv_size(opt_file_args) - 1;
4242                         if (pos > 0 && !strcmp(view->argv[pos], "--")) {
4243                                 for (; view->argv[pos]; pos++) {
4244                                         free((void *) view->argv[pos]);
4245                                         view->argv[pos] = NULL;
4246                                 }
4248                                 if (view->pipe)
4249                                         io_done(view->pipe);
4250                                 if (io_run(&view->io, IO_RD, view->dir, view->argv))
4251                                         return FALSE;
4252                         }
4253                 }
4254                 return TRUE;
4255         }
4257         return pager_read(view, data);
4260 static struct view_ops diff_ops = {
4261         "line",
4262         diff_argv,
4263         NULL,
4264         diff_read,
4265         pager_draw,
4266         pager_request,
4267         pager_grep,
4268         pager_select,
4269 };
4271 /*
4272  * Help backend
4273  */
4275 static bool help_keymap_hidden[ARRAY_SIZE(keymap_table)];
4277 static bool
4278 help_open_keymap_title(struct view *view, enum keymap keymap)
4280         struct line *line;
4282         line = add_line_format(view, LINE_HELP_KEYMAP, "[%c] %s bindings",
4283                                help_keymap_hidden[keymap] ? '+' : '-',
4284                                enum_name(keymap_table[keymap]));
4285         if (line)
4286                 line->other = keymap;
4288         return help_keymap_hidden[keymap];
4291 static void
4292 help_open_keymap(struct view *view, enum keymap keymap)
4294         const char *group = NULL;
4295         char buf[SIZEOF_STR];
4296         size_t bufpos;
4297         bool add_title = TRUE;
4298         int i;
4300         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
4301                 const char *key = NULL;
4303                 if (req_info[i].request == REQ_NONE)
4304                         continue;
4306                 if (!req_info[i].request) {
4307                         group = req_info[i].help;
4308                         continue;
4309                 }
4311                 key = get_keys(keymap, req_info[i].request, TRUE);
4312                 if (!key || !*key)
4313                         continue;
4315                 if (add_title && help_open_keymap_title(view, keymap))
4316                         return;
4317                 add_title = FALSE;
4319                 if (group) {
4320                         add_line_text(view, group, LINE_HELP_GROUP);
4321                         group = NULL;
4322                 }
4324                 add_line_format(view, LINE_DEFAULT, "    %-25s %-20s %s", key,
4325                                 enum_name(req_info[i]), req_info[i].help);
4326         }
4328         group = "External commands:";
4330         for (i = 0; i < run_requests; i++) {
4331                 struct run_request *req = get_run_request(REQ_NONE + i + 1);
4332                 const char *key;
4333                 int argc;
4335                 if (!req || req->keymap != keymap)
4336                         continue;
4338                 key = get_key_name(req->key);
4339                 if (!*key)
4340                         key = "(no key defined)";
4342                 if (add_title && help_open_keymap_title(view, keymap))
4343                         return;
4344                 if (group) {
4345                         add_line_text(view, group, LINE_HELP_GROUP);
4346                         group = NULL;
4347                 }
4349                 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
4350                         if (!string_format_from(buf, &bufpos, "%s%s",
4351                                                 argc ? " " : "", req->argv[argc]))
4352                                 return;
4354                 add_line_format(view, LINE_DEFAULT, "    %-25s `%s`", key, buf);
4355         }
4358 static bool
4359 help_open(struct view *view)
4361         enum keymap keymap;
4363         reset_view(view);
4364         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
4365         add_line_text(view, "", LINE_DEFAULT);
4367         for (keymap = 0; keymap < ARRAY_SIZE(keymap_table); keymap++)
4368                 help_open_keymap(view, keymap);
4370         return TRUE;
4373 static enum request
4374 help_request(struct view *view, enum request request, struct line *line)
4376         switch (request) {
4377         case REQ_ENTER:
4378                 if (line->type == LINE_HELP_KEYMAP) {
4379                         help_keymap_hidden[line->other] =
4380                                 !help_keymap_hidden[line->other];
4381                         view->p_restore = TRUE;
4382                         open_view(view, REQ_VIEW_HELP, OPEN_REFRESH);
4383                 }
4385                 return REQ_NONE;
4386         default:
4387                 return pager_request(view, request, line);
4388         }
4391 static struct view_ops help_ops = {
4392         "line",
4393         NULL,
4394         help_open,
4395         NULL,
4396         pager_draw,
4397         help_request,
4398         pager_grep,
4399         pager_select,
4400 };
4403 /*
4404  * Tree backend
4405  */
4407 struct tree_stack_entry {
4408         struct tree_stack_entry *prev;  /* Entry below this in the stack */
4409         unsigned long lineno;           /* Line number to restore */
4410         char *name;                     /* Position of name in opt_path */
4411 };
4413 /* The top of the path stack. */
4414 static struct tree_stack_entry *tree_stack = NULL;
4415 unsigned long tree_lineno = 0;
4417 static void
4418 pop_tree_stack_entry(void)
4420         struct tree_stack_entry *entry = tree_stack;
4422         tree_lineno = entry->lineno;
4423         entry->name[0] = 0;
4424         tree_stack = entry->prev;
4425         free(entry);
4428 static void
4429 push_tree_stack_entry(const char *name, unsigned long lineno)
4431         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
4432         size_t pathlen = strlen(opt_path);
4434         if (!entry)
4435                 return;
4437         entry->prev = tree_stack;
4438         entry->name = opt_path + pathlen;
4439         tree_stack = entry;
4441         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
4442                 pop_tree_stack_entry();
4443                 return;
4444         }
4446         /* Move the current line to the first tree entry. */
4447         tree_lineno = 1;
4448         entry->lineno = lineno;
4451 /* Parse output from git-ls-tree(1):
4452  *
4453  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4454  */
4456 #define SIZEOF_TREE_ATTR \
4457         STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4459 #define SIZEOF_TREE_MODE \
4460         STRING_SIZE("100644 ")
4462 #define TREE_ID_OFFSET \
4463         STRING_SIZE("100644 blob ")
4465 struct tree_entry {
4466         char id[SIZEOF_REV];
4467         mode_t mode;
4468         struct time time;               /* Date from the author ident. */
4469         const char *author;             /* Author of the commit. */
4470         char name[1];
4471 };
4473 static const char *
4474 tree_path(const struct line *line)
4476         return ((struct tree_entry *) line->data)->name;
4479 static int
4480 tree_compare_entry(const struct line *line1, const struct line *line2)
4482         if (line1->type != line2->type)
4483                 return line1->type == LINE_TREE_DIR ? -1 : 1;
4484         return strcmp(tree_path(line1), tree_path(line2));
4487 static const enum sort_field tree_sort_fields[] = {
4488         ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4489 };
4490 static struct sort_state tree_sort_state = SORT_STATE(tree_sort_fields);
4492 static int
4493 tree_compare(const void *l1, const void *l2)
4495         const struct line *line1 = (const struct line *) l1;
4496         const struct line *line2 = (const struct line *) l2;
4497         const struct tree_entry *entry1 = ((const struct line *) l1)->data;
4498         const struct tree_entry *entry2 = ((const struct line *) l2)->data;
4500         if (line1->type == LINE_TREE_HEAD)
4501                 return -1;
4502         if (line2->type == LINE_TREE_HEAD)
4503                 return 1;
4505         switch (get_sort_field(tree_sort_state)) {
4506         case ORDERBY_DATE:
4507                 return sort_order(tree_sort_state, timecmp(&entry1->time, &entry2->time));
4509         case ORDERBY_AUTHOR:
4510                 return sort_order(tree_sort_state, strcmp(entry1->author, entry2->author));
4512         case ORDERBY_NAME:
4513         default:
4514                 return sort_order(tree_sort_state, tree_compare_entry(line1, line2));
4515         }
4519 static struct line *
4520 tree_entry(struct view *view, enum line_type type, const char *path,
4521            const char *mode, const char *id)
4523         struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
4524         struct line *line = entry ? add_line_data(view, entry, type) : NULL;
4526         if (!entry || !line) {
4527                 free(entry);
4528                 return NULL;
4529         }
4531         strncpy(entry->name, path, strlen(path));
4532         if (mode)
4533                 entry->mode = strtoul(mode, NULL, 8);
4534         if (id)
4535                 string_copy_rev(entry->id, id);
4537         return line;
4540 static bool
4541 tree_read_date(struct view *view, char *text, bool *read_date)
4543         static const char *author_name;
4544         static struct time author_time;
4546         if (!text && *read_date) {
4547                 *read_date = FALSE;
4548                 return TRUE;
4550         } else if (!text) {
4551                 char *path = *opt_path ? opt_path : ".";
4552                 /* Find next entry to process */
4553                 const char *log_file[] = {
4554                         "git", "log", "--no-color", "--pretty=raw",
4555                                 "--cc", "--raw", view->id, "--", path, NULL
4556                 };
4558                 if (!view->lines) {
4559                         tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
4560                         report("Tree is empty");
4561                         return TRUE;
4562                 }
4564                 if (!start_update(view, log_file, opt_cdup)) {
4565                         report("Failed to load tree data");
4566                         return TRUE;
4567                 }
4569                 *read_date = TRUE;
4570                 return FALSE;
4572         } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
4573                 parse_author_line(text + STRING_SIZE("author "),
4574                                   &author_name, &author_time);
4576         } else if (*text == ':') {
4577                 char *pos;
4578                 size_t annotated = 1;
4579                 size_t i;
4581                 pos = strchr(text, '\t');
4582                 if (!pos)
4583                         return TRUE;
4584                 text = pos + 1;
4585                 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
4586                         text += strlen(opt_path);
4587                 pos = strchr(text, '/');
4588                 if (pos)
4589                         *pos = 0;
4591                 for (i = 1; i < view->lines; i++) {
4592                         struct line *line = &view->line[i];
4593                         struct tree_entry *entry = line->data;
4595                         annotated += !!entry->author;
4596                         if (entry->author || strcmp(entry->name, text))
4597                                 continue;
4599                         entry->author = author_name;
4600                         entry->time = author_time;
4601                         line->dirty = 1;
4602                         break;
4603                 }
4605                 if (annotated == view->lines)
4606                         io_kill(view->pipe);
4607         }
4608         return TRUE;
4611 static bool
4612 tree_read(struct view *view, char *text)
4614         static bool read_date = FALSE;
4615         struct tree_entry *data;
4616         struct line *entry, *line;
4617         enum line_type type;
4618         size_t textlen = text ? strlen(text) : 0;
4619         char *path = text + SIZEOF_TREE_ATTR;
4621         if (read_date || !text)
4622                 return tree_read_date(view, text, &read_date);
4624         if (textlen <= SIZEOF_TREE_ATTR)
4625                 return FALSE;
4626         if (view->lines == 0 &&
4627             !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
4628                 return FALSE;
4630         /* Strip the path part ... */
4631         if (*opt_path) {
4632                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
4633                 size_t striplen = strlen(opt_path);
4635                 if (pathlen > striplen)
4636                         memmove(path, path + striplen,
4637                                 pathlen - striplen + 1);
4639                 /* Insert "link" to parent directory. */
4640                 if (view->lines == 1 &&
4641                     !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
4642                         return FALSE;
4643         }
4645         type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
4646         entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
4647         if (!entry)
4648                 return FALSE;
4649         data = entry->data;
4651         /* Skip "Directory ..." and ".." line. */
4652         for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
4653                 if (tree_compare_entry(line, entry) <= 0)
4654                         continue;
4656                 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4658                 line->data = data;
4659                 line->type = type;
4660                 for (; line <= entry; line++)
4661                         line->dirty = line->cleareol = 1;
4662                 return TRUE;
4663         }
4665         if (tree_lineno > view->lineno) {
4666                 view->lineno = tree_lineno;
4667                 tree_lineno = 0;
4668         }
4670         return TRUE;
4673 static bool
4674 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4676         struct tree_entry *entry = line->data;
4678         if (line->type == LINE_TREE_HEAD) {
4679                 if (draw_text(view, line->type, "Directory path /", TRUE))
4680                         return TRUE;
4681         } else {
4682                 if (draw_mode(view, entry->mode))
4683                         return TRUE;
4685                 if (opt_author && draw_author(view, entry->author))
4686                         return TRUE;
4688                 if (opt_date && draw_date(view, &entry->time))
4689                         return TRUE;
4690         }
4691         if (draw_text(view, line->type, entry->name, TRUE))
4692                 return TRUE;
4693         return TRUE;
4696 static void
4697 open_blob_editor(const char *id)
4699         const char *blob_argv[] = { "git", "cat-file", "blob", id, NULL };
4700         char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4701         int fd = mkstemp(file);
4703         if (fd == -1)
4704                 report("Failed to create temporary file");
4705         else if (!io_run_append(blob_argv, fd))
4706                 report("Failed to save blob data to file");
4707         else
4708                 open_editor(file);
4709         if (fd != -1)
4710                 unlink(file);
4713 static enum request
4714 tree_request(struct view *view, enum request request, struct line *line)
4716         enum open_flags flags;
4717         struct tree_entry *entry = line->data;
4719         switch (request) {
4720         case REQ_VIEW_BLAME:
4721                 if (line->type != LINE_TREE_FILE) {
4722                         report("Blame only supported for files");
4723                         return REQ_NONE;
4724                 }
4726                 string_copy(opt_ref, view->vid);
4727                 return request;
4729         case REQ_EDIT:
4730                 if (line->type != LINE_TREE_FILE) {
4731                         report("Edit only supported for files");
4732                 } else if (!is_head_commit(view->vid)) {
4733                         open_blob_editor(entry->id);
4734                 } else {
4735                         open_editor(opt_file);
4736                 }
4737                 return REQ_NONE;
4739         case REQ_TOGGLE_SORT_FIELD:
4740         case REQ_TOGGLE_SORT_ORDER:
4741                 sort_view(view, request, &tree_sort_state, tree_compare);
4742                 return REQ_NONE;
4744         case REQ_PARENT:
4745                 if (!*opt_path) {
4746                         /* quit view if at top of tree */
4747                         return REQ_VIEW_CLOSE;
4748                 }
4749                 /* fake 'cd  ..' */
4750                 line = &view->line[1];
4751                 break;
4753         case REQ_ENTER:
4754                 break;
4756         default:
4757                 return request;
4758         }
4760         /* Cleanup the stack if the tree view is at a different tree. */
4761         while (!*opt_path && tree_stack)
4762                 pop_tree_stack_entry();
4764         switch (line->type) {
4765         case LINE_TREE_DIR:
4766                 /* Depending on whether it is a subdirectory or parent link
4767                  * mangle the path buffer. */
4768                 if (line == &view->line[1] && *opt_path) {
4769                         pop_tree_stack_entry();
4771                 } else {
4772                         const char *basename = tree_path(line);
4774                         push_tree_stack_entry(basename, view->lineno);
4775                 }
4777                 /* Trees and subtrees share the same ID, so they are not not
4778                  * unique like blobs. */
4779                 flags = OPEN_RELOAD;
4780                 request = REQ_VIEW_TREE;
4781                 break;
4783         case LINE_TREE_FILE:
4784                 flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
4785                 request = REQ_VIEW_BLOB;
4786                 break;
4788         default:
4789                 return REQ_NONE;
4790         }
4792         open_view(view, request, flags);
4793         if (request == REQ_VIEW_TREE)
4794                 view->lineno = tree_lineno;
4796         return REQ_NONE;
4799 static bool
4800 tree_grep(struct view *view, struct line *line)
4802         struct tree_entry *entry = line->data;
4803         const char *text[] = {
4804                 entry->name,
4805                 opt_author ? entry->author : "",
4806                 mkdate(&entry->time, opt_date),
4807                 NULL
4808         };
4810         return grep_text(view, text);
4813 static void
4814 tree_select(struct view *view, struct line *line)
4816         struct tree_entry *entry = line->data;
4818         if (line->type == LINE_TREE_FILE) {
4819                 string_copy_rev(ref_blob, entry->id);
4820                 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4822         } else if (line->type != LINE_TREE_DIR) {
4823                 return;
4824         }
4826         string_copy_rev(view->ref, entry->id);
4829 static bool
4830 tree_prepare(struct view *view)
4832         if (view->lines == 0 && opt_prefix[0]) {
4833                 char *pos = opt_prefix;
4835                 while (pos && *pos) {
4836                         char *end = strchr(pos, '/');
4838                         if (end)
4839                                 *end = 0;
4840                         push_tree_stack_entry(pos, 0);
4841                         pos = end;
4842                         if (end) {
4843                                 *end = '/';
4844                                 pos++;
4845                         }
4846                 }
4848         } else if (strcmp(view->vid, view->id)) {
4849                 opt_path[0] = 0;
4850         }
4852         return prepare_io(view, opt_cdup, view->ops->argv, TRUE);
4855 static const char *tree_argv[SIZEOF_ARG] = {
4856         "git", "ls-tree", "%(commit)", "%(directory)", NULL
4857 };
4859 static struct view_ops tree_ops = {
4860         "file",
4861         tree_argv,
4862         NULL,
4863         tree_read,
4864         tree_draw,
4865         tree_request,
4866         tree_grep,
4867         tree_select,
4868         tree_prepare,
4869 };
4871 static bool
4872 blob_read(struct view *view, char *line)
4874         if (!line)
4875                 return TRUE;
4876         return add_line_text(view, line, LINE_DEFAULT) != NULL;
4879 static enum request
4880 blob_request(struct view *view, enum request request, struct line *line)
4882         switch (request) {
4883         case REQ_EDIT:
4884                 open_blob_editor(view->vid);
4885                 return REQ_NONE;
4886         default:
4887                 return pager_request(view, request, line);
4888         }
4891 static const char *blob_argv[SIZEOF_ARG] = {
4892         "git", "cat-file", "blob", "%(blob)", NULL
4893 };
4895 static struct view_ops blob_ops = {
4896         "line",
4897         blob_argv,
4898         NULL,
4899         blob_read,
4900         pager_draw,
4901         blob_request,
4902         pager_grep,
4903         pager_select,
4904 };
4906 /*
4907  * Blame backend
4908  *
4909  * Loading the blame view is a two phase job:
4910  *
4911  *  1. File content is read either using opt_file from the
4912  *     filesystem or using git-cat-file.
4913  *  2. Then blame information is incrementally added by
4914  *     reading output from git-blame.
4915  */
4917 struct blame_commit {
4918         char id[SIZEOF_REV];            /* SHA1 ID. */
4919         char title[128];                /* First line of the commit message. */
4920         const char *author;             /* Author of the commit. */
4921         struct time time;               /* Date from the author ident. */
4922         char filename[128];             /* Name of file. */
4923         char parent_id[SIZEOF_REV];     /* Parent/previous SHA1 ID. */
4924         char parent_filename[128];      /* Parent/previous name of file. */
4925 };
4927 struct blame {
4928         struct blame_commit *commit;
4929         unsigned long lineno;
4930         char text[1];
4931 };
4933 static bool
4934 blame_open(struct view *view)
4936         char path[SIZEOF_STR];
4937         size_t i;
4939         if (!view->prev && *opt_prefix) {
4940                 string_copy(path, opt_file);
4941                 if (!string_format(opt_file, "%s%s", opt_prefix, path))
4942                         return FALSE;
4943         }
4945         if (*opt_ref || !io_open(&view->io, "%s%s", opt_cdup, opt_file)) {
4946                 const char *blame_cat_file_argv[] = {
4947                         "git", "cat-file", "blob", path, NULL
4948                 };
4950                 if (!string_format(path, "%s:%s", opt_ref, opt_file) ||
4951                     !start_update(view, blame_cat_file_argv, opt_cdup))
4952                         return FALSE;
4953         }
4955         /* First pass: remove multiple references to the same commit. */
4956         for (i = 0; i < view->lines; i++) {
4957                 struct blame *blame = view->line[i].data;
4959                 if (blame->commit && blame->commit->id[0])
4960                         blame->commit->id[0] = 0;
4961                 else
4962                         blame->commit = NULL;
4963         }
4965         /* Second pass: free existing references. */
4966         for (i = 0; i < view->lines; i++) {
4967                 struct blame *blame = view->line[i].data;
4969                 if (blame->commit)
4970                         free(blame->commit);
4971         }
4973         setup_update(view, opt_file);
4974         string_format(view->ref, "%s ...", opt_file);
4976         return TRUE;
4979 static struct blame_commit *
4980 get_blame_commit(struct view *view, const char *id)
4982         size_t i;
4984         for (i = 0; i < view->lines; i++) {
4985                 struct blame *blame = view->line[i].data;
4987                 if (!blame->commit)
4988                         continue;
4990                 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4991                         return blame->commit;
4992         }
4994         {
4995                 struct blame_commit *commit = calloc(1, sizeof(*commit));
4997                 if (commit)
4998                         string_ncopy(commit->id, id, SIZEOF_REV);
4999                 return commit;
5000         }
5003 static bool
5004 parse_number(const char **posref, size_t *number, size_t min, size_t max)
5006         const char *pos = *posref;
5008         *posref = NULL;
5009         pos = strchr(pos + 1, ' ');
5010         if (!pos || !isdigit(pos[1]))
5011                 return FALSE;
5012         *number = atoi(pos + 1);
5013         if (*number < min || *number > max)
5014                 return FALSE;
5016         *posref = pos;
5017         return TRUE;
5020 static struct blame_commit *
5021 parse_blame_commit(struct view *view, const char *text, int *blamed)
5023         struct blame_commit *commit;
5024         struct blame *blame;
5025         const char *pos = text + SIZEOF_REV - 2;
5026         size_t orig_lineno = 0;
5027         size_t lineno;
5028         size_t group;
5030         if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
5031                 return NULL;
5033         if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
5034             !parse_number(&pos, &lineno, 1, view->lines) ||
5035             !parse_number(&pos, &group, 1, view->lines - lineno + 1))
5036                 return NULL;
5038         commit = get_blame_commit(view, text);
5039         if (!commit)
5040                 return NULL;
5042         *blamed += group;
5043         while (group--) {
5044                 struct line *line = &view->line[lineno + group - 1];
5046                 blame = line->data;
5047                 blame->commit = commit;
5048                 blame->lineno = orig_lineno + group - 1;
5049                 line->dirty = 1;
5050         }
5052         return commit;
5055 static bool
5056 blame_read_file(struct view *view, const char *line, bool *read_file)
5058         if (!line) {
5059                 const char *blame_argv[] = {
5060                         "git", "blame", "--incremental",
5061                                 *opt_ref ? opt_ref : "--incremental", "--", opt_file, NULL
5062                 };
5064                 if (view->lines == 0 && !view->prev)
5065                         die("No blame exist for %s", view->vid);
5067                 if (view->lines == 0 || !start_update(view, blame_argv, opt_cdup)) {
5068                         report("Failed to load blame data");
5069                         return TRUE;
5070                 }
5072                 *read_file = FALSE;
5073                 return FALSE;
5075         } else {
5076                 size_t linelen = strlen(line);
5077                 struct blame *blame = malloc(sizeof(*blame) + linelen);
5079                 if (!blame)
5080                         return FALSE;
5082                 blame->commit = NULL;
5083                 strncpy(blame->text, line, linelen);
5084                 blame->text[linelen] = 0;
5085                 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
5086         }
5089 static bool
5090 match_blame_header(const char *name, char **line)
5092         size_t namelen = strlen(name);
5093         bool matched = !strncmp(name, *line, namelen);
5095         if (matched)
5096                 *line += namelen;
5098         return matched;
5101 static bool
5102 blame_read(struct view *view, char *line)
5104         static struct blame_commit *commit = NULL;
5105         static int blamed = 0;
5106         static bool read_file = TRUE;
5108         if (read_file)
5109                 return blame_read_file(view, line, &read_file);
5111         if (!line) {
5112                 /* Reset all! */
5113                 commit = NULL;
5114                 blamed = 0;
5115                 read_file = TRUE;
5116                 string_format(view->ref, "%s", view->vid);
5117                 if (view_is_displayed(view)) {
5118                         update_view_title(view);
5119                         redraw_view_from(view, 0);
5120                 }
5121                 return TRUE;
5122         }
5124         if (!commit) {
5125                 commit = parse_blame_commit(view, line, &blamed);
5126                 string_format(view->ref, "%s %2d%%", view->vid,
5127                               view->lines ? blamed * 100 / view->lines : 0);
5129         } else if (match_blame_header("author ", &line)) {
5130                 commit->author = get_author(line);
5132         } else if (match_blame_header("author-time ", &line)) {
5133                 parse_timesec(&commit->time, line);
5135         } else if (match_blame_header("author-tz ", &line)) {
5136                 parse_timezone(&commit->time, line);
5138         } else if (match_blame_header("summary ", &line)) {
5139                 string_ncopy(commit->title, line, strlen(line));
5141         } else if (match_blame_header("previous ", &line)) {
5142                 if (strlen(line) <= SIZEOF_REV)
5143                         return FALSE;
5144                 string_copy_rev(commit->parent_id, line);
5145                 line += SIZEOF_REV;
5146                 string_ncopy(commit->parent_filename, line, strlen(line));
5148         } else if (match_blame_header("filename ", &line)) {
5149                 string_ncopy(commit->filename, line, strlen(line));
5150                 commit = NULL;
5151         }
5153         return TRUE;
5156 static bool
5157 blame_draw(struct view *view, struct line *line, unsigned int lineno)
5159         struct blame *blame = line->data;
5160         struct time *time = NULL;
5161         const char *id = NULL, *author = NULL;
5163         if (blame->commit && *blame->commit->filename) {
5164                 id = blame->commit->id;
5165                 author = blame->commit->author;
5166                 time = &blame->commit->time;
5167         }
5169         if (opt_date && draw_date(view, time))
5170                 return TRUE;
5172         if (opt_author && draw_author(view, author))
5173                 return TRUE;
5175         if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
5176                 return TRUE;
5178         if (draw_lineno(view, lineno))
5179                 return TRUE;
5181         draw_text(view, LINE_DEFAULT, blame->text, TRUE);
5182         return TRUE;
5185 static bool
5186 check_blame_commit(struct blame *blame, bool check_null_id)
5188         if (!blame->commit)
5189                 report("Commit data not loaded yet");
5190         else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
5191                 report("No commit exist for the selected line");
5192         else
5193                 return TRUE;
5194         return FALSE;
5197 static void
5198 setup_blame_parent_line(struct view *view, struct blame *blame)
5200         char from[SIZEOF_REF + SIZEOF_STR];
5201         char to[SIZEOF_REF + SIZEOF_STR];
5202         const char *diff_tree_argv[] = {
5203                 "git", "diff", "--no-textconv", "--no-extdiff", "--no-color",
5204                         "-U0", from, to, "--", NULL
5205         };
5206         struct io io;
5207         int parent_lineno = -1;
5208         int blamed_lineno = -1;
5209         char *line;
5211         if (!string_format(from, "%s:%s", opt_ref, opt_file) ||
5212             !string_format(to, "%s:%s", blame->commit->id, blame->commit->filename) ||
5213             !io_run(&io, IO_RD, NULL, diff_tree_argv))
5214                 return;
5216         while ((line = io_get(&io, '\n', TRUE))) {
5217                 if (*line == '@') {
5218                         char *pos = strchr(line, '+');
5220                         parent_lineno = atoi(line + 4);
5221                         if (pos)
5222                                 blamed_lineno = atoi(pos + 1);
5224                 } else if (*line == '+' && parent_lineno != -1) {
5225                         if (blame->lineno == blamed_lineno - 1 &&
5226                             !strcmp(blame->text, line + 1)) {
5227                                 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
5228                                 break;
5229                         }
5230                         blamed_lineno++;
5231                 }
5232         }
5234         io_done(&io);
5237 static enum request
5238 blame_request(struct view *view, enum request request, struct line *line)
5240         enum open_flags flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
5241         struct blame *blame = line->data;
5243         switch (request) {
5244         case REQ_VIEW_BLAME:
5245                 if (check_blame_commit(blame, TRUE)) {
5246                         string_copy(opt_ref, blame->commit->id);
5247                         string_copy(opt_file, blame->commit->filename);
5248                         if (blame->lineno)
5249                                 view->lineno = blame->lineno;
5250                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5251                 }
5252                 break;
5254         case REQ_PARENT:
5255                 if (!check_blame_commit(blame, TRUE))
5256                         break;
5257                 if (!*blame->commit->parent_id) {
5258                         report("The selected commit has no parents");
5259                 } else {
5260                         string_copy_rev(opt_ref, blame->commit->parent_id);
5261                         string_copy(opt_file, blame->commit->parent_filename);
5262                         setup_blame_parent_line(view, blame);
5263                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5264                 }
5265                 break;
5267         case REQ_ENTER:
5268                 if (!check_blame_commit(blame, FALSE))
5269                         break;
5271                 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
5272                     !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
5273                         break;
5275                 if (!strcmp(blame->commit->id, NULL_ID)) {
5276                         struct view *diff = VIEW(REQ_VIEW_DIFF);
5277                         const char *diff_index_argv[] = {
5278                                 "git", "diff-index", "--root", "--patch-with-stat",
5279                                         "-C", "-M", "HEAD", "--", view->vid, NULL
5280                         };
5282                         if (!*blame->commit->parent_id) {
5283                                 diff_index_argv[1] = "diff";
5284                                 diff_index_argv[2] = "--no-color";
5285                                 diff_index_argv[6] = "--";
5286                                 diff_index_argv[7] = "/dev/null";
5287                         }
5289                         if (!prepare_update(diff, diff_index_argv, NULL)) {
5290                                 report("Failed to allocate diff command");
5291                                 break;
5292                         }
5293                         flags |= OPEN_PREPARED;
5294                 }
5296                 open_view(view, REQ_VIEW_DIFF, flags);
5297                 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
5298                         string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
5299                 break;
5301         default:
5302                 return request;
5303         }
5305         return REQ_NONE;
5308 static bool
5309 blame_grep(struct view *view, struct line *line)
5311         struct blame *blame = line->data;
5312         struct blame_commit *commit = blame->commit;
5313         const char *text[] = {
5314                 blame->text,
5315                 commit ? commit->title : "",
5316                 commit ? commit->id : "",
5317                 commit && opt_author ? commit->author : "",
5318                 commit ? mkdate(&commit->time, opt_date) : "",
5319                 NULL
5320         };
5322         return grep_text(view, text);
5325 static void
5326 blame_select(struct view *view, struct line *line)
5328         struct blame *blame = line->data;
5329         struct blame_commit *commit = blame->commit;
5331         if (!commit)
5332                 return;
5334         if (!strcmp(commit->id, NULL_ID))
5335                 string_ncopy(ref_commit, "HEAD", 4);
5336         else
5337                 string_copy_rev(ref_commit, commit->id);
5340 static struct view_ops blame_ops = {
5341         "line",
5342         NULL,
5343         blame_open,
5344         blame_read,
5345         blame_draw,
5346         blame_request,
5347         blame_grep,
5348         blame_select,
5349 };
5351 /*
5352  * Branch backend
5353  */
5355 struct branch {
5356         const char *author;             /* Author of the last commit. */
5357         struct time time;               /* Date of the last activity. */
5358         const struct ref *ref;          /* Name and commit ID information. */
5359 };
5361 static const struct ref branch_all;
5363 static const enum sort_field branch_sort_fields[] = {
5364         ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
5365 };
5366 static struct sort_state branch_sort_state = SORT_STATE(branch_sort_fields);
5368 static int
5369 branch_compare(const void *l1, const void *l2)
5371         const struct branch *branch1 = ((const struct line *) l1)->data;
5372         const struct branch *branch2 = ((const struct line *) l2)->data;
5374         switch (get_sort_field(branch_sort_state)) {
5375         case ORDERBY_DATE:
5376                 return sort_order(branch_sort_state, timecmp(&branch1->time, &branch2->time));
5378         case ORDERBY_AUTHOR:
5379                 return sort_order(branch_sort_state, strcmp(branch1->author, branch2->author));
5381         case ORDERBY_NAME:
5382         default:
5383                 return sort_order(branch_sort_state, strcmp(branch1->ref->name, branch2->ref->name));
5384         }
5387 static bool
5388 branch_draw(struct view *view, struct line *line, unsigned int lineno)
5390         struct branch *branch = line->data;
5391         enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
5393         if (opt_date && draw_date(view, &branch->time))
5394                 return TRUE;
5396         if (opt_author && draw_author(view, branch->author))
5397                 return TRUE;
5399         draw_text(view, type, branch->ref == &branch_all ? "All branches" : branch->ref->name, TRUE);
5400         return TRUE;
5403 static enum request
5404 branch_request(struct view *view, enum request request, struct line *line)
5406         struct branch *branch = line->data;
5408         switch (request) {
5409         case REQ_REFRESH:
5410                 load_refs();
5411                 open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
5412                 return REQ_NONE;
5414         case REQ_TOGGLE_SORT_FIELD:
5415         case REQ_TOGGLE_SORT_ORDER:
5416                 sort_view(view, request, &branch_sort_state, branch_compare);
5417                 return REQ_NONE;
5419         case REQ_ENTER:
5420         {
5421                 const struct ref *ref = branch->ref;
5422                 const char *all_branches_argv[] = {
5423                         "git", "log", "--no-color", "--pretty=raw", "--parents",
5424                               "--topo-order",
5425                               ref == &branch_all ? "--all" : ref->name, NULL
5426                 };
5427                 struct view *main_view = VIEW(REQ_VIEW_MAIN);
5429                 if (!prepare_update(main_view, all_branches_argv, NULL))
5430                         report("Failed to load view of all branches");
5431                 else
5432                         open_view(view, REQ_VIEW_MAIN, OPEN_PREPARED | OPEN_SPLIT);
5433                 return REQ_NONE;
5434         }
5435         default:
5436                 return request;
5437         }
5440 static bool
5441 branch_read(struct view *view, char *line)
5443         static char id[SIZEOF_REV];
5444         struct branch *reference;
5445         size_t i;
5447         if (!line)
5448                 return TRUE;
5450         switch (get_line_type(line)) {
5451         case LINE_COMMIT:
5452                 string_copy_rev(id, line + STRING_SIZE("commit "));
5453                 return TRUE;
5455         case LINE_AUTHOR:
5456                 for (i = 0, reference = NULL; i < view->lines; i++) {
5457                         struct branch *branch = view->line[i].data;
5459                         if (strcmp(branch->ref->id, id))
5460                                 continue;
5462                         view->line[i].dirty = TRUE;
5463                         if (reference) {
5464                                 branch->author = reference->author;
5465                                 branch->time = reference->time;
5466                                 continue;
5467                         }
5469                         parse_author_line(line + STRING_SIZE("author "),
5470                                           &branch->author, &branch->time);
5471                         reference = branch;
5472                 }
5473                 return TRUE;
5475         default:
5476                 return TRUE;
5477         }
5481 static bool
5482 branch_open_visitor(void *data, const struct ref *ref)
5484         struct view *view = data;
5485         struct branch *branch;
5487         if (ref->tag || ref->ltag || ref->remote)
5488                 return TRUE;
5490         branch = calloc(1, sizeof(*branch));
5491         if (!branch)
5492                 return FALSE;
5494         branch->ref = ref;
5495         return !!add_line_data(view, branch, LINE_DEFAULT);
5498 static bool
5499 branch_open(struct view *view)
5501         const char *branch_log[] = {
5502                 "git", "log", "--no-color", "--pretty=raw",
5503                         "--simplify-by-decoration", "--all", NULL
5504         };
5506         if (!start_update(view, branch_log, NULL)) {
5507                 report("Failed to load branch data");
5508                 return TRUE;
5509         }
5511         setup_update(view, view->id);
5512         branch_open_visitor(view, &branch_all);
5513         foreach_ref(branch_open_visitor, view);
5514         view->p_restore = TRUE;
5516         return TRUE;
5519 static bool
5520 branch_grep(struct view *view, struct line *line)
5522         struct branch *branch = line->data;
5523         const char *text[] = {
5524                 branch->ref->name,
5525                 branch->author,
5526                 NULL
5527         };
5529         return grep_text(view, text);
5532 static void
5533 branch_select(struct view *view, struct line *line)
5535         struct branch *branch = line->data;
5537         string_copy_rev(view->ref, branch->ref->id);
5538         string_copy_rev(ref_commit, branch->ref->id);
5539         string_copy_rev(ref_head, branch->ref->id);
5540         string_copy_rev(ref_branch, branch->ref->name);
5543 static struct view_ops branch_ops = {
5544         "branch",
5545         NULL,
5546         branch_open,
5547         branch_read,
5548         branch_draw,
5549         branch_request,
5550         branch_grep,
5551         branch_select,
5552 };
5554 /*
5555  * Status backend
5556  */
5558 struct status {
5559         char status;
5560         struct {
5561                 mode_t mode;
5562                 char rev[SIZEOF_REV];
5563                 char name[SIZEOF_STR];
5564         } old;
5565         struct {
5566                 mode_t mode;
5567                 char rev[SIZEOF_REV];
5568                 char name[SIZEOF_STR];
5569         } new;
5570 };
5572 static char status_onbranch[SIZEOF_STR];
5573 static struct status stage_status;
5574 static enum line_type stage_line_type;
5575 static size_t stage_chunks;
5576 static int *stage_chunk;
5578 DEFINE_ALLOCATOR(realloc_ints, int, 32)
5580 /* This should work even for the "On branch" line. */
5581 static inline bool
5582 status_has_none(struct view *view, struct line *line)
5584         return line < view->line + view->lines && !line[1].data;
5587 /* Get fields from the diff line:
5588  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5589  */
5590 static inline bool
5591 status_get_diff(struct status *file, const char *buf, size_t bufsize)
5593         const char *old_mode = buf +  1;
5594         const char *new_mode = buf +  8;
5595         const char *old_rev  = buf + 15;
5596         const char *new_rev  = buf + 56;
5597         const char *status   = buf + 97;
5599         if (bufsize < 98 ||
5600             old_mode[-1] != ':' ||
5601             new_mode[-1] != ' ' ||
5602             old_rev[-1]  != ' ' ||
5603             new_rev[-1]  != ' ' ||
5604             status[-1]   != ' ')
5605                 return FALSE;
5607         file->status = *status;
5609         string_copy_rev(file->old.rev, old_rev);
5610         string_copy_rev(file->new.rev, new_rev);
5612         file->old.mode = strtoul(old_mode, NULL, 8);
5613         file->new.mode = strtoul(new_mode, NULL, 8);
5615         file->old.name[0] = file->new.name[0] = 0;
5617         return TRUE;
5620 static bool
5621 status_run(struct view *view, const char *argv[], char status, enum line_type type)
5623         struct status *unmerged = NULL;
5624         char *buf;
5625         struct io io;
5627         if (!io_run(&io, IO_RD, opt_cdup, argv))
5628                 return FALSE;
5630         add_line_data(view, NULL, type);
5632         while ((buf = io_get(&io, 0, TRUE))) {
5633                 struct status *file = unmerged;
5635                 if (!file) {
5636                         file = calloc(1, sizeof(*file));
5637                         if (!file || !add_line_data(view, file, type))
5638                                 goto error_out;
5639                 }
5641                 /* Parse diff info part. */
5642                 if (status) {
5643                         file->status = status;
5644                         if (status == 'A')
5645                                 string_copy(file->old.rev, NULL_ID);
5647                 } else if (!file->status || file == unmerged) {
5648                         if (!status_get_diff(file, buf, strlen(buf)))
5649                                 goto error_out;
5651                         buf = io_get(&io, 0, TRUE);
5652                         if (!buf)
5653                                 break;
5655                         /* Collapse all modified entries that follow an
5656                          * associated unmerged entry. */
5657                         if (unmerged == file) {
5658                                 unmerged->status = 'U';
5659                                 unmerged = NULL;
5660                         } else if (file->status == 'U') {
5661                                 unmerged = file;
5662                         }
5663                 }
5665                 /* Grab the old name for rename/copy. */
5666                 if (!*file->old.name &&
5667                     (file->status == 'R' || file->status == 'C')) {
5668                         string_ncopy(file->old.name, buf, strlen(buf));
5670                         buf = io_get(&io, 0, TRUE);
5671                         if (!buf)
5672                                 break;
5673                 }
5675                 /* git-ls-files just delivers a NUL separated list of
5676                  * file names similar to the second half of the
5677                  * git-diff-* output. */
5678                 string_ncopy(file->new.name, buf, strlen(buf));
5679                 if (!*file->old.name)
5680                         string_copy(file->old.name, file->new.name);
5681                 file = NULL;
5682         }
5684         if (io_error(&io)) {
5685 error_out:
5686                 io_done(&io);
5687                 return FALSE;
5688         }
5690         if (!view->line[view->lines - 1].data)
5691                 add_line_data(view, NULL, LINE_STAT_NONE);
5693         io_done(&io);
5694         return TRUE;
5697 /* Don't show unmerged entries in the staged section. */
5698 static const char *status_diff_index_argv[] = {
5699         "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5700                              "--cached", "-M", "HEAD", NULL
5701 };
5703 static const char *status_diff_files_argv[] = {
5704         "git", "diff-files", "-z", NULL
5705 };
5707 static const char *status_list_other_argv[] = {
5708         "git", "ls-files", "-z", "--others", "--exclude-standard", opt_prefix, NULL, NULL,
5709 };
5711 static const char *status_list_no_head_argv[] = {
5712         "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5713 };
5715 static const char *update_index_argv[] = {
5716         "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5717 };
5719 /* Restore the previous line number to stay in the context or select a
5720  * line with something that can be updated. */
5721 static void
5722 status_restore(struct view *view)
5724         if (view->p_lineno >= view->lines)
5725                 view->p_lineno = view->lines - 1;
5726         while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
5727                 view->p_lineno++;
5728         while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
5729                 view->p_lineno--;
5731         /* If the above fails, always skip the "On branch" line. */
5732         if (view->p_lineno < view->lines)
5733                 view->lineno = view->p_lineno;
5734         else
5735                 view->lineno = 1;
5737         if (view->lineno < view->offset)
5738                 view->offset = view->lineno;
5739         else if (view->offset + view->height <= view->lineno)
5740                 view->offset = view->lineno - view->height + 1;
5742         view->p_restore = FALSE;
5745 static void
5746 status_update_onbranch(void)
5748         static const char *paths[][2] = {
5749                 { "rebase-apply/rebasing",      "Rebasing" },
5750                 { "rebase-apply/applying",      "Applying mailbox" },
5751                 { "rebase-apply/",              "Rebasing mailbox" },
5752                 { "rebase-merge/interactive",   "Interactive rebase" },
5753                 { "rebase-merge/",              "Rebase merge" },
5754                 { "MERGE_HEAD",                 "Merging" },
5755                 { "BISECT_LOG",                 "Bisecting" },
5756                 { "HEAD",                       "On branch" },
5757         };
5758         char buf[SIZEOF_STR];
5759         struct stat stat;
5760         int i;
5762         if (is_initial_commit()) {
5763                 string_copy(status_onbranch, "Initial commit");
5764                 return;
5765         }
5767         for (i = 0; i < ARRAY_SIZE(paths); i++) {
5768                 char *head = opt_head;
5770                 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5771                     lstat(buf, &stat) < 0)
5772                         continue;
5774                 if (!*opt_head) {
5775                         struct io io;
5777                         if (io_open(&io, "%s/rebase-merge/head-name", opt_git_dir) &&
5778                             io_read_buf(&io, buf, sizeof(buf))) {
5779                                 head = buf;
5780                                 if (!prefixcmp(head, "refs/heads/"))
5781                                         head += STRING_SIZE("refs/heads/");
5782                         }
5783                 }
5785                 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5786                         string_copy(status_onbranch, opt_head);
5787                 return;
5788         }
5790         string_copy(status_onbranch, "Not currently on any branch");
5793 /* First parse staged info using git-diff-index(1), then parse unstaged
5794  * info using git-diff-files(1), and finally untracked files using
5795  * git-ls-files(1). */
5796 static bool
5797 status_open(struct view *view)
5799         reset_view(view);
5801         add_line_data(view, NULL, LINE_STAT_HEAD);
5802         status_update_onbranch();
5804         io_run_bg(update_index_argv);
5806         if (is_initial_commit()) {
5807                 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5808                         return FALSE;
5809         } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5810                 return FALSE;
5811         }
5813         if (!opt_untracked_dirs_content)
5814                 status_list_other_argv[ARRAY_SIZE(status_list_other_argv) - 2] = "--directory";
5816         if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5817             !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5818                 return FALSE;
5820         /* Restore the exact position or use the specialized restore
5821          * mode? */
5822         if (!view->p_restore)
5823                 status_restore(view);
5824         return TRUE;
5827 static bool
5828 status_draw(struct view *view, struct line *line, unsigned int lineno)
5830         struct status *status = line->data;
5831         enum line_type type;
5832         const char *text;
5834         if (!status) {
5835                 switch (line->type) {
5836                 case LINE_STAT_STAGED:
5837                         type = LINE_STAT_SECTION;
5838                         text = "Changes to be committed:";
5839                         break;
5841                 case LINE_STAT_UNSTAGED:
5842                         type = LINE_STAT_SECTION;
5843                         text = "Changed but not updated:";
5844                         break;
5846                 case LINE_STAT_UNTRACKED:
5847                         type = LINE_STAT_SECTION;
5848                         text = "Untracked files:";
5849                         break;
5851                 case LINE_STAT_NONE:
5852                         type = LINE_DEFAULT;
5853                         text = "  (no files)";
5854                         break;
5856                 case LINE_STAT_HEAD:
5857                         type = LINE_STAT_HEAD;
5858                         text = status_onbranch;
5859                         break;
5861                 default:
5862                         return FALSE;
5863                 }
5864         } else {
5865                 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5867                 buf[0] = status->status;
5868                 if (draw_text(view, line->type, buf, TRUE))
5869                         return TRUE;
5870                 type = LINE_DEFAULT;
5871                 text = status->new.name;
5872         }
5874         draw_text(view, type, text, TRUE);
5875         return TRUE;
5878 static enum request
5879 status_load_error(struct view *view, struct view *stage, const char *path)
5881         if (displayed_views() == 2 || display[current_view] != view)
5882                 maximize_view(view);
5883         report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5884         return REQ_NONE;
5887 static enum request
5888 status_enter(struct view *view, struct line *line)
5890         struct status *status = line->data;
5891         const char *oldpath = status ? status->old.name : NULL;
5892         /* Diffs for unmerged entries are empty when passing the new
5893          * path, so leave it empty. */
5894         const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5895         const char *info;
5896         enum open_flags split;
5897         struct view *stage = VIEW(REQ_VIEW_STAGE);
5899         if (line->type == LINE_STAT_NONE ||
5900             (!status && line[1].type == LINE_STAT_NONE)) {
5901                 report("No file to diff");
5902                 return REQ_NONE;
5903         }
5905         switch (line->type) {
5906         case LINE_STAT_STAGED:
5907                 if (is_initial_commit()) {
5908                         const char *no_head_diff_argv[] = {
5909                                 "git", "diff", "--no-color", "--patch-with-stat",
5910                                         "--", "/dev/null", newpath, NULL
5911                         };
5913                         if (!prepare_update(stage, no_head_diff_argv, opt_cdup))
5914                                 return status_load_error(view, stage, newpath);
5915                 } else {
5916                         const char *index_show_argv[] = {
5917                                 "git", "diff-index", "--root", "--patch-with-stat",
5918                                         "-C", "-M", "--cached", "HEAD", "--",
5919                                         oldpath, newpath, NULL
5920                         };
5922                         if (!prepare_update(stage, index_show_argv, opt_cdup))
5923                                 return status_load_error(view, stage, newpath);
5924                 }
5926                 if (status)
5927                         info = "Staged changes to %s";
5928                 else
5929                         info = "Staged changes";
5930                 break;
5932         case LINE_STAT_UNSTAGED:
5933         {
5934                 const char *files_show_argv[] = {
5935                         "git", "diff-files", "--root", "--patch-with-stat",
5936                                 "-C", "-M", "--", oldpath, newpath, NULL
5937                 };
5939                 if (!prepare_update(stage, files_show_argv, opt_cdup))
5940                         return status_load_error(view, stage, newpath);
5941                 if (status)
5942                         info = "Unstaged changes to %s";
5943                 else
5944                         info = "Unstaged changes";
5945                 break;
5946         }
5947         case LINE_STAT_UNTRACKED:
5948                 if (!newpath) {
5949                         report("No file to show");
5950                         return REQ_NONE;
5951                 }
5953                 if (!suffixcmp(status->new.name, -1, "/")) {
5954                         report("Cannot display a directory");
5955                         return REQ_NONE;
5956                 }
5958                 if (!prepare_update_file(stage, newpath))
5959                         return status_load_error(view, stage, newpath);
5960                 info = "Untracked file %s";
5961                 break;
5963         case LINE_STAT_HEAD:
5964                 return REQ_NONE;
5966         default:
5967                 die("line type %d not handled in switch", line->type);
5968         }
5970         split = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
5971         open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5972         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5973                 if (status) {
5974                         stage_status = *status;
5975                 } else {
5976                         memset(&stage_status, 0, sizeof(stage_status));
5977                 }
5979                 stage_line_type = line->type;
5980                 stage_chunks = 0;
5981                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5982         }
5984         return REQ_NONE;
5987 static bool
5988 status_exists(struct status *status, enum line_type type)
5990         struct view *view = VIEW(REQ_VIEW_STATUS);
5991         unsigned long lineno;
5993         for (lineno = 0; lineno < view->lines; lineno++) {
5994                 struct line *line = &view->line[lineno];
5995                 struct status *pos = line->data;
5997                 if (line->type != type)
5998                         continue;
5999                 if (!pos && (!status || !status->status) && line[1].data) {
6000                         select_view_line(view, lineno);
6001                         return TRUE;
6002                 }
6003                 if (pos && !strcmp(status->new.name, pos->new.name)) {
6004                         select_view_line(view, lineno);
6005                         return TRUE;
6006                 }
6007         }
6009         return FALSE;
6013 static bool
6014 status_update_prepare(struct io *io, enum line_type type)
6016         const char *staged_argv[] = {
6017                 "git", "update-index", "-z", "--index-info", NULL
6018         };
6019         const char *others_argv[] = {
6020                 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
6021         };
6023         switch (type) {
6024         case LINE_STAT_STAGED:
6025                 return io_run(io, IO_WR, opt_cdup, staged_argv);
6027         case LINE_STAT_UNSTAGED:
6028         case LINE_STAT_UNTRACKED:
6029                 return io_run(io, IO_WR, opt_cdup, others_argv);
6031         default:
6032                 die("line type %d not handled in switch", type);
6033                 return FALSE;
6034         }
6037 static bool
6038 status_update_write(struct io *io, struct status *status, enum line_type type)
6040         char buf[SIZEOF_STR];
6041         size_t bufsize = 0;
6043         switch (type) {
6044         case LINE_STAT_STAGED:
6045                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
6046                                         status->old.mode,
6047                                         status->old.rev,
6048                                         status->old.name, 0))
6049                         return FALSE;
6050                 break;
6052         case LINE_STAT_UNSTAGED:
6053         case LINE_STAT_UNTRACKED:
6054                 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
6055                         return FALSE;
6056                 break;
6058         default:
6059                 die("line type %d not handled in switch", type);
6060         }
6062         return io_write(io, buf, bufsize);
6065 static bool
6066 status_update_file(struct status *status, enum line_type type)
6068         struct io io;
6069         bool result;
6071         if (!status_update_prepare(&io, type))
6072                 return FALSE;
6074         result = status_update_write(&io, status, type);
6075         return io_done(&io) && result;
6078 static bool
6079 status_update_files(struct view *view, struct line *line)
6081         char buf[sizeof(view->ref)];
6082         struct io io;
6083         bool result = TRUE;
6084         struct line *pos = view->line + view->lines;
6085         int files = 0;
6086         int file, done;
6087         int cursor_y = -1, cursor_x = -1;
6089         if (!status_update_prepare(&io, line->type))
6090                 return FALSE;
6092         for (pos = line; pos < view->line + view->lines && pos->data; pos++)
6093                 files++;
6095         string_copy(buf, view->ref);
6096         getsyx(cursor_y, cursor_x);
6097         for (file = 0, done = 5; result && file < files; line++, file++) {
6098                 int almost_done = file * 100 / files;
6100                 if (almost_done > done) {
6101                         done = almost_done;
6102                         string_format(view->ref, "updating file %u of %u (%d%% done)",
6103                                       file, files, done);
6104                         update_view_title(view);
6105                         setsyx(cursor_y, cursor_x);
6106                         doupdate();
6107                 }
6108                 result = status_update_write(&io, line->data, line->type);
6109         }
6110         string_copy(view->ref, buf);
6112         return io_done(&io) && result;
6115 static bool
6116 status_update(struct view *view)
6118         struct line *line = &view->line[view->lineno];
6120         assert(view->lines);
6122         if (!line->data) {
6123                 /* This should work even for the "On branch" line. */
6124                 if (line < view->line + view->lines && !line[1].data) {
6125                         report("Nothing to update");
6126                         return FALSE;
6127                 }
6129                 if (!status_update_files(view, line + 1)) {
6130                         report("Failed to update file status");
6131                         return FALSE;
6132                 }
6134         } else if (!status_update_file(line->data, line->type)) {
6135                 report("Failed to update file status");
6136                 return FALSE;
6137         }
6139         return TRUE;
6142 static bool
6143 status_revert(struct status *status, enum line_type type, bool has_none)
6145         if (!status || type != LINE_STAT_UNSTAGED) {
6146                 if (type == LINE_STAT_STAGED) {
6147                         report("Cannot revert changes to staged files");
6148                 } else if (type == LINE_STAT_UNTRACKED) {
6149                         report("Cannot revert changes to untracked files");
6150                 } else if (has_none) {
6151                         report("Nothing to revert");
6152                 } else {
6153                         report("Cannot revert changes to multiple files");
6154                 }
6156         } else if (prompt_yesno("Are you sure you want to revert changes?")) {
6157                 char mode[10] = "100644";
6158                 const char *reset_argv[] = {
6159                         "git", "update-index", "--cacheinfo", mode,
6160                                 status->old.rev, status->old.name, NULL
6161                 };
6162                 const char *checkout_argv[] = {
6163                         "git", "checkout", "--", status->old.name, NULL
6164                 };
6166                 if (status->status == 'U') {
6167                         string_format(mode, "%5o", status->old.mode);
6169                         if (status->old.mode == 0 && status->new.mode == 0) {
6170                                 reset_argv[2] = "--force-remove";
6171                                 reset_argv[3] = status->old.name;
6172                                 reset_argv[4] = NULL;
6173                         }
6175                         if (!io_run_fg(reset_argv, opt_cdup))
6176                                 return FALSE;
6177                         if (status->old.mode == 0 && status->new.mode == 0)
6178                                 return TRUE;
6179                 }
6181                 return io_run_fg(checkout_argv, opt_cdup);
6182         }
6184         return FALSE;
6187 static enum request
6188 status_request(struct view *view, enum request request, struct line *line)
6190         struct status *status = line->data;
6192         switch (request) {
6193         case REQ_STATUS_UPDATE:
6194                 if (!status_update(view))
6195                         return REQ_NONE;
6196                 break;
6198         case REQ_STATUS_REVERT:
6199                 if (!status_revert(status, line->type, status_has_none(view, line)))
6200                         return REQ_NONE;
6201                 break;
6203         case REQ_STATUS_MERGE:
6204                 if (!status || status->status != 'U') {
6205                         report("Merging only possible for files with unmerged status ('U').");
6206                         return REQ_NONE;
6207                 }
6208                 open_mergetool(status->new.name);
6209                 break;
6211         case REQ_EDIT:
6212                 if (!status)
6213                         return request;
6214                 if (status->status == 'D') {
6215                         report("File has been deleted.");
6216                         return REQ_NONE;
6217                 }
6219                 open_editor(status->new.name);
6220                 break;
6222         case REQ_VIEW_BLAME:
6223                 if (status)
6224                         opt_ref[0] = 0;
6225                 return request;
6227         case REQ_ENTER:
6228                 /* After returning the status view has been split to
6229                  * show the stage view. No further reloading is
6230                  * necessary. */
6231                 return status_enter(view, line);
6233         case REQ_REFRESH:
6234                 /* Simply reload the view. */
6235                 break;
6237         default:
6238                 return request;
6239         }
6241         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
6243         return REQ_NONE;
6246 static void
6247 status_select(struct view *view, struct line *line)
6249         struct status *status = line->data;
6250         char file[SIZEOF_STR] = "all files";
6251         const char *text;
6252         const char *key;
6254         if (status && !string_format(file, "'%s'", status->new.name))
6255                 return;
6257         if (!status && line[1].type == LINE_STAT_NONE)
6258                 line++;
6260         switch (line->type) {
6261         case LINE_STAT_STAGED:
6262                 text = "Press %s to unstage %s for commit";
6263                 break;
6265         case LINE_STAT_UNSTAGED:
6266                 text = "Press %s to stage %s for commit";
6267                 break;
6269         case LINE_STAT_UNTRACKED:
6270                 text = "Press %s to stage %s for addition";
6271                 break;
6273         case LINE_STAT_HEAD:
6274         case LINE_STAT_NONE:
6275                 text = "Nothing to update";
6276                 break;
6278         default:
6279                 die("line type %d not handled in switch", line->type);
6280         }
6282         if (status && status->status == 'U') {
6283                 text = "Press %s to resolve conflict in %s";
6284                 key = get_key(KEYMAP_STATUS, REQ_STATUS_MERGE);
6286         } else {
6287                 key = get_key(KEYMAP_STATUS, REQ_STATUS_UPDATE);
6288         }
6290         string_format(view->ref, text, key, file);
6291         if (status)
6292                 string_copy(opt_file, status->new.name);
6295 static bool
6296 status_grep(struct view *view, struct line *line)
6298         struct status *status = line->data;
6300         if (status) {
6301                 const char buf[2] = { status->status, 0 };
6302                 const char *text[] = { status->new.name, buf, NULL };
6304                 return grep_text(view, text);
6305         }
6307         return FALSE;
6310 static struct view_ops status_ops = {
6311         "file",
6312         NULL,
6313         status_open,
6314         NULL,
6315         status_draw,
6316         status_request,
6317         status_grep,
6318         status_select,
6319 };
6322 static bool
6323 stage_diff_write(struct io *io, struct line *line, struct line *end)
6325         while (line < end) {
6326                 if (!io_write(io, line->data, strlen(line->data)) ||
6327                     !io_write(io, "\n", 1))
6328                         return FALSE;
6329                 line++;
6330                 if (line->type == LINE_DIFF_CHUNK ||
6331                     line->type == LINE_DIFF_HEADER)
6332                         break;
6333         }
6335         return TRUE;
6338 static struct line *
6339 stage_diff_find(struct view *view, struct line *line, enum line_type type)
6341         for (; view->line < line; line--)
6342                 if (line->type == type)
6343                         return line;
6345         return NULL;
6348 static bool
6349 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
6351         const char *apply_argv[SIZEOF_ARG] = {
6352                 "git", "apply", "--whitespace=nowarn", NULL
6353         };
6354         struct line *diff_hdr;
6355         struct io io;
6356         int argc = 3;
6358         diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
6359         if (!diff_hdr)
6360                 return FALSE;
6362         if (!revert)
6363                 apply_argv[argc++] = "--cached";
6364         if (revert || stage_line_type == LINE_STAT_STAGED)
6365                 apply_argv[argc++] = "-R";
6366         apply_argv[argc++] = "-";
6367         apply_argv[argc++] = NULL;
6368         if (!io_run(&io, IO_WR, opt_cdup, apply_argv))
6369                 return FALSE;
6371         if (!stage_diff_write(&io, diff_hdr, chunk) ||
6372             !stage_diff_write(&io, chunk, view->line + view->lines))
6373                 chunk = NULL;
6375         io_done(&io);
6376         io_run_bg(update_index_argv);
6378         return chunk ? TRUE : FALSE;
6381 static bool
6382 stage_update(struct view *view, struct line *line)
6384         struct line *chunk = NULL;
6386         if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
6387                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6389         if (chunk) {
6390                 if (!stage_apply_chunk(view, chunk, FALSE)) {
6391                         report("Failed to apply chunk");
6392                         return FALSE;
6393                 }
6395         } else if (!stage_status.status) {
6396                 view = VIEW(REQ_VIEW_STATUS);
6398                 for (line = view->line; line < view->line + view->lines; line++)
6399                         if (line->type == stage_line_type)
6400                                 break;
6402                 if (!status_update_files(view, line + 1)) {
6403                         report("Failed to update files");
6404                         return FALSE;
6405                 }
6407         } else if (!status_update_file(&stage_status, stage_line_type)) {
6408                 report("Failed to update file");
6409                 return FALSE;
6410         }
6412         return TRUE;
6415 static bool
6416 stage_revert(struct view *view, struct line *line)
6418         struct line *chunk = NULL;
6420         if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
6421                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6423         if (chunk) {
6424                 if (!prompt_yesno("Are you sure you want to revert changes?"))
6425                         return FALSE;
6427                 if (!stage_apply_chunk(view, chunk, TRUE)) {
6428                         report("Failed to revert chunk");
6429                         return FALSE;
6430                 }
6431                 return TRUE;
6433         } else {
6434                 return status_revert(stage_status.status ? &stage_status : NULL,
6435                                      stage_line_type, FALSE);
6436         }
6440 static void
6441 stage_next(struct view *view, struct line *line)
6443         int i;
6445         if (!stage_chunks) {
6446                 for (line = view->line; line < view->line + view->lines; line++) {
6447                         if (line->type != LINE_DIFF_CHUNK)
6448                                 continue;
6450                         if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
6451                                 report("Allocation failure");
6452                                 return;
6453                         }
6455                         stage_chunk[stage_chunks++] = line - view->line;
6456                 }
6457         }
6459         for (i = 0; i < stage_chunks; i++) {
6460                 if (stage_chunk[i] > view->lineno) {
6461                         do_scroll_view(view, stage_chunk[i] - view->lineno);
6462                         report("Chunk %d of %d", i + 1, stage_chunks);
6463                         return;
6464                 }
6465         }
6467         report("No next chunk found");
6470 static enum request
6471 stage_request(struct view *view, enum request request, struct line *line)
6473         switch (request) {
6474         case REQ_STATUS_UPDATE:
6475                 if (!stage_update(view, line))
6476                         return REQ_NONE;
6477                 break;
6479         case REQ_STATUS_REVERT:
6480                 if (!stage_revert(view, line))
6481                         return REQ_NONE;
6482                 break;
6484         case REQ_STAGE_NEXT:
6485                 if (stage_line_type == LINE_STAT_UNTRACKED) {
6486                         report("File is untracked; press %s to add",
6487                                get_key(KEYMAP_STAGE, REQ_STATUS_UPDATE));
6488                         return REQ_NONE;
6489                 }
6490                 stage_next(view, line);
6491                 return REQ_NONE;
6493         case REQ_EDIT:
6494                 if (!stage_status.new.name[0])
6495                         return request;
6496                 if (stage_status.status == 'D') {
6497                         report("File has been deleted.");
6498                         return REQ_NONE;
6499                 }
6501                 open_editor(stage_status.new.name);
6502                 break;
6504         case REQ_REFRESH:
6505                 /* Reload everything ... */
6506                 break;
6508         case REQ_VIEW_BLAME:
6509                 if (stage_status.new.name[0]) {
6510                         string_copy(opt_file, stage_status.new.name);
6511                         opt_ref[0] = 0;
6512                 }
6513                 return request;
6515         case REQ_ENTER:
6516                 return pager_request(view, request, line);
6518         default:
6519                 return request;
6520         }
6522         VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
6523         open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
6525         /* Check whether the staged entry still exists, and close the
6526          * stage view if it doesn't. */
6527         if (!status_exists(&stage_status, stage_line_type)) {
6528                 status_restore(VIEW(REQ_VIEW_STATUS));
6529                 return REQ_VIEW_CLOSE;
6530         }
6532         if (stage_line_type == LINE_STAT_UNTRACKED) {
6533                 if (!suffixcmp(stage_status.new.name, -1, "/")) {
6534                         report("Cannot display a directory");
6535                         return REQ_NONE;
6536                 }
6538                 if (!prepare_update_file(view, stage_status.new.name)) {
6539                         report("Failed to open file: %s", strerror(errno));
6540                         return REQ_NONE;
6541                 }
6542         }
6543         open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
6545         return REQ_NONE;
6548 static struct view_ops stage_ops = {
6549         "line",
6550         NULL,
6551         NULL,
6552         pager_read,
6553         pager_draw,
6554         stage_request,
6555         pager_grep,
6556         pager_select,
6557 };
6560 /*
6561  * Revision graph
6562  */
6564 struct commit {
6565         char id[SIZEOF_REV];            /* SHA1 ID. */
6566         char title[128];                /* First line of the commit message. */
6567         const char *author;             /* Author of the commit. */
6568         struct time time;               /* Date from the author ident. */
6569         struct ref_list *refs;          /* Repository references. */
6570         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
6571         size_t graph_size;              /* The width of the graph array. */
6572         bool has_parents;               /* Rewritten --parents seen. */
6573 };
6575 /* Size of rev graph with no  "padding" columns */
6576 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6578 struct rev_graph {
6579         struct rev_graph *prev, *next, *parents;
6580         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
6581         size_t size;
6582         struct commit *commit;
6583         size_t pos;
6584         unsigned int boundary:1;
6585 };
6587 /* Parents of the commit being visualized. */
6588 static struct rev_graph graph_parents[4];
6590 /* The current stack of revisions on the graph. */
6591 static struct rev_graph graph_stacks[4] = {
6592         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
6593         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
6594         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
6595         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
6596 };
6598 static inline bool
6599 graph_parent_is_merge(struct rev_graph *graph)
6601         return graph->parents->size > 1;
6604 static inline void
6605 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
6607         struct commit *commit = graph->commit;
6609         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
6610                 commit->graph[commit->graph_size++] = symbol;
6613 static void
6614 clear_rev_graph(struct rev_graph *graph)
6616         graph->boundary = 0;
6617         graph->size = graph->pos = 0;
6618         graph->commit = NULL;
6619         memset(graph->parents, 0, sizeof(*graph->parents));
6622 static void
6623 done_rev_graph(struct rev_graph *graph)
6625         if (graph_parent_is_merge(graph) &&
6626             graph->pos < graph->size - 1 &&
6627             graph->next->size == graph->size + graph->parents->size - 1) {
6628                 size_t i = graph->pos + graph->parents->size - 1;
6630                 graph->commit->graph_size = i * 2;
6631                 while (i < graph->next->size - 1) {
6632                         append_to_rev_graph(graph, ' ');
6633                         append_to_rev_graph(graph, '\\');
6634                         i++;
6635                 }
6636         }
6638         clear_rev_graph(graph);
6641 static void
6642 push_rev_graph(struct rev_graph *graph, const char *parent)
6644         int i;
6646         /* "Collapse" duplicate parents lines.
6647          *
6648          * FIXME: This needs to also update update the drawn graph but
6649          * for now it just serves as a method for pruning graph lines. */
6650         for (i = 0; i < graph->size; i++)
6651                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
6652                         return;
6654         if (graph->size < SIZEOF_REVITEMS) {
6655                 string_copy_rev(graph->rev[graph->size++], parent);
6656         }
6659 static chtype
6660 get_rev_graph_symbol(struct rev_graph *graph)
6662         chtype symbol;
6664         if (graph->boundary)
6665                 symbol = REVGRAPH_BOUND;
6666         else if (graph->parents->size == 0)
6667                 symbol = REVGRAPH_INIT;
6668         else if (graph_parent_is_merge(graph))
6669                 symbol = REVGRAPH_MERGE;
6670         else if (graph->pos >= graph->size)
6671                 symbol = REVGRAPH_BRANCH;
6672         else
6673                 symbol = REVGRAPH_COMMIT;
6675         return symbol;
6678 static void
6679 draw_rev_graph(struct rev_graph *graph)
6681         struct rev_filler {
6682                 chtype separator, line;
6683         };
6684         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
6685         static struct rev_filler fillers[] = {
6686                 { ' ',  '|' },
6687                 { '`',  '.' },
6688                 { '\'', ' ' },
6689                 { '/',  ' ' },
6690         };
6691         chtype symbol = get_rev_graph_symbol(graph);
6692         struct rev_filler *filler;
6693         size_t i;
6695         fillers[DEFAULT].line = opt_line_graphics ? ACS_VLINE : '|';
6696         filler = &fillers[DEFAULT];
6698         for (i = 0; i < graph->pos; i++) {
6699                 append_to_rev_graph(graph, filler->line);
6700                 if (graph_parent_is_merge(graph->prev) &&
6701                     graph->prev->pos == i)
6702                         filler = &fillers[RSHARP];
6704                 append_to_rev_graph(graph, filler->separator);
6705         }
6707         /* Place the symbol for this revision. */
6708         append_to_rev_graph(graph, symbol);
6710         if (graph->prev->size > graph->size)
6711                 filler = &fillers[RDIAG];
6712         else
6713                 filler = &fillers[DEFAULT];
6715         i++;
6717         for (; i < graph->size; i++) {
6718                 append_to_rev_graph(graph, filler->separator);
6719                 append_to_rev_graph(graph, filler->line);
6720                 if (graph_parent_is_merge(graph->prev) &&
6721                     i < graph->prev->pos + graph->parents->size)
6722                         filler = &fillers[RSHARP];
6723                 if (graph->prev->size > graph->size)
6724                         filler = &fillers[LDIAG];
6725         }
6727         if (graph->prev->size > graph->size) {
6728                 append_to_rev_graph(graph, filler->separator);
6729                 if (filler->line != ' ')
6730                         append_to_rev_graph(graph, filler->line);
6731         }
6734 /* Prepare the next rev graph */
6735 static void
6736 prepare_rev_graph(struct rev_graph *graph)
6738         size_t i;
6740         /* First, traverse all lines of revisions up to the active one. */
6741         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6742                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6743                         break;
6745                 push_rev_graph(graph->next, graph->rev[graph->pos]);
6746         }
6748         /* Interleave the new revision parent(s). */
6749         for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6750                 push_rev_graph(graph->next, graph->parents->rev[i]);
6752         /* Lastly, put any remaining revisions. */
6753         for (i = graph->pos + 1; i < graph->size; i++)
6754                 push_rev_graph(graph->next, graph->rev[i]);
6757 static void
6758 update_rev_graph(struct view *view, struct rev_graph *graph)
6760         /* If this is the finalizing update ... */
6761         if (graph->commit)
6762                 prepare_rev_graph(graph);
6764         /* Graph visualization needs a one rev look-ahead,
6765          * so the first update doesn't visualize anything. */
6766         if (!graph->prev->commit)
6767                 return;
6769         if (view->lines > 2)
6770                 view->line[view->lines - 3].dirty = 1;
6771         if (view->lines > 1)
6772                 view->line[view->lines - 2].dirty = 1;
6773         draw_rev_graph(graph->prev);
6774         done_rev_graph(graph->prev->prev);
6778 /*
6779  * Main view backend
6780  */
6782 static const char *main_argv[SIZEOF_ARG] = {
6783         "git", "log", "--no-color", "--pretty=raw", "--parents",
6784                 "--topo-order", "%(diffargs)", "%(revargs)",
6785                 "--", "%(fileargs)", NULL
6786 };
6788 static bool
6789 main_draw(struct view *view, struct line *line, unsigned int lineno)
6791         struct commit *commit = line->data;
6793         if (!commit->author)
6794                 return FALSE;
6796         if (opt_date && draw_date(view, &commit->time))
6797                 return TRUE;
6799         if (opt_author && draw_author(view, commit->author))
6800                 return TRUE;
6802         if (opt_rev_graph && commit->graph_size &&
6803             draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6804                 return TRUE;
6806         if (opt_show_refs && commit->refs) {
6807                 size_t i;
6809                 for (i = 0; i < commit->refs->size; i++) {
6810                         struct ref *ref = commit->refs->refs[i];
6811                         enum line_type type;
6813                         if (ref->head)
6814                                 type = LINE_MAIN_HEAD;
6815                         else if (ref->ltag)
6816                                 type = LINE_MAIN_LOCAL_TAG;
6817                         else if (ref->tag)
6818                                 type = LINE_MAIN_TAG;
6819                         else if (ref->tracked)
6820                                 type = LINE_MAIN_TRACKED;
6821                         else if (ref->remote)
6822                                 type = LINE_MAIN_REMOTE;
6823                         else
6824                                 type = LINE_MAIN_REF;
6826                         if (draw_text(view, type, "[", TRUE) ||
6827                             draw_text(view, type, ref->name, TRUE) ||
6828                             draw_text(view, type, "]", TRUE))
6829                                 return TRUE;
6831                         if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6832                                 return TRUE;
6833                 }
6834         }
6836         draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6837         return TRUE;
6840 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6841 static bool
6842 main_read(struct view *view, char *line)
6844         static struct rev_graph *graph = graph_stacks;
6845         enum line_type type;
6846         struct commit *commit;
6848         if (!line) {
6849                 int i;
6851                 if (!view->lines && !view->prev)
6852                         die("No revisions match the given arguments.");
6853                 if (view->lines > 0) {
6854                         commit = view->line[view->lines - 1].data;
6855                         view->line[view->lines - 1].dirty = 1;
6856                         if (!commit->author) {
6857                                 view->lines--;
6858                                 free(commit);
6859                                 graph->commit = NULL;
6860                         }
6861                 }
6862                 update_rev_graph(view, graph);
6864                 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6865                         clear_rev_graph(&graph_stacks[i]);
6866                 return TRUE;
6867         }
6869         type = get_line_type(line);
6870         if (type == LINE_COMMIT) {
6871                 commit = calloc(1, sizeof(struct commit));
6872                 if (!commit)
6873                         return FALSE;
6875                 line += STRING_SIZE("commit ");
6876                 if (*line == '-') {
6877                         graph->boundary = 1;
6878                         line++;
6879                 }
6881                 string_copy_rev(commit->id, line);
6882                 commit->refs = get_ref_list(commit->id);
6883                 graph->commit = commit;
6884                 add_line_data(view, commit, LINE_MAIN_COMMIT);
6886                 while ((line = strchr(line, ' '))) {
6887                         line++;
6888                         push_rev_graph(graph->parents, line);
6889                         commit->has_parents = TRUE;
6890                 }
6891                 return TRUE;
6892         }
6894         if (!view->lines)
6895                 return TRUE;
6896         commit = view->line[view->lines - 1].data;
6898         switch (type) {
6899         case LINE_PARENT:
6900                 if (commit->has_parents)
6901                         break;
6902                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6903                 break;
6905         case LINE_AUTHOR:
6906                 parse_author_line(line + STRING_SIZE("author "),
6907                                   &commit->author, &commit->time);
6908                 update_rev_graph(view, graph);
6909                 graph = graph->next;
6910                 break;
6912         default:
6913                 /* Fill in the commit title if it has not already been set. */
6914                 if (commit->title[0])
6915                         break;
6917                 /* Require titles to start with a non-space character at the
6918                  * offset used by git log. */
6919                 if (strncmp(line, "    ", 4))
6920                         break;
6921                 line += 4;
6922                 /* Well, if the title starts with a whitespace character,
6923                  * try to be forgiving.  Otherwise we end up with no title. */
6924                 while (isspace(*line))
6925                         line++;
6926                 if (*line == '\0')
6927                         break;
6928                 /* FIXME: More graceful handling of titles; append "..." to
6929                  * shortened titles, etc. */
6931                 string_expand(commit->title, sizeof(commit->title), line, 1);
6932                 view->line[view->lines - 1].dirty = 1;
6933         }
6935         return TRUE;
6938 static enum request
6939 main_request(struct view *view, enum request request, struct line *line)
6941         enum open_flags flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
6943         switch (request) {
6944         case REQ_ENTER:
6945                 if (view_is_displayed(view) && display[0] != view)
6946                         maximize_view(view);
6947                 open_view(view, REQ_VIEW_DIFF, flags);
6948                 break;
6949         case REQ_REFRESH:
6950                 load_refs();
6951                 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6952                 break;
6953         default:
6954                 return request;
6955         }
6957         return REQ_NONE;
6960 static bool
6961 grep_refs(struct ref_list *list, regex_t *regex)
6963         regmatch_t pmatch;
6964         size_t i;
6966         if (!opt_show_refs || !list)
6967                 return FALSE;
6969         for (i = 0; i < list->size; i++) {
6970                 if (regexec(regex, list->refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6971                         return TRUE;
6972         }
6974         return FALSE;
6977 static bool
6978 main_grep(struct view *view, struct line *line)
6980         struct commit *commit = line->data;
6981         const char *text[] = {
6982                 commit->title,
6983                 opt_author ? commit->author : "",
6984                 mkdate(&commit->time, opt_date),
6985                 NULL
6986         };
6988         return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6991 static void
6992 main_select(struct view *view, struct line *line)
6994         struct commit *commit = line->data;
6996         string_copy_rev(view->ref, commit->id);
6997         string_copy_rev(ref_commit, view->ref);
7000 static struct view_ops main_ops = {
7001         "commit",
7002         main_argv,
7003         NULL,
7004         main_read,
7005         main_draw,
7006         main_request,
7007         main_grep,
7008         main_select,
7009 };
7012 /*
7013  * Status management
7014  */
7016 /* Whether or not the curses interface has been initialized. */
7017 static bool cursed = FALSE;
7019 /* Terminal hacks and workarounds. */
7020 static bool use_scroll_redrawwin;
7021 static bool use_scroll_status_wclear;
7023 /* The status window is used for polling keystrokes. */
7024 static WINDOW *status_win;
7026 /* Reading from the prompt? */
7027 static bool input_mode = FALSE;
7029 static bool status_empty = FALSE;
7031 /* Update status and title window. */
7032 static void
7033 report(const char *msg, ...)
7035         struct view *view = display[current_view];
7037         if (input_mode)
7038                 return;
7040         if (!view) {
7041                 char buf[SIZEOF_STR];
7042                 va_list args;
7044                 va_start(args, msg);
7045                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
7046                         buf[sizeof(buf) - 1] = 0;
7047                         buf[sizeof(buf) - 2] = '.';
7048                         buf[sizeof(buf) - 3] = '.';
7049                         buf[sizeof(buf) - 4] = '.';
7050                 }
7051                 va_end(args);
7052                 die("%s", buf);
7053         }
7055         if (!status_empty || *msg) {
7056                 va_list args;
7058                 va_start(args, msg);
7060                 wmove(status_win, 0, 0);
7061                 if (view->has_scrolled && use_scroll_status_wclear)
7062                         wclear(status_win);
7063                 if (*msg) {
7064                         vwprintw(status_win, msg, args);
7065                         status_empty = FALSE;
7066                 } else {
7067                         status_empty = TRUE;
7068                 }
7069                 wclrtoeol(status_win);
7070                 wnoutrefresh(status_win);
7072                 va_end(args);
7073         }
7075         update_view_title(view);
7078 static void
7079 init_display(void)
7081         const char *term;
7082         int x, y;
7084         /* Initialize the curses library */
7085         if (isatty(STDIN_FILENO)) {
7086                 cursed = !!initscr();
7087                 opt_tty = stdin;
7088         } else {
7089                 /* Leave stdin and stdout alone when acting as a pager. */
7090                 opt_tty = fopen("/dev/tty", "r+");
7091                 if (!opt_tty)
7092                         die("Failed to open /dev/tty");
7093                 cursed = !!newterm(NULL, opt_tty, opt_tty);
7094         }
7096         if (!cursed)
7097                 die("Failed to initialize curses");
7099         nonl();         /* Disable conversion and detect newlines from input. */
7100         cbreak();       /* Take input chars one at a time, no wait for \n */
7101         noecho();       /* Don't echo input */
7102         leaveok(stdscr, FALSE);
7104         if (has_colors())
7105                 init_colors();
7107         getmaxyx(stdscr, y, x);
7108         status_win = newwin(1, 0, y - 1, 0);
7109         if (!status_win)
7110                 die("Failed to create status window");
7112         /* Enable keyboard mapping */
7113         keypad(status_win, TRUE);
7114         wbkgdset(status_win, get_line_attr(LINE_STATUS));
7116 #if defined(NCURSES_VERSION_PATCH) && (NCURSES_VERSION_PATCH >= 20080119)
7117         set_tabsize(opt_tab_size);
7118 #else
7119         TABSIZE = opt_tab_size;
7120 #endif
7122         term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
7123         if (term && !strcmp(term, "gnome-terminal")) {
7124                 /* In the gnome-terminal-emulator, the message from
7125                  * scrolling up one line when impossible followed by
7126                  * scrolling down one line causes corruption of the
7127                  * status line. This is fixed by calling wclear. */
7128                 use_scroll_status_wclear = TRUE;
7129                 use_scroll_redrawwin = FALSE;
7131         } else if (term && !strcmp(term, "xrvt-xpm")) {
7132                 /* No problems with full optimizations in xrvt-(unicode)
7133                  * and aterm. */
7134                 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
7136         } else {
7137                 /* When scrolling in (u)xterm the last line in the
7138                  * scrolling direction will update slowly. */
7139                 use_scroll_redrawwin = TRUE;
7140                 use_scroll_status_wclear = FALSE;
7141         }
7144 static int
7145 get_input(int prompt_position)
7147         struct view *view;
7148         int i, key, cursor_y, cursor_x;
7150         if (prompt_position)
7151                 input_mode = TRUE;
7153         while (TRUE) {
7154                 bool loading = FALSE;
7156                 foreach_view (view, i) {
7157                         update_view(view);
7158                         if (view_is_displayed(view) && view->has_scrolled &&
7159                             use_scroll_redrawwin)
7160                                 redrawwin(view->win);
7161                         view->has_scrolled = FALSE;
7162                         if (view->pipe)
7163                                 loading = TRUE;
7164                 }
7166                 /* Update the cursor position. */
7167                 if (prompt_position) {
7168                         getbegyx(status_win, cursor_y, cursor_x);
7169                         cursor_x = prompt_position;
7170                 } else {
7171                         view = display[current_view];
7172                         getbegyx(view->win, cursor_y, cursor_x);
7173                         cursor_x = view->width - 1;
7174                         cursor_y += view->lineno - view->offset;
7175                 }
7176                 setsyx(cursor_y, cursor_x);
7178                 /* Refresh, accept single keystroke of input */
7179                 doupdate();
7180                 nodelay(status_win, loading);
7181                 key = wgetch(status_win);
7183                 /* wgetch() with nodelay() enabled returns ERR when
7184                  * there's no input. */
7185                 if (key == ERR) {
7187                 } else if (key == KEY_RESIZE) {
7188                         int height, width;
7190                         getmaxyx(stdscr, height, width);
7192                         wresize(status_win, 1, width);
7193                         mvwin(status_win, height - 1, 0);
7194                         wnoutrefresh(status_win);
7195                         resize_display();
7196                         redraw_display(TRUE);
7198                 } else {
7199                         input_mode = FALSE;
7200                         return key;
7201                 }
7202         }
7205 static char *
7206 prompt_input(const char *prompt, input_handler handler, void *data)
7208         enum input_status status = INPUT_OK;
7209         static char buf[SIZEOF_STR];
7210         size_t pos = 0;
7212         buf[pos] = 0;
7214         while (status == INPUT_OK || status == INPUT_SKIP) {
7215                 int key;
7217                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
7218                 wclrtoeol(status_win);
7220                 key = get_input(pos + 1);
7221                 switch (key) {
7222                 case KEY_RETURN:
7223                 case KEY_ENTER:
7224                 case '\n':
7225                         status = pos ? INPUT_STOP : INPUT_CANCEL;
7226                         break;
7228                 case KEY_BACKSPACE:
7229                         if (pos > 0)
7230                                 buf[--pos] = 0;
7231                         else
7232                                 status = INPUT_CANCEL;
7233                         break;
7235                 case KEY_ESC:
7236                         status = INPUT_CANCEL;
7237                         break;
7239                 default:
7240                         if (pos >= sizeof(buf)) {
7241                                 report("Input string too long");
7242                                 return NULL;
7243                         }
7245                         status = handler(data, buf, key);
7246                         if (status == INPUT_OK)
7247                                 buf[pos++] = (char) key;
7248                 }
7249         }
7251         /* Clear the status window */
7252         status_empty = FALSE;
7253         report("");
7255         if (status == INPUT_CANCEL)
7256                 return NULL;
7258         buf[pos++] = 0;
7260         return buf;
7263 static enum input_status
7264 prompt_yesno_handler(void *data, char *buf, int c)
7266         if (c == 'y' || c == 'Y')
7267                 return INPUT_STOP;
7268         if (c == 'n' || c == 'N')
7269                 return INPUT_CANCEL;
7270         return INPUT_SKIP;
7273 static bool
7274 prompt_yesno(const char *prompt)
7276         char prompt2[SIZEOF_STR];
7278         if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
7279                 return FALSE;
7281         return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
7284 static enum input_status
7285 read_prompt_handler(void *data, char *buf, int c)
7287         return isprint(c) ? INPUT_OK : INPUT_SKIP;
7290 static char *
7291 read_prompt(const char *prompt)
7293         return prompt_input(prompt, read_prompt_handler, NULL);
7296 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected)
7298         enum input_status status = INPUT_OK;
7299         int size = 0;
7301         while (items[size].text)
7302                 size++;
7304         while (status == INPUT_OK) {
7305                 const struct menu_item *item = &items[*selected];
7306                 int key;
7307                 int i;
7309                 mvwprintw(status_win, 0, 0, "%s (%d of %d) ",
7310                           prompt, *selected + 1, size);
7311                 if (item->hotkey)
7312                         wprintw(status_win, "[%c] ", (char) item->hotkey);
7313                 wprintw(status_win, "%s", item->text);
7314                 wclrtoeol(status_win);
7316                 key = get_input(COLS - 1);
7317                 switch (key) {
7318                 case KEY_RETURN:
7319                 case KEY_ENTER:
7320                 case '\n':
7321                         status = INPUT_STOP;
7322                         break;
7324                 case KEY_LEFT:
7325                 case KEY_UP:
7326                         *selected = *selected - 1;
7327                         if (*selected < 0)
7328                                 *selected = size - 1;
7329                         break;
7331                 case KEY_RIGHT:
7332                 case KEY_DOWN:
7333                         *selected = (*selected + 1) % size;
7334                         break;
7336                 case KEY_ESC:
7337                         status = INPUT_CANCEL;
7338                         break;
7340                 default:
7341                         for (i = 0; items[i].text; i++)
7342                                 if (items[i].hotkey == key) {
7343                                         *selected = i;
7344                                         status = INPUT_STOP;
7345                                         break;
7346                                 }
7347                 }
7348         }
7350         /* Clear the status window */
7351         status_empty = FALSE;
7352         report("");
7354         return status != INPUT_CANCEL;
7357 /*
7358  * Repository properties
7359  */
7361 static struct ref **refs = NULL;
7362 static size_t refs_size = 0;
7363 static struct ref *refs_head = NULL;
7365 static struct ref_list **ref_lists = NULL;
7366 static size_t ref_lists_size = 0;
7368 DEFINE_ALLOCATOR(realloc_refs, struct ref *, 256)
7369 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
7370 DEFINE_ALLOCATOR(realloc_ref_lists, struct ref_list *, 8)
7372 static int
7373 compare_refs(const void *ref1_, const void *ref2_)
7375         const struct ref *ref1 = *(const struct ref **)ref1_;
7376         const struct ref *ref2 = *(const struct ref **)ref2_;
7378         if (ref1->tag != ref2->tag)
7379                 return ref2->tag - ref1->tag;
7380         if (ref1->ltag != ref2->ltag)
7381                 return ref2->ltag - ref2->ltag;
7382         if (ref1->head != ref2->head)
7383                 return ref2->head - ref1->head;
7384         if (ref1->tracked != ref2->tracked)
7385                 return ref2->tracked - ref1->tracked;
7386         if (ref1->remote != ref2->remote)
7387                 return ref2->remote - ref1->remote;
7388         return strcmp(ref1->name, ref2->name);
7391 static void
7392 foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data)
7394         size_t i;
7396         for (i = 0; i < refs_size; i++)
7397                 if (!visitor(data, refs[i]))
7398                         break;
7401 static struct ref *
7402 get_ref_head()
7404         return refs_head;
7407 static struct ref_list *
7408 get_ref_list(const char *id)
7410         struct ref_list *list;
7411         size_t i;
7413         for (i = 0; i < ref_lists_size; i++)
7414                 if (!strcmp(id, ref_lists[i]->id))
7415                         return ref_lists[i];
7417         if (!realloc_ref_lists(&ref_lists, ref_lists_size, 1))
7418                 return NULL;
7419         list = calloc(1, sizeof(*list));
7420         if (!list)
7421                 return NULL;
7423         for (i = 0; i < refs_size; i++) {
7424                 if (!strcmp(id, refs[i]->id) &&
7425                     realloc_refs_list(&list->refs, list->size, 1))
7426                         list->refs[list->size++] = refs[i];
7427         }
7429         if (!list->refs) {
7430                 free(list);
7431                 return NULL;
7432         }
7434         qsort(list->refs, list->size, sizeof(*list->refs), compare_refs);
7435         ref_lists[ref_lists_size++] = list;
7436         return list;
7439 static int
7440 read_ref(char *id, size_t idlen, char *name, size_t namelen)
7442         struct ref *ref = NULL;
7443         bool tag = FALSE;
7444         bool ltag = FALSE;
7445         bool remote = FALSE;
7446         bool tracked = FALSE;
7447         bool head = FALSE;
7448         int from = 0, to = refs_size - 1;
7450         if (!prefixcmp(name, "refs/tags/")) {
7451                 if (!suffixcmp(name, namelen, "^{}")) {
7452                         namelen -= 3;
7453                         name[namelen] = 0;
7454                 } else {
7455                         ltag = TRUE;
7456                 }
7458                 tag = TRUE;
7459                 namelen -= STRING_SIZE("refs/tags/");
7460                 name    += STRING_SIZE("refs/tags/");
7462         } else if (!prefixcmp(name, "refs/remotes/")) {
7463                 remote = TRUE;
7464                 namelen -= STRING_SIZE("refs/remotes/");
7465                 name    += STRING_SIZE("refs/remotes/");
7466                 tracked  = !strcmp(opt_remote, name);
7468         } else if (!prefixcmp(name, "refs/heads/")) {
7469                 namelen -= STRING_SIZE("refs/heads/");
7470                 name    += STRING_SIZE("refs/heads/");
7471                 if (!strncmp(opt_head, name, namelen))
7472                         return OK;
7474         } else if (!strcmp(name, "HEAD")) {
7475                 head     = TRUE;
7476                 if (*opt_head) {
7477                         namelen  = strlen(opt_head);
7478                         name     = opt_head;
7479                 }
7480         }
7482         /* If we are reloading or it's an annotated tag, replace the
7483          * previous SHA1 with the resolved commit id; relies on the fact
7484          * git-ls-remote lists the commit id of an annotated tag right
7485          * before the commit id it points to. */
7486         while (from <= to) {
7487                 size_t pos = (to + from) / 2;
7488                 int cmp = strcmp(name, refs[pos]->name);
7490                 if (!cmp) {
7491                         ref = refs[pos];
7492                         break;
7493                 }
7495                 if (cmp < 0)
7496                         to = pos - 1;
7497                 else
7498                         from = pos + 1;
7499         }
7501         if (!ref) {
7502                 if (!realloc_refs(&refs, refs_size, 1))
7503                         return ERR;
7504                 ref = calloc(1, sizeof(*ref) + namelen);
7505                 if (!ref)
7506                         return ERR;
7507                 memmove(refs + from + 1, refs + from,
7508                         (refs_size - from) * sizeof(*refs));
7509                 refs[from] = ref;
7510                 strncpy(ref->name, name, namelen);
7511                 refs_size++;
7512         }
7514         ref->head = head;
7515         ref->tag = tag;
7516         ref->ltag = ltag;
7517         ref->remote = remote;
7518         ref->tracked = tracked;
7519         string_copy_rev(ref->id, id);
7521         if (head)
7522                 refs_head = ref;
7523         return OK;
7526 static int
7527 load_refs(void)
7529         const char *head_argv[] = {
7530                 "git", "symbolic-ref", "HEAD", NULL
7531         };
7532         static const char *ls_remote_argv[SIZEOF_ARG] = {
7533                 "git", "ls-remote", opt_git_dir, NULL
7534         };
7535         static bool init = FALSE;
7536         size_t i;
7538         if (!init) {
7539                 if (!argv_from_env(ls_remote_argv, "TIG_LS_REMOTE"))
7540                         die("TIG_LS_REMOTE contains too many arguments");
7541                 init = TRUE;
7542         }
7544         if (!*opt_git_dir)
7545                 return OK;
7547         if (io_run_buf(head_argv, opt_head, sizeof(opt_head)) &&
7548             !prefixcmp(opt_head, "refs/heads/")) {
7549                 char *offset = opt_head + STRING_SIZE("refs/heads/");
7551                 memmove(opt_head, offset, strlen(offset) + 1);
7552         }
7554         refs_head = NULL;
7555         for (i = 0; i < refs_size; i++)
7556                 refs[i]->id[0] = 0;
7558         if (io_run_load(ls_remote_argv, "\t", read_ref) == ERR)
7559                 return ERR;
7561         /* Update the ref lists to reflect changes. */
7562         for (i = 0; i < ref_lists_size; i++) {
7563                 struct ref_list *list = ref_lists[i];
7564                 size_t old, new;
7566                 for (old = new = 0; old < list->size; old++)
7567                         if (!strcmp(list->id, list->refs[old]->id))
7568                                 list->refs[new++] = list->refs[old];
7569                 list->size = new;
7570         }
7572         return OK;
7575 static void
7576 set_remote_branch(const char *name, const char *value, size_t valuelen)
7578         if (!strcmp(name, ".remote")) {
7579                 string_ncopy(opt_remote, value, valuelen);
7581         } else if (*opt_remote && !strcmp(name, ".merge")) {
7582                 size_t from = strlen(opt_remote);
7584                 if (!prefixcmp(value, "refs/heads/"))
7585                         value += STRING_SIZE("refs/heads/");
7587                 if (!string_format_from(opt_remote, &from, "/%s", value))
7588                         opt_remote[0] = 0;
7589         }
7592 static void
7593 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
7595         const char *argv[SIZEOF_ARG] = { name, "=" };
7596         int argc = 1 + (cmd == option_set_command);
7597         int error = ERR;
7599         if (!argv_from_string(argv, &argc, value))
7600                 config_msg = "Too many option arguments";
7601         else
7602                 error = cmd(argc, argv);
7604         if (error == ERR)
7605                 warn("Option 'tig.%s': %s", name, config_msg);
7608 static bool
7609 set_environment_variable(const char *name, const char *value)
7611         size_t len = strlen(name) + 1 + strlen(value) + 1;
7612         char *env = malloc(len);
7614         if (env &&
7615             string_nformat(env, len, NULL, "%s=%s", name, value) &&
7616             putenv(env) == 0)
7617                 return TRUE;
7618         free(env);
7619         return FALSE;
7622 static void
7623 set_work_tree(const char *value)
7625         char cwd[SIZEOF_STR];
7627         if (!getcwd(cwd, sizeof(cwd)))
7628                 die("Failed to get cwd path: %s", strerror(errno));
7629         if (chdir(opt_git_dir) < 0)
7630                 die("Failed to chdir(%s): %s", strerror(errno));
7631         if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7632                 die("Failed to get git path: %s", strerror(errno));
7633         if (chdir(cwd) < 0)
7634                 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7635         if (chdir(value) < 0)
7636                 die("Failed to chdir(%s): %s", value, strerror(errno));
7637         if (!getcwd(cwd, sizeof(cwd)))
7638                 die("Failed to get cwd path: %s", strerror(errno));
7639         if (!set_environment_variable("GIT_WORK_TREE", cwd))
7640                 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7641         if (!set_environment_variable("GIT_DIR", opt_git_dir))
7642                 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7643         opt_is_inside_work_tree = TRUE;
7646 static int
7647 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
7649         if (!strcmp(name, "i18n.commitencoding"))
7650                 string_ncopy(opt_encoding, value, valuelen);
7652         else if (!strcmp(name, "core.editor"))
7653                 string_ncopy(opt_editor, value, valuelen);
7655         else if (!strcmp(name, "core.worktree"))
7656                 set_work_tree(value);
7658         else if (!prefixcmp(name, "tig.color."))
7659                 set_repo_config_option(name + 10, value, option_color_command);
7661         else if (!prefixcmp(name, "tig.bind."))
7662                 set_repo_config_option(name + 9, value, option_bind_command);
7664         else if (!prefixcmp(name, "tig."))
7665                 set_repo_config_option(name + 4, value, option_set_command);
7667         else if (*opt_head && !prefixcmp(name, "branch.") &&
7668                  !strncmp(name + 7, opt_head, strlen(opt_head)))
7669                 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7671         return OK;
7674 static int
7675 load_git_config(void)
7677         const char *config_list_argv[] = { "git", "config", "--list", NULL };
7679         return io_run_load(config_list_argv, "=", read_repo_config_option);
7682 static int
7683 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
7685         if (!opt_git_dir[0]) {
7686                 string_ncopy(opt_git_dir, name, namelen);
7688         } else if (opt_is_inside_work_tree == -1) {
7689                 /* This can be 3 different values depending on the
7690                  * version of git being used. If git-rev-parse does not
7691                  * understand --is-inside-work-tree it will simply echo
7692                  * the option else either "true" or "false" is printed.
7693                  * Default to true for the unknown case. */
7694                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7696         } else if (*name == '.') {
7697                 string_ncopy(opt_cdup, name, namelen);
7699         } else {
7700                 string_ncopy(opt_prefix, name, namelen);
7701         }
7703         return OK;
7706 static int
7707 load_repo_info(void)
7709         const char *rev_parse_argv[] = {
7710                 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7711                         "--show-cdup", "--show-prefix", NULL
7712         };
7714         return io_run_load(rev_parse_argv, "=", read_repo_info);
7718 /*
7719  * Main
7720  */
7722 static const char usage[] =
7723 "tig " TIG_VERSION " (" __DATE__ ")\n"
7724 "\n"
7725 "Usage: tig        [options] [revs] [--] [paths]\n"
7726 "   or: tig show   [options] [revs] [--] [paths]\n"
7727 "   or: tig blame  [rev] path\n"
7728 "   or: tig status\n"
7729 "   or: tig <      [git command output]\n"
7730 "\n"
7731 "Options:\n"
7732 "  -v, --version   Show version and exit\n"
7733 "  -h, --help      Show help message and exit";
7735 static void __NORETURN
7736 quit(int sig)
7738         /* XXX: Restore tty modes and let the OS cleanup the rest! */
7739         if (cursed)
7740                 endwin();
7741         exit(0);
7744 static void __NORETURN
7745 die(const char *err, ...)
7747         va_list args;
7749         endwin();
7751         va_start(args, err);
7752         fputs("tig: ", stderr);
7753         vfprintf(stderr, err, args);
7754         fputs("\n", stderr);
7755         va_end(args);
7757         exit(1);
7760 static void
7761 warn(const char *msg, ...)
7763         va_list args;
7765         va_start(args, msg);
7766         fputs("tig warning: ", stderr);
7767         vfprintf(stderr, msg, args);
7768         fputs("\n", stderr);
7769         va_end(args);
7772 static const char ***filter_args;
7774 static int
7775 read_filter_args(char *name, size_t namelen, char *value, size_t valuelen)
7777         return argv_append(filter_args, name) ? OK : ERR;
7780 static void
7781 filter_rev_parse(const char ***args, const char *arg1, const char *arg2, const char *argv[])
7783         const char *rev_parse_argv[SIZEOF_ARG] = { "git", "rev-parse", arg1, arg2 };
7784         const char **all_argv = NULL;
7786         filter_args = args;
7787         if (!argv_append_array(&all_argv, rev_parse_argv) ||
7788             !argv_append_array(&all_argv, argv) ||
7789             !io_run_load(all_argv, "\n", read_filter_args) == ERR)
7790                 die("Failed to split arguments");
7791         argv_free(all_argv);
7792         free(all_argv);
7795 static void
7796 filter_options(const char *argv[])
7798         filter_rev_parse(&opt_file_args, "--no-revs", "--no-flags", argv);
7799         filter_rev_parse(&opt_diff_args, "--no-revs", "--flags", argv);
7800         filter_rev_parse(&opt_rev_args, "--symbolic", "--revs-only", argv);
7803 static enum request
7804 parse_options(int argc, const char *argv[])
7806         enum request request = REQ_VIEW_MAIN;
7807         const char *subcommand;
7808         bool seen_dashdash = FALSE;
7809         const char **filter_argv = NULL;
7810         int i;
7812         if (!isatty(STDIN_FILENO))
7813                 return REQ_VIEW_PAGER;
7815         if (argc <= 1)
7816                 return REQ_VIEW_MAIN;
7818         subcommand = argv[1];
7819         if (!strcmp(subcommand, "status")) {
7820                 if (argc > 2)
7821                         warn("ignoring arguments after `%s'", subcommand);
7822                 return REQ_VIEW_STATUS;
7824         } else if (!strcmp(subcommand, "blame")) {
7825                 if (argc <= 2 || argc > 4)
7826                         die("invalid number of options to blame\n\n%s", usage);
7828                 i = 2;
7829                 if (argc == 4) {
7830                         string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7831                         i++;
7832                 }
7834                 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7835                 return REQ_VIEW_BLAME;
7837         } else if (!strcmp(subcommand, "show")) {
7838                 request = REQ_VIEW_DIFF;
7840         } else {
7841                 subcommand = NULL;
7842         }
7844         for (i = 1 + !!subcommand; i < argc; i++) {
7845                 const char *opt = argv[i];
7847                 if (seen_dashdash) {
7848                         argv_append(&opt_file_args, opt);
7849                         continue;
7851                 } else if (!strcmp(opt, "--")) {
7852                         seen_dashdash = TRUE;
7853                         continue;
7855                 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7856                         printf("tig version %s\n", TIG_VERSION);
7857                         quit(0);
7859                 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7860                         printf("%s\n", usage);
7861                         quit(0);
7863                 } else if (!strcmp(opt, "--all")) {
7864                         argv_append(&opt_rev_args, opt);
7865                         continue;
7866                 }
7868                 if (!argv_append(&filter_argv, opt))
7869                         die("command too long");
7870         }
7872         if (filter_argv)
7873                 filter_options(filter_argv);
7875         return request;
7878 int
7879 main(int argc, const char *argv[])
7881         const char *codeset = "UTF-8";
7882         enum request request = parse_options(argc, argv);
7883         struct view *view;
7884         size_t i;
7886         signal(SIGINT, quit);
7887         signal(SIGPIPE, SIG_IGN);
7889         if (setlocale(LC_ALL, "")) {
7890                 codeset = nl_langinfo(CODESET);
7891         }
7893         if (load_repo_info() == ERR)
7894                 die("Failed to load repo info.");
7896         if (load_options() == ERR)
7897                 die("Failed to load user config.");
7899         if (load_git_config() == ERR)
7900                 die("Failed to load repo config.");
7902         /* Require a git repository unless when running in pager mode. */
7903         if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7904                 die("Not a git repository");
7906         if (*opt_encoding && strcmp(codeset, "UTF-8")) {
7907                 opt_iconv_in = iconv_open("UTF-8", opt_encoding);
7908                 if (opt_iconv_in == ICONV_NONE)
7909                         die("Failed to initialize character set conversion");
7910         }
7912         if (codeset && strcmp(codeset, "UTF-8")) {
7913                 opt_iconv_out = iconv_open(codeset, "UTF-8");
7914                 if (opt_iconv_out == ICONV_NONE)
7915                         die("Failed to initialize character set conversion");
7916         }
7918         if (load_refs() == ERR)
7919                 die("Failed to load refs.");
7921         foreach_view (view, i) {
7922                 if (getenv(view->cmd_env))
7923                         warn("Use of the %s environment variable is deprecated,"
7924                              " use options or TIG_DIFF_ARGS instead",
7925                              view->cmd_env);
7926                 if (!argv_from_env(view->ops->argv, view->cmd_env))
7927                         die("Too many arguments in the `%s` environment variable",
7928                             view->cmd_env);
7929         }
7931         init_display();
7933         while (view_driver(display[current_view], request)) {
7934                 int key = get_input(0);
7936                 view = display[current_view];
7937                 request = get_keybinding(view->keymap, key);
7939                 /* Some low-level request handling. This keeps access to
7940                  * status_win restricted. */
7941                 switch (request) {
7942                 case REQ_NONE:
7943                         report("Unknown key, press %s for help",
7944                                get_key(view->keymap, REQ_VIEW_HELP));
7945                         break;
7946                 case REQ_PROMPT:
7947                 {
7948                         char *cmd = read_prompt(":");
7950                         if (cmd && isdigit(*cmd)) {
7951                                 int lineno = view->lineno + 1;
7953                                 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7954                                         select_view_line(view, lineno - 1);
7955                                         report("");
7956                                 } else {
7957                                         report("Unable to parse '%s' as a line number", cmd);
7958                                 }
7960                         } else if (cmd) {
7961                                 struct view *next = VIEW(REQ_VIEW_PAGER);
7962                                 const char *argv[SIZEOF_ARG] = { "git" };
7963                                 int argc = 1;
7965                                 /* When running random commands, initially show the
7966                                  * command in the title. However, it maybe later be
7967                                  * overwritten if a commit line is selected. */
7968                                 string_ncopy(next->ref, cmd, strlen(cmd));
7970                                 if (!argv_from_string(argv, &argc, cmd)) {
7971                                         report("Too many arguments");
7972                                 } else if (!prepare_update(next, argv, NULL)) {
7973                                         report("Failed to format command");
7974                                 } else {
7975                                         open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7976                                 }
7977                         }
7979                         request = REQ_NONE;
7980                         break;
7981                 }
7982                 case REQ_SEARCH:
7983                 case REQ_SEARCH_BACK:
7984                 {
7985                         const char *prompt = request == REQ_SEARCH ? "/" : "?";
7986                         char *search = read_prompt(prompt);
7988                         if (search)
7989                                 string_ncopy(opt_search, search, strlen(search));
7990                         else if (*opt_search)
7991                                 request = request == REQ_SEARCH ?
7992                                         REQ_FIND_NEXT :
7993                                         REQ_FIND_PREV;
7994                         else
7995                                 request = REQ_NONE;
7996                         break;
7997                 }
7998                 default:
7999                         break;
8000                 }
8001         }
8003         quit(0);
8005         return 0;