Code

Don't show out-of-sight tildes
[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)
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                         if (!argv_append_array(dst_argv, opt_rev_args))
3250                                 break;
3251                         continue;
3252                 }
3254                 while (arg) {
3255                         char *next = strstr(arg, "%(");
3256                         int len = next - arg;
3257                         const char *value;
3259                         if (!next || !replace) {
3260                                 len = strlen(arg);
3261                                 value = "";
3263                         } else {
3264                                 value = format_arg(next);
3266                                 if (!value) {
3267                                         return FALSE;
3268                                 }
3269                         }
3271                         if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
3272                                 return FALSE;
3274                         arg = next && replace ? strchr(next, ')') + 1 : NULL;
3275                 }
3277                 if (!argv_append(dst_argv, buf))
3278                         break;
3279         }
3281         return src_argv[argc] == NULL;
3284 static bool
3285 restore_view_position(struct view *view)
3287         if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
3288                 return FALSE;
3290         /* Changing the view position cancels the restoring. */
3291         /* FIXME: Changing back to the first line is not detected. */
3292         if (view->offset != 0 || view->lineno != 0) {
3293                 view->p_restore = FALSE;
3294                 return FALSE;
3295         }
3297         if (goto_view_line(view, view->p_offset, view->p_lineno) &&
3298             view_is_displayed(view))
3299                 werase(view->win);
3301         view->yoffset = view->p_yoffset;
3302         view->p_restore = FALSE;
3304         return TRUE;
3307 static void
3308 end_update(struct view *view, bool force)
3310         if (!view->pipe)
3311                 return;
3312         while (!view->ops->read(view, NULL))
3313                 if (!force)
3314                         return;
3315         if (force)
3316                 io_kill(view->pipe);
3317         io_done(view->pipe);
3318         view->pipe = NULL;
3321 static void
3322 setup_update(struct view *view, const char *vid)
3324         reset_view(view);
3325         string_copy_rev(view->vid, vid);
3326         view->pipe = &view->io;
3327         view->start_time = time(NULL);
3330 static bool
3331 prepare_io(struct view *view, const char *dir, const char *argv[], bool replace)
3333         view->dir = dir;
3334         return format_argv(&view->argv, argv, replace);
3337 static bool
3338 prepare_update(struct view *view, const char *argv[], const char *dir)
3340         if (view->pipe)
3341                 end_update(view, TRUE);
3342         return prepare_io(view, dir, argv, FALSE);
3345 static bool
3346 start_update(struct view *view, const char **argv, const char *dir)
3348         if (view->pipe)
3349                 io_done(view->pipe);
3350         return prepare_io(view, dir, argv, FALSE) &&
3351                io_run(&view->io, IO_RD, dir, view->argv);
3354 static bool
3355 prepare_update_file(struct view *view, const char *name)
3357         if (view->pipe)
3358                 end_update(view, TRUE);
3359         argv_free(view->argv);
3360         return io_open(&view->io, "%s/%s", opt_cdup[0] ? opt_cdup : ".", name);
3363 static bool
3364 begin_update(struct view *view, bool refresh)
3366         if (view->pipe)
3367                 end_update(view, TRUE);
3369         if (!refresh) {
3370                 if (view->ops->prepare) {
3371                         if (!view->ops->prepare(view))
3372                                 return FALSE;
3373                 } else if (!prepare_io(view, NULL, view->ops->argv, TRUE)) {
3374                         return FALSE;
3375                 }
3377                 /* Put the current ref_* value to the view title ref
3378                  * member. This is needed by the blob view. Most other
3379                  * views sets it automatically after loading because the
3380                  * first line is a commit line. */
3381                 string_copy_rev(view->ref, view->id);
3382         }
3384         if (view->argv && view->argv[0] &&
3385             !io_run(&view->io, IO_RD, view->dir, view->argv))
3386                 return FALSE;
3388         setup_update(view, view->id);
3390         return TRUE;
3393 static bool
3394 update_view(struct view *view)
3396         char out_buffer[BUFSIZ * 2];
3397         char *line;
3398         /* Clear the view and redraw everything since the tree sorting
3399          * might have rearranged things. */
3400         bool redraw = view->lines == 0;
3401         bool can_read = TRUE;
3403         if (!view->pipe)
3404                 return TRUE;
3406         if (!io_can_read(view->pipe)) {
3407                 if (view->lines == 0 && view_is_displayed(view)) {
3408                         time_t secs = time(NULL) - view->start_time;
3410                         if (secs > 1 && secs > view->update_secs) {
3411                                 if (view->update_secs == 0)
3412                                         redraw_view(view);
3413                                 update_view_title(view);
3414                                 view->update_secs = secs;
3415                         }
3416                 }
3417                 return TRUE;
3418         }
3420         for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
3421                 if (opt_iconv_in != ICONV_NONE) {
3422                         ICONV_CONST char *inbuf = line;
3423                         size_t inlen = strlen(line) + 1;
3425                         char *outbuf = out_buffer;
3426                         size_t outlen = sizeof(out_buffer);
3428                         size_t ret;
3430                         ret = iconv(opt_iconv_in, &inbuf, &inlen, &outbuf, &outlen);
3431                         if (ret != (size_t) -1)
3432                                 line = out_buffer;
3433                 }
3435                 if (!view->ops->read(view, line)) {
3436                         report("Allocation failure");
3437                         end_update(view, TRUE);
3438                         return FALSE;
3439                 }
3440         }
3442         {
3443                 unsigned long lines = view->lines;
3444                 int digits;
3446                 for (digits = 0; lines; digits++)
3447                         lines /= 10;
3449                 /* Keep the displayed view in sync with line number scaling. */
3450                 if (digits != view->digits) {
3451                         view->digits = digits;
3452                         if (opt_line_number || view->type == VIEW_BLAME)
3453                                 redraw = TRUE;
3454                 }
3455         }
3457         if (io_error(view->pipe)) {
3458                 report("Failed to read: %s", io_strerror(view->pipe));
3459                 end_update(view, TRUE);
3461         } else if (io_eof(view->pipe)) {
3462                 if (view_is_displayed(view))
3463                         report("");
3464                 end_update(view, FALSE);
3465         }
3467         if (restore_view_position(view))
3468                 redraw = TRUE;
3470         if (!view_is_displayed(view))
3471                 return TRUE;
3473         if (redraw)
3474                 redraw_view_from(view, 0);
3475         else
3476                 redraw_view_dirty(view);
3478         /* Update the title _after_ the redraw so that if the redraw picks up a
3479          * commit reference in view->ref it'll be available here. */
3480         update_view_title(view);
3481         return TRUE;
3484 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
3486 static struct line *
3487 add_line_data(struct view *view, void *data, enum line_type type)
3489         struct line *line;
3491         if (!realloc_lines(&view->line, view->lines, 1))
3492                 return NULL;
3494         line = &view->line[view->lines++];
3495         memset(line, 0, sizeof(*line));
3496         line->type = type;
3497         line->data = data;
3498         line->dirty = 1;
3500         return line;
3503 static struct line *
3504 add_line_text(struct view *view, const char *text, enum line_type type)
3506         char *data = text ? strdup(text) : NULL;
3508         return data ? add_line_data(view, data, type) : NULL;
3511 static struct line *
3512 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
3514         char buf[SIZEOF_STR];
3515         va_list args;
3517         va_start(args, fmt);
3518         if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
3519                 buf[0] = 0;
3520         va_end(args);
3522         return buf[0] ? add_line_text(view, buf, type) : NULL;
3525 /*
3526  * View opening
3527  */
3529 enum open_flags {
3530         OPEN_DEFAULT = 0,       /* Use default view switching. */
3531         OPEN_SPLIT = 1,         /* Split current view. */
3532         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
3533         OPEN_REFRESH = 16,      /* Refresh view using previous command. */
3534         OPEN_PREPARED = 32,     /* Open already prepared command. */
3535 };
3537 static void
3538 open_view(struct view *prev, enum request request, enum open_flags flags)
3540         bool split = !!(flags & OPEN_SPLIT);
3541         bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
3542         bool nomaximize = !!(flags & OPEN_REFRESH);
3543         struct view *view = VIEW(request);
3544         int nviews = displayed_views();
3545         struct view *base_view = display[0];
3547         if (view == prev && nviews == 1 && !reload) {
3548                 report("Already in %s view", view->name);
3549                 return;
3550         }
3552         if (view->git_dir && !opt_git_dir[0]) {
3553                 report("The %s view is disabled in pager view", view->name);
3554                 return;
3555         }
3557         if (split) {
3558                 display[1] = view;
3559                 current_view = 1;
3560                 view->parent = prev;
3561         } else if (!nomaximize) {
3562                 /* Maximize the current view. */
3563                 memset(display, 0, sizeof(display));
3564                 current_view = 0;
3565                 display[current_view] = view;
3566         }
3568         /* No prev signals that this is the first loaded view. */
3569         if (prev && view != prev) {
3570                 view->prev = prev;
3571         }
3573         /* Resize the view when switching between split- and full-screen,
3574          * or when switching between two different full-screen views. */
3575         if (nviews != displayed_views() ||
3576             (nviews == 1 && base_view != display[0]))
3577                 resize_display();
3579         if (view->ops->open) {
3580                 if (view->pipe)
3581                         end_update(view, TRUE);
3582                 if (!view->ops->open(view)) {
3583                         report("Failed to load %s view", view->name);
3584                         return;
3585                 }
3586                 restore_view_position(view);
3588         } else if ((reload || strcmp(view->vid, view->id)) &&
3589                    !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3590                 report("Failed to load %s view", view->name);
3591                 return;
3592         }
3594         if (split && prev->lineno - prev->offset >= prev->height) {
3595                 /* Take the title line into account. */
3596                 int lines = prev->lineno - prev->offset - prev->height + 1;
3598                 /* Scroll the view that was split if the current line is
3599                  * outside the new limited view. */
3600                 do_scroll_view(prev, lines);
3601         }
3603         if (prev && view != prev && split && view_is_displayed(prev)) {
3604                 /* "Blur" the previous view. */
3605                 update_view_title(prev);
3606         }
3608         if (view->pipe && view->lines == 0) {
3609                 /* Clear the old view and let the incremental updating refill
3610                  * the screen. */
3611                 werase(view->win);
3612                 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3613                 report("");
3614         } else if (view_is_displayed(view)) {
3615                 redraw_view(view);
3616                 report("");
3617         }
3620 static void
3621 open_external_viewer(const char *argv[], const char *dir)
3623         def_prog_mode();           /* save current tty modes */
3624         endwin();                  /* restore original tty modes */
3625         io_run_fg(argv, dir);
3626         fprintf(stderr, "Press Enter to continue");
3627         getc(opt_tty);
3628         reset_prog_mode();
3629         redraw_display(TRUE);
3632 static void
3633 open_mergetool(const char *file)
3635         const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3637         open_external_viewer(mergetool_argv, opt_cdup);
3640 static void
3641 open_editor(const char *file)
3643         const char *editor_argv[] = { "vi", file, NULL };
3644         const char *editor;
3646         editor = getenv("GIT_EDITOR");
3647         if (!editor && *opt_editor)
3648                 editor = opt_editor;
3649         if (!editor)
3650                 editor = getenv("VISUAL");
3651         if (!editor)
3652                 editor = getenv("EDITOR");
3653         if (!editor)
3654                 editor = "vi";
3656         editor_argv[0] = editor;
3657         open_external_viewer(editor_argv, opt_cdup);
3660 static void
3661 open_run_request(enum request request)
3663         struct run_request *req = get_run_request(request);
3664         const char **argv = NULL;
3666         if (!req) {
3667                 report("Unknown run request");
3668                 return;
3669         }
3671         if (format_argv(&argv, req->argv, TRUE))
3672                 open_external_viewer(argv, NULL);
3673         if (argv)
3674                 argv_free(argv);
3675         free(argv);
3678 /*
3679  * User request switch noodle
3680  */
3682 static int
3683 view_driver(struct view *view, enum request request)
3685         int i;
3687         if (request == REQ_NONE)
3688                 return TRUE;
3690         if (request > REQ_NONE) {
3691                 open_run_request(request);
3692                 view_request(view, REQ_REFRESH);
3693                 return TRUE;
3694         }
3696         request = view_request(view, request);
3697         if (request == REQ_NONE)
3698                 return TRUE;
3700         switch (request) {
3701         case REQ_MOVE_UP:
3702         case REQ_MOVE_DOWN:
3703         case REQ_MOVE_PAGE_UP:
3704         case REQ_MOVE_PAGE_DOWN:
3705         case REQ_MOVE_FIRST_LINE:
3706         case REQ_MOVE_LAST_LINE:
3707                 move_view(view, request);
3708                 break;
3710         case REQ_SCROLL_FIRST_COL:
3711         case REQ_SCROLL_LEFT:
3712         case REQ_SCROLL_RIGHT:
3713         case REQ_SCROLL_LINE_DOWN:
3714         case REQ_SCROLL_LINE_UP:
3715         case REQ_SCROLL_PAGE_DOWN:
3716         case REQ_SCROLL_PAGE_UP:
3717                 scroll_view(view, request);
3718                 break;
3720         case REQ_VIEW_BLAME:
3721                 if (!opt_file[0]) {
3722                         report("No file chosen, press %s to open tree view",
3723                                get_key(view->keymap, REQ_VIEW_TREE));
3724                         break;
3725                 }
3726                 open_view(view, request, OPEN_DEFAULT);
3727                 break;
3729         case REQ_VIEW_BLOB:
3730                 if (!ref_blob[0]) {
3731                         report("No file chosen, press %s to open tree view",
3732                                get_key(view->keymap, REQ_VIEW_TREE));
3733                         break;
3734                 }
3735                 open_view(view, request, OPEN_DEFAULT);
3736                 break;
3738         case REQ_VIEW_PAGER:
3739                 if (view == NULL) {
3740                         if (!io_open(&VIEW(REQ_VIEW_PAGER)->io, ""))
3741                                 die("Failed to open stdin");
3742                         open_view(view, request, OPEN_PREPARED);
3743                         break;
3744                 }
3746                 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3747                         report("No pager content, press %s to run command from prompt",
3748                                get_key(view->keymap, REQ_PROMPT));
3749                         break;
3750                 }
3751                 open_view(view, request, OPEN_DEFAULT);
3752                 break;
3754         case REQ_VIEW_STAGE:
3755                 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3756                         report("No stage content, press %s to open the status view and choose file",
3757                                get_key(view->keymap, REQ_VIEW_STATUS));
3758                         break;
3759                 }
3760                 open_view(view, request, OPEN_DEFAULT);
3761                 break;
3763         case REQ_VIEW_STATUS:
3764                 if (opt_is_inside_work_tree == FALSE) {
3765                         report("The status view requires a working tree");
3766                         break;
3767                 }
3768                 open_view(view, request, OPEN_DEFAULT);
3769                 break;
3771         case REQ_VIEW_MAIN:
3772         case REQ_VIEW_DIFF:
3773         case REQ_VIEW_LOG:
3774         case REQ_VIEW_TREE:
3775         case REQ_VIEW_HELP:
3776         case REQ_VIEW_BRANCH:
3777                 open_view(view, request, OPEN_DEFAULT);
3778                 break;
3780         case REQ_NEXT:
3781         case REQ_PREVIOUS:
3782                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3784                 if (view->parent) {
3785                         int line;
3787                         view = view->parent;
3788                         line = view->lineno;
3789                         move_view(view, request);
3790                         if (view_is_displayed(view))
3791                                 update_view_title(view);
3792                         if (line != view->lineno)
3793                                 view_request(view, REQ_ENTER);
3794                 } else {
3795                         move_view(view, request);
3796                 }
3797                 break;
3799         case REQ_VIEW_NEXT:
3800         {
3801                 int nviews = displayed_views();
3802                 int next_view = (current_view + 1) % nviews;
3804                 if (next_view == current_view) {
3805                         report("Only one view is displayed");
3806                         break;
3807                 }
3809                 current_view = next_view;
3810                 /* Blur out the title of the previous view. */
3811                 update_view_title(view);
3812                 report("");
3813                 break;
3814         }
3815         case REQ_REFRESH:
3816                 report("Refreshing is not yet supported for the %s view", view->name);
3817                 break;
3819         case REQ_MAXIMIZE:
3820                 if (displayed_views() == 2)
3821                         maximize_view(view);
3822                 break;
3824         case REQ_OPTIONS:
3825                 open_option_menu();
3826                 break;
3828         case REQ_TOGGLE_LINENO:
3829                 toggle_view_option(&opt_line_number, "line numbers");
3830                 break;
3832         case REQ_TOGGLE_DATE:
3833                 toggle_date();
3834                 break;
3836         case REQ_TOGGLE_AUTHOR:
3837                 toggle_author();
3838                 break;
3840         case REQ_TOGGLE_REV_GRAPH:
3841                 toggle_view_option(&opt_rev_graph, "revision graph display");
3842                 break;
3844         case REQ_TOGGLE_REFS:
3845                 toggle_view_option(&opt_show_refs, "reference display");
3846                 break;
3848         case REQ_TOGGLE_SORT_FIELD:
3849         case REQ_TOGGLE_SORT_ORDER:
3850                 report("Sorting is not yet supported for the %s view", view->name);
3851                 break;
3853         case REQ_SEARCH:
3854         case REQ_SEARCH_BACK:
3855                 search_view(view, request);
3856                 break;
3858         case REQ_FIND_NEXT:
3859         case REQ_FIND_PREV:
3860                 find_next(view, request);
3861                 break;
3863         case REQ_STOP_LOADING:
3864                 foreach_view(view, i) {
3865                         if (view->pipe)
3866                                 report("Stopped loading the %s view", view->name),
3867                         end_update(view, TRUE);
3868                 }
3869                 break;
3871         case REQ_SHOW_VERSION:
3872                 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3873                 return TRUE;
3875         case REQ_SCREEN_REDRAW:
3876                 redraw_display(TRUE);
3877                 break;
3879         case REQ_EDIT:
3880                 report("Nothing to edit");
3881                 break;
3883         case REQ_ENTER:
3884                 report("Nothing to enter");
3885                 break;
3887         case REQ_VIEW_CLOSE:
3888                 /* XXX: Mark closed views by letting view->prev point to the
3889                  * view itself. Parents to closed view should never be
3890                  * followed. */
3891                 if (view->prev && view->prev != view) {
3892                         maximize_view(view->prev);
3893                         view->prev = view;
3894                         break;
3895                 }
3896                 /* Fall-through */
3897         case REQ_QUIT:
3898                 return FALSE;
3900         default:
3901                 report("Unknown key, press %s for help",
3902                        get_key(view->keymap, REQ_VIEW_HELP));
3903                 return TRUE;
3904         }
3906         return TRUE;
3910 /*
3911  * View backend utilities
3912  */
3914 enum sort_field {
3915         ORDERBY_NAME,
3916         ORDERBY_DATE,
3917         ORDERBY_AUTHOR,
3918 };
3920 struct sort_state {
3921         const enum sort_field *fields;
3922         size_t size, current;
3923         bool reverse;
3924 };
3926 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3927 #define get_sort_field(state) ((state).fields[(state).current])
3928 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3930 static void
3931 sort_view(struct view *view, enum request request, struct sort_state *state,
3932           int (*compare)(const void *, const void *))
3934         switch (request) {
3935         case REQ_TOGGLE_SORT_FIELD:
3936                 state->current = (state->current + 1) % state->size;
3937                 break;
3939         case REQ_TOGGLE_SORT_ORDER:
3940                 state->reverse = !state->reverse;
3941                 break;
3942         default:
3943                 die("Not a sort request");
3944         }
3946         qsort(view->line, view->lines, sizeof(*view->line), compare);
3947         redraw_view(view);
3950 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3952 /* Small author cache to reduce memory consumption. It uses binary
3953  * search to lookup or find place to position new entries. No entries
3954  * are ever freed. */
3955 static const char *
3956 get_author(const char *name)
3958         static const char **authors;
3959         static size_t authors_size;
3960         int from = 0, to = authors_size - 1;
3962         while (from <= to) {
3963                 size_t pos = (to + from) / 2;
3964                 int cmp = strcmp(name, authors[pos]);
3966                 if (!cmp)
3967                         return authors[pos];
3969                 if (cmp < 0)
3970                         to = pos - 1;
3971                 else
3972                         from = pos + 1;
3973         }
3975         if (!realloc_authors(&authors, authors_size, 1))
3976                 return NULL;
3977         name = strdup(name);
3978         if (!name)
3979                 return NULL;
3981         memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3982         authors[from] = name;
3983         authors_size++;
3985         return name;
3988 static void
3989 parse_timesec(struct time *time, const char *sec)
3991         time->sec = (time_t) atol(sec);
3994 static void
3995 parse_timezone(struct time *time, const char *zone)
3997         long tz;
3999         tz  = ('0' - zone[1]) * 60 * 60 * 10;
4000         tz += ('0' - zone[2]) * 60 * 60;
4001         tz += ('0' - zone[3]) * 60 * 10;
4002         tz += ('0' - zone[4]) * 60;
4004         if (zone[0] == '-')
4005                 tz = -tz;
4007         time->tz = tz;
4008         time->sec -= tz;
4011 /* Parse author lines where the name may be empty:
4012  *      author  <email@address.tld> 1138474660 +0100
4013  */
4014 static void
4015 parse_author_line(char *ident, const char **author, struct time *time)
4017         char *nameend = strchr(ident, '<');
4018         char *emailend = strchr(ident, '>');
4020         if (nameend && emailend)
4021                 *nameend = *emailend = 0;
4022         ident = chomp_string(ident);
4023         if (!*ident) {
4024                 if (nameend)
4025                         ident = chomp_string(nameend + 1);
4026                 if (!*ident)
4027                         ident = "Unknown";
4028         }
4030         *author = get_author(ident);
4032         /* Parse epoch and timezone */
4033         if (emailend && emailend[1] == ' ') {
4034                 char *secs = emailend + 2;
4035                 char *zone = strchr(secs, ' ');
4037                 parse_timesec(time, secs);
4039                 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
4040                         parse_timezone(time, zone + 1);
4041         }
4044 /*
4045  * Pager backend
4046  */
4048 static bool
4049 pager_draw(struct view *view, struct line *line, unsigned int lineno)
4051         if (opt_line_number && draw_lineno(view, lineno))
4052                 return TRUE;
4054         draw_text(view, line->type, line->data, TRUE);
4055         return TRUE;
4058 static bool
4059 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
4061         const char *describe_argv[] = { "git", "describe", commit_id, NULL };
4062         char ref[SIZEOF_STR];
4064         if (!io_run_buf(describe_argv, ref, sizeof(ref)) || !*ref)
4065                 return TRUE;
4067         /* This is the only fatal call, since it can "corrupt" the buffer. */
4068         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
4069                 return FALSE;
4071         return TRUE;
4074 static void
4075 add_pager_refs(struct view *view, struct line *line)
4077         char buf[SIZEOF_STR];
4078         char *commit_id = (char *)line->data + STRING_SIZE("commit ");
4079         struct ref_list *list;
4080         size_t bufpos = 0, i;
4081         const char *sep = "Refs: ";
4082         bool is_tag = FALSE;
4084         assert(line->type == LINE_COMMIT);
4086         list = get_ref_list(commit_id);
4087         if (!list) {
4088                 if (view->type == VIEW_DIFF)
4089                         goto try_add_describe_ref;
4090                 return;
4091         }
4093         for (i = 0; i < list->size; i++) {
4094                 struct ref *ref = list->refs[i];
4095                 const char *fmt = ref->tag    ? "%s[%s]" :
4096                                   ref->remote ? "%s<%s>" : "%s%s";
4098                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
4099                         return;
4100                 sep = ", ";
4101                 if (ref->tag)
4102                         is_tag = TRUE;
4103         }
4105         if (!is_tag && view->type == VIEW_DIFF) {
4106 try_add_describe_ref:
4107                 /* Add <tag>-g<commit_id> "fake" reference. */
4108                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
4109                         return;
4110         }
4112         if (bufpos == 0)
4113                 return;
4115         add_line_text(view, buf, LINE_PP_REFS);
4118 static bool
4119 pager_read(struct view *view, char *data)
4121         struct line *line;
4123         if (!data)
4124                 return TRUE;
4126         line = add_line_text(view, data, get_line_type(data));
4127         if (!line)
4128                 return FALSE;
4130         if (line->type == LINE_COMMIT &&
4131             (view->type == VIEW_DIFF ||
4132              view->type == VIEW_LOG))
4133                 add_pager_refs(view, line);
4135         return TRUE;
4138 static enum request
4139 pager_request(struct view *view, enum request request, struct line *line)
4141         int split = 0;
4143         if (request != REQ_ENTER)
4144                 return request;
4146         if (line->type == LINE_COMMIT &&
4147            (view->type == VIEW_LOG ||
4148             view->type == VIEW_PAGER)) {
4149                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
4150                 split = 1;
4151         }
4153         /* Always scroll the view even if it was split. That way
4154          * you can use Enter to scroll through the log view and
4155          * split open each commit diff. */
4156         scroll_view(view, REQ_SCROLL_LINE_DOWN);
4158         /* FIXME: A minor workaround. Scrolling the view will call report("")
4159          * but if we are scrolling a non-current view this won't properly
4160          * update the view title. */
4161         if (split)
4162                 update_view_title(view);
4164         return REQ_NONE;
4167 static bool
4168 pager_grep(struct view *view, struct line *line)
4170         const char *text[] = { line->data, NULL };
4172         return grep_text(view, text);
4175 static void
4176 pager_select(struct view *view, struct line *line)
4178         if (line->type == LINE_COMMIT) {
4179                 char *text = (char *)line->data + STRING_SIZE("commit ");
4181                 if (view->type != VIEW_PAGER)
4182                         string_copy_rev(view->ref, text);
4183                 string_copy_rev(ref_commit, text);
4184         }
4187 static struct view_ops pager_ops = {
4188         "line",
4189         NULL,
4190         NULL,
4191         pager_read,
4192         pager_draw,
4193         pager_request,
4194         pager_grep,
4195         pager_select,
4196 };
4198 static const char *log_argv[SIZEOF_ARG] = {
4199         "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
4200 };
4202 static enum request
4203 log_request(struct view *view, enum request request, struct line *line)
4205         switch (request) {
4206         case REQ_REFRESH:
4207                 load_refs();
4208                 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
4209                 return REQ_NONE;
4210         default:
4211                 return pager_request(view, request, line);
4212         }
4215 static struct view_ops log_ops = {
4216         "line",
4217         log_argv,
4218         NULL,
4219         pager_read,
4220         pager_draw,
4221         log_request,
4222         pager_grep,
4223         pager_select,
4224 };
4226 static const char *diff_argv[SIZEOF_ARG] = {
4227         "git", "show", "--pretty=fuller", "--no-color", "--root",
4228                 "--patch-with-stat", "--find-copies-harder", "-C",
4229                 "%(diffargs)", "%(commit)", "--", "%(fileargs)", NULL
4230 };
4232 static bool
4233 diff_read(struct view *view, char *data)
4235         if (!data) {
4236                 /* Fall back to retry if no diff will be shown. */
4237                 if (view->lines == 0 && opt_file_args) {
4238                         int pos = argv_size(view->argv)
4239                                 - argv_size(opt_file_args) - 1;
4241                         if (pos > 0 && !strcmp(view->argv[pos], "--")) {
4242                                 for (; view->argv[pos]; pos++) {
4243                                         free((void *) view->argv[pos]);
4244                                         view->argv[pos] = NULL;
4245                                 }
4247                                 if (view->pipe)
4248                                         io_done(view->pipe);
4249                                 if (io_run(&view->io, IO_RD, view->dir, view->argv))
4250                                         return FALSE;
4251                         }
4252                 }
4253                 return TRUE;
4254         }
4256         return pager_read(view, data);
4259 static struct view_ops diff_ops = {
4260         "line",
4261         diff_argv,
4262         NULL,
4263         diff_read,
4264         pager_draw,
4265         pager_request,
4266         pager_grep,
4267         pager_select,
4268 };
4270 /*
4271  * Help backend
4272  */
4274 static bool help_keymap_hidden[ARRAY_SIZE(keymap_table)];
4276 static bool
4277 help_open_keymap_title(struct view *view, enum keymap keymap)
4279         struct line *line;
4281         line = add_line_format(view, LINE_HELP_KEYMAP, "[%c] %s bindings",
4282                                help_keymap_hidden[keymap] ? '+' : '-',
4283                                enum_name(keymap_table[keymap]));
4284         if (line)
4285                 line->other = keymap;
4287         return help_keymap_hidden[keymap];
4290 static void
4291 help_open_keymap(struct view *view, enum keymap keymap)
4293         const char *group = NULL;
4294         char buf[SIZEOF_STR];
4295         size_t bufpos;
4296         bool add_title = TRUE;
4297         int i;
4299         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
4300                 const char *key = NULL;
4302                 if (req_info[i].request == REQ_NONE)
4303                         continue;
4305                 if (!req_info[i].request) {
4306                         group = req_info[i].help;
4307                         continue;
4308                 }
4310                 key = get_keys(keymap, req_info[i].request, TRUE);
4311                 if (!key || !*key)
4312                         continue;
4314                 if (add_title && help_open_keymap_title(view, keymap))
4315                         return;
4316                 add_title = FALSE;
4318                 if (group) {
4319                         add_line_text(view, group, LINE_HELP_GROUP);
4320                         group = NULL;
4321                 }
4323                 add_line_format(view, LINE_DEFAULT, "    %-25s %-20s %s", key,
4324                                 enum_name(req_info[i]), req_info[i].help);
4325         }
4327         group = "External commands:";
4329         for (i = 0; i < run_requests; i++) {
4330                 struct run_request *req = get_run_request(REQ_NONE + i + 1);
4331                 const char *key;
4332                 int argc;
4334                 if (!req || req->keymap != keymap)
4335                         continue;
4337                 key = get_key_name(req->key);
4338                 if (!*key)
4339                         key = "(no key defined)";
4341                 if (add_title && help_open_keymap_title(view, keymap))
4342                         return;
4343                 if (group) {
4344                         add_line_text(view, group, LINE_HELP_GROUP);
4345                         group = NULL;
4346                 }
4348                 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
4349                         if (!string_format_from(buf, &bufpos, "%s%s",
4350                                                 argc ? " " : "", req->argv[argc]))
4351                                 return;
4353                 add_line_format(view, LINE_DEFAULT, "    %-25s `%s`", key, buf);
4354         }
4357 static bool
4358 help_open(struct view *view)
4360         enum keymap keymap;
4362         reset_view(view);
4363         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
4364         add_line_text(view, "", LINE_DEFAULT);
4366         for (keymap = 0; keymap < ARRAY_SIZE(keymap_table); keymap++)
4367                 help_open_keymap(view, keymap);
4369         return TRUE;
4372 static enum request
4373 help_request(struct view *view, enum request request, struct line *line)
4375         switch (request) {
4376         case REQ_ENTER:
4377                 if (line->type == LINE_HELP_KEYMAP) {
4378                         help_keymap_hidden[line->other] =
4379                                 !help_keymap_hidden[line->other];
4380                         view->p_restore = TRUE;
4381                         open_view(view, REQ_VIEW_HELP, OPEN_REFRESH);
4382                 }
4384                 return REQ_NONE;
4385         default:
4386                 return pager_request(view, request, line);
4387         }
4390 static struct view_ops help_ops = {
4391         "line",
4392         NULL,
4393         help_open,
4394         NULL,
4395         pager_draw,
4396         help_request,
4397         pager_grep,
4398         pager_select,
4399 };
4402 /*
4403  * Tree backend
4404  */
4406 struct tree_stack_entry {
4407         struct tree_stack_entry *prev;  /* Entry below this in the stack */
4408         unsigned long lineno;           /* Line number to restore */
4409         char *name;                     /* Position of name in opt_path */
4410 };
4412 /* The top of the path stack. */
4413 static struct tree_stack_entry *tree_stack = NULL;
4414 unsigned long tree_lineno = 0;
4416 static void
4417 pop_tree_stack_entry(void)
4419         struct tree_stack_entry *entry = tree_stack;
4421         tree_lineno = entry->lineno;
4422         entry->name[0] = 0;
4423         tree_stack = entry->prev;
4424         free(entry);
4427 static void
4428 push_tree_stack_entry(const char *name, unsigned long lineno)
4430         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
4431         size_t pathlen = strlen(opt_path);
4433         if (!entry)
4434                 return;
4436         entry->prev = tree_stack;
4437         entry->name = opt_path + pathlen;
4438         tree_stack = entry;
4440         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
4441                 pop_tree_stack_entry();
4442                 return;
4443         }
4445         /* Move the current line to the first tree entry. */
4446         tree_lineno = 1;
4447         entry->lineno = lineno;
4450 /* Parse output from git-ls-tree(1):
4451  *
4452  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4453  */
4455 #define SIZEOF_TREE_ATTR \
4456         STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4458 #define SIZEOF_TREE_MODE \
4459         STRING_SIZE("100644 ")
4461 #define TREE_ID_OFFSET \
4462         STRING_SIZE("100644 blob ")
4464 struct tree_entry {
4465         char id[SIZEOF_REV];
4466         mode_t mode;
4467         struct time time;               /* Date from the author ident. */
4468         const char *author;             /* Author of the commit. */
4469         char name[1];
4470 };
4472 static const char *
4473 tree_path(const struct line *line)
4475         return ((struct tree_entry *) line->data)->name;
4478 static int
4479 tree_compare_entry(const struct line *line1, const struct line *line2)
4481         if (line1->type != line2->type)
4482                 return line1->type == LINE_TREE_DIR ? -1 : 1;
4483         return strcmp(tree_path(line1), tree_path(line2));
4486 static const enum sort_field tree_sort_fields[] = {
4487         ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4488 };
4489 static struct sort_state tree_sort_state = SORT_STATE(tree_sort_fields);
4491 static int
4492 tree_compare(const void *l1, const void *l2)
4494         const struct line *line1 = (const struct line *) l1;
4495         const struct line *line2 = (const struct line *) l2;
4496         const struct tree_entry *entry1 = ((const struct line *) l1)->data;
4497         const struct tree_entry *entry2 = ((const struct line *) l2)->data;
4499         if (line1->type == LINE_TREE_HEAD)
4500                 return -1;
4501         if (line2->type == LINE_TREE_HEAD)
4502                 return 1;
4504         switch (get_sort_field(tree_sort_state)) {
4505         case ORDERBY_DATE:
4506                 return sort_order(tree_sort_state, timecmp(&entry1->time, &entry2->time));
4508         case ORDERBY_AUTHOR:
4509                 return sort_order(tree_sort_state, strcmp(entry1->author, entry2->author));
4511         case ORDERBY_NAME:
4512         default:
4513                 return sort_order(tree_sort_state, tree_compare_entry(line1, line2));
4514         }
4518 static struct line *
4519 tree_entry(struct view *view, enum line_type type, const char *path,
4520            const char *mode, const char *id)
4522         struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
4523         struct line *line = entry ? add_line_data(view, entry, type) : NULL;
4525         if (!entry || !line) {
4526                 free(entry);
4527                 return NULL;
4528         }
4530         strncpy(entry->name, path, strlen(path));
4531         if (mode)
4532                 entry->mode = strtoul(mode, NULL, 8);
4533         if (id)
4534                 string_copy_rev(entry->id, id);
4536         return line;
4539 static bool
4540 tree_read_date(struct view *view, char *text, bool *read_date)
4542         static const char *author_name;
4543         static struct time author_time;
4545         if (!text && *read_date) {
4546                 *read_date = FALSE;
4547                 return TRUE;
4549         } else if (!text) {
4550                 char *path = *opt_path ? opt_path : ".";
4551                 /* Find next entry to process */
4552                 const char *log_file[] = {
4553                         "git", "log", "--no-color", "--pretty=raw",
4554                                 "--cc", "--raw", view->id, "--", path, NULL
4555                 };
4557                 if (!view->lines) {
4558                         tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
4559                         report("Tree is empty");
4560                         return TRUE;
4561                 }
4563                 if (!start_update(view, log_file, opt_cdup)) {
4564                         report("Failed to load tree data");
4565                         return TRUE;
4566                 }
4568                 *read_date = TRUE;
4569                 return FALSE;
4571         } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
4572                 parse_author_line(text + STRING_SIZE("author "),
4573                                   &author_name, &author_time);
4575         } else if (*text == ':') {
4576                 char *pos;
4577                 size_t annotated = 1;
4578                 size_t i;
4580                 pos = strchr(text, '\t');
4581                 if (!pos)
4582                         return TRUE;
4583                 text = pos + 1;
4584                 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
4585                         text += strlen(opt_path);
4586                 pos = strchr(text, '/');
4587                 if (pos)
4588                         *pos = 0;
4590                 for (i = 1; i < view->lines; i++) {
4591                         struct line *line = &view->line[i];
4592                         struct tree_entry *entry = line->data;
4594                         annotated += !!entry->author;
4595                         if (entry->author || strcmp(entry->name, text))
4596                                 continue;
4598                         entry->author = author_name;
4599                         entry->time = author_time;
4600                         line->dirty = 1;
4601                         break;
4602                 }
4604                 if (annotated == view->lines)
4605                         io_kill(view->pipe);
4606         }
4607         return TRUE;
4610 static bool
4611 tree_read(struct view *view, char *text)
4613         static bool read_date = FALSE;
4614         struct tree_entry *data;
4615         struct line *entry, *line;
4616         enum line_type type;
4617         size_t textlen = text ? strlen(text) : 0;
4618         char *path = text + SIZEOF_TREE_ATTR;
4620         if (read_date || !text)
4621                 return tree_read_date(view, text, &read_date);
4623         if (textlen <= SIZEOF_TREE_ATTR)
4624                 return FALSE;
4625         if (view->lines == 0 &&
4626             !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
4627                 return FALSE;
4629         /* Strip the path part ... */
4630         if (*opt_path) {
4631                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
4632                 size_t striplen = strlen(opt_path);
4634                 if (pathlen > striplen)
4635                         memmove(path, path + striplen,
4636                                 pathlen - striplen + 1);
4638                 /* Insert "link" to parent directory. */
4639                 if (view->lines == 1 &&
4640                     !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
4641                         return FALSE;
4642         }
4644         type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
4645         entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
4646         if (!entry)
4647                 return FALSE;
4648         data = entry->data;
4650         /* Skip "Directory ..." and ".." line. */
4651         for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
4652                 if (tree_compare_entry(line, entry) <= 0)
4653                         continue;
4655                 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4657                 line->data = data;
4658                 line->type = type;
4659                 for (; line <= entry; line++)
4660                         line->dirty = line->cleareol = 1;
4661                 return TRUE;
4662         }
4664         if (tree_lineno > view->lineno) {
4665                 view->lineno = tree_lineno;
4666                 tree_lineno = 0;
4667         }
4669         return TRUE;
4672 static bool
4673 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4675         struct tree_entry *entry = line->data;
4677         if (line->type == LINE_TREE_HEAD) {
4678                 if (draw_text(view, line->type, "Directory path /", TRUE))
4679                         return TRUE;
4680         } else {
4681                 if (draw_mode(view, entry->mode))
4682                         return TRUE;
4684                 if (opt_author && draw_author(view, entry->author))
4685                         return TRUE;
4687                 if (opt_date && draw_date(view, &entry->time))
4688                         return TRUE;
4689         }
4690         if (draw_text(view, line->type, entry->name, TRUE))
4691                 return TRUE;
4692         return TRUE;
4695 static void
4696 open_blob_editor(const char *id)
4698         const char *blob_argv[] = { "git", "cat-file", "blob", id, NULL };
4699         char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4700         int fd = mkstemp(file);
4702         if (fd == -1)
4703                 report("Failed to create temporary file");
4704         else if (!io_run_append(blob_argv, fd))
4705                 report("Failed to save blob data to file");
4706         else
4707                 open_editor(file);
4708         if (fd != -1)
4709                 unlink(file);
4712 static enum request
4713 tree_request(struct view *view, enum request request, struct line *line)
4715         enum open_flags flags;
4716         struct tree_entry *entry = line->data;
4718         switch (request) {
4719         case REQ_VIEW_BLAME:
4720                 if (line->type != LINE_TREE_FILE) {
4721                         report("Blame only supported for files");
4722                         return REQ_NONE;
4723                 }
4725                 string_copy(opt_ref, view->vid);
4726                 return request;
4728         case REQ_EDIT:
4729                 if (line->type != LINE_TREE_FILE) {
4730                         report("Edit only supported for files");
4731                 } else if (!is_head_commit(view->vid)) {
4732                         open_blob_editor(entry->id);
4733                 } else {
4734                         open_editor(opt_file);
4735                 }
4736                 return REQ_NONE;
4738         case REQ_TOGGLE_SORT_FIELD:
4739         case REQ_TOGGLE_SORT_ORDER:
4740                 sort_view(view, request, &tree_sort_state, tree_compare);
4741                 return REQ_NONE;
4743         case REQ_PARENT:
4744                 if (!*opt_path) {
4745                         /* quit view if at top of tree */
4746                         return REQ_VIEW_CLOSE;
4747                 }
4748                 /* fake 'cd  ..' */
4749                 line = &view->line[1];
4750                 break;
4752         case REQ_ENTER:
4753                 break;
4755         default:
4756                 return request;
4757         }
4759         /* Cleanup the stack if the tree view is at a different tree. */
4760         while (!*opt_path && tree_stack)
4761                 pop_tree_stack_entry();
4763         switch (line->type) {
4764         case LINE_TREE_DIR:
4765                 /* Depending on whether it is a subdirectory or parent link
4766                  * mangle the path buffer. */
4767                 if (line == &view->line[1] && *opt_path) {
4768                         pop_tree_stack_entry();
4770                 } else {
4771                         const char *basename = tree_path(line);
4773                         push_tree_stack_entry(basename, view->lineno);
4774                 }
4776                 /* Trees and subtrees share the same ID, so they are not not
4777                  * unique like blobs. */
4778                 flags = OPEN_RELOAD;
4779                 request = REQ_VIEW_TREE;
4780                 break;
4782         case LINE_TREE_FILE:
4783                 flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
4784                 request = REQ_VIEW_BLOB;
4785                 break;
4787         default:
4788                 return REQ_NONE;
4789         }
4791         open_view(view, request, flags);
4792         if (request == REQ_VIEW_TREE)
4793                 view->lineno = tree_lineno;
4795         return REQ_NONE;
4798 static bool
4799 tree_grep(struct view *view, struct line *line)
4801         struct tree_entry *entry = line->data;
4802         const char *text[] = {
4803                 entry->name,
4804                 opt_author ? entry->author : "",
4805                 mkdate(&entry->time, opt_date),
4806                 NULL
4807         };
4809         return grep_text(view, text);
4812 static void
4813 tree_select(struct view *view, struct line *line)
4815         struct tree_entry *entry = line->data;
4817         if (line->type == LINE_TREE_FILE) {
4818                 string_copy_rev(ref_blob, entry->id);
4819                 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4821         } else if (line->type != LINE_TREE_DIR) {
4822                 return;
4823         }
4825         string_copy_rev(view->ref, entry->id);
4828 static bool
4829 tree_prepare(struct view *view)
4831         if (view->lines == 0 && opt_prefix[0]) {
4832                 char *pos = opt_prefix;
4834                 while (pos && *pos) {
4835                         char *end = strchr(pos, '/');
4837                         if (end)
4838                                 *end = 0;
4839                         push_tree_stack_entry(pos, 0);
4840                         pos = end;
4841                         if (end) {
4842                                 *end = '/';
4843                                 pos++;
4844                         }
4845                 }
4847         } else if (strcmp(view->vid, view->id)) {
4848                 opt_path[0] = 0;
4849         }
4851         return prepare_io(view, opt_cdup, view->ops->argv, TRUE);
4854 static const char *tree_argv[SIZEOF_ARG] = {
4855         "git", "ls-tree", "%(commit)", "%(directory)", NULL
4856 };
4858 static struct view_ops tree_ops = {
4859         "file",
4860         tree_argv,
4861         NULL,
4862         tree_read,
4863         tree_draw,
4864         tree_request,
4865         tree_grep,
4866         tree_select,
4867         tree_prepare,
4868 };
4870 static bool
4871 blob_read(struct view *view, char *line)
4873         if (!line)
4874                 return TRUE;
4875         return add_line_text(view, line, LINE_DEFAULT) != NULL;
4878 static enum request
4879 blob_request(struct view *view, enum request request, struct line *line)
4881         switch (request) {
4882         case REQ_EDIT:
4883                 open_blob_editor(view->vid);
4884                 return REQ_NONE;
4885         default:
4886                 return pager_request(view, request, line);
4887         }
4890 static const char *blob_argv[SIZEOF_ARG] = {
4891         "git", "cat-file", "blob", "%(blob)", NULL
4892 };
4894 static struct view_ops blob_ops = {
4895         "line",
4896         blob_argv,
4897         NULL,
4898         blob_read,
4899         pager_draw,
4900         blob_request,
4901         pager_grep,
4902         pager_select,
4903 };
4905 /*
4906  * Blame backend
4907  *
4908  * Loading the blame view is a two phase job:
4909  *
4910  *  1. File content is read either using opt_file from the
4911  *     filesystem or using git-cat-file.
4912  *  2. Then blame information is incrementally added by
4913  *     reading output from git-blame.
4914  */
4916 struct blame_commit {
4917         char id[SIZEOF_REV];            /* SHA1 ID. */
4918         char title[128];                /* First line of the commit message. */
4919         const char *author;             /* Author of the commit. */
4920         struct time time;               /* Date from the author ident. */
4921         char filename[128];             /* Name of file. */
4922         char parent_id[SIZEOF_REV];     /* Parent/previous SHA1 ID. */
4923         char parent_filename[128];      /* Parent/previous name of file. */
4924 };
4926 struct blame {
4927         struct blame_commit *commit;
4928         unsigned long lineno;
4929         char text[1];
4930 };
4932 static bool
4933 blame_open(struct view *view)
4935         char path[SIZEOF_STR];
4936         size_t i;
4938         if (!view->prev && *opt_prefix) {
4939                 string_copy(path, opt_file);
4940                 if (!string_format(opt_file, "%s%s", opt_prefix, path))
4941                         return FALSE;
4942         }
4944         if (*opt_ref || !io_open(&view->io, "%s%s", opt_cdup, opt_file)) {
4945                 const char *blame_cat_file_argv[] = {
4946                         "git", "cat-file", "blob", path, NULL
4947                 };
4949                 if (!string_format(path, "%s:%s", opt_ref, opt_file) ||
4950                     !start_update(view, blame_cat_file_argv, opt_cdup))
4951                         return FALSE;
4952         }
4954         /* First pass: remove multiple references to the same commit. */
4955         for (i = 0; i < view->lines; i++) {
4956                 struct blame *blame = view->line[i].data;
4958                 if (blame->commit && blame->commit->id[0])
4959                         blame->commit->id[0] = 0;
4960                 else
4961                         blame->commit = NULL;
4962         }
4964         /* Second pass: free existing references. */
4965         for (i = 0; i < view->lines; i++) {
4966                 struct blame *blame = view->line[i].data;
4968                 if (blame->commit)
4969                         free(blame->commit);
4970         }
4972         setup_update(view, opt_file);
4973         string_format(view->ref, "%s ...", opt_file);
4975         return TRUE;
4978 static struct blame_commit *
4979 get_blame_commit(struct view *view, const char *id)
4981         size_t i;
4983         for (i = 0; i < view->lines; i++) {
4984                 struct blame *blame = view->line[i].data;
4986                 if (!blame->commit)
4987                         continue;
4989                 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4990                         return blame->commit;
4991         }
4993         {
4994                 struct blame_commit *commit = calloc(1, sizeof(*commit));
4996                 if (commit)
4997                         string_ncopy(commit->id, id, SIZEOF_REV);
4998                 return commit;
4999         }
5002 static bool
5003 parse_number(const char **posref, size_t *number, size_t min, size_t max)
5005         const char *pos = *posref;
5007         *posref = NULL;
5008         pos = strchr(pos + 1, ' ');
5009         if (!pos || !isdigit(pos[1]))
5010                 return FALSE;
5011         *number = atoi(pos + 1);
5012         if (*number < min || *number > max)
5013                 return FALSE;
5015         *posref = pos;
5016         return TRUE;
5019 static struct blame_commit *
5020 parse_blame_commit(struct view *view, const char *text, int *blamed)
5022         struct blame_commit *commit;
5023         struct blame *blame;
5024         const char *pos = text + SIZEOF_REV - 2;
5025         size_t orig_lineno = 0;
5026         size_t lineno;
5027         size_t group;
5029         if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
5030                 return NULL;
5032         if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
5033             !parse_number(&pos, &lineno, 1, view->lines) ||
5034             !parse_number(&pos, &group, 1, view->lines - lineno + 1))
5035                 return NULL;
5037         commit = get_blame_commit(view, text);
5038         if (!commit)
5039                 return NULL;
5041         *blamed += group;
5042         while (group--) {
5043                 struct line *line = &view->line[lineno + group - 1];
5045                 blame = line->data;
5046                 blame->commit = commit;
5047                 blame->lineno = orig_lineno + group - 1;
5048                 line->dirty = 1;
5049         }
5051         return commit;
5054 static bool
5055 blame_read_file(struct view *view, const char *line, bool *read_file)
5057         if (!line) {
5058                 const char *blame_argv[] = {
5059                         "git", "blame", "--incremental",
5060                                 *opt_ref ? opt_ref : "--incremental", "--", opt_file, NULL
5061                 };
5063                 if (view->lines == 0 && !view->prev)
5064                         die("No blame exist for %s", view->vid);
5066                 if (view->lines == 0 || !start_update(view, blame_argv, opt_cdup)) {
5067                         report("Failed to load blame data");
5068                         return TRUE;
5069                 }
5071                 *read_file = FALSE;
5072                 return FALSE;
5074         } else {
5075                 size_t linelen = strlen(line);
5076                 struct blame *blame = malloc(sizeof(*blame) + linelen);
5078                 if (!blame)
5079                         return FALSE;
5081                 blame->commit = NULL;
5082                 strncpy(blame->text, line, linelen);
5083                 blame->text[linelen] = 0;
5084                 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
5085         }
5088 static bool
5089 match_blame_header(const char *name, char **line)
5091         size_t namelen = strlen(name);
5092         bool matched = !strncmp(name, *line, namelen);
5094         if (matched)
5095                 *line += namelen;
5097         return matched;
5100 static bool
5101 blame_read(struct view *view, char *line)
5103         static struct blame_commit *commit = NULL;
5104         static int blamed = 0;
5105         static bool read_file = TRUE;
5107         if (read_file)
5108                 return blame_read_file(view, line, &read_file);
5110         if (!line) {
5111                 /* Reset all! */
5112                 commit = NULL;
5113                 blamed = 0;
5114                 read_file = TRUE;
5115                 string_format(view->ref, "%s", view->vid);
5116                 if (view_is_displayed(view)) {
5117                         update_view_title(view);
5118                         redraw_view_from(view, 0);
5119                 }
5120                 return TRUE;
5121         }
5123         if (!commit) {
5124                 commit = parse_blame_commit(view, line, &blamed);
5125                 string_format(view->ref, "%s %2d%%", view->vid,
5126                               view->lines ? blamed * 100 / view->lines : 0);
5128         } else if (match_blame_header("author ", &line)) {
5129                 commit->author = get_author(line);
5131         } else if (match_blame_header("author-time ", &line)) {
5132                 parse_timesec(&commit->time, line);
5134         } else if (match_blame_header("author-tz ", &line)) {
5135                 parse_timezone(&commit->time, line);
5137         } else if (match_blame_header("summary ", &line)) {
5138                 string_ncopy(commit->title, line, strlen(line));
5140         } else if (match_blame_header("previous ", &line)) {
5141                 if (strlen(line) <= SIZEOF_REV)
5142                         return FALSE;
5143                 string_copy_rev(commit->parent_id, line);
5144                 line += SIZEOF_REV;
5145                 string_ncopy(commit->parent_filename, line, strlen(line));
5147         } else if (match_blame_header("filename ", &line)) {
5148                 string_ncopy(commit->filename, line, strlen(line));
5149                 commit = NULL;
5150         }
5152         return TRUE;
5155 static bool
5156 blame_draw(struct view *view, struct line *line, unsigned int lineno)
5158         struct blame *blame = line->data;
5159         struct time *time = NULL;
5160         const char *id = NULL, *author = NULL;
5162         if (blame->commit && *blame->commit->filename) {
5163                 id = blame->commit->id;
5164                 author = blame->commit->author;
5165                 time = &blame->commit->time;
5166         }
5168         if (opt_date && draw_date(view, time))
5169                 return TRUE;
5171         if (opt_author && draw_author(view, author))
5172                 return TRUE;
5174         if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
5175                 return TRUE;
5177         if (draw_lineno(view, lineno))
5178                 return TRUE;
5180         draw_text(view, LINE_DEFAULT, blame->text, TRUE);
5181         return TRUE;
5184 static bool
5185 check_blame_commit(struct blame *blame, bool check_null_id)
5187         if (!blame->commit)
5188                 report("Commit data not loaded yet");
5189         else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
5190                 report("No commit exist for the selected line");
5191         else
5192                 return TRUE;
5193         return FALSE;
5196 static void
5197 setup_blame_parent_line(struct view *view, struct blame *blame)
5199         char from[SIZEOF_REF + SIZEOF_STR];
5200         char to[SIZEOF_REF + SIZEOF_STR];
5201         const char *diff_tree_argv[] = {
5202                 "git", "diff", "--no-textconv", "--no-extdiff", "--no-color",
5203                         "-U0", from, to, "--", NULL
5204         };
5205         struct io io;
5206         int parent_lineno = -1;
5207         int blamed_lineno = -1;
5208         char *line;
5210         if (!string_format(from, "%s:%s", opt_ref, opt_file) ||
5211             !string_format(to, "%s:%s", blame->commit->id, blame->commit->filename) ||
5212             !io_run(&io, IO_RD, NULL, diff_tree_argv))
5213                 return;
5215         while ((line = io_get(&io, '\n', TRUE))) {
5216                 if (*line == '@') {
5217                         char *pos = strchr(line, '+');
5219                         parent_lineno = atoi(line + 4);
5220                         if (pos)
5221                                 blamed_lineno = atoi(pos + 1);
5223                 } else if (*line == '+' && parent_lineno != -1) {
5224                         if (blame->lineno == blamed_lineno - 1 &&
5225                             !strcmp(blame->text, line + 1)) {
5226                                 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
5227                                 break;
5228                         }
5229                         blamed_lineno++;
5230                 }
5231         }
5233         io_done(&io);
5236 static enum request
5237 blame_request(struct view *view, enum request request, struct line *line)
5239         enum open_flags flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
5240         struct blame *blame = line->data;
5242         switch (request) {
5243         case REQ_VIEW_BLAME:
5244                 if (check_blame_commit(blame, TRUE)) {
5245                         string_copy(opt_ref, blame->commit->id);
5246                         string_copy(opt_file, blame->commit->filename);
5247                         if (blame->lineno)
5248                                 view->lineno = blame->lineno;
5249                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5250                 }
5251                 break;
5253         case REQ_PARENT:
5254                 if (!check_blame_commit(blame, TRUE))
5255                         break;
5256                 if (!*blame->commit->parent_id) {
5257                         report("The selected commit has no parents");
5258                 } else {
5259                         string_copy_rev(opt_ref, blame->commit->parent_id);
5260                         string_copy(opt_file, blame->commit->parent_filename);
5261                         setup_blame_parent_line(view, blame);
5262                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5263                 }
5264                 break;
5266         case REQ_ENTER:
5267                 if (!check_blame_commit(blame, FALSE))
5268                         break;
5270                 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
5271                     !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
5272                         break;
5274                 if (!strcmp(blame->commit->id, NULL_ID)) {
5275                         struct view *diff = VIEW(REQ_VIEW_DIFF);
5276                         const char *diff_index_argv[] = {
5277                                 "git", "diff-index", "--root", "--patch-with-stat",
5278                                         "-C", "-M", "HEAD", "--", view->vid, NULL
5279                         };
5281                         if (!*blame->commit->parent_id) {
5282                                 diff_index_argv[1] = "diff";
5283                                 diff_index_argv[2] = "--no-color";
5284                                 diff_index_argv[6] = "--";
5285                                 diff_index_argv[7] = "/dev/null";
5286                         }
5288                         if (!prepare_update(diff, diff_index_argv, NULL)) {
5289                                 report("Failed to allocate diff command");
5290                                 break;
5291                         }
5292                         flags |= OPEN_PREPARED;
5293                 }
5295                 open_view(view, REQ_VIEW_DIFF, flags);
5296                 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
5297                         string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
5298                 break;
5300         default:
5301                 return request;
5302         }
5304         return REQ_NONE;
5307 static bool
5308 blame_grep(struct view *view, struct line *line)
5310         struct blame *blame = line->data;
5311         struct blame_commit *commit = blame->commit;
5312         const char *text[] = {
5313                 blame->text,
5314                 commit ? commit->title : "",
5315                 commit ? commit->id : "",
5316                 commit && opt_author ? commit->author : "",
5317                 commit ? mkdate(&commit->time, opt_date) : "",
5318                 NULL
5319         };
5321         return grep_text(view, text);
5324 static void
5325 blame_select(struct view *view, struct line *line)
5327         struct blame *blame = line->data;
5328         struct blame_commit *commit = blame->commit;
5330         if (!commit)
5331                 return;
5333         if (!strcmp(commit->id, NULL_ID))
5334                 string_ncopy(ref_commit, "HEAD", 4);
5335         else
5336                 string_copy_rev(ref_commit, commit->id);
5339 static struct view_ops blame_ops = {
5340         "line",
5341         NULL,
5342         blame_open,
5343         blame_read,
5344         blame_draw,
5345         blame_request,
5346         blame_grep,
5347         blame_select,
5348 };
5350 /*
5351  * Branch backend
5352  */
5354 struct branch {
5355         const char *author;             /* Author of the last commit. */
5356         struct time time;               /* Date of the last activity. */
5357         const struct ref *ref;          /* Name and commit ID information. */
5358 };
5360 static const struct ref branch_all;
5362 static const enum sort_field branch_sort_fields[] = {
5363         ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
5364 };
5365 static struct sort_state branch_sort_state = SORT_STATE(branch_sort_fields);
5367 static int
5368 branch_compare(const void *l1, const void *l2)
5370         const struct branch *branch1 = ((const struct line *) l1)->data;
5371         const struct branch *branch2 = ((const struct line *) l2)->data;
5373         switch (get_sort_field(branch_sort_state)) {
5374         case ORDERBY_DATE:
5375                 return sort_order(branch_sort_state, timecmp(&branch1->time, &branch2->time));
5377         case ORDERBY_AUTHOR:
5378                 return sort_order(branch_sort_state, strcmp(branch1->author, branch2->author));
5380         case ORDERBY_NAME:
5381         default:
5382                 return sort_order(branch_sort_state, strcmp(branch1->ref->name, branch2->ref->name));
5383         }
5386 static bool
5387 branch_draw(struct view *view, struct line *line, unsigned int lineno)
5389         struct branch *branch = line->data;
5390         enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
5392         if (opt_date && draw_date(view, &branch->time))
5393                 return TRUE;
5395         if (opt_author && draw_author(view, branch->author))
5396                 return TRUE;
5398         draw_text(view, type, branch->ref == &branch_all ? "All branches" : branch->ref->name, TRUE);
5399         return TRUE;
5402 static enum request
5403 branch_request(struct view *view, enum request request, struct line *line)
5405         struct branch *branch = line->data;
5407         switch (request) {
5408         case REQ_REFRESH:
5409                 load_refs();
5410                 open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
5411                 return REQ_NONE;
5413         case REQ_TOGGLE_SORT_FIELD:
5414         case REQ_TOGGLE_SORT_ORDER:
5415                 sort_view(view, request, &branch_sort_state, branch_compare);
5416                 return REQ_NONE;
5418         case REQ_ENTER:
5419         {
5420                 const struct ref *ref = branch->ref;
5421                 const char *all_branches_argv[] = {
5422                         "git", "log", "--no-color", "--pretty=raw", "--parents",
5423                               "--topo-order",
5424                               ref == &branch_all ? "--all" : ref->name, NULL
5425                 };
5426                 struct view *main_view = VIEW(REQ_VIEW_MAIN);
5428                 if (!prepare_update(main_view, all_branches_argv, NULL))
5429                         report("Failed to load view of all branches");
5430                 else
5431                         open_view(view, REQ_VIEW_MAIN, OPEN_PREPARED | OPEN_SPLIT);
5432                 return REQ_NONE;
5433         }
5434         default:
5435                 return request;
5436         }
5439 static bool
5440 branch_read(struct view *view, char *line)
5442         static char id[SIZEOF_REV];
5443         struct branch *reference;
5444         size_t i;
5446         if (!line)
5447                 return TRUE;
5449         switch (get_line_type(line)) {
5450         case LINE_COMMIT:
5451                 string_copy_rev(id, line + STRING_SIZE("commit "));
5452                 return TRUE;
5454         case LINE_AUTHOR:
5455                 for (i = 0, reference = NULL; i < view->lines; i++) {
5456                         struct branch *branch = view->line[i].data;
5458                         if (strcmp(branch->ref->id, id))
5459                                 continue;
5461                         view->line[i].dirty = TRUE;
5462                         if (reference) {
5463                                 branch->author = reference->author;
5464                                 branch->time = reference->time;
5465                                 continue;
5466                         }
5468                         parse_author_line(line + STRING_SIZE("author "),
5469                                           &branch->author, &branch->time);
5470                         reference = branch;
5471                 }
5472                 return TRUE;
5474         default:
5475                 return TRUE;
5476         }
5480 static bool
5481 branch_open_visitor(void *data, const struct ref *ref)
5483         struct view *view = data;
5484         struct branch *branch;
5486         if (ref->tag || ref->ltag || ref->remote)
5487                 return TRUE;
5489         branch = calloc(1, sizeof(*branch));
5490         if (!branch)
5491                 return FALSE;
5493         branch->ref = ref;
5494         return !!add_line_data(view, branch, LINE_DEFAULT);
5497 static bool
5498 branch_open(struct view *view)
5500         const char *branch_log[] = {
5501                 "git", "log", "--no-color", "--pretty=raw",
5502                         "--simplify-by-decoration", "--all", NULL
5503         };
5505         if (!start_update(view, branch_log, NULL)) {
5506                 report("Failed to load branch data");
5507                 return TRUE;
5508         }
5510         setup_update(view, view->id);
5511         branch_open_visitor(view, &branch_all);
5512         foreach_ref(branch_open_visitor, view);
5513         view->p_restore = TRUE;
5515         return TRUE;
5518 static bool
5519 branch_grep(struct view *view, struct line *line)
5521         struct branch *branch = line->data;
5522         const char *text[] = {
5523                 branch->ref->name,
5524                 branch->author,
5525                 NULL
5526         };
5528         return grep_text(view, text);
5531 static void
5532 branch_select(struct view *view, struct line *line)
5534         struct branch *branch = line->data;
5536         string_copy_rev(view->ref, branch->ref->id);
5537         string_copy_rev(ref_commit, branch->ref->id);
5538         string_copy_rev(ref_head, branch->ref->id);
5539         string_copy_rev(ref_branch, branch->ref->name);
5542 static struct view_ops branch_ops = {
5543         "branch",
5544         NULL,
5545         branch_open,
5546         branch_read,
5547         branch_draw,
5548         branch_request,
5549         branch_grep,
5550         branch_select,
5551 };
5553 /*
5554  * Status backend
5555  */
5557 struct status {
5558         char status;
5559         struct {
5560                 mode_t mode;
5561                 char rev[SIZEOF_REV];
5562                 char name[SIZEOF_STR];
5563         } old;
5564         struct {
5565                 mode_t mode;
5566                 char rev[SIZEOF_REV];
5567                 char name[SIZEOF_STR];
5568         } new;
5569 };
5571 static char status_onbranch[SIZEOF_STR];
5572 static struct status stage_status;
5573 static enum line_type stage_line_type;
5574 static size_t stage_chunks;
5575 static int *stage_chunk;
5577 DEFINE_ALLOCATOR(realloc_ints, int, 32)
5579 /* This should work even for the "On branch" line. */
5580 static inline bool
5581 status_has_none(struct view *view, struct line *line)
5583         return line < view->line + view->lines && !line[1].data;
5586 /* Get fields from the diff line:
5587  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5588  */
5589 static inline bool
5590 status_get_diff(struct status *file, const char *buf, size_t bufsize)
5592         const char *old_mode = buf +  1;
5593         const char *new_mode = buf +  8;
5594         const char *old_rev  = buf + 15;
5595         const char *new_rev  = buf + 56;
5596         const char *status   = buf + 97;
5598         if (bufsize < 98 ||
5599             old_mode[-1] != ':' ||
5600             new_mode[-1] != ' ' ||
5601             old_rev[-1]  != ' ' ||
5602             new_rev[-1]  != ' ' ||
5603             status[-1]   != ' ')
5604                 return FALSE;
5606         file->status = *status;
5608         string_copy_rev(file->old.rev, old_rev);
5609         string_copy_rev(file->new.rev, new_rev);
5611         file->old.mode = strtoul(old_mode, NULL, 8);
5612         file->new.mode = strtoul(new_mode, NULL, 8);
5614         file->old.name[0] = file->new.name[0] = 0;
5616         return TRUE;
5619 static bool
5620 status_run(struct view *view, const char *argv[], char status, enum line_type type)
5622         struct status *unmerged = NULL;
5623         char *buf;
5624         struct io io;
5626         if (!io_run(&io, IO_RD, opt_cdup, argv))
5627                 return FALSE;
5629         add_line_data(view, NULL, type);
5631         while ((buf = io_get(&io, 0, TRUE))) {
5632                 struct status *file = unmerged;
5634                 if (!file) {
5635                         file = calloc(1, sizeof(*file));
5636                         if (!file || !add_line_data(view, file, type))
5637                                 goto error_out;
5638                 }
5640                 /* Parse diff info part. */
5641                 if (status) {
5642                         file->status = status;
5643                         if (status == 'A')
5644                                 string_copy(file->old.rev, NULL_ID);
5646                 } else if (!file->status || file == unmerged) {
5647                         if (!status_get_diff(file, buf, strlen(buf)))
5648                                 goto error_out;
5650                         buf = io_get(&io, 0, TRUE);
5651                         if (!buf)
5652                                 break;
5654                         /* Collapse all modified entries that follow an
5655                          * associated unmerged entry. */
5656                         if (unmerged == file) {
5657                                 unmerged->status = 'U';
5658                                 unmerged = NULL;
5659                         } else if (file->status == 'U') {
5660                                 unmerged = file;
5661                         }
5662                 }
5664                 /* Grab the old name for rename/copy. */
5665                 if (!*file->old.name &&
5666                     (file->status == 'R' || file->status == 'C')) {
5667                         string_ncopy(file->old.name, buf, strlen(buf));
5669                         buf = io_get(&io, 0, TRUE);
5670                         if (!buf)
5671                                 break;
5672                 }
5674                 /* git-ls-files just delivers a NUL separated list of
5675                  * file names similar to the second half of the
5676                  * git-diff-* output. */
5677                 string_ncopy(file->new.name, buf, strlen(buf));
5678                 if (!*file->old.name)
5679                         string_copy(file->old.name, file->new.name);
5680                 file = NULL;
5681         }
5683         if (io_error(&io)) {
5684 error_out:
5685                 io_done(&io);
5686                 return FALSE;
5687         }
5689         if (!view->line[view->lines - 1].data)
5690                 add_line_data(view, NULL, LINE_STAT_NONE);
5692         io_done(&io);
5693         return TRUE;
5696 /* Don't show unmerged entries in the staged section. */
5697 static const char *status_diff_index_argv[] = {
5698         "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5699                              "--cached", "-M", "HEAD", NULL
5700 };
5702 static const char *status_diff_files_argv[] = {
5703         "git", "diff-files", "-z", NULL
5704 };
5706 static const char *status_list_other_argv[] = {
5707         "git", "ls-files", "-z", "--others", "--exclude-standard", opt_prefix, NULL, NULL,
5708 };
5710 static const char *status_list_no_head_argv[] = {
5711         "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5712 };
5714 static const char *update_index_argv[] = {
5715         "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5716 };
5718 /* Restore the previous line number to stay in the context or select a
5719  * line with something that can be updated. */
5720 static void
5721 status_restore(struct view *view)
5723         if (view->p_lineno >= view->lines)
5724                 view->p_lineno = view->lines - 1;
5725         while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
5726                 view->p_lineno++;
5727         while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
5728                 view->p_lineno--;
5730         /* If the above fails, always skip the "On branch" line. */
5731         if (view->p_lineno < view->lines)
5732                 view->lineno = view->p_lineno;
5733         else
5734                 view->lineno = 1;
5736         if (view->lineno < view->offset)
5737                 view->offset = view->lineno;
5738         else if (view->offset + view->height <= view->lineno)
5739                 view->offset = view->lineno - view->height + 1;
5741         view->p_restore = FALSE;
5744 static void
5745 status_update_onbranch(void)
5747         static const char *paths[][2] = {
5748                 { "rebase-apply/rebasing",      "Rebasing" },
5749                 { "rebase-apply/applying",      "Applying mailbox" },
5750                 { "rebase-apply/",              "Rebasing mailbox" },
5751                 { "rebase-merge/interactive",   "Interactive rebase" },
5752                 { "rebase-merge/",              "Rebase merge" },
5753                 { "MERGE_HEAD",                 "Merging" },
5754                 { "BISECT_LOG",                 "Bisecting" },
5755                 { "HEAD",                       "On branch" },
5756         };
5757         char buf[SIZEOF_STR];
5758         struct stat stat;
5759         int i;
5761         if (is_initial_commit()) {
5762                 string_copy(status_onbranch, "Initial commit");
5763                 return;
5764         }
5766         for (i = 0; i < ARRAY_SIZE(paths); i++) {
5767                 char *head = opt_head;
5769                 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5770                     lstat(buf, &stat) < 0)
5771                         continue;
5773                 if (!*opt_head) {
5774                         struct io io;
5776                         if (io_open(&io, "%s/rebase-merge/head-name", opt_git_dir) &&
5777                             io_read_buf(&io, buf, sizeof(buf))) {
5778                                 head = buf;
5779                                 if (!prefixcmp(head, "refs/heads/"))
5780                                         head += STRING_SIZE("refs/heads/");
5781                         }
5782                 }
5784                 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5785                         string_copy(status_onbranch, opt_head);
5786                 return;
5787         }
5789         string_copy(status_onbranch, "Not currently on any branch");
5792 /* First parse staged info using git-diff-index(1), then parse unstaged
5793  * info using git-diff-files(1), and finally untracked files using
5794  * git-ls-files(1). */
5795 static bool
5796 status_open(struct view *view)
5798         reset_view(view);
5800         add_line_data(view, NULL, LINE_STAT_HEAD);
5801         status_update_onbranch();
5803         io_run_bg(update_index_argv);
5805         if (is_initial_commit()) {
5806                 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5807                         return FALSE;
5808         } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5809                 return FALSE;
5810         }
5812         if (!opt_untracked_dirs_content)
5813                 status_list_other_argv[ARRAY_SIZE(status_list_other_argv) - 2] = "--directory";
5815         if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5816             !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5817                 return FALSE;
5819         /* Restore the exact position or use the specialized restore
5820          * mode? */
5821         if (!view->p_restore)
5822                 status_restore(view);
5823         return TRUE;
5826 static bool
5827 status_draw(struct view *view, struct line *line, unsigned int lineno)
5829         struct status *status = line->data;
5830         enum line_type type;
5831         const char *text;
5833         if (!status) {
5834                 switch (line->type) {
5835                 case LINE_STAT_STAGED:
5836                         type = LINE_STAT_SECTION;
5837                         text = "Changes to be committed:";
5838                         break;
5840                 case LINE_STAT_UNSTAGED:
5841                         type = LINE_STAT_SECTION;
5842                         text = "Changed but not updated:";
5843                         break;
5845                 case LINE_STAT_UNTRACKED:
5846                         type = LINE_STAT_SECTION;
5847                         text = "Untracked files:";
5848                         break;
5850                 case LINE_STAT_NONE:
5851                         type = LINE_DEFAULT;
5852                         text = "  (no files)";
5853                         break;
5855                 case LINE_STAT_HEAD:
5856                         type = LINE_STAT_HEAD;
5857                         text = status_onbranch;
5858                         break;
5860                 default:
5861                         return FALSE;
5862                 }
5863         } else {
5864                 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5866                 buf[0] = status->status;
5867                 if (draw_text(view, line->type, buf, TRUE))
5868                         return TRUE;
5869                 type = LINE_DEFAULT;
5870                 text = status->new.name;
5871         }
5873         draw_text(view, type, text, TRUE);
5874         return TRUE;
5877 static enum request
5878 status_load_error(struct view *view, struct view *stage, const char *path)
5880         if (displayed_views() == 2 || display[current_view] != view)
5881                 maximize_view(view);
5882         report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5883         return REQ_NONE;
5886 static enum request
5887 status_enter(struct view *view, struct line *line)
5889         struct status *status = line->data;
5890         const char *oldpath = status ? status->old.name : NULL;
5891         /* Diffs for unmerged entries are empty when passing the new
5892          * path, so leave it empty. */
5893         const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5894         const char *info;
5895         enum open_flags split;
5896         struct view *stage = VIEW(REQ_VIEW_STAGE);
5898         if (line->type == LINE_STAT_NONE ||
5899             (!status && line[1].type == LINE_STAT_NONE)) {
5900                 report("No file to diff");
5901                 return REQ_NONE;
5902         }
5904         switch (line->type) {
5905         case LINE_STAT_STAGED:
5906                 if (is_initial_commit()) {
5907                         const char *no_head_diff_argv[] = {
5908                                 "git", "diff", "--no-color", "--patch-with-stat",
5909                                         "--", "/dev/null", newpath, NULL
5910                         };
5912                         if (!prepare_update(stage, no_head_diff_argv, opt_cdup))
5913                                 return status_load_error(view, stage, newpath);
5914                 } else {
5915                         const char *index_show_argv[] = {
5916                                 "git", "diff-index", "--root", "--patch-with-stat",
5917                                         "-C", "-M", "--cached", "HEAD", "--",
5918                                         oldpath, newpath, NULL
5919                         };
5921                         if (!prepare_update(stage, index_show_argv, opt_cdup))
5922                                 return status_load_error(view, stage, newpath);
5923                 }
5925                 if (status)
5926                         info = "Staged changes to %s";
5927                 else
5928                         info = "Staged changes";
5929                 break;
5931         case LINE_STAT_UNSTAGED:
5932         {
5933                 const char *files_show_argv[] = {
5934                         "git", "diff-files", "--root", "--patch-with-stat",
5935                                 "-C", "-M", "--", oldpath, newpath, NULL
5936                 };
5938                 if (!prepare_update(stage, files_show_argv, opt_cdup))
5939                         return status_load_error(view, stage, newpath);
5940                 if (status)
5941                         info = "Unstaged changes to %s";
5942                 else
5943                         info = "Unstaged changes";
5944                 break;
5945         }
5946         case LINE_STAT_UNTRACKED:
5947                 if (!newpath) {
5948                         report("No file to show");
5949                         return REQ_NONE;
5950                 }
5952                 if (!suffixcmp(status->new.name, -1, "/")) {
5953                         report("Cannot display a directory");
5954                         return REQ_NONE;
5955                 }
5957                 if (!prepare_update_file(stage, newpath))
5958                         return status_load_error(view, stage, newpath);
5959                 info = "Untracked file %s";
5960                 break;
5962         case LINE_STAT_HEAD:
5963                 return REQ_NONE;
5965         default:
5966                 die("line type %d not handled in switch", line->type);
5967         }
5969         split = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
5970         open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5971         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5972                 if (status) {
5973                         stage_status = *status;
5974                 } else {
5975                         memset(&stage_status, 0, sizeof(stage_status));
5976                 }
5978                 stage_line_type = line->type;
5979                 stage_chunks = 0;
5980                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5981         }
5983         return REQ_NONE;
5986 static bool
5987 status_exists(struct status *status, enum line_type type)
5989         struct view *view = VIEW(REQ_VIEW_STATUS);
5990         unsigned long lineno;
5992         for (lineno = 0; lineno < view->lines; lineno++) {
5993                 struct line *line = &view->line[lineno];
5994                 struct status *pos = line->data;
5996                 if (line->type != type)
5997                         continue;
5998                 if (!pos && (!status || !status->status) && line[1].data) {
5999                         select_view_line(view, lineno);
6000                         return TRUE;
6001                 }
6002                 if (pos && !strcmp(status->new.name, pos->new.name)) {
6003                         select_view_line(view, lineno);
6004                         return TRUE;
6005                 }
6006         }
6008         return FALSE;
6012 static bool
6013 status_update_prepare(struct io *io, enum line_type type)
6015         const char *staged_argv[] = {
6016                 "git", "update-index", "-z", "--index-info", NULL
6017         };
6018         const char *others_argv[] = {
6019                 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
6020         };
6022         switch (type) {
6023         case LINE_STAT_STAGED:
6024                 return io_run(io, IO_WR, opt_cdup, staged_argv);
6026         case LINE_STAT_UNSTAGED:
6027         case LINE_STAT_UNTRACKED:
6028                 return io_run(io, IO_WR, opt_cdup, others_argv);
6030         default:
6031                 die("line type %d not handled in switch", type);
6032                 return FALSE;
6033         }
6036 static bool
6037 status_update_write(struct io *io, struct status *status, enum line_type type)
6039         char buf[SIZEOF_STR];
6040         size_t bufsize = 0;
6042         switch (type) {
6043         case LINE_STAT_STAGED:
6044                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
6045                                         status->old.mode,
6046                                         status->old.rev,
6047                                         status->old.name, 0))
6048                         return FALSE;
6049                 break;
6051         case LINE_STAT_UNSTAGED:
6052         case LINE_STAT_UNTRACKED:
6053                 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
6054                         return FALSE;
6055                 break;
6057         default:
6058                 die("line type %d not handled in switch", type);
6059         }
6061         return io_write(io, buf, bufsize);
6064 static bool
6065 status_update_file(struct status *status, enum line_type type)
6067         struct io io;
6068         bool result;
6070         if (!status_update_prepare(&io, type))
6071                 return FALSE;
6073         result = status_update_write(&io, status, type);
6074         return io_done(&io) && result;
6077 static bool
6078 status_update_files(struct view *view, struct line *line)
6080         char buf[sizeof(view->ref)];
6081         struct io io;
6082         bool result = TRUE;
6083         struct line *pos = view->line + view->lines;
6084         int files = 0;
6085         int file, done;
6086         int cursor_y = -1, cursor_x = -1;
6088         if (!status_update_prepare(&io, line->type))
6089                 return FALSE;
6091         for (pos = line; pos < view->line + view->lines && pos->data; pos++)
6092                 files++;
6094         string_copy(buf, view->ref);
6095         getsyx(cursor_y, cursor_x);
6096         for (file = 0, done = 5; result && file < files; line++, file++) {
6097                 int almost_done = file * 100 / files;
6099                 if (almost_done > done) {
6100                         done = almost_done;
6101                         string_format(view->ref, "updating file %u of %u (%d%% done)",
6102                                       file, files, done);
6103                         update_view_title(view);
6104                         setsyx(cursor_y, cursor_x);
6105                         doupdate();
6106                 }
6107                 result = status_update_write(&io, line->data, line->type);
6108         }
6109         string_copy(view->ref, buf);
6111         return io_done(&io) && result;
6114 static bool
6115 status_update(struct view *view)
6117         struct line *line = &view->line[view->lineno];
6119         assert(view->lines);
6121         if (!line->data) {
6122                 /* This should work even for the "On branch" line. */
6123                 if (line < view->line + view->lines && !line[1].data) {
6124                         report("Nothing to update");
6125                         return FALSE;
6126                 }
6128                 if (!status_update_files(view, line + 1)) {
6129                         report("Failed to update file status");
6130                         return FALSE;
6131                 }
6133         } else if (!status_update_file(line->data, line->type)) {
6134                 report("Failed to update file status");
6135                 return FALSE;
6136         }
6138         return TRUE;
6141 static bool
6142 status_revert(struct status *status, enum line_type type, bool has_none)
6144         if (!status || type != LINE_STAT_UNSTAGED) {
6145                 if (type == LINE_STAT_STAGED) {
6146                         report("Cannot revert changes to staged files");
6147                 } else if (type == LINE_STAT_UNTRACKED) {
6148                         report("Cannot revert changes to untracked files");
6149                 } else if (has_none) {
6150                         report("Nothing to revert");
6151                 } else {
6152                         report("Cannot revert changes to multiple files");
6153                 }
6155         } else if (prompt_yesno("Are you sure you want to revert changes?")) {
6156                 char mode[10] = "100644";
6157                 const char *reset_argv[] = {
6158                         "git", "update-index", "--cacheinfo", mode,
6159                                 status->old.rev, status->old.name, NULL
6160                 };
6161                 const char *checkout_argv[] = {
6162                         "git", "checkout", "--", status->old.name, NULL
6163                 };
6165                 if (status->status == 'U') {
6166                         string_format(mode, "%5o", status->old.mode);
6168                         if (status->old.mode == 0 && status->new.mode == 0) {
6169                                 reset_argv[2] = "--force-remove";
6170                                 reset_argv[3] = status->old.name;
6171                                 reset_argv[4] = NULL;
6172                         }
6174                         if (!io_run_fg(reset_argv, opt_cdup))
6175                                 return FALSE;
6176                         if (status->old.mode == 0 && status->new.mode == 0)
6177                                 return TRUE;
6178                 }
6180                 return io_run_fg(checkout_argv, opt_cdup);
6181         }
6183         return FALSE;
6186 static enum request
6187 status_request(struct view *view, enum request request, struct line *line)
6189         struct status *status = line->data;
6191         switch (request) {
6192         case REQ_STATUS_UPDATE:
6193                 if (!status_update(view))
6194                         return REQ_NONE;
6195                 break;
6197         case REQ_STATUS_REVERT:
6198                 if (!status_revert(status, line->type, status_has_none(view, line)))
6199                         return REQ_NONE;
6200                 break;
6202         case REQ_STATUS_MERGE:
6203                 if (!status || status->status != 'U') {
6204                         report("Merging only possible for files with unmerged status ('U').");
6205                         return REQ_NONE;
6206                 }
6207                 open_mergetool(status->new.name);
6208                 break;
6210         case REQ_EDIT:
6211                 if (!status)
6212                         return request;
6213                 if (status->status == 'D') {
6214                         report("File has been deleted.");
6215                         return REQ_NONE;
6216                 }
6218                 open_editor(status->new.name);
6219                 break;
6221         case REQ_VIEW_BLAME:
6222                 if (status)
6223                         opt_ref[0] = 0;
6224                 return request;
6226         case REQ_ENTER:
6227                 /* After returning the status view has been split to
6228                  * show the stage view. No further reloading is
6229                  * necessary. */
6230                 return status_enter(view, line);
6232         case REQ_REFRESH:
6233                 /* Simply reload the view. */
6234                 break;
6236         default:
6237                 return request;
6238         }
6240         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
6242         return REQ_NONE;
6245 static void
6246 status_select(struct view *view, struct line *line)
6248         struct status *status = line->data;
6249         char file[SIZEOF_STR] = "all files";
6250         const char *text;
6251         const char *key;
6253         if (status && !string_format(file, "'%s'", status->new.name))
6254                 return;
6256         if (!status && line[1].type == LINE_STAT_NONE)
6257                 line++;
6259         switch (line->type) {
6260         case LINE_STAT_STAGED:
6261                 text = "Press %s to unstage %s for commit";
6262                 break;
6264         case LINE_STAT_UNSTAGED:
6265                 text = "Press %s to stage %s for commit";
6266                 break;
6268         case LINE_STAT_UNTRACKED:
6269                 text = "Press %s to stage %s for addition";
6270                 break;
6272         case LINE_STAT_HEAD:
6273         case LINE_STAT_NONE:
6274                 text = "Nothing to update";
6275                 break;
6277         default:
6278                 die("line type %d not handled in switch", line->type);
6279         }
6281         if (status && status->status == 'U') {
6282                 text = "Press %s to resolve conflict in %s";
6283                 key = get_key(KEYMAP_STATUS, REQ_STATUS_MERGE);
6285         } else {
6286                 key = get_key(KEYMAP_STATUS, REQ_STATUS_UPDATE);
6287         }
6289         string_format(view->ref, text, key, file);
6290         if (status)
6291                 string_copy(opt_file, status->new.name);
6294 static bool
6295 status_grep(struct view *view, struct line *line)
6297         struct status *status = line->data;
6299         if (status) {
6300                 const char buf[2] = { status->status, 0 };
6301                 const char *text[] = { status->new.name, buf, NULL };
6303                 return grep_text(view, text);
6304         }
6306         return FALSE;
6309 static struct view_ops status_ops = {
6310         "file",
6311         NULL,
6312         status_open,
6313         NULL,
6314         status_draw,
6315         status_request,
6316         status_grep,
6317         status_select,
6318 };
6321 static bool
6322 stage_diff_write(struct io *io, struct line *line, struct line *end)
6324         while (line < end) {
6325                 if (!io_write(io, line->data, strlen(line->data)) ||
6326                     !io_write(io, "\n", 1))
6327                         return FALSE;
6328                 line++;
6329                 if (line->type == LINE_DIFF_CHUNK ||
6330                     line->type == LINE_DIFF_HEADER)
6331                         break;
6332         }
6334         return TRUE;
6337 static struct line *
6338 stage_diff_find(struct view *view, struct line *line, enum line_type type)
6340         for (; view->line < line; line--)
6341                 if (line->type == type)
6342                         return line;
6344         return NULL;
6347 static bool
6348 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
6350         const char *apply_argv[SIZEOF_ARG] = {
6351                 "git", "apply", "--whitespace=nowarn", NULL
6352         };
6353         struct line *diff_hdr;
6354         struct io io;
6355         int argc = 3;
6357         diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
6358         if (!diff_hdr)
6359                 return FALSE;
6361         if (!revert)
6362                 apply_argv[argc++] = "--cached";
6363         if (revert || stage_line_type == LINE_STAT_STAGED)
6364                 apply_argv[argc++] = "-R";
6365         apply_argv[argc++] = "-";
6366         apply_argv[argc++] = NULL;
6367         if (!io_run(&io, IO_WR, opt_cdup, apply_argv))
6368                 return FALSE;
6370         if (!stage_diff_write(&io, diff_hdr, chunk) ||
6371             !stage_diff_write(&io, chunk, view->line + view->lines))
6372                 chunk = NULL;
6374         io_done(&io);
6375         io_run_bg(update_index_argv);
6377         return chunk ? TRUE : FALSE;
6380 static bool
6381 stage_update(struct view *view, struct line *line)
6383         struct line *chunk = NULL;
6385         if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
6386                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6388         if (chunk) {
6389                 if (!stage_apply_chunk(view, chunk, FALSE)) {
6390                         report("Failed to apply chunk");
6391                         return FALSE;
6392                 }
6394         } else if (!stage_status.status) {
6395                 view = VIEW(REQ_VIEW_STATUS);
6397                 for (line = view->line; line < view->line + view->lines; line++)
6398                         if (line->type == stage_line_type)
6399                                 break;
6401                 if (!status_update_files(view, line + 1)) {
6402                         report("Failed to update files");
6403                         return FALSE;
6404                 }
6406         } else if (!status_update_file(&stage_status, stage_line_type)) {
6407                 report("Failed to update file");
6408                 return FALSE;
6409         }
6411         return TRUE;
6414 static bool
6415 stage_revert(struct view *view, struct line *line)
6417         struct line *chunk = NULL;
6419         if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
6420                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6422         if (chunk) {
6423                 if (!prompt_yesno("Are you sure you want to revert changes?"))
6424                         return FALSE;
6426                 if (!stage_apply_chunk(view, chunk, TRUE)) {
6427                         report("Failed to revert chunk");
6428                         return FALSE;
6429                 }
6430                 return TRUE;
6432         } else {
6433                 return status_revert(stage_status.status ? &stage_status : NULL,
6434                                      stage_line_type, FALSE);
6435         }
6439 static void
6440 stage_next(struct view *view, struct line *line)
6442         int i;
6444         if (!stage_chunks) {
6445                 for (line = view->line; line < view->line + view->lines; line++) {
6446                         if (line->type != LINE_DIFF_CHUNK)
6447                                 continue;
6449                         if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
6450                                 report("Allocation failure");
6451                                 return;
6452                         }
6454                         stage_chunk[stage_chunks++] = line - view->line;
6455                 }
6456         }
6458         for (i = 0; i < stage_chunks; i++) {
6459                 if (stage_chunk[i] > view->lineno) {
6460                         do_scroll_view(view, stage_chunk[i] - view->lineno);
6461                         report("Chunk %d of %d", i + 1, stage_chunks);
6462                         return;
6463                 }
6464         }
6466         report("No next chunk found");
6469 static enum request
6470 stage_request(struct view *view, enum request request, struct line *line)
6472         switch (request) {
6473         case REQ_STATUS_UPDATE:
6474                 if (!stage_update(view, line))
6475                         return REQ_NONE;
6476                 break;
6478         case REQ_STATUS_REVERT:
6479                 if (!stage_revert(view, line))
6480                         return REQ_NONE;
6481                 break;
6483         case REQ_STAGE_NEXT:
6484                 if (stage_line_type == LINE_STAT_UNTRACKED) {
6485                         report("File is untracked; press %s to add",
6486                                get_key(KEYMAP_STAGE, REQ_STATUS_UPDATE));
6487                         return REQ_NONE;
6488                 }
6489                 stage_next(view, line);
6490                 return REQ_NONE;
6492         case REQ_EDIT:
6493                 if (!stage_status.new.name[0])
6494                         return request;
6495                 if (stage_status.status == 'D') {
6496                         report("File has been deleted.");
6497                         return REQ_NONE;
6498                 }
6500                 open_editor(stage_status.new.name);
6501                 break;
6503         case REQ_REFRESH:
6504                 /* Reload everything ... */
6505                 break;
6507         case REQ_VIEW_BLAME:
6508                 if (stage_status.new.name[0]) {
6509                         string_copy(opt_file, stage_status.new.name);
6510                         opt_ref[0] = 0;
6511                 }
6512                 return request;
6514         case REQ_ENTER:
6515                 return pager_request(view, request, line);
6517         default:
6518                 return request;
6519         }
6521         VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
6522         open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
6524         /* Check whether the staged entry still exists, and close the
6525          * stage view if it doesn't. */
6526         if (!status_exists(&stage_status, stage_line_type)) {
6527                 status_restore(VIEW(REQ_VIEW_STATUS));
6528                 return REQ_VIEW_CLOSE;
6529         }
6531         if (stage_line_type == LINE_STAT_UNTRACKED) {
6532                 if (!suffixcmp(stage_status.new.name, -1, "/")) {
6533                         report("Cannot display a directory");
6534                         return REQ_NONE;
6535                 }
6537                 if (!prepare_update_file(view, stage_status.new.name)) {
6538                         report("Failed to open file: %s", strerror(errno));
6539                         return REQ_NONE;
6540                 }
6541         }
6542         open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
6544         return REQ_NONE;
6547 static struct view_ops stage_ops = {
6548         "line",
6549         NULL,
6550         NULL,
6551         pager_read,
6552         pager_draw,
6553         stage_request,
6554         pager_grep,
6555         pager_select,
6556 };
6559 /*
6560  * Revision graph
6561  */
6563 struct commit {
6564         char id[SIZEOF_REV];            /* SHA1 ID. */
6565         char title[128];                /* First line of the commit message. */
6566         const char *author;             /* Author of the commit. */
6567         struct time time;               /* Date from the author ident. */
6568         struct ref_list *refs;          /* Repository references. */
6569         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
6570         size_t graph_size;              /* The width of the graph array. */
6571         bool has_parents;               /* Rewritten --parents seen. */
6572 };
6574 /* Size of rev graph with no  "padding" columns */
6575 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6577 struct rev_graph {
6578         struct rev_graph *prev, *next, *parents;
6579         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
6580         size_t size;
6581         struct commit *commit;
6582         size_t pos;
6583         unsigned int boundary:1;
6584 };
6586 /* Parents of the commit being visualized. */
6587 static struct rev_graph graph_parents[4];
6589 /* The current stack of revisions on the graph. */
6590 static struct rev_graph graph_stacks[4] = {
6591         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
6592         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
6593         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
6594         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
6595 };
6597 static inline bool
6598 graph_parent_is_merge(struct rev_graph *graph)
6600         return graph->parents->size > 1;
6603 static inline void
6604 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
6606         struct commit *commit = graph->commit;
6608         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
6609                 commit->graph[commit->graph_size++] = symbol;
6612 static void
6613 clear_rev_graph(struct rev_graph *graph)
6615         graph->boundary = 0;
6616         graph->size = graph->pos = 0;
6617         graph->commit = NULL;
6618         memset(graph->parents, 0, sizeof(*graph->parents));
6621 static void
6622 done_rev_graph(struct rev_graph *graph)
6624         if (graph_parent_is_merge(graph) &&
6625             graph->pos < graph->size - 1 &&
6626             graph->next->size == graph->size + graph->parents->size - 1) {
6627                 size_t i = graph->pos + graph->parents->size - 1;
6629                 graph->commit->graph_size = i * 2;
6630                 while (i < graph->next->size - 1) {
6631                         append_to_rev_graph(graph, ' ');
6632                         append_to_rev_graph(graph, '\\');
6633                         i++;
6634                 }
6635         }
6637         clear_rev_graph(graph);
6640 static void
6641 push_rev_graph(struct rev_graph *graph, const char *parent)
6643         int i;
6645         /* "Collapse" duplicate parents lines.
6646          *
6647          * FIXME: This needs to also update update the drawn graph but
6648          * for now it just serves as a method for pruning graph lines. */
6649         for (i = 0; i < graph->size; i++)
6650                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
6651                         return;
6653         if (graph->size < SIZEOF_REVITEMS) {
6654                 string_copy_rev(graph->rev[graph->size++], parent);
6655         }
6658 static chtype
6659 get_rev_graph_symbol(struct rev_graph *graph)
6661         chtype symbol;
6663         if (graph->boundary)
6664                 symbol = REVGRAPH_BOUND;
6665         else if (graph->parents->size == 0)
6666                 symbol = REVGRAPH_INIT;
6667         else if (graph_parent_is_merge(graph))
6668                 symbol = REVGRAPH_MERGE;
6669         else if (graph->pos >= graph->size)
6670                 symbol = REVGRAPH_BRANCH;
6671         else
6672                 symbol = REVGRAPH_COMMIT;
6674         return symbol;
6677 static void
6678 draw_rev_graph(struct rev_graph *graph)
6680         struct rev_filler {
6681                 chtype separator, line;
6682         };
6683         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
6684         static struct rev_filler fillers[] = {
6685                 { ' ',  '|' },
6686                 { '`',  '.' },
6687                 { '\'', ' ' },
6688                 { '/',  ' ' },
6689         };
6690         chtype symbol = get_rev_graph_symbol(graph);
6691         struct rev_filler *filler;
6692         size_t i;
6694         fillers[DEFAULT].line = opt_line_graphics ? ACS_VLINE : '|';
6695         filler = &fillers[DEFAULT];
6697         for (i = 0; i < graph->pos; i++) {
6698                 append_to_rev_graph(graph, filler->line);
6699                 if (graph_parent_is_merge(graph->prev) &&
6700                     graph->prev->pos == i)
6701                         filler = &fillers[RSHARP];
6703                 append_to_rev_graph(graph, filler->separator);
6704         }
6706         /* Place the symbol for this revision. */
6707         append_to_rev_graph(graph, symbol);
6709         if (graph->prev->size > graph->size)
6710                 filler = &fillers[RDIAG];
6711         else
6712                 filler = &fillers[DEFAULT];
6714         i++;
6716         for (; i < graph->size; i++) {
6717                 append_to_rev_graph(graph, filler->separator);
6718                 append_to_rev_graph(graph, filler->line);
6719                 if (graph_parent_is_merge(graph->prev) &&
6720                     i < graph->prev->pos + graph->parents->size)
6721                         filler = &fillers[RSHARP];
6722                 if (graph->prev->size > graph->size)
6723                         filler = &fillers[LDIAG];
6724         }
6726         if (graph->prev->size > graph->size) {
6727                 append_to_rev_graph(graph, filler->separator);
6728                 if (filler->line != ' ')
6729                         append_to_rev_graph(graph, filler->line);
6730         }
6733 /* Prepare the next rev graph */
6734 static void
6735 prepare_rev_graph(struct rev_graph *graph)
6737         size_t i;
6739         /* First, traverse all lines of revisions up to the active one. */
6740         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6741                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6742                         break;
6744                 push_rev_graph(graph->next, graph->rev[graph->pos]);
6745         }
6747         /* Interleave the new revision parent(s). */
6748         for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6749                 push_rev_graph(graph->next, graph->parents->rev[i]);
6751         /* Lastly, put any remaining revisions. */
6752         for (i = graph->pos + 1; i < graph->size; i++)
6753                 push_rev_graph(graph->next, graph->rev[i]);
6756 static void
6757 update_rev_graph(struct view *view, struct rev_graph *graph)
6759         /* If this is the finalizing update ... */
6760         if (graph->commit)
6761                 prepare_rev_graph(graph);
6763         /* Graph visualization needs a one rev look-ahead,
6764          * so the first update doesn't visualize anything. */
6765         if (!graph->prev->commit)
6766                 return;
6768         if (view->lines > 2)
6769                 view->line[view->lines - 3].dirty = 1;
6770         if (view->lines > 1)
6771                 view->line[view->lines - 2].dirty = 1;
6772         draw_rev_graph(graph->prev);
6773         done_rev_graph(graph->prev->prev);
6777 /*
6778  * Main view backend
6779  */
6781 static const char *main_argv[SIZEOF_ARG] = {
6782         "git", "log", "--no-color", "--pretty=raw", "--parents",
6783                 "--topo-order", "%(diffargs)", "%(revargs)",
6784                 "--", "%(fileargs)", NULL
6785 };
6787 static bool
6788 main_draw(struct view *view, struct line *line, unsigned int lineno)
6790         struct commit *commit = line->data;
6792         if (!commit->author)
6793                 return FALSE;
6795         if (opt_date && draw_date(view, &commit->time))
6796                 return TRUE;
6798         if (opt_author && draw_author(view, commit->author))
6799                 return TRUE;
6801         if (opt_rev_graph && commit->graph_size &&
6802             draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6803                 return TRUE;
6805         if (opt_show_refs && commit->refs) {
6806                 size_t i;
6808                 for (i = 0; i < commit->refs->size; i++) {
6809                         struct ref *ref = commit->refs->refs[i];
6810                         enum line_type type;
6812                         if (ref->head)
6813                                 type = LINE_MAIN_HEAD;
6814                         else if (ref->ltag)
6815                                 type = LINE_MAIN_LOCAL_TAG;
6816                         else if (ref->tag)
6817                                 type = LINE_MAIN_TAG;
6818                         else if (ref->tracked)
6819                                 type = LINE_MAIN_TRACKED;
6820                         else if (ref->remote)
6821                                 type = LINE_MAIN_REMOTE;
6822                         else
6823                                 type = LINE_MAIN_REF;
6825                         if (draw_text(view, type, "[", TRUE) ||
6826                             draw_text(view, type, ref->name, TRUE) ||
6827                             draw_text(view, type, "]", TRUE))
6828                                 return TRUE;
6830                         if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6831                                 return TRUE;
6832                 }
6833         }
6835         draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6836         return TRUE;
6839 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6840 static bool
6841 main_read(struct view *view, char *line)
6843         static struct rev_graph *graph = graph_stacks;
6844         enum line_type type;
6845         struct commit *commit;
6847         if (!line) {
6848                 int i;
6850                 if (!view->lines && !view->prev)
6851                         die("No revisions match the given arguments.");
6852                 if (view->lines > 0) {
6853                         commit = view->line[view->lines - 1].data;
6854                         view->line[view->lines - 1].dirty = 1;
6855                         if (!commit->author) {
6856                                 view->lines--;
6857                                 free(commit);
6858                                 graph->commit = NULL;
6859                         }
6860                 }
6861                 update_rev_graph(view, graph);
6863                 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6864                         clear_rev_graph(&graph_stacks[i]);
6865                 return TRUE;
6866         }
6868         type = get_line_type(line);
6869         if (type == LINE_COMMIT) {
6870                 commit = calloc(1, sizeof(struct commit));
6871                 if (!commit)
6872                         return FALSE;
6874                 line += STRING_SIZE("commit ");
6875                 if (*line == '-') {
6876                         graph->boundary = 1;
6877                         line++;
6878                 }
6880                 string_copy_rev(commit->id, line);
6881                 commit->refs = get_ref_list(commit->id);
6882                 graph->commit = commit;
6883                 add_line_data(view, commit, LINE_MAIN_COMMIT);
6885                 while ((line = strchr(line, ' '))) {
6886                         line++;
6887                         push_rev_graph(graph->parents, line);
6888                         commit->has_parents = TRUE;
6889                 }
6890                 return TRUE;
6891         }
6893         if (!view->lines)
6894                 return TRUE;
6895         commit = view->line[view->lines - 1].data;
6897         switch (type) {
6898         case LINE_PARENT:
6899                 if (commit->has_parents)
6900                         break;
6901                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6902                 break;
6904         case LINE_AUTHOR:
6905                 parse_author_line(line + STRING_SIZE("author "),
6906                                   &commit->author, &commit->time);
6907                 update_rev_graph(view, graph);
6908                 graph = graph->next;
6909                 break;
6911         default:
6912                 /* Fill in the commit title if it has not already been set. */
6913                 if (commit->title[0])
6914                         break;
6916                 /* Require titles to start with a non-space character at the
6917                  * offset used by git log. */
6918                 if (strncmp(line, "    ", 4))
6919                         break;
6920                 line += 4;
6921                 /* Well, if the title starts with a whitespace character,
6922                  * try to be forgiving.  Otherwise we end up with no title. */
6923                 while (isspace(*line))
6924                         line++;
6925                 if (*line == '\0')
6926                         break;
6927                 /* FIXME: More graceful handling of titles; append "..." to
6928                  * shortened titles, etc. */
6930                 string_expand(commit->title, sizeof(commit->title), line, 1);
6931                 view->line[view->lines - 1].dirty = 1;
6932         }
6934         return TRUE;
6937 static enum request
6938 main_request(struct view *view, enum request request, struct line *line)
6940         enum open_flags flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
6942         switch (request) {
6943         case REQ_ENTER:
6944                 open_view(view, REQ_VIEW_DIFF, flags);
6945                 break;
6946         case REQ_REFRESH:
6947                 load_refs();
6948                 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6949                 break;
6950         default:
6951                 return request;
6952         }
6954         return REQ_NONE;
6957 static bool
6958 grep_refs(struct ref_list *list, regex_t *regex)
6960         regmatch_t pmatch;
6961         size_t i;
6963         if (!opt_show_refs || !list)
6964                 return FALSE;
6966         for (i = 0; i < list->size; i++) {
6967                 if (regexec(regex, list->refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6968                         return TRUE;
6969         }
6971         return FALSE;
6974 static bool
6975 main_grep(struct view *view, struct line *line)
6977         struct commit *commit = line->data;
6978         const char *text[] = {
6979                 commit->title,
6980                 opt_author ? commit->author : "",
6981                 mkdate(&commit->time, opt_date),
6982                 NULL
6983         };
6985         return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6988 static void
6989 main_select(struct view *view, struct line *line)
6991         struct commit *commit = line->data;
6993         string_copy_rev(view->ref, commit->id);
6994         string_copy_rev(ref_commit, view->ref);
6997 static struct view_ops main_ops = {
6998         "commit",
6999         main_argv,
7000         NULL,
7001         main_read,
7002         main_draw,
7003         main_request,
7004         main_grep,
7005         main_select,
7006 };
7009 /*
7010  * Status management
7011  */
7013 /* Whether or not the curses interface has been initialized. */
7014 static bool cursed = FALSE;
7016 /* Terminal hacks and workarounds. */
7017 static bool use_scroll_redrawwin;
7018 static bool use_scroll_status_wclear;
7020 /* The status window is used for polling keystrokes. */
7021 static WINDOW *status_win;
7023 /* Reading from the prompt? */
7024 static bool input_mode = FALSE;
7026 static bool status_empty = FALSE;
7028 /* Update status and title window. */
7029 static void
7030 report(const char *msg, ...)
7032         struct view *view = display[current_view];
7034         if (input_mode)
7035                 return;
7037         if (!view) {
7038                 char buf[SIZEOF_STR];
7039                 va_list args;
7041                 va_start(args, msg);
7042                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
7043                         buf[sizeof(buf) - 1] = 0;
7044                         buf[sizeof(buf) - 2] = '.';
7045                         buf[sizeof(buf) - 3] = '.';
7046                         buf[sizeof(buf) - 4] = '.';
7047                 }
7048                 va_end(args);
7049                 die("%s", buf);
7050         }
7052         if (!status_empty || *msg) {
7053                 va_list args;
7055                 va_start(args, msg);
7057                 wmove(status_win, 0, 0);
7058                 if (view->has_scrolled && use_scroll_status_wclear)
7059                         wclear(status_win);
7060                 if (*msg) {
7061                         vwprintw(status_win, msg, args);
7062                         status_empty = FALSE;
7063                 } else {
7064                         status_empty = TRUE;
7065                 }
7066                 wclrtoeol(status_win);
7067                 wnoutrefresh(status_win);
7069                 va_end(args);
7070         }
7072         update_view_title(view);
7075 static void
7076 init_display(void)
7078         const char *term;
7079         int x, y;
7081         /* Initialize the curses library */
7082         if (isatty(STDIN_FILENO)) {
7083                 cursed = !!initscr();
7084                 opt_tty = stdin;
7085         } else {
7086                 /* Leave stdin and stdout alone when acting as a pager. */
7087                 opt_tty = fopen("/dev/tty", "r+");
7088                 if (!opt_tty)
7089                         die("Failed to open /dev/tty");
7090                 cursed = !!newterm(NULL, opt_tty, opt_tty);
7091         }
7093         if (!cursed)
7094                 die("Failed to initialize curses");
7096         nonl();         /* Disable conversion and detect newlines from input. */
7097         cbreak();       /* Take input chars one at a time, no wait for \n */
7098         noecho();       /* Don't echo input */
7099         leaveok(stdscr, FALSE);
7101         if (has_colors())
7102                 init_colors();
7104         getmaxyx(stdscr, y, x);
7105         status_win = newwin(1, 0, y - 1, 0);
7106         if (!status_win)
7107                 die("Failed to create status window");
7109         /* Enable keyboard mapping */
7110         keypad(status_win, TRUE);
7111         wbkgdset(status_win, get_line_attr(LINE_STATUS));
7113         TABSIZE = opt_tab_size;
7115         term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
7116         if (term && !strcmp(term, "gnome-terminal")) {
7117                 /* In the gnome-terminal-emulator, the message from
7118                  * scrolling up one line when impossible followed by
7119                  * scrolling down one line causes corruption of the
7120                  * status line. This is fixed by calling wclear. */
7121                 use_scroll_status_wclear = TRUE;
7122                 use_scroll_redrawwin = FALSE;
7124         } else if (term && !strcmp(term, "xrvt-xpm")) {
7125                 /* No problems with full optimizations in xrvt-(unicode)
7126                  * and aterm. */
7127                 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
7129         } else {
7130                 /* When scrolling in (u)xterm the last line in the
7131                  * scrolling direction will update slowly. */
7132                 use_scroll_redrawwin = TRUE;
7133                 use_scroll_status_wclear = FALSE;
7134         }
7137 static int
7138 get_input(int prompt_position)
7140         struct view *view;
7141         int i, key, cursor_y, cursor_x;
7143         if (prompt_position)
7144                 input_mode = TRUE;
7146         while (TRUE) {
7147                 bool loading = FALSE;
7149                 foreach_view (view, i) {
7150                         update_view(view);
7151                         if (view_is_displayed(view) && view->has_scrolled &&
7152                             use_scroll_redrawwin)
7153                                 redrawwin(view->win);
7154                         view->has_scrolled = FALSE;
7155                         if (view->pipe)
7156                                 loading = TRUE;
7157                 }
7159                 /* Update the cursor position. */
7160                 if (prompt_position) {
7161                         getbegyx(status_win, cursor_y, cursor_x);
7162                         cursor_x = prompt_position;
7163                 } else {
7164                         view = display[current_view];
7165                         getbegyx(view->win, cursor_y, cursor_x);
7166                         cursor_x = view->width - 1;
7167                         cursor_y += view->lineno - view->offset;
7168                 }
7169                 setsyx(cursor_y, cursor_x);
7171                 /* Refresh, accept single keystroke of input */
7172                 doupdate();
7173                 nodelay(status_win, loading);
7174                 key = wgetch(status_win);
7176                 /* wgetch() with nodelay() enabled returns ERR when
7177                  * there's no input. */
7178                 if (key == ERR) {
7180                 } else if (key == KEY_RESIZE) {
7181                         int height, width;
7183                         getmaxyx(stdscr, height, width);
7185                         wresize(status_win, 1, width);
7186                         mvwin(status_win, height - 1, 0);
7187                         wnoutrefresh(status_win);
7188                         resize_display();
7189                         redraw_display(TRUE);
7191                 } else {
7192                         input_mode = FALSE;
7193                         return key;
7194                 }
7195         }
7198 static char *
7199 prompt_input(const char *prompt, input_handler handler, void *data)
7201         enum input_status status = INPUT_OK;
7202         static char buf[SIZEOF_STR];
7203         size_t pos = 0;
7205         buf[pos] = 0;
7207         while (status == INPUT_OK || status == INPUT_SKIP) {
7208                 int key;
7210                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
7211                 wclrtoeol(status_win);
7213                 key = get_input(pos + 1);
7214                 switch (key) {
7215                 case KEY_RETURN:
7216                 case KEY_ENTER:
7217                 case '\n':
7218                         status = pos ? INPUT_STOP : INPUT_CANCEL;
7219                         break;
7221                 case KEY_BACKSPACE:
7222                         if (pos > 0)
7223                                 buf[--pos] = 0;
7224                         else
7225                                 status = INPUT_CANCEL;
7226                         break;
7228                 case KEY_ESC:
7229                         status = INPUT_CANCEL;
7230                         break;
7232                 default:
7233                         if (pos >= sizeof(buf)) {
7234                                 report("Input string too long");
7235                                 return NULL;
7236                         }
7238                         status = handler(data, buf, key);
7239                         if (status == INPUT_OK)
7240                                 buf[pos++] = (char) key;
7241                 }
7242         }
7244         /* Clear the status window */
7245         status_empty = FALSE;
7246         report("");
7248         if (status == INPUT_CANCEL)
7249                 return NULL;
7251         buf[pos++] = 0;
7253         return buf;
7256 static enum input_status
7257 prompt_yesno_handler(void *data, char *buf, int c)
7259         if (c == 'y' || c == 'Y')
7260                 return INPUT_STOP;
7261         if (c == 'n' || c == 'N')
7262                 return INPUT_CANCEL;
7263         return INPUT_SKIP;
7266 static bool
7267 prompt_yesno(const char *prompt)
7269         char prompt2[SIZEOF_STR];
7271         if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
7272                 return FALSE;
7274         return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
7277 static enum input_status
7278 read_prompt_handler(void *data, char *buf, int c)
7280         return isprint(c) ? INPUT_OK : INPUT_SKIP;
7283 static char *
7284 read_prompt(const char *prompt)
7286         return prompt_input(prompt, read_prompt_handler, NULL);
7289 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected)
7291         enum input_status status = INPUT_OK;
7292         int size = 0;
7294         while (items[size].text)
7295                 size++;
7297         while (status == INPUT_OK) {
7298                 const struct menu_item *item = &items[*selected];
7299                 int key;
7300                 int i;
7302                 mvwprintw(status_win, 0, 0, "%s (%d of %d) ",
7303                           prompt, *selected + 1, size);
7304                 if (item->hotkey)
7305                         wprintw(status_win, "[%c] ", (char) item->hotkey);
7306                 wprintw(status_win, "%s", item->text);
7307                 wclrtoeol(status_win);
7309                 key = get_input(COLS - 1);
7310                 switch (key) {
7311                 case KEY_RETURN:
7312                 case KEY_ENTER:
7313                 case '\n':
7314                         status = INPUT_STOP;
7315                         break;
7317                 case KEY_LEFT:
7318                 case KEY_UP:
7319                         *selected = *selected - 1;
7320                         if (*selected < 0)
7321                                 *selected = size - 1;
7322                         break;
7324                 case KEY_RIGHT:
7325                 case KEY_DOWN:
7326                         *selected = (*selected + 1) % size;
7327                         break;
7329                 case KEY_ESC:
7330                         status = INPUT_CANCEL;
7331                         break;
7333                 default:
7334                         for (i = 0; items[i].text; i++)
7335                                 if (items[i].hotkey == key) {
7336                                         *selected = i;
7337                                         status = INPUT_STOP;
7338                                         break;
7339                                 }
7340                 }
7341         }
7343         /* Clear the status window */
7344         status_empty = FALSE;
7345         report("");
7347         return status != INPUT_CANCEL;
7350 /*
7351  * Repository properties
7352  */
7354 static struct ref **refs = NULL;
7355 static size_t refs_size = 0;
7356 static struct ref *refs_head = NULL;
7358 static struct ref_list **ref_lists = NULL;
7359 static size_t ref_lists_size = 0;
7361 DEFINE_ALLOCATOR(realloc_refs, struct ref *, 256)
7362 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
7363 DEFINE_ALLOCATOR(realloc_ref_lists, struct ref_list *, 8)
7365 static int
7366 compare_refs(const void *ref1_, const void *ref2_)
7368         const struct ref *ref1 = *(const struct ref **)ref1_;
7369         const struct ref *ref2 = *(const struct ref **)ref2_;
7371         if (ref1->tag != ref2->tag)
7372                 return ref2->tag - ref1->tag;
7373         if (ref1->ltag != ref2->ltag)
7374                 return ref2->ltag - ref2->ltag;
7375         if (ref1->head != ref2->head)
7376                 return ref2->head - ref1->head;
7377         if (ref1->tracked != ref2->tracked)
7378                 return ref2->tracked - ref1->tracked;
7379         if (ref1->remote != ref2->remote)
7380                 return ref2->remote - ref1->remote;
7381         return strcmp(ref1->name, ref2->name);
7384 static void
7385 foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data)
7387         size_t i;
7389         for (i = 0; i < refs_size; i++)
7390                 if (!visitor(data, refs[i]))
7391                         break;
7394 static struct ref *
7395 get_ref_head()
7397         return refs_head;
7400 static struct ref_list *
7401 get_ref_list(const char *id)
7403         struct ref_list *list;
7404         size_t i;
7406         for (i = 0; i < ref_lists_size; i++)
7407                 if (!strcmp(id, ref_lists[i]->id))
7408                         return ref_lists[i];
7410         if (!realloc_ref_lists(&ref_lists, ref_lists_size, 1))
7411                 return NULL;
7412         list = calloc(1, sizeof(*list));
7413         if (!list)
7414                 return NULL;
7416         for (i = 0; i < refs_size; i++) {
7417                 if (!strcmp(id, refs[i]->id) &&
7418                     realloc_refs_list(&list->refs, list->size, 1))
7419                         list->refs[list->size++] = refs[i];
7420         }
7422         if (!list->refs) {
7423                 free(list);
7424                 return NULL;
7425         }
7427         qsort(list->refs, list->size, sizeof(*list->refs), compare_refs);
7428         ref_lists[ref_lists_size++] = list;
7429         return list;
7432 static int
7433 read_ref(char *id, size_t idlen, char *name, size_t namelen)
7435         struct ref *ref = NULL;
7436         bool tag = FALSE;
7437         bool ltag = FALSE;
7438         bool remote = FALSE;
7439         bool tracked = FALSE;
7440         bool head = FALSE;
7441         int from = 0, to = refs_size - 1;
7443         if (!prefixcmp(name, "refs/tags/")) {
7444                 if (!suffixcmp(name, namelen, "^{}")) {
7445                         namelen -= 3;
7446                         name[namelen] = 0;
7447                 } else {
7448                         ltag = TRUE;
7449                 }
7451                 tag = TRUE;
7452                 namelen -= STRING_SIZE("refs/tags/");
7453                 name    += STRING_SIZE("refs/tags/");
7455         } else if (!prefixcmp(name, "refs/remotes/")) {
7456                 remote = TRUE;
7457                 namelen -= STRING_SIZE("refs/remotes/");
7458                 name    += STRING_SIZE("refs/remotes/");
7459                 tracked  = !strcmp(opt_remote, name);
7461         } else if (!prefixcmp(name, "refs/heads/")) {
7462                 namelen -= STRING_SIZE("refs/heads/");
7463                 name    += STRING_SIZE("refs/heads/");
7464                 if (!strncmp(opt_head, name, namelen))
7465                         return OK;
7467         } else if (!strcmp(name, "HEAD")) {
7468                 head     = TRUE;
7469                 if (*opt_head) {
7470                         namelen  = strlen(opt_head);
7471                         name     = opt_head;
7472                 }
7473         }
7475         /* If we are reloading or it's an annotated tag, replace the
7476          * previous SHA1 with the resolved commit id; relies on the fact
7477          * git-ls-remote lists the commit id of an annotated tag right
7478          * before the commit id it points to. */
7479         while (from <= to) {
7480                 size_t pos = (to + from) / 2;
7481                 int cmp = strcmp(name, refs[pos]->name);
7483                 if (!cmp) {
7484                         ref = refs[pos];
7485                         break;
7486                 }
7488                 if (cmp < 0)
7489                         to = pos - 1;
7490                 else
7491                         from = pos + 1;
7492         }
7494         if (!ref) {
7495                 if (!realloc_refs(&refs, refs_size, 1))
7496                         return ERR;
7497                 ref = calloc(1, sizeof(*ref) + namelen);
7498                 if (!ref)
7499                         return ERR;
7500                 memmove(refs + from + 1, refs + from,
7501                         (refs_size - from) * sizeof(*refs));
7502                 refs[from] = ref;
7503                 strncpy(ref->name, name, namelen);
7504                 refs_size++;
7505         }
7507         ref->head = head;
7508         ref->tag = tag;
7509         ref->ltag = ltag;
7510         ref->remote = remote;
7511         ref->tracked = tracked;
7512         string_copy_rev(ref->id, id);
7514         if (head)
7515                 refs_head = ref;
7516         return OK;
7519 static int
7520 load_refs(void)
7522         const char *head_argv[] = {
7523                 "git", "symbolic-ref", "HEAD", NULL
7524         };
7525         static const char *ls_remote_argv[SIZEOF_ARG] = {
7526                 "git", "ls-remote", opt_git_dir, NULL
7527         };
7528         static bool init = FALSE;
7529         size_t i;
7531         if (!init) {
7532                 if (!argv_from_env(ls_remote_argv, "TIG_LS_REMOTE"))
7533                         die("TIG_LS_REMOTE contains too many arguments");
7534                 init = TRUE;
7535         }
7537         if (!*opt_git_dir)
7538                 return OK;
7540         if (io_run_buf(head_argv, opt_head, sizeof(opt_head)) &&
7541             !prefixcmp(opt_head, "refs/heads/")) {
7542                 char *offset = opt_head + STRING_SIZE("refs/heads/");
7544                 memmove(opt_head, offset, strlen(offset) + 1);
7545         }
7547         refs_head = NULL;
7548         for (i = 0; i < refs_size; i++)
7549                 refs[i]->id[0] = 0;
7551         if (io_run_load(ls_remote_argv, "\t", read_ref) == ERR)
7552                 return ERR;
7554         /* Update the ref lists to reflect changes. */
7555         for (i = 0; i < ref_lists_size; i++) {
7556                 struct ref_list *list = ref_lists[i];
7557                 size_t old, new;
7559                 for (old = new = 0; old < list->size; old++)
7560                         if (!strcmp(list->id, list->refs[old]->id))
7561                                 list->refs[new++] = list->refs[old];
7562                 list->size = new;
7563         }
7565         return OK;
7568 static void
7569 set_remote_branch(const char *name, const char *value, size_t valuelen)
7571         if (!strcmp(name, ".remote")) {
7572                 string_ncopy(opt_remote, value, valuelen);
7574         } else if (*opt_remote && !strcmp(name, ".merge")) {
7575                 size_t from = strlen(opt_remote);
7577                 if (!prefixcmp(value, "refs/heads/"))
7578                         value += STRING_SIZE("refs/heads/");
7580                 if (!string_format_from(opt_remote, &from, "/%s", value))
7581                         opt_remote[0] = 0;
7582         }
7585 static void
7586 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
7588         const char *argv[SIZEOF_ARG] = { name, "=" };
7589         int argc = 1 + (cmd == option_set_command);
7590         int error = ERR;
7592         if (!argv_from_string(argv, &argc, value))
7593                 config_msg = "Too many option arguments";
7594         else
7595                 error = cmd(argc, argv);
7597         if (error == ERR)
7598                 warn("Option 'tig.%s': %s", name, config_msg);
7601 static bool
7602 set_environment_variable(const char *name, const char *value)
7604         size_t len = strlen(name) + 1 + strlen(value) + 1;
7605         char *env = malloc(len);
7607         if (env &&
7608             string_nformat(env, len, NULL, "%s=%s", name, value) &&
7609             putenv(env) == 0)
7610                 return TRUE;
7611         free(env);
7612         return FALSE;
7615 static void
7616 set_work_tree(const char *value)
7618         char cwd[SIZEOF_STR];
7620         if (!getcwd(cwd, sizeof(cwd)))
7621                 die("Failed to get cwd path: %s", strerror(errno));
7622         if (chdir(opt_git_dir) < 0)
7623                 die("Failed to chdir(%s): %s", strerror(errno));
7624         if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7625                 die("Failed to get git path: %s", strerror(errno));
7626         if (chdir(cwd) < 0)
7627                 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7628         if (chdir(value) < 0)
7629                 die("Failed to chdir(%s): %s", value, strerror(errno));
7630         if (!getcwd(cwd, sizeof(cwd)))
7631                 die("Failed to get cwd path: %s", strerror(errno));
7632         if (!set_environment_variable("GIT_WORK_TREE", cwd))
7633                 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7634         if (!set_environment_variable("GIT_DIR", opt_git_dir))
7635                 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7636         opt_is_inside_work_tree = TRUE;
7639 static int
7640 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
7642         if (!strcmp(name, "i18n.commitencoding"))
7643                 string_ncopy(opt_encoding, value, valuelen);
7645         else if (!strcmp(name, "core.editor"))
7646                 string_ncopy(opt_editor, value, valuelen);
7648         else if (!strcmp(name, "core.worktree"))
7649                 set_work_tree(value);
7651         else if (!prefixcmp(name, "tig.color."))
7652                 set_repo_config_option(name + 10, value, option_color_command);
7654         else if (!prefixcmp(name, "tig.bind."))
7655                 set_repo_config_option(name + 9, value, option_bind_command);
7657         else if (!prefixcmp(name, "tig."))
7658                 set_repo_config_option(name + 4, value, option_set_command);
7660         else if (*opt_head && !prefixcmp(name, "branch.") &&
7661                  !strncmp(name + 7, opt_head, strlen(opt_head)))
7662                 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7664         return OK;
7667 static int
7668 load_git_config(void)
7670         const char *config_list_argv[] = { "git", "config", "--list", NULL };
7672         return io_run_load(config_list_argv, "=", read_repo_config_option);
7675 static int
7676 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
7678         if (!opt_git_dir[0]) {
7679                 string_ncopy(opt_git_dir, name, namelen);
7681         } else if (opt_is_inside_work_tree == -1) {
7682                 /* This can be 3 different values depending on the
7683                  * version of git being used. If git-rev-parse does not
7684                  * understand --is-inside-work-tree it will simply echo
7685                  * the option else either "true" or "false" is printed.
7686                  * Default to true for the unknown case. */
7687                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7689         } else if (*name == '.') {
7690                 string_ncopy(opt_cdup, name, namelen);
7692         } else {
7693                 string_ncopy(opt_prefix, name, namelen);
7694         }
7696         return OK;
7699 static int
7700 load_repo_info(void)
7702         const char *rev_parse_argv[] = {
7703                 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7704                         "--show-cdup", "--show-prefix", NULL
7705         };
7707         return io_run_load(rev_parse_argv, "=", read_repo_info);
7711 /*
7712  * Main
7713  */
7715 static const char usage[] =
7716 "tig " TIG_VERSION " (" __DATE__ ")\n"
7717 "\n"
7718 "Usage: tig        [options] [revs] [--] [paths]\n"
7719 "   or: tig show   [options] [revs] [--] [paths]\n"
7720 "   or: tig blame  [rev] path\n"
7721 "   or: tig status\n"
7722 "   or: tig <      [git command output]\n"
7723 "\n"
7724 "Options:\n"
7725 "  -v, --version   Show version and exit\n"
7726 "  -h, --help      Show help message and exit";
7728 static void __NORETURN
7729 quit(int sig)
7731         /* XXX: Restore tty modes and let the OS cleanup the rest! */
7732         if (cursed)
7733                 endwin();
7734         exit(0);
7737 static void __NORETURN
7738 die(const char *err, ...)
7740         va_list args;
7742         endwin();
7744         va_start(args, err);
7745         fputs("tig: ", stderr);
7746         vfprintf(stderr, err, args);
7747         fputs("\n", stderr);
7748         va_end(args);
7750         exit(1);
7753 static void
7754 warn(const char *msg, ...)
7756         va_list args;
7758         va_start(args, msg);
7759         fputs("tig warning: ", stderr);
7760         vfprintf(stderr, msg, args);
7761         fputs("\n", stderr);
7762         va_end(args);
7765 static const char ***filter_args;
7767 static int
7768 read_filter_args(char *name, size_t namelen, char *value, size_t valuelen)
7770         return argv_append(filter_args, name) ? OK : ERR;
7773 static void
7774 filter_rev_parse(const char ***args, const char *arg1, const char *arg2, const char *argv[])
7776         const char *rev_parse_argv[SIZEOF_ARG] = { "git", "rev-parse", arg1, arg2 };
7777         const char **all_argv = NULL;
7779         filter_args = args;
7780         if (!argv_append_array(&all_argv, rev_parse_argv) ||
7781             !argv_append_array(&all_argv, argv) ||
7782             !io_run_load(all_argv, "\n", read_filter_args) == ERR)
7783                 die("Failed to split arguments");
7784         argv_free(all_argv);
7785         free(all_argv);
7788 static void
7789 filter_options(const char *argv[])
7791         filter_rev_parse(&opt_file_args, "--no-revs", "--no-flags", argv);
7792         filter_rev_parse(&opt_diff_args, "--no-revs", "--flags", argv);
7793         filter_rev_parse(&opt_rev_args, "--symbolic", "--revs-only", argv);
7796 static enum request
7797 parse_options(int argc, const char *argv[])
7799         enum request request = REQ_VIEW_MAIN;
7800         const char *subcommand;
7801         bool seen_dashdash = FALSE;
7802         const char **filter_argv = NULL;
7803         int i;
7805         if (!isatty(STDIN_FILENO))
7806                 return REQ_VIEW_PAGER;
7808         if (argc <= 1)
7809                 return REQ_VIEW_MAIN;
7811         subcommand = argv[1];
7812         if (!strcmp(subcommand, "status")) {
7813                 if (argc > 2)
7814                         warn("ignoring arguments after `%s'", subcommand);
7815                 return REQ_VIEW_STATUS;
7817         } else if (!strcmp(subcommand, "blame")) {
7818                 if (argc <= 2 || argc > 4)
7819                         die("invalid number of options to blame\n\n%s", usage);
7821                 i = 2;
7822                 if (argc == 4) {
7823                         string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7824                         i++;
7825                 }
7827                 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7828                 return REQ_VIEW_BLAME;
7830         } else if (!strcmp(subcommand, "show")) {
7831                 request = REQ_VIEW_DIFF;
7833         } else {
7834                 subcommand = NULL;
7835         }
7837         for (i = 1 + !!subcommand; i < argc; i++) {
7838                 const char *opt = argv[i];
7840                 if (seen_dashdash) {
7841                         argv_append(&opt_file_args, opt);
7842                         continue;
7844                 } else if (!strcmp(opt, "--")) {
7845                         seen_dashdash = TRUE;
7846                         continue;
7848                 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7849                         printf("tig version %s\n", TIG_VERSION);
7850                         quit(0);
7852                 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7853                         printf("%s\n", usage);
7854                         quit(0);
7856                 } else if (!strcmp(opt, "--all")) {
7857                         argv_append(&opt_rev_args, opt);
7858                         continue;
7859                 }
7861                 if (!argv_append(&filter_argv, opt))
7862                         die("command too long");
7863         }
7865         if (filter_argv)
7866                 filter_options(filter_argv);
7868         return request;
7871 int
7872 main(int argc, const char *argv[])
7874         const char *codeset = "UTF-8";
7875         enum request request = parse_options(argc, argv);
7876         struct view *view;
7877         size_t i;
7879         signal(SIGINT, quit);
7880         signal(SIGPIPE, SIG_IGN);
7882         if (setlocale(LC_ALL, "")) {
7883                 codeset = nl_langinfo(CODESET);
7884         }
7886         if (load_repo_info() == ERR)
7887                 die("Failed to load repo info.");
7889         if (load_options() == ERR)
7890                 die("Failed to load user config.");
7892         if (load_git_config() == ERR)
7893                 die("Failed to load repo config.");
7895         /* Require a git repository unless when running in pager mode. */
7896         if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7897                 die("Not a git repository");
7899         if (*opt_encoding && strcmp(codeset, "UTF-8")) {
7900                 opt_iconv_in = iconv_open("UTF-8", opt_encoding);
7901                 if (opt_iconv_in == ICONV_NONE)
7902                         die("Failed to initialize character set conversion");
7903         }
7905         if (codeset && strcmp(codeset, "UTF-8")) {
7906                 opt_iconv_out = iconv_open(codeset, "UTF-8");
7907                 if (opt_iconv_out == ICONV_NONE)
7908                         die("Failed to initialize character set conversion");
7909         }
7911         if (load_refs() == ERR)
7912                 die("Failed to load refs.");
7914         foreach_view (view, i) {
7915                 if (getenv(view->cmd_env))
7916                         warn("Use of the %s environment variable is deprecated,"
7917                              " use options or TIG_DIFF_ARGS instead",
7918                              view->cmd_env);
7919                 if (!argv_from_env(view->ops->argv, view->cmd_env))
7920                         die("Too many arguments in the `%s` environment variable",
7921                             view->cmd_env);
7922         }
7924         init_display();
7926         while (view_driver(display[current_view], request)) {
7927                 int key = get_input(0);
7929                 view = display[current_view];
7930                 request = get_keybinding(view->keymap, key);
7932                 /* Some low-level request handling. This keeps access to
7933                  * status_win restricted. */
7934                 switch (request) {
7935                 case REQ_NONE:
7936                         report("Unknown key, press %s for help",
7937                                get_key(view->keymap, REQ_VIEW_HELP));
7938                         break;
7939                 case REQ_PROMPT:
7940                 {
7941                         char *cmd = read_prompt(":");
7943                         if (cmd && isdigit(*cmd)) {
7944                                 int lineno = view->lineno + 1;
7946                                 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7947                                         select_view_line(view, lineno - 1);
7948                                         report("");
7949                                 } else {
7950                                         report("Unable to parse '%s' as a line number", cmd);
7951                                 }
7953                         } else if (cmd) {
7954                                 struct view *next = VIEW(REQ_VIEW_PAGER);
7955                                 const char *argv[SIZEOF_ARG] = { "git" };
7956                                 int argc = 1;
7958                                 /* When running random commands, initially show the
7959                                  * command in the title. However, it maybe later be
7960                                  * overwritten if a commit line is selected. */
7961                                 string_ncopy(next->ref, cmd, strlen(cmd));
7963                                 if (!argv_from_string(argv, &argc, cmd)) {
7964                                         report("Too many arguments");
7965                                 } else if (!prepare_update(next, argv, NULL)) {
7966                                         report("Failed to format command");
7967                                 } else {
7968                                         open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7969                                 }
7970                         }
7972                         request = REQ_NONE;
7973                         break;
7974                 }
7975                 case REQ_SEARCH:
7976                 case REQ_SEARCH_BACK:
7977                 {
7978                         const char *prompt = request == REQ_SEARCH ? "/" : "?";
7979                         char *search = read_prompt(prompt);
7981                         if (search)
7982                                 string_ncopy(opt_search, search, strlen(search));
7983                         else if (*opt_search)
7984                                 request = request == REQ_SEARCH ?
7985                                         REQ_FIND_NEXT :
7986                                         REQ_FIND_PREV;
7987                         else
7988                                 request = REQ_NONE;
7989                         break;
7990                 }
7991                 default:
7992                         break;
7993                 }
7994         }
7996         quit(0);
7998         return 0;