Code

903efdeb7fe264ba5312e0a7979cfcc99fe00c5f
[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_DATE_SHORT, "Toggle short (date-only) dates"), \
1158         REQ_(TOGGLE_AUTHOR,     "Toggle author display"), \
1159         REQ_(TOGGLE_REV_GRAPH,  "Toggle revision graph visualization"), \
1160         REQ_(TOGGLE_REFS,       "Toggle reference display (tags/branches)"), \
1161         REQ_(TOGGLE_SORT_ORDER, "Toggle ascending/descending sort order"), \
1162         REQ_(TOGGLE_SORT_FIELD, "Toggle field to sort by"), \
1163         \
1164         REQ_GROUP("Misc") \
1165         REQ_(PROMPT,            "Bring up the prompt"), \
1166         REQ_(SCREEN_REDRAW,     "Redraw the screen"), \
1167         REQ_(SHOW_VERSION,      "Show version information"), \
1168         REQ_(STOP_LOADING,      "Stop all loading views"), \
1169         REQ_(EDIT,              "Open in editor"), \
1170         REQ_(NONE,              "Do nothing")
1173 /* User action requests. */
1174 enum request {
1175 #define REQ_GROUP(help)
1176 #define REQ_(req, help) REQ_##req
1178         /* Offset all requests to avoid conflicts with ncurses getch values. */
1179         REQ_UNKNOWN = KEY_MAX + 1,
1180         REQ_OFFSET,
1181         REQ_INFO
1183 #undef  REQ_GROUP
1184 #undef  REQ_
1185 };
1187 struct request_info {
1188         enum request request;
1189         const char *name;
1190         int namelen;
1191         const char *help;
1192 };
1194 static const struct request_info req_info[] = {
1195 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
1196 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
1197         REQ_INFO
1198 #undef  REQ_GROUP
1199 #undef  REQ_
1200 };
1202 static enum request
1203 get_request(const char *name)
1205         int namelen = strlen(name);
1206         int i;
1208         for (i = 0; i < ARRAY_SIZE(req_info); i++)
1209                 if (enum_equals(req_info[i], name, namelen))
1210                         return req_info[i].request;
1212         return REQ_UNKNOWN;
1216 /*
1217  * Options
1218  */
1220 /* Option and state variables. */
1221 static enum date opt_date               = DATE_DEFAULT;
1222 static enum author opt_author           = AUTHOR_DEFAULT;
1223 static bool opt_line_number             = FALSE;
1224 static bool opt_line_graphics           = TRUE;
1225 static bool opt_rev_graph               = FALSE;
1226 static bool opt_show_refs               = 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         { ' ',          REQ_MOVE_PAGE_DOWN },
1456         { KEY_PPAGE,    REQ_MOVE_PAGE_UP },
1457         { KEY_CTL('U'), REQ_MOVE_PAGE_UP },
1458         { 'b',          REQ_MOVE_PAGE_UP },
1459         { '-',          REQ_MOVE_PAGE_UP },
1461         /* Scrolling */
1462         { '|',          REQ_SCROLL_FIRST_COL },
1463         { KEY_LEFT,     REQ_SCROLL_LEFT },
1464         { KEY_RIGHT,    REQ_SCROLL_RIGHT },
1465         { KEY_IC,       REQ_SCROLL_LINE_UP },
1466         { KEY_CTL('Y'), REQ_SCROLL_LINE_UP },
1467         { KEY_DC,       REQ_SCROLL_LINE_DOWN },
1468         { KEY_CTL('E'), REQ_SCROLL_LINE_DOWN },
1469         { 'w',          REQ_SCROLL_PAGE_UP },
1470         { 's',          REQ_SCROLL_PAGE_DOWN },
1472         /* Searching */
1473         { '/',          REQ_SEARCH },
1474         { '?',          REQ_SEARCH_BACK },
1475         { 'n',          REQ_FIND_NEXT },
1476         { 'N',          REQ_FIND_PREV },
1478         /* Misc */
1479         { 'Q',          REQ_QUIT },
1480         { 'z',          REQ_STOP_LOADING },
1481         { 'v',          REQ_SHOW_VERSION },
1482         { 'r',          REQ_SCREEN_REDRAW },
1483         { KEY_CTL('L'), REQ_SCREEN_REDRAW },
1484         { 'o',          REQ_OPTIONS },
1485         { '.',          REQ_TOGGLE_LINENO },
1486         { 'D',          REQ_TOGGLE_DATE },
1487         { 'A',          REQ_TOGGLE_AUTHOR },
1488         { 'g',          REQ_TOGGLE_REV_GRAPH },
1489         { 'F',          REQ_TOGGLE_REFS },
1490         { 'I',          REQ_TOGGLE_SORT_ORDER },
1491         { 'i',          REQ_TOGGLE_SORT_FIELD },
1492         { ':',          REQ_PROMPT },
1493         { 'u',          REQ_STATUS_UPDATE },
1494         { '!',          REQ_STATUS_REVERT },
1495         { 'M',          REQ_STATUS_MERGE },
1496         { '@',          REQ_STAGE_NEXT },
1497         { ',',          REQ_PARENT },
1498         { 'e',          REQ_EDIT },
1499 };
1501 #define KEYMAP_INFO \
1502         KEYMAP_(GENERIC), \
1503         KEYMAP_(MAIN), \
1504         KEYMAP_(DIFF), \
1505         KEYMAP_(LOG), \
1506         KEYMAP_(TREE), \
1507         KEYMAP_(BLOB), \
1508         KEYMAP_(BLAME), \
1509         KEYMAP_(BRANCH), \
1510         KEYMAP_(PAGER), \
1511         KEYMAP_(HELP), \
1512         KEYMAP_(STATUS), \
1513         KEYMAP_(STAGE)
1515 enum keymap {
1516 #define KEYMAP_(name) KEYMAP_##name
1517         KEYMAP_INFO
1518 #undef  KEYMAP_
1519 };
1521 static const struct enum_map keymap_table[] = {
1522 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1523         KEYMAP_INFO
1524 #undef  KEYMAP_
1525 };
1527 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1529 struct keybinding_table {
1530         struct keybinding *data;
1531         size_t size;
1532 };
1534 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1536 static void
1537 add_keybinding(enum keymap keymap, enum request request, int key)
1539         struct keybinding_table *table = &keybindings[keymap];
1540         size_t i;
1542         for (i = 0; i < keybindings[keymap].size; i++) {
1543                 if (keybindings[keymap].data[i].alias == key) {
1544                         keybindings[keymap].data[i].request = request;
1545                         return;
1546                 }
1547         }
1549         table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1550         if (!table->data)
1551                 die("Failed to allocate keybinding");
1552         table->data[table->size].alias = key;
1553         table->data[table->size++].request = request;
1555         if (request == REQ_NONE && keymap == KEYMAP_GENERIC) {
1556                 int i;
1558                 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1559                         if (default_keybindings[i].alias == key)
1560                                 default_keybindings[i].request = REQ_NONE;
1561         }
1564 /* Looks for a key binding first in the given map, then in the generic map, and
1565  * lastly in the default keybindings. */
1566 static enum request
1567 get_keybinding(enum keymap keymap, int key)
1569         size_t i;
1571         for (i = 0; i < keybindings[keymap].size; i++)
1572                 if (keybindings[keymap].data[i].alias == key)
1573                         return keybindings[keymap].data[i].request;
1575         for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1576                 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1577                         return keybindings[KEYMAP_GENERIC].data[i].request;
1579         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1580                 if (default_keybindings[i].alias == key)
1581                         return default_keybindings[i].request;
1583         return (enum request) key;
1587 struct key {
1588         const char *name;
1589         int value;
1590 };
1592 static const struct key key_table[] = {
1593         { "Enter",      KEY_RETURN },
1594         { "Space",      ' ' },
1595         { "Backspace",  KEY_BACKSPACE },
1596         { "Tab",        KEY_TAB },
1597         { "Escape",     KEY_ESC },
1598         { "Left",       KEY_LEFT },
1599         { "Right",      KEY_RIGHT },
1600         { "Up",         KEY_UP },
1601         { "Down",       KEY_DOWN },
1602         { "Insert",     KEY_IC },
1603         { "Delete",     KEY_DC },
1604         { "Hash",       '#' },
1605         { "Home",       KEY_HOME },
1606         { "End",        KEY_END },
1607         { "PageUp",     KEY_PPAGE },
1608         { "PageDown",   KEY_NPAGE },
1609         { "F1",         KEY_F(1) },
1610         { "F2",         KEY_F(2) },
1611         { "F3",         KEY_F(3) },
1612         { "F4",         KEY_F(4) },
1613         { "F5",         KEY_F(5) },
1614         { "F6",         KEY_F(6) },
1615         { "F7",         KEY_F(7) },
1616         { "F8",         KEY_F(8) },
1617         { "F9",         KEY_F(9) },
1618         { "F10",        KEY_F(10) },
1619         { "F11",        KEY_F(11) },
1620         { "F12",        KEY_F(12) },
1621 };
1623 static int
1624 get_key_value(const char *name)
1626         int i;
1628         for (i = 0; i < ARRAY_SIZE(key_table); i++)
1629                 if (!strcasecmp(key_table[i].name, name))
1630                         return key_table[i].value;
1632         if (strlen(name) == 2 && name[0] == '^' && isprint(*name))
1633                 return (int)name[1] & 0x1f;
1634         if (strlen(name) == 1 && isprint(*name))
1635                 return (int) *name;
1636         return ERR;
1639 static const char *
1640 get_key_name(int key_value)
1642         static char key_char[] = "'X'\0";
1643         const char *seq = NULL;
1644         int key;
1646         for (key = 0; key < ARRAY_SIZE(key_table); key++)
1647                 if (key_table[key].value == key_value)
1648                         seq = key_table[key].name;
1650         if (seq == NULL && key_value < 0x7f) {
1651                 char *s = key_char + 1;
1653                 if (key_value >= 0x20) {
1654                         *s++ = key_value;
1655                 } else {
1656                         *s++ = '^';
1657                         *s++ = 0x40 | (key_value & 0x1f);
1658                 }
1659                 *s++ = '\'';
1660                 *s++ = '\0';
1661                 seq = key_char;
1662         }
1664         return seq ? seq : "(no key)";
1667 static bool
1668 append_key(char *buf, size_t *pos, const struct keybinding *keybinding)
1670         const char *sep = *pos > 0 ? ", " : "";
1671         const char *keyname = get_key_name(keybinding->alias);
1673         return string_nformat(buf, BUFSIZ, pos, "%s%s", sep, keyname);
1676 static bool
1677 append_keymap_request_keys(char *buf, size_t *pos, enum request request,
1678                            enum keymap keymap, bool all)
1680         int i;
1682         for (i = 0; i < keybindings[keymap].size; i++) {
1683                 if (keybindings[keymap].data[i].request == request) {
1684                         if (!append_key(buf, pos, &keybindings[keymap].data[i]))
1685                                 return FALSE;
1686                         if (!all)
1687                                 break;
1688                 }
1689         }
1691         return TRUE;
1694 #define get_key(keymap, request) get_keys(keymap, request, FALSE)
1696 static const char *
1697 get_keys(enum keymap keymap, enum request request, bool all)
1699         static char buf[BUFSIZ];
1700         size_t pos = 0;
1701         int i;
1703         buf[pos] = 0;
1705         if (!append_keymap_request_keys(buf, &pos, request, keymap, all))
1706                 return "Too many keybindings!";
1707         if (pos > 0 && !all)
1708                 return buf;
1710         if (keymap != KEYMAP_GENERIC) {
1711                 /* Only the generic keymap includes the default keybindings when
1712                  * listing all keys. */
1713                 if (all)
1714                         return buf;
1716                 if (!append_keymap_request_keys(buf, &pos, request, KEYMAP_GENERIC, all))
1717                         return "Too many keybindings!";
1718                 if (pos)
1719                         return buf;
1720         }
1722         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1723                 if (default_keybindings[i].request == request) {
1724                         if (!append_key(buf, &pos, &default_keybindings[i]))
1725                                 return "Too many keybindings!";
1726                         if (!all)
1727                                 return buf;
1728                 }
1729         }
1731         return buf;
1734 struct run_request {
1735         enum keymap keymap;
1736         int key;
1737         const char **argv;
1738 };
1740 static struct run_request *run_request;
1741 static size_t run_requests;
1743 DEFINE_ALLOCATOR(realloc_run_requests, struct run_request, 8)
1745 static enum request
1746 add_run_request(enum keymap keymap, int key, const char **argv)
1748         struct run_request *req;
1750         if (!realloc_run_requests(&run_request, run_requests, 1))
1751                 return REQ_NONE;
1753         req = &run_request[run_requests];
1754         req->keymap = keymap;
1755         req->key = key;
1756         req->argv = NULL;
1758         if (!argv_copy(&req->argv, argv))
1759                 return REQ_NONE;
1761         return REQ_NONE + ++run_requests;
1764 static struct run_request *
1765 get_run_request(enum request request)
1767         if (request <= REQ_NONE)
1768                 return NULL;
1769         return &run_request[request - REQ_NONE - 1];
1772 static void
1773 add_builtin_run_requests(void)
1775         const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1776         const char *checkout[] = { "git", "checkout", "%(branch)", NULL };
1777         const char *commit[] = { "git", "commit", NULL };
1778         const char *gc[] = { "git", "gc", NULL };
1779         struct run_request reqs[] = {
1780                 { KEYMAP_MAIN,    'C', cherry_pick },
1781                 { KEYMAP_STATUS,  'C', commit },
1782                 { KEYMAP_BRANCH,  'C', checkout },
1783                 { KEYMAP_GENERIC, 'G', gc },
1784         };
1785         int i;
1787         for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1788                 enum request req = get_keybinding(reqs[i].keymap, reqs[i].key);
1790                 if (req != reqs[i].key)
1791                         continue;
1792                 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argv);
1793                 if (req != REQ_NONE)
1794                         add_keybinding(reqs[i].keymap, req, reqs[i].key);
1795         }
1798 /*
1799  * User config file handling.
1800  */
1802 static int   config_lineno;
1803 static bool  config_errors;
1804 static const char *config_msg;
1806 static const struct enum_map color_map[] = {
1807 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1808         COLOR_MAP(DEFAULT),
1809         COLOR_MAP(BLACK),
1810         COLOR_MAP(BLUE),
1811         COLOR_MAP(CYAN),
1812         COLOR_MAP(GREEN),
1813         COLOR_MAP(MAGENTA),
1814         COLOR_MAP(RED),
1815         COLOR_MAP(WHITE),
1816         COLOR_MAP(YELLOW),
1817 };
1819 static const struct enum_map attr_map[] = {
1820 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1821         ATTR_MAP(NORMAL),
1822         ATTR_MAP(BLINK),
1823         ATTR_MAP(BOLD),
1824         ATTR_MAP(DIM),
1825         ATTR_MAP(REVERSE),
1826         ATTR_MAP(STANDOUT),
1827         ATTR_MAP(UNDERLINE),
1828 };
1830 #define set_attribute(attr, name)       map_enum(attr, attr_map, name)
1832 static int parse_step(double *opt, const char *arg)
1834         *opt = atoi(arg);
1835         if (!strchr(arg, '%'))
1836                 return OK;
1838         /* "Shift down" so 100% and 1 does not conflict. */
1839         *opt = (*opt - 1) / 100;
1840         if (*opt >= 1.0) {
1841                 *opt = 0.99;
1842                 config_msg = "Step value larger than 100%";
1843                 return ERR;
1844         }
1845         if (*opt < 0.0) {
1846                 *opt = 1;
1847                 config_msg = "Invalid step value";
1848                 return ERR;
1849         }
1850         return OK;
1853 static int
1854 parse_int(int *opt, const char *arg, int min, int max)
1856         int value = atoi(arg);
1858         if (min <= value && value <= max) {
1859                 *opt = value;
1860                 return OK;
1861         }
1863         config_msg = "Integer value out of bound";
1864         return ERR;
1867 static bool
1868 set_color(int *color, const char *name)
1870         if (map_enum(color, color_map, name))
1871                 return TRUE;
1872         if (!prefixcmp(name, "color"))
1873                 return parse_int(color, name + 5, 0, 255) == OK;
1874         return FALSE;
1877 /* Wants: object fgcolor bgcolor [attribute] */
1878 static int
1879 option_color_command(int argc, const char *argv[])
1881         struct line_info *info;
1883         if (argc < 3) {
1884                 config_msg = "Wrong number of arguments given to color command";
1885                 return ERR;
1886         }
1888         info = get_line_info(argv[0]);
1889         if (!info) {
1890                 static const struct enum_map obsolete[] = {
1891                         ENUM_MAP("main-delim",  LINE_DELIMITER),
1892                         ENUM_MAP("main-date",   LINE_DATE),
1893                         ENUM_MAP("main-author", LINE_AUTHOR),
1894                 };
1895                 int index;
1897                 if (!map_enum(&index, obsolete, argv[0])) {
1898                         config_msg = "Unknown color name";
1899                         return ERR;
1900                 }
1901                 info = &line_info[index];
1902         }
1904         if (!set_color(&info->fg, argv[1]) ||
1905             !set_color(&info->bg, argv[2])) {
1906                 config_msg = "Unknown color";
1907                 return ERR;
1908         }
1910         info->attr = 0;
1911         while (argc-- > 3) {
1912                 int attr;
1914                 if (!set_attribute(&attr, argv[argc])) {
1915                         config_msg = "Unknown attribute";
1916                         return ERR;
1917                 }
1918                 info->attr |= attr;
1919         }
1921         return OK;
1924 static int parse_bool(bool *opt, const char *arg)
1926         *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1927                 ? TRUE : FALSE;
1928         return OK;
1931 static int parse_enum_do(unsigned int *opt, const char *arg,
1932                          const struct enum_map *map, size_t map_size)
1934         bool is_true;
1936         assert(map_size > 1);
1938         if (map_enum_do(map, map_size, (int *) opt, arg))
1939                 return OK;
1941         if (parse_bool(&is_true, arg) != OK)
1942                 return ERR;
1944         *opt = is_true ? map[1].value : map[0].value;
1945         return OK;
1948 #define parse_enum(opt, arg, map) \
1949         parse_enum_do(opt, arg, map, ARRAY_SIZE(map))
1951 static int
1952 parse_string(char *opt, const char *arg, size_t optsize)
1954         int arglen = strlen(arg);
1956         switch (arg[0]) {
1957         case '\"':
1958         case '\'':
1959                 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1960                         config_msg = "Unmatched quotation";
1961                         return ERR;
1962                 }
1963                 arg += 1; arglen -= 2;
1964         default:
1965                 string_ncopy_do(opt, optsize, arg, arglen);
1966                 return OK;
1967         }
1970 /* Wants: name = value */
1971 static int
1972 option_set_command(int argc, const char *argv[])
1974         if (argc != 3) {
1975                 config_msg = "Wrong number of arguments given to set command";
1976                 return ERR;
1977         }
1979         if (strcmp(argv[1], "=")) {
1980                 config_msg = "No value assigned";
1981                 return ERR;
1982         }
1984         if (!strcmp(argv[0], "show-author"))
1985                 return parse_enum(&opt_author, argv[2], author_map);
1987         if (!strcmp(argv[0], "show-date"))
1988                 return parse_enum(&opt_date, argv[2], date_map);
1990         if (!strcmp(argv[0], "show-rev-graph"))
1991                 return parse_bool(&opt_rev_graph, argv[2]);
1993         if (!strcmp(argv[0], "show-refs"))
1994                 return parse_bool(&opt_show_refs, argv[2]);
1996         if (!strcmp(argv[0], "show-line-numbers"))
1997                 return parse_bool(&opt_line_number, argv[2]);
1999         if (!strcmp(argv[0], "line-graphics"))
2000                 return parse_bool(&opt_line_graphics, argv[2]);
2002         if (!strcmp(argv[0], "line-number-interval"))
2003                 return parse_int(&opt_num_interval, argv[2], 1, 1024);
2005         if (!strcmp(argv[0], "author-width"))
2006                 return parse_int(&opt_author_cols, argv[2], 0, 1024);
2008         if (!strcmp(argv[0], "horizontal-scroll"))
2009                 return parse_step(&opt_hscroll, argv[2]);
2011         if (!strcmp(argv[0], "split-view-height"))
2012                 return parse_step(&opt_scale_split_view, argv[2]);
2014         if (!strcmp(argv[0], "tab-size"))
2015                 return parse_int(&opt_tab_size, argv[2], 1, 1024);
2017         if (!strcmp(argv[0], "commit-encoding"))
2018                 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
2020         config_msg = "Unknown variable name";
2021         return ERR;
2024 /* Wants: mode request key */
2025 static int
2026 option_bind_command(int argc, const char *argv[])
2028         enum request request;
2029         int keymap = -1;
2030         int key;
2032         if (argc < 3) {
2033                 config_msg = "Wrong number of arguments given to bind command";
2034                 return ERR;
2035         }
2037         if (!set_keymap(&keymap, argv[0])) {
2038                 config_msg = "Unknown key map";
2039                 return ERR;
2040         }
2042         key = get_key_value(argv[1]);
2043         if (key == ERR) {
2044                 config_msg = "Unknown key";
2045                 return ERR;
2046         }
2048         request = get_request(argv[2]);
2049         if (request == REQ_UNKNOWN) {
2050                 static const struct enum_map obsolete[] = {
2051                         ENUM_MAP("cherry-pick",         REQ_NONE),
2052                         ENUM_MAP("screen-resize",       REQ_NONE),
2053                         ENUM_MAP("tree-parent",         REQ_PARENT),
2054                 };
2055                 int alias;
2057                 if (map_enum(&alias, obsolete, argv[2])) {
2058                         if (alias != REQ_NONE)
2059                                 add_keybinding(keymap, alias, key);
2060                         config_msg = "Obsolete request name";
2061                         return ERR;
2062                 }
2063         }
2064         if (request == REQ_UNKNOWN && *argv[2]++ == '!')
2065                 request = add_run_request(keymap, key, argv + 2);
2066         if (request == REQ_UNKNOWN) {
2067                 config_msg = "Unknown request name";
2068                 return ERR;
2069         }
2071         add_keybinding(keymap, request, key);
2073         return OK;
2076 static int
2077 set_option(const char *opt, char *value)
2079         const char *argv[SIZEOF_ARG];
2080         int argc = 0;
2082         if (!argv_from_string(argv, &argc, value)) {
2083                 config_msg = "Too many option arguments";
2084                 return ERR;
2085         }
2087         if (!strcmp(opt, "color"))
2088                 return option_color_command(argc, argv);
2090         if (!strcmp(opt, "set"))
2091                 return option_set_command(argc, argv);
2093         if (!strcmp(opt, "bind"))
2094                 return option_bind_command(argc, argv);
2096         config_msg = "Unknown option command";
2097         return ERR;
2100 static int
2101 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
2103         int status = OK;
2105         config_lineno++;
2106         config_msg = "Internal error";
2108         /* Check for comment markers, since read_properties() will
2109          * only ensure opt and value are split at first " \t". */
2110         optlen = strcspn(opt, "#");
2111         if (optlen == 0)
2112                 return OK;
2114         if (opt[optlen] != 0) {
2115                 config_msg = "No option value";
2116                 status = ERR;
2118         }  else {
2119                 /* Look for comment endings in the value. */
2120                 size_t len = strcspn(value, "#");
2122                 if (len < valuelen) {
2123                         valuelen = len;
2124                         value[valuelen] = 0;
2125                 }
2127                 status = set_option(opt, value);
2128         }
2130         if (status == ERR) {
2131                 warn("Error on line %d, near '%.*s': %s",
2132                      config_lineno, (int) optlen, opt, config_msg);
2133                 config_errors = TRUE;
2134         }
2136         /* Always keep going if errors are encountered. */
2137         return OK;
2140 static void
2141 load_option_file(const char *path)
2143         struct io io;
2145         /* It's OK that the file doesn't exist. */
2146         if (!io_open(&io, "%s", path))
2147                 return;
2149         config_lineno = 0;
2150         config_errors = FALSE;
2152         if (io_load(&io, " \t", read_option) == ERR ||
2153             config_errors == TRUE)
2154                 warn("Errors while loading %s.", path);
2157 static int
2158 load_options(void)
2160         const char *home = getenv("HOME");
2161         const char *tigrc_user = getenv("TIGRC_USER");
2162         const char *tigrc_system = getenv("TIGRC_SYSTEM");
2163         const char *tig_diff_opts = getenv("TIG_DIFF_OPTS");
2164         char buf[SIZEOF_STR];
2166         if (!tigrc_system)
2167                 tigrc_system = SYSCONFDIR "/tigrc";
2168         load_option_file(tigrc_system);
2170         if (!tigrc_user) {
2171                 if (!home || !string_format(buf, "%s/.tigrc", home))
2172                         return ERR;
2173                 tigrc_user = buf;
2174         }
2175         load_option_file(tigrc_user);
2177         /* Add _after_ loading config files to avoid adding run requests
2178          * that conflict with keybindings. */
2179         add_builtin_run_requests();
2181         if (!opt_diff_args && tig_diff_opts && *tig_diff_opts) {
2182                 static const char *diff_opts[SIZEOF_ARG] = { NULL };
2183                 int argc = 0;
2185                 if (!string_format(buf, "%s", tig_diff_opts) ||
2186                     !argv_from_string(diff_opts, &argc, buf))
2187                         die("TIG_DIFF_OPTS contains too many arguments");
2188                 else if (!argv_copy(&opt_diff_args, diff_opts))
2189                         die("Failed to format TIG_DIFF_OPTS arguments");
2190         }
2192         return OK;
2196 /*
2197  * The viewer
2198  */
2200 struct view;
2201 struct view_ops;
2203 /* The display array of active views and the index of the current view. */
2204 static struct view *display[2];
2205 static unsigned int current_view;
2207 #define foreach_displayed_view(view, i) \
2208         for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
2210 #define displayed_views()       (display[1] != NULL ? 2 : 1)
2212 /* Current head and commit ID */
2213 static char ref_blob[SIZEOF_REF]        = "";
2214 static char ref_commit[SIZEOF_REF]      = "HEAD";
2215 static char ref_head[SIZEOF_REF]        = "HEAD";
2216 static char ref_branch[SIZEOF_REF]      = "";
2218 enum view_type {
2219         VIEW_MAIN,
2220         VIEW_DIFF,
2221         VIEW_LOG,
2222         VIEW_TREE,
2223         VIEW_BLOB,
2224         VIEW_BLAME,
2225         VIEW_BRANCH,
2226         VIEW_HELP,
2227         VIEW_PAGER,
2228         VIEW_STATUS,
2229         VIEW_STAGE,
2230 };
2232 struct view {
2233         enum view_type type;    /* View type */
2234         const char *name;       /* View name */
2235         const char *cmd_env;    /* Command line set via environment */
2236         const char *id;         /* Points to either of ref_{head,commit,blob} */
2238         struct view_ops *ops;   /* View operations */
2240         enum keymap keymap;     /* What keymap does this view have */
2241         bool git_dir;           /* Whether the view requires a git directory. */
2243         char ref[SIZEOF_REF];   /* Hovered commit reference */
2244         char vid[SIZEOF_REF];   /* View ID. Set to id member when updating. */
2246         int height, width;      /* The width and height of the main window */
2247         WINDOW *win;            /* The main window */
2248         WINDOW *title;          /* The title window living below the main window */
2250         /* Navigation */
2251         unsigned long offset;   /* Offset of the window top */
2252         unsigned long yoffset;  /* Offset from the window side. */
2253         unsigned long lineno;   /* Current line number */
2254         unsigned long p_offset; /* Previous offset of the window top */
2255         unsigned long p_yoffset;/* Previous offset from the window side */
2256         unsigned long p_lineno; /* Previous current line number */
2257         bool p_restore;         /* Should the previous position be restored. */
2259         /* Searching */
2260         char grep[SIZEOF_STR];  /* Search string */
2261         regex_t *regex;         /* Pre-compiled regexp */
2263         /* If non-NULL, points to the view that opened this view. If this view
2264          * is closed tig will switch back to the parent view. */
2265         struct view *parent;
2266         struct view *prev;
2268         /* Buffering */
2269         size_t lines;           /* Total number of lines */
2270         struct line *line;      /* Line index */
2271         unsigned int digits;    /* Number of digits in the lines member. */
2273         /* Drawing */
2274         struct line *curline;   /* Line currently being drawn. */
2275         enum line_type curtype; /* Attribute currently used for drawing. */
2276         unsigned long col;      /* Column when drawing. */
2277         bool has_scrolled;      /* View was scrolled. */
2279         /* Loading */
2280         const char **argv;      /* Shell command arguments. */
2281         const char *dir;        /* Directory from which to execute. */
2282         struct io io;
2283         struct io *pipe;
2284         time_t start_time;
2285         time_t update_secs;
2286 };
2288 struct view_ops {
2289         /* What type of content being displayed. Used in the title bar. */
2290         const char *type;
2291         /* Default command arguments. */
2292         const char **argv;
2293         /* Open and reads in all view content. */
2294         bool (*open)(struct view *view);
2295         /* Read one line; updates view->line. */
2296         bool (*read)(struct view *view, char *data);
2297         /* Draw one line; @lineno must be < view->height. */
2298         bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
2299         /* Depending on view handle a special requests. */
2300         enum request (*request)(struct view *view, enum request request, struct line *line);
2301         /* Search for regexp in a line. */
2302         bool (*grep)(struct view *view, struct line *line);
2303         /* Select line */
2304         void (*select)(struct view *view, struct line *line);
2305         /* Prepare view for loading */
2306         bool (*prepare)(struct view *view);
2307 };
2309 static struct view_ops blame_ops;
2310 static struct view_ops blob_ops;
2311 static struct view_ops diff_ops;
2312 static struct view_ops help_ops;
2313 static struct view_ops log_ops;
2314 static struct view_ops main_ops;
2315 static struct view_ops pager_ops;
2316 static struct view_ops stage_ops;
2317 static struct view_ops status_ops;
2318 static struct view_ops tree_ops;
2319 static struct view_ops branch_ops;
2321 #define VIEW_STR(type, name, env, ref, ops, map, git) \
2322         { type, name, #env, ref, ops, map, git }
2324 #define VIEW_(id, name, ops, git, ref) \
2325         VIEW_STR(VIEW_##id, name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
2327 static struct view views[] = {
2328         VIEW_(MAIN,   "main",   &main_ops,   TRUE,  ref_head),
2329         VIEW_(DIFF,   "diff",   &diff_ops,   TRUE,  ref_commit),
2330         VIEW_(LOG,    "log",    &log_ops,    TRUE,  ref_head),
2331         VIEW_(TREE,   "tree",   &tree_ops,   TRUE,  ref_commit),
2332         VIEW_(BLOB,   "blob",   &blob_ops,   TRUE,  ref_blob),
2333         VIEW_(BLAME,  "blame",  &blame_ops,  TRUE,  ref_commit),
2334         VIEW_(BRANCH, "branch", &branch_ops, TRUE,  ref_head),
2335         VIEW_(HELP,   "help",   &help_ops,   FALSE, ""),
2336         VIEW_(PAGER,  "pager",  &pager_ops,  FALSE, ""),
2337         VIEW_(STATUS, "status", &status_ops, TRUE,  ""),
2338         VIEW_(STAGE,  "stage",  &stage_ops,  TRUE,  ""),
2339 };
2341 #define VIEW(req)       (&views[(req) - REQ_OFFSET - 1])
2343 #define foreach_view(view, i) \
2344         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2346 #define view_is_displayed(view) \
2347         (view == display[0] || view == display[1])
2349 static enum request
2350 view_request(struct view *view, enum request request)
2352         if (!view || !view->lines)
2353                 return request;
2354         return view->ops->request(view, request, &view->line[view->lineno]);
2358 /*
2359  * View drawing.
2360  */
2362 static inline void
2363 set_view_attr(struct view *view, enum line_type type)
2365         if (!view->curline->selected && view->curtype != type) {
2366                 (void) wattrset(view->win, get_line_attr(type));
2367                 wchgat(view->win, -1, 0, type, NULL);
2368                 view->curtype = type;
2369         }
2372 static int
2373 draw_chars(struct view *view, enum line_type type, const char *string,
2374            int max_len, bool use_tilde)
2376         static char out_buffer[BUFSIZ * 2];
2377         int len = 0;
2378         int col = 0;
2379         int trimmed = FALSE;
2380         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2382         if (max_len <= 0)
2383                 return 0;
2385         len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde, opt_tab_size);
2387         set_view_attr(view, type);
2388         if (len > 0) {
2389                 if (opt_iconv_out != ICONV_NONE) {
2390                         ICONV_CONST char *inbuf = (ICONV_CONST char *) string;
2391                         size_t inlen = len + 1;
2393                         char *outbuf = out_buffer;
2394                         size_t outlen = sizeof(out_buffer);
2396                         size_t ret;
2398                         ret = iconv(opt_iconv_out, &inbuf, &inlen, &outbuf, &outlen);
2399                         if (ret != (size_t) -1) {
2400                                 string = out_buffer;
2401                                 len = sizeof(out_buffer) - outlen;
2402                         }
2403                 }
2405                 waddnstr(view->win, string, len);
2406         }
2407         if (trimmed && use_tilde) {
2408                 set_view_attr(view, LINE_DELIMITER);
2409                 waddch(view->win, '~');
2410                 col++;
2411         }
2413         return col;
2416 static int
2417 draw_space(struct view *view, enum line_type type, int max, int spaces)
2419         static char space[] = "                    ";
2420         int col = 0;
2422         spaces = MIN(max, spaces);
2424         while (spaces > 0) {
2425                 int len = MIN(spaces, sizeof(space) - 1);
2427                 col += draw_chars(view, type, space, len, FALSE);
2428                 spaces -= len;
2429         }
2431         return col;
2434 static bool
2435 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
2437         char text[SIZEOF_STR];
2439         do {
2440                 size_t pos = string_expand(text, sizeof(text), string, opt_tab_size);
2442                 view->col += draw_chars(view, type, text, view->width + view->yoffset - view->col, trim);
2443                 string += pos;
2444         } while (*string && view->width + view->yoffset > view->col);
2446         return view->width + view->yoffset <= view->col;
2449 static bool
2450 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
2452         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2453         int max = view->width + view->yoffset - view->col;
2454         int i;
2456         if (max < size)
2457                 size = max;
2459         set_view_attr(view, type);
2460         /* Using waddch() instead of waddnstr() ensures that
2461          * they'll be rendered correctly for the cursor line. */
2462         for (i = skip; i < size; i++)
2463                 waddch(view->win, graphic[i]);
2465         view->col += size;
2466         if (size < max && skip <= size)
2467                 waddch(view->win, ' ');
2468         view->col++;
2470         return view->width + view->yoffset <= view->col;
2473 static bool
2474 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
2476         int max = MIN(view->width + view->yoffset - view->col, len);
2477         int col;
2479         if (text)
2480                 col = draw_chars(view, type, text, max - 1, trim);
2481         else
2482                 col = draw_space(view, type, max - 1, max - 1);
2484         view->col += col;
2485         view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
2486         return view->width + view->yoffset <= view->col;
2489 static bool
2490 draw_date(struct view *view, struct time *time)
2492         const char *date = mkdate(time, opt_date);
2493         int cols = opt_date == DATE_SHORT ? DATE_SHORT_COLS : DATE_COLS;
2495         return draw_field(view, LINE_DATE, date, cols, FALSE);
2498 static bool
2499 draw_author(struct view *view, const char *author)
2501         bool trim = opt_author_cols == 0 || opt_author_cols > 5;
2502         bool abbreviate = opt_author == AUTHOR_ABBREVIATED || !trim;
2504         if (abbreviate && author)
2505                 author = get_author_initials(author);
2507         return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2510 static bool
2511 draw_mode(struct view *view, mode_t mode)
2513         const char *str;
2515         if (S_ISDIR(mode))
2516                 str = "drwxr-xr-x";
2517         else if (S_ISLNK(mode))
2518                 str = "lrwxrwxrwx";
2519         else if (S_ISGITLINK(mode))
2520                 str = "m---------";
2521         else if (S_ISREG(mode) && mode & S_IXUSR)
2522                 str = "-rwxr-xr-x";
2523         else if (S_ISREG(mode))
2524                 str = "-rw-r--r--";
2525         else
2526                 str = "----------";
2528         return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2531 static bool
2532 draw_lineno(struct view *view, unsigned int lineno)
2534         char number[10];
2535         int digits3 = view->digits < 3 ? 3 : view->digits;
2536         int max = MIN(view->width + view->yoffset - view->col, digits3);
2537         char *text = NULL;
2538         chtype separator = opt_line_graphics ? ACS_VLINE : '|';
2540         lineno += view->offset + 1;
2541         if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2542                 static char fmt[] = "%1ld";
2544                 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2545                 if (string_format(number, fmt, lineno))
2546                         text = number;
2547         }
2548         if (text)
2549                 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2550         else
2551                 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2552         return draw_graphic(view, LINE_DEFAULT, &separator, 1);
2555 static bool
2556 draw_view_line(struct view *view, unsigned int lineno)
2558         struct line *line;
2559         bool selected = (view->offset + lineno == view->lineno);
2561         assert(view_is_displayed(view));
2563         if (view->offset + lineno >= view->lines)
2564                 return FALSE;
2566         line = &view->line[view->offset + lineno];
2568         wmove(view->win, lineno, 0);
2569         if (line->cleareol)
2570                 wclrtoeol(view->win);
2571         view->col = 0;
2572         view->curline = line;
2573         view->curtype = LINE_NONE;
2574         line->selected = FALSE;
2575         line->dirty = line->cleareol = 0;
2577         if (selected) {
2578                 set_view_attr(view, LINE_CURSOR);
2579                 line->selected = TRUE;
2580                 view->ops->select(view, line);
2581         }
2583         return view->ops->draw(view, line, lineno);
2586 static void
2587 redraw_view_dirty(struct view *view)
2589         bool dirty = FALSE;
2590         int lineno;
2592         for (lineno = 0; lineno < view->height; lineno++) {
2593                 if (view->offset + lineno >= view->lines)
2594                         break;
2595                 if (!view->line[view->offset + lineno].dirty)
2596                         continue;
2597                 dirty = TRUE;
2598                 if (!draw_view_line(view, lineno))
2599                         break;
2600         }
2602         if (!dirty)
2603                 return;
2604         wnoutrefresh(view->win);
2607 static void
2608 redraw_view_from(struct view *view, int lineno)
2610         assert(0 <= lineno && lineno < view->height);
2612         for (; lineno < view->height; lineno++) {
2613                 if (!draw_view_line(view, lineno))
2614                         break;
2615         }
2617         wnoutrefresh(view->win);
2620 static void
2621 redraw_view(struct view *view)
2623         werase(view->win);
2624         redraw_view_from(view, 0);
2628 static void
2629 update_view_title(struct view *view)
2631         char buf[SIZEOF_STR];
2632         char state[SIZEOF_STR];
2633         size_t bufpos = 0, statelen = 0;
2635         assert(view_is_displayed(view));
2637         if (view->type != VIEW_STATUS && view->lines) {
2638                 unsigned int view_lines = view->offset + view->height;
2639                 unsigned int lines = view->lines
2640                                    ? MIN(view_lines, view->lines) * 100 / view->lines
2641                                    : 0;
2643                 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2644                                    view->ops->type,
2645                                    view->lineno + 1,
2646                                    view->lines,
2647                                    lines);
2649         }
2651         if (view->pipe) {
2652                 time_t secs = time(NULL) - view->start_time;
2654                 /* Three git seconds are a long time ... */
2655                 if (secs > 2)
2656                         string_format_from(state, &statelen, " loading %lds", secs);
2657         }
2659         string_format_from(buf, &bufpos, "[%s]", view->name);
2660         if (*view->ref && bufpos < view->width) {
2661                 size_t refsize = strlen(view->ref);
2662                 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2664                 if (minsize < view->width)
2665                         refsize = view->width - minsize + 7;
2666                 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2667         }
2669         if (statelen && bufpos < view->width) {
2670                 string_format_from(buf, &bufpos, "%s", state);
2671         }
2673         if (view == display[current_view])
2674                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2675         else
2676                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2678         mvwaddnstr(view->title, 0, 0, buf, bufpos);
2679         wclrtoeol(view->title);
2680         wnoutrefresh(view->title);
2683 static int
2684 apply_step(double step, int value)
2686         if (step >= 1)
2687                 return (int) step;
2688         value *= step + 0.01;
2689         return value ? value : 1;
2692 static void
2693 resize_display(void)
2695         int offset, i;
2696         struct view *base = display[0];
2697         struct view *view = display[1] ? display[1] : display[0];
2699         /* Setup window dimensions */
2701         getmaxyx(stdscr, base->height, base->width);
2703         /* Make room for the status window. */
2704         base->height -= 1;
2706         if (view != base) {
2707                 /* Horizontal split. */
2708                 view->width   = base->width;
2709                 view->height  = apply_step(opt_scale_split_view, base->height);
2710                 view->height  = MAX(view->height, MIN_VIEW_HEIGHT);
2711                 view->height  = MIN(view->height, base->height - MIN_VIEW_HEIGHT);
2712                 base->height -= view->height;
2714                 /* Make room for the title bar. */
2715                 view->height -= 1;
2716         }
2718         /* Make room for the title bar. */
2719         base->height -= 1;
2721         offset = 0;
2723         foreach_displayed_view (view, i) {
2724                 if (!view->win) {
2725                         view->win = newwin(view->height, 0, offset, 0);
2726                         if (!view->win)
2727                                 die("Failed to create %s view", view->name);
2729                         scrollok(view->win, FALSE);
2731                         view->title = newwin(1, 0, offset + view->height, 0);
2732                         if (!view->title)
2733                                 die("Failed to create title window");
2735                 } else {
2736                         wresize(view->win, view->height, view->width);
2737                         mvwin(view->win,   offset, 0);
2738                         mvwin(view->title, offset + view->height, 0);
2739                 }
2741                 offset += view->height + 1;
2742         }
2745 static void
2746 redraw_display(bool clear)
2748         struct view *view;
2749         int i;
2751         foreach_displayed_view (view, i) {
2752                 if (clear)
2753                         wclear(view->win);
2754                 redraw_view(view);
2755                 update_view_title(view);
2756         }
2760 /*
2761  * Option management
2762  */
2764 static void
2765 toggle_enum_option_do(unsigned int *opt, const char *help,
2766                       const struct enum_map *map, size_t size)
2768         *opt = (*opt + 1) % size;
2769         redraw_display(FALSE);
2770         report("Displaying %s %s", enum_name(map[*opt]), help);
2773 #define toggle_enum_option(opt, help, map) \
2774         toggle_enum_option_do(opt, help, map, ARRAY_SIZE(map))
2776 #define toggle_date() toggle_enum_option(&opt_date, "dates", date_map)
2777 #define toggle_author() toggle_enum_option(&opt_author, "author names", author_map)
2779 static void
2780 toggle_view_option(bool *option, const char *help)
2782         *option = !*option;
2783         redraw_display(FALSE);
2784         report("%sabling %s", *option ? "En" : "Dis", help);
2787 static void
2788 open_option_menu(void)
2790         const struct menu_item menu[] = {
2791                 { '.', "line numbers", &opt_line_number },
2792                 { 'D', "date display", &opt_date },
2793                 { 'A', "author display", &opt_author },
2794                 { 'g', "revision graph display", &opt_rev_graph },
2795                 { 'F', "reference display", &opt_show_refs },
2796                 { 0 }
2797         };
2798         int selected = 0;
2800         if (prompt_menu("Toggle option", menu, &selected)) {
2801                 if (menu[selected].data == &opt_date)
2802                         toggle_date();
2803                 else if (menu[selected].data == &opt_author)
2804                         toggle_author();
2805                 else
2806                         toggle_view_option(menu[selected].data, menu[selected].text);
2807         }
2810 static void
2811 maximize_view(struct view *view)
2813         memset(display, 0, sizeof(display));
2814         current_view = 0;
2815         display[current_view] = view;
2816         resize_display();
2817         redraw_display(FALSE);
2818         report("");
2822 /*
2823  * Navigation
2824  */
2826 static bool
2827 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2829         if (lineno >= view->lines)
2830                 lineno = view->lines > 0 ? view->lines - 1 : 0;
2832         if (offset > lineno || offset + view->height <= lineno) {
2833                 unsigned long half = view->height / 2;
2835                 if (lineno > half)
2836                         offset = lineno - half;
2837                 else
2838                         offset = 0;
2839         }
2841         if (offset != view->offset || lineno != view->lineno) {
2842                 view->offset = offset;
2843                 view->lineno = lineno;
2844                 return TRUE;
2845         }
2847         return FALSE;
2850 /* Scrolling backend */
2851 static void
2852 do_scroll_view(struct view *view, int lines)
2854         bool redraw_current_line = FALSE;
2856         /* The rendering expects the new offset. */
2857         view->offset += lines;
2859         assert(0 <= view->offset && view->offset < view->lines);
2860         assert(lines);
2862         /* Move current line into the view. */
2863         if (view->lineno < view->offset) {
2864                 view->lineno = view->offset;
2865                 redraw_current_line = TRUE;
2866         } else if (view->lineno >= view->offset + view->height) {
2867                 view->lineno = view->offset + view->height - 1;
2868                 redraw_current_line = TRUE;
2869         }
2871         assert(view->offset <= view->lineno && view->lineno < view->lines);
2873         /* Redraw the whole screen if scrolling is pointless. */
2874         if (view->height < ABS(lines)) {
2875                 redraw_view(view);
2877         } else {
2878                 int line = lines > 0 ? view->height - lines : 0;
2879                 int end = line + ABS(lines);
2881                 scrollok(view->win, TRUE);
2882                 wscrl(view->win, lines);
2883                 scrollok(view->win, FALSE);
2885                 while (line < end && draw_view_line(view, line))
2886                         line++;
2888                 if (redraw_current_line)
2889                         draw_view_line(view, view->lineno - view->offset);
2890                 wnoutrefresh(view->win);
2891         }
2893         view->has_scrolled = TRUE;
2894         report("");
2897 /* Scroll frontend */
2898 static void
2899 scroll_view(struct view *view, enum request request)
2901         int lines = 1;
2903         assert(view_is_displayed(view));
2905         switch (request) {
2906         case REQ_SCROLL_FIRST_COL:
2907                 view->yoffset = 0;
2908                 redraw_view_from(view, 0);
2909                 report("");
2910                 return;
2911         case REQ_SCROLL_LEFT:
2912                 if (view->yoffset == 0) {
2913                         report("Cannot scroll beyond the first column");
2914                         return;
2915                 }
2916                 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2917                         view->yoffset = 0;
2918                 else
2919                         view->yoffset -= apply_step(opt_hscroll, view->width);
2920                 redraw_view_from(view, 0);
2921                 report("");
2922                 return;
2923         case REQ_SCROLL_RIGHT:
2924                 view->yoffset += apply_step(opt_hscroll, view->width);
2925                 redraw_view(view);
2926                 report("");
2927                 return;
2928         case REQ_SCROLL_PAGE_DOWN:
2929                 lines = view->height;
2930         case REQ_SCROLL_LINE_DOWN:
2931                 if (view->offset + lines > view->lines)
2932                         lines = view->lines - view->offset;
2934                 if (lines == 0 || view->offset + view->height >= view->lines) {
2935                         report("Cannot scroll beyond the last line");
2936                         return;
2937                 }
2938                 break;
2940         case REQ_SCROLL_PAGE_UP:
2941                 lines = view->height;
2942         case REQ_SCROLL_LINE_UP:
2943                 if (lines > view->offset)
2944                         lines = view->offset;
2946                 if (lines == 0) {
2947                         report("Cannot scroll beyond the first line");
2948                         return;
2949                 }
2951                 lines = -lines;
2952                 break;
2954         default:
2955                 die("request %d not handled in switch", request);
2956         }
2958         do_scroll_view(view, lines);
2961 /* Cursor moving */
2962 static void
2963 move_view(struct view *view, enum request request)
2965         int scroll_steps = 0;
2966         int steps;
2968         switch (request) {
2969         case REQ_MOVE_FIRST_LINE:
2970                 steps = -view->lineno;
2971                 break;
2973         case REQ_MOVE_LAST_LINE:
2974                 steps = view->lines - view->lineno - 1;
2975                 break;
2977         case REQ_MOVE_PAGE_UP:
2978                 steps = view->height > view->lineno
2979                       ? -view->lineno : -view->height;
2980                 break;
2982         case REQ_MOVE_PAGE_DOWN:
2983                 steps = view->lineno + view->height >= view->lines
2984                       ? view->lines - view->lineno - 1 : view->height;
2985                 break;
2987         case REQ_MOVE_UP:
2988                 steps = -1;
2989                 break;
2991         case REQ_MOVE_DOWN:
2992                 steps = 1;
2993                 break;
2995         default:
2996                 die("request %d not handled in switch", request);
2997         }
2999         if (steps <= 0 && view->lineno == 0) {
3000                 report("Cannot move beyond the first line");
3001                 return;
3003         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
3004                 report("Cannot move beyond the last line");
3005                 return;
3006         }
3008         /* Move the current line */
3009         view->lineno += steps;
3010         assert(0 <= view->lineno && view->lineno < view->lines);
3012         /* Check whether the view needs to be scrolled */
3013         if (view->lineno < view->offset ||
3014             view->lineno >= view->offset + view->height) {
3015                 scroll_steps = steps;
3016                 if (steps < 0 && -steps > view->offset) {
3017                         scroll_steps = -view->offset;
3019                 } else if (steps > 0) {
3020                         if (view->lineno == view->lines - 1 &&
3021                             view->lines > view->height) {
3022                                 scroll_steps = view->lines - view->offset - 1;
3023                                 if (scroll_steps >= view->height)
3024                                         scroll_steps -= view->height - 1;
3025                         }
3026                 }
3027         }
3029         if (!view_is_displayed(view)) {
3030                 view->offset += scroll_steps;
3031                 assert(0 <= view->offset && view->offset < view->lines);
3032                 view->ops->select(view, &view->line[view->lineno]);
3033                 return;
3034         }
3036         /* Repaint the old "current" line if we be scrolling */
3037         if (ABS(steps) < view->height)
3038                 draw_view_line(view, view->lineno - steps - view->offset);
3040         if (scroll_steps) {
3041                 do_scroll_view(view, scroll_steps);
3042                 return;
3043         }
3045         /* Draw the current line */
3046         draw_view_line(view, view->lineno - view->offset);
3048         wnoutrefresh(view->win);
3049         report("");
3053 /*
3054  * Searching
3055  */
3057 static void search_view(struct view *view, enum request request);
3059 static bool
3060 grep_text(struct view *view, const char *text[])
3062         regmatch_t pmatch;
3063         size_t i;
3065         for (i = 0; text[i]; i++)
3066                 if (*text[i] &&
3067                     regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
3068                         return TRUE;
3069         return FALSE;
3072 static void
3073 select_view_line(struct view *view, unsigned long lineno)
3075         unsigned long old_lineno = view->lineno;
3076         unsigned long old_offset = view->offset;
3078         if (goto_view_line(view, view->offset, lineno)) {
3079                 if (view_is_displayed(view)) {
3080                         if (old_offset != view->offset) {
3081                                 redraw_view(view);
3082                         } else {
3083                                 draw_view_line(view, old_lineno - view->offset);
3084                                 draw_view_line(view, view->lineno - view->offset);
3085                                 wnoutrefresh(view->win);
3086                         }
3087                 } else {
3088                         view->ops->select(view, &view->line[view->lineno]);
3089                 }
3090         }
3093 static void
3094 find_next(struct view *view, enum request request)
3096         unsigned long lineno = view->lineno;
3097         int direction;
3099         if (!*view->grep) {
3100                 if (!*opt_search)
3101                         report("No previous search");
3102                 else
3103                         search_view(view, request);
3104                 return;
3105         }
3107         switch (request) {
3108         case REQ_SEARCH:
3109         case REQ_FIND_NEXT:
3110                 direction = 1;
3111                 break;
3113         case REQ_SEARCH_BACK:
3114         case REQ_FIND_PREV:
3115                 direction = -1;
3116                 break;
3118         default:
3119                 return;
3120         }
3122         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
3123                 lineno += direction;
3125         /* Note, lineno is unsigned long so will wrap around in which case it
3126          * will become bigger than view->lines. */
3127         for (; lineno < view->lines; lineno += direction) {
3128                 if (view->ops->grep(view, &view->line[lineno])) {
3129                         select_view_line(view, lineno);
3130                         report("Line %ld matches '%s'", lineno + 1, view->grep);
3131                         return;
3132                 }
3133         }
3135         report("No match found for '%s'", view->grep);
3138 static void
3139 search_view(struct view *view, enum request request)
3141         int regex_err;
3143         if (view->regex) {
3144                 regfree(view->regex);
3145                 *view->grep = 0;
3146         } else {
3147                 view->regex = calloc(1, sizeof(*view->regex));
3148                 if (!view->regex)
3149                         return;
3150         }
3152         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
3153         if (regex_err != 0) {
3154                 char buf[SIZEOF_STR] = "unknown error";
3156                 regerror(regex_err, view->regex, buf, sizeof(buf));
3157                 report("Search failed: %s", buf);
3158                 return;
3159         }
3161         string_copy(view->grep, opt_search);
3163         find_next(view, request);
3166 /*
3167  * Incremental updating
3168  */
3170 static void
3171 reset_view(struct view *view)
3173         int i;
3175         for (i = 0; i < view->lines; i++)
3176                 free(view->line[i].data);
3177         free(view->line);
3179         view->p_offset = view->offset;
3180         view->p_yoffset = view->yoffset;
3181         view->p_lineno = view->lineno;
3183         view->line = NULL;
3184         view->offset = 0;
3185         view->yoffset = 0;
3186         view->lines  = 0;
3187         view->lineno = 0;
3188         view->vid[0] = 0;
3189         view->update_secs = 0;
3192 static const char *
3193 format_arg(const char *name)
3195         static struct {
3196                 const char *name;
3197                 size_t namelen;
3198                 const char *value;
3199                 const char *value_if_empty;
3200         } vars[] = {
3201 #define FORMAT_VAR(name, value, value_if_empty) \
3202         { name, STRING_SIZE(name), value, value_if_empty }
3203                 FORMAT_VAR("%(directory)",      opt_path,       ""),
3204                 FORMAT_VAR("%(file)",           opt_file,       ""),
3205                 FORMAT_VAR("%(ref)",            opt_ref,        "HEAD"),
3206                 FORMAT_VAR("%(head)",           ref_head,       ""),
3207                 FORMAT_VAR("%(commit)",         ref_commit,     ""),
3208                 FORMAT_VAR("%(blob)",           ref_blob,       ""),
3209                 FORMAT_VAR("%(branch)",         ref_branch,     ""),
3210         };
3211         int i;
3213         for (i = 0; i < ARRAY_SIZE(vars); i++)
3214                 if (!strncmp(name, vars[i].name, vars[i].namelen))
3215                         return *vars[i].value ? vars[i].value : vars[i].value_if_empty;
3217         report("Unknown replacement: `%s`", name);
3218         return NULL;
3221 static bool
3222 format_argv(const char ***dst_argv, const char *src_argv[], bool replace)
3224         char buf[SIZEOF_STR];
3225         int argc;
3227         argv_free(*dst_argv);
3229         for (argc = 0; src_argv[argc]; argc++) {
3230                 const char *arg = src_argv[argc];
3231                 size_t bufpos = 0;
3233                 if (!strcmp(arg, "%(fileargs)")) {
3234                         if (!argv_append_array(dst_argv, opt_file_args))
3235                                 break;
3236                         continue;
3238                 } else if (!strcmp(arg, "%(diffargs)")) {
3239                         if (!argv_append_array(dst_argv, opt_diff_args))
3240                                 break;
3241                         continue;
3243                 } else if (!strcmp(arg, "%(revargs)")) {
3244                         if (!argv_append_array(dst_argv, opt_rev_args))
3245                                 break;
3246                         continue;
3247                 }
3249                 while (arg) {
3250                         char *next = strstr(arg, "%(");
3251                         int len = next - arg;
3252                         const char *value;
3254                         if (!next || !replace) {
3255                                 len = strlen(arg);
3256                                 value = "";
3258                         } else {
3259                                 value = format_arg(next);
3261                                 if (!value) {
3262                                         return FALSE;
3263                                 }
3264                         }
3266                         if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
3267                                 return FALSE;
3269                         arg = next && replace ? strchr(next, ')') + 1 : NULL;
3270                 }
3272                 if (!argv_append(dst_argv, buf))
3273                         break;
3274         }
3276         return src_argv[argc] == NULL;
3279 static bool
3280 restore_view_position(struct view *view)
3282         if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
3283                 return FALSE;
3285         /* Changing the view position cancels the restoring. */
3286         /* FIXME: Changing back to the first line is not detected. */
3287         if (view->offset != 0 || view->lineno != 0) {
3288                 view->p_restore = FALSE;
3289                 return FALSE;
3290         }
3292         if (goto_view_line(view, view->p_offset, view->p_lineno) &&
3293             view_is_displayed(view))
3294                 werase(view->win);
3296         view->yoffset = view->p_yoffset;
3297         view->p_restore = FALSE;
3299         return TRUE;
3302 static void
3303 end_update(struct view *view, bool force)
3305         if (!view->pipe)
3306                 return;
3307         while (!view->ops->read(view, NULL))
3308                 if (!force)
3309                         return;
3310         if (force)
3311                 io_kill(view->pipe);
3312         io_done(view->pipe);
3313         view->pipe = NULL;
3316 static void
3317 setup_update(struct view *view, const char *vid)
3319         reset_view(view);
3320         string_copy_rev(view->vid, vid);
3321         view->pipe = &view->io;
3322         view->start_time = time(NULL);
3325 static bool
3326 prepare_io(struct view *view, const char *dir, const char *argv[], bool replace)
3328         view->dir = dir;
3329         return format_argv(&view->argv, argv, replace);
3332 static bool
3333 prepare_update(struct view *view, const char *argv[], const char *dir)
3335         if (view->pipe)
3336                 end_update(view, TRUE);
3337         return prepare_io(view, dir, argv, FALSE);
3340 static bool
3341 start_update(struct view *view, const char **argv, const char *dir)
3343         if (view->pipe)
3344                 io_done(view->pipe);
3345         return prepare_io(view, dir, argv, FALSE) &&
3346                io_run(&view->io, IO_RD, dir, view->argv);
3349 static bool
3350 prepare_update_file(struct view *view, const char *name)
3352         if (view->pipe)
3353                 end_update(view, TRUE);
3354         argv_free(view->argv);
3355         return io_open(&view->io, "%s/%s", opt_cdup[0] ? opt_cdup : ".", name);
3358 static bool
3359 begin_update(struct view *view, bool refresh)
3361         if (view->pipe)
3362                 end_update(view, TRUE);
3364         if (!refresh) {
3365                 if (view->ops->prepare) {
3366                         if (!view->ops->prepare(view))
3367                                 return FALSE;
3368                 } else if (!prepare_io(view, NULL, view->ops->argv, TRUE)) {
3369                         return FALSE;
3370                 }
3372                 /* Put the current ref_* value to the view title ref
3373                  * member. This is needed by the blob view. Most other
3374                  * views sets it automatically after loading because the
3375                  * first line is a commit line. */
3376                 string_copy_rev(view->ref, view->id);
3377         }
3379         if (view->argv && view->argv[0] &&
3380             !io_run(&view->io, IO_RD, view->dir, view->argv))
3381                 return FALSE;
3383         setup_update(view, view->id);
3385         return TRUE;
3388 static bool
3389 update_view(struct view *view)
3391         char out_buffer[BUFSIZ * 2];
3392         char *line;
3393         /* Clear the view and redraw everything since the tree sorting
3394          * might have rearranged things. */
3395         bool redraw = view->lines == 0;
3396         bool can_read = TRUE;
3398         if (!view->pipe)
3399                 return TRUE;
3401         if (!io_can_read(view->pipe)) {
3402                 if (view->lines == 0 && view_is_displayed(view)) {
3403                         time_t secs = time(NULL) - view->start_time;
3405                         if (secs > 1 && secs > view->update_secs) {
3406                                 if (view->update_secs == 0)
3407                                         redraw_view(view);
3408                                 update_view_title(view);
3409                                 view->update_secs = secs;
3410                         }
3411                 }
3412                 return TRUE;
3413         }
3415         for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
3416                 if (opt_iconv_in != ICONV_NONE) {
3417                         ICONV_CONST char *inbuf = line;
3418                         size_t inlen = strlen(line) + 1;
3420                         char *outbuf = out_buffer;
3421                         size_t outlen = sizeof(out_buffer);
3423                         size_t ret;
3425                         ret = iconv(opt_iconv_in, &inbuf, &inlen, &outbuf, &outlen);
3426                         if (ret != (size_t) -1)
3427                                 line = out_buffer;
3428                 }
3430                 if (!view->ops->read(view, line)) {
3431                         report("Allocation failure");
3432                         end_update(view, TRUE);
3433                         return FALSE;
3434                 }
3435         }
3437         {
3438                 unsigned long lines = view->lines;
3439                 int digits;
3441                 for (digits = 0; lines; digits++)
3442                         lines /= 10;
3444                 /* Keep the displayed view in sync with line number scaling. */
3445                 if (digits != view->digits) {
3446                         view->digits = digits;
3447                         if (opt_line_number || view->type == VIEW_BLAME)
3448                                 redraw = TRUE;
3449                 }
3450         }
3452         if (io_error(view->pipe)) {
3453                 report("Failed to read: %s", io_strerror(view->pipe));
3454                 end_update(view, TRUE);
3456         } else if (io_eof(view->pipe)) {
3457                 if (view_is_displayed(view))
3458                         report("");
3459                 end_update(view, FALSE);
3460         }
3462         if (restore_view_position(view))
3463                 redraw = TRUE;
3465         if (!view_is_displayed(view))
3466                 return TRUE;
3468         if (redraw)
3469                 redraw_view_from(view, 0);
3470         else
3471                 redraw_view_dirty(view);
3473         /* Update the title _after_ the redraw so that if the redraw picks up a
3474          * commit reference in view->ref it'll be available here. */
3475         update_view_title(view);
3476         return TRUE;
3479 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
3481 static struct line *
3482 add_line_data(struct view *view, void *data, enum line_type type)
3484         struct line *line;
3486         if (!realloc_lines(&view->line, view->lines, 1))
3487                 return NULL;
3489         line = &view->line[view->lines++];
3490         memset(line, 0, sizeof(*line));
3491         line->type = type;
3492         line->data = data;
3493         line->dirty = 1;
3495         return line;
3498 static struct line *
3499 add_line_text(struct view *view, const char *text, enum line_type type)
3501         char *data = text ? strdup(text) : NULL;
3503         return data ? add_line_data(view, data, type) : NULL;
3506 static struct line *
3507 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
3509         char buf[SIZEOF_STR];
3510         va_list args;
3512         va_start(args, fmt);
3513         if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
3514                 buf[0] = 0;
3515         va_end(args);
3517         return buf[0] ? add_line_text(view, buf, type) : NULL;
3520 /*
3521  * View opening
3522  */
3524 enum open_flags {
3525         OPEN_DEFAULT = 0,       /* Use default view switching. */
3526         OPEN_SPLIT = 1,         /* Split current view. */
3527         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
3528         OPEN_REFRESH = 16,      /* Refresh view using previous command. */
3529         OPEN_PREPARED = 32,     /* Open already prepared command. */
3530 };
3532 static void
3533 open_view(struct view *prev, enum request request, enum open_flags flags)
3535         bool split = !!(flags & OPEN_SPLIT);
3536         bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
3537         bool nomaximize = !!(flags & OPEN_REFRESH);
3538         struct view *view = VIEW(request);
3539         int nviews = displayed_views();
3540         struct view *base_view = display[0];
3542         if (view == prev && nviews == 1 && !reload) {
3543                 report("Already in %s view", view->name);
3544                 return;
3545         }
3547         if (view->git_dir && !opt_git_dir[0]) {
3548                 report("The %s view is disabled in pager view", view->name);
3549                 return;
3550         }
3552         if (split) {
3553                 display[1] = view;
3554                 current_view = 1;
3555                 view->parent = prev;
3556         } else if (!nomaximize) {
3557                 /* Maximize the current view. */
3558                 memset(display, 0, sizeof(display));
3559                 current_view = 0;
3560                 display[current_view] = view;
3561         }
3563         /* No prev signals that this is the first loaded view. */
3564         if (prev && view != prev) {
3565                 view->prev = prev;
3566         }
3568         /* Resize the view when switching between split- and full-screen,
3569          * or when switching between two different full-screen views. */
3570         if (nviews != displayed_views() ||
3571             (nviews == 1 && base_view != display[0]))
3572                 resize_display();
3574         if (view->ops->open) {
3575                 if (view->pipe)
3576                         end_update(view, TRUE);
3577                 if (!view->ops->open(view)) {
3578                         report("Failed to load %s view", view->name);
3579                         return;
3580                 }
3581                 restore_view_position(view);
3583         } else if ((reload || strcmp(view->vid, view->id)) &&
3584                    !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3585                 report("Failed to load %s view", view->name);
3586                 return;
3587         }
3589         if (split && prev->lineno - prev->offset >= prev->height) {
3590                 /* Take the title line into account. */
3591                 int lines = prev->lineno - prev->offset - prev->height + 1;
3593                 /* Scroll the view that was split if the current line is
3594                  * outside the new limited view. */
3595                 do_scroll_view(prev, lines);
3596         }
3598         if (prev && view != prev && split && view_is_displayed(prev)) {
3599                 /* "Blur" the previous view. */
3600                 update_view_title(prev);
3601         }
3603         if (view->pipe && view->lines == 0) {
3604                 /* Clear the old view and let the incremental updating refill
3605                  * the screen. */
3606                 werase(view->win);
3607                 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3608                 report("");
3609         } else if (view_is_displayed(view)) {
3610                 redraw_view(view);
3611                 report("");
3612         }
3615 static void
3616 open_external_viewer(const char *argv[], const char *dir)
3618         def_prog_mode();           /* save current tty modes */
3619         endwin();                  /* restore original tty modes */
3620         io_run_fg(argv, dir);
3621         fprintf(stderr, "Press Enter to continue");
3622         getc(opt_tty);
3623         reset_prog_mode();
3624         redraw_display(TRUE);
3627 static void
3628 open_mergetool(const char *file)
3630         const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3632         open_external_viewer(mergetool_argv, opt_cdup);
3635 static void
3636 open_editor(const char *file)
3638         const char *editor_argv[] = { "vi", file, NULL };
3639         const char *editor;
3641         editor = getenv("GIT_EDITOR");
3642         if (!editor && *opt_editor)
3643                 editor = opt_editor;
3644         if (!editor)
3645                 editor = getenv("VISUAL");
3646         if (!editor)
3647                 editor = getenv("EDITOR");
3648         if (!editor)
3649                 editor = "vi";
3651         editor_argv[0] = editor;
3652         open_external_viewer(editor_argv, opt_cdup);
3655 static void
3656 open_run_request(enum request request)
3658         struct run_request *req = get_run_request(request);
3659         const char **argv = NULL;
3661         if (!req) {
3662                 report("Unknown run request");
3663                 return;
3664         }
3666         if (format_argv(&argv, req->argv, TRUE))
3667                 open_external_viewer(argv, NULL);
3668         if (argv)
3669                 argv_free(argv);
3670         free(argv);
3673 /*
3674  * User request switch noodle
3675  */
3677 static int
3678 view_driver(struct view *view, enum request request)
3680         int i;
3682         if (request == REQ_NONE)
3683                 return TRUE;
3685         if (request > REQ_NONE) {
3686                 open_run_request(request);
3687                 view_request(view, REQ_REFRESH);
3688                 return TRUE;
3689         }
3691         request = view_request(view, request);
3692         if (request == REQ_NONE)
3693                 return TRUE;
3695         switch (request) {
3696         case REQ_MOVE_UP:
3697         case REQ_MOVE_DOWN:
3698         case REQ_MOVE_PAGE_UP:
3699         case REQ_MOVE_PAGE_DOWN:
3700         case REQ_MOVE_FIRST_LINE:
3701         case REQ_MOVE_LAST_LINE:
3702                 move_view(view, request);
3703                 break;
3705         case REQ_SCROLL_FIRST_COL:
3706         case REQ_SCROLL_LEFT:
3707         case REQ_SCROLL_RIGHT:
3708         case REQ_SCROLL_LINE_DOWN:
3709         case REQ_SCROLL_LINE_UP:
3710         case REQ_SCROLL_PAGE_DOWN:
3711         case REQ_SCROLL_PAGE_UP:
3712                 scroll_view(view, request);
3713                 break;
3715         case REQ_VIEW_BLAME:
3716                 if (!opt_file[0]) {
3717                         report("No file chosen, press %s to open tree view",
3718                                get_key(view->keymap, REQ_VIEW_TREE));
3719                         break;
3720                 }
3721                 open_view(view, request, OPEN_DEFAULT);
3722                 break;
3724         case REQ_VIEW_BLOB:
3725                 if (!ref_blob[0]) {
3726                         report("No file chosen, press %s to open tree view",
3727                                get_key(view->keymap, REQ_VIEW_TREE));
3728                         break;
3729                 }
3730                 open_view(view, request, OPEN_DEFAULT);
3731                 break;
3733         case REQ_VIEW_PAGER:
3734                 if (view == NULL) {
3735                         if (!io_open(&VIEW(REQ_VIEW_PAGER)->io, ""))
3736                                 die("Failed to open stdin");
3737                         open_view(view, request, OPEN_PREPARED);
3738                         break;
3739                 }
3741                 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3742                         report("No pager content, press %s to run command from prompt",
3743                                get_key(view->keymap, REQ_PROMPT));
3744                         break;
3745                 }
3746                 open_view(view, request, OPEN_DEFAULT);
3747                 break;
3749         case REQ_VIEW_STAGE:
3750                 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3751                         report("No stage content, press %s to open the status view and choose file",
3752                                get_key(view->keymap, REQ_VIEW_STATUS));
3753                         break;
3754                 }
3755                 open_view(view, request, OPEN_DEFAULT);
3756                 break;
3758         case REQ_VIEW_STATUS:
3759                 if (opt_is_inside_work_tree == FALSE) {
3760                         report("The status view requires a working tree");
3761                         break;
3762                 }
3763                 open_view(view, request, OPEN_DEFAULT);
3764                 break;
3766         case REQ_VIEW_MAIN:
3767         case REQ_VIEW_DIFF:
3768         case REQ_VIEW_LOG:
3769         case REQ_VIEW_TREE:
3770         case REQ_VIEW_HELP:
3771         case REQ_VIEW_BRANCH:
3772                 open_view(view, request, OPEN_DEFAULT);
3773                 break;
3775         case REQ_NEXT:
3776         case REQ_PREVIOUS:
3777                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3779                 if (view->parent) {
3780                         int line;
3782                         view = view->parent;
3783                         line = view->lineno;
3784                         move_view(view, request);
3785                         if (view_is_displayed(view))
3786                                 update_view_title(view);
3787                         if (line != view->lineno)
3788                                 view_request(view, REQ_ENTER);
3789                 } else {
3790                         move_view(view, request);
3791                 }
3792                 break;
3794         case REQ_VIEW_NEXT:
3795         {
3796                 int nviews = displayed_views();
3797                 int next_view = (current_view + 1) % nviews;
3799                 if (next_view == current_view) {
3800                         report("Only one view is displayed");
3801                         break;
3802                 }
3804                 current_view = next_view;
3805                 /* Blur out the title of the previous view. */
3806                 update_view_title(view);
3807                 report("");
3808                 break;
3809         }
3810         case REQ_REFRESH:
3811                 report("Refreshing is not yet supported for the %s view", view->name);
3812                 break;
3814         case REQ_MAXIMIZE:
3815                 if (displayed_views() == 2)
3816                         maximize_view(view);
3817                 break;
3819         case REQ_OPTIONS:
3820                 open_option_menu();
3821                 break;
3823         case REQ_TOGGLE_LINENO:
3824                 toggle_view_option(&opt_line_number, "line numbers");
3825                 break;
3827         case REQ_TOGGLE_DATE:
3828                 toggle_date();
3829                 break;
3831         case REQ_TOGGLE_AUTHOR:
3832                 toggle_author();
3833                 break;
3835         case REQ_TOGGLE_REV_GRAPH:
3836                 toggle_view_option(&opt_rev_graph, "revision graph display");
3837                 break;
3839         case REQ_TOGGLE_REFS:
3840                 toggle_view_option(&opt_show_refs, "reference display");
3841                 break;
3843         case REQ_TOGGLE_SORT_FIELD:
3844         case REQ_TOGGLE_SORT_ORDER:
3845                 report("Sorting is not yet supported for the %s view", view->name);
3846                 break;
3848         case REQ_SEARCH:
3849         case REQ_SEARCH_BACK:
3850                 search_view(view, request);
3851                 break;
3853         case REQ_FIND_NEXT:
3854         case REQ_FIND_PREV:
3855                 find_next(view, request);
3856                 break;
3858         case REQ_STOP_LOADING:
3859                 foreach_view(view, i) {
3860                         if (view->pipe)
3861                                 report("Stopped loading the %s view", view->name),
3862                         end_update(view, TRUE);
3863                 }
3864                 break;
3866         case REQ_SHOW_VERSION:
3867                 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3868                 return TRUE;
3870         case REQ_SCREEN_REDRAW:
3871                 redraw_display(TRUE);
3872                 break;
3874         case REQ_EDIT:
3875                 report("Nothing to edit");
3876                 break;
3878         case REQ_ENTER:
3879                 report("Nothing to enter");
3880                 break;
3882         case REQ_VIEW_CLOSE:
3883                 /* XXX: Mark closed views by letting view->prev point to the
3884                  * view itself. Parents to closed view should never be
3885                  * followed. */
3886                 if (view->prev && view->prev != view) {
3887                         maximize_view(view->prev);
3888                         view->prev = view;
3889                         break;
3890                 }
3891                 /* Fall-through */
3892         case REQ_QUIT:
3893                 return FALSE;
3895         default:
3896                 report("Unknown key, press %s for help",
3897                        get_key(view->keymap, REQ_VIEW_HELP));
3898                 return TRUE;
3899         }
3901         return TRUE;
3905 /*
3906  * View backend utilities
3907  */
3909 enum sort_field {
3910         ORDERBY_NAME,
3911         ORDERBY_DATE,
3912         ORDERBY_AUTHOR,
3913 };
3915 struct sort_state {
3916         const enum sort_field *fields;
3917         size_t size, current;
3918         bool reverse;
3919 };
3921 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3922 #define get_sort_field(state) ((state).fields[(state).current])
3923 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3925 static void
3926 sort_view(struct view *view, enum request request, struct sort_state *state,
3927           int (*compare)(const void *, const void *))
3929         switch (request) {
3930         case REQ_TOGGLE_SORT_FIELD:
3931                 state->current = (state->current + 1) % state->size;
3932                 break;
3934         case REQ_TOGGLE_SORT_ORDER:
3935                 state->reverse = !state->reverse;
3936                 break;
3937         default:
3938                 die("Not a sort request");
3939         }
3941         qsort(view->line, view->lines, sizeof(*view->line), compare);
3942         redraw_view(view);
3945 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3947 /* Small author cache to reduce memory consumption. It uses binary
3948  * search to lookup or find place to position new entries. No entries
3949  * are ever freed. */
3950 static const char *
3951 get_author(const char *name)
3953         static const char **authors;
3954         static size_t authors_size;
3955         int from = 0, to = authors_size - 1;
3957         while (from <= to) {
3958                 size_t pos = (to + from) / 2;
3959                 int cmp = strcmp(name, authors[pos]);
3961                 if (!cmp)
3962                         return authors[pos];
3964                 if (cmp < 0)
3965                         to = pos - 1;
3966                 else
3967                         from = pos + 1;
3968         }
3970         if (!realloc_authors(&authors, authors_size, 1))
3971                 return NULL;
3972         name = strdup(name);
3973         if (!name)
3974                 return NULL;
3976         memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3977         authors[from] = name;
3978         authors_size++;
3980         return name;
3983 static void
3984 parse_timesec(struct time *time, const char *sec)
3986         time->sec = (time_t) atol(sec);
3989 static void
3990 parse_timezone(struct time *time, const char *zone)
3992         long tz;
3994         tz  = ('0' - zone[1]) * 60 * 60 * 10;
3995         tz += ('0' - zone[2]) * 60 * 60;
3996         tz += ('0' - zone[3]) * 60 * 10;
3997         tz += ('0' - zone[4]) * 60;
3999         if (zone[0] == '-')
4000                 tz = -tz;
4002         time->tz = tz;
4003         time->sec -= tz;
4006 /* Parse author lines where the name may be empty:
4007  *      author  <email@address.tld> 1138474660 +0100
4008  */
4009 static void
4010 parse_author_line(char *ident, const char **author, struct time *time)
4012         char *nameend = strchr(ident, '<');
4013         char *emailend = strchr(ident, '>');
4015         if (nameend && emailend)
4016                 *nameend = *emailend = 0;
4017         ident = chomp_string(ident);
4018         if (!*ident) {
4019                 if (nameend)
4020                         ident = chomp_string(nameend + 1);
4021                 if (!*ident)
4022                         ident = "Unknown";
4023         }
4025         *author = get_author(ident);
4027         /* Parse epoch and timezone */
4028         if (emailend && emailend[1] == ' ') {
4029                 char *secs = emailend + 2;
4030                 char *zone = strchr(secs, ' ');
4032                 parse_timesec(time, secs);
4034                 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
4035                         parse_timezone(time, zone + 1);
4036         }
4039 /*
4040  * Pager backend
4041  */
4043 static bool
4044 pager_draw(struct view *view, struct line *line, unsigned int lineno)
4046         if (opt_line_number && draw_lineno(view, lineno))
4047                 return TRUE;
4049         draw_text(view, line->type, line->data, TRUE);
4050         return TRUE;
4053 static bool
4054 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
4056         const char *describe_argv[] = { "git", "describe", commit_id, NULL };
4057         char ref[SIZEOF_STR];
4059         if (!io_run_buf(describe_argv, ref, sizeof(ref)) || !*ref)
4060                 return TRUE;
4062         /* This is the only fatal call, since it can "corrupt" the buffer. */
4063         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
4064                 return FALSE;
4066         return TRUE;
4069 static void
4070 add_pager_refs(struct view *view, struct line *line)
4072         char buf[SIZEOF_STR];
4073         char *commit_id = (char *)line->data + STRING_SIZE("commit ");
4074         struct ref_list *list;
4075         size_t bufpos = 0, i;
4076         const char *sep = "Refs: ";
4077         bool is_tag = FALSE;
4079         assert(line->type == LINE_COMMIT);
4081         list = get_ref_list(commit_id);
4082         if (!list) {
4083                 if (view->type == VIEW_DIFF)
4084                         goto try_add_describe_ref;
4085                 return;
4086         }
4088         for (i = 0; i < list->size; i++) {
4089                 struct ref *ref = list->refs[i];
4090                 const char *fmt = ref->tag    ? "%s[%s]" :
4091                                   ref->remote ? "%s<%s>" : "%s%s";
4093                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
4094                         return;
4095                 sep = ", ";
4096                 if (ref->tag)
4097                         is_tag = TRUE;
4098         }
4100         if (!is_tag && view->type == VIEW_DIFF) {
4101 try_add_describe_ref:
4102                 /* Add <tag>-g<commit_id> "fake" reference. */
4103                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
4104                         return;
4105         }
4107         if (bufpos == 0)
4108                 return;
4110         add_line_text(view, buf, LINE_PP_REFS);
4113 static bool
4114 pager_read(struct view *view, char *data)
4116         struct line *line;
4118         if (!data)
4119                 return TRUE;
4121         line = add_line_text(view, data, get_line_type(data));
4122         if (!line)
4123                 return FALSE;
4125         if (line->type == LINE_COMMIT &&
4126             (view->type == VIEW_DIFF ||
4127              view->type == VIEW_LOG))
4128                 add_pager_refs(view, line);
4130         return TRUE;
4133 static enum request
4134 pager_request(struct view *view, enum request request, struct line *line)
4136         int split = 0;
4138         if (request != REQ_ENTER)
4139                 return request;
4141         if (line->type == LINE_COMMIT &&
4142            (view->type == VIEW_LOG ||
4143             view->type == VIEW_PAGER)) {
4144                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
4145                 split = 1;
4146         }
4148         /* Always scroll the view even if it was split. That way
4149          * you can use Enter to scroll through the log view and
4150          * split open each commit diff. */
4151         scroll_view(view, REQ_SCROLL_LINE_DOWN);
4153         /* FIXME: A minor workaround. Scrolling the view will call report("")
4154          * but if we are scrolling a non-current view this won't properly
4155          * update the view title. */
4156         if (split)
4157                 update_view_title(view);
4159         return REQ_NONE;
4162 static bool
4163 pager_grep(struct view *view, struct line *line)
4165         const char *text[] = { line->data, NULL };
4167         return grep_text(view, text);
4170 static void
4171 pager_select(struct view *view, struct line *line)
4173         if (line->type == LINE_COMMIT) {
4174                 char *text = (char *)line->data + STRING_SIZE("commit ");
4176                 if (view->type != VIEW_PAGER)
4177                         string_copy_rev(view->ref, text);
4178                 string_copy_rev(ref_commit, text);
4179         }
4182 static struct view_ops pager_ops = {
4183         "line",
4184         NULL,
4185         NULL,
4186         pager_read,
4187         pager_draw,
4188         pager_request,
4189         pager_grep,
4190         pager_select,
4191 };
4193 static const char *log_argv[SIZEOF_ARG] = {
4194         "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
4195 };
4197 static enum request
4198 log_request(struct view *view, enum request request, struct line *line)
4200         switch (request) {
4201         case REQ_REFRESH:
4202                 load_refs();
4203                 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
4204                 return REQ_NONE;
4205         default:
4206                 return pager_request(view, request, line);
4207         }
4210 static struct view_ops log_ops = {
4211         "line",
4212         log_argv,
4213         NULL,
4214         pager_read,
4215         pager_draw,
4216         log_request,
4217         pager_grep,
4218         pager_select,
4219 };
4221 static const char *diff_argv[SIZEOF_ARG] = {
4222         "git", "show", "--pretty=fuller", "--no-color", "--root",
4223                 "--patch-with-stat", "--find-copies-harder", "-C",
4224                 "%(diffargs)", "%(commit)", "--", "%(fileargs)", NULL
4225 };
4227 static bool
4228 diff_read(struct view *view, char *data)
4230         if (!data) {
4231                 /* Fall back to retry if no diff will be shown. */
4232                 if (view->lines == 0 && opt_file_args) {
4233                         int pos = argv_size(view->argv)
4234                                 - argv_size(opt_file_args) - 1;
4236                         if (pos > 0 && !strcmp(view->argv[pos], "--")) {
4237                                 for (; view->argv[pos]; pos++) {
4238                                         free((void *) view->argv[pos]);
4239                                         view->argv[pos] = NULL;
4240                                 }
4242                                 if (view->pipe)
4243                                         io_done(view->pipe);
4244                                 if (io_run(&view->io, IO_RD, view->dir, view->argv))
4245                                         return FALSE;
4246                         }
4247                 }
4248                 return TRUE;
4249         }
4251         return pager_read(view, data);
4254 static struct view_ops diff_ops = {
4255         "line",
4256         diff_argv,
4257         NULL,
4258         diff_read,
4259         pager_draw,
4260         pager_request,
4261         pager_grep,
4262         pager_select,
4263 };
4265 /*
4266  * Help backend
4267  */
4269 static bool help_keymap_hidden[ARRAY_SIZE(keymap_table)];
4271 static bool
4272 help_open_keymap_title(struct view *view, enum keymap keymap)
4274         struct line *line;
4276         line = add_line_format(view, LINE_HELP_KEYMAP, "[%c] %s bindings",
4277                                help_keymap_hidden[keymap] ? '+' : '-',
4278                                enum_name(keymap_table[keymap]));
4279         if (line)
4280                 line->other = keymap;
4282         return help_keymap_hidden[keymap];
4285 static void
4286 help_open_keymap(struct view *view, enum keymap keymap)
4288         const char *group = NULL;
4289         char buf[SIZEOF_STR];
4290         size_t bufpos;
4291         bool add_title = TRUE;
4292         int i;
4294         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
4295                 const char *key = NULL;
4297                 if (req_info[i].request == REQ_NONE)
4298                         continue;
4300                 if (!req_info[i].request) {
4301                         group = req_info[i].help;
4302                         continue;
4303                 }
4305                 key = get_keys(keymap, req_info[i].request, TRUE);
4306                 if (!key || !*key)
4307                         continue;
4309                 if (add_title && help_open_keymap_title(view, keymap))
4310                         return;
4311                 add_title = FALSE;
4313                 if (group) {
4314                         add_line_text(view, group, LINE_HELP_GROUP);
4315                         group = NULL;
4316                 }
4318                 add_line_format(view, LINE_DEFAULT, "    %-25s %-20s %s", key,
4319                                 enum_name(req_info[i]), req_info[i].help);
4320         }
4322         group = "External commands:";
4324         for (i = 0; i < run_requests; i++) {
4325                 struct run_request *req = get_run_request(REQ_NONE + i + 1);
4326                 const char *key;
4327                 int argc;
4329                 if (!req || req->keymap != keymap)
4330                         continue;
4332                 key = get_key_name(req->key);
4333                 if (!*key)
4334                         key = "(no key defined)";
4336                 if (add_title && help_open_keymap_title(view, keymap))
4337                         return;
4338                 if (group) {
4339                         add_line_text(view, group, LINE_HELP_GROUP);
4340                         group = NULL;
4341                 }
4343                 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
4344                         if (!string_format_from(buf, &bufpos, "%s%s",
4345                                                 argc ? " " : "", req->argv[argc]))
4346                                 return;
4348                 add_line_format(view, LINE_DEFAULT, "    %-25s `%s`", key, buf);
4349         }
4352 static bool
4353 help_open(struct view *view)
4355         enum keymap keymap;
4357         reset_view(view);
4358         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
4359         add_line_text(view, "", LINE_DEFAULT);
4361         for (keymap = 0; keymap < ARRAY_SIZE(keymap_table); keymap++)
4362                 help_open_keymap(view, keymap);
4364         return TRUE;
4367 static enum request
4368 help_request(struct view *view, enum request request, struct line *line)
4370         switch (request) {
4371         case REQ_ENTER:
4372                 if (line->type == LINE_HELP_KEYMAP) {
4373                         help_keymap_hidden[line->other] =
4374                                 !help_keymap_hidden[line->other];
4375                         view->p_restore = TRUE;
4376                         open_view(view, REQ_VIEW_HELP, OPEN_REFRESH);
4377                 }
4379                 return REQ_NONE;
4380         default:
4381                 return pager_request(view, request, line);
4382         }
4385 static struct view_ops help_ops = {
4386         "line",
4387         NULL,
4388         help_open,
4389         NULL,
4390         pager_draw,
4391         help_request,
4392         pager_grep,
4393         pager_select,
4394 };
4397 /*
4398  * Tree backend
4399  */
4401 struct tree_stack_entry {
4402         struct tree_stack_entry *prev;  /* Entry below this in the stack */
4403         unsigned long lineno;           /* Line number to restore */
4404         char *name;                     /* Position of name in opt_path */
4405 };
4407 /* The top of the path stack. */
4408 static struct tree_stack_entry *tree_stack = NULL;
4409 unsigned long tree_lineno = 0;
4411 static void
4412 pop_tree_stack_entry(void)
4414         struct tree_stack_entry *entry = tree_stack;
4416         tree_lineno = entry->lineno;
4417         entry->name[0] = 0;
4418         tree_stack = entry->prev;
4419         free(entry);
4422 static void
4423 push_tree_stack_entry(const char *name, unsigned long lineno)
4425         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
4426         size_t pathlen = strlen(opt_path);
4428         if (!entry)
4429                 return;
4431         entry->prev = tree_stack;
4432         entry->name = opt_path + pathlen;
4433         tree_stack = entry;
4435         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
4436                 pop_tree_stack_entry();
4437                 return;
4438         }
4440         /* Move the current line to the first tree entry. */
4441         tree_lineno = 1;
4442         entry->lineno = lineno;
4445 /* Parse output from git-ls-tree(1):
4446  *
4447  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4448  */
4450 #define SIZEOF_TREE_ATTR \
4451         STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4453 #define SIZEOF_TREE_MODE \
4454         STRING_SIZE("100644 ")
4456 #define TREE_ID_OFFSET \
4457         STRING_SIZE("100644 blob ")
4459 struct tree_entry {
4460         char id[SIZEOF_REV];
4461         mode_t mode;
4462         struct time time;               /* Date from the author ident. */
4463         const char *author;             /* Author of the commit. */
4464         char name[1];
4465 };
4467 static const char *
4468 tree_path(const struct line *line)
4470         return ((struct tree_entry *) line->data)->name;
4473 static int
4474 tree_compare_entry(const struct line *line1, const struct line *line2)
4476         if (line1->type != line2->type)
4477                 return line1->type == LINE_TREE_DIR ? -1 : 1;
4478         return strcmp(tree_path(line1), tree_path(line2));
4481 static const enum sort_field tree_sort_fields[] = {
4482         ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4483 };
4484 static struct sort_state tree_sort_state = SORT_STATE(tree_sort_fields);
4486 static int
4487 tree_compare(const void *l1, const void *l2)
4489         const struct line *line1 = (const struct line *) l1;
4490         const struct line *line2 = (const struct line *) l2;
4491         const struct tree_entry *entry1 = ((const struct line *) l1)->data;
4492         const struct tree_entry *entry2 = ((const struct line *) l2)->data;
4494         if (line1->type == LINE_TREE_HEAD)
4495                 return -1;
4496         if (line2->type == LINE_TREE_HEAD)
4497                 return 1;
4499         switch (get_sort_field(tree_sort_state)) {
4500         case ORDERBY_DATE:
4501                 return sort_order(tree_sort_state, timecmp(&entry1->time, &entry2->time));
4503         case ORDERBY_AUTHOR:
4504                 return sort_order(tree_sort_state, strcmp(entry1->author, entry2->author));
4506         case ORDERBY_NAME:
4507         default:
4508                 return sort_order(tree_sort_state, tree_compare_entry(line1, line2));
4509         }
4513 static struct line *
4514 tree_entry(struct view *view, enum line_type type, const char *path,
4515            const char *mode, const char *id)
4517         struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
4518         struct line *line = entry ? add_line_data(view, entry, type) : NULL;
4520         if (!entry || !line) {
4521                 free(entry);
4522                 return NULL;
4523         }
4525         strncpy(entry->name, path, strlen(path));
4526         if (mode)
4527                 entry->mode = strtoul(mode, NULL, 8);
4528         if (id)
4529                 string_copy_rev(entry->id, id);
4531         return line;
4534 static bool
4535 tree_read_date(struct view *view, char *text, bool *read_date)
4537         static const char *author_name;
4538         static struct time author_time;
4540         if (!text && *read_date) {
4541                 *read_date = FALSE;
4542                 return TRUE;
4544         } else if (!text) {
4545                 char *path = *opt_path ? opt_path : ".";
4546                 /* Find next entry to process */
4547                 const char *log_file[] = {
4548                         "git", "log", "--no-color", "--pretty=raw",
4549                                 "--cc", "--raw", view->id, "--", path, NULL
4550                 };
4552                 if (!view->lines) {
4553                         tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
4554                         report("Tree is empty");
4555                         return TRUE;
4556                 }
4558                 if (!start_update(view, log_file, opt_cdup)) {
4559                         report("Failed to load tree data");
4560                         return TRUE;
4561                 }
4563                 *read_date = TRUE;
4564                 return FALSE;
4566         } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
4567                 parse_author_line(text + STRING_SIZE("author "),
4568                                   &author_name, &author_time);
4570         } else if (*text == ':') {
4571                 char *pos;
4572                 size_t annotated = 1;
4573                 size_t i;
4575                 pos = strchr(text, '\t');
4576                 if (!pos)
4577                         return TRUE;
4578                 text = pos + 1;
4579                 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
4580                         text += strlen(opt_path);
4581                 pos = strchr(text, '/');
4582                 if (pos)
4583                         *pos = 0;
4585                 for (i = 1; i < view->lines; i++) {
4586                         struct line *line = &view->line[i];
4587                         struct tree_entry *entry = line->data;
4589                         annotated += !!entry->author;
4590                         if (entry->author || strcmp(entry->name, text))
4591                                 continue;
4593                         entry->author = author_name;
4594                         entry->time = author_time;
4595                         line->dirty = 1;
4596                         break;
4597                 }
4599                 if (annotated == view->lines)
4600                         io_kill(view->pipe);
4601         }
4602         return TRUE;
4605 static bool
4606 tree_read(struct view *view, char *text)
4608         static bool read_date = FALSE;
4609         struct tree_entry *data;
4610         struct line *entry, *line;
4611         enum line_type type;
4612         size_t textlen = text ? strlen(text) : 0;
4613         char *path = text + SIZEOF_TREE_ATTR;
4615         if (read_date || !text)
4616                 return tree_read_date(view, text, &read_date);
4618         if (textlen <= SIZEOF_TREE_ATTR)
4619                 return FALSE;
4620         if (view->lines == 0 &&
4621             !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
4622                 return FALSE;
4624         /* Strip the path part ... */
4625         if (*opt_path) {
4626                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
4627                 size_t striplen = strlen(opt_path);
4629                 if (pathlen > striplen)
4630                         memmove(path, path + striplen,
4631                                 pathlen - striplen + 1);
4633                 /* Insert "link" to parent directory. */
4634                 if (view->lines == 1 &&
4635                     !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
4636                         return FALSE;
4637         }
4639         type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
4640         entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
4641         if (!entry)
4642                 return FALSE;
4643         data = entry->data;
4645         /* Skip "Directory ..." and ".." line. */
4646         for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
4647                 if (tree_compare_entry(line, entry) <= 0)
4648                         continue;
4650                 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4652                 line->data = data;
4653                 line->type = type;
4654                 for (; line <= entry; line++)
4655                         line->dirty = line->cleareol = 1;
4656                 return TRUE;
4657         }
4659         if (tree_lineno > view->lineno) {
4660                 view->lineno = tree_lineno;
4661                 tree_lineno = 0;
4662         }
4664         return TRUE;
4667 static bool
4668 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4670         struct tree_entry *entry = line->data;
4672         if (line->type == LINE_TREE_HEAD) {
4673                 if (draw_text(view, line->type, "Directory path /", TRUE))
4674                         return TRUE;
4675         } else {
4676                 if (draw_mode(view, entry->mode))
4677                         return TRUE;
4679                 if (opt_author && draw_author(view, entry->author))
4680                         return TRUE;
4682                 if (opt_date && draw_date(view, &entry->time))
4683                         return TRUE;
4684         }
4685         if (draw_text(view, line->type, entry->name, TRUE))
4686                 return TRUE;
4687         return TRUE;
4690 static void
4691 open_blob_editor(const char *id)
4693         const char *blob_argv[] = { "git", "cat-file", "blob", id, NULL };
4694         char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4695         int fd = mkstemp(file);
4697         if (fd == -1)
4698                 report("Failed to create temporary file");
4699         else if (!io_run_append(blob_argv, fd))
4700                 report("Failed to save blob data to file");
4701         else
4702                 open_editor(file);
4703         if (fd != -1)
4704                 unlink(file);
4707 static enum request
4708 tree_request(struct view *view, enum request request, struct line *line)
4710         enum open_flags flags;
4711         struct tree_entry *entry = line->data;
4713         switch (request) {
4714         case REQ_VIEW_BLAME:
4715                 if (line->type != LINE_TREE_FILE) {
4716                         report("Blame only supported for files");
4717                         return REQ_NONE;
4718                 }
4720                 string_copy(opt_ref, view->vid);
4721                 return request;
4723         case REQ_EDIT:
4724                 if (line->type != LINE_TREE_FILE) {
4725                         report("Edit only supported for files");
4726                 } else if (!is_head_commit(view->vid)) {
4727                         open_blob_editor(entry->id);
4728                 } else {
4729                         open_editor(opt_file);
4730                 }
4731                 return REQ_NONE;
4733         case REQ_TOGGLE_SORT_FIELD:
4734         case REQ_TOGGLE_SORT_ORDER:
4735                 sort_view(view, request, &tree_sort_state, tree_compare);
4736                 return REQ_NONE;
4738         case REQ_PARENT:
4739                 if (!*opt_path) {
4740                         /* quit view if at top of tree */
4741                         return REQ_VIEW_CLOSE;
4742                 }
4743                 /* fake 'cd  ..' */
4744                 line = &view->line[1];
4745                 break;
4747         case REQ_ENTER:
4748                 break;
4750         default:
4751                 return request;
4752         }
4754         /* Cleanup the stack if the tree view is at a different tree. */
4755         while (!*opt_path && tree_stack)
4756                 pop_tree_stack_entry();
4758         switch (line->type) {
4759         case LINE_TREE_DIR:
4760                 /* Depending on whether it is a subdirectory or parent link
4761                  * mangle the path buffer. */
4762                 if (line == &view->line[1] && *opt_path) {
4763                         pop_tree_stack_entry();
4765                 } else {
4766                         const char *basename = tree_path(line);
4768                         push_tree_stack_entry(basename, view->lineno);
4769                 }
4771                 /* Trees and subtrees share the same ID, so they are not not
4772                  * unique like blobs. */
4773                 flags = OPEN_RELOAD;
4774                 request = REQ_VIEW_TREE;
4775                 break;
4777         case LINE_TREE_FILE:
4778                 flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
4779                 request = REQ_VIEW_BLOB;
4780                 break;
4782         default:
4783                 return REQ_NONE;
4784         }
4786         open_view(view, request, flags);
4787         if (request == REQ_VIEW_TREE)
4788                 view->lineno = tree_lineno;
4790         return REQ_NONE;
4793 static bool
4794 tree_grep(struct view *view, struct line *line)
4796         struct tree_entry *entry = line->data;
4797         const char *text[] = {
4798                 entry->name,
4799                 opt_author ? entry->author : "",
4800                 mkdate(&entry->time, opt_date),
4801                 NULL
4802         };
4804         return grep_text(view, text);
4807 static void
4808 tree_select(struct view *view, struct line *line)
4810         struct tree_entry *entry = line->data;
4812         if (line->type == LINE_TREE_FILE) {
4813                 string_copy_rev(ref_blob, entry->id);
4814                 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4816         } else if (line->type != LINE_TREE_DIR) {
4817                 return;
4818         }
4820         string_copy_rev(view->ref, entry->id);
4823 static bool
4824 tree_prepare(struct view *view)
4826         if (view->lines == 0 && opt_prefix[0]) {
4827                 char *pos = opt_prefix;
4829                 while (pos && *pos) {
4830                         char *end = strchr(pos, '/');
4832                         if (end)
4833                                 *end = 0;
4834                         push_tree_stack_entry(pos, 0);
4835                         pos = end;
4836                         if (end) {
4837                                 *end = '/';
4838                                 pos++;
4839                         }
4840                 }
4842         } else if (strcmp(view->vid, view->id)) {
4843                 opt_path[0] = 0;
4844         }
4846         return prepare_io(view, opt_cdup, view->ops->argv, TRUE);
4849 static const char *tree_argv[SIZEOF_ARG] = {
4850         "git", "ls-tree", "%(commit)", "%(directory)", NULL
4851 };
4853 static struct view_ops tree_ops = {
4854         "file",
4855         tree_argv,
4856         NULL,
4857         tree_read,
4858         tree_draw,
4859         tree_request,
4860         tree_grep,
4861         tree_select,
4862         tree_prepare,
4863 };
4865 static bool
4866 blob_read(struct view *view, char *line)
4868         if (!line)
4869                 return TRUE;
4870         return add_line_text(view, line, LINE_DEFAULT) != NULL;
4873 static enum request
4874 blob_request(struct view *view, enum request request, struct line *line)
4876         switch (request) {
4877         case REQ_EDIT:
4878                 open_blob_editor(view->vid);
4879                 return REQ_NONE;
4880         default:
4881                 return pager_request(view, request, line);
4882         }
4885 static const char *blob_argv[SIZEOF_ARG] = {
4886         "git", "cat-file", "blob", "%(blob)", NULL
4887 };
4889 static struct view_ops blob_ops = {
4890         "line",
4891         blob_argv,
4892         NULL,
4893         blob_read,
4894         pager_draw,
4895         blob_request,
4896         pager_grep,
4897         pager_select,
4898 };
4900 /*
4901  * Blame backend
4902  *
4903  * Loading the blame view is a two phase job:
4904  *
4905  *  1. File content is read either using opt_file from the
4906  *     filesystem or using git-cat-file.
4907  *  2. Then blame information is incrementally added by
4908  *     reading output from git-blame.
4909  */
4911 struct blame_commit {
4912         char id[SIZEOF_REV];            /* SHA1 ID. */
4913         char title[128];                /* First line of the commit message. */
4914         const char *author;             /* Author of the commit. */
4915         struct time time;               /* Date from the author ident. */
4916         char filename[128];             /* Name of file. */
4917         char parent_id[SIZEOF_REV];     /* Parent/previous SHA1 ID. */
4918         char parent_filename[128];      /* Parent/previous name of file. */
4919 };
4921 struct blame {
4922         struct blame_commit *commit;
4923         unsigned long lineno;
4924         char text[1];
4925 };
4927 static bool
4928 blame_open(struct view *view)
4930         char path[SIZEOF_STR];
4931         size_t i;
4933         if (!view->prev && *opt_prefix) {
4934                 string_copy(path, opt_file);
4935                 if (!string_format(opt_file, "%s%s", opt_prefix, path))
4936                         return FALSE;
4937         }
4939         if (*opt_ref || !io_open(&view->io, "%s%s", opt_cdup, opt_file)) {
4940                 const char *blame_cat_file_argv[] = {
4941                         "git", "cat-file", "blob", path, NULL
4942                 };
4944                 if (!string_format(path, "%s:%s", opt_ref, opt_file) ||
4945                     !start_update(view, blame_cat_file_argv, opt_cdup))
4946                         return FALSE;
4947         }
4949         /* First pass: remove multiple references to the same commit. */
4950         for (i = 0; i < view->lines; i++) {
4951                 struct blame *blame = view->line[i].data;
4953                 if (blame->commit && blame->commit->id[0])
4954                         blame->commit->id[0] = 0;
4955                 else
4956                         blame->commit = NULL;
4957         }
4959         /* Second pass: free existing references. */
4960         for (i = 0; i < view->lines; i++) {
4961                 struct blame *blame = view->line[i].data;
4963                 if (blame->commit)
4964                         free(blame->commit);
4965         }
4967         setup_update(view, opt_file);
4968         string_format(view->ref, "%s ...", opt_file);
4970         return TRUE;
4973 static struct blame_commit *
4974 get_blame_commit(struct view *view, const char *id)
4976         size_t i;
4978         for (i = 0; i < view->lines; i++) {
4979                 struct blame *blame = view->line[i].data;
4981                 if (!blame->commit)
4982                         continue;
4984                 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4985                         return blame->commit;
4986         }
4988         {
4989                 struct blame_commit *commit = calloc(1, sizeof(*commit));
4991                 if (commit)
4992                         string_ncopy(commit->id, id, SIZEOF_REV);
4993                 return commit;
4994         }
4997 static bool
4998 parse_number(const char **posref, size_t *number, size_t min, size_t max)
5000         const char *pos = *posref;
5002         *posref = NULL;
5003         pos = strchr(pos + 1, ' ');
5004         if (!pos || !isdigit(pos[1]))
5005                 return FALSE;
5006         *number = atoi(pos + 1);
5007         if (*number < min || *number > max)
5008                 return FALSE;
5010         *posref = pos;
5011         return TRUE;
5014 static struct blame_commit *
5015 parse_blame_commit(struct view *view, const char *text, int *blamed)
5017         struct blame_commit *commit;
5018         struct blame *blame;
5019         const char *pos = text + SIZEOF_REV - 2;
5020         size_t orig_lineno = 0;
5021         size_t lineno;
5022         size_t group;
5024         if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
5025                 return NULL;
5027         if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
5028             !parse_number(&pos, &lineno, 1, view->lines) ||
5029             !parse_number(&pos, &group, 1, view->lines - lineno + 1))
5030                 return NULL;
5032         commit = get_blame_commit(view, text);
5033         if (!commit)
5034                 return NULL;
5036         *blamed += group;
5037         while (group--) {
5038                 struct line *line = &view->line[lineno + group - 1];
5040                 blame = line->data;
5041                 blame->commit = commit;
5042                 blame->lineno = orig_lineno + group - 1;
5043                 line->dirty = 1;
5044         }
5046         return commit;
5049 static bool
5050 blame_read_file(struct view *view, const char *line, bool *read_file)
5052         if (!line) {
5053                 const char *blame_argv[] = {
5054                         "git", "blame", "--incremental",
5055                                 *opt_ref ? opt_ref : "--incremental", "--", opt_file, NULL
5056                 };
5058                 if (view->lines == 0 && !view->prev)
5059                         die("No blame exist for %s", view->vid);
5061                 if (view->lines == 0 || !start_update(view, blame_argv, opt_cdup)) {
5062                         report("Failed to load blame data");
5063                         return TRUE;
5064                 }
5066                 *read_file = FALSE;
5067                 return FALSE;
5069         } else {
5070                 size_t linelen = strlen(line);
5071                 struct blame *blame = malloc(sizeof(*blame) + linelen);
5073                 if (!blame)
5074                         return FALSE;
5076                 blame->commit = NULL;
5077                 strncpy(blame->text, line, linelen);
5078                 blame->text[linelen] = 0;
5079                 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
5080         }
5083 static bool
5084 match_blame_header(const char *name, char **line)
5086         size_t namelen = strlen(name);
5087         bool matched = !strncmp(name, *line, namelen);
5089         if (matched)
5090                 *line += namelen;
5092         return matched;
5095 static bool
5096 blame_read(struct view *view, char *line)
5098         static struct blame_commit *commit = NULL;
5099         static int blamed = 0;
5100         static bool read_file = TRUE;
5102         if (read_file)
5103                 return blame_read_file(view, line, &read_file);
5105         if (!line) {
5106                 /* Reset all! */
5107                 commit = NULL;
5108                 blamed = 0;
5109                 read_file = TRUE;
5110                 string_format(view->ref, "%s", view->vid);
5111                 if (view_is_displayed(view)) {
5112                         update_view_title(view);
5113                         redraw_view_from(view, 0);
5114                 }
5115                 return TRUE;
5116         }
5118         if (!commit) {
5119                 commit = parse_blame_commit(view, line, &blamed);
5120                 string_format(view->ref, "%s %2d%%", view->vid,
5121                               view->lines ? blamed * 100 / view->lines : 0);
5123         } else if (match_blame_header("author ", &line)) {
5124                 commit->author = get_author(line);
5126         } else if (match_blame_header("author-time ", &line)) {
5127                 parse_timesec(&commit->time, line);
5129         } else if (match_blame_header("author-tz ", &line)) {
5130                 parse_timezone(&commit->time, line);
5132         } else if (match_blame_header("summary ", &line)) {
5133                 string_ncopy(commit->title, line, strlen(line));
5135         } else if (match_blame_header("previous ", &line)) {
5136                 if (strlen(line) <= SIZEOF_REV)
5137                         return FALSE;
5138                 string_copy_rev(commit->parent_id, line);
5139                 line += SIZEOF_REV;
5140                 string_ncopy(commit->parent_filename, line, strlen(line));
5142         } else if (match_blame_header("filename ", &line)) {
5143                 string_ncopy(commit->filename, line, strlen(line));
5144                 commit = NULL;
5145         }
5147         return TRUE;
5150 static bool
5151 blame_draw(struct view *view, struct line *line, unsigned int lineno)
5153         struct blame *blame = line->data;
5154         struct time *time = NULL;
5155         const char *id = NULL, *author = NULL;
5157         if (blame->commit && *blame->commit->filename) {
5158                 id = blame->commit->id;
5159                 author = blame->commit->author;
5160                 time = &blame->commit->time;
5161         }
5163         if (opt_date && draw_date(view, time))
5164                 return TRUE;
5166         if (opt_author && draw_author(view, author))
5167                 return TRUE;
5169         if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
5170                 return TRUE;
5172         if (draw_lineno(view, lineno))
5173                 return TRUE;
5175         draw_text(view, LINE_DEFAULT, blame->text, TRUE);
5176         return TRUE;
5179 static bool
5180 check_blame_commit(struct blame *blame, bool check_null_id)
5182         if (!blame->commit)
5183                 report("Commit data not loaded yet");
5184         else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
5185                 report("No commit exist for the selected line");
5186         else
5187                 return TRUE;
5188         return FALSE;
5191 static void
5192 setup_blame_parent_line(struct view *view, struct blame *blame)
5194         char from[SIZEOF_REF + SIZEOF_STR];
5195         char to[SIZEOF_REF + SIZEOF_STR];
5196         const char *diff_tree_argv[] = {
5197                 "git", "diff", "--no-textconv", "--no-extdiff", "--no-color",
5198                         "-U0", from, to, "--", NULL
5199         };
5200         struct io io;
5201         int parent_lineno = -1;
5202         int blamed_lineno = -1;
5203         char *line;
5205         if (!string_format(from, "%s:%s", opt_ref, opt_file) ||
5206             !string_format(to, "%s:%s", blame->commit->id, blame->commit->filename) ||
5207             !io_run(&io, IO_RD, NULL, diff_tree_argv))
5208                 return;
5210         while ((line = io_get(&io, '\n', TRUE))) {
5211                 if (*line == '@') {
5212                         char *pos = strchr(line, '+');
5214                         parent_lineno = atoi(line + 4);
5215                         if (pos)
5216                                 blamed_lineno = atoi(pos + 1);
5218                 } else if (*line == '+' && parent_lineno != -1) {
5219                         if (blame->lineno == blamed_lineno - 1 &&
5220                             !strcmp(blame->text, line + 1)) {
5221                                 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
5222                                 break;
5223                         }
5224                         blamed_lineno++;
5225                 }
5226         }
5228         io_done(&io);
5231 static enum request
5232 blame_request(struct view *view, enum request request, struct line *line)
5234         enum open_flags flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
5235         struct blame *blame = line->data;
5237         switch (request) {
5238         case REQ_VIEW_BLAME:
5239                 if (check_blame_commit(blame, TRUE)) {
5240                         string_copy(opt_ref, blame->commit->id);
5241                         string_copy(opt_file, blame->commit->filename);
5242                         if (blame->lineno)
5243                                 view->lineno = blame->lineno;
5244                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5245                 }
5246                 break;
5248         case REQ_PARENT:
5249                 if (!check_blame_commit(blame, TRUE))
5250                         break;
5251                 if (!*blame->commit->parent_id) {
5252                         report("The selected commit has no parents");
5253                 } else {
5254                         string_copy_rev(opt_ref, blame->commit->parent_id);
5255                         string_copy(opt_file, blame->commit->parent_filename);
5256                         setup_blame_parent_line(view, blame);
5257                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5258                 }
5259                 break;
5261         case REQ_ENTER:
5262                 if (!check_blame_commit(blame, FALSE))
5263                         break;
5265                 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
5266                     !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
5267                         break;
5269                 if (!strcmp(blame->commit->id, NULL_ID)) {
5270                         struct view *diff = VIEW(REQ_VIEW_DIFF);
5271                         const char *diff_index_argv[] = {
5272                                 "git", "diff-index", "--root", "--patch-with-stat",
5273                                         "-C", "-M", "HEAD", "--", view->vid, NULL
5274                         };
5276                         if (!*blame->commit->parent_id) {
5277                                 diff_index_argv[1] = "diff";
5278                                 diff_index_argv[2] = "--no-color";
5279                                 diff_index_argv[6] = "--";
5280                                 diff_index_argv[7] = "/dev/null";
5281                         }
5283                         if (!prepare_update(diff, diff_index_argv, NULL)) {
5284                                 report("Failed to allocate diff command");
5285                                 break;
5286                         }
5287                         flags |= OPEN_PREPARED;
5288                 }
5290                 open_view(view, REQ_VIEW_DIFF, flags);
5291                 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
5292                         string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
5293                 break;
5295         default:
5296                 return request;
5297         }
5299         return REQ_NONE;
5302 static bool
5303 blame_grep(struct view *view, struct line *line)
5305         struct blame *blame = line->data;
5306         struct blame_commit *commit = blame->commit;
5307         const char *text[] = {
5308                 blame->text,
5309                 commit ? commit->title : "",
5310                 commit ? commit->id : "",
5311                 commit && opt_author ? commit->author : "",
5312                 commit ? mkdate(&commit->time, opt_date) : "",
5313                 NULL
5314         };
5316         return grep_text(view, text);
5319 static void
5320 blame_select(struct view *view, struct line *line)
5322         struct blame *blame = line->data;
5323         struct blame_commit *commit = blame->commit;
5325         if (!commit)
5326                 return;
5328         if (!strcmp(commit->id, NULL_ID))
5329                 string_ncopy(ref_commit, "HEAD", 4);
5330         else
5331                 string_copy_rev(ref_commit, commit->id);
5334 static struct view_ops blame_ops = {
5335         "line",
5336         NULL,
5337         blame_open,
5338         blame_read,
5339         blame_draw,
5340         blame_request,
5341         blame_grep,
5342         blame_select,
5343 };
5345 /*
5346  * Branch backend
5347  */
5349 struct branch {
5350         const char *author;             /* Author of the last commit. */
5351         struct time time;               /* Date of the last activity. */
5352         const struct ref *ref;          /* Name and commit ID information. */
5353 };
5355 static const struct ref branch_all;
5357 static const enum sort_field branch_sort_fields[] = {
5358         ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
5359 };
5360 static struct sort_state branch_sort_state = SORT_STATE(branch_sort_fields);
5362 static int
5363 branch_compare(const void *l1, const void *l2)
5365         const struct branch *branch1 = ((const struct line *) l1)->data;
5366         const struct branch *branch2 = ((const struct line *) l2)->data;
5368         switch (get_sort_field(branch_sort_state)) {
5369         case ORDERBY_DATE:
5370                 return sort_order(branch_sort_state, timecmp(&branch1->time, &branch2->time));
5372         case ORDERBY_AUTHOR:
5373                 return sort_order(branch_sort_state, strcmp(branch1->author, branch2->author));
5375         case ORDERBY_NAME:
5376         default:
5377                 return sort_order(branch_sort_state, strcmp(branch1->ref->name, branch2->ref->name));
5378         }
5381 static bool
5382 branch_draw(struct view *view, struct line *line, unsigned int lineno)
5384         struct branch *branch = line->data;
5385         enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
5387         if (opt_date && draw_date(view, &branch->time))
5388                 return TRUE;
5390         if (opt_author && draw_author(view, branch->author))
5391                 return TRUE;
5393         draw_text(view, type, branch->ref == &branch_all ? "All branches" : branch->ref->name, TRUE);
5394         return TRUE;
5397 static enum request
5398 branch_request(struct view *view, enum request request, struct line *line)
5400         struct branch *branch = line->data;
5402         switch (request) {
5403         case REQ_REFRESH:
5404                 load_refs();
5405                 open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
5406                 return REQ_NONE;
5408         case REQ_TOGGLE_SORT_FIELD:
5409         case REQ_TOGGLE_SORT_ORDER:
5410                 sort_view(view, request, &branch_sort_state, branch_compare);
5411                 return REQ_NONE;
5413         case REQ_ENTER:
5414         {
5415                 const struct ref *ref = branch->ref;
5416                 const char *all_branches_argv[] = {
5417                         "git", "log", "--no-color", "--pretty=raw", "--parents",
5418                               "--topo-order",
5419                               ref == &branch_all ? "--all" : ref->name, NULL
5420                 };
5421                 struct view *main_view = VIEW(REQ_VIEW_MAIN);
5423                 if (!prepare_update(main_view, all_branches_argv, NULL))
5424                         report("Failed to load view of all branches");
5425                 else
5426                         open_view(view, REQ_VIEW_MAIN, OPEN_PREPARED | OPEN_SPLIT);
5427                 return REQ_NONE;
5428         }
5429         default:
5430                 return request;
5431         }
5434 static bool
5435 branch_read(struct view *view, char *line)
5437         static char id[SIZEOF_REV];
5438         struct branch *reference;
5439         size_t i;
5441         if (!line)
5442                 return TRUE;
5444         switch (get_line_type(line)) {
5445         case LINE_COMMIT:
5446                 string_copy_rev(id, line + STRING_SIZE("commit "));
5447                 return TRUE;
5449         case LINE_AUTHOR:
5450                 for (i = 0, reference = NULL; i < view->lines; i++) {
5451                         struct branch *branch = view->line[i].data;
5453                         if (strcmp(branch->ref->id, id))
5454                                 continue;
5456                         view->line[i].dirty = TRUE;
5457                         if (reference) {
5458                                 branch->author = reference->author;
5459                                 branch->time = reference->time;
5460                                 continue;
5461                         }
5463                         parse_author_line(line + STRING_SIZE("author "),
5464                                           &branch->author, &branch->time);
5465                         reference = branch;
5466                 }
5467                 return TRUE;
5469         default:
5470                 return TRUE;
5471         }
5475 static bool
5476 branch_open_visitor(void *data, const struct ref *ref)
5478         struct view *view = data;
5479         struct branch *branch;
5481         if (ref->tag || ref->ltag || ref->remote)
5482                 return TRUE;
5484         branch = calloc(1, sizeof(*branch));
5485         if (!branch)
5486                 return FALSE;
5488         branch->ref = ref;
5489         return !!add_line_data(view, branch, LINE_DEFAULT);
5492 static bool
5493 branch_open(struct view *view)
5495         const char *branch_log[] = {
5496                 "git", "log", "--no-color", "--pretty=raw",
5497                         "--simplify-by-decoration", "--all", NULL
5498         };
5500         if (!start_update(view, branch_log, NULL)) {
5501                 report("Failed to load branch data");
5502                 return TRUE;
5503         }
5505         setup_update(view, view->id);
5506         branch_open_visitor(view, &branch_all);
5507         foreach_ref(branch_open_visitor, view);
5508         view->p_restore = TRUE;
5510         return TRUE;
5513 static bool
5514 branch_grep(struct view *view, struct line *line)
5516         struct branch *branch = line->data;
5517         const char *text[] = {
5518                 branch->ref->name,
5519                 branch->author,
5520                 NULL
5521         };
5523         return grep_text(view, text);
5526 static void
5527 branch_select(struct view *view, struct line *line)
5529         struct branch *branch = line->data;
5531         string_copy_rev(view->ref, branch->ref->id);
5532         string_copy_rev(ref_commit, branch->ref->id);
5533         string_copy_rev(ref_head, branch->ref->id);
5534         string_copy_rev(ref_branch, branch->ref->name);
5537 static struct view_ops branch_ops = {
5538         "branch",
5539         NULL,
5540         branch_open,
5541         branch_read,
5542         branch_draw,
5543         branch_request,
5544         branch_grep,
5545         branch_select,
5546 };
5548 /*
5549  * Status backend
5550  */
5552 struct status {
5553         char status;
5554         struct {
5555                 mode_t mode;
5556                 char rev[SIZEOF_REV];
5557                 char name[SIZEOF_STR];
5558         } old;
5559         struct {
5560                 mode_t mode;
5561                 char rev[SIZEOF_REV];
5562                 char name[SIZEOF_STR];
5563         } new;
5564 };
5566 static char status_onbranch[SIZEOF_STR];
5567 static struct status stage_status;
5568 static enum line_type stage_line_type;
5569 static size_t stage_chunks;
5570 static int *stage_chunk;
5572 DEFINE_ALLOCATOR(realloc_ints, int, 32)
5574 /* This should work even for the "On branch" line. */
5575 static inline bool
5576 status_has_none(struct view *view, struct line *line)
5578         return line < view->line + view->lines && !line[1].data;
5581 /* Get fields from the diff line:
5582  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5583  */
5584 static inline bool
5585 status_get_diff(struct status *file, const char *buf, size_t bufsize)
5587         const char *old_mode = buf +  1;
5588         const char *new_mode = buf +  8;
5589         const char *old_rev  = buf + 15;
5590         const char *new_rev  = buf + 56;
5591         const char *status   = buf + 97;
5593         if (bufsize < 98 ||
5594             old_mode[-1] != ':' ||
5595             new_mode[-1] != ' ' ||
5596             old_rev[-1]  != ' ' ||
5597             new_rev[-1]  != ' ' ||
5598             status[-1]   != ' ')
5599                 return FALSE;
5601         file->status = *status;
5603         string_copy_rev(file->old.rev, old_rev);
5604         string_copy_rev(file->new.rev, new_rev);
5606         file->old.mode = strtoul(old_mode, NULL, 8);
5607         file->new.mode = strtoul(new_mode, NULL, 8);
5609         file->old.name[0] = file->new.name[0] = 0;
5611         return TRUE;
5614 static bool
5615 status_run(struct view *view, const char *argv[], char status, enum line_type type)
5617         struct status *unmerged = NULL;
5618         char *buf;
5619         struct io io;
5621         if (!io_run(&io, IO_RD, opt_cdup, argv))
5622                 return FALSE;
5624         add_line_data(view, NULL, type);
5626         while ((buf = io_get(&io, 0, TRUE))) {
5627                 struct status *file = unmerged;
5629                 if (!file) {
5630                         file = calloc(1, sizeof(*file));
5631                         if (!file || !add_line_data(view, file, type))
5632                                 goto error_out;
5633                 }
5635                 /* Parse diff info part. */
5636                 if (status) {
5637                         file->status = status;
5638                         if (status == 'A')
5639                                 string_copy(file->old.rev, NULL_ID);
5641                 } else if (!file->status || file == unmerged) {
5642                         if (!status_get_diff(file, buf, strlen(buf)))
5643                                 goto error_out;
5645                         buf = io_get(&io, 0, TRUE);
5646                         if (!buf)
5647                                 break;
5649                         /* Collapse all modified entries that follow an
5650                          * associated unmerged entry. */
5651                         if (unmerged == file) {
5652                                 unmerged->status = 'U';
5653                                 unmerged = NULL;
5654                         } else if (file->status == 'U') {
5655                                 unmerged = file;
5656                         }
5657                 }
5659                 /* Grab the old name for rename/copy. */
5660                 if (!*file->old.name &&
5661                     (file->status == 'R' || file->status == 'C')) {
5662                         string_ncopy(file->old.name, buf, strlen(buf));
5664                         buf = io_get(&io, 0, TRUE);
5665                         if (!buf)
5666                                 break;
5667                 }
5669                 /* git-ls-files just delivers a NUL separated list of
5670                  * file names similar to the second half of the
5671                  * git-diff-* output. */
5672                 string_ncopy(file->new.name, buf, strlen(buf));
5673                 if (!*file->old.name)
5674                         string_copy(file->old.name, file->new.name);
5675                 file = NULL;
5676         }
5678         if (io_error(&io)) {
5679 error_out:
5680                 io_done(&io);
5681                 return FALSE;
5682         }
5684         if (!view->line[view->lines - 1].data)
5685                 add_line_data(view, NULL, LINE_STAT_NONE);
5687         io_done(&io);
5688         return TRUE;
5691 /* Don't show unmerged entries in the staged section. */
5692 static const char *status_diff_index_argv[] = {
5693         "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5694                              "--cached", "-M", "HEAD", NULL
5695 };
5697 static const char *status_diff_files_argv[] = {
5698         "git", "diff-files", "-z", NULL
5699 };
5701 static const char *status_list_other_argv[] = {
5702         "git", "ls-files", "-z", "--others", "--exclude-standard", opt_prefix, NULL
5703 };
5705 static const char *status_list_no_head_argv[] = {
5706         "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5707 };
5709 static const char *update_index_argv[] = {
5710         "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5711 };
5713 /* Restore the previous line number to stay in the context or select a
5714  * line with something that can be updated. */
5715 static void
5716 status_restore(struct view *view)
5718         if (view->p_lineno >= view->lines)
5719                 view->p_lineno = view->lines - 1;
5720         while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
5721                 view->p_lineno++;
5722         while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
5723                 view->p_lineno--;
5725         /* If the above fails, always skip the "On branch" line. */
5726         if (view->p_lineno < view->lines)
5727                 view->lineno = view->p_lineno;
5728         else
5729                 view->lineno = 1;
5731         if (view->lineno < view->offset)
5732                 view->offset = view->lineno;
5733         else if (view->offset + view->height <= view->lineno)
5734                 view->offset = view->lineno - view->height + 1;
5736         view->p_restore = FALSE;
5739 static void
5740 status_update_onbranch(void)
5742         static const char *paths[][2] = {
5743                 { "rebase-apply/rebasing",      "Rebasing" },
5744                 { "rebase-apply/applying",      "Applying mailbox" },
5745                 { "rebase-apply/",              "Rebasing mailbox" },
5746                 { "rebase-merge/interactive",   "Interactive rebase" },
5747                 { "rebase-merge/",              "Rebase merge" },
5748                 { "MERGE_HEAD",                 "Merging" },
5749                 { "BISECT_LOG",                 "Bisecting" },
5750                 { "HEAD",                       "On branch" },
5751         };
5752         char buf[SIZEOF_STR];
5753         struct stat stat;
5754         int i;
5756         if (is_initial_commit()) {
5757                 string_copy(status_onbranch, "Initial commit");
5758                 return;
5759         }
5761         for (i = 0; i < ARRAY_SIZE(paths); i++) {
5762                 char *head = opt_head;
5764                 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5765                     lstat(buf, &stat) < 0)
5766                         continue;
5768                 if (!*opt_head) {
5769                         struct io io;
5771                         if (io_open(&io, "%s/rebase-merge/head-name", opt_git_dir) &&
5772                             io_read_buf(&io, buf, sizeof(buf))) {
5773                                 head = buf;
5774                                 if (!prefixcmp(head, "refs/heads/"))
5775                                         head += STRING_SIZE("refs/heads/");
5776                         }
5777                 }
5779                 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5780                         string_copy(status_onbranch, opt_head);
5781                 return;
5782         }
5784         string_copy(status_onbranch, "Not currently on any branch");
5787 /* First parse staged info using git-diff-index(1), then parse unstaged
5788  * info using git-diff-files(1), and finally untracked files using
5789  * git-ls-files(1). */
5790 static bool
5791 status_open(struct view *view)
5793         reset_view(view);
5795         add_line_data(view, NULL, LINE_STAT_HEAD);
5796         status_update_onbranch();
5798         io_run_bg(update_index_argv);
5800         if (is_initial_commit()) {
5801                 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5802                         return FALSE;
5803         } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5804                 return FALSE;
5805         }
5807         if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5808             !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5809                 return FALSE;
5811         /* Restore the exact position or use the specialized restore
5812          * mode? */
5813         if (!view->p_restore)
5814                 status_restore(view);
5815         return TRUE;
5818 static bool
5819 status_draw(struct view *view, struct line *line, unsigned int lineno)
5821         struct status *status = line->data;
5822         enum line_type type;
5823         const char *text;
5825         if (!status) {
5826                 switch (line->type) {
5827                 case LINE_STAT_STAGED:
5828                         type = LINE_STAT_SECTION;
5829                         text = "Changes to be committed:";
5830                         break;
5832                 case LINE_STAT_UNSTAGED:
5833                         type = LINE_STAT_SECTION;
5834                         text = "Changed but not updated:";
5835                         break;
5837                 case LINE_STAT_UNTRACKED:
5838                         type = LINE_STAT_SECTION;
5839                         text = "Untracked files:";
5840                         break;
5842                 case LINE_STAT_NONE:
5843                         type = LINE_DEFAULT;
5844                         text = "  (no files)";
5845                         break;
5847                 case LINE_STAT_HEAD:
5848                         type = LINE_STAT_HEAD;
5849                         text = status_onbranch;
5850                         break;
5852                 default:
5853                         return FALSE;
5854                 }
5855         } else {
5856                 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5858                 buf[0] = status->status;
5859                 if (draw_text(view, line->type, buf, TRUE))
5860                         return TRUE;
5861                 type = LINE_DEFAULT;
5862                 text = status->new.name;
5863         }
5865         draw_text(view, type, text, TRUE);
5866         return TRUE;
5869 static enum request
5870 status_load_error(struct view *view, struct view *stage, const char *path)
5872         if (displayed_views() == 2 || display[current_view] != view)
5873                 maximize_view(view);
5874         report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5875         return REQ_NONE;
5878 static enum request
5879 status_enter(struct view *view, struct line *line)
5881         struct status *status = line->data;
5882         const char *oldpath = status ? status->old.name : NULL;
5883         /* Diffs for unmerged entries are empty when passing the new
5884          * path, so leave it empty. */
5885         const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5886         const char *info;
5887         enum open_flags split;
5888         struct view *stage = VIEW(REQ_VIEW_STAGE);
5890         if (line->type == LINE_STAT_NONE ||
5891             (!status && line[1].type == LINE_STAT_NONE)) {
5892                 report("No file to diff");
5893                 return REQ_NONE;
5894         }
5896         switch (line->type) {
5897         case LINE_STAT_STAGED:
5898                 if (is_initial_commit()) {
5899                         const char *no_head_diff_argv[] = {
5900                                 "git", "diff", "--no-color", "--patch-with-stat",
5901                                         "--", "/dev/null", newpath, NULL
5902                         };
5904                         if (!prepare_update(stage, no_head_diff_argv, opt_cdup))
5905                                 return status_load_error(view, stage, newpath);
5906                 } else {
5907                         const char *index_show_argv[] = {
5908                                 "git", "diff-index", "--root", "--patch-with-stat",
5909                                         "-C", "-M", "--cached", "HEAD", "--",
5910                                         oldpath, newpath, NULL
5911                         };
5913                         if (!prepare_update(stage, index_show_argv, opt_cdup))
5914                                 return status_load_error(view, stage, newpath);
5915                 }
5917                 if (status)
5918                         info = "Staged changes to %s";
5919                 else
5920                         info = "Staged changes";
5921                 break;
5923         case LINE_STAT_UNSTAGED:
5924         {
5925                 const char *files_show_argv[] = {
5926                         "git", "diff-files", "--root", "--patch-with-stat",
5927                                 "-C", "-M", "--", oldpath, newpath, NULL
5928                 };
5930                 if (!prepare_update(stage, files_show_argv, opt_cdup))
5931                         return status_load_error(view, stage, newpath);
5932                 if (status)
5933                         info = "Unstaged changes to %s";
5934                 else
5935                         info = "Unstaged changes";
5936                 break;
5937         }
5938         case LINE_STAT_UNTRACKED:
5939                 if (!newpath) {
5940                         report("No file to show");
5941                         return REQ_NONE;
5942                 }
5944                 if (!suffixcmp(status->new.name, -1, "/")) {
5945                         report("Cannot display a directory");
5946                         return REQ_NONE;
5947                 }
5949                 if (!prepare_update_file(stage, newpath))
5950                         return status_load_error(view, stage, newpath);
5951                 info = "Untracked file %s";
5952                 break;
5954         case LINE_STAT_HEAD:
5955                 return REQ_NONE;
5957         default:
5958                 die("line type %d not handled in switch", line->type);
5959         }
5961         split = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
5962         open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5963         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5964                 if (status) {
5965                         stage_status = *status;
5966                 } else {
5967                         memset(&stage_status, 0, sizeof(stage_status));
5968                 }
5970                 stage_line_type = line->type;
5971                 stage_chunks = 0;
5972                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5973         }
5975         return REQ_NONE;
5978 static bool
5979 status_exists(struct status *status, enum line_type type)
5981         struct view *view = VIEW(REQ_VIEW_STATUS);
5982         unsigned long lineno;
5984         for (lineno = 0; lineno < view->lines; lineno++) {
5985                 struct line *line = &view->line[lineno];
5986                 struct status *pos = line->data;
5988                 if (line->type != type)
5989                         continue;
5990                 if (!pos && (!status || !status->status) && line[1].data) {
5991                         select_view_line(view, lineno);
5992                         return TRUE;
5993                 }
5994                 if (pos && !strcmp(status->new.name, pos->new.name)) {
5995                         select_view_line(view, lineno);
5996                         return TRUE;
5997                 }
5998         }
6000         return FALSE;
6004 static bool
6005 status_update_prepare(struct io *io, enum line_type type)
6007         const char *staged_argv[] = {
6008                 "git", "update-index", "-z", "--index-info", NULL
6009         };
6010         const char *others_argv[] = {
6011                 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
6012         };
6014         switch (type) {
6015         case LINE_STAT_STAGED:
6016                 return io_run(io, IO_WR, opt_cdup, staged_argv);
6018         case LINE_STAT_UNSTAGED:
6019         case LINE_STAT_UNTRACKED:
6020                 return io_run(io, IO_WR, opt_cdup, others_argv);
6022         default:
6023                 die("line type %d not handled in switch", type);
6024                 return FALSE;
6025         }
6028 static bool
6029 status_update_write(struct io *io, struct status *status, enum line_type type)
6031         char buf[SIZEOF_STR];
6032         size_t bufsize = 0;
6034         switch (type) {
6035         case LINE_STAT_STAGED:
6036                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
6037                                         status->old.mode,
6038                                         status->old.rev,
6039                                         status->old.name, 0))
6040                         return FALSE;
6041                 break;
6043         case LINE_STAT_UNSTAGED:
6044         case LINE_STAT_UNTRACKED:
6045                 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
6046                         return FALSE;
6047                 break;
6049         default:
6050                 die("line type %d not handled in switch", type);
6051         }
6053         return io_write(io, buf, bufsize);
6056 static bool
6057 status_update_file(struct status *status, enum line_type type)
6059         struct io io;
6060         bool result;
6062         if (!status_update_prepare(&io, type))
6063                 return FALSE;
6065         result = status_update_write(&io, status, type);
6066         return io_done(&io) && result;
6069 static bool
6070 status_update_files(struct view *view, struct line *line)
6072         char buf[sizeof(view->ref)];
6073         struct io io;
6074         bool result = TRUE;
6075         struct line *pos = view->line + view->lines;
6076         int files = 0;
6077         int file, done;
6078         int cursor_y = -1, cursor_x = -1;
6080         if (!status_update_prepare(&io, line->type))
6081                 return FALSE;
6083         for (pos = line; pos < view->line + view->lines && pos->data; pos++)
6084                 files++;
6086         string_copy(buf, view->ref);
6087         getsyx(cursor_y, cursor_x);
6088         for (file = 0, done = 5; result && file < files; line++, file++) {
6089                 int almost_done = file * 100 / files;
6091                 if (almost_done > done) {
6092                         done = almost_done;
6093                         string_format(view->ref, "updating file %u of %u (%d%% done)",
6094                                       file, files, done);
6095                         update_view_title(view);
6096                         setsyx(cursor_y, cursor_x);
6097                         doupdate();
6098                 }
6099                 result = status_update_write(&io, line->data, line->type);
6100         }
6101         string_copy(view->ref, buf);
6103         return io_done(&io) && result;
6106 static bool
6107 status_update(struct view *view)
6109         struct line *line = &view->line[view->lineno];
6111         assert(view->lines);
6113         if (!line->data) {
6114                 /* This should work even for the "On branch" line. */
6115                 if (line < view->line + view->lines && !line[1].data) {
6116                         report("Nothing to update");
6117                         return FALSE;
6118                 }
6120                 if (!status_update_files(view, line + 1)) {
6121                         report("Failed to update file status");
6122                         return FALSE;
6123                 }
6125         } else if (!status_update_file(line->data, line->type)) {
6126                 report("Failed to update file status");
6127                 return FALSE;
6128         }
6130         return TRUE;
6133 static bool
6134 status_revert(struct status *status, enum line_type type, bool has_none)
6136         if (!status || type != LINE_STAT_UNSTAGED) {
6137                 if (type == LINE_STAT_STAGED) {
6138                         report("Cannot revert changes to staged files");
6139                 } else if (type == LINE_STAT_UNTRACKED) {
6140                         report("Cannot revert changes to untracked files");
6141                 } else if (has_none) {
6142                         report("Nothing to revert");
6143                 } else {
6144                         report("Cannot revert changes to multiple files");
6145                 }
6147         } else if (prompt_yesno("Are you sure you want to revert changes?")) {
6148                 char mode[10] = "100644";
6149                 const char *reset_argv[] = {
6150                         "git", "update-index", "--cacheinfo", mode,
6151                                 status->old.rev, status->old.name, NULL
6152                 };
6153                 const char *checkout_argv[] = {
6154                         "git", "checkout", "--", status->old.name, NULL
6155                 };
6157                 if (status->status == 'U') {
6158                         string_format(mode, "%5o", status->old.mode);
6160                         if (status->old.mode == 0 && status->new.mode == 0) {
6161                                 reset_argv[2] = "--force-remove";
6162                                 reset_argv[3] = status->old.name;
6163                                 reset_argv[4] = NULL;
6164                         }
6166                         if (!io_run_fg(reset_argv, opt_cdup))
6167                                 return FALSE;
6168                         if (status->old.mode == 0 && status->new.mode == 0)
6169                                 return TRUE;
6170                 }
6172                 return io_run_fg(checkout_argv, opt_cdup);
6173         }
6175         return FALSE;
6178 static enum request
6179 status_request(struct view *view, enum request request, struct line *line)
6181         struct status *status = line->data;
6183         switch (request) {
6184         case REQ_STATUS_UPDATE:
6185                 if (!status_update(view))
6186                         return REQ_NONE;
6187                 break;
6189         case REQ_STATUS_REVERT:
6190                 if (!status_revert(status, line->type, status_has_none(view, line)))
6191                         return REQ_NONE;
6192                 break;
6194         case REQ_STATUS_MERGE:
6195                 if (!status || status->status != 'U') {
6196                         report("Merging only possible for files with unmerged status ('U').");
6197                         return REQ_NONE;
6198                 }
6199                 open_mergetool(status->new.name);
6200                 break;
6202         case REQ_EDIT:
6203                 if (!status)
6204                         return request;
6205                 if (status->status == 'D') {
6206                         report("File has been deleted.");
6207                         return REQ_NONE;
6208                 }
6210                 open_editor(status->new.name);
6211                 break;
6213         case REQ_VIEW_BLAME:
6214                 if (status)
6215                         opt_ref[0] = 0;
6216                 return request;
6218         case REQ_ENTER:
6219                 /* After returning the status view has been split to
6220                  * show the stage view. No further reloading is
6221                  * necessary. */
6222                 return status_enter(view, line);
6224         case REQ_REFRESH:
6225                 /* Simply reload the view. */
6226                 break;
6228         default:
6229                 return request;
6230         }
6232         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
6234         return REQ_NONE;
6237 static void
6238 status_select(struct view *view, struct line *line)
6240         struct status *status = line->data;
6241         char file[SIZEOF_STR] = "all files";
6242         const char *text;
6243         const char *key;
6245         if (status && !string_format(file, "'%s'", status->new.name))
6246                 return;
6248         if (!status && line[1].type == LINE_STAT_NONE)
6249                 line++;
6251         switch (line->type) {
6252         case LINE_STAT_STAGED:
6253                 text = "Press %s to unstage %s for commit";
6254                 break;
6256         case LINE_STAT_UNSTAGED:
6257                 text = "Press %s to stage %s for commit";
6258                 break;
6260         case LINE_STAT_UNTRACKED:
6261                 text = "Press %s to stage %s for addition";
6262                 break;
6264         case LINE_STAT_HEAD:
6265         case LINE_STAT_NONE:
6266                 text = "Nothing to update";
6267                 break;
6269         default:
6270                 die("line type %d not handled in switch", line->type);
6271         }
6273         if (status && status->status == 'U') {
6274                 text = "Press %s to resolve conflict in %s";
6275                 key = get_key(KEYMAP_STATUS, REQ_STATUS_MERGE);
6277         } else {
6278                 key = get_key(KEYMAP_STATUS, REQ_STATUS_UPDATE);
6279         }
6281         string_format(view->ref, text, key, file);
6282         if (status)
6283                 string_copy(opt_file, status->new.name);
6286 static bool
6287 status_grep(struct view *view, struct line *line)
6289         struct status *status = line->data;
6291         if (status) {
6292                 const char buf[2] = { status->status, 0 };
6293                 const char *text[] = { status->new.name, buf, NULL };
6295                 return grep_text(view, text);
6296         }
6298         return FALSE;
6301 static struct view_ops status_ops = {
6302         "file",
6303         NULL,
6304         status_open,
6305         NULL,
6306         status_draw,
6307         status_request,
6308         status_grep,
6309         status_select,
6310 };
6313 static bool
6314 stage_diff_write(struct io *io, struct line *line, struct line *end)
6316         while (line < end) {
6317                 if (!io_write(io, line->data, strlen(line->data)) ||
6318                     !io_write(io, "\n", 1))
6319                         return FALSE;
6320                 line++;
6321                 if (line->type == LINE_DIFF_CHUNK ||
6322                     line->type == LINE_DIFF_HEADER)
6323                         break;
6324         }
6326         return TRUE;
6329 static struct line *
6330 stage_diff_find(struct view *view, struct line *line, enum line_type type)
6332         for (; view->line < line; line--)
6333                 if (line->type == type)
6334                         return line;
6336         return NULL;
6339 static bool
6340 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
6342         const char *apply_argv[SIZEOF_ARG] = {
6343                 "git", "apply", "--whitespace=nowarn", NULL
6344         };
6345         struct line *diff_hdr;
6346         struct io io;
6347         int argc = 3;
6349         diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
6350         if (!diff_hdr)
6351                 return FALSE;
6353         if (!revert)
6354                 apply_argv[argc++] = "--cached";
6355         if (revert || stage_line_type == LINE_STAT_STAGED)
6356                 apply_argv[argc++] = "-R";
6357         apply_argv[argc++] = "-";
6358         apply_argv[argc++] = NULL;
6359         if (!io_run(&io, IO_WR, opt_cdup, apply_argv))
6360                 return FALSE;
6362         if (!stage_diff_write(&io, diff_hdr, chunk) ||
6363             !stage_diff_write(&io, chunk, view->line + view->lines))
6364                 chunk = NULL;
6366         io_done(&io);
6367         io_run_bg(update_index_argv);
6369         return chunk ? TRUE : FALSE;
6372 static bool
6373 stage_update(struct view *view, struct line *line)
6375         struct line *chunk = NULL;
6377         if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
6378                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6380         if (chunk) {
6381                 if (!stage_apply_chunk(view, chunk, FALSE)) {
6382                         report("Failed to apply chunk");
6383                         return FALSE;
6384                 }
6386         } else if (!stage_status.status) {
6387                 view = VIEW(REQ_VIEW_STATUS);
6389                 for (line = view->line; line < view->line + view->lines; line++)
6390                         if (line->type == stage_line_type)
6391                                 break;
6393                 if (!status_update_files(view, line + 1)) {
6394                         report("Failed to update files");
6395                         return FALSE;
6396                 }
6398         } else if (!status_update_file(&stage_status, stage_line_type)) {
6399                 report("Failed to update file");
6400                 return FALSE;
6401         }
6403         return TRUE;
6406 static bool
6407 stage_revert(struct view *view, struct line *line)
6409         struct line *chunk = NULL;
6411         if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
6412                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6414         if (chunk) {
6415                 if (!prompt_yesno("Are you sure you want to revert changes?"))
6416                         return FALSE;
6418                 if (!stage_apply_chunk(view, chunk, TRUE)) {
6419                         report("Failed to revert chunk");
6420                         return FALSE;
6421                 }
6422                 return TRUE;
6424         } else {
6425                 return status_revert(stage_status.status ? &stage_status : NULL,
6426                                      stage_line_type, FALSE);
6427         }
6431 static void
6432 stage_next(struct view *view, struct line *line)
6434         int i;
6436         if (!stage_chunks) {
6437                 for (line = view->line; line < view->line + view->lines; line++) {
6438                         if (line->type != LINE_DIFF_CHUNK)
6439                                 continue;
6441                         if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
6442                                 report("Allocation failure");
6443                                 return;
6444                         }
6446                         stage_chunk[stage_chunks++] = line - view->line;
6447                 }
6448         }
6450         for (i = 0; i < stage_chunks; i++) {
6451                 if (stage_chunk[i] > view->lineno) {
6452                         do_scroll_view(view, stage_chunk[i] - view->lineno);
6453                         report("Chunk %d of %d", i + 1, stage_chunks);
6454                         return;
6455                 }
6456         }
6458         report("No next chunk found");
6461 static enum request
6462 stage_request(struct view *view, enum request request, struct line *line)
6464         switch (request) {
6465         case REQ_STATUS_UPDATE:
6466                 if (!stage_update(view, line))
6467                         return REQ_NONE;
6468                 break;
6470         case REQ_STATUS_REVERT:
6471                 if (!stage_revert(view, line))
6472                         return REQ_NONE;
6473                 break;
6475         case REQ_STAGE_NEXT:
6476                 if (stage_line_type == LINE_STAT_UNTRACKED) {
6477                         report("File is untracked; press %s to add",
6478                                get_key(KEYMAP_STAGE, REQ_STATUS_UPDATE));
6479                         return REQ_NONE;
6480                 }
6481                 stage_next(view, line);
6482                 return REQ_NONE;
6484         case REQ_EDIT:
6485                 if (!stage_status.new.name[0])
6486                         return request;
6487                 if (stage_status.status == 'D') {
6488                         report("File has been deleted.");
6489                         return REQ_NONE;
6490                 }
6492                 open_editor(stage_status.new.name);
6493                 break;
6495         case REQ_REFRESH:
6496                 /* Reload everything ... */
6497                 break;
6499         case REQ_VIEW_BLAME:
6500                 if (stage_status.new.name[0]) {
6501                         string_copy(opt_file, stage_status.new.name);
6502                         opt_ref[0] = 0;
6503                 }
6504                 return request;
6506         case REQ_ENTER:
6507                 return pager_request(view, request, line);
6509         default:
6510                 return request;
6511         }
6513         VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
6514         open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
6516         /* Check whether the staged entry still exists, and close the
6517          * stage view if it doesn't. */
6518         if (!status_exists(&stage_status, stage_line_type)) {
6519                 status_restore(VIEW(REQ_VIEW_STATUS));
6520                 return REQ_VIEW_CLOSE;
6521         }
6523         if (stage_line_type == LINE_STAT_UNTRACKED) {
6524                 if (!suffixcmp(stage_status.new.name, -1, "/")) {
6525                         report("Cannot display a directory");
6526                         return REQ_NONE;
6527                 }
6529                 if (!prepare_update_file(view, stage_status.new.name)) {
6530                         report("Failed to open file: %s", strerror(errno));
6531                         return REQ_NONE;
6532                 }
6533         }
6534         open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
6536         return REQ_NONE;
6539 static struct view_ops stage_ops = {
6540         "line",
6541         NULL,
6542         NULL,
6543         pager_read,
6544         pager_draw,
6545         stage_request,
6546         pager_grep,
6547         pager_select,
6548 };
6551 /*
6552  * Revision graph
6553  */
6555 struct commit {
6556         char id[SIZEOF_REV];            /* SHA1 ID. */
6557         char title[128];                /* First line of the commit message. */
6558         const char *author;             /* Author of the commit. */
6559         struct time time;               /* Date from the author ident. */
6560         struct ref_list *refs;          /* Repository references. */
6561         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
6562         size_t graph_size;              /* The width of the graph array. */
6563         bool has_parents;               /* Rewritten --parents seen. */
6564 };
6566 /* Size of rev graph with no  "padding" columns */
6567 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6569 struct rev_graph {
6570         struct rev_graph *prev, *next, *parents;
6571         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
6572         size_t size;
6573         struct commit *commit;
6574         size_t pos;
6575         unsigned int boundary:1;
6576 };
6578 /* Parents of the commit being visualized. */
6579 static struct rev_graph graph_parents[4];
6581 /* The current stack of revisions on the graph. */
6582 static struct rev_graph graph_stacks[4] = {
6583         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
6584         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
6585         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
6586         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
6587 };
6589 static inline bool
6590 graph_parent_is_merge(struct rev_graph *graph)
6592         return graph->parents->size > 1;
6595 static inline void
6596 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
6598         struct commit *commit = graph->commit;
6600         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
6601                 commit->graph[commit->graph_size++] = symbol;
6604 static void
6605 clear_rev_graph(struct rev_graph *graph)
6607         graph->boundary = 0;
6608         graph->size = graph->pos = 0;
6609         graph->commit = NULL;
6610         memset(graph->parents, 0, sizeof(*graph->parents));
6613 static void
6614 done_rev_graph(struct rev_graph *graph)
6616         if (graph_parent_is_merge(graph) &&
6617             graph->pos < graph->size - 1 &&
6618             graph->next->size == graph->size + graph->parents->size - 1) {
6619                 size_t i = graph->pos + graph->parents->size - 1;
6621                 graph->commit->graph_size = i * 2;
6622                 while (i < graph->next->size - 1) {
6623                         append_to_rev_graph(graph, ' ');
6624                         append_to_rev_graph(graph, '\\');
6625                         i++;
6626                 }
6627         }
6629         clear_rev_graph(graph);
6632 static void
6633 push_rev_graph(struct rev_graph *graph, const char *parent)
6635         int i;
6637         /* "Collapse" duplicate parents lines.
6638          *
6639          * FIXME: This needs to also update update the drawn graph but
6640          * for now it just serves as a method for pruning graph lines. */
6641         for (i = 0; i < graph->size; i++)
6642                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
6643                         return;
6645         if (graph->size < SIZEOF_REVITEMS) {
6646                 string_copy_rev(graph->rev[graph->size++], parent);
6647         }
6650 static chtype
6651 get_rev_graph_symbol(struct rev_graph *graph)
6653         chtype symbol;
6655         if (graph->boundary)
6656                 symbol = REVGRAPH_BOUND;
6657         else if (graph->parents->size == 0)
6658                 symbol = REVGRAPH_INIT;
6659         else if (graph_parent_is_merge(graph))
6660                 symbol = REVGRAPH_MERGE;
6661         else if (graph->pos >= graph->size)
6662                 symbol = REVGRAPH_BRANCH;
6663         else
6664                 symbol = REVGRAPH_COMMIT;
6666         return symbol;
6669 static void
6670 draw_rev_graph(struct rev_graph *graph)
6672         struct rev_filler {
6673                 chtype separator, line;
6674         };
6675         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
6676         static struct rev_filler fillers[] = {
6677                 { ' ',  '|' },
6678                 { '`',  '.' },
6679                 { '\'', ' ' },
6680                 { '/',  ' ' },
6681         };
6682         chtype symbol = get_rev_graph_symbol(graph);
6683         struct rev_filler *filler;
6684         size_t i;
6686         fillers[DEFAULT].line = opt_line_graphics ? ACS_VLINE : '|';
6687         filler = &fillers[DEFAULT];
6689         for (i = 0; i < graph->pos; i++) {
6690                 append_to_rev_graph(graph, filler->line);
6691                 if (graph_parent_is_merge(graph->prev) &&
6692                     graph->prev->pos == i)
6693                         filler = &fillers[RSHARP];
6695                 append_to_rev_graph(graph, filler->separator);
6696         }
6698         /* Place the symbol for this revision. */
6699         append_to_rev_graph(graph, symbol);
6701         if (graph->prev->size > graph->size)
6702                 filler = &fillers[RDIAG];
6703         else
6704                 filler = &fillers[DEFAULT];
6706         i++;
6708         for (; i < graph->size; i++) {
6709                 append_to_rev_graph(graph, filler->separator);
6710                 append_to_rev_graph(graph, filler->line);
6711                 if (graph_parent_is_merge(graph->prev) &&
6712                     i < graph->prev->pos + graph->parents->size)
6713                         filler = &fillers[RSHARP];
6714                 if (graph->prev->size > graph->size)
6715                         filler = &fillers[LDIAG];
6716         }
6718         if (graph->prev->size > graph->size) {
6719                 append_to_rev_graph(graph, filler->separator);
6720                 if (filler->line != ' ')
6721                         append_to_rev_graph(graph, filler->line);
6722         }
6725 /* Prepare the next rev graph */
6726 static void
6727 prepare_rev_graph(struct rev_graph *graph)
6729         size_t i;
6731         /* First, traverse all lines of revisions up to the active one. */
6732         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6733                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6734                         break;
6736                 push_rev_graph(graph->next, graph->rev[graph->pos]);
6737         }
6739         /* Interleave the new revision parent(s). */
6740         for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6741                 push_rev_graph(graph->next, graph->parents->rev[i]);
6743         /* Lastly, put any remaining revisions. */
6744         for (i = graph->pos + 1; i < graph->size; i++)
6745                 push_rev_graph(graph->next, graph->rev[i]);
6748 static void
6749 update_rev_graph(struct view *view, struct rev_graph *graph)
6751         /* If this is the finalizing update ... */
6752         if (graph->commit)
6753                 prepare_rev_graph(graph);
6755         /* Graph visualization needs a one rev look-ahead,
6756          * so the first update doesn't visualize anything. */
6757         if (!graph->prev->commit)
6758                 return;
6760         if (view->lines > 2)
6761                 view->line[view->lines - 3].dirty = 1;
6762         if (view->lines > 1)
6763                 view->line[view->lines - 2].dirty = 1;
6764         draw_rev_graph(graph->prev);
6765         done_rev_graph(graph->prev->prev);
6769 /*
6770  * Main view backend
6771  */
6773 static const char *main_argv[SIZEOF_ARG] = {
6774         "git", "log", "--no-color", "--pretty=raw", "--parents",
6775                 "--topo-order", "%(diffargs)", "%(revargs)",
6776                 "--", "%(fileargs)", NULL
6777 };
6779 static bool
6780 main_draw(struct view *view, struct line *line, unsigned int lineno)
6782         struct commit *commit = line->data;
6784         if (!commit->author)
6785                 return FALSE;
6787         if (opt_date && draw_date(view, &commit->time))
6788                 return TRUE;
6790         if (opt_author && draw_author(view, commit->author))
6791                 return TRUE;
6793         if (opt_rev_graph && commit->graph_size &&
6794             draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6795                 return TRUE;
6797         if (opt_show_refs && commit->refs) {
6798                 size_t i;
6800                 for (i = 0; i < commit->refs->size; i++) {
6801                         struct ref *ref = commit->refs->refs[i];
6802                         enum line_type type;
6804                         if (ref->head)
6805                                 type = LINE_MAIN_HEAD;
6806                         else if (ref->ltag)
6807                                 type = LINE_MAIN_LOCAL_TAG;
6808                         else if (ref->tag)
6809                                 type = LINE_MAIN_TAG;
6810                         else if (ref->tracked)
6811                                 type = LINE_MAIN_TRACKED;
6812                         else if (ref->remote)
6813                                 type = LINE_MAIN_REMOTE;
6814                         else
6815                                 type = LINE_MAIN_REF;
6817                         if (draw_text(view, type, "[", TRUE) ||
6818                             draw_text(view, type, ref->name, TRUE) ||
6819                             draw_text(view, type, "]", TRUE))
6820                                 return TRUE;
6822                         if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6823                                 return TRUE;
6824                 }
6825         }
6827         draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6828         return TRUE;
6831 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6832 static bool
6833 main_read(struct view *view, char *line)
6835         static struct rev_graph *graph = graph_stacks;
6836         enum line_type type;
6837         struct commit *commit;
6839         if (!line) {
6840                 int i;
6842                 if (!view->lines && !view->prev)
6843                         die("No revisions match the given arguments.");
6844                 if (view->lines > 0) {
6845                         commit = view->line[view->lines - 1].data;
6846                         view->line[view->lines - 1].dirty = 1;
6847                         if (!commit->author) {
6848                                 view->lines--;
6849                                 free(commit);
6850                                 graph->commit = NULL;
6851                         }
6852                 }
6853                 update_rev_graph(view, graph);
6855                 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6856                         clear_rev_graph(&graph_stacks[i]);
6857                 return TRUE;
6858         }
6860         type = get_line_type(line);
6861         if (type == LINE_COMMIT) {
6862                 commit = calloc(1, sizeof(struct commit));
6863                 if (!commit)
6864                         return FALSE;
6866                 line += STRING_SIZE("commit ");
6867                 if (*line == '-') {
6868                         graph->boundary = 1;
6869                         line++;
6870                 }
6872                 string_copy_rev(commit->id, line);
6873                 commit->refs = get_ref_list(commit->id);
6874                 graph->commit = commit;
6875                 add_line_data(view, commit, LINE_MAIN_COMMIT);
6877                 while ((line = strchr(line, ' '))) {
6878                         line++;
6879                         push_rev_graph(graph->parents, line);
6880                         commit->has_parents = TRUE;
6881                 }
6882                 return TRUE;
6883         }
6885         if (!view->lines)
6886                 return TRUE;
6887         commit = view->line[view->lines - 1].data;
6889         switch (type) {
6890         case LINE_PARENT:
6891                 if (commit->has_parents)
6892                         break;
6893                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6894                 break;
6896         case LINE_AUTHOR:
6897                 parse_author_line(line + STRING_SIZE("author "),
6898                                   &commit->author, &commit->time);
6899                 update_rev_graph(view, graph);
6900                 graph = graph->next;
6901                 break;
6903         default:
6904                 /* Fill in the commit title if it has not already been set. */
6905                 if (commit->title[0])
6906                         break;
6908                 /* Require titles to start with a non-space character at the
6909                  * offset used by git log. */
6910                 if (strncmp(line, "    ", 4))
6911                         break;
6912                 line += 4;
6913                 /* Well, if the title starts with a whitespace character,
6914                  * try to be forgiving.  Otherwise we end up with no title. */
6915                 while (isspace(*line))
6916                         line++;
6917                 if (*line == '\0')
6918                         break;
6919                 /* FIXME: More graceful handling of titles; append "..." to
6920                  * shortened titles, etc. */
6922                 string_expand(commit->title, sizeof(commit->title), line, 1);
6923                 view->line[view->lines - 1].dirty = 1;
6924         }
6926         return TRUE;
6929 static enum request
6930 main_request(struct view *view, enum request request, struct line *line)
6932         enum open_flags flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
6934         switch (request) {
6935         case REQ_ENTER:
6936                 open_view(view, REQ_VIEW_DIFF, flags);
6937                 break;
6938         case REQ_REFRESH:
6939                 load_refs();
6940                 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6941                 break;
6942         default:
6943                 return request;
6944         }
6946         return REQ_NONE;
6949 static bool
6950 grep_refs(struct ref_list *list, regex_t *regex)
6952         regmatch_t pmatch;
6953         size_t i;
6955         if (!opt_show_refs || !list)
6956                 return FALSE;
6958         for (i = 0; i < list->size; i++) {
6959                 if (regexec(regex, list->refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6960                         return TRUE;
6961         }
6963         return FALSE;
6966 static bool
6967 main_grep(struct view *view, struct line *line)
6969         struct commit *commit = line->data;
6970         const char *text[] = {
6971                 commit->title,
6972                 opt_author ? commit->author : "",
6973                 mkdate(&commit->time, opt_date),
6974                 NULL
6975         };
6977         return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6980 static void
6981 main_select(struct view *view, struct line *line)
6983         struct commit *commit = line->data;
6985         string_copy_rev(view->ref, commit->id);
6986         string_copy_rev(ref_commit, view->ref);
6989 static struct view_ops main_ops = {
6990         "commit",
6991         main_argv,
6992         NULL,
6993         main_read,
6994         main_draw,
6995         main_request,
6996         main_grep,
6997         main_select,
6998 };
7001 /*
7002  * Status management
7003  */
7005 /* Whether or not the curses interface has been initialized. */
7006 static bool cursed = FALSE;
7008 /* Terminal hacks and workarounds. */
7009 static bool use_scroll_redrawwin;
7010 static bool use_scroll_status_wclear;
7012 /* The status window is used for polling keystrokes. */
7013 static WINDOW *status_win;
7015 /* Reading from the prompt? */
7016 static bool input_mode = FALSE;
7018 static bool status_empty = FALSE;
7020 /* Update status and title window. */
7021 static void
7022 report(const char *msg, ...)
7024         struct view *view = display[current_view];
7026         if (input_mode)
7027                 return;
7029         if (!view) {
7030                 char buf[SIZEOF_STR];
7031                 va_list args;
7033                 va_start(args, msg);
7034                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
7035                         buf[sizeof(buf) - 1] = 0;
7036                         buf[sizeof(buf) - 2] = '.';
7037                         buf[sizeof(buf) - 3] = '.';
7038                         buf[sizeof(buf) - 4] = '.';
7039                 }
7040                 va_end(args);
7041                 die("%s", buf);
7042         }
7044         if (!status_empty || *msg) {
7045                 va_list args;
7047                 va_start(args, msg);
7049                 wmove(status_win, 0, 0);
7050                 if (view->has_scrolled && use_scroll_status_wclear)
7051                         wclear(status_win);
7052                 if (*msg) {
7053                         vwprintw(status_win, msg, args);
7054                         status_empty = FALSE;
7055                 } else {
7056                         status_empty = TRUE;
7057                 }
7058                 wclrtoeol(status_win);
7059                 wnoutrefresh(status_win);
7061                 va_end(args);
7062         }
7064         update_view_title(view);
7067 static void
7068 init_display(void)
7070         const char *term;
7071         int x, y;
7073         /* Initialize the curses library */
7074         if (isatty(STDIN_FILENO)) {
7075                 cursed = !!initscr();
7076                 opt_tty = stdin;
7077         } else {
7078                 /* Leave stdin and stdout alone when acting as a pager. */
7079                 opt_tty = fopen("/dev/tty", "r+");
7080                 if (!opt_tty)
7081                         die("Failed to open /dev/tty");
7082                 cursed = !!newterm(NULL, opt_tty, opt_tty);
7083         }
7085         if (!cursed)
7086                 die("Failed to initialize curses");
7088         nonl();         /* Disable conversion and detect newlines from input. */
7089         cbreak();       /* Take input chars one at a time, no wait for \n */
7090         noecho();       /* Don't echo input */
7091         leaveok(stdscr, FALSE);
7093         if (has_colors())
7094                 init_colors();
7096         getmaxyx(stdscr, y, x);
7097         status_win = newwin(1, 0, y - 1, 0);
7098         if (!status_win)
7099                 die("Failed to create status window");
7101         /* Enable keyboard mapping */
7102         keypad(status_win, TRUE);
7103         wbkgdset(status_win, get_line_attr(LINE_STATUS));
7105         TABSIZE = opt_tab_size;
7107         term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
7108         if (term && !strcmp(term, "gnome-terminal")) {
7109                 /* In the gnome-terminal-emulator, the message from
7110                  * scrolling up one line when impossible followed by
7111                  * scrolling down one line causes corruption of the
7112                  * status line. This is fixed by calling wclear. */
7113                 use_scroll_status_wclear = TRUE;
7114                 use_scroll_redrawwin = FALSE;
7116         } else if (term && !strcmp(term, "xrvt-xpm")) {
7117                 /* No problems with full optimizations in xrvt-(unicode)
7118                  * and aterm. */
7119                 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
7121         } else {
7122                 /* When scrolling in (u)xterm the last line in the
7123                  * scrolling direction will update slowly. */
7124                 use_scroll_redrawwin = TRUE;
7125                 use_scroll_status_wclear = FALSE;
7126         }
7129 static int
7130 get_input(int prompt_position)
7132         struct view *view;
7133         int i, key, cursor_y, cursor_x;
7135         if (prompt_position)
7136                 input_mode = TRUE;
7138         while (TRUE) {
7139                 bool loading = FALSE;
7141                 foreach_view (view, i) {
7142                         update_view(view);
7143                         if (view_is_displayed(view) && view->has_scrolled &&
7144                             use_scroll_redrawwin)
7145                                 redrawwin(view->win);
7146                         view->has_scrolled = FALSE;
7147                         if (view->pipe)
7148                                 loading = TRUE;
7149                 }
7151                 /* Update the cursor position. */
7152                 if (prompt_position) {
7153                         getbegyx(status_win, cursor_y, cursor_x);
7154                         cursor_x = prompt_position;
7155                 } else {
7156                         view = display[current_view];
7157                         getbegyx(view->win, cursor_y, cursor_x);
7158                         cursor_x = view->width - 1;
7159                         cursor_y += view->lineno - view->offset;
7160                 }
7161                 setsyx(cursor_y, cursor_x);
7163                 /* Refresh, accept single keystroke of input */
7164                 doupdate();
7165                 nodelay(status_win, loading);
7166                 key = wgetch(status_win);
7168                 /* wgetch() with nodelay() enabled returns ERR when
7169                  * there's no input. */
7170                 if (key == ERR) {
7172                 } else if (key == KEY_RESIZE) {
7173                         int height, width;
7175                         getmaxyx(stdscr, height, width);
7177                         wresize(status_win, 1, width);
7178                         mvwin(status_win, height - 1, 0);
7179                         wnoutrefresh(status_win);
7180                         resize_display();
7181                         redraw_display(TRUE);
7183                 } else {
7184                         input_mode = FALSE;
7185                         return key;
7186                 }
7187         }
7190 static char *
7191 prompt_input(const char *prompt, input_handler handler, void *data)
7193         enum input_status status = INPUT_OK;
7194         static char buf[SIZEOF_STR];
7195         size_t pos = 0;
7197         buf[pos] = 0;
7199         while (status == INPUT_OK || status == INPUT_SKIP) {
7200                 int key;
7202                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
7203                 wclrtoeol(status_win);
7205                 key = get_input(pos + 1);
7206                 switch (key) {
7207                 case KEY_RETURN:
7208                 case KEY_ENTER:
7209                 case '\n':
7210                         status = pos ? INPUT_STOP : INPUT_CANCEL;
7211                         break;
7213                 case KEY_BACKSPACE:
7214                         if (pos > 0)
7215                                 buf[--pos] = 0;
7216                         else
7217                                 status = INPUT_CANCEL;
7218                         break;
7220                 case KEY_ESC:
7221                         status = INPUT_CANCEL;
7222                         break;
7224                 default:
7225                         if (pos >= sizeof(buf)) {
7226                                 report("Input string too long");
7227                                 return NULL;
7228                         }
7230                         status = handler(data, buf, key);
7231                         if (status == INPUT_OK)
7232                                 buf[pos++] = (char) key;
7233                 }
7234         }
7236         /* Clear the status window */
7237         status_empty = FALSE;
7238         report("");
7240         if (status == INPUT_CANCEL)
7241                 return NULL;
7243         buf[pos++] = 0;
7245         return buf;
7248 static enum input_status
7249 prompt_yesno_handler(void *data, char *buf, int c)
7251         if (c == 'y' || c == 'Y')
7252                 return INPUT_STOP;
7253         if (c == 'n' || c == 'N')
7254                 return INPUT_CANCEL;
7255         return INPUT_SKIP;
7258 static bool
7259 prompt_yesno(const char *prompt)
7261         char prompt2[SIZEOF_STR];
7263         if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
7264                 return FALSE;
7266         return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
7269 static enum input_status
7270 read_prompt_handler(void *data, char *buf, int c)
7272         return isprint(c) ? INPUT_OK : INPUT_SKIP;
7275 static char *
7276 read_prompt(const char *prompt)
7278         return prompt_input(prompt, read_prompt_handler, NULL);
7281 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected)
7283         enum input_status status = INPUT_OK;
7284         int size = 0;
7286         while (items[size].text)
7287                 size++;
7289         while (status == INPUT_OK) {
7290                 const struct menu_item *item = &items[*selected];
7291                 int key;
7292                 int i;
7294                 mvwprintw(status_win, 0, 0, "%s (%d of %d) ",
7295                           prompt, *selected + 1, size);
7296                 if (item->hotkey)
7297                         wprintw(status_win, "[%c] ", (char) item->hotkey);
7298                 wprintw(status_win, "%s", item->text);
7299                 wclrtoeol(status_win);
7301                 key = get_input(COLS - 1);
7302                 switch (key) {
7303                 case KEY_RETURN:
7304                 case KEY_ENTER:
7305                 case '\n':
7306                         status = INPUT_STOP;
7307                         break;
7309                 case KEY_LEFT:
7310                 case KEY_UP:
7311                         *selected = *selected - 1;
7312                         if (*selected < 0)
7313                                 *selected = size - 1;
7314                         break;
7316                 case KEY_RIGHT:
7317                 case KEY_DOWN:
7318                         *selected = (*selected + 1) % size;
7319                         break;
7321                 case KEY_ESC:
7322                         status = INPUT_CANCEL;
7323                         break;
7325                 default:
7326                         for (i = 0; items[i].text; i++)
7327                                 if (items[i].hotkey == key) {
7328                                         *selected = i;
7329                                         status = INPUT_STOP;
7330                                         break;
7331                                 }
7332                 }
7333         }
7335         /* Clear the status window */
7336         status_empty = FALSE;
7337         report("");
7339         return status != INPUT_CANCEL;
7342 /*
7343  * Repository properties
7344  */
7346 static struct ref **refs = NULL;
7347 static size_t refs_size = 0;
7348 static struct ref *refs_head = NULL;
7350 static struct ref_list **ref_lists = NULL;
7351 static size_t ref_lists_size = 0;
7353 DEFINE_ALLOCATOR(realloc_refs, struct ref *, 256)
7354 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
7355 DEFINE_ALLOCATOR(realloc_ref_lists, struct ref_list *, 8)
7357 static int
7358 compare_refs(const void *ref1_, const void *ref2_)
7360         const struct ref *ref1 = *(const struct ref **)ref1_;
7361         const struct ref *ref2 = *(const struct ref **)ref2_;
7363         if (ref1->tag != ref2->tag)
7364                 return ref2->tag - ref1->tag;
7365         if (ref1->ltag != ref2->ltag)
7366                 return ref2->ltag - ref2->ltag;
7367         if (ref1->head != ref2->head)
7368                 return ref2->head - ref1->head;
7369         if (ref1->tracked != ref2->tracked)
7370                 return ref2->tracked - ref1->tracked;
7371         if (ref1->remote != ref2->remote)
7372                 return ref2->remote - ref1->remote;
7373         return strcmp(ref1->name, ref2->name);
7376 static void
7377 foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data)
7379         size_t i;
7381         for (i = 0; i < refs_size; i++)
7382                 if (!visitor(data, refs[i]))
7383                         break;
7386 static struct ref *
7387 get_ref_head()
7389         return refs_head;
7392 static struct ref_list *
7393 get_ref_list(const char *id)
7395         struct ref_list *list;
7396         size_t i;
7398         for (i = 0; i < ref_lists_size; i++)
7399                 if (!strcmp(id, ref_lists[i]->id))
7400                         return ref_lists[i];
7402         if (!realloc_ref_lists(&ref_lists, ref_lists_size, 1))
7403                 return NULL;
7404         list = calloc(1, sizeof(*list));
7405         if (!list)
7406                 return NULL;
7408         for (i = 0; i < refs_size; i++) {
7409                 if (!strcmp(id, refs[i]->id) &&
7410                     realloc_refs_list(&list->refs, list->size, 1))
7411                         list->refs[list->size++] = refs[i];
7412         }
7414         if (!list->refs) {
7415                 free(list);
7416                 return NULL;
7417         }
7419         qsort(list->refs, list->size, sizeof(*list->refs), compare_refs);
7420         ref_lists[ref_lists_size++] = list;
7421         return list;
7424 static int
7425 read_ref(char *id, size_t idlen, char *name, size_t namelen)
7427         struct ref *ref = NULL;
7428         bool tag = FALSE;
7429         bool ltag = FALSE;
7430         bool remote = FALSE;
7431         bool tracked = FALSE;
7432         bool head = FALSE;
7433         int from = 0, to = refs_size - 1;
7435         if (!prefixcmp(name, "refs/tags/")) {
7436                 if (!suffixcmp(name, namelen, "^{}")) {
7437                         namelen -= 3;
7438                         name[namelen] = 0;
7439                 } else {
7440                         ltag = TRUE;
7441                 }
7443                 tag = TRUE;
7444                 namelen -= STRING_SIZE("refs/tags/");
7445                 name    += STRING_SIZE("refs/tags/");
7447         } else if (!prefixcmp(name, "refs/remotes/")) {
7448                 remote = TRUE;
7449                 namelen -= STRING_SIZE("refs/remotes/");
7450                 name    += STRING_SIZE("refs/remotes/");
7451                 tracked  = !strcmp(opt_remote, name);
7453         } else if (!prefixcmp(name, "refs/heads/")) {
7454                 namelen -= STRING_SIZE("refs/heads/");
7455                 name    += STRING_SIZE("refs/heads/");
7456                 if (!strncmp(opt_head, name, namelen))
7457                         return OK;
7459         } else if (!strcmp(name, "HEAD")) {
7460                 head     = TRUE;
7461                 if (*opt_head) {
7462                         namelen  = strlen(opt_head);
7463                         name     = opt_head;
7464                 }
7465         }
7467         /* If we are reloading or it's an annotated tag, replace the
7468          * previous SHA1 with the resolved commit id; relies on the fact
7469          * git-ls-remote lists the commit id of an annotated tag right
7470          * before the commit id it points to. */
7471         while (from <= to) {
7472                 size_t pos = (to + from) / 2;
7473                 int cmp = strcmp(name, refs[pos]->name);
7475                 if (!cmp) {
7476                         ref = refs[pos];
7477                         break;
7478                 }
7480                 if (cmp < 0)
7481                         to = pos - 1;
7482                 else
7483                         from = pos + 1;
7484         }
7486         if (!ref) {
7487                 if (!realloc_refs(&refs, refs_size, 1))
7488                         return ERR;
7489                 ref = calloc(1, sizeof(*ref) + namelen);
7490                 if (!ref)
7491                         return ERR;
7492                 memmove(refs + from + 1, refs + from,
7493                         (refs_size - from) * sizeof(*refs));
7494                 refs[from] = ref;
7495                 strncpy(ref->name, name, namelen);
7496                 refs_size++;
7497         }
7499         ref->head = head;
7500         ref->tag = tag;
7501         ref->ltag = ltag;
7502         ref->remote = remote;
7503         ref->tracked = tracked;
7504         string_copy_rev(ref->id, id);
7506         if (head)
7507                 refs_head = ref;
7508         return OK;
7511 static int
7512 load_refs(void)
7514         const char *head_argv[] = {
7515                 "git", "symbolic-ref", "HEAD", NULL
7516         };
7517         static const char *ls_remote_argv[SIZEOF_ARG] = {
7518                 "git", "ls-remote", opt_git_dir, NULL
7519         };
7520         static bool init = FALSE;
7521         size_t i;
7523         if (!init) {
7524                 if (!argv_from_env(ls_remote_argv, "TIG_LS_REMOTE"))
7525                         die("TIG_LS_REMOTE contains too many arguments");
7526                 init = TRUE;
7527         }
7529         if (!*opt_git_dir)
7530                 return OK;
7532         if (io_run_buf(head_argv, opt_head, sizeof(opt_head)) &&
7533             !prefixcmp(opt_head, "refs/heads/")) {
7534                 char *offset = opt_head + STRING_SIZE("refs/heads/");
7536                 memmove(opt_head, offset, strlen(offset) + 1);
7537         }
7539         refs_head = NULL;
7540         for (i = 0; i < refs_size; i++)
7541                 refs[i]->id[0] = 0;
7543         if (io_run_load(ls_remote_argv, "\t", read_ref) == ERR)
7544                 return ERR;
7546         /* Update the ref lists to reflect changes. */
7547         for (i = 0; i < ref_lists_size; i++) {
7548                 struct ref_list *list = ref_lists[i];
7549                 size_t old, new;
7551                 for (old = new = 0; old < list->size; old++)
7552                         if (!strcmp(list->id, list->refs[old]->id))
7553                                 list->refs[new++] = list->refs[old];
7554                 list->size = new;
7555         }
7557         return OK;
7560 static void
7561 set_remote_branch(const char *name, const char *value, size_t valuelen)
7563         if (!strcmp(name, ".remote")) {
7564                 string_ncopy(opt_remote, value, valuelen);
7566         } else if (*opt_remote && !strcmp(name, ".merge")) {
7567                 size_t from = strlen(opt_remote);
7569                 if (!prefixcmp(value, "refs/heads/"))
7570                         value += STRING_SIZE("refs/heads/");
7572                 if (!string_format_from(opt_remote, &from, "/%s", value))
7573                         opt_remote[0] = 0;
7574         }
7577 static void
7578 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
7580         const char *argv[SIZEOF_ARG] = { name, "=" };
7581         int argc = 1 + (cmd == option_set_command);
7582         int error = ERR;
7584         if (!argv_from_string(argv, &argc, value))
7585                 config_msg = "Too many option arguments";
7586         else
7587                 error = cmd(argc, argv);
7589         if (error == ERR)
7590                 warn("Option 'tig.%s': %s", name, config_msg);
7593 static bool
7594 set_environment_variable(const char *name, const char *value)
7596         size_t len = strlen(name) + 1 + strlen(value) + 1;
7597         char *env = malloc(len);
7599         if (env &&
7600             string_nformat(env, len, NULL, "%s=%s", name, value) &&
7601             putenv(env) == 0)
7602                 return TRUE;
7603         free(env);
7604         return FALSE;
7607 static void
7608 set_work_tree(const char *value)
7610         char cwd[SIZEOF_STR];
7612         if (!getcwd(cwd, sizeof(cwd)))
7613                 die("Failed to get cwd path: %s", strerror(errno));
7614         if (chdir(opt_git_dir) < 0)
7615                 die("Failed to chdir(%s): %s", strerror(errno));
7616         if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7617                 die("Failed to get git path: %s", strerror(errno));
7618         if (chdir(cwd) < 0)
7619                 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7620         if (chdir(value) < 0)
7621                 die("Failed to chdir(%s): %s", value, strerror(errno));
7622         if (!getcwd(cwd, sizeof(cwd)))
7623                 die("Failed to get cwd path: %s", strerror(errno));
7624         if (!set_environment_variable("GIT_WORK_TREE", cwd))
7625                 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7626         if (!set_environment_variable("GIT_DIR", opt_git_dir))
7627                 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7628         opt_is_inside_work_tree = TRUE;
7631 static int
7632 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
7634         if (!strcmp(name, "i18n.commitencoding"))
7635                 string_ncopy(opt_encoding, value, valuelen);
7637         else if (!strcmp(name, "core.editor"))
7638                 string_ncopy(opt_editor, value, valuelen);
7640         else if (!strcmp(name, "core.worktree"))
7641                 set_work_tree(value);
7643         else if (!prefixcmp(name, "tig.color."))
7644                 set_repo_config_option(name + 10, value, option_color_command);
7646         else if (!prefixcmp(name, "tig.bind."))
7647                 set_repo_config_option(name + 9, value, option_bind_command);
7649         else if (!prefixcmp(name, "tig."))
7650                 set_repo_config_option(name + 4, value, option_set_command);
7652         else if (*opt_head && !prefixcmp(name, "branch.") &&
7653                  !strncmp(name + 7, opt_head, strlen(opt_head)))
7654                 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7656         return OK;
7659 static int
7660 load_git_config(void)
7662         const char *config_list_argv[] = { "git", "config", "--list", NULL };
7664         return io_run_load(config_list_argv, "=", read_repo_config_option);
7667 static int
7668 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
7670         if (!opt_git_dir[0]) {
7671                 string_ncopy(opt_git_dir, name, namelen);
7673         } else if (opt_is_inside_work_tree == -1) {
7674                 /* This can be 3 different values depending on the
7675                  * version of git being used. If git-rev-parse does not
7676                  * understand --is-inside-work-tree it will simply echo
7677                  * the option else either "true" or "false" is printed.
7678                  * Default to true for the unknown case. */
7679                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7681         } else if (*name == '.') {
7682                 string_ncopy(opt_cdup, name, namelen);
7684         } else {
7685                 string_ncopy(opt_prefix, name, namelen);
7686         }
7688         return OK;
7691 static int
7692 load_repo_info(void)
7694         const char *rev_parse_argv[] = {
7695                 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7696                         "--show-cdup", "--show-prefix", NULL
7697         };
7699         return io_run_load(rev_parse_argv, "=", read_repo_info);
7703 /*
7704  * Main
7705  */
7707 static const char usage[] =
7708 "tig " TIG_VERSION " (" __DATE__ ")\n"
7709 "\n"
7710 "Usage: tig        [options] [revs] [--] [paths]\n"
7711 "   or: tig show   [options] [revs] [--] [paths]\n"
7712 "   or: tig blame  [rev] path\n"
7713 "   or: tig status\n"
7714 "   or: tig <      [git command output]\n"
7715 "\n"
7716 "Options:\n"
7717 "  -v, --version   Show version and exit\n"
7718 "  -h, --help      Show help message and exit";
7720 static void __NORETURN
7721 quit(int sig)
7723         /* XXX: Restore tty modes and let the OS cleanup the rest! */
7724         if (cursed)
7725                 endwin();
7726         exit(0);
7729 static void __NORETURN
7730 die(const char *err, ...)
7732         va_list args;
7734         endwin();
7736         va_start(args, err);
7737         fputs("tig: ", stderr);
7738         vfprintf(stderr, err, args);
7739         fputs("\n", stderr);
7740         va_end(args);
7742         exit(1);
7745 static void
7746 warn(const char *msg, ...)
7748         va_list args;
7750         va_start(args, msg);
7751         fputs("tig warning: ", stderr);
7752         vfprintf(stderr, msg, args);
7753         fputs("\n", stderr);
7754         va_end(args);
7757 static const char ***filter_args;
7759 static int
7760 read_filter_args(char *name, size_t namelen, char *value, size_t valuelen)
7762         return argv_append(filter_args, name) ? OK : ERR;
7765 static void
7766 filter_rev_parse(const char ***args, const char *arg1, const char *arg2, const char *argv[])
7768         const char *rev_parse_argv[SIZEOF_ARG] = { "git", "rev-parse", arg1, arg2 };
7769         const char **all_argv = NULL;
7771         filter_args = args;
7772         if (!argv_append_array(&all_argv, rev_parse_argv) ||
7773             !argv_append_array(&all_argv, argv) ||
7774             !io_run_load(all_argv, "\n", read_filter_args) == ERR)
7775                 die("Failed to split arguments");
7776         argv_free(all_argv);
7777         free(all_argv);
7780 static void
7781 filter_options(const char *argv[])
7783         filter_rev_parse(&opt_file_args, "--no-revs", "--no-flags", argv);
7784         filter_rev_parse(&opt_diff_args, "--no-revs", "--flags", argv);
7785         filter_rev_parse(&opt_rev_args, "--symbolic", "--revs-only", argv);
7788 static enum request
7789 parse_options(int argc, const char *argv[])
7791         enum request request = REQ_VIEW_MAIN;
7792         const char *subcommand;
7793         bool seen_dashdash = FALSE;
7794         const char **filter_argv = NULL;
7795         int i;
7797         if (!isatty(STDIN_FILENO))
7798                 return REQ_VIEW_PAGER;
7800         if (argc <= 1)
7801                 return REQ_VIEW_MAIN;
7803         subcommand = argv[1];
7804         if (!strcmp(subcommand, "status")) {
7805                 if (argc > 2)
7806                         warn("ignoring arguments after `%s'", subcommand);
7807                 return REQ_VIEW_STATUS;
7809         } else if (!strcmp(subcommand, "blame")) {
7810                 if (argc <= 2 || argc > 4)
7811                         die("invalid number of options to blame\n\n%s", usage);
7813                 i = 2;
7814                 if (argc == 4) {
7815                         string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7816                         i++;
7817                 }
7819                 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7820                 return REQ_VIEW_BLAME;
7822         } else if (!strcmp(subcommand, "show")) {
7823                 request = REQ_VIEW_DIFF;
7825         } else {
7826                 subcommand = NULL;
7827         }
7829         for (i = 1 + !!subcommand; i < argc; i++) {
7830                 const char *opt = argv[i];
7832                 if (seen_dashdash) {
7833                         argv_append(&opt_file_args, opt);
7834                         continue;
7836                 } else if (!strcmp(opt, "--")) {
7837                         seen_dashdash = TRUE;
7838                         continue;
7840                 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7841                         printf("tig version %s\n", TIG_VERSION);
7842                         quit(0);
7844                 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7845                         printf("%s\n", usage);
7846                         quit(0);
7848                 } else if (!strcmp(opt, "--all")) {
7849                         argv_append(&opt_rev_args, opt);
7850                         continue;
7851                 }
7853                 if (!argv_append(&filter_argv, opt))
7854                         die("command too long");
7855         }
7857         if (filter_argv)
7858                 filter_options(filter_argv);
7860         return request;
7863 int
7864 main(int argc, const char *argv[])
7866         const char *codeset = "UTF-8";
7867         enum request request = parse_options(argc, argv);
7868         struct view *view;
7869         size_t i;
7871         signal(SIGINT, quit);
7872         signal(SIGPIPE, SIG_IGN);
7874         if (setlocale(LC_ALL, "")) {
7875                 codeset = nl_langinfo(CODESET);
7876         }
7878         if (load_repo_info() == ERR)
7879                 die("Failed to load repo info.");
7881         if (load_options() == ERR)
7882                 die("Failed to load user config.");
7884         if (load_git_config() == ERR)
7885                 die("Failed to load repo config.");
7887         /* Require a git repository unless when running in pager mode. */
7888         if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7889                 die("Not a git repository");
7891         if (*opt_encoding && strcmp(codeset, "UTF-8")) {
7892                 opt_iconv_in = iconv_open("UTF-8", opt_encoding);
7893                 if (opt_iconv_in == ICONV_NONE)
7894                         die("Failed to initialize character set conversion");
7895         }
7897         if (codeset && strcmp(codeset, "UTF-8")) {
7898                 opt_iconv_out = iconv_open(codeset, "UTF-8");
7899                 if (opt_iconv_out == ICONV_NONE)
7900                         die("Failed to initialize character set conversion");
7901         }
7903         if (load_refs() == ERR)
7904                 die("Failed to load refs.");
7906         foreach_view (view, i) {
7907                 if (getenv(view->cmd_env))
7908                         warn("Use of the %s environment variable is deprecated,"
7909                              " use options or TIG_DIFF_ARGS instead",
7910                              view->cmd_env);
7911                 if (!argv_from_env(view->ops->argv, view->cmd_env))
7912                         die("Too many arguments in the `%s` environment variable",
7913                             view->cmd_env);
7914         }
7916         init_display();
7918         while (view_driver(display[current_view], request)) {
7919                 int key = get_input(0);
7921                 view = display[current_view];
7922                 request = get_keybinding(view->keymap, key);
7924                 /* Some low-level request handling. This keeps access to
7925                  * status_win restricted. */
7926                 switch (request) {
7927                 case REQ_NONE:
7928                         report("Unknown key, press %s for help",
7929                                get_key(view->keymap, REQ_VIEW_HELP));
7930                         break;
7931                 case REQ_PROMPT:
7932                 {
7933                         char *cmd = read_prompt(":");
7935                         if (cmd && isdigit(*cmd)) {
7936                                 int lineno = view->lineno + 1;
7938                                 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7939                                         select_view_line(view, lineno - 1);
7940                                         report("");
7941                                 } else {
7942                                         report("Unable to parse '%s' as a line number", cmd);
7943                                 }
7945                         } else if (cmd) {
7946                                 struct view *next = VIEW(REQ_VIEW_PAGER);
7947                                 const char *argv[SIZEOF_ARG] = { "git" };
7948                                 int argc = 1;
7950                                 /* When running random commands, initially show the
7951                                  * command in the title. However, it maybe later be
7952                                  * overwritten if a commit line is selected. */
7953                                 string_ncopy(next->ref, cmd, strlen(cmd));
7955                                 if (!argv_from_string(argv, &argc, cmd)) {
7956                                         report("Too many arguments");
7957                                 } else if (!prepare_update(next, argv, NULL)) {
7958                                         report("Failed to format command");
7959                                 } else {
7960                                         open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7961                                 }
7962                         }
7964                         request = REQ_NONE;
7965                         break;
7966                 }
7967                 case REQ_SEARCH:
7968                 case REQ_SEARCH_BACK:
7969                 {
7970                         const char *prompt = request == REQ_SEARCH ? "/" : "?";
7971                         char *search = read_prompt(prompt);
7973                         if (search)
7974                                 string_ncopy(opt_search, search, strlen(search));
7975                         else if (*opt_search)
7976                                 request = request == REQ_SEARCH ?
7977                                         REQ_FIND_NEXT :
7978                                         REQ_FIND_PREV;
7979                         else
7980                                 request = REQ_NONE;
7981                         break;
7982                 }
7983                 default:
7984                         break;
7985                 }
7986         }
7988         quit(0);
7990         return 0;