Code

b6df54c82060163ce895102c69180a1294b48e78
[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);
2410         }
2411         if (trimmed && use_tilde) {
2412                 set_view_attr(view, LINE_DELIMITER);
2413                 waddch(view->win, '~');
2414                 col++;
2415         }
2417         return col;
2420 static int
2421 draw_space(struct view *view, enum line_type type, int max, int spaces)
2423         static char space[] = "                    ";
2424         int col = 0;
2426         spaces = MIN(max, spaces);
2428         while (spaces > 0) {
2429                 int len = MIN(spaces, sizeof(space) - 1);
2431                 col += draw_chars(view, type, space, len, FALSE);
2432                 spaces -= len;
2433         }
2435         return col;
2438 static bool
2439 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
2441         char text[SIZEOF_STR];
2443         do {
2444                 size_t pos = string_expand(text, sizeof(text), string, opt_tab_size);
2446                 view->col += draw_chars(view, type, text, view->width + view->yoffset - view->col, trim);
2447                 string += pos;
2448         } while (*string && view->width + view->yoffset > view->col);
2450         return view->width + view->yoffset <= view->col;
2453 static bool
2454 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
2456         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2457         int max = view->width + view->yoffset - view->col;
2458         int i;
2460         if (max < size)
2461                 size = max;
2463         set_view_attr(view, type);
2464         /* Using waddch() instead of waddnstr() ensures that
2465          * they'll be rendered correctly for the cursor line. */
2466         for (i = skip; i < size; i++)
2467                 waddch(view->win, graphic[i]);
2469         view->col += size;
2470         if (size < max && skip <= size)
2471                 waddch(view->win, ' ');
2472         view->col++;
2474         return view->width + view->yoffset <= view->col;
2477 static bool
2478 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
2480         int max = MIN(view->width + view->yoffset - view->col, len);
2481         int col;
2483         if (text)
2484                 col = draw_chars(view, type, text, max - 1, trim);
2485         else
2486                 col = draw_space(view, type, max - 1, max - 1);
2488         view->col += col;
2489         view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
2490         return view->width + view->yoffset <= view->col;
2493 static bool
2494 draw_date(struct view *view, struct time *time)
2496         const char *date = mkdate(time, opt_date);
2497         int cols = opt_date == DATE_SHORT ? DATE_SHORT_COLS : DATE_COLS;
2499         return draw_field(view, LINE_DATE, date, cols, FALSE);
2502 static bool
2503 draw_author(struct view *view, const char *author)
2505         bool trim = opt_author_cols == 0 || opt_author_cols > 5;
2506         bool abbreviate = opt_author == AUTHOR_ABBREVIATED || !trim;
2508         if (abbreviate && author)
2509                 author = get_author_initials(author);
2511         return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2514 static bool
2515 draw_mode(struct view *view, mode_t mode)
2517         const char *str;
2519         if (S_ISDIR(mode))
2520                 str = "drwxr-xr-x";
2521         else if (S_ISLNK(mode))
2522                 str = "lrwxrwxrwx";
2523         else if (S_ISGITLINK(mode))
2524                 str = "m---------";
2525         else if (S_ISREG(mode) && mode & S_IXUSR)
2526                 str = "-rwxr-xr-x";
2527         else if (S_ISREG(mode))
2528                 str = "-rw-r--r--";
2529         else
2530                 str = "----------";
2532         return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2535 static bool
2536 draw_lineno(struct view *view, unsigned int lineno)
2538         char number[10];
2539         int digits3 = view->digits < 3 ? 3 : view->digits;
2540         int max = MIN(view->width + view->yoffset - view->col, digits3);
2541         char *text = NULL;
2542         chtype separator = opt_line_graphics ? ACS_VLINE : '|';
2544         lineno += view->offset + 1;
2545         if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2546                 static char fmt[] = "%1ld";
2548                 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2549                 if (string_format(number, fmt, lineno))
2550                         text = number;
2551         }
2552         if (text)
2553                 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2554         else
2555                 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2556         return draw_graphic(view, LINE_DEFAULT, &separator, 1);
2559 static bool
2560 draw_view_line(struct view *view, unsigned int lineno)
2562         struct line *line;
2563         bool selected = (view->offset + lineno == view->lineno);
2565         assert(view_is_displayed(view));
2567         if (view->offset + lineno >= view->lines)
2568                 return FALSE;
2570         line = &view->line[view->offset + lineno];
2572         wmove(view->win, lineno, 0);
2573         if (line->cleareol)
2574                 wclrtoeol(view->win);
2575         view->col = 0;
2576         view->curline = line;
2577         view->curtype = LINE_NONE;
2578         line->selected = FALSE;
2579         line->dirty = line->cleareol = 0;
2581         if (selected) {
2582                 set_view_attr(view, LINE_CURSOR);
2583                 line->selected = TRUE;
2584                 view->ops->select(view, line);
2585         }
2587         return view->ops->draw(view, line, lineno);
2590 static void
2591 redraw_view_dirty(struct view *view)
2593         bool dirty = FALSE;
2594         int lineno;
2596         for (lineno = 0; lineno < view->height; lineno++) {
2597                 if (view->offset + lineno >= view->lines)
2598                         break;
2599                 if (!view->line[view->offset + lineno].dirty)
2600                         continue;
2601                 dirty = TRUE;
2602                 if (!draw_view_line(view, lineno))
2603                         break;
2604         }
2606         if (!dirty)
2607                 return;
2608         wnoutrefresh(view->win);
2611 static void
2612 redraw_view_from(struct view *view, int lineno)
2614         assert(0 <= lineno && lineno < view->height);
2616         for (; lineno < view->height; lineno++) {
2617                 if (!draw_view_line(view, lineno))
2618                         break;
2619         }
2621         wnoutrefresh(view->win);
2624 static void
2625 redraw_view(struct view *view)
2627         werase(view->win);
2628         redraw_view_from(view, 0);
2632 static void
2633 update_view_title(struct view *view)
2635         char buf[SIZEOF_STR];
2636         char state[SIZEOF_STR];
2637         size_t bufpos = 0, statelen = 0;
2639         assert(view_is_displayed(view));
2641         if (view->type != VIEW_STATUS && view->lines) {
2642                 unsigned int view_lines = view->offset + view->height;
2643                 unsigned int lines = view->lines
2644                                    ? MIN(view_lines, view->lines) * 100 / view->lines
2645                                    : 0;
2647                 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2648                                    view->ops->type,
2649                                    view->lineno + 1,
2650                                    view->lines,
2651                                    lines);
2653         }
2655         if (view->pipe) {
2656                 time_t secs = time(NULL) - view->start_time;
2658                 /* Three git seconds are a long time ... */
2659                 if (secs > 2)
2660                         string_format_from(state, &statelen, " loading %lds", secs);
2661         }
2663         string_format_from(buf, &bufpos, "[%s]", view->name);
2664         if (*view->ref && bufpos < view->width) {
2665                 size_t refsize = strlen(view->ref);
2666                 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2668                 if (minsize < view->width)
2669                         refsize = view->width - minsize + 7;
2670                 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2671         }
2673         if (statelen && bufpos < view->width) {
2674                 string_format_from(buf, &bufpos, "%s", state);
2675         }
2677         if (view == display[current_view])
2678                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2679         else
2680                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2682         mvwaddnstr(view->title, 0, 0, buf, bufpos);
2683         wclrtoeol(view->title);
2684         wnoutrefresh(view->title);
2687 static int
2688 apply_step(double step, int value)
2690         if (step >= 1)
2691                 return (int) step;
2692         value *= step + 0.01;
2693         return value ? value : 1;
2696 static void
2697 resize_display(void)
2699         int offset, i;
2700         struct view *base = display[0];
2701         struct view *view = display[1] ? display[1] : display[0];
2703         /* Setup window dimensions */
2705         getmaxyx(stdscr, base->height, base->width);
2707         /* Make room for the status window. */
2708         base->height -= 1;
2710         if (view != base) {
2711                 /* Horizontal split. */
2712                 view->width   = base->width;
2713                 view->height  = apply_step(opt_scale_split_view, base->height);
2714                 view->height  = MAX(view->height, MIN_VIEW_HEIGHT);
2715                 view->height  = MIN(view->height, base->height - MIN_VIEW_HEIGHT);
2716                 base->height -= view->height;
2718                 /* Make room for the title bar. */
2719                 view->height -= 1;
2720         }
2722         /* Make room for the title bar. */
2723         base->height -= 1;
2725         offset = 0;
2727         foreach_displayed_view (view, i) {
2728                 if (!view->win) {
2729                         view->win = newwin(view->height, 0, offset, 0);
2730                         if (!view->win)
2731                                 die("Failed to create %s view", view->name);
2733                         scrollok(view->win, FALSE);
2735                         view->title = newwin(1, 0, offset + view->height, 0);
2736                         if (!view->title)
2737                                 die("Failed to create title window");
2739                 } else {
2740                         wresize(view->win, view->height, view->width);
2741                         mvwin(view->win,   offset, 0);
2742                         mvwin(view->title, offset + view->height, 0);
2743                 }
2745                 offset += view->height + 1;
2746         }
2749 static void
2750 redraw_display(bool clear)
2752         struct view *view;
2753         int i;
2755         foreach_displayed_view (view, i) {
2756                 if (clear)
2757                         wclear(view->win);
2758                 redraw_view(view);
2759                 update_view_title(view);
2760         }
2764 /*
2765  * Option management
2766  */
2768 static void
2769 toggle_enum_option_do(unsigned int *opt, const char *help,
2770                       const struct enum_map *map, size_t size)
2772         *opt = (*opt + 1) % size;
2773         redraw_display(FALSE);
2774         report("Displaying %s %s", enum_name(map[*opt]), help);
2777 #define toggle_enum_option(opt, help, map) \
2778         toggle_enum_option_do(opt, help, map, ARRAY_SIZE(map))
2780 #define toggle_date() toggle_enum_option(&opt_date, "dates", date_map)
2781 #define toggle_author() toggle_enum_option(&opt_author, "author names", author_map)
2783 static void
2784 toggle_view_option(bool *option, const char *help)
2786         *option = !*option;
2787         redraw_display(FALSE);
2788         report("%sabling %s", *option ? "En" : "Dis", help);
2791 static void
2792 open_option_menu(void)
2794         const struct menu_item menu[] = {
2795                 { '.', "line numbers", &opt_line_number },
2796                 { 'D', "date display", &opt_date },
2797                 { 'A', "author display", &opt_author },
2798                 { 'g', "revision graph display", &opt_rev_graph },
2799                 { 'F', "reference display", &opt_show_refs },
2800                 { 0 }
2801         };
2802         int selected = 0;
2804         if (prompt_menu("Toggle option", menu, &selected)) {
2805                 if (menu[selected].data == &opt_date)
2806                         toggle_date();
2807                 else if (menu[selected].data == &opt_author)
2808                         toggle_author();
2809                 else
2810                         toggle_view_option(menu[selected].data, menu[selected].text);
2811         }
2814 static void
2815 maximize_view(struct view *view)
2817         memset(display, 0, sizeof(display));
2818         current_view = 0;
2819         display[current_view] = view;
2820         resize_display();
2821         redraw_display(FALSE);
2822         report("");
2826 /*
2827  * Navigation
2828  */
2830 static bool
2831 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2833         if (lineno >= view->lines)
2834                 lineno = view->lines > 0 ? view->lines - 1 : 0;
2836         if (offset > lineno || offset + view->height <= lineno) {
2837                 unsigned long half = view->height / 2;
2839                 if (lineno > half)
2840                         offset = lineno - half;
2841                 else
2842                         offset = 0;
2843         }
2845         if (offset != view->offset || lineno != view->lineno) {
2846                 view->offset = offset;
2847                 view->lineno = lineno;
2848                 return TRUE;
2849         }
2851         return FALSE;
2854 /* Scrolling backend */
2855 static void
2856 do_scroll_view(struct view *view, int lines)
2858         bool redraw_current_line = FALSE;
2860         /* The rendering expects the new offset. */
2861         view->offset += lines;
2863         assert(0 <= view->offset && view->offset < view->lines);
2864         assert(lines);
2866         /* Move current line into the view. */
2867         if (view->lineno < view->offset) {
2868                 view->lineno = view->offset;
2869                 redraw_current_line = TRUE;
2870         } else if (view->lineno >= view->offset + view->height) {
2871                 view->lineno = view->offset + view->height - 1;
2872                 redraw_current_line = TRUE;
2873         }
2875         assert(view->offset <= view->lineno && view->lineno < view->lines);
2877         /* Redraw the whole screen if scrolling is pointless. */
2878         if (view->height < ABS(lines)) {
2879                 redraw_view(view);
2881         } else {
2882                 int line = lines > 0 ? view->height - lines : 0;
2883                 int end = line + ABS(lines);
2885                 scrollok(view->win, TRUE);
2886                 wscrl(view->win, lines);
2887                 scrollok(view->win, FALSE);
2889                 while (line < end && draw_view_line(view, line))
2890                         line++;
2892                 if (redraw_current_line)
2893                         draw_view_line(view, view->lineno - view->offset);
2894                 wnoutrefresh(view->win);
2895         }
2897         view->has_scrolled = TRUE;
2898         report("");
2901 /* Scroll frontend */
2902 static void
2903 scroll_view(struct view *view, enum request request)
2905         int lines = 1;
2907         assert(view_is_displayed(view));
2909         switch (request) {
2910         case REQ_SCROLL_FIRST_COL:
2911                 view->yoffset = 0;
2912                 redraw_view_from(view, 0);
2913                 report("");
2914                 return;
2915         case REQ_SCROLL_LEFT:
2916                 if (view->yoffset == 0) {
2917                         report("Cannot scroll beyond the first column");
2918                         return;
2919                 }
2920                 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2921                         view->yoffset = 0;
2922                 else
2923                         view->yoffset -= apply_step(opt_hscroll, view->width);
2924                 redraw_view_from(view, 0);
2925                 report("");
2926                 return;
2927         case REQ_SCROLL_RIGHT:
2928                 view->yoffset += apply_step(opt_hscroll, view->width);
2929                 redraw_view(view);
2930                 report("");
2931                 return;
2932         case REQ_SCROLL_PAGE_DOWN:
2933                 lines = view->height;
2934         case REQ_SCROLL_LINE_DOWN:
2935                 if (view->offset + lines > view->lines)
2936                         lines = view->lines - view->offset;
2938                 if (lines == 0 || view->offset + view->height >= view->lines) {
2939                         report("Cannot scroll beyond the last line");
2940                         return;
2941                 }
2942                 break;
2944         case REQ_SCROLL_PAGE_UP:
2945                 lines = view->height;
2946         case REQ_SCROLL_LINE_UP:
2947                 if (lines > view->offset)
2948                         lines = view->offset;
2950                 if (lines == 0) {
2951                         report("Cannot scroll beyond the first line");
2952                         return;
2953                 }
2955                 lines = -lines;
2956                 break;
2958         default:
2959                 die("request %d not handled in switch", request);
2960         }
2962         do_scroll_view(view, lines);
2965 /* Cursor moving */
2966 static void
2967 move_view(struct view *view, enum request request)
2969         int scroll_steps = 0;
2970         int steps;
2972         switch (request) {
2973         case REQ_MOVE_FIRST_LINE:
2974                 steps = -view->lineno;
2975                 break;
2977         case REQ_MOVE_LAST_LINE:
2978                 steps = view->lines - view->lineno - 1;
2979                 break;
2981         case REQ_MOVE_PAGE_UP:
2982                 steps = view->height > view->lineno
2983                       ? -view->lineno : -view->height;
2984                 break;
2986         case REQ_MOVE_PAGE_DOWN:
2987                 steps = view->lineno + view->height >= view->lines
2988                       ? view->lines - view->lineno - 1 : view->height;
2989                 break;
2991         case REQ_MOVE_UP:
2992                 steps = -1;
2993                 break;
2995         case REQ_MOVE_DOWN:
2996                 steps = 1;
2997                 break;
2999         default:
3000                 die("request %d not handled in switch", request);
3001         }
3003         if (steps <= 0 && view->lineno == 0) {
3004                 report("Cannot move beyond the first line");
3005                 return;
3007         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
3008                 report("Cannot move beyond the last line");
3009                 return;
3010         }
3012         /* Move the current line */
3013         view->lineno += steps;
3014         assert(0 <= view->lineno && view->lineno < view->lines);
3016         /* Check whether the view needs to be scrolled */
3017         if (view->lineno < view->offset ||
3018             view->lineno >= view->offset + view->height) {
3019                 scroll_steps = steps;
3020                 if (steps < 0 && -steps > view->offset) {
3021                         scroll_steps = -view->offset;
3023                 } else if (steps > 0) {
3024                         if (view->lineno == view->lines - 1 &&
3025                             view->lines > view->height) {
3026                                 scroll_steps = view->lines - view->offset - 1;
3027                                 if (scroll_steps >= view->height)
3028                                         scroll_steps -= view->height - 1;
3029                         }
3030                 }
3031         }
3033         if (!view_is_displayed(view)) {
3034                 view->offset += scroll_steps;
3035                 assert(0 <= view->offset && view->offset < view->lines);
3036                 view->ops->select(view, &view->line[view->lineno]);
3037                 return;
3038         }
3040         /* Repaint the old "current" line if we be scrolling */
3041         if (ABS(steps) < view->height)
3042                 draw_view_line(view, view->lineno - steps - view->offset);
3044         if (scroll_steps) {
3045                 do_scroll_view(view, scroll_steps);
3046                 return;
3047         }
3049         /* Draw the current line */
3050         draw_view_line(view, view->lineno - view->offset);
3052         wnoutrefresh(view->win);
3053         report("");
3057 /*
3058  * Searching
3059  */
3061 static void search_view(struct view *view, enum request request);
3063 static bool
3064 grep_text(struct view *view, const char *text[])
3066         regmatch_t pmatch;
3067         size_t i;
3069         for (i = 0; text[i]; i++)
3070                 if (*text[i] &&
3071                     regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
3072                         return TRUE;
3073         return FALSE;
3076 static void
3077 select_view_line(struct view *view, unsigned long lineno)
3079         unsigned long old_lineno = view->lineno;
3080         unsigned long old_offset = view->offset;
3082         if (goto_view_line(view, view->offset, lineno)) {
3083                 if (view_is_displayed(view)) {
3084                         if (old_offset != view->offset) {
3085                                 redraw_view(view);
3086                         } else {
3087                                 draw_view_line(view, old_lineno - view->offset);
3088                                 draw_view_line(view, view->lineno - view->offset);
3089                                 wnoutrefresh(view->win);
3090                         }
3091                 } else {
3092                         view->ops->select(view, &view->line[view->lineno]);
3093                 }
3094         }
3097 static void
3098 find_next(struct view *view, enum request request)
3100         unsigned long lineno = view->lineno;
3101         int direction;
3103         if (!*view->grep) {
3104                 if (!*opt_search)
3105                         report("No previous search");
3106                 else
3107                         search_view(view, request);
3108                 return;
3109         }
3111         switch (request) {
3112         case REQ_SEARCH:
3113         case REQ_FIND_NEXT:
3114                 direction = 1;
3115                 break;
3117         case REQ_SEARCH_BACK:
3118         case REQ_FIND_PREV:
3119                 direction = -1;
3120                 break;
3122         default:
3123                 return;
3124         }
3126         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
3127                 lineno += direction;
3129         /* Note, lineno is unsigned long so will wrap around in which case it
3130          * will become bigger than view->lines. */
3131         for (; lineno < view->lines; lineno += direction) {
3132                 if (view->ops->grep(view, &view->line[lineno])) {
3133                         select_view_line(view, lineno);
3134                         report("Line %ld matches '%s'", lineno + 1, view->grep);
3135                         return;
3136                 }
3137         }
3139         report("No match found for '%s'", view->grep);
3142 static void
3143 search_view(struct view *view, enum request request)
3145         int regex_err;
3147         if (view->regex) {
3148                 regfree(view->regex);
3149                 *view->grep = 0;
3150         } else {
3151                 view->regex = calloc(1, sizeof(*view->regex));
3152                 if (!view->regex)
3153                         return;
3154         }
3156         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
3157         if (regex_err != 0) {
3158                 char buf[SIZEOF_STR] = "unknown error";
3160                 regerror(regex_err, view->regex, buf, sizeof(buf));
3161                 report("Search failed: %s", buf);
3162                 return;
3163         }
3165         string_copy(view->grep, opt_search);
3167         find_next(view, request);
3170 /*
3171  * Incremental updating
3172  */
3174 static void
3175 reset_view(struct view *view)
3177         int i;
3179         for (i = 0; i < view->lines; i++)
3180                 free(view->line[i].data);
3181         free(view->line);
3183         view->p_offset = view->offset;
3184         view->p_yoffset = view->yoffset;
3185         view->p_lineno = view->lineno;
3187         view->line = NULL;
3188         view->offset = 0;
3189         view->yoffset = 0;
3190         view->lines  = 0;
3191         view->lineno = 0;
3192         view->vid[0] = 0;
3193         view->update_secs = 0;
3196 static const char *
3197 format_arg(const char *name)
3199         static struct {
3200                 const char *name;
3201                 size_t namelen;
3202                 const char *value;
3203                 const char *value_if_empty;
3204         } vars[] = {
3205 #define FORMAT_VAR(name, value, value_if_empty) \
3206         { name, STRING_SIZE(name), value, value_if_empty }
3207                 FORMAT_VAR("%(directory)",      opt_path,       ""),
3208                 FORMAT_VAR("%(file)",           opt_file,       ""),
3209                 FORMAT_VAR("%(ref)",            opt_ref,        "HEAD"),
3210                 FORMAT_VAR("%(head)",           ref_head,       ""),
3211                 FORMAT_VAR("%(commit)",         ref_commit,     ""),
3212                 FORMAT_VAR("%(blob)",           ref_blob,       ""),
3213                 FORMAT_VAR("%(branch)",         ref_branch,     ""),
3214         };
3215         int i;
3217         for (i = 0; i < ARRAY_SIZE(vars); i++)
3218                 if (!strncmp(name, vars[i].name, vars[i].namelen))
3219                         return *vars[i].value ? vars[i].value : vars[i].value_if_empty;
3221         report("Unknown replacement: `%s`", name);
3222         return NULL;
3225 static bool
3226 format_argv(const char ***dst_argv, const char *src_argv[], bool replace)
3228         char buf[SIZEOF_STR];
3229         int argc;
3231         argv_free(*dst_argv);
3233         for (argc = 0; src_argv[argc]; argc++) {
3234                 const char *arg = src_argv[argc];
3235                 size_t bufpos = 0;
3237                 if (!strcmp(arg, "%(fileargs)")) {
3238                         if (!argv_append_array(dst_argv, opt_file_args))
3239                                 break;
3240                         continue;
3242                 } else if (!strcmp(arg, "%(diffargs)")) {
3243                         if (!argv_append_array(dst_argv, opt_diff_args))
3244                                 break;
3245                         continue;
3247                 } else if (!strcmp(arg, "%(revargs)")) {
3248                         if (!argv_append_array(dst_argv, opt_rev_args))
3249                                 break;
3250                         continue;
3251                 }
3253                 while (arg) {
3254                         char *next = strstr(arg, "%(");
3255                         int len = next - arg;
3256                         const char *value;
3258                         if (!next || !replace) {
3259                                 len = strlen(arg);
3260                                 value = "";
3262                         } else {
3263                                 value = format_arg(next);
3265                                 if (!value) {
3266                                         return FALSE;
3267                                 }
3268                         }
3270                         if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
3271                                 return FALSE;
3273                         arg = next && replace ? strchr(next, ')') + 1 : NULL;
3274                 }
3276                 if (!argv_append(dst_argv, buf))
3277                         break;
3278         }
3280         return src_argv[argc] == NULL;
3283 static bool
3284 restore_view_position(struct view *view)
3286         if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
3287                 return FALSE;
3289         /* Changing the view position cancels the restoring. */
3290         /* FIXME: Changing back to the first line is not detected. */
3291         if (view->offset != 0 || view->lineno != 0) {
3292                 view->p_restore = FALSE;
3293                 return FALSE;
3294         }
3296         if (goto_view_line(view, view->p_offset, view->p_lineno) &&
3297             view_is_displayed(view))
3298                 werase(view->win);
3300         view->yoffset = view->p_yoffset;
3301         view->p_restore = FALSE;
3303         return TRUE;
3306 static void
3307 end_update(struct view *view, bool force)
3309         if (!view->pipe)
3310                 return;
3311         while (!view->ops->read(view, NULL))
3312                 if (!force)
3313                         return;
3314         if (force)
3315                 io_kill(view->pipe);
3316         io_done(view->pipe);
3317         view->pipe = NULL;
3320 static void
3321 setup_update(struct view *view, const char *vid)
3323         reset_view(view);
3324         string_copy_rev(view->vid, vid);
3325         view->pipe = &view->io;
3326         view->start_time = time(NULL);
3329 static bool
3330 prepare_io(struct view *view, const char *dir, const char *argv[], bool replace)
3332         view->dir = dir;
3333         return format_argv(&view->argv, argv, replace);
3336 static bool
3337 prepare_update(struct view *view, const char *argv[], const char *dir)
3339         if (view->pipe)
3340                 end_update(view, TRUE);
3341         return prepare_io(view, dir, argv, FALSE);
3344 static bool
3345 start_update(struct view *view, const char **argv, const char *dir)
3347         if (view->pipe)
3348                 io_done(view->pipe);
3349         return prepare_io(view, dir, argv, FALSE) &&
3350                io_run(&view->io, IO_RD, dir, view->argv);
3353 static bool
3354 prepare_update_file(struct view *view, const char *name)
3356         if (view->pipe)
3357                 end_update(view, TRUE);
3358         argv_free(view->argv);
3359         return io_open(&view->io, "%s/%s", opt_cdup[0] ? opt_cdup : ".", name);
3362 static bool
3363 begin_update(struct view *view, bool refresh)
3365         if (view->pipe)
3366                 end_update(view, TRUE);
3368         if (!refresh) {
3369                 if (view->ops->prepare) {
3370                         if (!view->ops->prepare(view))
3371                                 return FALSE;
3372                 } else if (!prepare_io(view, NULL, view->ops->argv, TRUE)) {
3373                         return FALSE;
3374                 }
3376                 /* Put the current ref_* value to the view title ref
3377                  * member. This is needed by the blob view. Most other
3378                  * views sets it automatically after loading because the
3379                  * first line is a commit line. */
3380                 string_copy_rev(view->ref, view->id);
3381         }
3383         if (view->argv && view->argv[0] &&
3384             !io_run(&view->io, IO_RD, view->dir, view->argv))
3385                 return FALSE;
3387         setup_update(view, view->id);
3389         return TRUE;
3392 static bool
3393 update_view(struct view *view)
3395         char out_buffer[BUFSIZ * 2];
3396         char *line;
3397         /* Clear the view and redraw everything since the tree sorting
3398          * might have rearranged things. */
3399         bool redraw = view->lines == 0;
3400         bool can_read = TRUE;
3402         if (!view->pipe)
3403                 return TRUE;
3405         if (!io_can_read(view->pipe)) {
3406                 if (view->lines == 0 && view_is_displayed(view)) {
3407                         time_t secs = time(NULL) - view->start_time;
3409                         if (secs > 1 && secs > view->update_secs) {
3410                                 if (view->update_secs == 0)
3411                                         redraw_view(view);
3412                                 update_view_title(view);
3413                                 view->update_secs = secs;
3414                         }
3415                 }
3416                 return TRUE;
3417         }
3419         for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
3420                 if (opt_iconv_in != ICONV_NONE) {
3421                         ICONV_CONST char *inbuf = line;
3422                         size_t inlen = strlen(line) + 1;
3424                         char *outbuf = out_buffer;
3425                         size_t outlen = sizeof(out_buffer);
3427                         size_t ret;
3429                         ret = iconv(opt_iconv_in, &inbuf, &inlen, &outbuf, &outlen);
3430                         if (ret != (size_t) -1)
3431                                 line = out_buffer;
3432                 }
3434                 if (!view->ops->read(view, line)) {
3435                         report("Allocation failure");
3436                         end_update(view, TRUE);
3437                         return FALSE;
3438                 }
3439         }
3441         {
3442                 unsigned long lines = view->lines;
3443                 int digits;
3445                 for (digits = 0; lines; digits++)
3446                         lines /= 10;
3448                 /* Keep the displayed view in sync with line number scaling. */
3449                 if (digits != view->digits) {
3450                         view->digits = digits;
3451                         if (opt_line_number || view->type == VIEW_BLAME)
3452                                 redraw = TRUE;
3453                 }
3454         }
3456         if (io_error(view->pipe)) {
3457                 report("Failed to read: %s", io_strerror(view->pipe));
3458                 end_update(view, TRUE);
3460         } else if (io_eof(view->pipe)) {
3461                 if (view_is_displayed(view))
3462                         report("");
3463                 end_update(view, FALSE);
3464         }
3466         if (restore_view_position(view))
3467                 redraw = TRUE;
3469         if (!view_is_displayed(view))
3470                 return TRUE;
3472         if (redraw)
3473                 redraw_view_from(view, 0);
3474         else
3475                 redraw_view_dirty(view);
3477         /* Update the title _after_ the redraw so that if the redraw picks up a
3478          * commit reference in view->ref it'll be available here. */
3479         update_view_title(view);
3480         return TRUE;
3483 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
3485 static struct line *
3486 add_line_data(struct view *view, void *data, enum line_type type)
3488         struct line *line;
3490         if (!realloc_lines(&view->line, view->lines, 1))
3491                 return NULL;
3493         line = &view->line[view->lines++];
3494         memset(line, 0, sizeof(*line));
3495         line->type = type;
3496         line->data = data;
3497         line->dirty = 1;
3499         return line;
3502 static struct line *
3503 add_line_text(struct view *view, const char *text, enum line_type type)
3505         char *data = text ? strdup(text) : NULL;
3507         return data ? add_line_data(view, data, type) : NULL;
3510 static struct line *
3511 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
3513         char buf[SIZEOF_STR];
3514         va_list args;
3516         va_start(args, fmt);
3517         if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
3518                 buf[0] = 0;
3519         va_end(args);
3521         return buf[0] ? add_line_text(view, buf, type) : NULL;
3524 /*
3525  * View opening
3526  */
3528 enum open_flags {
3529         OPEN_DEFAULT = 0,       /* Use default view switching. */
3530         OPEN_SPLIT = 1,         /* Split current view. */
3531         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
3532         OPEN_REFRESH = 16,      /* Refresh view using previous command. */
3533         OPEN_PREPARED = 32,     /* Open already prepared command. */
3534 };
3536 static void
3537 open_view(struct view *prev, enum request request, enum open_flags flags)
3539         bool split = !!(flags & OPEN_SPLIT);
3540         bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
3541         bool nomaximize = !!(flags & OPEN_REFRESH);
3542         struct view *view = VIEW(request);
3543         int nviews = displayed_views();
3544         struct view *base_view = display[0];
3546         if (view == prev && nviews == 1 && !reload) {
3547                 report("Already in %s view", view->name);
3548                 return;
3549         }
3551         if (view->git_dir && !opt_git_dir[0]) {
3552                 report("The %s view is disabled in pager view", view->name);
3553                 return;
3554         }
3556         if (split) {
3557                 display[1] = view;
3558                 current_view = 1;
3559                 view->parent = prev;
3560         } else if (!nomaximize) {
3561                 /* Maximize the current view. */
3562                 memset(display, 0, sizeof(display));
3563                 current_view = 0;
3564                 display[current_view] = view;
3565         }
3567         /* No prev signals that this is the first loaded view. */
3568         if (prev && view != prev) {
3569                 view->prev = prev;
3570         }
3572         /* Resize the view when switching between split- and full-screen,
3573          * or when switching between two different full-screen views. */
3574         if (nviews != displayed_views() ||
3575             (nviews == 1 && base_view != display[0]))
3576                 resize_display();
3578         if (view->ops->open) {
3579                 if (view->pipe)
3580                         end_update(view, TRUE);
3581                 if (!view->ops->open(view)) {
3582                         report("Failed to load %s view", view->name);
3583                         return;
3584                 }
3585                 restore_view_position(view);
3587         } else if ((reload || strcmp(view->vid, view->id)) &&
3588                    !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3589                 report("Failed to load %s view", view->name);
3590                 return;
3591         }
3593         if (split && prev->lineno - prev->offset >= prev->height) {
3594                 /* Take the title line into account. */
3595                 int lines = prev->lineno - prev->offset - prev->height + 1;
3597                 /* Scroll the view that was split if the current line is
3598                  * outside the new limited view. */
3599                 do_scroll_view(prev, lines);
3600         }
3602         if (prev && view != prev && split && view_is_displayed(prev)) {
3603                 /* "Blur" the previous view. */
3604                 update_view_title(prev);
3605         }
3607         if (view->pipe && view->lines == 0) {
3608                 /* Clear the old view and let the incremental updating refill
3609                  * the screen. */
3610                 werase(view->win);
3611                 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3612                 report("");
3613         } else if (view_is_displayed(view)) {
3614                 redraw_view(view);
3615                 report("");
3616         }
3619 static void
3620 open_external_viewer(const char *argv[], const char *dir)
3622         def_prog_mode();           /* save current tty modes */
3623         endwin();                  /* restore original tty modes */
3624         io_run_fg(argv, dir);
3625         fprintf(stderr, "Press Enter to continue");
3626         getc(opt_tty);
3627         reset_prog_mode();
3628         redraw_display(TRUE);
3631 static void
3632 open_mergetool(const char *file)
3634         const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3636         open_external_viewer(mergetool_argv, opt_cdup);
3639 static void
3640 open_editor(const char *file)
3642         const char *editor_argv[] = { "vi", file, NULL };
3643         const char *editor;
3645         editor = getenv("GIT_EDITOR");
3646         if (!editor && *opt_editor)
3647                 editor = opt_editor;
3648         if (!editor)
3649                 editor = getenv("VISUAL");
3650         if (!editor)
3651                 editor = getenv("EDITOR");
3652         if (!editor)
3653                 editor = "vi";
3655         editor_argv[0] = editor;
3656         open_external_viewer(editor_argv, opt_cdup);
3659 static void
3660 open_run_request(enum request request)
3662         struct run_request *req = get_run_request(request);
3663         const char **argv = NULL;
3665         if (!req) {
3666                 report("Unknown run request");
3667                 return;
3668         }
3670         if (format_argv(&argv, req->argv, TRUE))
3671                 open_external_viewer(argv, NULL);
3672         if (argv)
3673                 argv_free(argv);
3674         free(argv);
3677 /*
3678  * User request switch noodle
3679  */
3681 static int
3682 view_driver(struct view *view, enum request request)
3684         int i;
3686         if (request == REQ_NONE)
3687                 return TRUE;
3689         if (request > REQ_NONE) {
3690                 open_run_request(request);
3691                 view_request(view, REQ_REFRESH);
3692                 return TRUE;
3693         }
3695         request = view_request(view, request);
3696         if (request == REQ_NONE)
3697                 return TRUE;
3699         switch (request) {
3700         case REQ_MOVE_UP:
3701         case REQ_MOVE_DOWN:
3702         case REQ_MOVE_PAGE_UP:
3703         case REQ_MOVE_PAGE_DOWN:
3704         case REQ_MOVE_FIRST_LINE:
3705         case REQ_MOVE_LAST_LINE:
3706                 move_view(view, request);
3707                 break;
3709         case REQ_SCROLL_FIRST_COL:
3710         case REQ_SCROLL_LEFT:
3711         case REQ_SCROLL_RIGHT:
3712         case REQ_SCROLL_LINE_DOWN:
3713         case REQ_SCROLL_LINE_UP:
3714         case REQ_SCROLL_PAGE_DOWN:
3715         case REQ_SCROLL_PAGE_UP:
3716                 scroll_view(view, request);
3717                 break;
3719         case REQ_VIEW_BLAME:
3720                 if (!opt_file[0]) {
3721                         report("No file chosen, press %s to open tree view",
3722                                get_key(view->keymap, REQ_VIEW_TREE));
3723                         break;
3724                 }
3725                 open_view(view, request, OPEN_DEFAULT);
3726                 break;
3728         case REQ_VIEW_BLOB:
3729                 if (!ref_blob[0]) {
3730                         report("No file chosen, press %s to open tree view",
3731                                get_key(view->keymap, REQ_VIEW_TREE));
3732                         break;
3733                 }
3734                 open_view(view, request, OPEN_DEFAULT);
3735                 break;
3737         case REQ_VIEW_PAGER:
3738                 if (view == NULL) {
3739                         if (!io_open(&VIEW(REQ_VIEW_PAGER)->io, ""))
3740                                 die("Failed to open stdin");
3741                         open_view(view, request, OPEN_PREPARED);
3742                         break;
3743                 }
3745                 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3746                         report("No pager content, press %s to run command from prompt",
3747                                get_key(view->keymap, REQ_PROMPT));
3748                         break;
3749                 }
3750                 open_view(view, request, OPEN_DEFAULT);
3751                 break;
3753         case REQ_VIEW_STAGE:
3754                 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3755                         report("No stage content, press %s to open the status view and choose file",
3756                                get_key(view->keymap, REQ_VIEW_STATUS));
3757                         break;
3758                 }
3759                 open_view(view, request, OPEN_DEFAULT);
3760                 break;
3762         case REQ_VIEW_STATUS:
3763                 if (opt_is_inside_work_tree == FALSE) {
3764                         report("The status view requires a working tree");
3765                         break;
3766                 }
3767                 open_view(view, request, OPEN_DEFAULT);
3768                 break;
3770         case REQ_VIEW_MAIN:
3771         case REQ_VIEW_DIFF:
3772         case REQ_VIEW_LOG:
3773         case REQ_VIEW_TREE:
3774         case REQ_VIEW_HELP:
3775         case REQ_VIEW_BRANCH:
3776                 open_view(view, request, OPEN_DEFAULT);
3777                 break;
3779         case REQ_NEXT:
3780         case REQ_PREVIOUS:
3781                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3783                 if (view->parent) {
3784                         int line;
3786                         view = view->parent;
3787                         line = view->lineno;
3788                         move_view(view, request);
3789                         if (view_is_displayed(view))
3790                                 update_view_title(view);
3791                         if (line != view->lineno)
3792                                 view_request(view, REQ_ENTER);
3793                 } else {
3794                         move_view(view, request);
3795                 }
3796                 break;
3798         case REQ_VIEW_NEXT:
3799         {
3800                 int nviews = displayed_views();
3801                 int next_view = (current_view + 1) % nviews;
3803                 if (next_view == current_view) {
3804                         report("Only one view is displayed");
3805                         break;
3806                 }
3808                 current_view = next_view;
3809                 /* Blur out the title of the previous view. */
3810                 update_view_title(view);
3811                 report("");
3812                 break;
3813         }
3814         case REQ_REFRESH:
3815                 report("Refreshing is not yet supported for the %s view", view->name);
3816                 break;
3818         case REQ_MAXIMIZE:
3819                 if (displayed_views() == 2)
3820                         maximize_view(view);
3821                 break;
3823         case REQ_OPTIONS:
3824                 open_option_menu();
3825                 break;
3827         case REQ_TOGGLE_LINENO:
3828                 toggle_view_option(&opt_line_number, "line numbers");
3829                 break;
3831         case REQ_TOGGLE_DATE:
3832                 toggle_date();
3833                 break;
3835         case REQ_TOGGLE_AUTHOR:
3836                 toggle_author();
3837                 break;
3839         case REQ_TOGGLE_REV_GRAPH:
3840                 toggle_view_option(&opt_rev_graph, "revision graph display");
3841                 break;
3843         case REQ_TOGGLE_REFS:
3844                 toggle_view_option(&opt_show_refs, "reference display");
3845                 break;
3847         case REQ_TOGGLE_SORT_FIELD:
3848         case REQ_TOGGLE_SORT_ORDER:
3849                 report("Sorting is not yet supported for the %s view", view->name);
3850                 break;
3852         case REQ_SEARCH:
3853         case REQ_SEARCH_BACK:
3854                 search_view(view, request);
3855                 break;
3857         case REQ_FIND_NEXT:
3858         case REQ_FIND_PREV:
3859                 find_next(view, request);
3860                 break;
3862         case REQ_STOP_LOADING:
3863                 foreach_view(view, i) {
3864                         if (view->pipe)
3865                                 report("Stopped loading the %s view", view->name),
3866                         end_update(view, TRUE);
3867                 }
3868                 break;
3870         case REQ_SHOW_VERSION:
3871                 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3872                 return TRUE;
3874         case REQ_SCREEN_REDRAW:
3875                 redraw_display(TRUE);
3876                 break;
3878         case REQ_EDIT:
3879                 report("Nothing to edit");
3880                 break;
3882         case REQ_ENTER:
3883                 report("Nothing to enter");
3884                 break;
3886         case REQ_VIEW_CLOSE:
3887                 /* XXX: Mark closed views by letting view->prev point to the
3888                  * view itself. Parents to closed view should never be
3889                  * followed. */
3890                 if (view->prev && view->prev != view) {
3891                         maximize_view(view->prev);
3892                         view->prev = view;
3893                         break;
3894                 }
3895                 /* Fall-through */
3896         case REQ_QUIT:
3897                 return FALSE;
3899         default:
3900                 report("Unknown key, press %s for help",
3901                        get_key(view->keymap, REQ_VIEW_HELP));
3902                 return TRUE;
3903         }
3905         return TRUE;
3909 /*
3910  * View backend utilities
3911  */
3913 enum sort_field {
3914         ORDERBY_NAME,
3915         ORDERBY_DATE,
3916         ORDERBY_AUTHOR,
3917 };
3919 struct sort_state {
3920         const enum sort_field *fields;
3921         size_t size, current;
3922         bool reverse;
3923 };
3925 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3926 #define get_sort_field(state) ((state).fields[(state).current])
3927 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3929 static void
3930 sort_view(struct view *view, enum request request, struct sort_state *state,
3931           int (*compare)(const void *, const void *))
3933         switch (request) {
3934         case REQ_TOGGLE_SORT_FIELD:
3935                 state->current = (state->current + 1) % state->size;
3936                 break;
3938         case REQ_TOGGLE_SORT_ORDER:
3939                 state->reverse = !state->reverse;
3940                 break;
3941         default:
3942                 die("Not a sort request");
3943         }
3945         qsort(view->line, view->lines, sizeof(*view->line), compare);
3946         redraw_view(view);
3949 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3951 /* Small author cache to reduce memory consumption. It uses binary
3952  * search to lookup or find place to position new entries. No entries
3953  * are ever freed. */
3954 static const char *
3955 get_author(const char *name)
3957         static const char **authors;
3958         static size_t authors_size;
3959         int from = 0, to = authors_size - 1;
3961         while (from <= to) {
3962                 size_t pos = (to + from) / 2;
3963                 int cmp = strcmp(name, authors[pos]);
3965                 if (!cmp)
3966                         return authors[pos];
3968                 if (cmp < 0)
3969                         to = pos - 1;
3970                 else
3971                         from = pos + 1;
3972         }
3974         if (!realloc_authors(&authors, authors_size, 1))
3975                 return NULL;
3976         name = strdup(name);
3977         if (!name)
3978                 return NULL;
3980         memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3981         authors[from] = name;
3982         authors_size++;
3984         return name;
3987 static void
3988 parse_timesec(struct time *time, const char *sec)
3990         time->sec = (time_t) atol(sec);
3993 static void
3994 parse_timezone(struct time *time, const char *zone)
3996         long tz;
3998         tz  = ('0' - zone[1]) * 60 * 60 * 10;
3999         tz += ('0' - zone[2]) * 60 * 60;
4000         tz += ('0' - zone[3]) * 60 * 10;
4001         tz += ('0' - zone[4]) * 60;
4003         if (zone[0] == '-')
4004                 tz = -tz;
4006         time->tz = tz;
4007         time->sec -= tz;
4010 /* Parse author lines where the name may be empty:
4011  *      author  <email@address.tld> 1138474660 +0100
4012  */
4013 static void
4014 parse_author_line(char *ident, const char **author, struct time *time)
4016         char *nameend = strchr(ident, '<');
4017         char *emailend = strchr(ident, '>');
4019         if (nameend && emailend)
4020                 *nameend = *emailend = 0;
4021         ident = chomp_string(ident);
4022         if (!*ident) {
4023                 if (nameend)
4024                         ident = chomp_string(nameend + 1);
4025                 if (!*ident)
4026                         ident = "Unknown";
4027         }
4029         *author = get_author(ident);
4031         /* Parse epoch and timezone */
4032         if (emailend && emailend[1] == ' ') {
4033                 char *secs = emailend + 2;
4034                 char *zone = strchr(secs, ' ');
4036                 parse_timesec(time, secs);
4038                 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
4039                         parse_timezone(time, zone + 1);
4040         }
4043 /*
4044  * Pager backend
4045  */
4047 static bool
4048 pager_draw(struct view *view, struct line *line, unsigned int lineno)
4050         if (opt_line_number && draw_lineno(view, lineno))
4051                 return TRUE;
4053         draw_text(view, line->type, line->data, TRUE);
4054         return TRUE;
4057 static bool
4058 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
4060         const char *describe_argv[] = { "git", "describe", commit_id, NULL };
4061         char ref[SIZEOF_STR];
4063         if (!io_run_buf(describe_argv, ref, sizeof(ref)) || !*ref)
4064                 return TRUE;
4066         /* This is the only fatal call, since it can "corrupt" the buffer. */
4067         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
4068                 return FALSE;
4070         return TRUE;
4073 static void
4074 add_pager_refs(struct view *view, struct line *line)
4076         char buf[SIZEOF_STR];
4077         char *commit_id = (char *)line->data + STRING_SIZE("commit ");
4078         struct ref_list *list;
4079         size_t bufpos = 0, i;
4080         const char *sep = "Refs: ";
4081         bool is_tag = FALSE;
4083         assert(line->type == LINE_COMMIT);
4085         list = get_ref_list(commit_id);
4086         if (!list) {
4087                 if (view->type == VIEW_DIFF)
4088                         goto try_add_describe_ref;
4089                 return;
4090         }
4092         for (i = 0; i < list->size; i++) {
4093                 struct ref *ref = list->refs[i];
4094                 const char *fmt = ref->tag    ? "%s[%s]" :
4095                                   ref->remote ? "%s<%s>" : "%s%s";
4097                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
4098                         return;
4099                 sep = ", ";
4100                 if (ref->tag)
4101                         is_tag = TRUE;
4102         }
4104         if (!is_tag && view->type == VIEW_DIFF) {
4105 try_add_describe_ref:
4106                 /* Add <tag>-g<commit_id> "fake" reference. */
4107                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
4108                         return;
4109         }
4111         if (bufpos == 0)
4112                 return;
4114         add_line_text(view, buf, LINE_PP_REFS);
4117 static bool
4118 pager_read(struct view *view, char *data)
4120         struct line *line;
4122         if (!data)
4123                 return TRUE;
4125         line = add_line_text(view, data, get_line_type(data));
4126         if (!line)
4127                 return FALSE;
4129         if (line->type == LINE_COMMIT &&
4130             (view->type == VIEW_DIFF ||
4131              view->type == VIEW_LOG))
4132                 add_pager_refs(view, line);
4134         return TRUE;
4137 static enum request
4138 pager_request(struct view *view, enum request request, struct line *line)
4140         int split = 0;
4142         if (request != REQ_ENTER)
4143                 return request;
4145         if (line->type == LINE_COMMIT &&
4146            (view->type == VIEW_LOG ||
4147             view->type == VIEW_PAGER)) {
4148                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
4149                 split = 1;
4150         }
4152         /* Always scroll the view even if it was split. That way
4153          * you can use Enter to scroll through the log view and
4154          * split open each commit diff. */
4155         scroll_view(view, REQ_SCROLL_LINE_DOWN);
4157         /* FIXME: A minor workaround. Scrolling the view will call report("")
4158          * but if we are scrolling a non-current view this won't properly
4159          * update the view title. */
4160         if (split)
4161                 update_view_title(view);
4163         return REQ_NONE;
4166 static bool
4167 pager_grep(struct view *view, struct line *line)
4169         const char *text[] = { line->data, NULL };
4171         return grep_text(view, text);
4174 static void
4175 pager_select(struct view *view, struct line *line)
4177         if (line->type == LINE_COMMIT) {
4178                 char *text = (char *)line->data + STRING_SIZE("commit ");
4180                 if (view->type != VIEW_PAGER)
4181                         string_copy_rev(view->ref, text);
4182                 string_copy_rev(ref_commit, text);
4183         }
4186 static struct view_ops pager_ops = {
4187         "line",
4188         NULL,
4189         NULL,
4190         pager_read,
4191         pager_draw,
4192         pager_request,
4193         pager_grep,
4194         pager_select,
4195 };
4197 static const char *log_argv[SIZEOF_ARG] = {
4198         "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
4199 };
4201 static enum request
4202 log_request(struct view *view, enum request request, struct line *line)
4204         switch (request) {
4205         case REQ_REFRESH:
4206                 load_refs();
4207                 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
4208                 return REQ_NONE;
4209         default:
4210                 return pager_request(view, request, line);
4211         }
4214 static struct view_ops log_ops = {
4215         "line",
4216         log_argv,
4217         NULL,
4218         pager_read,
4219         pager_draw,
4220         log_request,
4221         pager_grep,
4222         pager_select,
4223 };
4225 static const char *diff_argv[SIZEOF_ARG] = {
4226         "git", "show", "--pretty=fuller", "--no-color", "--root",
4227                 "--patch-with-stat", "--find-copies-harder", "-C",
4228                 "%(diffargs)", "%(commit)", "--", "%(fileargs)", NULL
4229 };
4231 static bool
4232 diff_read(struct view *view, char *data)
4234         if (!data) {
4235                 /* Fall back to retry if no diff will be shown. */
4236                 if (view->lines == 0 && opt_file_args) {
4237                         int pos = argv_size(view->argv)
4238                                 - argv_size(opt_file_args) - 1;
4240                         if (pos > 0 && !strcmp(view->argv[pos], "--")) {
4241                                 for (; view->argv[pos]; pos++) {
4242                                         free((void *) view->argv[pos]);
4243                                         view->argv[pos] = NULL;
4244                                 }
4246                                 if (view->pipe)
4247                                         io_done(view->pipe);
4248                                 if (io_run(&view->io, IO_RD, view->dir, view->argv))
4249                                         return FALSE;
4250                         }
4251                 }
4252                 return TRUE;
4253         }
4255         return pager_read(view, data);
4258 static struct view_ops diff_ops = {
4259         "line",
4260         diff_argv,
4261         NULL,
4262         diff_read,
4263         pager_draw,
4264         pager_request,
4265         pager_grep,
4266         pager_select,
4267 };
4269 /*
4270  * Help backend
4271  */
4273 static bool help_keymap_hidden[ARRAY_SIZE(keymap_table)];
4275 static bool
4276 help_open_keymap_title(struct view *view, enum keymap keymap)
4278         struct line *line;
4280         line = add_line_format(view, LINE_HELP_KEYMAP, "[%c] %s bindings",
4281                                help_keymap_hidden[keymap] ? '+' : '-',
4282                                enum_name(keymap_table[keymap]));
4283         if (line)
4284                 line->other = keymap;
4286         return help_keymap_hidden[keymap];
4289 static void
4290 help_open_keymap(struct view *view, enum keymap keymap)
4292         const char *group = NULL;
4293         char buf[SIZEOF_STR];
4294         size_t bufpos;
4295         bool add_title = TRUE;
4296         int i;
4298         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
4299                 const char *key = NULL;
4301                 if (req_info[i].request == REQ_NONE)
4302                         continue;
4304                 if (!req_info[i].request) {
4305                         group = req_info[i].help;
4306                         continue;
4307                 }
4309                 key = get_keys(keymap, req_info[i].request, TRUE);
4310                 if (!key || !*key)
4311                         continue;
4313                 if (add_title && help_open_keymap_title(view, keymap))
4314                         return;
4315                 add_title = FALSE;
4317                 if (group) {
4318                         add_line_text(view, group, LINE_HELP_GROUP);
4319                         group = NULL;
4320                 }
4322                 add_line_format(view, LINE_DEFAULT, "    %-25s %-20s %s", key,
4323                                 enum_name(req_info[i]), req_info[i].help);
4324         }
4326         group = "External commands:";
4328         for (i = 0; i < run_requests; i++) {
4329                 struct run_request *req = get_run_request(REQ_NONE + i + 1);
4330                 const char *key;
4331                 int argc;
4333                 if (!req || req->keymap != keymap)
4334                         continue;
4336                 key = get_key_name(req->key);
4337                 if (!*key)
4338                         key = "(no key defined)";
4340                 if (add_title && help_open_keymap_title(view, keymap))
4341                         return;
4342                 if (group) {
4343                         add_line_text(view, group, LINE_HELP_GROUP);
4344                         group = NULL;
4345                 }
4347                 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
4348                         if (!string_format_from(buf, &bufpos, "%s%s",
4349                                                 argc ? " " : "", req->argv[argc]))
4350                                 return;
4352                 add_line_format(view, LINE_DEFAULT, "    %-25s `%s`", key, buf);
4353         }
4356 static bool
4357 help_open(struct view *view)
4359         enum keymap keymap;
4361         reset_view(view);
4362         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
4363         add_line_text(view, "", LINE_DEFAULT);
4365         for (keymap = 0; keymap < ARRAY_SIZE(keymap_table); keymap++)
4366                 help_open_keymap(view, keymap);
4368         return TRUE;
4371 static enum request
4372 help_request(struct view *view, enum request request, struct line *line)
4374         switch (request) {
4375         case REQ_ENTER:
4376                 if (line->type == LINE_HELP_KEYMAP) {
4377                         help_keymap_hidden[line->other] =
4378                                 !help_keymap_hidden[line->other];
4379                         view->p_restore = TRUE;
4380                         open_view(view, REQ_VIEW_HELP, OPEN_REFRESH);
4381                 }
4383                 return REQ_NONE;
4384         default:
4385                 return pager_request(view, request, line);
4386         }
4389 static struct view_ops help_ops = {
4390         "line",
4391         NULL,
4392         help_open,
4393         NULL,
4394         pager_draw,
4395         help_request,
4396         pager_grep,
4397         pager_select,
4398 };
4401 /*
4402  * Tree backend
4403  */
4405 struct tree_stack_entry {
4406         struct tree_stack_entry *prev;  /* Entry below this in the stack */
4407         unsigned long lineno;           /* Line number to restore */
4408         char *name;                     /* Position of name in opt_path */
4409 };
4411 /* The top of the path stack. */
4412 static struct tree_stack_entry *tree_stack = NULL;
4413 unsigned long tree_lineno = 0;
4415 static void
4416 pop_tree_stack_entry(void)
4418         struct tree_stack_entry *entry = tree_stack;
4420         tree_lineno = entry->lineno;
4421         entry->name[0] = 0;
4422         tree_stack = entry->prev;
4423         free(entry);
4426 static void
4427 push_tree_stack_entry(const char *name, unsigned long lineno)
4429         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
4430         size_t pathlen = strlen(opt_path);
4432         if (!entry)
4433                 return;
4435         entry->prev = tree_stack;
4436         entry->name = opt_path + pathlen;
4437         tree_stack = entry;
4439         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
4440                 pop_tree_stack_entry();
4441                 return;
4442         }
4444         /* Move the current line to the first tree entry. */
4445         tree_lineno = 1;
4446         entry->lineno = lineno;
4449 /* Parse output from git-ls-tree(1):
4450  *
4451  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4452  */
4454 #define SIZEOF_TREE_ATTR \
4455         STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4457 #define SIZEOF_TREE_MODE \
4458         STRING_SIZE("100644 ")
4460 #define TREE_ID_OFFSET \
4461         STRING_SIZE("100644 blob ")
4463 struct tree_entry {
4464         char id[SIZEOF_REV];
4465         mode_t mode;
4466         struct time time;               /* Date from the author ident. */
4467         const char *author;             /* Author of the commit. */
4468         char name[1];
4469 };
4471 static const char *
4472 tree_path(const struct line *line)
4474         return ((struct tree_entry *) line->data)->name;
4477 static int
4478 tree_compare_entry(const struct line *line1, const struct line *line2)
4480         if (line1->type != line2->type)
4481                 return line1->type == LINE_TREE_DIR ? -1 : 1;
4482         return strcmp(tree_path(line1), tree_path(line2));
4485 static const enum sort_field tree_sort_fields[] = {
4486         ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4487 };
4488 static struct sort_state tree_sort_state = SORT_STATE(tree_sort_fields);
4490 static int
4491 tree_compare(const void *l1, const void *l2)
4493         const struct line *line1 = (const struct line *) l1;
4494         const struct line *line2 = (const struct line *) l2;
4495         const struct tree_entry *entry1 = ((const struct line *) l1)->data;
4496         const struct tree_entry *entry2 = ((const struct line *) l2)->data;
4498         if (line1->type == LINE_TREE_HEAD)
4499                 return -1;
4500         if (line2->type == LINE_TREE_HEAD)
4501                 return 1;
4503         switch (get_sort_field(tree_sort_state)) {
4504         case ORDERBY_DATE:
4505                 return sort_order(tree_sort_state, timecmp(&entry1->time, &entry2->time));
4507         case ORDERBY_AUTHOR:
4508                 return sort_order(tree_sort_state, strcmp(entry1->author, entry2->author));
4510         case ORDERBY_NAME:
4511         default:
4512                 return sort_order(tree_sort_state, tree_compare_entry(line1, line2));
4513         }
4517 static struct line *
4518 tree_entry(struct view *view, enum line_type type, const char *path,
4519            const char *mode, const char *id)
4521         struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
4522         struct line *line = entry ? add_line_data(view, entry, type) : NULL;
4524         if (!entry || !line) {
4525                 free(entry);
4526                 return NULL;
4527         }
4529         strncpy(entry->name, path, strlen(path));
4530         if (mode)
4531                 entry->mode = strtoul(mode, NULL, 8);
4532         if (id)
4533                 string_copy_rev(entry->id, id);
4535         return line;
4538 static bool
4539 tree_read_date(struct view *view, char *text, bool *read_date)
4541         static const char *author_name;
4542         static struct time author_time;
4544         if (!text && *read_date) {
4545                 *read_date = FALSE;
4546                 return TRUE;
4548         } else if (!text) {
4549                 char *path = *opt_path ? opt_path : ".";
4550                 /* Find next entry to process */
4551                 const char *log_file[] = {
4552                         "git", "log", "--no-color", "--pretty=raw",
4553                                 "--cc", "--raw", view->id, "--", path, NULL
4554                 };
4556                 if (!view->lines) {
4557                         tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
4558                         report("Tree is empty");
4559                         return TRUE;
4560                 }
4562                 if (!start_update(view, log_file, opt_cdup)) {
4563                         report("Failed to load tree data");
4564                         return TRUE;
4565                 }
4567                 *read_date = TRUE;
4568                 return FALSE;
4570         } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
4571                 parse_author_line(text + STRING_SIZE("author "),
4572                                   &author_name, &author_time);
4574         } else if (*text == ':') {
4575                 char *pos;
4576                 size_t annotated = 1;
4577                 size_t i;
4579                 pos = strchr(text, '\t');
4580                 if (!pos)
4581                         return TRUE;
4582                 text = pos + 1;
4583                 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
4584                         text += strlen(opt_path);
4585                 pos = strchr(text, '/');
4586                 if (pos)
4587                         *pos = 0;
4589                 for (i = 1; i < view->lines; i++) {
4590                         struct line *line = &view->line[i];
4591                         struct tree_entry *entry = line->data;
4593                         annotated += !!entry->author;
4594                         if (entry->author || strcmp(entry->name, text))
4595                                 continue;
4597                         entry->author = author_name;
4598                         entry->time = author_time;
4599                         line->dirty = 1;
4600                         break;
4601                 }
4603                 if (annotated == view->lines)
4604                         io_kill(view->pipe);
4605         }
4606         return TRUE;
4609 static bool
4610 tree_read(struct view *view, char *text)
4612         static bool read_date = FALSE;
4613         struct tree_entry *data;
4614         struct line *entry, *line;
4615         enum line_type type;
4616         size_t textlen = text ? strlen(text) : 0;
4617         char *path = text + SIZEOF_TREE_ATTR;
4619         if (read_date || !text)
4620                 return tree_read_date(view, text, &read_date);
4622         if (textlen <= SIZEOF_TREE_ATTR)
4623                 return FALSE;
4624         if (view->lines == 0 &&
4625             !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
4626                 return FALSE;
4628         /* Strip the path part ... */
4629         if (*opt_path) {
4630                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
4631                 size_t striplen = strlen(opt_path);
4633                 if (pathlen > striplen)
4634                         memmove(path, path + striplen,
4635                                 pathlen - striplen + 1);
4637                 /* Insert "link" to parent directory. */
4638                 if (view->lines == 1 &&
4639                     !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
4640                         return FALSE;
4641         }
4643         type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
4644         entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
4645         if (!entry)
4646                 return FALSE;
4647         data = entry->data;
4649         /* Skip "Directory ..." and ".." line. */
4650         for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
4651                 if (tree_compare_entry(line, entry) <= 0)
4652                         continue;
4654                 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4656                 line->data = data;
4657                 line->type = type;
4658                 for (; line <= entry; line++)
4659                         line->dirty = line->cleareol = 1;
4660                 return TRUE;
4661         }
4663         if (tree_lineno > view->lineno) {
4664                 view->lineno = tree_lineno;
4665                 tree_lineno = 0;
4666         }
4668         return TRUE;
4671 static bool
4672 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4674         struct tree_entry *entry = line->data;
4676         if (line->type == LINE_TREE_HEAD) {
4677                 if (draw_text(view, line->type, "Directory path /", TRUE))
4678                         return TRUE;
4679         } else {
4680                 if (draw_mode(view, entry->mode))
4681                         return TRUE;
4683                 if (opt_author && draw_author(view, entry->author))
4684                         return TRUE;
4686                 if (opt_date && draw_date(view, &entry->time))
4687                         return TRUE;
4688         }
4689         if (draw_text(view, line->type, entry->name, TRUE))
4690                 return TRUE;
4691         return TRUE;
4694 static void
4695 open_blob_editor(const char *id)
4697         const char *blob_argv[] = { "git", "cat-file", "blob", id, NULL };
4698         char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4699         int fd = mkstemp(file);
4701         if (fd == -1)
4702                 report("Failed to create temporary file");
4703         else if (!io_run_append(blob_argv, fd))
4704                 report("Failed to save blob data to file");
4705         else
4706                 open_editor(file);
4707         if (fd != -1)
4708                 unlink(file);
4711 static enum request
4712 tree_request(struct view *view, enum request request, struct line *line)
4714         enum open_flags flags;
4715         struct tree_entry *entry = line->data;
4717         switch (request) {
4718         case REQ_VIEW_BLAME:
4719                 if (line->type != LINE_TREE_FILE) {
4720                         report("Blame only supported for files");
4721                         return REQ_NONE;
4722                 }
4724                 string_copy(opt_ref, view->vid);
4725                 return request;
4727         case REQ_EDIT:
4728                 if (line->type != LINE_TREE_FILE) {
4729                         report("Edit only supported for files");
4730                 } else if (!is_head_commit(view->vid)) {
4731                         open_blob_editor(entry->id);
4732                 } else {
4733                         open_editor(opt_file);
4734                 }
4735                 return REQ_NONE;
4737         case REQ_TOGGLE_SORT_FIELD:
4738         case REQ_TOGGLE_SORT_ORDER:
4739                 sort_view(view, request, &tree_sort_state, tree_compare);
4740                 return REQ_NONE;
4742         case REQ_PARENT:
4743                 if (!*opt_path) {
4744                         /* quit view if at top of tree */
4745                         return REQ_VIEW_CLOSE;
4746                 }
4747                 /* fake 'cd  ..' */
4748                 line = &view->line[1];
4749                 break;
4751         case REQ_ENTER:
4752                 break;
4754         default:
4755                 return request;
4756         }
4758         /* Cleanup the stack if the tree view is at a different tree. */
4759         while (!*opt_path && tree_stack)
4760                 pop_tree_stack_entry();
4762         switch (line->type) {
4763         case LINE_TREE_DIR:
4764                 /* Depending on whether it is a subdirectory or parent link
4765                  * mangle the path buffer. */
4766                 if (line == &view->line[1] && *opt_path) {
4767                         pop_tree_stack_entry();
4769                 } else {
4770                         const char *basename = tree_path(line);
4772                         push_tree_stack_entry(basename, view->lineno);
4773                 }
4775                 /* Trees and subtrees share the same ID, so they are not not
4776                  * unique like blobs. */
4777                 flags = OPEN_RELOAD;
4778                 request = REQ_VIEW_TREE;
4779                 break;
4781         case LINE_TREE_FILE:
4782                 flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
4783                 request = REQ_VIEW_BLOB;
4784                 break;
4786         default:
4787                 return REQ_NONE;
4788         }
4790         open_view(view, request, flags);
4791         if (request == REQ_VIEW_TREE)
4792                 view->lineno = tree_lineno;
4794         return REQ_NONE;
4797 static bool
4798 tree_grep(struct view *view, struct line *line)
4800         struct tree_entry *entry = line->data;
4801         const char *text[] = {
4802                 entry->name,
4803                 opt_author ? entry->author : "",
4804                 mkdate(&entry->time, opt_date),
4805                 NULL
4806         };
4808         return grep_text(view, text);
4811 static void
4812 tree_select(struct view *view, struct line *line)
4814         struct tree_entry *entry = line->data;
4816         if (line->type == LINE_TREE_FILE) {
4817                 string_copy_rev(ref_blob, entry->id);
4818                 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4820         } else if (line->type != LINE_TREE_DIR) {
4821                 return;
4822         }
4824         string_copy_rev(view->ref, entry->id);
4827 static bool
4828 tree_prepare(struct view *view)
4830         if (view->lines == 0 && opt_prefix[0]) {
4831                 char *pos = opt_prefix;
4833                 while (pos && *pos) {
4834                         char *end = strchr(pos, '/');
4836                         if (end)
4837                                 *end = 0;
4838                         push_tree_stack_entry(pos, 0);
4839                         pos = end;
4840                         if (end) {
4841                                 *end = '/';
4842                                 pos++;
4843                         }
4844                 }
4846         } else if (strcmp(view->vid, view->id)) {
4847                 opt_path[0] = 0;
4848         }
4850         return prepare_io(view, opt_cdup, view->ops->argv, TRUE);
4853 static const char *tree_argv[SIZEOF_ARG] = {
4854         "git", "ls-tree", "%(commit)", "%(directory)", NULL
4855 };
4857 static struct view_ops tree_ops = {
4858         "file",
4859         tree_argv,
4860         NULL,
4861         tree_read,
4862         tree_draw,
4863         tree_request,
4864         tree_grep,
4865         tree_select,
4866         tree_prepare,
4867 };
4869 static bool
4870 blob_read(struct view *view, char *line)
4872         if (!line)
4873                 return TRUE;
4874         return add_line_text(view, line, LINE_DEFAULT) != NULL;
4877 static enum request
4878 blob_request(struct view *view, enum request request, struct line *line)
4880         switch (request) {
4881         case REQ_EDIT:
4882                 open_blob_editor(view->vid);
4883                 return REQ_NONE;
4884         default:
4885                 return pager_request(view, request, line);
4886         }
4889 static const char *blob_argv[SIZEOF_ARG] = {
4890         "git", "cat-file", "blob", "%(blob)", NULL
4891 };
4893 static struct view_ops blob_ops = {
4894         "line",
4895         blob_argv,
4896         NULL,
4897         blob_read,
4898         pager_draw,
4899         blob_request,
4900         pager_grep,
4901         pager_select,
4902 };
4904 /*
4905  * Blame backend
4906  *
4907  * Loading the blame view is a two phase job:
4908  *
4909  *  1. File content is read either using opt_file from the
4910  *     filesystem or using git-cat-file.
4911  *  2. Then blame information is incrementally added by
4912  *     reading output from git-blame.
4913  */
4915 struct blame_commit {
4916         char id[SIZEOF_REV];            /* SHA1 ID. */
4917         char title[128];                /* First line of the commit message. */
4918         const char *author;             /* Author of the commit. */
4919         struct time time;               /* Date from the author ident. */
4920         char filename[128];             /* Name of file. */
4921         char parent_id[SIZEOF_REV];     /* Parent/previous SHA1 ID. */
4922         char parent_filename[128];      /* Parent/previous name of file. */
4923 };
4925 struct blame {
4926         struct blame_commit *commit;
4927         unsigned long lineno;
4928         char text[1];
4929 };
4931 static bool
4932 blame_open(struct view *view)
4934         char path[SIZEOF_STR];
4935         size_t i;
4937         if (!view->prev && *opt_prefix) {
4938                 string_copy(path, opt_file);
4939                 if (!string_format(opt_file, "%s%s", opt_prefix, path))
4940                         return FALSE;
4941         }
4943         if (*opt_ref || !io_open(&view->io, "%s%s", opt_cdup, opt_file)) {
4944                 const char *blame_cat_file_argv[] = {
4945                         "git", "cat-file", "blob", path, NULL
4946                 };
4948                 if (!string_format(path, "%s:%s", opt_ref, opt_file) ||
4949                     !start_update(view, blame_cat_file_argv, opt_cdup))
4950                         return FALSE;
4951         }
4953         /* First pass: remove multiple references to the same commit. */
4954         for (i = 0; i < view->lines; i++) {
4955                 struct blame *blame = view->line[i].data;
4957                 if (blame->commit && blame->commit->id[0])
4958                         blame->commit->id[0] = 0;
4959                 else
4960                         blame->commit = NULL;
4961         }
4963         /* Second pass: free existing references. */
4964         for (i = 0; i < view->lines; i++) {
4965                 struct blame *blame = view->line[i].data;
4967                 if (blame->commit)
4968                         free(blame->commit);
4969         }
4971         setup_update(view, opt_file);
4972         string_format(view->ref, "%s ...", opt_file);
4974         return TRUE;
4977 static struct blame_commit *
4978 get_blame_commit(struct view *view, const char *id)
4980         size_t i;
4982         for (i = 0; i < view->lines; i++) {
4983                 struct blame *blame = view->line[i].data;
4985                 if (!blame->commit)
4986                         continue;
4988                 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4989                         return blame->commit;
4990         }
4992         {
4993                 struct blame_commit *commit = calloc(1, sizeof(*commit));
4995                 if (commit)
4996                         string_ncopy(commit->id, id, SIZEOF_REV);
4997                 return commit;
4998         }
5001 static bool
5002 parse_number(const char **posref, size_t *number, size_t min, size_t max)
5004         const char *pos = *posref;
5006         *posref = NULL;
5007         pos = strchr(pos + 1, ' ');
5008         if (!pos || !isdigit(pos[1]))
5009                 return FALSE;
5010         *number = atoi(pos + 1);
5011         if (*number < min || *number > max)
5012                 return FALSE;
5014         *posref = pos;
5015         return TRUE;
5018 static struct blame_commit *
5019 parse_blame_commit(struct view *view, const char *text, int *blamed)
5021         struct blame_commit *commit;
5022         struct blame *blame;
5023         const char *pos = text + SIZEOF_REV - 2;
5024         size_t orig_lineno = 0;
5025         size_t lineno;
5026         size_t group;
5028         if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
5029                 return NULL;
5031         if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
5032             !parse_number(&pos, &lineno, 1, view->lines) ||
5033             !parse_number(&pos, &group, 1, view->lines - lineno + 1))
5034                 return NULL;
5036         commit = get_blame_commit(view, text);
5037         if (!commit)
5038                 return NULL;
5040         *blamed += group;
5041         while (group--) {
5042                 struct line *line = &view->line[lineno + group - 1];
5044                 blame = line->data;
5045                 blame->commit = commit;
5046                 blame->lineno = orig_lineno + group - 1;
5047                 line->dirty = 1;
5048         }
5050         return commit;
5053 static bool
5054 blame_read_file(struct view *view, const char *line, bool *read_file)
5056         if (!line) {
5057                 const char *blame_argv[] = {
5058                         "git", "blame", "--incremental",
5059                                 *opt_ref ? opt_ref : "--incremental", "--", opt_file, NULL
5060                 };
5062                 if (view->lines == 0 && !view->prev)
5063                         die("No blame exist for %s", view->vid);
5065                 if (view->lines == 0 || !start_update(view, blame_argv, opt_cdup)) {
5066                         report("Failed to load blame data");
5067                         return TRUE;
5068                 }
5070                 *read_file = FALSE;
5071                 return FALSE;
5073         } else {
5074                 size_t linelen = strlen(line);
5075                 struct blame *blame = malloc(sizeof(*blame) + linelen);
5077                 if (!blame)
5078                         return FALSE;
5080                 blame->commit = NULL;
5081                 strncpy(blame->text, line, linelen);
5082                 blame->text[linelen] = 0;
5083                 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
5084         }
5087 static bool
5088 match_blame_header(const char *name, char **line)
5090         size_t namelen = strlen(name);
5091         bool matched = !strncmp(name, *line, namelen);
5093         if (matched)
5094                 *line += namelen;
5096         return matched;
5099 static bool
5100 blame_read(struct view *view, char *line)
5102         static struct blame_commit *commit = NULL;
5103         static int blamed = 0;
5104         static bool read_file = TRUE;
5106         if (read_file)
5107                 return blame_read_file(view, line, &read_file);
5109         if (!line) {
5110                 /* Reset all! */
5111                 commit = NULL;
5112                 blamed = 0;
5113                 read_file = TRUE;
5114                 string_format(view->ref, "%s", view->vid);
5115                 if (view_is_displayed(view)) {
5116                         update_view_title(view);
5117                         redraw_view_from(view, 0);
5118                 }
5119                 return TRUE;
5120         }
5122         if (!commit) {
5123                 commit = parse_blame_commit(view, line, &blamed);
5124                 string_format(view->ref, "%s %2d%%", view->vid,
5125                               view->lines ? blamed * 100 / view->lines : 0);
5127         } else if (match_blame_header("author ", &line)) {
5128                 commit->author = get_author(line);
5130         } else if (match_blame_header("author-time ", &line)) {
5131                 parse_timesec(&commit->time, line);
5133         } else if (match_blame_header("author-tz ", &line)) {
5134                 parse_timezone(&commit->time, line);
5136         } else if (match_blame_header("summary ", &line)) {
5137                 string_ncopy(commit->title, line, strlen(line));
5139         } else if (match_blame_header("previous ", &line)) {
5140                 if (strlen(line) <= SIZEOF_REV)
5141                         return FALSE;
5142                 string_copy_rev(commit->parent_id, line);
5143                 line += SIZEOF_REV;
5144                 string_ncopy(commit->parent_filename, line, strlen(line));
5146         } else if (match_blame_header("filename ", &line)) {
5147                 string_ncopy(commit->filename, line, strlen(line));
5148                 commit = NULL;
5149         }
5151         return TRUE;
5154 static bool
5155 blame_draw(struct view *view, struct line *line, unsigned int lineno)
5157         struct blame *blame = line->data;
5158         struct time *time = NULL;
5159         const char *id = NULL, *author = NULL;
5161         if (blame->commit && *blame->commit->filename) {
5162                 id = blame->commit->id;
5163                 author = blame->commit->author;
5164                 time = &blame->commit->time;
5165         }
5167         if (opt_date && draw_date(view, time))
5168                 return TRUE;
5170         if (opt_author && draw_author(view, author))
5171                 return TRUE;
5173         if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
5174                 return TRUE;
5176         if (draw_lineno(view, lineno))
5177                 return TRUE;
5179         draw_text(view, LINE_DEFAULT, blame->text, TRUE);
5180         return TRUE;
5183 static bool
5184 check_blame_commit(struct blame *blame, bool check_null_id)
5186         if (!blame->commit)
5187                 report("Commit data not loaded yet");
5188         else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
5189                 report("No commit exist for the selected line");
5190         else
5191                 return TRUE;
5192         return FALSE;
5195 static void
5196 setup_blame_parent_line(struct view *view, struct blame *blame)
5198         char from[SIZEOF_REF + SIZEOF_STR];
5199         char to[SIZEOF_REF + SIZEOF_STR];
5200         const char *diff_tree_argv[] = {
5201                 "git", "diff", "--no-textconv", "--no-extdiff", "--no-color",
5202                         "-U0", from, to, "--", NULL
5203         };
5204         struct io io;
5205         int parent_lineno = -1;
5206         int blamed_lineno = -1;
5207         char *line;
5209         if (!string_format(from, "%s:%s", opt_ref, opt_file) ||
5210             !string_format(to, "%s:%s", blame->commit->id, blame->commit->filename) ||
5211             !io_run(&io, IO_RD, NULL, diff_tree_argv))
5212                 return;
5214         while ((line = io_get(&io, '\n', TRUE))) {
5215                 if (*line == '@') {
5216                         char *pos = strchr(line, '+');
5218                         parent_lineno = atoi(line + 4);
5219                         if (pos)
5220                                 blamed_lineno = atoi(pos + 1);
5222                 } else if (*line == '+' && parent_lineno != -1) {
5223                         if (blame->lineno == blamed_lineno - 1 &&
5224                             !strcmp(blame->text, line + 1)) {
5225                                 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
5226                                 break;
5227                         }
5228                         blamed_lineno++;
5229                 }
5230         }
5232         io_done(&io);
5235 static enum request
5236 blame_request(struct view *view, enum request request, struct line *line)
5238         enum open_flags flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
5239         struct blame *blame = line->data;
5241         switch (request) {
5242         case REQ_VIEW_BLAME:
5243                 if (check_blame_commit(blame, TRUE)) {
5244                         string_copy(opt_ref, blame->commit->id);
5245                         string_copy(opt_file, blame->commit->filename);
5246                         if (blame->lineno)
5247                                 view->lineno = blame->lineno;
5248                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5249                 }
5250                 break;
5252         case REQ_PARENT:
5253                 if (!check_blame_commit(blame, TRUE))
5254                         break;
5255                 if (!*blame->commit->parent_id) {
5256                         report("The selected commit has no parents");
5257                 } else {
5258                         string_copy_rev(opt_ref, blame->commit->parent_id);
5259                         string_copy(opt_file, blame->commit->parent_filename);
5260                         setup_blame_parent_line(view, blame);
5261                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5262                 }
5263                 break;
5265         case REQ_ENTER:
5266                 if (!check_blame_commit(blame, FALSE))
5267                         break;
5269                 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
5270                     !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
5271                         break;
5273                 if (!strcmp(blame->commit->id, NULL_ID)) {
5274                         struct view *diff = VIEW(REQ_VIEW_DIFF);
5275                         const char *diff_index_argv[] = {
5276                                 "git", "diff-index", "--root", "--patch-with-stat",
5277                                         "-C", "-M", "HEAD", "--", view->vid, NULL
5278                         };
5280                         if (!*blame->commit->parent_id) {
5281                                 diff_index_argv[1] = "diff";
5282                                 diff_index_argv[2] = "--no-color";
5283                                 diff_index_argv[6] = "--";
5284                                 diff_index_argv[7] = "/dev/null";
5285                         }
5287                         if (!prepare_update(diff, diff_index_argv, NULL)) {
5288                                 report("Failed to allocate diff command");
5289                                 break;
5290                         }
5291                         flags |= OPEN_PREPARED;
5292                 }
5294                 open_view(view, REQ_VIEW_DIFF, flags);
5295                 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
5296                         string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
5297                 break;
5299         default:
5300                 return request;
5301         }
5303         return REQ_NONE;
5306 static bool
5307 blame_grep(struct view *view, struct line *line)
5309         struct blame *blame = line->data;
5310         struct blame_commit *commit = blame->commit;
5311         const char *text[] = {
5312                 blame->text,
5313                 commit ? commit->title : "",
5314                 commit ? commit->id : "",
5315                 commit && opt_author ? commit->author : "",
5316                 commit ? mkdate(&commit->time, opt_date) : "",
5317                 NULL
5318         };
5320         return grep_text(view, text);
5323 static void
5324 blame_select(struct view *view, struct line *line)
5326         struct blame *blame = line->data;
5327         struct blame_commit *commit = blame->commit;
5329         if (!commit)
5330                 return;
5332         if (!strcmp(commit->id, NULL_ID))
5333                 string_ncopy(ref_commit, "HEAD", 4);
5334         else
5335                 string_copy_rev(ref_commit, commit->id);
5338 static struct view_ops blame_ops = {
5339         "line",
5340         NULL,
5341         blame_open,
5342         blame_read,
5343         blame_draw,
5344         blame_request,
5345         blame_grep,
5346         blame_select,
5347 };
5349 /*
5350  * Branch backend
5351  */
5353 struct branch {
5354         const char *author;             /* Author of the last commit. */
5355         struct time time;               /* Date of the last activity. */
5356         const struct ref *ref;          /* Name and commit ID information. */
5357 };
5359 static const struct ref branch_all;
5361 static const enum sort_field branch_sort_fields[] = {
5362         ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
5363 };
5364 static struct sort_state branch_sort_state = SORT_STATE(branch_sort_fields);
5366 static int
5367 branch_compare(const void *l1, const void *l2)
5369         const struct branch *branch1 = ((const struct line *) l1)->data;
5370         const struct branch *branch2 = ((const struct line *) l2)->data;
5372         switch (get_sort_field(branch_sort_state)) {
5373         case ORDERBY_DATE:
5374                 return sort_order(branch_sort_state, timecmp(&branch1->time, &branch2->time));
5376         case ORDERBY_AUTHOR:
5377                 return sort_order(branch_sort_state, strcmp(branch1->author, branch2->author));
5379         case ORDERBY_NAME:
5380         default:
5381                 return sort_order(branch_sort_state, strcmp(branch1->ref->name, branch2->ref->name));
5382         }
5385 static bool
5386 branch_draw(struct view *view, struct line *line, unsigned int lineno)
5388         struct branch *branch = line->data;
5389         enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
5391         if (opt_date && draw_date(view, &branch->time))
5392                 return TRUE;
5394         if (opt_author && draw_author(view, branch->author))
5395                 return TRUE;
5397         draw_text(view, type, branch->ref == &branch_all ? "All branches" : branch->ref->name, TRUE);
5398         return TRUE;
5401 static enum request
5402 branch_request(struct view *view, enum request request, struct line *line)
5404         struct branch *branch = line->data;
5406         switch (request) {
5407         case REQ_REFRESH:
5408                 load_refs();
5409                 open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
5410                 return REQ_NONE;
5412         case REQ_TOGGLE_SORT_FIELD:
5413         case REQ_TOGGLE_SORT_ORDER:
5414                 sort_view(view, request, &branch_sort_state, branch_compare);
5415                 return REQ_NONE;
5417         case REQ_ENTER:
5418         {
5419                 const struct ref *ref = branch->ref;
5420                 const char *all_branches_argv[] = {
5421                         "git", "log", "--no-color", "--pretty=raw", "--parents",
5422                               "--topo-order",
5423                               ref == &branch_all ? "--all" : ref->name, NULL
5424                 };
5425                 struct view *main_view = VIEW(REQ_VIEW_MAIN);
5427                 if (!prepare_update(main_view, all_branches_argv, NULL))
5428                         report("Failed to load view of all branches");
5429                 else
5430                         open_view(view, REQ_VIEW_MAIN, OPEN_PREPARED | OPEN_SPLIT);
5431                 return REQ_NONE;
5432         }
5433         default:
5434                 return request;
5435         }
5438 static bool
5439 branch_read(struct view *view, char *line)
5441         static char id[SIZEOF_REV];
5442         struct branch *reference;
5443         size_t i;
5445         if (!line)
5446                 return TRUE;
5448         switch (get_line_type(line)) {
5449         case LINE_COMMIT:
5450                 string_copy_rev(id, line + STRING_SIZE("commit "));
5451                 return TRUE;
5453         case LINE_AUTHOR:
5454                 for (i = 0, reference = NULL; i < view->lines; i++) {
5455                         struct branch *branch = view->line[i].data;
5457                         if (strcmp(branch->ref->id, id))
5458                                 continue;
5460                         view->line[i].dirty = TRUE;
5461                         if (reference) {
5462                                 branch->author = reference->author;
5463                                 branch->time = reference->time;
5464                                 continue;
5465                         }
5467                         parse_author_line(line + STRING_SIZE("author "),
5468                                           &branch->author, &branch->time);
5469                         reference = branch;
5470                 }
5471                 return TRUE;
5473         default:
5474                 return TRUE;
5475         }
5479 static bool
5480 branch_open_visitor(void *data, const struct ref *ref)
5482         struct view *view = data;
5483         struct branch *branch;
5485         if (ref->tag || ref->ltag || ref->remote)
5486                 return TRUE;
5488         branch = calloc(1, sizeof(*branch));
5489         if (!branch)
5490                 return FALSE;
5492         branch->ref = ref;
5493         return !!add_line_data(view, branch, LINE_DEFAULT);
5496 static bool
5497 branch_open(struct view *view)
5499         const char *branch_log[] = {
5500                 "git", "log", "--no-color", "--pretty=raw",
5501                         "--simplify-by-decoration", "--all", NULL
5502         };
5504         if (!start_update(view, branch_log, NULL)) {
5505                 report("Failed to load branch data");
5506                 return TRUE;
5507         }
5509         setup_update(view, view->id);
5510         branch_open_visitor(view, &branch_all);
5511         foreach_ref(branch_open_visitor, view);
5512         view->p_restore = TRUE;
5514         return TRUE;
5517 static bool
5518 branch_grep(struct view *view, struct line *line)
5520         struct branch *branch = line->data;
5521         const char *text[] = {
5522                 branch->ref->name,
5523                 branch->author,
5524                 NULL
5525         };
5527         return grep_text(view, text);
5530 static void
5531 branch_select(struct view *view, struct line *line)
5533         struct branch *branch = line->data;
5535         string_copy_rev(view->ref, branch->ref->id);
5536         string_copy_rev(ref_commit, branch->ref->id);
5537         string_copy_rev(ref_head, branch->ref->id);
5538         string_copy_rev(ref_branch, branch->ref->name);
5541 static struct view_ops branch_ops = {
5542         "branch",
5543         NULL,
5544         branch_open,
5545         branch_read,
5546         branch_draw,
5547         branch_request,
5548         branch_grep,
5549         branch_select,
5550 };
5552 /*
5553  * Status backend
5554  */
5556 struct status {
5557         char status;
5558         struct {
5559                 mode_t mode;
5560                 char rev[SIZEOF_REV];
5561                 char name[SIZEOF_STR];
5562         } old;
5563         struct {
5564                 mode_t mode;
5565                 char rev[SIZEOF_REV];
5566                 char name[SIZEOF_STR];
5567         } new;
5568 };
5570 static char status_onbranch[SIZEOF_STR];
5571 static struct status stage_status;
5572 static enum line_type stage_line_type;
5573 static size_t stage_chunks;
5574 static int *stage_chunk;
5576 DEFINE_ALLOCATOR(realloc_ints, int, 32)
5578 /* This should work even for the "On branch" line. */
5579 static inline bool
5580 status_has_none(struct view *view, struct line *line)
5582         return line < view->line + view->lines && !line[1].data;
5585 /* Get fields from the diff line:
5586  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5587  */
5588 static inline bool
5589 status_get_diff(struct status *file, const char *buf, size_t bufsize)
5591         const char *old_mode = buf +  1;
5592         const char *new_mode = buf +  8;
5593         const char *old_rev  = buf + 15;
5594         const char *new_rev  = buf + 56;
5595         const char *status   = buf + 97;
5597         if (bufsize < 98 ||
5598             old_mode[-1] != ':' ||
5599             new_mode[-1] != ' ' ||
5600             old_rev[-1]  != ' ' ||
5601             new_rev[-1]  != ' ' ||
5602             status[-1]   != ' ')
5603                 return FALSE;
5605         file->status = *status;
5607         string_copy_rev(file->old.rev, old_rev);
5608         string_copy_rev(file->new.rev, new_rev);
5610         file->old.mode = strtoul(old_mode, NULL, 8);
5611         file->new.mode = strtoul(new_mode, NULL, 8);
5613         file->old.name[0] = file->new.name[0] = 0;
5615         return TRUE;
5618 static bool
5619 status_run(struct view *view, const char *argv[], char status, enum line_type type)
5621         struct status *unmerged = NULL;
5622         char *buf;
5623         struct io io;
5625         if (!io_run(&io, IO_RD, opt_cdup, argv))
5626                 return FALSE;
5628         add_line_data(view, NULL, type);
5630         while ((buf = io_get(&io, 0, TRUE))) {
5631                 struct status *file = unmerged;
5633                 if (!file) {
5634                         file = calloc(1, sizeof(*file));
5635                         if (!file || !add_line_data(view, file, type))
5636                                 goto error_out;
5637                 }
5639                 /* Parse diff info part. */
5640                 if (status) {
5641                         file->status = status;
5642                         if (status == 'A')
5643                                 string_copy(file->old.rev, NULL_ID);
5645                 } else if (!file->status || file == unmerged) {
5646                         if (!status_get_diff(file, buf, strlen(buf)))
5647                                 goto error_out;
5649                         buf = io_get(&io, 0, TRUE);
5650                         if (!buf)
5651                                 break;
5653                         /* Collapse all modified entries that follow an
5654                          * associated unmerged entry. */
5655                         if (unmerged == file) {
5656                                 unmerged->status = 'U';
5657                                 unmerged = NULL;
5658                         } else if (file->status == 'U') {
5659                                 unmerged = file;
5660                         }
5661                 }
5663                 /* Grab the old name for rename/copy. */
5664                 if (!*file->old.name &&
5665                     (file->status == 'R' || file->status == 'C')) {
5666                         string_ncopy(file->old.name, buf, strlen(buf));
5668                         buf = io_get(&io, 0, TRUE);
5669                         if (!buf)
5670                                 break;
5671                 }
5673                 /* git-ls-files just delivers a NUL separated list of
5674                  * file names similar to the second half of the
5675                  * git-diff-* output. */
5676                 string_ncopy(file->new.name, buf, strlen(buf));
5677                 if (!*file->old.name)
5678                         string_copy(file->old.name, file->new.name);
5679                 file = NULL;
5680         }
5682         if (io_error(&io)) {
5683 error_out:
5684                 io_done(&io);
5685                 return FALSE;
5686         }
5688         if (!view->line[view->lines - 1].data)
5689                 add_line_data(view, NULL, LINE_STAT_NONE);
5691         io_done(&io);
5692         return TRUE;
5695 /* Don't show unmerged entries in the staged section. */
5696 static const char *status_diff_index_argv[] = {
5697         "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5698                              "--cached", "-M", "HEAD", NULL
5699 };
5701 static const char *status_diff_files_argv[] = {
5702         "git", "diff-files", "-z", NULL
5703 };
5705 static const char *status_list_other_argv[] = {
5706         "git", "ls-files", "-z", "--others", "--exclude-standard", opt_prefix, NULL, NULL,
5707 };
5709 static const char *status_list_no_head_argv[] = {
5710         "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5711 };
5713 static const char *update_index_argv[] = {
5714         "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5715 };
5717 /* Restore the previous line number to stay in the context or select a
5718  * line with something that can be updated. */
5719 static void
5720 status_restore(struct view *view)
5722         if (view->p_lineno >= view->lines)
5723                 view->p_lineno = view->lines - 1;
5724         while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
5725                 view->p_lineno++;
5726         while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
5727                 view->p_lineno--;
5729         /* If the above fails, always skip the "On branch" line. */
5730         if (view->p_lineno < view->lines)
5731                 view->lineno = view->p_lineno;
5732         else
5733                 view->lineno = 1;
5735         if (view->lineno < view->offset)
5736                 view->offset = view->lineno;
5737         else if (view->offset + view->height <= view->lineno)
5738                 view->offset = view->lineno - view->height + 1;
5740         view->p_restore = FALSE;
5743 static void
5744 status_update_onbranch(void)
5746         static const char *paths[][2] = {
5747                 { "rebase-apply/rebasing",      "Rebasing" },
5748                 { "rebase-apply/applying",      "Applying mailbox" },
5749                 { "rebase-apply/",              "Rebasing mailbox" },
5750                 { "rebase-merge/interactive",   "Interactive rebase" },
5751                 { "rebase-merge/",              "Rebase merge" },
5752                 { "MERGE_HEAD",                 "Merging" },
5753                 { "BISECT_LOG",                 "Bisecting" },
5754                 { "HEAD",                       "On branch" },
5755         };
5756         char buf[SIZEOF_STR];
5757         struct stat stat;
5758         int i;
5760         if (is_initial_commit()) {
5761                 string_copy(status_onbranch, "Initial commit");
5762                 return;
5763         }
5765         for (i = 0; i < ARRAY_SIZE(paths); i++) {
5766                 char *head = opt_head;
5768                 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5769                     lstat(buf, &stat) < 0)
5770                         continue;
5772                 if (!*opt_head) {
5773                         struct io io;
5775                         if (io_open(&io, "%s/rebase-merge/head-name", opt_git_dir) &&
5776                             io_read_buf(&io, buf, sizeof(buf))) {
5777                                 head = buf;
5778                                 if (!prefixcmp(head, "refs/heads/"))
5779                                         head += STRING_SIZE("refs/heads/");
5780                         }
5781                 }
5783                 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5784                         string_copy(status_onbranch, opt_head);
5785                 return;
5786         }
5788         string_copy(status_onbranch, "Not currently on any branch");
5791 /* First parse staged info using git-diff-index(1), then parse unstaged
5792  * info using git-diff-files(1), and finally untracked files using
5793  * git-ls-files(1). */
5794 static bool
5795 status_open(struct view *view)
5797         reset_view(view);
5799         add_line_data(view, NULL, LINE_STAT_HEAD);
5800         status_update_onbranch();
5802         io_run_bg(update_index_argv);
5804         if (is_initial_commit()) {
5805                 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5806                         return FALSE;
5807         } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5808                 return FALSE;
5809         }
5811         if (!opt_untracked_dirs_content)
5812                 status_list_other_argv[ARRAY_SIZE(status_list_other_argv) - 2] = "--directory";
5814         if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5815             !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5816                 return FALSE;
5818         /* Restore the exact position or use the specialized restore
5819          * mode? */
5820         if (!view->p_restore)
5821                 status_restore(view);
5822         return TRUE;
5825 static bool
5826 status_draw(struct view *view, struct line *line, unsigned int lineno)
5828         struct status *status = line->data;
5829         enum line_type type;
5830         const char *text;
5832         if (!status) {
5833                 switch (line->type) {
5834                 case LINE_STAT_STAGED:
5835                         type = LINE_STAT_SECTION;
5836                         text = "Changes to be committed:";
5837                         break;
5839                 case LINE_STAT_UNSTAGED:
5840                         type = LINE_STAT_SECTION;
5841                         text = "Changed but not updated:";
5842                         break;
5844                 case LINE_STAT_UNTRACKED:
5845                         type = LINE_STAT_SECTION;
5846                         text = "Untracked files:";
5847                         break;
5849                 case LINE_STAT_NONE:
5850                         type = LINE_DEFAULT;
5851                         text = "  (no files)";
5852                         break;
5854                 case LINE_STAT_HEAD:
5855                         type = LINE_STAT_HEAD;
5856                         text = status_onbranch;
5857                         break;
5859                 default:
5860                         return FALSE;
5861                 }
5862         } else {
5863                 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5865                 buf[0] = status->status;
5866                 if (draw_text(view, line->type, buf, TRUE))
5867                         return TRUE;
5868                 type = LINE_DEFAULT;
5869                 text = status->new.name;
5870         }
5872         draw_text(view, type, text, TRUE);
5873         return TRUE;
5876 static enum request
5877 status_load_error(struct view *view, struct view *stage, const char *path)
5879         if (displayed_views() == 2 || display[current_view] != view)
5880                 maximize_view(view);
5881         report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5882         return REQ_NONE;
5885 static enum request
5886 status_enter(struct view *view, struct line *line)
5888         struct status *status = line->data;
5889         const char *oldpath = status ? status->old.name : NULL;
5890         /* Diffs for unmerged entries are empty when passing the new
5891          * path, so leave it empty. */
5892         const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5893         const char *info;
5894         enum open_flags split;
5895         struct view *stage = VIEW(REQ_VIEW_STAGE);
5897         if (line->type == LINE_STAT_NONE ||
5898             (!status && line[1].type == LINE_STAT_NONE)) {
5899                 report("No file to diff");
5900                 return REQ_NONE;
5901         }
5903         switch (line->type) {
5904         case LINE_STAT_STAGED:
5905                 if (is_initial_commit()) {
5906                         const char *no_head_diff_argv[] = {
5907                                 "git", "diff", "--no-color", "--patch-with-stat",
5908                                         "--", "/dev/null", newpath, NULL
5909                         };
5911                         if (!prepare_update(stage, no_head_diff_argv, opt_cdup))
5912                                 return status_load_error(view, stage, newpath);
5913                 } else {
5914                         const char *index_show_argv[] = {
5915                                 "git", "diff-index", "--root", "--patch-with-stat",
5916                                         "-C", "-M", "--cached", "HEAD", "--",
5917                                         oldpath, newpath, NULL
5918                         };
5920                         if (!prepare_update(stage, index_show_argv, opt_cdup))
5921                                 return status_load_error(view, stage, newpath);
5922                 }
5924                 if (status)
5925                         info = "Staged changes to %s";
5926                 else
5927                         info = "Staged changes";
5928                 break;
5930         case LINE_STAT_UNSTAGED:
5931         {
5932                 const char *files_show_argv[] = {
5933                         "git", "diff-files", "--root", "--patch-with-stat",
5934                                 "-C", "-M", "--", oldpath, newpath, NULL
5935                 };
5937                 if (!prepare_update(stage, files_show_argv, opt_cdup))
5938                         return status_load_error(view, stage, newpath);
5939                 if (status)
5940                         info = "Unstaged changes to %s";
5941                 else
5942                         info = "Unstaged changes";
5943                 break;
5944         }
5945         case LINE_STAT_UNTRACKED:
5946                 if (!newpath) {
5947                         report("No file to show");
5948                         return REQ_NONE;
5949                 }
5951                 if (!suffixcmp(status->new.name, -1, "/")) {
5952                         report("Cannot display a directory");
5953                         return REQ_NONE;
5954                 }
5956                 if (!prepare_update_file(stage, newpath))
5957                         return status_load_error(view, stage, newpath);
5958                 info = "Untracked file %s";
5959                 break;
5961         case LINE_STAT_HEAD:
5962                 return REQ_NONE;
5964         default:
5965                 die("line type %d not handled in switch", line->type);
5966         }
5968         split = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
5969         open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5970         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5971                 if (status) {
5972                         stage_status = *status;
5973                 } else {
5974                         memset(&stage_status, 0, sizeof(stage_status));
5975                 }
5977                 stage_line_type = line->type;
5978                 stage_chunks = 0;
5979                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5980         }
5982         return REQ_NONE;
5985 static bool
5986 status_exists(struct status *status, enum line_type type)
5988         struct view *view = VIEW(REQ_VIEW_STATUS);
5989         unsigned long lineno;
5991         for (lineno = 0; lineno < view->lines; lineno++) {
5992                 struct line *line = &view->line[lineno];
5993                 struct status *pos = line->data;
5995                 if (line->type != type)
5996                         continue;
5997                 if (!pos && (!status || !status->status) && line[1].data) {
5998                         select_view_line(view, lineno);
5999                         return TRUE;
6000                 }
6001                 if (pos && !strcmp(status->new.name, pos->new.name)) {
6002                         select_view_line(view, lineno);
6003                         return TRUE;
6004                 }
6005         }
6007         return FALSE;
6011 static bool
6012 status_update_prepare(struct io *io, enum line_type type)
6014         const char *staged_argv[] = {
6015                 "git", "update-index", "-z", "--index-info", NULL
6016         };
6017         const char *others_argv[] = {
6018                 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
6019         };
6021         switch (type) {
6022         case LINE_STAT_STAGED:
6023                 return io_run(io, IO_WR, opt_cdup, staged_argv);
6025         case LINE_STAT_UNSTAGED:
6026         case LINE_STAT_UNTRACKED:
6027                 return io_run(io, IO_WR, opt_cdup, others_argv);
6029         default:
6030                 die("line type %d not handled in switch", type);
6031                 return FALSE;
6032         }
6035 static bool
6036 status_update_write(struct io *io, struct status *status, enum line_type type)
6038         char buf[SIZEOF_STR];
6039         size_t bufsize = 0;
6041         switch (type) {
6042         case LINE_STAT_STAGED:
6043                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
6044                                         status->old.mode,
6045                                         status->old.rev,
6046                                         status->old.name, 0))
6047                         return FALSE;
6048                 break;
6050         case LINE_STAT_UNSTAGED:
6051         case LINE_STAT_UNTRACKED:
6052                 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
6053                         return FALSE;
6054                 break;
6056         default:
6057                 die("line type %d not handled in switch", type);
6058         }
6060         return io_write(io, buf, bufsize);
6063 static bool
6064 status_update_file(struct status *status, enum line_type type)
6066         struct io io;
6067         bool result;
6069         if (!status_update_prepare(&io, type))
6070                 return FALSE;
6072         result = status_update_write(&io, status, type);
6073         return io_done(&io) && result;
6076 static bool
6077 status_update_files(struct view *view, struct line *line)
6079         char buf[sizeof(view->ref)];
6080         struct io io;
6081         bool result = TRUE;
6082         struct line *pos = view->line + view->lines;
6083         int files = 0;
6084         int file, done;
6085         int cursor_y = -1, cursor_x = -1;
6087         if (!status_update_prepare(&io, line->type))
6088                 return FALSE;
6090         for (pos = line; pos < view->line + view->lines && pos->data; pos++)
6091                 files++;
6093         string_copy(buf, view->ref);
6094         getsyx(cursor_y, cursor_x);
6095         for (file = 0, done = 5; result && file < files; line++, file++) {
6096                 int almost_done = file * 100 / files;
6098                 if (almost_done > done) {
6099                         done = almost_done;
6100                         string_format(view->ref, "updating file %u of %u (%d%% done)",
6101                                       file, files, done);
6102                         update_view_title(view);
6103                         setsyx(cursor_y, cursor_x);
6104                         doupdate();
6105                 }
6106                 result = status_update_write(&io, line->data, line->type);
6107         }
6108         string_copy(view->ref, buf);
6110         return io_done(&io) && result;
6113 static bool
6114 status_update(struct view *view)
6116         struct line *line = &view->line[view->lineno];
6118         assert(view->lines);
6120         if (!line->data) {
6121                 /* This should work even for the "On branch" line. */
6122                 if (line < view->line + view->lines && !line[1].data) {
6123                         report("Nothing to update");
6124                         return FALSE;
6125                 }
6127                 if (!status_update_files(view, line + 1)) {
6128                         report("Failed to update file status");
6129                         return FALSE;
6130                 }
6132         } else if (!status_update_file(line->data, line->type)) {
6133                 report("Failed to update file status");
6134                 return FALSE;
6135         }
6137         return TRUE;
6140 static bool
6141 status_revert(struct status *status, enum line_type type, bool has_none)
6143         if (!status || type != LINE_STAT_UNSTAGED) {
6144                 if (type == LINE_STAT_STAGED) {
6145                         report("Cannot revert changes to staged files");
6146                 } else if (type == LINE_STAT_UNTRACKED) {
6147                         report("Cannot revert changes to untracked files");
6148                 } else if (has_none) {
6149                         report("Nothing to revert");
6150                 } else {
6151                         report("Cannot revert changes to multiple files");
6152                 }
6154         } else if (prompt_yesno("Are you sure you want to revert changes?")) {
6155                 char mode[10] = "100644";
6156                 const char *reset_argv[] = {
6157                         "git", "update-index", "--cacheinfo", mode,
6158                                 status->old.rev, status->old.name, NULL
6159                 };
6160                 const char *checkout_argv[] = {
6161                         "git", "checkout", "--", status->old.name, NULL
6162                 };
6164                 if (status->status == 'U') {
6165                         string_format(mode, "%5o", status->old.mode);
6167                         if (status->old.mode == 0 && status->new.mode == 0) {
6168                                 reset_argv[2] = "--force-remove";
6169                                 reset_argv[3] = status->old.name;
6170                                 reset_argv[4] = NULL;
6171                         }
6173                         if (!io_run_fg(reset_argv, opt_cdup))
6174                                 return FALSE;
6175                         if (status->old.mode == 0 && status->new.mode == 0)
6176                                 return TRUE;
6177                 }
6179                 return io_run_fg(checkout_argv, opt_cdup);
6180         }
6182         return FALSE;
6185 static enum request
6186 status_request(struct view *view, enum request request, struct line *line)
6188         struct status *status = line->data;
6190         switch (request) {
6191         case REQ_STATUS_UPDATE:
6192                 if (!status_update(view))
6193                         return REQ_NONE;
6194                 break;
6196         case REQ_STATUS_REVERT:
6197                 if (!status_revert(status, line->type, status_has_none(view, line)))
6198                         return REQ_NONE;
6199                 break;
6201         case REQ_STATUS_MERGE:
6202                 if (!status || status->status != 'U') {
6203                         report("Merging only possible for files with unmerged status ('U').");
6204                         return REQ_NONE;
6205                 }
6206                 open_mergetool(status->new.name);
6207                 break;
6209         case REQ_EDIT:
6210                 if (!status)
6211                         return request;
6212                 if (status->status == 'D') {
6213                         report("File has been deleted.");
6214                         return REQ_NONE;
6215                 }
6217                 open_editor(status->new.name);
6218                 break;
6220         case REQ_VIEW_BLAME:
6221                 if (status)
6222                         opt_ref[0] = 0;
6223                 return request;
6225         case REQ_ENTER:
6226                 /* After returning the status view has been split to
6227                  * show the stage view. No further reloading is
6228                  * necessary. */
6229                 return status_enter(view, line);
6231         case REQ_REFRESH:
6232                 /* Simply reload the view. */
6233                 break;
6235         default:
6236                 return request;
6237         }
6239         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
6241         return REQ_NONE;
6244 static void
6245 status_select(struct view *view, struct line *line)
6247         struct status *status = line->data;
6248         char file[SIZEOF_STR] = "all files";
6249         const char *text;
6250         const char *key;
6252         if (status && !string_format(file, "'%s'", status->new.name))
6253                 return;
6255         if (!status && line[1].type == LINE_STAT_NONE)
6256                 line++;
6258         switch (line->type) {
6259         case LINE_STAT_STAGED:
6260                 text = "Press %s to unstage %s for commit";
6261                 break;
6263         case LINE_STAT_UNSTAGED:
6264                 text = "Press %s to stage %s for commit";
6265                 break;
6267         case LINE_STAT_UNTRACKED:
6268                 text = "Press %s to stage %s for addition";
6269                 break;
6271         case LINE_STAT_HEAD:
6272         case LINE_STAT_NONE:
6273                 text = "Nothing to update";
6274                 break;
6276         default:
6277                 die("line type %d not handled in switch", line->type);
6278         }
6280         if (status && status->status == 'U') {
6281                 text = "Press %s to resolve conflict in %s";
6282                 key = get_key(KEYMAP_STATUS, REQ_STATUS_MERGE);
6284         } else {
6285                 key = get_key(KEYMAP_STATUS, REQ_STATUS_UPDATE);
6286         }
6288         string_format(view->ref, text, key, file);
6289         if (status)
6290                 string_copy(opt_file, status->new.name);
6293 static bool
6294 status_grep(struct view *view, struct line *line)
6296         struct status *status = line->data;
6298         if (status) {
6299                 const char buf[2] = { status->status, 0 };
6300                 const char *text[] = { status->new.name, buf, NULL };
6302                 return grep_text(view, text);
6303         }
6305         return FALSE;
6308 static struct view_ops status_ops = {
6309         "file",
6310         NULL,
6311         status_open,
6312         NULL,
6313         status_draw,
6314         status_request,
6315         status_grep,
6316         status_select,
6317 };
6320 static bool
6321 stage_diff_write(struct io *io, struct line *line, struct line *end)
6323         while (line < end) {
6324                 if (!io_write(io, line->data, strlen(line->data)) ||
6325                     !io_write(io, "\n", 1))
6326                         return FALSE;
6327                 line++;
6328                 if (line->type == LINE_DIFF_CHUNK ||
6329                     line->type == LINE_DIFF_HEADER)
6330                         break;
6331         }
6333         return TRUE;
6336 static struct line *
6337 stage_diff_find(struct view *view, struct line *line, enum line_type type)
6339         for (; view->line < line; line--)
6340                 if (line->type == type)
6341                         return line;
6343         return NULL;
6346 static bool
6347 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
6349         const char *apply_argv[SIZEOF_ARG] = {
6350                 "git", "apply", "--whitespace=nowarn", NULL
6351         };
6352         struct line *diff_hdr;
6353         struct io io;
6354         int argc = 3;
6356         diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
6357         if (!diff_hdr)
6358                 return FALSE;
6360         if (!revert)
6361                 apply_argv[argc++] = "--cached";
6362         if (revert || stage_line_type == LINE_STAT_STAGED)
6363                 apply_argv[argc++] = "-R";
6364         apply_argv[argc++] = "-";
6365         apply_argv[argc++] = NULL;
6366         if (!io_run(&io, IO_WR, opt_cdup, apply_argv))
6367                 return FALSE;
6369         if (!stage_diff_write(&io, diff_hdr, chunk) ||
6370             !stage_diff_write(&io, chunk, view->line + view->lines))
6371                 chunk = NULL;
6373         io_done(&io);
6374         io_run_bg(update_index_argv);
6376         return chunk ? TRUE : FALSE;
6379 static bool
6380 stage_update(struct view *view, struct line *line)
6382         struct line *chunk = NULL;
6384         if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
6385                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6387         if (chunk) {
6388                 if (!stage_apply_chunk(view, chunk, FALSE)) {
6389                         report("Failed to apply chunk");
6390                         return FALSE;
6391                 }
6393         } else if (!stage_status.status) {
6394                 view = VIEW(REQ_VIEW_STATUS);
6396                 for (line = view->line; line < view->line + view->lines; line++)
6397                         if (line->type == stage_line_type)
6398                                 break;
6400                 if (!status_update_files(view, line + 1)) {
6401                         report("Failed to update files");
6402                         return FALSE;
6403                 }
6405         } else if (!status_update_file(&stage_status, stage_line_type)) {
6406                 report("Failed to update file");
6407                 return FALSE;
6408         }
6410         return TRUE;
6413 static bool
6414 stage_revert(struct view *view, struct line *line)
6416         struct line *chunk = NULL;
6418         if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
6419                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6421         if (chunk) {
6422                 if (!prompt_yesno("Are you sure you want to revert changes?"))
6423                         return FALSE;
6425                 if (!stage_apply_chunk(view, chunk, TRUE)) {
6426                         report("Failed to revert chunk");
6427                         return FALSE;
6428                 }
6429                 return TRUE;
6431         } else {
6432                 return status_revert(stage_status.status ? &stage_status : NULL,
6433                                      stage_line_type, FALSE);
6434         }
6438 static void
6439 stage_next(struct view *view, struct line *line)
6441         int i;
6443         if (!stage_chunks) {
6444                 for (line = view->line; line < view->line + view->lines; line++) {
6445                         if (line->type != LINE_DIFF_CHUNK)
6446                                 continue;
6448                         if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
6449                                 report("Allocation failure");
6450                                 return;
6451                         }
6453                         stage_chunk[stage_chunks++] = line - view->line;
6454                 }
6455         }
6457         for (i = 0; i < stage_chunks; i++) {
6458                 if (stage_chunk[i] > view->lineno) {
6459                         do_scroll_view(view, stage_chunk[i] - view->lineno);
6460                         report("Chunk %d of %d", i + 1, stage_chunks);
6461                         return;
6462                 }
6463         }
6465         report("No next chunk found");
6468 static enum request
6469 stage_request(struct view *view, enum request request, struct line *line)
6471         switch (request) {
6472         case REQ_STATUS_UPDATE:
6473                 if (!stage_update(view, line))
6474                         return REQ_NONE;
6475                 break;
6477         case REQ_STATUS_REVERT:
6478                 if (!stage_revert(view, line))
6479                         return REQ_NONE;
6480                 break;
6482         case REQ_STAGE_NEXT:
6483                 if (stage_line_type == LINE_STAT_UNTRACKED) {
6484                         report("File is untracked; press %s to add",
6485                                get_key(KEYMAP_STAGE, REQ_STATUS_UPDATE));
6486                         return REQ_NONE;
6487                 }
6488                 stage_next(view, line);
6489                 return REQ_NONE;
6491         case REQ_EDIT:
6492                 if (!stage_status.new.name[0])
6493                         return request;
6494                 if (stage_status.status == 'D') {
6495                         report("File has been deleted.");
6496                         return REQ_NONE;
6497                 }
6499                 open_editor(stage_status.new.name);
6500                 break;
6502         case REQ_REFRESH:
6503                 /* Reload everything ... */
6504                 break;
6506         case REQ_VIEW_BLAME:
6507                 if (stage_status.new.name[0]) {
6508                         string_copy(opt_file, stage_status.new.name);
6509                         opt_ref[0] = 0;
6510                 }
6511                 return request;
6513         case REQ_ENTER:
6514                 return pager_request(view, request, line);
6516         default:
6517                 return request;
6518         }
6520         VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
6521         open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
6523         /* Check whether the staged entry still exists, and close the
6524          * stage view if it doesn't. */
6525         if (!status_exists(&stage_status, stage_line_type)) {
6526                 status_restore(VIEW(REQ_VIEW_STATUS));
6527                 return REQ_VIEW_CLOSE;
6528         }
6530         if (stage_line_type == LINE_STAT_UNTRACKED) {
6531                 if (!suffixcmp(stage_status.new.name, -1, "/")) {
6532                         report("Cannot display a directory");
6533                         return REQ_NONE;
6534                 }
6536                 if (!prepare_update_file(view, stage_status.new.name)) {
6537                         report("Failed to open file: %s", strerror(errno));
6538                         return REQ_NONE;
6539                 }
6540         }
6541         open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
6543         return REQ_NONE;
6546 static struct view_ops stage_ops = {
6547         "line",
6548         NULL,
6549         NULL,
6550         pager_read,
6551         pager_draw,
6552         stage_request,
6553         pager_grep,
6554         pager_select,
6555 };
6558 /*
6559  * Revision graph
6560  */
6562 struct commit {
6563         char id[SIZEOF_REV];            /* SHA1 ID. */
6564         char title[128];                /* First line of the commit message. */
6565         const char *author;             /* Author of the commit. */
6566         struct time time;               /* Date from the author ident. */
6567         struct ref_list *refs;          /* Repository references. */
6568         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
6569         size_t graph_size;              /* The width of the graph array. */
6570         bool has_parents;               /* Rewritten --parents seen. */
6571 };
6573 /* Size of rev graph with no  "padding" columns */
6574 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6576 struct rev_graph {
6577         struct rev_graph *prev, *next, *parents;
6578         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
6579         size_t size;
6580         struct commit *commit;
6581         size_t pos;
6582         unsigned int boundary:1;
6583 };
6585 /* Parents of the commit being visualized. */
6586 static struct rev_graph graph_parents[4];
6588 /* The current stack of revisions on the graph. */
6589 static struct rev_graph graph_stacks[4] = {
6590         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
6591         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
6592         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
6593         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
6594 };
6596 static inline bool
6597 graph_parent_is_merge(struct rev_graph *graph)
6599         return graph->parents->size > 1;
6602 static inline void
6603 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
6605         struct commit *commit = graph->commit;
6607         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
6608                 commit->graph[commit->graph_size++] = symbol;
6611 static void
6612 clear_rev_graph(struct rev_graph *graph)
6614         graph->boundary = 0;
6615         graph->size = graph->pos = 0;
6616         graph->commit = NULL;
6617         memset(graph->parents, 0, sizeof(*graph->parents));
6620 static void
6621 done_rev_graph(struct rev_graph *graph)
6623         if (graph_parent_is_merge(graph) &&
6624             graph->pos < graph->size - 1 &&
6625             graph->next->size == graph->size + graph->parents->size - 1) {
6626                 size_t i = graph->pos + graph->parents->size - 1;
6628                 graph->commit->graph_size = i * 2;
6629                 while (i < graph->next->size - 1) {
6630                         append_to_rev_graph(graph, ' ');
6631                         append_to_rev_graph(graph, '\\');
6632                         i++;
6633                 }
6634         }
6636         clear_rev_graph(graph);
6639 static void
6640 push_rev_graph(struct rev_graph *graph, const char *parent)
6642         int i;
6644         /* "Collapse" duplicate parents lines.
6645          *
6646          * FIXME: This needs to also update update the drawn graph but
6647          * for now it just serves as a method for pruning graph lines. */
6648         for (i = 0; i < graph->size; i++)
6649                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
6650                         return;
6652         if (graph->size < SIZEOF_REVITEMS) {
6653                 string_copy_rev(graph->rev[graph->size++], parent);
6654         }
6657 static chtype
6658 get_rev_graph_symbol(struct rev_graph *graph)
6660         chtype symbol;
6662         if (graph->boundary)
6663                 symbol = REVGRAPH_BOUND;
6664         else if (graph->parents->size == 0)
6665                 symbol = REVGRAPH_INIT;
6666         else if (graph_parent_is_merge(graph))
6667                 symbol = REVGRAPH_MERGE;
6668         else if (graph->pos >= graph->size)
6669                 symbol = REVGRAPH_BRANCH;
6670         else
6671                 symbol = REVGRAPH_COMMIT;
6673         return symbol;
6676 static void
6677 draw_rev_graph(struct rev_graph *graph)
6679         struct rev_filler {
6680                 chtype separator, line;
6681         };
6682         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
6683         static struct rev_filler fillers[] = {
6684                 { ' ',  '|' },
6685                 { '`',  '.' },
6686                 { '\'', ' ' },
6687                 { '/',  ' ' },
6688         };
6689         chtype symbol = get_rev_graph_symbol(graph);
6690         struct rev_filler *filler;
6691         size_t i;
6693         fillers[DEFAULT].line = opt_line_graphics ? ACS_VLINE : '|';
6694         filler = &fillers[DEFAULT];
6696         for (i = 0; i < graph->pos; i++) {
6697                 append_to_rev_graph(graph, filler->line);
6698                 if (graph_parent_is_merge(graph->prev) &&
6699                     graph->prev->pos == i)
6700                         filler = &fillers[RSHARP];
6702                 append_to_rev_graph(graph, filler->separator);
6703         }
6705         /* Place the symbol for this revision. */
6706         append_to_rev_graph(graph, symbol);
6708         if (graph->prev->size > graph->size)
6709                 filler = &fillers[RDIAG];
6710         else
6711                 filler = &fillers[DEFAULT];
6713         i++;
6715         for (; i < graph->size; i++) {
6716                 append_to_rev_graph(graph, filler->separator);
6717                 append_to_rev_graph(graph, filler->line);
6718                 if (graph_parent_is_merge(graph->prev) &&
6719                     i < graph->prev->pos + graph->parents->size)
6720                         filler = &fillers[RSHARP];
6721                 if (graph->prev->size > graph->size)
6722                         filler = &fillers[LDIAG];
6723         }
6725         if (graph->prev->size > graph->size) {
6726                 append_to_rev_graph(graph, filler->separator);
6727                 if (filler->line != ' ')
6728                         append_to_rev_graph(graph, filler->line);
6729         }
6732 /* Prepare the next rev graph */
6733 static void
6734 prepare_rev_graph(struct rev_graph *graph)
6736         size_t i;
6738         /* First, traverse all lines of revisions up to the active one. */
6739         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6740                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6741                         break;
6743                 push_rev_graph(graph->next, graph->rev[graph->pos]);
6744         }
6746         /* Interleave the new revision parent(s). */
6747         for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6748                 push_rev_graph(graph->next, graph->parents->rev[i]);
6750         /* Lastly, put any remaining revisions. */
6751         for (i = graph->pos + 1; i < graph->size; i++)
6752                 push_rev_graph(graph->next, graph->rev[i]);
6755 static void
6756 update_rev_graph(struct view *view, struct rev_graph *graph)
6758         /* If this is the finalizing update ... */
6759         if (graph->commit)
6760                 prepare_rev_graph(graph);
6762         /* Graph visualization needs a one rev look-ahead,
6763          * so the first update doesn't visualize anything. */
6764         if (!graph->prev->commit)
6765                 return;
6767         if (view->lines > 2)
6768                 view->line[view->lines - 3].dirty = 1;
6769         if (view->lines > 1)
6770                 view->line[view->lines - 2].dirty = 1;
6771         draw_rev_graph(graph->prev);
6772         done_rev_graph(graph->prev->prev);
6776 /*
6777  * Main view backend
6778  */
6780 static const char *main_argv[SIZEOF_ARG] = {
6781         "git", "log", "--no-color", "--pretty=raw", "--parents",
6782                 "--topo-order", "%(diffargs)", "%(revargs)",
6783                 "--", "%(fileargs)", NULL
6784 };
6786 static bool
6787 main_draw(struct view *view, struct line *line, unsigned int lineno)
6789         struct commit *commit = line->data;
6791         if (!commit->author)
6792                 return FALSE;
6794         if (opt_date && draw_date(view, &commit->time))
6795                 return TRUE;
6797         if (opt_author && draw_author(view, commit->author))
6798                 return TRUE;
6800         if (opt_rev_graph && commit->graph_size &&
6801             draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6802                 return TRUE;
6804         if (opt_show_refs && commit->refs) {
6805                 size_t i;
6807                 for (i = 0; i < commit->refs->size; i++) {
6808                         struct ref *ref = commit->refs->refs[i];
6809                         enum line_type type;
6811                         if (ref->head)
6812                                 type = LINE_MAIN_HEAD;
6813                         else if (ref->ltag)
6814                                 type = LINE_MAIN_LOCAL_TAG;
6815                         else if (ref->tag)
6816                                 type = LINE_MAIN_TAG;
6817                         else if (ref->tracked)
6818                                 type = LINE_MAIN_TRACKED;
6819                         else if (ref->remote)
6820                                 type = LINE_MAIN_REMOTE;
6821                         else
6822                                 type = LINE_MAIN_REF;
6824                         if (draw_text(view, type, "[", TRUE) ||
6825                             draw_text(view, type, ref->name, TRUE) ||
6826                             draw_text(view, type, "]", TRUE))
6827                                 return TRUE;
6829                         if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6830                                 return TRUE;
6831                 }
6832         }
6834         draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6835         return TRUE;
6838 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6839 static bool
6840 main_read(struct view *view, char *line)
6842         static struct rev_graph *graph = graph_stacks;
6843         enum line_type type;
6844         struct commit *commit;
6846         if (!line) {
6847                 int i;
6849                 if (!view->lines && !view->prev)
6850                         die("No revisions match the given arguments.");
6851                 if (view->lines > 0) {
6852                         commit = view->line[view->lines - 1].data;
6853                         view->line[view->lines - 1].dirty = 1;
6854                         if (!commit->author) {
6855                                 view->lines--;
6856                                 free(commit);
6857                                 graph->commit = NULL;
6858                         }
6859                 }
6860                 update_rev_graph(view, graph);
6862                 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6863                         clear_rev_graph(&graph_stacks[i]);
6864                 return TRUE;
6865         }
6867         type = get_line_type(line);
6868         if (type == LINE_COMMIT) {
6869                 commit = calloc(1, sizeof(struct commit));
6870                 if (!commit)
6871                         return FALSE;
6873                 line += STRING_SIZE("commit ");
6874                 if (*line == '-') {
6875                         graph->boundary = 1;
6876                         line++;
6877                 }
6879                 string_copy_rev(commit->id, line);
6880                 commit->refs = get_ref_list(commit->id);
6881                 graph->commit = commit;
6882                 add_line_data(view, commit, LINE_MAIN_COMMIT);
6884                 while ((line = strchr(line, ' '))) {
6885                         line++;
6886                         push_rev_graph(graph->parents, line);
6887                         commit->has_parents = TRUE;
6888                 }
6889                 return TRUE;
6890         }
6892         if (!view->lines)
6893                 return TRUE;
6894         commit = view->line[view->lines - 1].data;
6896         switch (type) {
6897         case LINE_PARENT:
6898                 if (commit->has_parents)
6899                         break;
6900                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6901                 break;
6903         case LINE_AUTHOR:
6904                 parse_author_line(line + STRING_SIZE("author "),
6905                                   &commit->author, &commit->time);
6906                 update_rev_graph(view, graph);
6907                 graph = graph->next;
6908                 break;
6910         default:
6911                 /* Fill in the commit title if it has not already been set. */
6912                 if (commit->title[0])
6913                         break;
6915                 /* Require titles to start with a non-space character at the
6916                  * offset used by git log. */
6917                 if (strncmp(line, "    ", 4))
6918                         break;
6919                 line += 4;
6920                 /* Well, if the title starts with a whitespace character,
6921                  * try to be forgiving.  Otherwise we end up with no title. */
6922                 while (isspace(*line))
6923                         line++;
6924                 if (*line == '\0')
6925                         break;
6926                 /* FIXME: More graceful handling of titles; append "..." to
6927                  * shortened titles, etc. */
6929                 string_expand(commit->title, sizeof(commit->title), line, 1);
6930                 view->line[view->lines - 1].dirty = 1;
6931         }
6933         return TRUE;
6936 static enum request
6937 main_request(struct view *view, enum request request, struct line *line)
6939         enum open_flags flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
6941         switch (request) {
6942         case REQ_ENTER:
6943                 open_view(view, REQ_VIEW_DIFF, flags);
6944                 break;
6945         case REQ_REFRESH:
6946                 load_refs();
6947                 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6948                 break;
6949         default:
6950                 return request;
6951         }
6953         return REQ_NONE;
6956 static bool
6957 grep_refs(struct ref_list *list, regex_t *regex)
6959         regmatch_t pmatch;
6960         size_t i;
6962         if (!opt_show_refs || !list)
6963                 return FALSE;
6965         for (i = 0; i < list->size; i++) {
6966                 if (regexec(regex, list->refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6967                         return TRUE;
6968         }
6970         return FALSE;
6973 static bool
6974 main_grep(struct view *view, struct line *line)
6976         struct commit *commit = line->data;
6977         const char *text[] = {
6978                 commit->title,
6979                 opt_author ? commit->author : "",
6980                 mkdate(&commit->time, opt_date),
6981                 NULL
6982         };
6984         return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6987 static void
6988 main_select(struct view *view, struct line *line)
6990         struct commit *commit = line->data;
6992         string_copy_rev(view->ref, commit->id);
6993         string_copy_rev(ref_commit, view->ref);
6996 static struct view_ops main_ops = {
6997         "commit",
6998         main_argv,
6999         NULL,
7000         main_read,
7001         main_draw,
7002         main_request,
7003         main_grep,
7004         main_select,
7005 };
7008 /*
7009  * Status management
7010  */
7012 /* Whether or not the curses interface has been initialized. */
7013 static bool cursed = FALSE;
7015 /* Terminal hacks and workarounds. */
7016 static bool use_scroll_redrawwin;
7017 static bool use_scroll_status_wclear;
7019 /* The status window is used for polling keystrokes. */
7020 static WINDOW *status_win;
7022 /* Reading from the prompt? */
7023 static bool input_mode = FALSE;
7025 static bool status_empty = FALSE;
7027 /* Update status and title window. */
7028 static void
7029 report(const char *msg, ...)
7031         struct view *view = display[current_view];
7033         if (input_mode)
7034                 return;
7036         if (!view) {
7037                 char buf[SIZEOF_STR];
7038                 va_list args;
7040                 va_start(args, msg);
7041                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
7042                         buf[sizeof(buf) - 1] = 0;
7043                         buf[sizeof(buf) - 2] = '.';
7044                         buf[sizeof(buf) - 3] = '.';
7045                         buf[sizeof(buf) - 4] = '.';
7046                 }
7047                 va_end(args);
7048                 die("%s", buf);
7049         }
7051         if (!status_empty || *msg) {
7052                 va_list args;
7054                 va_start(args, msg);
7056                 wmove(status_win, 0, 0);
7057                 if (view->has_scrolled && use_scroll_status_wclear)
7058                         wclear(status_win);
7059                 if (*msg) {
7060                         vwprintw(status_win, msg, args);
7061                         status_empty = FALSE;
7062                 } else {
7063                         status_empty = TRUE;
7064                 }
7065                 wclrtoeol(status_win);
7066                 wnoutrefresh(status_win);
7068                 va_end(args);
7069         }
7071         update_view_title(view);
7074 static void
7075 init_display(void)
7077         const char *term;
7078         int x, y;
7080         /* Initialize the curses library */
7081         if (isatty(STDIN_FILENO)) {
7082                 cursed = !!initscr();
7083                 opt_tty = stdin;
7084         } else {
7085                 /* Leave stdin and stdout alone when acting as a pager. */
7086                 opt_tty = fopen("/dev/tty", "r+");
7087                 if (!opt_tty)
7088                         die("Failed to open /dev/tty");
7089                 cursed = !!newterm(NULL, opt_tty, opt_tty);
7090         }
7092         if (!cursed)
7093                 die("Failed to initialize curses");
7095         nonl();         /* Disable conversion and detect newlines from input. */
7096         cbreak();       /* Take input chars one at a time, no wait for \n */
7097         noecho();       /* Don't echo input */
7098         leaveok(stdscr, FALSE);
7100         if (has_colors())
7101                 init_colors();
7103         getmaxyx(stdscr, y, x);
7104         status_win = newwin(1, 0, y - 1, 0);
7105         if (!status_win)
7106                 die("Failed to create status window");
7108         /* Enable keyboard mapping */
7109         keypad(status_win, TRUE);
7110         wbkgdset(status_win, get_line_attr(LINE_STATUS));
7112         TABSIZE = opt_tab_size;
7114         term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
7115         if (term && !strcmp(term, "gnome-terminal")) {
7116                 /* In the gnome-terminal-emulator, the message from
7117                  * scrolling up one line when impossible followed by
7118                  * scrolling down one line causes corruption of the
7119                  * status line. This is fixed by calling wclear. */
7120                 use_scroll_status_wclear = TRUE;
7121                 use_scroll_redrawwin = FALSE;
7123         } else if (term && !strcmp(term, "xrvt-xpm")) {
7124                 /* No problems with full optimizations in xrvt-(unicode)
7125                  * and aterm. */
7126                 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
7128         } else {
7129                 /* When scrolling in (u)xterm the last line in the
7130                  * scrolling direction will update slowly. */
7131                 use_scroll_redrawwin = TRUE;
7132                 use_scroll_status_wclear = FALSE;
7133         }
7136 static int
7137 get_input(int prompt_position)
7139         struct view *view;
7140         int i, key, cursor_y, cursor_x;
7142         if (prompt_position)
7143                 input_mode = TRUE;
7145         while (TRUE) {
7146                 bool loading = FALSE;
7148                 foreach_view (view, i) {
7149                         update_view(view);
7150                         if (view_is_displayed(view) && view->has_scrolled &&
7151                             use_scroll_redrawwin)
7152                                 redrawwin(view->win);
7153                         view->has_scrolled = FALSE;
7154                         if (view->pipe)
7155                                 loading = TRUE;
7156                 }
7158                 /* Update the cursor position. */
7159                 if (prompt_position) {
7160                         getbegyx(status_win, cursor_y, cursor_x);
7161                         cursor_x = prompt_position;
7162                 } else {
7163                         view = display[current_view];
7164                         getbegyx(view->win, cursor_y, cursor_x);
7165                         cursor_x = view->width - 1;
7166                         cursor_y += view->lineno - view->offset;
7167                 }
7168                 setsyx(cursor_y, cursor_x);
7170                 /* Refresh, accept single keystroke of input */
7171                 doupdate();
7172                 nodelay(status_win, loading);
7173                 key = wgetch(status_win);
7175                 /* wgetch() with nodelay() enabled returns ERR when
7176                  * there's no input. */
7177                 if (key == ERR) {
7179                 } else if (key == KEY_RESIZE) {
7180                         int height, width;
7182                         getmaxyx(stdscr, height, width);
7184                         wresize(status_win, 1, width);
7185                         mvwin(status_win, height - 1, 0);
7186                         wnoutrefresh(status_win);
7187                         resize_display();
7188                         redraw_display(TRUE);
7190                 } else {
7191                         input_mode = FALSE;
7192                         return key;
7193                 }
7194         }
7197 static char *
7198 prompt_input(const char *prompt, input_handler handler, void *data)
7200         enum input_status status = INPUT_OK;
7201         static char buf[SIZEOF_STR];
7202         size_t pos = 0;
7204         buf[pos] = 0;
7206         while (status == INPUT_OK || status == INPUT_SKIP) {
7207                 int key;
7209                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
7210                 wclrtoeol(status_win);
7212                 key = get_input(pos + 1);
7213                 switch (key) {
7214                 case KEY_RETURN:
7215                 case KEY_ENTER:
7216                 case '\n':
7217                         status = pos ? INPUT_STOP : INPUT_CANCEL;
7218                         break;
7220                 case KEY_BACKSPACE:
7221                         if (pos > 0)
7222                                 buf[--pos] = 0;
7223                         else
7224                                 status = INPUT_CANCEL;
7225                         break;
7227                 case KEY_ESC:
7228                         status = INPUT_CANCEL;
7229                         break;
7231                 default:
7232                         if (pos >= sizeof(buf)) {
7233                                 report("Input string too long");
7234                                 return NULL;
7235                         }
7237                         status = handler(data, buf, key);
7238                         if (status == INPUT_OK)
7239                                 buf[pos++] = (char) key;
7240                 }
7241         }
7243         /* Clear the status window */
7244         status_empty = FALSE;
7245         report("");
7247         if (status == INPUT_CANCEL)
7248                 return NULL;
7250         buf[pos++] = 0;
7252         return buf;
7255 static enum input_status
7256 prompt_yesno_handler(void *data, char *buf, int c)
7258         if (c == 'y' || c == 'Y')
7259                 return INPUT_STOP;
7260         if (c == 'n' || c == 'N')
7261                 return INPUT_CANCEL;
7262         return INPUT_SKIP;
7265 static bool
7266 prompt_yesno(const char *prompt)
7268         char prompt2[SIZEOF_STR];
7270         if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
7271                 return FALSE;
7273         return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
7276 static enum input_status
7277 read_prompt_handler(void *data, char *buf, int c)
7279         return isprint(c) ? INPUT_OK : INPUT_SKIP;
7282 static char *
7283 read_prompt(const char *prompt)
7285         return prompt_input(prompt, read_prompt_handler, NULL);
7288 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected)
7290         enum input_status status = INPUT_OK;
7291         int size = 0;
7293         while (items[size].text)
7294                 size++;
7296         while (status == INPUT_OK) {
7297                 const struct menu_item *item = &items[*selected];
7298                 int key;
7299                 int i;
7301                 mvwprintw(status_win, 0, 0, "%s (%d of %d) ",
7302                           prompt, *selected + 1, size);
7303                 if (item->hotkey)
7304                         wprintw(status_win, "[%c] ", (char) item->hotkey);
7305                 wprintw(status_win, "%s", item->text);
7306                 wclrtoeol(status_win);
7308                 key = get_input(COLS - 1);
7309                 switch (key) {
7310                 case KEY_RETURN:
7311                 case KEY_ENTER:
7312                 case '\n':
7313                         status = INPUT_STOP;
7314                         break;
7316                 case KEY_LEFT:
7317                 case KEY_UP:
7318                         *selected = *selected - 1;
7319                         if (*selected < 0)
7320                                 *selected = size - 1;
7321                         break;
7323                 case KEY_RIGHT:
7324                 case KEY_DOWN:
7325                         *selected = (*selected + 1) % size;
7326                         break;
7328                 case KEY_ESC:
7329                         status = INPUT_CANCEL;
7330                         break;
7332                 default:
7333                         for (i = 0; items[i].text; i++)
7334                                 if (items[i].hotkey == key) {
7335                                         *selected = i;
7336                                         status = INPUT_STOP;
7337                                         break;
7338                                 }
7339                 }
7340         }
7342         /* Clear the status window */
7343         status_empty = FALSE;
7344         report("");
7346         return status != INPUT_CANCEL;
7349 /*
7350  * Repository properties
7351  */
7353 static struct ref **refs = NULL;
7354 static size_t refs_size = 0;
7355 static struct ref *refs_head = NULL;
7357 static struct ref_list **ref_lists = NULL;
7358 static size_t ref_lists_size = 0;
7360 DEFINE_ALLOCATOR(realloc_refs, struct ref *, 256)
7361 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
7362 DEFINE_ALLOCATOR(realloc_ref_lists, struct ref_list *, 8)
7364 static int
7365 compare_refs(const void *ref1_, const void *ref2_)
7367         const struct ref *ref1 = *(const struct ref **)ref1_;
7368         const struct ref *ref2 = *(const struct ref **)ref2_;
7370         if (ref1->tag != ref2->tag)
7371                 return ref2->tag - ref1->tag;
7372         if (ref1->ltag != ref2->ltag)
7373                 return ref2->ltag - ref2->ltag;
7374         if (ref1->head != ref2->head)
7375                 return ref2->head - ref1->head;
7376         if (ref1->tracked != ref2->tracked)
7377                 return ref2->tracked - ref1->tracked;
7378         if (ref1->remote != ref2->remote)
7379                 return ref2->remote - ref1->remote;
7380         return strcmp(ref1->name, ref2->name);
7383 static void
7384 foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data)
7386         size_t i;
7388         for (i = 0; i < refs_size; i++)
7389                 if (!visitor(data, refs[i]))
7390                         break;
7393 static struct ref *
7394 get_ref_head()
7396         return refs_head;
7399 static struct ref_list *
7400 get_ref_list(const char *id)
7402         struct ref_list *list;
7403         size_t i;
7405         for (i = 0; i < ref_lists_size; i++)
7406                 if (!strcmp(id, ref_lists[i]->id))
7407                         return ref_lists[i];
7409         if (!realloc_ref_lists(&ref_lists, ref_lists_size, 1))
7410                 return NULL;
7411         list = calloc(1, sizeof(*list));
7412         if (!list)
7413                 return NULL;
7415         for (i = 0; i < refs_size; i++) {
7416                 if (!strcmp(id, refs[i]->id) &&
7417                     realloc_refs_list(&list->refs, list->size, 1))
7418                         list->refs[list->size++] = refs[i];
7419         }
7421         if (!list->refs) {
7422                 free(list);
7423                 return NULL;
7424         }
7426         qsort(list->refs, list->size, sizeof(*list->refs), compare_refs);
7427         ref_lists[ref_lists_size++] = list;
7428         return list;
7431 static int
7432 read_ref(char *id, size_t idlen, char *name, size_t namelen)
7434         struct ref *ref = NULL;
7435         bool tag = FALSE;
7436         bool ltag = FALSE;
7437         bool remote = FALSE;
7438         bool tracked = FALSE;
7439         bool head = FALSE;
7440         int from = 0, to = refs_size - 1;
7442         if (!prefixcmp(name, "refs/tags/")) {
7443                 if (!suffixcmp(name, namelen, "^{}")) {
7444                         namelen -= 3;
7445                         name[namelen] = 0;
7446                 } else {
7447                         ltag = TRUE;
7448                 }
7450                 tag = TRUE;
7451                 namelen -= STRING_SIZE("refs/tags/");
7452                 name    += STRING_SIZE("refs/tags/");
7454         } else if (!prefixcmp(name, "refs/remotes/")) {
7455                 remote = TRUE;
7456                 namelen -= STRING_SIZE("refs/remotes/");
7457                 name    += STRING_SIZE("refs/remotes/");
7458                 tracked  = !strcmp(opt_remote, name);
7460         } else if (!prefixcmp(name, "refs/heads/")) {
7461                 namelen -= STRING_SIZE("refs/heads/");
7462                 name    += STRING_SIZE("refs/heads/");
7463                 if (!strncmp(opt_head, name, namelen))
7464                         return OK;
7466         } else if (!strcmp(name, "HEAD")) {
7467                 head     = TRUE;
7468                 if (*opt_head) {
7469                         namelen  = strlen(opt_head);
7470                         name     = opt_head;
7471                 }
7472         }
7474         /* If we are reloading or it's an annotated tag, replace the
7475          * previous SHA1 with the resolved commit id; relies on the fact
7476          * git-ls-remote lists the commit id of an annotated tag right
7477          * before the commit id it points to. */
7478         while (from <= to) {
7479                 size_t pos = (to + from) / 2;
7480                 int cmp = strcmp(name, refs[pos]->name);
7482                 if (!cmp) {
7483                         ref = refs[pos];
7484                         break;
7485                 }
7487                 if (cmp < 0)
7488                         to = pos - 1;
7489                 else
7490                         from = pos + 1;
7491         }
7493         if (!ref) {
7494                 if (!realloc_refs(&refs, refs_size, 1))
7495                         return ERR;
7496                 ref = calloc(1, sizeof(*ref) + namelen);
7497                 if (!ref)
7498                         return ERR;
7499                 memmove(refs + from + 1, refs + from,
7500                         (refs_size - from) * sizeof(*refs));
7501                 refs[from] = ref;
7502                 strncpy(ref->name, name, namelen);
7503                 refs_size++;
7504         }
7506         ref->head = head;
7507         ref->tag = tag;
7508         ref->ltag = ltag;
7509         ref->remote = remote;
7510         ref->tracked = tracked;
7511         string_copy_rev(ref->id, id);
7513         if (head)
7514                 refs_head = ref;
7515         return OK;
7518 static int
7519 load_refs(void)
7521         const char *head_argv[] = {
7522                 "git", "symbolic-ref", "HEAD", NULL
7523         };
7524         static const char *ls_remote_argv[SIZEOF_ARG] = {
7525                 "git", "ls-remote", opt_git_dir, NULL
7526         };
7527         static bool init = FALSE;
7528         size_t i;
7530         if (!init) {
7531                 if (!argv_from_env(ls_remote_argv, "TIG_LS_REMOTE"))
7532                         die("TIG_LS_REMOTE contains too many arguments");
7533                 init = TRUE;
7534         }
7536         if (!*opt_git_dir)
7537                 return OK;
7539         if (io_run_buf(head_argv, opt_head, sizeof(opt_head)) &&
7540             !prefixcmp(opt_head, "refs/heads/")) {
7541                 char *offset = opt_head + STRING_SIZE("refs/heads/");
7543                 memmove(opt_head, offset, strlen(offset) + 1);
7544         }
7546         refs_head = NULL;
7547         for (i = 0; i < refs_size; i++)
7548                 refs[i]->id[0] = 0;
7550         if (io_run_load(ls_remote_argv, "\t", read_ref) == ERR)
7551                 return ERR;
7553         /* Update the ref lists to reflect changes. */
7554         for (i = 0; i < ref_lists_size; i++) {
7555                 struct ref_list *list = ref_lists[i];
7556                 size_t old, new;
7558                 for (old = new = 0; old < list->size; old++)
7559                         if (!strcmp(list->id, list->refs[old]->id))
7560                                 list->refs[new++] = list->refs[old];
7561                 list->size = new;
7562         }
7564         return OK;
7567 static void
7568 set_remote_branch(const char *name, const char *value, size_t valuelen)
7570         if (!strcmp(name, ".remote")) {
7571                 string_ncopy(opt_remote, value, valuelen);
7573         } else if (*opt_remote && !strcmp(name, ".merge")) {
7574                 size_t from = strlen(opt_remote);
7576                 if (!prefixcmp(value, "refs/heads/"))
7577                         value += STRING_SIZE("refs/heads/");
7579                 if (!string_format_from(opt_remote, &from, "/%s", value))
7580                         opt_remote[0] = 0;
7581         }
7584 static void
7585 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
7587         const char *argv[SIZEOF_ARG] = { name, "=" };
7588         int argc = 1 + (cmd == option_set_command);
7589         int error = ERR;
7591         if (!argv_from_string(argv, &argc, value))
7592                 config_msg = "Too many option arguments";
7593         else
7594                 error = cmd(argc, argv);
7596         if (error == ERR)
7597                 warn("Option 'tig.%s': %s", name, config_msg);
7600 static bool
7601 set_environment_variable(const char *name, const char *value)
7603         size_t len = strlen(name) + 1 + strlen(value) + 1;
7604         char *env = malloc(len);
7606         if (env &&
7607             string_nformat(env, len, NULL, "%s=%s", name, value) &&
7608             putenv(env) == 0)
7609                 return TRUE;
7610         free(env);
7611         return FALSE;
7614 static void
7615 set_work_tree(const char *value)
7617         char cwd[SIZEOF_STR];
7619         if (!getcwd(cwd, sizeof(cwd)))
7620                 die("Failed to get cwd path: %s", strerror(errno));
7621         if (chdir(opt_git_dir) < 0)
7622                 die("Failed to chdir(%s): %s", strerror(errno));
7623         if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7624                 die("Failed to get git path: %s", strerror(errno));
7625         if (chdir(cwd) < 0)
7626                 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7627         if (chdir(value) < 0)
7628                 die("Failed to chdir(%s): %s", value, strerror(errno));
7629         if (!getcwd(cwd, sizeof(cwd)))
7630                 die("Failed to get cwd path: %s", strerror(errno));
7631         if (!set_environment_variable("GIT_WORK_TREE", cwd))
7632                 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7633         if (!set_environment_variable("GIT_DIR", opt_git_dir))
7634                 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7635         opt_is_inside_work_tree = TRUE;
7638 static int
7639 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
7641         if (!strcmp(name, "i18n.commitencoding"))
7642                 string_ncopy(opt_encoding, value, valuelen);
7644         else if (!strcmp(name, "core.editor"))
7645                 string_ncopy(opt_editor, value, valuelen);
7647         else if (!strcmp(name, "core.worktree"))
7648                 set_work_tree(value);
7650         else if (!prefixcmp(name, "tig.color."))
7651                 set_repo_config_option(name + 10, value, option_color_command);
7653         else if (!prefixcmp(name, "tig.bind."))
7654                 set_repo_config_option(name + 9, value, option_bind_command);
7656         else if (!prefixcmp(name, "tig."))
7657                 set_repo_config_option(name + 4, value, option_set_command);
7659         else if (*opt_head && !prefixcmp(name, "branch.") &&
7660                  !strncmp(name + 7, opt_head, strlen(opt_head)))
7661                 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7663         return OK;
7666 static int
7667 load_git_config(void)
7669         const char *config_list_argv[] = { "git", "config", "--list", NULL };
7671         return io_run_load(config_list_argv, "=", read_repo_config_option);
7674 static int
7675 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
7677         if (!opt_git_dir[0]) {
7678                 string_ncopy(opt_git_dir, name, namelen);
7680         } else if (opt_is_inside_work_tree == -1) {
7681                 /* This can be 3 different values depending on the
7682                  * version of git being used. If git-rev-parse does not
7683                  * understand --is-inside-work-tree it will simply echo
7684                  * the option else either "true" or "false" is printed.
7685                  * Default to true for the unknown case. */
7686                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7688         } else if (*name == '.') {
7689                 string_ncopy(opt_cdup, name, namelen);
7691         } else {
7692                 string_ncopy(opt_prefix, name, namelen);
7693         }
7695         return OK;
7698 static int
7699 load_repo_info(void)
7701         const char *rev_parse_argv[] = {
7702                 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7703                         "--show-cdup", "--show-prefix", NULL
7704         };
7706         return io_run_load(rev_parse_argv, "=", read_repo_info);
7710 /*
7711  * Main
7712  */
7714 static const char usage[] =
7715 "tig " TIG_VERSION " (" __DATE__ ")\n"
7716 "\n"
7717 "Usage: tig        [options] [revs] [--] [paths]\n"
7718 "   or: tig show   [options] [revs] [--] [paths]\n"
7719 "   or: tig blame  [rev] path\n"
7720 "   or: tig status\n"
7721 "   or: tig <      [git command output]\n"
7722 "\n"
7723 "Options:\n"
7724 "  -v, --version   Show version and exit\n"
7725 "  -h, --help      Show help message and exit";
7727 static void __NORETURN
7728 quit(int sig)
7730         /* XXX: Restore tty modes and let the OS cleanup the rest! */
7731         if (cursed)
7732                 endwin();
7733         exit(0);
7736 static void __NORETURN
7737 die(const char *err, ...)
7739         va_list args;
7741         endwin();
7743         va_start(args, err);
7744         fputs("tig: ", stderr);
7745         vfprintf(stderr, err, args);
7746         fputs("\n", stderr);
7747         va_end(args);
7749         exit(1);
7752 static void
7753 warn(const char *msg, ...)
7755         va_list args;
7757         va_start(args, msg);
7758         fputs("tig warning: ", stderr);
7759         vfprintf(stderr, msg, args);
7760         fputs("\n", stderr);
7761         va_end(args);
7764 static const char ***filter_args;
7766 static int
7767 read_filter_args(char *name, size_t namelen, char *value, size_t valuelen)
7769         return argv_append(filter_args, name) ? OK : ERR;
7772 static void
7773 filter_rev_parse(const char ***args, const char *arg1, const char *arg2, const char *argv[])
7775         const char *rev_parse_argv[SIZEOF_ARG] = { "git", "rev-parse", arg1, arg2 };
7776         const char **all_argv = NULL;
7778         filter_args = args;
7779         if (!argv_append_array(&all_argv, rev_parse_argv) ||
7780             !argv_append_array(&all_argv, argv) ||
7781             !io_run_load(all_argv, "\n", read_filter_args) == ERR)
7782                 die("Failed to split arguments");
7783         argv_free(all_argv);
7784         free(all_argv);
7787 static void
7788 filter_options(const char *argv[])
7790         filter_rev_parse(&opt_file_args, "--no-revs", "--no-flags", argv);
7791         filter_rev_parse(&opt_diff_args, "--no-revs", "--flags", argv);
7792         filter_rev_parse(&opt_rev_args, "--symbolic", "--revs-only", argv);
7795 static enum request
7796 parse_options(int argc, const char *argv[])
7798         enum request request = REQ_VIEW_MAIN;
7799         const char *subcommand;
7800         bool seen_dashdash = FALSE;
7801         const char **filter_argv = NULL;
7802         int i;
7804         if (!isatty(STDIN_FILENO))
7805                 return REQ_VIEW_PAGER;
7807         if (argc <= 1)
7808                 return REQ_VIEW_MAIN;
7810         subcommand = argv[1];
7811         if (!strcmp(subcommand, "status")) {
7812                 if (argc > 2)
7813                         warn("ignoring arguments after `%s'", subcommand);
7814                 return REQ_VIEW_STATUS;
7816         } else if (!strcmp(subcommand, "blame")) {
7817                 if (argc <= 2 || argc > 4)
7818                         die("invalid number of options to blame\n\n%s", usage);
7820                 i = 2;
7821                 if (argc == 4) {
7822                         string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7823                         i++;
7824                 }
7826                 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7827                 return REQ_VIEW_BLAME;
7829         } else if (!strcmp(subcommand, "show")) {
7830                 request = REQ_VIEW_DIFF;
7832         } else {
7833                 subcommand = NULL;
7834         }
7836         for (i = 1 + !!subcommand; i < argc; i++) {
7837                 const char *opt = argv[i];
7839                 if (seen_dashdash) {
7840                         argv_append(&opt_file_args, opt);
7841                         continue;
7843                 } else if (!strcmp(opt, "--")) {
7844                         seen_dashdash = TRUE;
7845                         continue;
7847                 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7848                         printf("tig version %s\n", TIG_VERSION);
7849                         quit(0);
7851                 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7852                         printf("%s\n", usage);
7853                         quit(0);
7855                 } else if (!strcmp(opt, "--all")) {
7856                         argv_append(&opt_rev_args, opt);
7857                         continue;
7858                 }
7860                 if (!argv_append(&filter_argv, opt))
7861                         die("command too long");
7862         }
7864         if (filter_argv)
7865                 filter_options(filter_argv);
7867         return request;
7870 int
7871 main(int argc, const char *argv[])
7873         const char *codeset = "UTF-8";
7874         enum request request = parse_options(argc, argv);
7875         struct view *view;
7876         size_t i;
7878         signal(SIGINT, quit);
7879         signal(SIGPIPE, SIG_IGN);
7881         if (setlocale(LC_ALL, "")) {
7882                 codeset = nl_langinfo(CODESET);
7883         }
7885         if (load_repo_info() == ERR)
7886                 die("Failed to load repo info.");
7888         if (load_options() == ERR)
7889                 die("Failed to load user config.");
7891         if (load_git_config() == ERR)
7892                 die("Failed to load repo config.");
7894         /* Require a git repository unless when running in pager mode. */
7895         if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7896                 die("Not a git repository");
7898         if (*opt_encoding && strcmp(codeset, "UTF-8")) {
7899                 opt_iconv_in = iconv_open("UTF-8", opt_encoding);
7900                 if (opt_iconv_in == ICONV_NONE)
7901                         die("Failed to initialize character set conversion");
7902         }
7904         if (codeset && strcmp(codeset, "UTF-8")) {
7905                 opt_iconv_out = iconv_open(codeset, "UTF-8");
7906                 if (opt_iconv_out == ICONV_NONE)
7907                         die("Failed to initialize character set conversion");
7908         }
7910         if (load_refs() == ERR)
7911                 die("Failed to load refs.");
7913         foreach_view (view, i) {
7914                 if (getenv(view->cmd_env))
7915                         warn("Use of the %s environment variable is deprecated,"
7916                              " use options or TIG_DIFF_ARGS instead",
7917                              view->cmd_env);
7918                 if (!argv_from_env(view->ops->argv, view->cmd_env))
7919                         die("Too many arguments in the `%s` environment variable",
7920                             view->cmd_env);
7921         }
7923         init_display();
7925         while (view_driver(display[current_view], request)) {
7926                 int key = get_input(0);
7928                 view = display[current_view];
7929                 request = get_keybinding(view->keymap, key);
7931                 /* Some low-level request handling. This keeps access to
7932                  * status_win restricted. */
7933                 switch (request) {
7934                 case REQ_NONE:
7935                         report("Unknown key, press %s for help",
7936                                get_key(view->keymap, REQ_VIEW_HELP));
7937                         break;
7938                 case REQ_PROMPT:
7939                 {
7940                         char *cmd = read_prompt(":");
7942                         if (cmd && isdigit(*cmd)) {
7943                                 int lineno = view->lineno + 1;
7945                                 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7946                                         select_view_line(view, lineno - 1);
7947                                         report("");
7948                                 } else {
7949                                         report("Unable to parse '%s' as a line number", cmd);
7950                                 }
7952                         } else if (cmd) {
7953                                 struct view *next = VIEW(REQ_VIEW_PAGER);
7954                                 const char *argv[SIZEOF_ARG] = { "git" };
7955                                 int argc = 1;
7957                                 /* When running random commands, initially show the
7958                                  * command in the title. However, it maybe later be
7959                                  * overwritten if a commit line is selected. */
7960                                 string_ncopy(next->ref, cmd, strlen(cmd));
7962                                 if (!argv_from_string(argv, &argc, cmd)) {
7963                                         report("Too many arguments");
7964                                 } else if (!prepare_update(next, argv, NULL)) {
7965                                         report("Failed to format command");
7966                                 } else {
7967                                         open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7968                                 }
7969                         }
7971                         request = REQ_NONE;
7972                         break;
7973                 }
7974                 case REQ_SEARCH:
7975                 case REQ_SEARCH_BACK:
7976                 {
7977                         const char *prompt = request == REQ_SEARCH ? "/" : "?";
7978                         char *search = read_prompt(prompt);
7980                         if (search)
7981                                 string_ncopy(opt_search, search, strlen(search));
7982                         else if (*opt_search)
7983                                 request = request == REQ_SEARCH ?
7984                                         REQ_FIND_NEXT :
7985                                         REQ_FIND_PREV;
7986                         else
7987                                 request = REQ_NONE;
7988                         break;
7989                 }
7990                 default:
7991                         break;
7992                 }
7993         }
7995         quit(0);
7997         return 0;