Code

Add vi-like ^ bindings
[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_LEFT,       "Scroll two columns left"), \
1140         REQ_(SCROLL_RIGHT,      "Scroll two columns right"), \
1141         REQ_(SCROLL_LINE_UP,    "Scroll one line up"), \
1142         REQ_(SCROLL_LINE_DOWN,  "Scroll one line down"), \
1143         REQ_(SCROLL_PAGE_UP,    "Scroll one page up"), \
1144         REQ_(SCROLL_PAGE_DOWN,  "Scroll one page down"), \
1145         \
1146         REQ_GROUP("Searching") \
1147         REQ_(SEARCH,            "Search the view"), \
1148         REQ_(SEARCH_BACK,       "Search backwards in the view"), \
1149         REQ_(FIND_NEXT,         "Find next search match"), \
1150         REQ_(FIND_PREV,         "Find previous search match"), \
1151         \
1152         REQ_GROUP("Option manipulation") \
1153         REQ_(OPTIONS,           "Open option menu"), \
1154         REQ_(TOGGLE_LINENO,     "Toggle line numbers"), \
1155         REQ_(TOGGLE_DATE,       "Toggle date display"), \
1156         REQ_(TOGGLE_DATE_SHORT, "Toggle short (date-only) dates"), \
1157         REQ_(TOGGLE_AUTHOR,     "Toggle author display"), \
1158         REQ_(TOGGLE_REV_GRAPH,  "Toggle revision graph visualization"), \
1159         REQ_(TOGGLE_REFS,       "Toggle reference display (tags/branches)"), \
1160         REQ_(TOGGLE_SORT_ORDER, "Toggle ascending/descending sort order"), \
1161         REQ_(TOGGLE_SORT_FIELD, "Toggle field to sort by"), \
1162         \
1163         REQ_GROUP("Misc") \
1164         REQ_(PROMPT,            "Bring up the prompt"), \
1165         REQ_(SCREEN_REDRAW,     "Redraw the screen"), \
1166         REQ_(SHOW_VERSION,      "Show version information"), \
1167         REQ_(STOP_LOADING,      "Stop all loading views"), \
1168         REQ_(EDIT,              "Open in editor"), \
1169         REQ_(NONE,              "Do nothing")
1172 /* User action requests. */
1173 enum request {
1174 #define REQ_GROUP(help)
1175 #define REQ_(req, help) REQ_##req
1177         /* Offset all requests to avoid conflicts with ncurses getch values. */
1178         REQ_UNKNOWN = KEY_MAX + 1,
1179         REQ_OFFSET,
1180         REQ_INFO
1182 #undef  REQ_GROUP
1183 #undef  REQ_
1184 };
1186 struct request_info {
1187         enum request request;
1188         const char *name;
1189         int namelen;
1190         const char *help;
1191 };
1193 static const struct request_info req_info[] = {
1194 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
1195 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
1196         REQ_INFO
1197 #undef  REQ_GROUP
1198 #undef  REQ_
1199 };
1201 static enum request
1202 get_request(const char *name)
1204         int namelen = strlen(name);
1205         int i;
1207         for (i = 0; i < ARRAY_SIZE(req_info); i++)
1208                 if (enum_equals(req_info[i], name, namelen))
1209                         return req_info[i].request;
1211         return REQ_UNKNOWN;
1215 /*
1216  * Options
1217  */
1219 /* Option and state variables. */
1220 static enum date opt_date               = DATE_DEFAULT;
1221 static enum author opt_author           = AUTHOR_DEFAULT;
1222 static bool opt_line_number             = FALSE;
1223 static bool opt_line_graphics           = TRUE;
1224 static bool opt_rev_graph               = FALSE;
1225 static bool opt_show_refs               = TRUE;
1226 static int opt_num_interval             = 5;
1227 static double opt_hscroll               = 0.50;
1228 static double opt_scale_split_view      = 2.0 / 3.0;
1229 static int opt_tab_size                 = 8;
1230 static int opt_author_cols              = AUTHOR_COLS;
1231 static char opt_path[SIZEOF_STR]        = "";
1232 static char opt_file[SIZEOF_STR]        = "";
1233 static char opt_ref[SIZEOF_REF]         = "";
1234 static char opt_head[SIZEOF_REF]        = "";
1235 static char opt_remote[SIZEOF_REF]      = "";
1236 static char opt_encoding[20]            = "UTF-8";
1237 static iconv_t opt_iconv_in             = ICONV_NONE;
1238 static iconv_t opt_iconv_out            = ICONV_NONE;
1239 static char opt_search[SIZEOF_STR]      = "";
1240 static char opt_cdup[SIZEOF_STR]        = "";
1241 static char opt_prefix[SIZEOF_STR]      = "";
1242 static char opt_git_dir[SIZEOF_STR]     = "";
1243 static signed char opt_is_inside_work_tree      = -1; /* set to TRUE or FALSE */
1244 static char opt_editor[SIZEOF_STR]      = "";
1245 static FILE *opt_tty                    = NULL;
1246 static const char **opt_diff_args       = NULL;
1247 static const char **opt_rev_args        = NULL;
1248 static const char **opt_file_args       = NULL;
1250 #define is_initial_commit()     (!get_ref_head())
1251 #define is_head_commit(rev)     (!strcmp((rev), "HEAD") || (get_ref_head() && !strcmp(rev, get_ref_head()->id)))
1254 /*
1255  * Line-oriented content detection.
1256  */
1258 #define LINE_INFO \
1259 LINE(DIFF_HEADER,  "diff --git ",       COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1260 LINE(DIFF_CHUNK,   "@@",                COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1261 LINE(DIFF_ADD,     "+",                 COLOR_GREEN,    COLOR_DEFAULT,  0), \
1262 LINE(DIFF_DEL,     "-",                 COLOR_RED,      COLOR_DEFAULT,  0), \
1263 LINE(DIFF_INDEX,        "index ",         COLOR_BLUE,   COLOR_DEFAULT,  0), \
1264 LINE(DIFF_OLDMODE,      "old file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1265 LINE(DIFF_NEWMODE,      "new file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1266 LINE(DIFF_COPY_FROM,    "copy from",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
1267 LINE(DIFF_COPY_TO,      "copy to",        COLOR_YELLOW, COLOR_DEFAULT,  0), \
1268 LINE(DIFF_RENAME_FROM,  "rename from",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
1269 LINE(DIFF_RENAME_TO,    "rename to",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
1270 LINE(DIFF_SIMILARITY,   "similarity ",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
1271 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1272 LINE(DIFF_TREE,         "diff-tree ",     COLOR_BLUE,   COLOR_DEFAULT,  0), \
1273 LINE(PP_AUTHOR,    "Author: ",          COLOR_CYAN,     COLOR_DEFAULT,  0), \
1274 LINE(PP_COMMIT,    "Commit: ",          COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1275 LINE(PP_MERGE,     "Merge: ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
1276 LINE(PP_DATE,      "Date:   ",          COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1277 LINE(PP_ADATE,     "AuthorDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1278 LINE(PP_CDATE,     "CommitDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1279 LINE(PP_REFS,      "Refs: ",            COLOR_RED,      COLOR_DEFAULT,  0), \
1280 LINE(COMMIT,       "commit ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
1281 LINE(PARENT,       "parent ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
1282 LINE(TREE,         "tree ",             COLOR_BLUE,     COLOR_DEFAULT,  0), \
1283 LINE(AUTHOR,       "author ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
1284 LINE(COMMITTER,    "committer ",        COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1285 LINE(SIGNOFF,      "    Signed-off-by", COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1286 LINE(ACKED,        "    Acked-by",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1287 LINE(TESTED,       "    Tested-by",     COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1288 LINE(REVIEWED,     "    Reviewed-by",   COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1289 LINE(DEFAULT,      "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
1290 LINE(CURSOR,       "",                  COLOR_WHITE,    COLOR_GREEN,    A_BOLD), \
1291 LINE(STATUS,       "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
1292 LINE(DELIMITER,    "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1293 LINE(DATE,         "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
1294 LINE(MODE,         "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1295 LINE(LINE_NUMBER,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1296 LINE(TITLE_BLUR,   "",                  COLOR_WHITE,    COLOR_BLUE,     0), \
1297 LINE(TITLE_FOCUS,  "",                  COLOR_WHITE,    COLOR_BLUE,     A_BOLD), \
1298 LINE(MAIN_COMMIT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
1299 LINE(MAIN_TAG,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
1300 LINE(MAIN_LOCAL_TAG,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1301 LINE(MAIN_REMOTE,  "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1302 LINE(MAIN_TRACKED, "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_BOLD), \
1303 LINE(MAIN_REF,     "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1304 LINE(MAIN_HEAD,    "",                  COLOR_CYAN,     COLOR_DEFAULT,  A_BOLD), \
1305 LINE(MAIN_REVGRAPH,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1306 LINE(TREE_HEAD,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_BOLD), \
1307 LINE(TREE_DIR,     "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_NORMAL), \
1308 LINE(TREE_FILE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
1309 LINE(STAT_HEAD,    "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1310 LINE(STAT_SECTION, "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1311 LINE(STAT_NONE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
1312 LINE(STAT_STAGED,  "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1313 LINE(STAT_UNSTAGED,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1314 LINE(STAT_UNTRACKED,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1315 LINE(HELP_KEYMAP,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1316 LINE(HELP_GROUP,   "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
1317 LINE(BLAME_ID,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0)
1319 enum line_type {
1320 #define LINE(type, line, fg, bg, attr) \
1321         LINE_##type
1322         LINE_INFO,
1323         LINE_NONE
1324 #undef  LINE
1325 };
1327 struct line_info {
1328         const char *name;       /* Option name. */
1329         int namelen;            /* Size of option name. */
1330         const char *line;       /* The start of line to match. */
1331         int linelen;            /* Size of string to match. */
1332         int fg, bg, attr;       /* Color and text attributes for the lines. */
1333 };
1335 static struct line_info line_info[] = {
1336 #define LINE(type, line, fg, bg, attr) \
1337         { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1338         LINE_INFO
1339 #undef  LINE
1340 };
1342 static enum line_type
1343 get_line_type(const char *line)
1345         int linelen = strlen(line);
1346         enum line_type type;
1348         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1349                 /* Case insensitive search matches Signed-off-by lines better. */
1350                 if (linelen >= line_info[type].linelen &&
1351                     !strncasecmp(line_info[type].line, line, line_info[type].linelen))
1352                         return type;
1354         return LINE_DEFAULT;
1357 static inline int
1358 get_line_attr(enum line_type type)
1360         assert(type < ARRAY_SIZE(line_info));
1361         return COLOR_PAIR(type) | line_info[type].attr;
1364 static struct line_info *
1365 get_line_info(const char *name)
1367         size_t namelen = strlen(name);
1368         enum line_type type;
1370         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1371                 if (enum_equals(line_info[type], name, namelen))
1372                         return &line_info[type];
1374         return NULL;
1377 static void
1378 init_colors(void)
1380         int default_bg = line_info[LINE_DEFAULT].bg;
1381         int default_fg = line_info[LINE_DEFAULT].fg;
1382         enum line_type type;
1384         start_color();
1386         if (assume_default_colors(default_fg, default_bg) == ERR) {
1387                 default_bg = COLOR_BLACK;
1388                 default_fg = COLOR_WHITE;
1389         }
1391         for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1392                 struct line_info *info = &line_info[type];
1393                 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1394                 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1396                 init_pair(type, fg, bg);
1397         }
1400 struct line {
1401         enum line_type type;
1403         /* State flags */
1404         unsigned int selected:1;
1405         unsigned int dirty:1;
1406         unsigned int cleareol:1;
1407         unsigned int other:16;
1409         void *data;             /* User data */
1410 };
1413 /*
1414  * Keys
1415  */
1417 struct keybinding {
1418         int alias;
1419         enum request request;
1420 };
1422 static struct keybinding default_keybindings[] = {
1423         /* View switching */
1424         { 'm',          REQ_VIEW_MAIN },
1425         { 'd',          REQ_VIEW_DIFF },
1426         { 'l',          REQ_VIEW_LOG },
1427         { 't',          REQ_VIEW_TREE },
1428         { 'f',          REQ_VIEW_BLOB },
1429         { 'B',          REQ_VIEW_BLAME },
1430         { 'H',          REQ_VIEW_BRANCH },
1431         { 'p',          REQ_VIEW_PAGER },
1432         { 'h',          REQ_VIEW_HELP },
1433         { 'S',          REQ_VIEW_STATUS },
1434         { 'c',          REQ_VIEW_STAGE },
1436         /* View manipulation */
1437         { 'q',          REQ_VIEW_CLOSE },
1438         { KEY_TAB,      REQ_VIEW_NEXT },
1439         { KEY_RETURN,   REQ_ENTER },
1440         { KEY_UP,       REQ_PREVIOUS },
1441         { KEY_CTL('P'), REQ_PREVIOUS },
1442         { KEY_DOWN,     REQ_NEXT },
1443         { KEY_CTL('N'), REQ_NEXT },
1444         { 'R',          REQ_REFRESH },
1445         { KEY_F(5),     REQ_REFRESH },
1446         { 'O',          REQ_MAXIMIZE },
1448         /* Cursor navigation */
1449         { 'k',          REQ_MOVE_UP },
1450         { 'j',          REQ_MOVE_DOWN },
1451         { KEY_HOME,     REQ_MOVE_FIRST_LINE },
1452         { KEY_END,      REQ_MOVE_LAST_LINE },
1453         { KEY_NPAGE,    REQ_MOVE_PAGE_DOWN },
1454         { ' ',          REQ_MOVE_PAGE_DOWN },
1455         { KEY_PPAGE,    REQ_MOVE_PAGE_UP },
1456         { KEY_CTL('U'), REQ_MOVE_PAGE_UP },
1457         { 'b',          REQ_MOVE_PAGE_UP },
1458         { '-',          REQ_MOVE_PAGE_UP },
1460         /* Scrolling */
1461         { KEY_LEFT,     REQ_SCROLL_LEFT },
1462         { KEY_RIGHT,    REQ_SCROLL_RIGHT },
1463         { KEY_IC,       REQ_SCROLL_LINE_UP },
1464         { KEY_CTL('Y'), REQ_SCROLL_LINE_UP },
1465         { KEY_DC,       REQ_SCROLL_LINE_DOWN },
1466         { KEY_CTL('E'), REQ_SCROLL_LINE_DOWN },
1467         { 'w',          REQ_SCROLL_PAGE_UP },
1468         { 's',          REQ_SCROLL_PAGE_DOWN },
1470         /* Searching */
1471         { '/',          REQ_SEARCH },
1472         { '?',          REQ_SEARCH_BACK },
1473         { 'n',          REQ_FIND_NEXT },
1474         { 'N',          REQ_FIND_PREV },
1476         /* Misc */
1477         { 'Q',          REQ_QUIT },
1478         { 'z',          REQ_STOP_LOADING },
1479         { 'v',          REQ_SHOW_VERSION },
1480         { 'r',          REQ_SCREEN_REDRAW },
1481         { KEY_CTL('L'), REQ_SCREEN_REDRAW },
1482         { 'o',          REQ_OPTIONS },
1483         { '.',          REQ_TOGGLE_LINENO },
1484         { 'D',          REQ_TOGGLE_DATE },
1485         { 'A',          REQ_TOGGLE_AUTHOR },
1486         { 'g',          REQ_TOGGLE_REV_GRAPH },
1487         { 'F',          REQ_TOGGLE_REFS },
1488         { 'I',          REQ_TOGGLE_SORT_ORDER },
1489         { 'i',          REQ_TOGGLE_SORT_FIELD },
1490         { ':',          REQ_PROMPT },
1491         { 'u',          REQ_STATUS_UPDATE },
1492         { '!',          REQ_STATUS_REVERT },
1493         { 'M',          REQ_STATUS_MERGE },
1494         { '@',          REQ_STAGE_NEXT },
1495         { ',',          REQ_PARENT },
1496         { 'e',          REQ_EDIT },
1497 };
1499 #define KEYMAP_INFO \
1500         KEYMAP_(GENERIC), \
1501         KEYMAP_(MAIN), \
1502         KEYMAP_(DIFF), \
1503         KEYMAP_(LOG), \
1504         KEYMAP_(TREE), \
1505         KEYMAP_(BLOB), \
1506         KEYMAP_(BLAME), \
1507         KEYMAP_(BRANCH), \
1508         KEYMAP_(PAGER), \
1509         KEYMAP_(HELP), \
1510         KEYMAP_(STATUS), \
1511         KEYMAP_(STAGE)
1513 enum keymap {
1514 #define KEYMAP_(name) KEYMAP_##name
1515         KEYMAP_INFO
1516 #undef  KEYMAP_
1517 };
1519 static const struct enum_map keymap_table[] = {
1520 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1521         KEYMAP_INFO
1522 #undef  KEYMAP_
1523 };
1525 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1527 struct keybinding_table {
1528         struct keybinding *data;
1529         size_t size;
1530 };
1532 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1534 static void
1535 add_keybinding(enum keymap keymap, enum request request, int key)
1537         struct keybinding_table *table = &keybindings[keymap];
1538         size_t i;
1540         for (i = 0; i < keybindings[keymap].size; i++) {
1541                 if (keybindings[keymap].data[i].alias == key) {
1542                         keybindings[keymap].data[i].request = request;
1543                         return;
1544                 }
1545         }
1547         table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1548         if (!table->data)
1549                 die("Failed to allocate keybinding");
1550         table->data[table->size].alias = key;
1551         table->data[table->size++].request = request;
1553         if (request == REQ_NONE && keymap == KEYMAP_GENERIC) {
1554                 int i;
1556                 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1557                         if (default_keybindings[i].alias == key)
1558                                 default_keybindings[i].request = REQ_NONE;
1559         }
1562 /* Looks for a key binding first in the given map, then in the generic map, and
1563  * lastly in the default keybindings. */
1564 static enum request
1565 get_keybinding(enum keymap keymap, int key)
1567         size_t i;
1569         for (i = 0; i < keybindings[keymap].size; i++)
1570                 if (keybindings[keymap].data[i].alias == key)
1571                         return keybindings[keymap].data[i].request;
1573         for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1574                 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1575                         return keybindings[KEYMAP_GENERIC].data[i].request;
1577         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1578                 if (default_keybindings[i].alias == key)
1579                         return default_keybindings[i].request;
1581         return (enum request) key;
1585 struct key {
1586         const char *name;
1587         int value;
1588 };
1590 static const struct key key_table[] = {
1591         { "Enter",      KEY_RETURN },
1592         { "Space",      ' ' },
1593         { "Backspace",  KEY_BACKSPACE },
1594         { "Tab",        KEY_TAB },
1595         { "Escape",     KEY_ESC },
1596         { "Left",       KEY_LEFT },
1597         { "Right",      KEY_RIGHT },
1598         { "Up",         KEY_UP },
1599         { "Down",       KEY_DOWN },
1600         { "Insert",     KEY_IC },
1601         { "Delete",     KEY_DC },
1602         { "Hash",       '#' },
1603         { "Home",       KEY_HOME },
1604         { "End",        KEY_END },
1605         { "PageUp",     KEY_PPAGE },
1606         { "PageDown",   KEY_NPAGE },
1607         { "F1",         KEY_F(1) },
1608         { "F2",         KEY_F(2) },
1609         { "F3",         KEY_F(3) },
1610         { "F4",         KEY_F(4) },
1611         { "F5",         KEY_F(5) },
1612         { "F6",         KEY_F(6) },
1613         { "F7",         KEY_F(7) },
1614         { "F8",         KEY_F(8) },
1615         { "F9",         KEY_F(9) },
1616         { "F10",        KEY_F(10) },
1617         { "F11",        KEY_F(11) },
1618         { "F12",        KEY_F(12) },
1619 };
1621 static int
1622 get_key_value(const char *name)
1624         int i;
1626         for (i = 0; i < ARRAY_SIZE(key_table); i++)
1627                 if (!strcasecmp(key_table[i].name, name))
1628                         return key_table[i].value;
1630         if (strlen(name) == 2 && name[0] == '^' && isprint(*name))
1631                 return (int)name[1] & 0x1f;
1632         if (strlen(name) == 1 && isprint(*name))
1633                 return (int) *name;
1634         return ERR;
1637 static const char *
1638 get_key_name(int key_value)
1640         static char key_char[] = "'X'\0";
1641         const char *seq = NULL;
1642         int key;
1644         for (key = 0; key < ARRAY_SIZE(key_table); key++)
1645                 if (key_table[key].value == key_value)
1646                         seq = key_table[key].name;
1648         if (seq == NULL && key_value < 0x7f) {
1649                 char *s = key_char + 1;
1651                 if (key_value >= 0x20) {
1652                         *s++ = key_value;
1653                 } else {
1654                         *s++ = '^';
1655                         *s++ = 0x40 | (key_value & 0x1f);
1656                 }
1657                 *s++ = '\'';
1658                 *s++ = '\0';
1659                 seq = key_char;
1660         }
1662         return seq ? seq : "(no key)";
1665 static bool
1666 append_key(char *buf, size_t *pos, const struct keybinding *keybinding)
1668         const char *sep = *pos > 0 ? ", " : "";
1669         const char *keyname = get_key_name(keybinding->alias);
1671         return string_nformat(buf, BUFSIZ, pos, "%s%s", sep, keyname);
1674 static bool
1675 append_keymap_request_keys(char *buf, size_t *pos, enum request request,
1676                            enum keymap keymap, bool all)
1678         int i;
1680         for (i = 0; i < keybindings[keymap].size; i++) {
1681                 if (keybindings[keymap].data[i].request == request) {
1682                         if (!append_key(buf, pos, &keybindings[keymap].data[i]))
1683                                 return FALSE;
1684                         if (!all)
1685                                 break;
1686                 }
1687         }
1689         return TRUE;
1692 #define get_key(keymap, request) get_keys(keymap, request, FALSE)
1694 static const char *
1695 get_keys(enum keymap keymap, enum request request, bool all)
1697         static char buf[BUFSIZ];
1698         size_t pos = 0;
1699         int i;
1701         buf[pos] = 0;
1703         if (!append_keymap_request_keys(buf, &pos, request, keymap, all))
1704                 return "Too many keybindings!";
1705         if (pos > 0 && !all)
1706                 return buf;
1708         if (keymap != KEYMAP_GENERIC) {
1709                 /* Only the generic keymap includes the default keybindings when
1710                  * listing all keys. */
1711                 if (all)
1712                         return buf;
1714                 if (!append_keymap_request_keys(buf, &pos, request, KEYMAP_GENERIC, all))
1715                         return "Too many keybindings!";
1716                 if (pos)
1717                         return buf;
1718         }
1720         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1721                 if (default_keybindings[i].request == request) {
1722                         if (!append_key(buf, &pos, &default_keybindings[i]))
1723                                 return "Too many keybindings!";
1724                         if (!all)
1725                                 return buf;
1726                 }
1727         }
1729         return buf;
1732 struct run_request {
1733         enum keymap keymap;
1734         int key;
1735         const char **argv;
1736 };
1738 static struct run_request *run_request;
1739 static size_t run_requests;
1741 DEFINE_ALLOCATOR(realloc_run_requests, struct run_request, 8)
1743 static enum request
1744 add_run_request(enum keymap keymap, int key, const char **argv)
1746         struct run_request *req;
1748         if (!realloc_run_requests(&run_request, run_requests, 1))
1749                 return REQ_NONE;
1751         req = &run_request[run_requests];
1752         req->keymap = keymap;
1753         req->key = key;
1754         req->argv = NULL;
1756         if (!argv_copy(&req->argv, argv))
1757                 return REQ_NONE;
1759         return REQ_NONE + ++run_requests;
1762 static struct run_request *
1763 get_run_request(enum request request)
1765         if (request <= REQ_NONE)
1766                 return NULL;
1767         return &run_request[request - REQ_NONE - 1];
1770 static void
1771 add_builtin_run_requests(void)
1773         const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1774         const char *checkout[] = { "git", "checkout", "%(branch)", NULL };
1775         const char *commit[] = { "git", "commit", NULL };
1776         const char *gc[] = { "git", "gc", NULL };
1777         struct run_request reqs[] = {
1778                 { KEYMAP_MAIN,    'C', cherry_pick },
1779                 { KEYMAP_STATUS,  'C', commit },
1780                 { KEYMAP_BRANCH,  'C', checkout },
1781                 { KEYMAP_GENERIC, 'G', gc },
1782         };
1783         int i;
1785         for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1786                 enum request req = get_keybinding(reqs[i].keymap, reqs[i].key);
1788                 if (req != reqs[i].key)
1789                         continue;
1790                 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argv);
1791                 if (req != REQ_NONE)
1792                         add_keybinding(reqs[i].keymap, req, reqs[i].key);
1793         }
1796 /*
1797  * User config file handling.
1798  */
1800 static int   config_lineno;
1801 static bool  config_errors;
1802 static const char *config_msg;
1804 static const struct enum_map color_map[] = {
1805 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1806         COLOR_MAP(DEFAULT),
1807         COLOR_MAP(BLACK),
1808         COLOR_MAP(BLUE),
1809         COLOR_MAP(CYAN),
1810         COLOR_MAP(GREEN),
1811         COLOR_MAP(MAGENTA),
1812         COLOR_MAP(RED),
1813         COLOR_MAP(WHITE),
1814         COLOR_MAP(YELLOW),
1815 };
1817 static const struct enum_map attr_map[] = {
1818 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1819         ATTR_MAP(NORMAL),
1820         ATTR_MAP(BLINK),
1821         ATTR_MAP(BOLD),
1822         ATTR_MAP(DIM),
1823         ATTR_MAP(REVERSE),
1824         ATTR_MAP(STANDOUT),
1825         ATTR_MAP(UNDERLINE),
1826 };
1828 #define set_attribute(attr, name)       map_enum(attr, attr_map, name)
1830 static int parse_step(double *opt, const char *arg)
1832         *opt = atoi(arg);
1833         if (!strchr(arg, '%'))
1834                 return OK;
1836         /* "Shift down" so 100% and 1 does not conflict. */
1837         *opt = (*opt - 1) / 100;
1838         if (*opt >= 1.0) {
1839                 *opt = 0.99;
1840                 config_msg = "Step value larger than 100%";
1841                 return ERR;
1842         }
1843         if (*opt < 0.0) {
1844                 *opt = 1;
1845                 config_msg = "Invalid step value";
1846                 return ERR;
1847         }
1848         return OK;
1851 static int
1852 parse_int(int *opt, const char *arg, int min, int max)
1854         int value = atoi(arg);
1856         if (min <= value && value <= max) {
1857                 *opt = value;
1858                 return OK;
1859         }
1861         config_msg = "Integer value out of bound";
1862         return ERR;
1865 static bool
1866 set_color(int *color, const char *name)
1868         if (map_enum(color, color_map, name))
1869                 return TRUE;
1870         if (!prefixcmp(name, "color"))
1871                 return parse_int(color, name + 5, 0, 255) == OK;
1872         return FALSE;
1875 /* Wants: object fgcolor bgcolor [attribute] */
1876 static int
1877 option_color_command(int argc, const char *argv[])
1879         struct line_info *info;
1881         if (argc < 3) {
1882                 config_msg = "Wrong number of arguments given to color command";
1883                 return ERR;
1884         }
1886         info = get_line_info(argv[0]);
1887         if (!info) {
1888                 static const struct enum_map obsolete[] = {
1889                         ENUM_MAP("main-delim",  LINE_DELIMITER),
1890                         ENUM_MAP("main-date",   LINE_DATE),
1891                         ENUM_MAP("main-author", LINE_AUTHOR),
1892                 };
1893                 int index;
1895                 if (!map_enum(&index, obsolete, argv[0])) {
1896                         config_msg = "Unknown color name";
1897                         return ERR;
1898                 }
1899                 info = &line_info[index];
1900         }
1902         if (!set_color(&info->fg, argv[1]) ||
1903             !set_color(&info->bg, argv[2])) {
1904                 config_msg = "Unknown color";
1905                 return ERR;
1906         }
1908         info->attr = 0;
1909         while (argc-- > 3) {
1910                 int attr;
1912                 if (!set_attribute(&attr, argv[argc])) {
1913                         config_msg = "Unknown attribute";
1914                         return ERR;
1915                 }
1916                 info->attr |= attr;
1917         }
1919         return OK;
1922 static int parse_bool(bool *opt, const char *arg)
1924         *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1925                 ? TRUE : FALSE;
1926         return OK;
1929 static int parse_enum_do(unsigned int *opt, const char *arg,
1930                          const struct enum_map *map, size_t map_size)
1932         bool is_true;
1934         assert(map_size > 1);
1936         if (map_enum_do(map, map_size, (int *) opt, arg))
1937                 return OK;
1939         if (parse_bool(&is_true, arg) != OK)
1940                 return ERR;
1942         *opt = is_true ? map[1].value : map[0].value;
1943         return OK;
1946 #define parse_enum(opt, arg, map) \
1947         parse_enum_do(opt, arg, map, ARRAY_SIZE(map))
1949 static int
1950 parse_string(char *opt, const char *arg, size_t optsize)
1952         int arglen = strlen(arg);
1954         switch (arg[0]) {
1955         case '\"':
1956         case '\'':
1957                 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1958                         config_msg = "Unmatched quotation";
1959                         return ERR;
1960                 }
1961                 arg += 1; arglen -= 2;
1962         default:
1963                 string_ncopy_do(opt, optsize, arg, arglen);
1964                 return OK;
1965         }
1968 /* Wants: name = value */
1969 static int
1970 option_set_command(int argc, const char *argv[])
1972         if (argc != 3) {
1973                 config_msg = "Wrong number of arguments given to set command";
1974                 return ERR;
1975         }
1977         if (strcmp(argv[1], "=")) {
1978                 config_msg = "No value assigned";
1979                 return ERR;
1980         }
1982         if (!strcmp(argv[0], "show-author"))
1983                 return parse_enum(&opt_author, argv[2], author_map);
1985         if (!strcmp(argv[0], "show-date"))
1986                 return parse_enum(&opt_date, argv[2], date_map);
1988         if (!strcmp(argv[0], "show-rev-graph"))
1989                 return parse_bool(&opt_rev_graph, argv[2]);
1991         if (!strcmp(argv[0], "show-refs"))
1992                 return parse_bool(&opt_show_refs, argv[2]);
1994         if (!strcmp(argv[0], "show-line-numbers"))
1995                 return parse_bool(&opt_line_number, argv[2]);
1997         if (!strcmp(argv[0], "line-graphics"))
1998                 return parse_bool(&opt_line_graphics, argv[2]);
2000         if (!strcmp(argv[0], "line-number-interval"))
2001                 return parse_int(&opt_num_interval, argv[2], 1, 1024);
2003         if (!strcmp(argv[0], "author-width"))
2004                 return parse_int(&opt_author_cols, argv[2], 0, 1024);
2006         if (!strcmp(argv[0], "horizontal-scroll"))
2007                 return parse_step(&opt_hscroll, argv[2]);
2009         if (!strcmp(argv[0], "split-view-height"))
2010                 return parse_step(&opt_scale_split_view, argv[2]);
2012         if (!strcmp(argv[0], "tab-size"))
2013                 return parse_int(&opt_tab_size, argv[2], 1, 1024);
2015         if (!strcmp(argv[0], "commit-encoding"))
2016                 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
2018         config_msg = "Unknown variable name";
2019         return ERR;
2022 /* Wants: mode request key */
2023 static int
2024 option_bind_command(int argc, const char *argv[])
2026         enum request request;
2027         int keymap = -1;
2028         int key;
2030         if (argc < 3) {
2031                 config_msg = "Wrong number of arguments given to bind command";
2032                 return ERR;
2033         }
2035         if (!set_keymap(&keymap, argv[0])) {
2036                 config_msg = "Unknown key map";
2037                 return ERR;
2038         }
2040         key = get_key_value(argv[1]);
2041         if (key == ERR) {
2042                 config_msg = "Unknown key";
2043                 return ERR;
2044         }
2046         request = get_request(argv[2]);
2047         if (request == REQ_UNKNOWN) {
2048                 static const struct enum_map obsolete[] = {
2049                         ENUM_MAP("cherry-pick",         REQ_NONE),
2050                         ENUM_MAP("screen-resize",       REQ_NONE),
2051                         ENUM_MAP("tree-parent",         REQ_PARENT),
2052                 };
2053                 int alias;
2055                 if (map_enum(&alias, obsolete, argv[2])) {
2056                         if (alias != REQ_NONE)
2057                                 add_keybinding(keymap, alias, key);
2058                         config_msg = "Obsolete request name";
2059                         return ERR;
2060                 }
2061         }
2062         if (request == REQ_UNKNOWN && *argv[2]++ == '!')
2063                 request = add_run_request(keymap, key, argv + 2);
2064         if (request == REQ_UNKNOWN) {
2065                 config_msg = "Unknown request name";
2066                 return ERR;
2067         }
2069         add_keybinding(keymap, request, key);
2071         return OK;
2074 static int
2075 set_option(const char *opt, char *value)
2077         const char *argv[SIZEOF_ARG];
2078         int argc = 0;
2080         if (!argv_from_string(argv, &argc, value)) {
2081                 config_msg = "Too many option arguments";
2082                 return ERR;
2083         }
2085         if (!strcmp(opt, "color"))
2086                 return option_color_command(argc, argv);
2088         if (!strcmp(opt, "set"))
2089                 return option_set_command(argc, argv);
2091         if (!strcmp(opt, "bind"))
2092                 return option_bind_command(argc, argv);
2094         config_msg = "Unknown option command";
2095         return ERR;
2098 static int
2099 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
2101         int status = OK;
2103         config_lineno++;
2104         config_msg = "Internal error";
2106         /* Check for comment markers, since read_properties() will
2107          * only ensure opt and value are split at first " \t". */
2108         optlen = strcspn(opt, "#");
2109         if (optlen == 0)
2110                 return OK;
2112         if (opt[optlen] != 0) {
2113                 config_msg = "No option value";
2114                 status = ERR;
2116         }  else {
2117                 /* Look for comment endings in the value. */
2118                 size_t len = strcspn(value, "#");
2120                 if (len < valuelen) {
2121                         valuelen = len;
2122                         value[valuelen] = 0;
2123                 }
2125                 status = set_option(opt, value);
2126         }
2128         if (status == ERR) {
2129                 warn("Error on line %d, near '%.*s': %s",
2130                      config_lineno, (int) optlen, opt, config_msg);
2131                 config_errors = TRUE;
2132         }
2134         /* Always keep going if errors are encountered. */
2135         return OK;
2138 static void
2139 load_option_file(const char *path)
2141         struct io io;
2143         /* It's OK that the file doesn't exist. */
2144         if (!io_open(&io, "%s", path))
2145                 return;
2147         config_lineno = 0;
2148         config_errors = FALSE;
2150         if (io_load(&io, " \t", read_option) == ERR ||
2151             config_errors == TRUE)
2152                 warn("Errors while loading %s.", path);
2155 static int
2156 load_options(void)
2158         const char *home = getenv("HOME");
2159         const char *tigrc_user = getenv("TIGRC_USER");
2160         const char *tigrc_system = getenv("TIGRC_SYSTEM");
2161         const char *tig_diff_opts = getenv("TIG_DIFF_OPTS");
2162         char buf[SIZEOF_STR];
2164         if (!tigrc_system)
2165                 tigrc_system = SYSCONFDIR "/tigrc";
2166         load_option_file(tigrc_system);
2168         if (!tigrc_user) {
2169                 if (!home || !string_format(buf, "%s/.tigrc", home))
2170                         return ERR;
2171                 tigrc_user = buf;
2172         }
2173         load_option_file(tigrc_user);
2175         /* Add _after_ loading config files to avoid adding run requests
2176          * that conflict with keybindings. */
2177         add_builtin_run_requests();
2179         if (!opt_diff_args && tig_diff_opts && *tig_diff_opts) {
2180                 static const char *diff_opts[SIZEOF_ARG] = { NULL };
2181                 int argc = 0;
2183                 if (!string_format(buf, "%s", tig_diff_opts) ||
2184                     !argv_from_string(diff_opts, &argc, buf))
2185                         die("TIG_DIFF_OPTS contains too many arguments");
2186                 else if (!argv_copy(&opt_diff_args, diff_opts))
2187                         die("Failed to format TIG_DIFF_OPTS arguments");
2188         }
2190         return OK;
2194 /*
2195  * The viewer
2196  */
2198 struct view;
2199 struct view_ops;
2201 /* The display array of active views and the index of the current view. */
2202 static struct view *display[2];
2203 static unsigned int current_view;
2205 #define foreach_displayed_view(view, i) \
2206         for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
2208 #define displayed_views()       (display[1] != NULL ? 2 : 1)
2210 /* Current head and commit ID */
2211 static char ref_blob[SIZEOF_REF]        = "";
2212 static char ref_commit[SIZEOF_REF]      = "HEAD";
2213 static char ref_head[SIZEOF_REF]        = "HEAD";
2214 static char ref_branch[SIZEOF_REF]      = "";
2216 enum view_type {
2217         VIEW_MAIN,
2218         VIEW_DIFF,
2219         VIEW_LOG,
2220         VIEW_TREE,
2221         VIEW_BLOB,
2222         VIEW_BLAME,
2223         VIEW_BRANCH,
2224         VIEW_HELP,
2225         VIEW_PAGER,
2226         VIEW_STATUS,
2227         VIEW_STAGE,
2228 };
2230 struct view {
2231         enum view_type type;    /* View type */
2232         const char *name;       /* View name */
2233         const char *cmd_env;    /* Command line set via environment */
2234         const char *id;         /* Points to either of ref_{head,commit,blob} */
2236         struct view_ops *ops;   /* View operations */
2238         enum keymap keymap;     /* What keymap does this view have */
2239         bool git_dir;           /* Whether the view requires a git directory. */
2241         char ref[SIZEOF_REF];   /* Hovered commit reference */
2242         char vid[SIZEOF_REF];   /* View ID. Set to id member when updating. */
2244         int height, width;      /* The width and height of the main window */
2245         WINDOW *win;            /* The main window */
2246         WINDOW *title;          /* The title window living below the main window */
2248         /* Navigation */
2249         unsigned long offset;   /* Offset of the window top */
2250         unsigned long yoffset;  /* Offset from the window side. */
2251         unsigned long lineno;   /* Current line number */
2252         unsigned long p_offset; /* Previous offset of the window top */
2253         unsigned long p_yoffset;/* Previous offset from the window side */
2254         unsigned long p_lineno; /* Previous current line number */
2255         bool p_restore;         /* Should the previous position be restored. */
2257         /* Searching */
2258         char grep[SIZEOF_STR];  /* Search string */
2259         regex_t *regex;         /* Pre-compiled regexp */
2261         /* If non-NULL, points to the view that opened this view. If this view
2262          * is closed tig will switch back to the parent view. */
2263         struct view *parent;
2264         struct view *prev;
2266         /* Buffering */
2267         size_t lines;           /* Total number of lines */
2268         struct line *line;      /* Line index */
2269         unsigned int digits;    /* Number of digits in the lines member. */
2271         /* Drawing */
2272         struct line *curline;   /* Line currently being drawn. */
2273         enum line_type curtype; /* Attribute currently used for drawing. */
2274         unsigned long col;      /* Column when drawing. */
2275         bool has_scrolled;      /* View was scrolled. */
2277         /* Loading */
2278         const char **argv;      /* Shell command arguments. */
2279         const char *dir;        /* Directory from which to execute. */
2280         struct io io;
2281         struct io *pipe;
2282         time_t start_time;
2283         time_t update_secs;
2284 };
2286 struct view_ops {
2287         /* What type of content being displayed. Used in the title bar. */
2288         const char *type;
2289         /* Default command arguments. */
2290         const char **argv;
2291         /* Open and reads in all view content. */
2292         bool (*open)(struct view *view);
2293         /* Read one line; updates view->line. */
2294         bool (*read)(struct view *view, char *data);
2295         /* Draw one line; @lineno must be < view->height. */
2296         bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
2297         /* Depending on view handle a special requests. */
2298         enum request (*request)(struct view *view, enum request request, struct line *line);
2299         /* Search for regexp in a line. */
2300         bool (*grep)(struct view *view, struct line *line);
2301         /* Select line */
2302         void (*select)(struct view *view, struct line *line);
2303         /* Prepare view for loading */
2304         bool (*prepare)(struct view *view);
2305 };
2307 static struct view_ops blame_ops;
2308 static struct view_ops blob_ops;
2309 static struct view_ops diff_ops;
2310 static struct view_ops help_ops;
2311 static struct view_ops log_ops;
2312 static struct view_ops main_ops;
2313 static struct view_ops pager_ops;
2314 static struct view_ops stage_ops;
2315 static struct view_ops status_ops;
2316 static struct view_ops tree_ops;
2317 static struct view_ops branch_ops;
2319 #define VIEW_STR(type, name, env, ref, ops, map, git) \
2320         { type, name, #env, ref, ops, map, git }
2322 #define VIEW_(id, name, ops, git, ref) \
2323         VIEW_STR(VIEW_##id, name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
2325 static struct view views[] = {
2326         VIEW_(MAIN,   "main",   &main_ops,   TRUE,  ref_head),
2327         VIEW_(DIFF,   "diff",   &diff_ops,   TRUE,  ref_commit),
2328         VIEW_(LOG,    "log",    &log_ops,    TRUE,  ref_head),
2329         VIEW_(TREE,   "tree",   &tree_ops,   TRUE,  ref_commit),
2330         VIEW_(BLOB,   "blob",   &blob_ops,   TRUE,  ref_blob),
2331         VIEW_(BLAME,  "blame",  &blame_ops,  TRUE,  ref_commit),
2332         VIEW_(BRANCH, "branch", &branch_ops, TRUE,  ref_head),
2333         VIEW_(HELP,   "help",   &help_ops,   FALSE, ""),
2334         VIEW_(PAGER,  "pager",  &pager_ops,  FALSE, ""),
2335         VIEW_(STATUS, "status", &status_ops, TRUE,  ""),
2336         VIEW_(STAGE,  "stage",  &stage_ops,  TRUE,  ""),
2337 };
2339 #define VIEW(req)       (&views[(req) - REQ_OFFSET - 1])
2341 #define foreach_view(view, i) \
2342         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2344 #define view_is_displayed(view) \
2345         (view == display[0] || view == display[1])
2347 static enum request
2348 view_request(struct view *view, enum request request)
2350         if (!view || !view->lines)
2351                 return request;
2352         return view->ops->request(view, request, &view->line[view->lineno]);
2356 /*
2357  * View drawing.
2358  */
2360 static inline void
2361 set_view_attr(struct view *view, enum line_type type)
2363         if (!view->curline->selected && view->curtype != type) {
2364                 (void) wattrset(view->win, get_line_attr(type));
2365                 wchgat(view->win, -1, 0, type, NULL);
2366                 view->curtype = type;
2367         }
2370 static int
2371 draw_chars(struct view *view, enum line_type type, const char *string,
2372            int max_len, bool use_tilde)
2374         static char out_buffer[BUFSIZ * 2];
2375         int len = 0;
2376         int col = 0;
2377         int trimmed = FALSE;
2378         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2380         if (max_len <= 0)
2381                 return 0;
2383         len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde, opt_tab_size);
2385         set_view_attr(view, type);
2386         if (len > 0) {
2387                 if (opt_iconv_out != ICONV_NONE) {
2388                         ICONV_CONST char *inbuf = (ICONV_CONST char *) string;
2389                         size_t inlen = len + 1;
2391                         char *outbuf = out_buffer;
2392                         size_t outlen = sizeof(out_buffer);
2394                         size_t ret;
2396                         ret = iconv(opt_iconv_out, &inbuf, &inlen, &outbuf, &outlen);
2397                         if (ret != (size_t) -1) {
2398                                 string = out_buffer;
2399                                 len = sizeof(out_buffer) - outlen;
2400                         }
2401                 }
2403                 waddnstr(view->win, string, len);
2404         }
2405         if (trimmed && use_tilde) {
2406                 set_view_attr(view, LINE_DELIMITER);
2407                 waddch(view->win, '~');
2408                 col++;
2409         }
2411         return col;
2414 static int
2415 draw_space(struct view *view, enum line_type type, int max, int spaces)
2417         static char space[] = "                    ";
2418         int col = 0;
2420         spaces = MIN(max, spaces);
2422         while (spaces > 0) {
2423                 int len = MIN(spaces, sizeof(space) - 1);
2425                 col += draw_chars(view, type, space, len, FALSE);
2426                 spaces -= len;
2427         }
2429         return col;
2432 static bool
2433 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
2435         char text[SIZEOF_STR];
2437         do {
2438                 size_t pos = string_expand(text, sizeof(text), string, opt_tab_size);
2440                 view->col += draw_chars(view, type, text, view->width + view->yoffset - view->col, trim);
2441                 string += pos;
2442         } while (*string && view->width + view->yoffset > view->col);
2444         return view->width + view->yoffset <= view->col;
2447 static bool
2448 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
2450         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2451         int max = view->width + view->yoffset - view->col;
2452         int i;
2454         if (max < size)
2455                 size = max;
2457         set_view_attr(view, type);
2458         /* Using waddch() instead of waddnstr() ensures that
2459          * they'll be rendered correctly for the cursor line. */
2460         for (i = skip; i < size; i++)
2461                 waddch(view->win, graphic[i]);
2463         view->col += size;
2464         if (size < max && skip <= size)
2465                 waddch(view->win, ' ');
2466         view->col++;
2468         return view->width + view->yoffset <= view->col;
2471 static bool
2472 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
2474         int max = MIN(view->width + view->yoffset - view->col, len);
2475         int col;
2477         if (text)
2478                 col = draw_chars(view, type, text, max - 1, trim);
2479         else
2480                 col = draw_space(view, type, max - 1, max - 1);
2482         view->col += col;
2483         view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
2484         return view->width + view->yoffset <= view->col;
2487 static bool
2488 draw_date(struct view *view, struct time *time)
2490         const char *date = mkdate(time, opt_date);
2491         int cols = opt_date == DATE_SHORT ? DATE_SHORT_COLS : DATE_COLS;
2493         return draw_field(view, LINE_DATE, date, cols, FALSE);
2496 static bool
2497 draw_author(struct view *view, const char *author)
2499         bool trim = opt_author_cols == 0 || opt_author_cols > 5;
2500         bool abbreviate = opt_author == AUTHOR_ABBREVIATED || !trim;
2502         if (abbreviate && author)
2503                 author = get_author_initials(author);
2505         return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2508 static bool
2509 draw_mode(struct view *view, mode_t mode)
2511         const char *str;
2513         if (S_ISDIR(mode))
2514                 str = "drwxr-xr-x";
2515         else if (S_ISLNK(mode))
2516                 str = "lrwxrwxrwx";
2517         else if (S_ISGITLINK(mode))
2518                 str = "m---------";
2519         else if (S_ISREG(mode) && mode & S_IXUSR)
2520                 str = "-rwxr-xr-x";
2521         else if (S_ISREG(mode))
2522                 str = "-rw-r--r--";
2523         else
2524                 str = "----------";
2526         return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2529 static bool
2530 draw_lineno(struct view *view, unsigned int lineno)
2532         char number[10];
2533         int digits3 = view->digits < 3 ? 3 : view->digits;
2534         int max = MIN(view->width + view->yoffset - view->col, digits3);
2535         char *text = NULL;
2536         chtype separator = opt_line_graphics ? ACS_VLINE : '|';
2538         lineno += view->offset + 1;
2539         if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2540                 static char fmt[] = "%1ld";
2542                 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2543                 if (string_format(number, fmt, lineno))
2544                         text = number;
2545         }
2546         if (text)
2547                 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2548         else
2549                 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2550         return draw_graphic(view, LINE_DEFAULT, &separator, 1);
2553 static bool
2554 draw_view_line(struct view *view, unsigned int lineno)
2556         struct line *line;
2557         bool selected = (view->offset + lineno == view->lineno);
2559         assert(view_is_displayed(view));
2561         if (view->offset + lineno >= view->lines)
2562                 return FALSE;
2564         line = &view->line[view->offset + lineno];
2566         wmove(view->win, lineno, 0);
2567         if (line->cleareol)
2568                 wclrtoeol(view->win);
2569         view->col = 0;
2570         view->curline = line;
2571         view->curtype = LINE_NONE;
2572         line->selected = FALSE;
2573         line->dirty = line->cleareol = 0;
2575         if (selected) {
2576                 set_view_attr(view, LINE_CURSOR);
2577                 line->selected = TRUE;
2578                 view->ops->select(view, line);
2579         }
2581         return view->ops->draw(view, line, lineno);
2584 static void
2585 redraw_view_dirty(struct view *view)
2587         bool dirty = FALSE;
2588         int lineno;
2590         for (lineno = 0; lineno < view->height; lineno++) {
2591                 if (view->offset + lineno >= view->lines)
2592                         break;
2593                 if (!view->line[view->offset + lineno].dirty)
2594                         continue;
2595                 dirty = TRUE;
2596                 if (!draw_view_line(view, lineno))
2597                         break;
2598         }
2600         if (!dirty)
2601                 return;
2602         wnoutrefresh(view->win);
2605 static void
2606 redraw_view_from(struct view *view, int lineno)
2608         assert(0 <= lineno && lineno < view->height);
2610         for (; lineno < view->height; lineno++) {
2611                 if (!draw_view_line(view, lineno))
2612                         break;
2613         }
2615         wnoutrefresh(view->win);
2618 static void
2619 redraw_view(struct view *view)
2621         werase(view->win);
2622         redraw_view_from(view, 0);
2626 static void
2627 update_view_title(struct view *view)
2629         char buf[SIZEOF_STR];
2630         char state[SIZEOF_STR];
2631         size_t bufpos = 0, statelen = 0;
2633         assert(view_is_displayed(view));
2635         if (view->type != VIEW_STATUS && view->lines) {
2636                 unsigned int view_lines = view->offset + view->height;
2637                 unsigned int lines = view->lines
2638                                    ? MIN(view_lines, view->lines) * 100 / view->lines
2639                                    : 0;
2641                 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2642                                    view->ops->type,
2643                                    view->lineno + 1,
2644                                    view->lines,
2645                                    lines);
2647         }
2649         if (view->pipe) {
2650                 time_t secs = time(NULL) - view->start_time;
2652                 /* Three git seconds are a long time ... */
2653                 if (secs > 2)
2654                         string_format_from(state, &statelen, " loading %lds", secs);
2655         }
2657         string_format_from(buf, &bufpos, "[%s]", view->name);
2658         if (*view->ref && bufpos < view->width) {
2659                 size_t refsize = strlen(view->ref);
2660                 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2662                 if (minsize < view->width)
2663                         refsize = view->width - minsize + 7;
2664                 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2665         }
2667         if (statelen && bufpos < view->width) {
2668                 string_format_from(buf, &bufpos, "%s", state);
2669         }
2671         if (view == display[current_view])
2672                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2673         else
2674                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2676         mvwaddnstr(view->title, 0, 0, buf, bufpos);
2677         wclrtoeol(view->title);
2678         wnoutrefresh(view->title);
2681 static int
2682 apply_step(double step, int value)
2684         if (step >= 1)
2685                 return (int) step;
2686         value *= step + 0.01;
2687         return value ? value : 1;
2690 static void
2691 resize_display(void)
2693         int offset, i;
2694         struct view *base = display[0];
2695         struct view *view = display[1] ? display[1] : display[0];
2697         /* Setup window dimensions */
2699         getmaxyx(stdscr, base->height, base->width);
2701         /* Make room for the status window. */
2702         base->height -= 1;
2704         if (view != base) {
2705                 /* Horizontal split. */
2706                 view->width   = base->width;
2707                 view->height  = apply_step(opt_scale_split_view, base->height);
2708                 view->height  = MAX(view->height, MIN_VIEW_HEIGHT);
2709                 view->height  = MIN(view->height, base->height - MIN_VIEW_HEIGHT);
2710                 base->height -= view->height;
2712                 /* Make room for the title bar. */
2713                 view->height -= 1;
2714         }
2716         /* Make room for the title bar. */
2717         base->height -= 1;
2719         offset = 0;
2721         foreach_displayed_view (view, i) {
2722                 if (!view->win) {
2723                         view->win = newwin(view->height, 0, offset, 0);
2724                         if (!view->win)
2725                                 die("Failed to create %s view", view->name);
2727                         scrollok(view->win, FALSE);
2729                         view->title = newwin(1, 0, offset + view->height, 0);
2730                         if (!view->title)
2731                                 die("Failed to create title window");
2733                 } else {
2734                         wresize(view->win, view->height, view->width);
2735                         mvwin(view->win,   offset, 0);
2736                         mvwin(view->title, offset + view->height, 0);
2737                 }
2739                 offset += view->height + 1;
2740         }
2743 static void
2744 redraw_display(bool clear)
2746         struct view *view;
2747         int i;
2749         foreach_displayed_view (view, i) {
2750                 if (clear)
2751                         wclear(view->win);
2752                 redraw_view(view);
2753                 update_view_title(view);
2754         }
2758 /*
2759  * Option management
2760  */
2762 static void
2763 toggle_enum_option_do(unsigned int *opt, const char *help,
2764                       const struct enum_map *map, size_t size)
2766         *opt = (*opt + 1) % size;
2767         redraw_display(FALSE);
2768         report("Displaying %s %s", enum_name(map[*opt]), help);
2771 #define toggle_enum_option(opt, help, map) \
2772         toggle_enum_option_do(opt, help, map, ARRAY_SIZE(map))
2774 #define toggle_date() toggle_enum_option(&opt_date, "dates", date_map)
2775 #define toggle_author() toggle_enum_option(&opt_author, "author names", author_map)
2777 static void
2778 toggle_view_option(bool *option, const char *help)
2780         *option = !*option;
2781         redraw_display(FALSE);
2782         report("%sabling %s", *option ? "En" : "Dis", help);
2785 static void
2786 open_option_menu(void)
2788         const struct menu_item menu[] = {
2789                 { '.', "line numbers", &opt_line_number },
2790                 { 'D', "date display", &opt_date },
2791                 { 'A', "author display", &opt_author },
2792                 { 'g', "revision graph display", &opt_rev_graph },
2793                 { 'F', "reference display", &opt_show_refs },
2794                 { 0 }
2795         };
2796         int selected = 0;
2798         if (prompt_menu("Toggle option", menu, &selected)) {
2799                 if (menu[selected].data == &opt_date)
2800                         toggle_date();
2801                 else if (menu[selected].data == &opt_author)
2802                         toggle_author();
2803                 else
2804                         toggle_view_option(menu[selected].data, menu[selected].text);
2805         }
2808 static void
2809 maximize_view(struct view *view)
2811         memset(display, 0, sizeof(display));
2812         current_view = 0;
2813         display[current_view] = view;
2814         resize_display();
2815         redraw_display(FALSE);
2816         report("");
2820 /*
2821  * Navigation
2822  */
2824 static bool
2825 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2827         if (lineno >= view->lines)
2828                 lineno = view->lines > 0 ? view->lines - 1 : 0;
2830         if (offset > lineno || offset + view->height <= lineno) {
2831                 unsigned long half = view->height / 2;
2833                 if (lineno > half)
2834                         offset = lineno - half;
2835                 else
2836                         offset = 0;
2837         }
2839         if (offset != view->offset || lineno != view->lineno) {
2840                 view->offset = offset;
2841                 view->lineno = lineno;
2842                 return TRUE;
2843         }
2845         return FALSE;
2848 /* Scrolling backend */
2849 static void
2850 do_scroll_view(struct view *view, int lines)
2852         bool redraw_current_line = FALSE;
2854         /* The rendering expects the new offset. */
2855         view->offset += lines;
2857         assert(0 <= view->offset && view->offset < view->lines);
2858         assert(lines);
2860         /* Move current line into the view. */
2861         if (view->lineno < view->offset) {
2862                 view->lineno = view->offset;
2863                 redraw_current_line = TRUE;
2864         } else if (view->lineno >= view->offset + view->height) {
2865                 view->lineno = view->offset + view->height - 1;
2866                 redraw_current_line = TRUE;
2867         }
2869         assert(view->offset <= view->lineno && view->lineno < view->lines);
2871         /* Redraw the whole screen if scrolling is pointless. */
2872         if (view->height < ABS(lines)) {
2873                 redraw_view(view);
2875         } else {
2876                 int line = lines > 0 ? view->height - lines : 0;
2877                 int end = line + ABS(lines);
2879                 scrollok(view->win, TRUE);
2880                 wscrl(view->win, lines);
2881                 scrollok(view->win, FALSE);
2883                 while (line < end && draw_view_line(view, line))
2884                         line++;
2886                 if (redraw_current_line)
2887                         draw_view_line(view, view->lineno - view->offset);
2888                 wnoutrefresh(view->win);
2889         }
2891         view->has_scrolled = TRUE;
2892         report("");
2895 /* Scroll frontend */
2896 static void
2897 scroll_view(struct view *view, enum request request)
2899         int lines = 1;
2901         assert(view_is_displayed(view));
2903         switch (request) {
2904         case REQ_SCROLL_LEFT:
2905                 if (view->yoffset == 0) {
2906                         report("Cannot scroll beyond the first column");
2907                         return;
2908                 }
2909                 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2910                         view->yoffset = 0;
2911                 else
2912                         view->yoffset -= apply_step(opt_hscroll, view->width);
2913                 redraw_view_from(view, 0);
2914                 report("");
2915                 return;
2916         case REQ_SCROLL_RIGHT:
2917                 view->yoffset += apply_step(opt_hscroll, view->width);
2918                 redraw_view(view);
2919                 report("");
2920                 return;
2921         case REQ_SCROLL_PAGE_DOWN:
2922                 lines = view->height;
2923         case REQ_SCROLL_LINE_DOWN:
2924                 if (view->offset + lines > view->lines)
2925                         lines = view->lines - view->offset;
2927                 if (lines == 0 || view->offset + view->height >= view->lines) {
2928                         report("Cannot scroll beyond the last line");
2929                         return;
2930                 }
2931                 break;
2933         case REQ_SCROLL_PAGE_UP:
2934                 lines = view->height;
2935         case REQ_SCROLL_LINE_UP:
2936                 if (lines > view->offset)
2937                         lines = view->offset;
2939                 if (lines == 0) {
2940                         report("Cannot scroll beyond the first line");
2941                         return;
2942                 }
2944                 lines = -lines;
2945                 break;
2947         default:
2948                 die("request %d not handled in switch", request);
2949         }
2951         do_scroll_view(view, lines);
2954 /* Cursor moving */
2955 static void
2956 move_view(struct view *view, enum request request)
2958         int scroll_steps = 0;
2959         int steps;
2961         switch (request) {
2962         case REQ_MOVE_FIRST_LINE:
2963                 steps = -view->lineno;
2964                 break;
2966         case REQ_MOVE_LAST_LINE:
2967                 steps = view->lines - view->lineno - 1;
2968                 break;
2970         case REQ_MOVE_PAGE_UP:
2971                 steps = view->height > view->lineno
2972                       ? -view->lineno : -view->height;
2973                 break;
2975         case REQ_MOVE_PAGE_DOWN:
2976                 steps = view->lineno + view->height >= view->lines
2977                       ? view->lines - view->lineno - 1 : view->height;
2978                 break;
2980         case REQ_MOVE_UP:
2981                 steps = -1;
2982                 break;
2984         case REQ_MOVE_DOWN:
2985                 steps = 1;
2986                 break;
2988         default:
2989                 die("request %d not handled in switch", request);
2990         }
2992         if (steps <= 0 && view->lineno == 0) {
2993                 report("Cannot move beyond the first line");
2994                 return;
2996         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
2997                 report("Cannot move beyond the last line");
2998                 return;
2999         }
3001         /* Move the current line */
3002         view->lineno += steps;
3003         assert(0 <= view->lineno && view->lineno < view->lines);
3005         /* Check whether the view needs to be scrolled */
3006         if (view->lineno < view->offset ||
3007             view->lineno >= view->offset + view->height) {
3008                 scroll_steps = steps;
3009                 if (steps < 0 && -steps > view->offset) {
3010                         scroll_steps = -view->offset;
3012                 } else if (steps > 0) {
3013                         if (view->lineno == view->lines - 1 &&
3014                             view->lines > view->height) {
3015                                 scroll_steps = view->lines - view->offset - 1;
3016                                 if (scroll_steps >= view->height)
3017                                         scroll_steps -= view->height - 1;
3018                         }
3019                 }
3020         }
3022         if (!view_is_displayed(view)) {
3023                 view->offset += scroll_steps;
3024                 assert(0 <= view->offset && view->offset < view->lines);
3025                 view->ops->select(view, &view->line[view->lineno]);
3026                 return;
3027         }
3029         /* Repaint the old "current" line if we be scrolling */
3030         if (ABS(steps) < view->height)
3031                 draw_view_line(view, view->lineno - steps - view->offset);
3033         if (scroll_steps) {
3034                 do_scroll_view(view, scroll_steps);
3035                 return;
3036         }
3038         /* Draw the current line */
3039         draw_view_line(view, view->lineno - view->offset);
3041         wnoutrefresh(view->win);
3042         report("");
3046 /*
3047  * Searching
3048  */
3050 static void search_view(struct view *view, enum request request);
3052 static bool
3053 grep_text(struct view *view, const char *text[])
3055         regmatch_t pmatch;
3056         size_t i;
3058         for (i = 0; text[i]; i++)
3059                 if (*text[i] &&
3060                     regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
3061                         return TRUE;
3062         return FALSE;
3065 static void
3066 select_view_line(struct view *view, unsigned long lineno)
3068         unsigned long old_lineno = view->lineno;
3069         unsigned long old_offset = view->offset;
3071         if (goto_view_line(view, view->offset, lineno)) {
3072                 if (view_is_displayed(view)) {
3073                         if (old_offset != view->offset) {
3074                                 redraw_view(view);
3075                         } else {
3076                                 draw_view_line(view, old_lineno - view->offset);
3077                                 draw_view_line(view, view->lineno - view->offset);
3078                                 wnoutrefresh(view->win);
3079                         }
3080                 } else {
3081                         view->ops->select(view, &view->line[view->lineno]);
3082                 }
3083         }
3086 static void
3087 find_next(struct view *view, enum request request)
3089         unsigned long lineno = view->lineno;
3090         int direction;
3092         if (!*view->grep) {
3093                 if (!*opt_search)
3094                         report("No previous search");
3095                 else
3096                         search_view(view, request);
3097                 return;
3098         }
3100         switch (request) {
3101         case REQ_SEARCH:
3102         case REQ_FIND_NEXT:
3103                 direction = 1;
3104                 break;
3106         case REQ_SEARCH_BACK:
3107         case REQ_FIND_PREV:
3108                 direction = -1;
3109                 break;
3111         default:
3112                 return;
3113         }
3115         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
3116                 lineno += direction;
3118         /* Note, lineno is unsigned long so will wrap around in which case it
3119          * will become bigger than view->lines. */
3120         for (; lineno < view->lines; lineno += direction) {
3121                 if (view->ops->grep(view, &view->line[lineno])) {
3122                         select_view_line(view, lineno);
3123                         report("Line %ld matches '%s'", lineno + 1, view->grep);
3124                         return;
3125                 }
3126         }
3128         report("No match found for '%s'", view->grep);
3131 static void
3132 search_view(struct view *view, enum request request)
3134         int regex_err;
3136         if (view->regex) {
3137                 regfree(view->regex);
3138                 *view->grep = 0;
3139         } else {
3140                 view->regex = calloc(1, sizeof(*view->regex));
3141                 if (!view->regex)
3142                         return;
3143         }
3145         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
3146         if (regex_err != 0) {
3147                 char buf[SIZEOF_STR] = "unknown error";
3149                 regerror(regex_err, view->regex, buf, sizeof(buf));
3150                 report("Search failed: %s", buf);
3151                 return;
3152         }
3154         string_copy(view->grep, opt_search);
3156         find_next(view, request);
3159 /*
3160  * Incremental updating
3161  */
3163 static void
3164 reset_view(struct view *view)
3166         int i;
3168         for (i = 0; i < view->lines; i++)
3169                 free(view->line[i].data);
3170         free(view->line);
3172         view->p_offset = view->offset;
3173         view->p_yoffset = view->yoffset;
3174         view->p_lineno = view->lineno;
3176         view->line = NULL;
3177         view->offset = 0;
3178         view->yoffset = 0;
3179         view->lines  = 0;
3180         view->lineno = 0;
3181         view->vid[0] = 0;
3182         view->update_secs = 0;
3185 static const char *
3186 format_arg(const char *name)
3188         static struct {
3189                 const char *name;
3190                 size_t namelen;
3191                 const char *value;
3192                 const char *value_if_empty;
3193         } vars[] = {
3194 #define FORMAT_VAR(name, value, value_if_empty) \
3195         { name, STRING_SIZE(name), value, value_if_empty }
3196                 FORMAT_VAR("%(directory)",      opt_path,       ""),
3197                 FORMAT_VAR("%(file)",           opt_file,       ""),
3198                 FORMAT_VAR("%(ref)",            opt_ref,        "HEAD"),
3199                 FORMAT_VAR("%(head)",           ref_head,       ""),
3200                 FORMAT_VAR("%(commit)",         ref_commit,     ""),
3201                 FORMAT_VAR("%(blob)",           ref_blob,       ""),
3202                 FORMAT_VAR("%(branch)",         ref_branch,     ""),
3203         };
3204         int i;
3206         for (i = 0; i < ARRAY_SIZE(vars); i++)
3207                 if (!strncmp(name, vars[i].name, vars[i].namelen))
3208                         return *vars[i].value ? vars[i].value : vars[i].value_if_empty;
3210         report("Unknown replacement: `%s`", name);
3211         return NULL;
3214 static bool
3215 format_argv(const char ***dst_argv, const char *src_argv[], bool replace)
3217         char buf[SIZEOF_STR];
3218         int argc;
3220         argv_free(*dst_argv);
3222         for (argc = 0; src_argv[argc]; argc++) {
3223                 const char *arg = src_argv[argc];
3224                 size_t bufpos = 0;
3226                 if (!strcmp(arg, "%(fileargs)")) {
3227                         if (!argv_append_array(dst_argv, opt_file_args))
3228                                 break;
3229                         continue;
3231                 } else if (!strcmp(arg, "%(diffargs)")) {
3232                         if (!argv_append_array(dst_argv, opt_diff_args))
3233                                 break;
3234                         continue;
3236                 } else if (!strcmp(arg, "%(revargs)")) {
3237                         if (!argv_append_array(dst_argv, opt_rev_args))
3238                                 break;
3239                         continue;
3240                 }
3242                 while (arg) {
3243                         char *next = strstr(arg, "%(");
3244                         int len = next - arg;
3245                         const char *value;
3247                         if (!next || !replace) {
3248                                 len = strlen(arg);
3249                                 value = "";
3251                         } else {
3252                                 value = format_arg(next);
3254                                 if (!value) {
3255                                         return FALSE;
3256                                 }
3257                         }
3259                         if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
3260                                 return FALSE;
3262                         arg = next && replace ? strchr(next, ')') + 1 : NULL;
3263                 }
3265                 if (!argv_append(dst_argv, buf))
3266                         break;
3267         }
3269         return src_argv[argc] == NULL;
3272 static bool
3273 restore_view_position(struct view *view)
3275         if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
3276                 return FALSE;
3278         /* Changing the view position cancels the restoring. */
3279         /* FIXME: Changing back to the first line is not detected. */
3280         if (view->offset != 0 || view->lineno != 0) {
3281                 view->p_restore = FALSE;
3282                 return FALSE;
3283         }
3285         if (goto_view_line(view, view->p_offset, view->p_lineno) &&
3286             view_is_displayed(view))
3287                 werase(view->win);
3289         view->yoffset = view->p_yoffset;
3290         view->p_restore = FALSE;
3292         return TRUE;
3295 static void
3296 end_update(struct view *view, bool force)
3298         if (!view->pipe)
3299                 return;
3300         while (!view->ops->read(view, NULL))
3301                 if (!force)
3302                         return;
3303         if (force)
3304                 io_kill(view->pipe);
3305         io_done(view->pipe);
3306         view->pipe = NULL;
3309 static void
3310 setup_update(struct view *view, const char *vid)
3312         reset_view(view);
3313         string_copy_rev(view->vid, vid);
3314         view->pipe = &view->io;
3315         view->start_time = time(NULL);
3318 static bool
3319 prepare_io(struct view *view, const char *dir, const char *argv[], bool replace)
3321         view->dir = dir;
3322         return format_argv(&view->argv, argv, replace);
3325 static bool
3326 prepare_update(struct view *view, const char *argv[], const char *dir)
3328         if (view->pipe)
3329                 end_update(view, TRUE);
3330         return prepare_io(view, dir, argv, FALSE);
3333 static bool
3334 start_update(struct view *view, const char **argv, const char *dir)
3336         if (view->pipe)
3337                 io_done(view->pipe);
3338         return prepare_io(view, dir, argv, FALSE) &&
3339                io_run(&view->io, IO_RD, dir, view->argv);
3342 static bool
3343 prepare_update_file(struct view *view, const char *name)
3345         if (view->pipe)
3346                 end_update(view, TRUE);
3347         argv_free(view->argv);
3348         return io_open(&view->io, "%s/%s", opt_cdup[0] ? opt_cdup : ".", name);
3351 static bool
3352 begin_update(struct view *view, bool refresh)
3354         if (view->pipe)
3355                 end_update(view, TRUE);
3357         if (!refresh) {
3358                 if (view->ops->prepare) {
3359                         if (!view->ops->prepare(view))
3360                                 return FALSE;
3361                 } else if (!prepare_io(view, NULL, view->ops->argv, TRUE)) {
3362                         return FALSE;
3363                 }
3365                 /* Put the current ref_* value to the view title ref
3366                  * member. This is needed by the blob view. Most other
3367                  * views sets it automatically after loading because the
3368                  * first line is a commit line. */
3369                 string_copy_rev(view->ref, view->id);
3370         }
3372         if (view->argv && view->argv[0] &&
3373             !io_run(&view->io, IO_RD, view->dir, view->argv))
3374                 return FALSE;
3376         setup_update(view, view->id);
3378         return TRUE;
3381 static bool
3382 update_view(struct view *view)
3384         char out_buffer[BUFSIZ * 2];
3385         char *line;
3386         /* Clear the view and redraw everything since the tree sorting
3387          * might have rearranged things. */
3388         bool redraw = view->lines == 0;
3389         bool can_read = TRUE;
3391         if (!view->pipe)
3392                 return TRUE;
3394         if (!io_can_read(view->pipe)) {
3395                 if (view->lines == 0 && view_is_displayed(view)) {
3396                         time_t secs = time(NULL) - view->start_time;
3398                         if (secs > 1 && secs > view->update_secs) {
3399                                 if (view->update_secs == 0)
3400                                         redraw_view(view);
3401                                 update_view_title(view);
3402                                 view->update_secs = secs;
3403                         }
3404                 }
3405                 return TRUE;
3406         }
3408         for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
3409                 if (opt_iconv_in != ICONV_NONE) {
3410                         ICONV_CONST char *inbuf = line;
3411                         size_t inlen = strlen(line) + 1;
3413                         char *outbuf = out_buffer;
3414                         size_t outlen = sizeof(out_buffer);
3416                         size_t ret;
3418                         ret = iconv(opt_iconv_in, &inbuf, &inlen, &outbuf, &outlen);
3419                         if (ret != (size_t) -1)
3420                                 line = out_buffer;
3421                 }
3423                 if (!view->ops->read(view, line)) {
3424                         report("Allocation failure");
3425                         end_update(view, TRUE);
3426                         return FALSE;
3427                 }
3428         }
3430         {
3431                 unsigned long lines = view->lines;
3432                 int digits;
3434                 for (digits = 0; lines; digits++)
3435                         lines /= 10;
3437                 /* Keep the displayed view in sync with line number scaling. */
3438                 if (digits != view->digits) {
3439                         view->digits = digits;
3440                         if (opt_line_number || view->type == VIEW_BLAME)
3441                                 redraw = TRUE;
3442                 }
3443         }
3445         if (io_error(view->pipe)) {
3446                 report("Failed to read: %s", io_strerror(view->pipe));
3447                 end_update(view, TRUE);
3449         } else if (io_eof(view->pipe)) {
3450                 if (view_is_displayed(view))
3451                         report("");
3452                 end_update(view, FALSE);
3453         }
3455         if (restore_view_position(view))
3456                 redraw = TRUE;
3458         if (!view_is_displayed(view))
3459                 return TRUE;
3461         if (redraw)
3462                 redraw_view_from(view, 0);
3463         else
3464                 redraw_view_dirty(view);
3466         /* Update the title _after_ the redraw so that if the redraw picks up a
3467          * commit reference in view->ref it'll be available here. */
3468         update_view_title(view);
3469         return TRUE;
3472 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
3474 static struct line *
3475 add_line_data(struct view *view, void *data, enum line_type type)
3477         struct line *line;
3479         if (!realloc_lines(&view->line, view->lines, 1))
3480                 return NULL;
3482         line = &view->line[view->lines++];
3483         memset(line, 0, sizeof(*line));
3484         line->type = type;
3485         line->data = data;
3486         line->dirty = 1;
3488         return line;
3491 static struct line *
3492 add_line_text(struct view *view, const char *text, enum line_type type)
3494         char *data = text ? strdup(text) : NULL;
3496         return data ? add_line_data(view, data, type) : NULL;
3499 static struct line *
3500 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
3502         char buf[SIZEOF_STR];
3503         va_list args;
3505         va_start(args, fmt);
3506         if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
3507                 buf[0] = 0;
3508         va_end(args);
3510         return buf[0] ? add_line_text(view, buf, type) : NULL;
3513 /*
3514  * View opening
3515  */
3517 enum open_flags {
3518         OPEN_DEFAULT = 0,       /* Use default view switching. */
3519         OPEN_SPLIT = 1,         /* Split current view. */
3520         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
3521         OPEN_REFRESH = 16,      /* Refresh view using previous command. */
3522         OPEN_PREPARED = 32,     /* Open already prepared command. */
3523 };
3525 static void
3526 open_view(struct view *prev, enum request request, enum open_flags flags)
3528         bool split = !!(flags & OPEN_SPLIT);
3529         bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
3530         bool nomaximize = !!(flags & OPEN_REFRESH);
3531         struct view *view = VIEW(request);
3532         int nviews = displayed_views();
3533         struct view *base_view = display[0];
3535         if (view == prev && nviews == 1 && !reload) {
3536                 report("Already in %s view", view->name);
3537                 return;
3538         }
3540         if (view->git_dir && !opt_git_dir[0]) {
3541                 report("The %s view is disabled in pager view", view->name);
3542                 return;
3543         }
3545         if (split) {
3546                 display[1] = view;
3547                 current_view = 1;
3548                 view->parent = prev;
3549         } else if (!nomaximize) {
3550                 /* Maximize the current view. */
3551                 memset(display, 0, sizeof(display));
3552                 current_view = 0;
3553                 display[current_view] = view;
3554         }
3556         /* No prev signals that this is the first loaded view. */
3557         if (prev && view != prev) {
3558                 view->prev = prev;
3559         }
3561         /* Resize the view when switching between split- and full-screen,
3562          * or when switching between two different full-screen views. */
3563         if (nviews != displayed_views() ||
3564             (nviews == 1 && base_view != display[0]))
3565                 resize_display();
3567         if (view->ops->open) {
3568                 if (view->pipe)
3569                         end_update(view, TRUE);
3570                 if (!view->ops->open(view)) {
3571                         report("Failed to load %s view", view->name);
3572                         return;
3573                 }
3574                 restore_view_position(view);
3576         } else if ((reload || strcmp(view->vid, view->id)) &&
3577                    !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3578                 report("Failed to load %s view", view->name);
3579                 return;
3580         }
3582         if (split && prev->lineno - prev->offset >= prev->height) {
3583                 /* Take the title line into account. */
3584                 int lines = prev->lineno - prev->offset - prev->height + 1;
3586                 /* Scroll the view that was split if the current line is
3587                  * outside the new limited view. */
3588                 do_scroll_view(prev, lines);
3589         }
3591         if (prev && view != prev && split && view_is_displayed(prev)) {
3592                 /* "Blur" the previous view. */
3593                 update_view_title(prev);
3594         }
3596         if (view->pipe && view->lines == 0) {
3597                 /* Clear the old view and let the incremental updating refill
3598                  * the screen. */
3599                 werase(view->win);
3600                 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3601                 report("");
3602         } else if (view_is_displayed(view)) {
3603                 redraw_view(view);
3604                 report("");
3605         }
3608 static void
3609 open_external_viewer(const char *argv[], const char *dir)
3611         def_prog_mode();           /* save current tty modes */
3612         endwin();                  /* restore original tty modes */
3613         io_run_fg(argv, dir);
3614         fprintf(stderr, "Press Enter to continue");
3615         getc(opt_tty);
3616         reset_prog_mode();
3617         redraw_display(TRUE);
3620 static void
3621 open_mergetool(const char *file)
3623         const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3625         open_external_viewer(mergetool_argv, opt_cdup);
3628 static void
3629 open_editor(const char *file)
3631         const char *editor_argv[] = { "vi", file, NULL };
3632         const char *editor;
3634         editor = getenv("GIT_EDITOR");
3635         if (!editor && *opt_editor)
3636                 editor = opt_editor;
3637         if (!editor)
3638                 editor = getenv("VISUAL");
3639         if (!editor)
3640                 editor = getenv("EDITOR");
3641         if (!editor)
3642                 editor = "vi";
3644         editor_argv[0] = editor;
3645         open_external_viewer(editor_argv, opt_cdup);
3648 static void
3649 open_run_request(enum request request)
3651         struct run_request *req = get_run_request(request);
3652         const char **argv = NULL;
3654         if (!req) {
3655                 report("Unknown run request");
3656                 return;
3657         }
3659         if (format_argv(&argv, req->argv, TRUE))
3660                 open_external_viewer(argv, NULL);
3661         if (argv)
3662                 argv_free(argv);
3663         free(argv);
3666 /*
3667  * User request switch noodle
3668  */
3670 static int
3671 view_driver(struct view *view, enum request request)
3673         int i;
3675         if (request == REQ_NONE)
3676                 return TRUE;
3678         if (request > REQ_NONE) {
3679                 open_run_request(request);
3680                 view_request(view, REQ_REFRESH);
3681                 return TRUE;
3682         }
3684         request = view_request(view, request);
3685         if (request == REQ_NONE)
3686                 return TRUE;
3688         switch (request) {
3689         case REQ_MOVE_UP:
3690         case REQ_MOVE_DOWN:
3691         case REQ_MOVE_PAGE_UP:
3692         case REQ_MOVE_PAGE_DOWN:
3693         case REQ_MOVE_FIRST_LINE:
3694         case REQ_MOVE_LAST_LINE:
3695                 move_view(view, request);
3696                 break;
3698         case REQ_SCROLL_LEFT:
3699         case REQ_SCROLL_RIGHT:
3700         case REQ_SCROLL_LINE_DOWN:
3701         case REQ_SCROLL_LINE_UP:
3702         case REQ_SCROLL_PAGE_DOWN:
3703         case REQ_SCROLL_PAGE_UP:
3704                 scroll_view(view, request);
3705                 break;
3707         case REQ_VIEW_BLAME:
3708                 if (!opt_file[0]) {
3709                         report("No file chosen, press %s to open tree view",
3710                                get_key(view->keymap, REQ_VIEW_TREE));
3711                         break;
3712                 }
3713                 open_view(view, request, OPEN_DEFAULT);
3714                 break;
3716         case REQ_VIEW_BLOB:
3717                 if (!ref_blob[0]) {
3718                         report("No file chosen, press %s to open tree view",
3719                                get_key(view->keymap, REQ_VIEW_TREE));
3720                         break;
3721                 }
3722                 open_view(view, request, OPEN_DEFAULT);
3723                 break;
3725         case REQ_VIEW_PAGER:
3726                 if (view == NULL) {
3727                         if (!io_open(&VIEW(REQ_VIEW_PAGER)->io, ""))
3728                                 die("Failed to open stdin");
3729                         open_view(view, request, OPEN_PREPARED);
3730                         break;
3731                 }
3733                 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3734                         report("No pager content, press %s to run command from prompt",
3735                                get_key(view->keymap, REQ_PROMPT));
3736                         break;
3737                 }
3738                 open_view(view, request, OPEN_DEFAULT);
3739                 break;
3741         case REQ_VIEW_STAGE:
3742                 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3743                         report("No stage content, press %s to open the status view and choose file",
3744                                get_key(view->keymap, REQ_VIEW_STATUS));
3745                         break;
3746                 }
3747                 open_view(view, request, OPEN_DEFAULT);
3748                 break;
3750         case REQ_VIEW_STATUS:
3751                 if (opt_is_inside_work_tree == FALSE) {
3752                         report("The status view requires a working tree");
3753                         break;
3754                 }
3755                 open_view(view, request, OPEN_DEFAULT);
3756                 break;
3758         case REQ_VIEW_MAIN:
3759         case REQ_VIEW_DIFF:
3760         case REQ_VIEW_LOG:
3761         case REQ_VIEW_TREE:
3762         case REQ_VIEW_HELP:
3763         case REQ_VIEW_BRANCH:
3764                 open_view(view, request, OPEN_DEFAULT);
3765                 break;
3767         case REQ_NEXT:
3768         case REQ_PREVIOUS:
3769                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3771                 if (view->parent) {
3772                         int line;
3774                         view = view->parent;
3775                         line = view->lineno;
3776                         move_view(view, request);
3777                         if (view_is_displayed(view))
3778                                 update_view_title(view);
3779                         if (line != view->lineno)
3780                                 view_request(view, REQ_ENTER);
3781                 } else {
3782                         move_view(view, request);
3783                 }
3784                 break;
3786         case REQ_VIEW_NEXT:
3787         {
3788                 int nviews = displayed_views();
3789                 int next_view = (current_view + 1) % nviews;
3791                 if (next_view == current_view) {
3792                         report("Only one view is displayed");
3793                         break;
3794                 }
3796                 current_view = next_view;
3797                 /* Blur out the title of the previous view. */
3798                 update_view_title(view);
3799                 report("");
3800                 break;
3801         }
3802         case REQ_REFRESH:
3803                 report("Refreshing is not yet supported for the %s view", view->name);
3804                 break;
3806         case REQ_MAXIMIZE:
3807                 if (displayed_views() == 2)
3808                         maximize_view(view);
3809                 break;
3811         case REQ_OPTIONS:
3812                 open_option_menu();
3813                 break;
3815         case REQ_TOGGLE_LINENO:
3816                 toggle_view_option(&opt_line_number, "line numbers");
3817                 break;
3819         case REQ_TOGGLE_DATE:
3820                 toggle_date();
3821                 break;
3823         case REQ_TOGGLE_AUTHOR:
3824                 toggle_author();
3825                 break;
3827         case REQ_TOGGLE_REV_GRAPH:
3828                 toggle_view_option(&opt_rev_graph, "revision graph display");
3829                 break;
3831         case REQ_TOGGLE_REFS:
3832                 toggle_view_option(&opt_show_refs, "reference display");
3833                 break;
3835         case REQ_TOGGLE_SORT_FIELD:
3836         case REQ_TOGGLE_SORT_ORDER:
3837                 report("Sorting is not yet supported for the %s view", view->name);
3838                 break;
3840         case REQ_SEARCH:
3841         case REQ_SEARCH_BACK:
3842                 search_view(view, request);
3843                 break;
3845         case REQ_FIND_NEXT:
3846         case REQ_FIND_PREV:
3847                 find_next(view, request);
3848                 break;
3850         case REQ_STOP_LOADING:
3851                 foreach_view(view, i) {
3852                         if (view->pipe)
3853                                 report("Stopped loading the %s view", view->name),
3854                         end_update(view, TRUE);
3855                 }
3856                 break;
3858         case REQ_SHOW_VERSION:
3859                 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3860                 return TRUE;
3862         case REQ_SCREEN_REDRAW:
3863                 redraw_display(TRUE);
3864                 break;
3866         case REQ_EDIT:
3867                 report("Nothing to edit");
3868                 break;
3870         case REQ_ENTER:
3871                 report("Nothing to enter");
3872                 break;
3874         case REQ_VIEW_CLOSE:
3875                 /* XXX: Mark closed views by letting view->prev point to the
3876                  * view itself. Parents to closed view should never be
3877                  * followed. */
3878                 if (view->prev && view->prev != view) {
3879                         maximize_view(view->prev);
3880                         view->prev = view;
3881                         break;
3882                 }
3883                 /* Fall-through */
3884         case REQ_QUIT:
3885                 return FALSE;
3887         default:
3888                 report("Unknown key, press %s for help",
3889                        get_key(view->keymap, REQ_VIEW_HELP));
3890                 return TRUE;
3891         }
3893         return TRUE;
3897 /*
3898  * View backend utilities
3899  */
3901 enum sort_field {
3902         ORDERBY_NAME,
3903         ORDERBY_DATE,
3904         ORDERBY_AUTHOR,
3905 };
3907 struct sort_state {
3908         const enum sort_field *fields;
3909         size_t size, current;
3910         bool reverse;
3911 };
3913 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3914 #define get_sort_field(state) ((state).fields[(state).current])
3915 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3917 static void
3918 sort_view(struct view *view, enum request request, struct sort_state *state,
3919           int (*compare)(const void *, const void *))
3921         switch (request) {
3922         case REQ_TOGGLE_SORT_FIELD:
3923                 state->current = (state->current + 1) % state->size;
3924                 break;
3926         case REQ_TOGGLE_SORT_ORDER:
3927                 state->reverse = !state->reverse;
3928                 break;
3929         default:
3930                 die("Not a sort request");
3931         }
3933         qsort(view->line, view->lines, sizeof(*view->line), compare);
3934         redraw_view(view);
3937 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3939 /* Small author cache to reduce memory consumption. It uses binary
3940  * search to lookup or find place to position new entries. No entries
3941  * are ever freed. */
3942 static const char *
3943 get_author(const char *name)
3945         static const char **authors;
3946         static size_t authors_size;
3947         int from = 0, to = authors_size - 1;
3949         while (from <= to) {
3950                 size_t pos = (to + from) / 2;
3951                 int cmp = strcmp(name, authors[pos]);
3953                 if (!cmp)
3954                         return authors[pos];
3956                 if (cmp < 0)
3957                         to = pos - 1;
3958                 else
3959                         from = pos + 1;
3960         }
3962         if (!realloc_authors(&authors, authors_size, 1))
3963                 return NULL;
3964         name = strdup(name);
3965         if (!name)
3966                 return NULL;
3968         memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3969         authors[from] = name;
3970         authors_size++;
3972         return name;
3975 static void
3976 parse_timesec(struct time *time, const char *sec)
3978         time->sec = (time_t) atol(sec);
3981 static void
3982 parse_timezone(struct time *time, const char *zone)
3984         long tz;
3986         tz  = ('0' - zone[1]) * 60 * 60 * 10;
3987         tz += ('0' - zone[2]) * 60 * 60;
3988         tz += ('0' - zone[3]) * 60 * 10;
3989         tz += ('0' - zone[4]) * 60;
3991         if (zone[0] == '-')
3992                 tz = -tz;
3994         time->tz = tz;
3995         time->sec -= tz;
3998 /* Parse author lines where the name may be empty:
3999  *      author  <email@address.tld> 1138474660 +0100
4000  */
4001 static void
4002 parse_author_line(char *ident, const char **author, struct time *time)
4004         char *nameend = strchr(ident, '<');
4005         char *emailend = strchr(ident, '>');
4007         if (nameend && emailend)
4008                 *nameend = *emailend = 0;
4009         ident = chomp_string(ident);
4010         if (!*ident) {
4011                 if (nameend)
4012                         ident = chomp_string(nameend + 1);
4013                 if (!*ident)
4014                         ident = "Unknown";
4015         }
4017         *author = get_author(ident);
4019         /* Parse epoch and timezone */
4020         if (emailend && emailend[1] == ' ') {
4021                 char *secs = emailend + 2;
4022                 char *zone = strchr(secs, ' ');
4024                 parse_timesec(time, secs);
4026                 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
4027                         parse_timezone(time, zone + 1);
4028         }
4031 /*
4032  * Pager backend
4033  */
4035 static bool
4036 pager_draw(struct view *view, struct line *line, unsigned int lineno)
4038         if (opt_line_number && draw_lineno(view, lineno))
4039                 return TRUE;
4041         draw_text(view, line->type, line->data, TRUE);
4042         return TRUE;
4045 static bool
4046 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
4048         const char *describe_argv[] = { "git", "describe", commit_id, NULL };
4049         char ref[SIZEOF_STR];
4051         if (!io_run_buf(describe_argv, ref, sizeof(ref)) || !*ref)
4052                 return TRUE;
4054         /* This is the only fatal call, since it can "corrupt" the buffer. */
4055         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
4056                 return FALSE;
4058         return TRUE;
4061 static void
4062 add_pager_refs(struct view *view, struct line *line)
4064         char buf[SIZEOF_STR];
4065         char *commit_id = (char *)line->data + STRING_SIZE("commit ");
4066         struct ref_list *list;
4067         size_t bufpos = 0, i;
4068         const char *sep = "Refs: ";
4069         bool is_tag = FALSE;
4071         assert(line->type == LINE_COMMIT);
4073         list = get_ref_list(commit_id);
4074         if (!list) {
4075                 if (view->type == VIEW_DIFF)
4076                         goto try_add_describe_ref;
4077                 return;
4078         }
4080         for (i = 0; i < list->size; i++) {
4081                 struct ref *ref = list->refs[i];
4082                 const char *fmt = ref->tag    ? "%s[%s]" :
4083                                   ref->remote ? "%s<%s>" : "%s%s";
4085                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
4086                         return;
4087                 sep = ", ";
4088                 if (ref->tag)
4089                         is_tag = TRUE;
4090         }
4092         if (!is_tag && view->type == VIEW_DIFF) {
4093 try_add_describe_ref:
4094                 /* Add <tag>-g<commit_id> "fake" reference. */
4095                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
4096                         return;
4097         }
4099         if (bufpos == 0)
4100                 return;
4102         add_line_text(view, buf, LINE_PP_REFS);
4105 static bool
4106 pager_read(struct view *view, char *data)
4108         struct line *line;
4110         if (!data)
4111                 return TRUE;
4113         line = add_line_text(view, data, get_line_type(data));
4114         if (!line)
4115                 return FALSE;
4117         if (line->type == LINE_COMMIT &&
4118             (view->type == VIEW_DIFF ||
4119              view->type == VIEW_LOG))
4120                 add_pager_refs(view, line);
4122         return TRUE;
4125 static enum request
4126 pager_request(struct view *view, enum request request, struct line *line)
4128         int split = 0;
4130         if (request != REQ_ENTER)
4131                 return request;
4133         if (line->type == LINE_COMMIT &&
4134            (view->type == VIEW_LOG ||
4135             view->type == VIEW_PAGER)) {
4136                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
4137                 split = 1;
4138         }
4140         /* Always scroll the view even if it was split. That way
4141          * you can use Enter to scroll through the log view and
4142          * split open each commit diff. */
4143         scroll_view(view, REQ_SCROLL_LINE_DOWN);
4145         /* FIXME: A minor workaround. Scrolling the view will call report("")
4146          * but if we are scrolling a non-current view this won't properly
4147          * update the view title. */
4148         if (split)
4149                 update_view_title(view);
4151         return REQ_NONE;
4154 static bool
4155 pager_grep(struct view *view, struct line *line)
4157         const char *text[] = { line->data, NULL };
4159         return grep_text(view, text);
4162 static void
4163 pager_select(struct view *view, struct line *line)
4165         if (line->type == LINE_COMMIT) {
4166                 char *text = (char *)line->data + STRING_SIZE("commit ");
4168                 if (view->type != VIEW_PAGER)
4169                         string_copy_rev(view->ref, text);
4170                 string_copy_rev(ref_commit, text);
4171         }
4174 static struct view_ops pager_ops = {
4175         "line",
4176         NULL,
4177         NULL,
4178         pager_read,
4179         pager_draw,
4180         pager_request,
4181         pager_grep,
4182         pager_select,
4183 };
4185 static const char *log_argv[SIZEOF_ARG] = {
4186         "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
4187 };
4189 static enum request
4190 log_request(struct view *view, enum request request, struct line *line)
4192         switch (request) {
4193         case REQ_REFRESH:
4194                 load_refs();
4195                 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
4196                 return REQ_NONE;
4197         default:
4198                 return pager_request(view, request, line);
4199         }
4202 static struct view_ops log_ops = {
4203         "line",
4204         log_argv,
4205         NULL,
4206         pager_read,
4207         pager_draw,
4208         log_request,
4209         pager_grep,
4210         pager_select,
4211 };
4213 static const char *diff_argv[SIZEOF_ARG] = {
4214         "git", "show", "--pretty=fuller", "--no-color", "--root",
4215                 "--patch-with-stat", "--find-copies-harder", "-C",
4216                 "%(diffargs)", "%(commit)", "--", "%(fileargs)", NULL
4217 };
4219 static bool
4220 diff_read(struct view *view, char *data)
4222         if (!data) {
4223                 /* Fall back to retry if no diff will be shown. */
4224                 if (view->lines == 0 && opt_file_args) {
4225                         int pos = argv_size(view->argv)
4226                                 - argv_size(opt_file_args) - 1;
4228                         if (pos > 0 && !strcmp(view->argv[pos], "--")) {
4229                                 for (; view->argv[pos]; pos++) {
4230                                         free((void *) view->argv[pos]);
4231                                         view->argv[pos] = NULL;
4232                                 }
4234                                 if (view->pipe)
4235                                         io_done(view->pipe);
4236                                 if (io_run(&view->io, IO_RD, view->dir, view->argv))
4237                                         return FALSE;
4238                         }
4239                 }
4240                 return TRUE;
4241         }
4243         return pager_read(view, data);
4246 static struct view_ops diff_ops = {
4247         "line",
4248         diff_argv,
4249         NULL,
4250         diff_read,
4251         pager_draw,
4252         pager_request,
4253         pager_grep,
4254         pager_select,
4255 };
4257 /*
4258  * Help backend
4259  */
4261 static bool help_keymap_hidden[ARRAY_SIZE(keymap_table)];
4263 static bool
4264 help_open_keymap_title(struct view *view, enum keymap keymap)
4266         struct line *line;
4268         line = add_line_format(view, LINE_HELP_KEYMAP, "[%c] %s bindings",
4269                                help_keymap_hidden[keymap] ? '+' : '-',
4270                                enum_name(keymap_table[keymap]));
4271         if (line)
4272                 line->other = keymap;
4274         return help_keymap_hidden[keymap];
4277 static void
4278 help_open_keymap(struct view *view, enum keymap keymap)
4280         const char *group = NULL;
4281         char buf[SIZEOF_STR];
4282         size_t bufpos;
4283         bool add_title = TRUE;
4284         int i;
4286         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
4287                 const char *key = NULL;
4289                 if (req_info[i].request == REQ_NONE)
4290                         continue;
4292                 if (!req_info[i].request) {
4293                         group = req_info[i].help;
4294                         continue;
4295                 }
4297                 key = get_keys(keymap, req_info[i].request, TRUE);
4298                 if (!key || !*key)
4299                         continue;
4301                 if (add_title && help_open_keymap_title(view, keymap))
4302                         return;
4303                 add_title = FALSE;
4305                 if (group) {
4306                         add_line_text(view, group, LINE_HELP_GROUP);
4307                         group = NULL;
4308                 }
4310                 add_line_format(view, LINE_DEFAULT, "    %-25s %-20s %s", key,
4311                                 enum_name(req_info[i]), req_info[i].help);
4312         }
4314         group = "External commands:";
4316         for (i = 0; i < run_requests; i++) {
4317                 struct run_request *req = get_run_request(REQ_NONE + i + 1);
4318                 const char *key;
4319                 int argc;
4321                 if (!req || req->keymap != keymap)
4322                         continue;
4324                 key = get_key_name(req->key);
4325                 if (!*key)
4326                         key = "(no key defined)";
4328                 if (add_title && help_open_keymap_title(view, keymap))
4329                         return;
4330                 if (group) {
4331                         add_line_text(view, group, LINE_HELP_GROUP);
4332                         group = NULL;
4333                 }
4335                 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
4336                         if (!string_format_from(buf, &bufpos, "%s%s",
4337                                                 argc ? " " : "", req->argv[argc]))
4338                                 return;
4340                 add_line_format(view, LINE_DEFAULT, "    %-25s `%s`", key, buf);
4341         }
4344 static bool
4345 help_open(struct view *view)
4347         enum keymap keymap;
4349         reset_view(view);
4350         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
4351         add_line_text(view, "", LINE_DEFAULT);
4353         for (keymap = 0; keymap < ARRAY_SIZE(keymap_table); keymap++)
4354                 help_open_keymap(view, keymap);
4356         return TRUE;
4359 static enum request
4360 help_request(struct view *view, enum request request, struct line *line)
4362         switch (request) {
4363         case REQ_ENTER:
4364                 if (line->type == LINE_HELP_KEYMAP) {
4365                         help_keymap_hidden[line->other] =
4366                                 !help_keymap_hidden[line->other];
4367                         view->p_restore = TRUE;
4368                         open_view(view, REQ_VIEW_HELP, OPEN_REFRESH);
4369                 }
4371                 return REQ_NONE;
4372         default:
4373                 return pager_request(view, request, line);
4374         }
4377 static struct view_ops help_ops = {
4378         "line",
4379         NULL,
4380         help_open,
4381         NULL,
4382         pager_draw,
4383         help_request,
4384         pager_grep,
4385         pager_select,
4386 };
4389 /*
4390  * Tree backend
4391  */
4393 struct tree_stack_entry {
4394         struct tree_stack_entry *prev;  /* Entry below this in the stack */
4395         unsigned long lineno;           /* Line number to restore */
4396         char *name;                     /* Position of name in opt_path */
4397 };
4399 /* The top of the path stack. */
4400 static struct tree_stack_entry *tree_stack = NULL;
4401 unsigned long tree_lineno = 0;
4403 static void
4404 pop_tree_stack_entry(void)
4406         struct tree_stack_entry *entry = tree_stack;
4408         tree_lineno = entry->lineno;
4409         entry->name[0] = 0;
4410         tree_stack = entry->prev;
4411         free(entry);
4414 static void
4415 push_tree_stack_entry(const char *name, unsigned long lineno)
4417         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
4418         size_t pathlen = strlen(opt_path);
4420         if (!entry)
4421                 return;
4423         entry->prev = tree_stack;
4424         entry->name = opt_path + pathlen;
4425         tree_stack = entry;
4427         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
4428                 pop_tree_stack_entry();
4429                 return;
4430         }
4432         /* Move the current line to the first tree entry. */
4433         tree_lineno = 1;
4434         entry->lineno = lineno;
4437 /* Parse output from git-ls-tree(1):
4438  *
4439  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4440  */
4442 #define SIZEOF_TREE_ATTR \
4443         STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4445 #define SIZEOF_TREE_MODE \
4446         STRING_SIZE("100644 ")
4448 #define TREE_ID_OFFSET \
4449         STRING_SIZE("100644 blob ")
4451 struct tree_entry {
4452         char id[SIZEOF_REV];
4453         mode_t mode;
4454         struct time time;               /* Date from the author ident. */
4455         const char *author;             /* Author of the commit. */
4456         char name[1];
4457 };
4459 static const char *
4460 tree_path(const struct line *line)
4462         return ((struct tree_entry *) line->data)->name;
4465 static int
4466 tree_compare_entry(const struct line *line1, const struct line *line2)
4468         if (line1->type != line2->type)
4469                 return line1->type == LINE_TREE_DIR ? -1 : 1;
4470         return strcmp(tree_path(line1), tree_path(line2));
4473 static const enum sort_field tree_sort_fields[] = {
4474         ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4475 };
4476 static struct sort_state tree_sort_state = SORT_STATE(tree_sort_fields);
4478 static int
4479 tree_compare(const void *l1, const void *l2)
4481         const struct line *line1 = (const struct line *) l1;
4482         const struct line *line2 = (const struct line *) l2;
4483         const struct tree_entry *entry1 = ((const struct line *) l1)->data;
4484         const struct tree_entry *entry2 = ((const struct line *) l2)->data;
4486         if (line1->type == LINE_TREE_HEAD)
4487                 return -1;
4488         if (line2->type == LINE_TREE_HEAD)
4489                 return 1;
4491         switch (get_sort_field(tree_sort_state)) {
4492         case ORDERBY_DATE:
4493                 return sort_order(tree_sort_state, timecmp(&entry1->time, &entry2->time));
4495         case ORDERBY_AUTHOR:
4496                 return sort_order(tree_sort_state, strcmp(entry1->author, entry2->author));
4498         case ORDERBY_NAME:
4499         default:
4500                 return sort_order(tree_sort_state, tree_compare_entry(line1, line2));
4501         }
4505 static struct line *
4506 tree_entry(struct view *view, enum line_type type, const char *path,
4507            const char *mode, const char *id)
4509         struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
4510         struct line *line = entry ? add_line_data(view, entry, type) : NULL;
4512         if (!entry || !line) {
4513                 free(entry);
4514                 return NULL;
4515         }
4517         strncpy(entry->name, path, strlen(path));
4518         if (mode)
4519                 entry->mode = strtoul(mode, NULL, 8);
4520         if (id)
4521                 string_copy_rev(entry->id, id);
4523         return line;
4526 static bool
4527 tree_read_date(struct view *view, char *text, bool *read_date)
4529         static const char *author_name;
4530         static struct time author_time;
4532         if (!text && *read_date) {
4533                 *read_date = FALSE;
4534                 return TRUE;
4536         } else if (!text) {
4537                 char *path = *opt_path ? opt_path : ".";
4538                 /* Find next entry to process */
4539                 const char *log_file[] = {
4540                         "git", "log", "--no-color", "--pretty=raw",
4541                                 "--cc", "--raw", view->id, "--", path, NULL
4542                 };
4544                 if (!view->lines) {
4545                         tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
4546                         report("Tree is empty");
4547                         return TRUE;
4548                 }
4550                 if (!start_update(view, log_file, opt_cdup)) {
4551                         report("Failed to load tree data");
4552                         return TRUE;
4553                 }
4555                 *read_date = TRUE;
4556                 return FALSE;
4558         } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
4559                 parse_author_line(text + STRING_SIZE("author "),
4560                                   &author_name, &author_time);
4562         } else if (*text == ':') {
4563                 char *pos;
4564                 size_t annotated = 1;
4565                 size_t i;
4567                 pos = strchr(text, '\t');
4568                 if (!pos)
4569                         return TRUE;
4570                 text = pos + 1;
4571                 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
4572                         text += strlen(opt_path);
4573                 pos = strchr(text, '/');
4574                 if (pos)
4575                         *pos = 0;
4577                 for (i = 1; i < view->lines; i++) {
4578                         struct line *line = &view->line[i];
4579                         struct tree_entry *entry = line->data;
4581                         annotated += !!entry->author;
4582                         if (entry->author || strcmp(entry->name, text))
4583                                 continue;
4585                         entry->author = author_name;
4586                         entry->time = author_time;
4587                         line->dirty = 1;
4588                         break;
4589                 }
4591                 if (annotated == view->lines)
4592                         io_kill(view->pipe);
4593         }
4594         return TRUE;
4597 static bool
4598 tree_read(struct view *view, char *text)
4600         static bool read_date = FALSE;
4601         struct tree_entry *data;
4602         struct line *entry, *line;
4603         enum line_type type;
4604         size_t textlen = text ? strlen(text) : 0;
4605         char *path = text + SIZEOF_TREE_ATTR;
4607         if (read_date || !text)
4608                 return tree_read_date(view, text, &read_date);
4610         if (textlen <= SIZEOF_TREE_ATTR)
4611                 return FALSE;
4612         if (view->lines == 0 &&
4613             !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
4614                 return FALSE;
4616         /* Strip the path part ... */
4617         if (*opt_path) {
4618                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
4619                 size_t striplen = strlen(opt_path);
4621                 if (pathlen > striplen)
4622                         memmove(path, path + striplen,
4623                                 pathlen - striplen + 1);
4625                 /* Insert "link" to parent directory. */
4626                 if (view->lines == 1 &&
4627                     !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
4628                         return FALSE;
4629         }
4631         type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
4632         entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
4633         if (!entry)
4634                 return FALSE;
4635         data = entry->data;
4637         /* Skip "Directory ..." and ".." line. */
4638         for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
4639                 if (tree_compare_entry(line, entry) <= 0)
4640                         continue;
4642                 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4644                 line->data = data;
4645                 line->type = type;
4646                 for (; line <= entry; line++)
4647                         line->dirty = line->cleareol = 1;
4648                 return TRUE;
4649         }
4651         if (tree_lineno > view->lineno) {
4652                 view->lineno = tree_lineno;
4653                 tree_lineno = 0;
4654         }
4656         return TRUE;
4659 static bool
4660 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4662         struct tree_entry *entry = line->data;
4664         if (line->type == LINE_TREE_HEAD) {
4665                 if (draw_text(view, line->type, "Directory path /", TRUE))
4666                         return TRUE;
4667         } else {
4668                 if (draw_mode(view, entry->mode))
4669                         return TRUE;
4671                 if (opt_author && draw_author(view, entry->author))
4672                         return TRUE;
4674                 if (opt_date && draw_date(view, &entry->time))
4675                         return TRUE;
4676         }
4677         if (draw_text(view, line->type, entry->name, TRUE))
4678                 return TRUE;
4679         return TRUE;
4682 static void
4683 open_blob_editor(const char *id)
4685         const char *blob_argv[] = { "git", "cat-file", "blob", id, NULL };
4686         char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4687         int fd = mkstemp(file);
4689         if (fd == -1)
4690                 report("Failed to create temporary file");
4691         else if (!io_run_append(blob_argv, fd))
4692                 report("Failed to save blob data to file");
4693         else
4694                 open_editor(file);
4695         if (fd != -1)
4696                 unlink(file);
4699 static enum request
4700 tree_request(struct view *view, enum request request, struct line *line)
4702         enum open_flags flags;
4703         struct tree_entry *entry = line->data;
4705         switch (request) {
4706         case REQ_VIEW_BLAME:
4707                 if (line->type != LINE_TREE_FILE) {
4708                         report("Blame only supported for files");
4709                         return REQ_NONE;
4710                 }
4712                 string_copy(opt_ref, view->vid);
4713                 return request;
4715         case REQ_EDIT:
4716                 if (line->type != LINE_TREE_FILE) {
4717                         report("Edit only supported for files");
4718                 } else if (!is_head_commit(view->vid)) {
4719                         open_blob_editor(entry->id);
4720                 } else {
4721                         open_editor(opt_file);
4722                 }
4723                 return REQ_NONE;
4725         case REQ_TOGGLE_SORT_FIELD:
4726         case REQ_TOGGLE_SORT_ORDER:
4727                 sort_view(view, request, &tree_sort_state, tree_compare);
4728                 return REQ_NONE;
4730         case REQ_PARENT:
4731                 if (!*opt_path) {
4732                         /* quit view if at top of tree */
4733                         return REQ_VIEW_CLOSE;
4734                 }
4735                 /* fake 'cd  ..' */
4736                 line = &view->line[1];
4737                 break;
4739         case REQ_ENTER:
4740                 break;
4742         default:
4743                 return request;
4744         }
4746         /* Cleanup the stack if the tree view is at a different tree. */
4747         while (!*opt_path && tree_stack)
4748                 pop_tree_stack_entry();
4750         switch (line->type) {
4751         case LINE_TREE_DIR:
4752                 /* Depending on whether it is a subdirectory or parent link
4753                  * mangle the path buffer. */
4754                 if (line == &view->line[1] && *opt_path) {
4755                         pop_tree_stack_entry();
4757                 } else {
4758                         const char *basename = tree_path(line);
4760                         push_tree_stack_entry(basename, view->lineno);
4761                 }
4763                 /* Trees and subtrees share the same ID, so they are not not
4764                  * unique like blobs. */
4765                 flags = OPEN_RELOAD;
4766                 request = REQ_VIEW_TREE;
4767                 break;
4769         case LINE_TREE_FILE:
4770                 flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
4771                 request = REQ_VIEW_BLOB;
4772                 break;
4774         default:
4775                 return REQ_NONE;
4776         }
4778         open_view(view, request, flags);
4779         if (request == REQ_VIEW_TREE)
4780                 view->lineno = tree_lineno;
4782         return REQ_NONE;
4785 static bool
4786 tree_grep(struct view *view, struct line *line)
4788         struct tree_entry *entry = line->data;
4789         const char *text[] = {
4790                 entry->name,
4791                 opt_author ? entry->author : "",
4792                 mkdate(&entry->time, opt_date),
4793                 NULL
4794         };
4796         return grep_text(view, text);
4799 static void
4800 tree_select(struct view *view, struct line *line)
4802         struct tree_entry *entry = line->data;
4804         if (line->type == LINE_TREE_FILE) {
4805                 string_copy_rev(ref_blob, entry->id);
4806                 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4808         } else if (line->type != LINE_TREE_DIR) {
4809                 return;
4810         }
4812         string_copy_rev(view->ref, entry->id);
4815 static bool
4816 tree_prepare(struct view *view)
4818         if (view->lines == 0 && opt_prefix[0]) {
4819                 char *pos = opt_prefix;
4821                 while (pos && *pos) {
4822                         char *end = strchr(pos, '/');
4824                         if (end)
4825                                 *end = 0;
4826                         push_tree_stack_entry(pos, 0);
4827                         pos = end;
4828                         if (end) {
4829                                 *end = '/';
4830                                 pos++;
4831                         }
4832                 }
4834         } else if (strcmp(view->vid, view->id)) {
4835                 opt_path[0] = 0;
4836         }
4838         return prepare_io(view, opt_cdup, view->ops->argv, TRUE);
4841 static const char *tree_argv[SIZEOF_ARG] = {
4842         "git", "ls-tree", "%(commit)", "%(directory)", NULL
4843 };
4845 static struct view_ops tree_ops = {
4846         "file",
4847         tree_argv,
4848         NULL,
4849         tree_read,
4850         tree_draw,
4851         tree_request,
4852         tree_grep,
4853         tree_select,
4854         tree_prepare,
4855 };
4857 static bool
4858 blob_read(struct view *view, char *line)
4860         if (!line)
4861                 return TRUE;
4862         return add_line_text(view, line, LINE_DEFAULT) != NULL;
4865 static enum request
4866 blob_request(struct view *view, enum request request, struct line *line)
4868         switch (request) {
4869         case REQ_EDIT:
4870                 open_blob_editor(view->vid);
4871                 return REQ_NONE;
4872         default:
4873                 return pager_request(view, request, line);
4874         }
4877 static const char *blob_argv[SIZEOF_ARG] = {
4878         "git", "cat-file", "blob", "%(blob)", NULL
4879 };
4881 static struct view_ops blob_ops = {
4882         "line",
4883         blob_argv,
4884         NULL,
4885         blob_read,
4886         pager_draw,
4887         blob_request,
4888         pager_grep,
4889         pager_select,
4890 };
4892 /*
4893  * Blame backend
4894  *
4895  * Loading the blame view is a two phase job:
4896  *
4897  *  1. File content is read either using opt_file from the
4898  *     filesystem or using git-cat-file.
4899  *  2. Then blame information is incrementally added by
4900  *     reading output from git-blame.
4901  */
4903 struct blame_commit {
4904         char id[SIZEOF_REV];            /* SHA1 ID. */
4905         char title[128];                /* First line of the commit message. */
4906         const char *author;             /* Author of the commit. */
4907         struct time time;               /* Date from the author ident. */
4908         char filename[128];             /* Name of file. */
4909         char parent_id[SIZEOF_REV];     /* Parent/previous SHA1 ID. */
4910         char parent_filename[128];      /* Parent/previous name of file. */
4911 };
4913 struct blame {
4914         struct blame_commit *commit;
4915         unsigned long lineno;
4916         char text[1];
4917 };
4919 static bool
4920 blame_open(struct view *view)
4922         char path[SIZEOF_STR];
4923         size_t i;
4925         if (!view->prev && *opt_prefix) {
4926                 string_copy(path, opt_file);
4927                 if (!string_format(opt_file, "%s%s", opt_prefix, path))
4928                         return FALSE;
4929         }
4931         if (*opt_ref || !io_open(&view->io, "%s%s", opt_cdup, opt_file)) {
4932                 const char *blame_cat_file_argv[] = {
4933                         "git", "cat-file", "blob", path, NULL
4934                 };
4936                 if (!string_format(path, "%s:%s", opt_ref, opt_file) ||
4937                     !start_update(view, blame_cat_file_argv, opt_cdup))
4938                         return FALSE;
4939         }
4941         /* First pass: remove multiple references to the same commit. */
4942         for (i = 0; i < view->lines; i++) {
4943                 struct blame *blame = view->line[i].data;
4945                 if (blame->commit && blame->commit->id[0])
4946                         blame->commit->id[0] = 0;
4947                 else
4948                         blame->commit = NULL;
4949         }
4951         /* Second pass: free existing references. */
4952         for (i = 0; i < view->lines; i++) {
4953                 struct blame *blame = view->line[i].data;
4955                 if (blame->commit)
4956                         free(blame->commit);
4957         }
4959         setup_update(view, opt_file);
4960         string_format(view->ref, "%s ...", opt_file);
4962         return TRUE;
4965 static struct blame_commit *
4966 get_blame_commit(struct view *view, const char *id)
4968         size_t i;
4970         for (i = 0; i < view->lines; i++) {
4971                 struct blame *blame = view->line[i].data;
4973                 if (!blame->commit)
4974                         continue;
4976                 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4977                         return blame->commit;
4978         }
4980         {
4981                 struct blame_commit *commit = calloc(1, sizeof(*commit));
4983                 if (commit)
4984                         string_ncopy(commit->id, id, SIZEOF_REV);
4985                 return commit;
4986         }
4989 static bool
4990 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4992         const char *pos = *posref;
4994         *posref = NULL;
4995         pos = strchr(pos + 1, ' ');
4996         if (!pos || !isdigit(pos[1]))
4997                 return FALSE;
4998         *number = atoi(pos + 1);
4999         if (*number < min || *number > max)
5000                 return FALSE;
5002         *posref = pos;
5003         return TRUE;
5006 static struct blame_commit *
5007 parse_blame_commit(struct view *view, const char *text, int *blamed)
5009         struct blame_commit *commit;
5010         struct blame *blame;
5011         const char *pos = text + SIZEOF_REV - 2;
5012         size_t orig_lineno = 0;
5013         size_t lineno;
5014         size_t group;
5016         if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
5017                 return NULL;
5019         if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
5020             !parse_number(&pos, &lineno, 1, view->lines) ||
5021             !parse_number(&pos, &group, 1, view->lines - lineno + 1))
5022                 return NULL;
5024         commit = get_blame_commit(view, text);
5025         if (!commit)
5026                 return NULL;
5028         *blamed += group;
5029         while (group--) {
5030                 struct line *line = &view->line[lineno + group - 1];
5032                 blame = line->data;
5033                 blame->commit = commit;
5034                 blame->lineno = orig_lineno + group - 1;
5035                 line->dirty = 1;
5036         }
5038         return commit;
5041 static bool
5042 blame_read_file(struct view *view, const char *line, bool *read_file)
5044         if (!line) {
5045                 const char *blame_argv[] = {
5046                         "git", "blame", "--incremental",
5047                                 *opt_ref ? opt_ref : "--incremental", "--", opt_file, NULL
5048                 };
5050                 if (view->lines == 0 && !view->prev)
5051                         die("No blame exist for %s", view->vid);
5053                 if (view->lines == 0 || !start_update(view, blame_argv, opt_cdup)) {
5054                         report("Failed to load blame data");
5055                         return TRUE;
5056                 }
5058                 *read_file = FALSE;
5059                 return FALSE;
5061         } else {
5062                 size_t linelen = strlen(line);
5063                 struct blame *blame = malloc(sizeof(*blame) + linelen);
5065                 if (!blame)
5066                         return FALSE;
5068                 blame->commit = NULL;
5069                 strncpy(blame->text, line, linelen);
5070                 blame->text[linelen] = 0;
5071                 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
5072         }
5075 static bool
5076 match_blame_header(const char *name, char **line)
5078         size_t namelen = strlen(name);
5079         bool matched = !strncmp(name, *line, namelen);
5081         if (matched)
5082                 *line += namelen;
5084         return matched;
5087 static bool
5088 blame_read(struct view *view, char *line)
5090         static struct blame_commit *commit = NULL;
5091         static int blamed = 0;
5092         static bool read_file = TRUE;
5094         if (read_file)
5095                 return blame_read_file(view, line, &read_file);
5097         if (!line) {
5098                 /* Reset all! */
5099                 commit = NULL;
5100                 blamed = 0;
5101                 read_file = TRUE;
5102                 string_format(view->ref, "%s", view->vid);
5103                 if (view_is_displayed(view)) {
5104                         update_view_title(view);
5105                         redraw_view_from(view, 0);
5106                 }
5107                 return TRUE;
5108         }
5110         if (!commit) {
5111                 commit = parse_blame_commit(view, line, &blamed);
5112                 string_format(view->ref, "%s %2d%%", view->vid,
5113                               view->lines ? blamed * 100 / view->lines : 0);
5115         } else if (match_blame_header("author ", &line)) {
5116                 commit->author = get_author(line);
5118         } else if (match_blame_header("author-time ", &line)) {
5119                 parse_timesec(&commit->time, line);
5121         } else if (match_blame_header("author-tz ", &line)) {
5122                 parse_timezone(&commit->time, line);
5124         } else if (match_blame_header("summary ", &line)) {
5125                 string_ncopy(commit->title, line, strlen(line));
5127         } else if (match_blame_header("previous ", &line)) {
5128                 if (strlen(line) <= SIZEOF_REV)
5129                         return FALSE;
5130                 string_copy_rev(commit->parent_id, line);
5131                 line += SIZEOF_REV;
5132                 string_ncopy(commit->parent_filename, line, strlen(line));
5134         } else if (match_blame_header("filename ", &line)) {
5135                 string_ncopy(commit->filename, line, strlen(line));
5136                 commit = NULL;
5137         }
5139         return TRUE;
5142 static bool
5143 blame_draw(struct view *view, struct line *line, unsigned int lineno)
5145         struct blame *blame = line->data;
5146         struct time *time = NULL;
5147         const char *id = NULL, *author = NULL;
5149         if (blame->commit && *blame->commit->filename) {
5150                 id = blame->commit->id;
5151                 author = blame->commit->author;
5152                 time = &blame->commit->time;
5153         }
5155         if (opt_date && draw_date(view, time))
5156                 return TRUE;
5158         if (opt_author && draw_author(view, author))
5159                 return TRUE;
5161         if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
5162                 return TRUE;
5164         if (draw_lineno(view, lineno))
5165                 return TRUE;
5167         draw_text(view, LINE_DEFAULT, blame->text, TRUE);
5168         return TRUE;
5171 static bool
5172 check_blame_commit(struct blame *blame, bool check_null_id)
5174         if (!blame->commit)
5175                 report("Commit data not loaded yet");
5176         else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
5177                 report("No commit exist for the selected line");
5178         else
5179                 return TRUE;
5180         return FALSE;
5183 static void
5184 setup_blame_parent_line(struct view *view, struct blame *blame)
5186         char from[SIZEOF_REF + SIZEOF_STR];
5187         char to[SIZEOF_REF + SIZEOF_STR];
5188         const char *diff_tree_argv[] = {
5189                 "git", "diff", "--no-textconv", "--no-extdiff", "--no-color",
5190                         "-U0", from, to, "--", NULL
5191         };
5192         struct io io;
5193         int parent_lineno = -1;
5194         int blamed_lineno = -1;
5195         char *line;
5197         if (!string_format(from, "%s:%s", opt_ref, opt_file) ||
5198             !string_format(to, "%s:%s", blame->commit->id, blame->commit->filename) ||
5199             !io_run(&io, IO_RD, NULL, diff_tree_argv))
5200                 return;
5202         while ((line = io_get(&io, '\n', TRUE))) {
5203                 if (*line == '@') {
5204                         char *pos = strchr(line, '+');
5206                         parent_lineno = atoi(line + 4);
5207                         if (pos)
5208                                 blamed_lineno = atoi(pos + 1);
5210                 } else if (*line == '+' && parent_lineno != -1) {
5211                         if (blame->lineno == blamed_lineno - 1 &&
5212                             !strcmp(blame->text, line + 1)) {
5213                                 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
5214                                 break;
5215                         }
5216                         blamed_lineno++;
5217                 }
5218         }
5220         io_done(&io);
5223 static enum request
5224 blame_request(struct view *view, enum request request, struct line *line)
5226         enum open_flags flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
5227         struct blame *blame = line->data;
5229         switch (request) {
5230         case REQ_VIEW_BLAME:
5231                 if (check_blame_commit(blame, TRUE)) {
5232                         string_copy(opt_ref, blame->commit->id);
5233                         string_copy(opt_file, blame->commit->filename);
5234                         if (blame->lineno)
5235                                 view->lineno = blame->lineno;
5236                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5237                 }
5238                 break;
5240         case REQ_PARENT:
5241                 if (!check_blame_commit(blame, TRUE))
5242                         break;
5243                 if (!*blame->commit->parent_id) {
5244                         report("The selected commit has no parents");
5245                 } else {
5246                         string_copy_rev(opt_ref, blame->commit->parent_id);
5247                         string_copy(opt_file, blame->commit->parent_filename);
5248                         setup_blame_parent_line(view, blame);
5249                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5250                 }
5251                 break;
5253         case REQ_ENTER:
5254                 if (!check_blame_commit(blame, FALSE))
5255                         break;
5257                 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
5258                     !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
5259                         break;
5261                 if (!strcmp(blame->commit->id, NULL_ID)) {
5262                         struct view *diff = VIEW(REQ_VIEW_DIFF);
5263                         const char *diff_index_argv[] = {
5264                                 "git", "diff-index", "--root", "--patch-with-stat",
5265                                         "-C", "-M", "HEAD", "--", view->vid, NULL
5266                         };
5268                         if (!*blame->commit->parent_id) {
5269                                 diff_index_argv[1] = "diff";
5270                                 diff_index_argv[2] = "--no-color";
5271                                 diff_index_argv[6] = "--";
5272                                 diff_index_argv[7] = "/dev/null";
5273                         }
5275                         if (!prepare_update(diff, diff_index_argv, NULL)) {
5276                                 report("Failed to allocate diff command");
5277                                 break;
5278                         }
5279                         flags |= OPEN_PREPARED;
5280                 }
5282                 open_view(view, REQ_VIEW_DIFF, flags);
5283                 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
5284                         string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
5285                 break;
5287         default:
5288                 return request;
5289         }
5291         return REQ_NONE;
5294 static bool
5295 blame_grep(struct view *view, struct line *line)
5297         struct blame *blame = line->data;
5298         struct blame_commit *commit = blame->commit;
5299         const char *text[] = {
5300                 blame->text,
5301                 commit ? commit->title : "",
5302                 commit ? commit->id : "",
5303                 commit && opt_author ? commit->author : "",
5304                 commit ? mkdate(&commit->time, opt_date) : "",
5305                 NULL
5306         };
5308         return grep_text(view, text);
5311 static void
5312 blame_select(struct view *view, struct line *line)
5314         struct blame *blame = line->data;
5315         struct blame_commit *commit = blame->commit;
5317         if (!commit)
5318                 return;
5320         if (!strcmp(commit->id, NULL_ID))
5321                 string_ncopy(ref_commit, "HEAD", 4);
5322         else
5323                 string_copy_rev(ref_commit, commit->id);
5326 static struct view_ops blame_ops = {
5327         "line",
5328         NULL,
5329         blame_open,
5330         blame_read,
5331         blame_draw,
5332         blame_request,
5333         blame_grep,
5334         blame_select,
5335 };
5337 /*
5338  * Branch backend
5339  */
5341 struct branch {
5342         const char *author;             /* Author of the last commit. */
5343         struct time time;               /* Date of the last activity. */
5344         const struct ref *ref;          /* Name and commit ID information. */
5345 };
5347 static const struct ref branch_all;
5349 static const enum sort_field branch_sort_fields[] = {
5350         ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
5351 };
5352 static struct sort_state branch_sort_state = SORT_STATE(branch_sort_fields);
5354 static int
5355 branch_compare(const void *l1, const void *l2)
5357         const struct branch *branch1 = ((const struct line *) l1)->data;
5358         const struct branch *branch2 = ((const struct line *) l2)->data;
5360         switch (get_sort_field(branch_sort_state)) {
5361         case ORDERBY_DATE:
5362                 return sort_order(branch_sort_state, timecmp(&branch1->time, &branch2->time));
5364         case ORDERBY_AUTHOR:
5365                 return sort_order(branch_sort_state, strcmp(branch1->author, branch2->author));
5367         case ORDERBY_NAME:
5368         default:
5369                 return sort_order(branch_sort_state, strcmp(branch1->ref->name, branch2->ref->name));
5370         }
5373 static bool
5374 branch_draw(struct view *view, struct line *line, unsigned int lineno)
5376         struct branch *branch = line->data;
5377         enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
5379         if (opt_date && draw_date(view, &branch->time))
5380                 return TRUE;
5382         if (opt_author && draw_author(view, branch->author))
5383                 return TRUE;
5385         draw_text(view, type, branch->ref == &branch_all ? "All branches" : branch->ref->name, TRUE);
5386         return TRUE;
5389 static enum request
5390 branch_request(struct view *view, enum request request, struct line *line)
5392         struct branch *branch = line->data;
5394         switch (request) {
5395         case REQ_REFRESH:
5396                 load_refs();
5397                 open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
5398                 return REQ_NONE;
5400         case REQ_TOGGLE_SORT_FIELD:
5401         case REQ_TOGGLE_SORT_ORDER:
5402                 sort_view(view, request, &branch_sort_state, branch_compare);
5403                 return REQ_NONE;
5405         case REQ_ENTER:
5406         {
5407                 const struct ref *ref = branch->ref;
5408                 const char *all_branches_argv[] = {
5409                         "git", "log", "--no-color", "--pretty=raw", "--parents",
5410                               "--topo-order",
5411                               ref == &branch_all ? "--all" : ref->name, NULL
5412                 };
5413                 struct view *main_view = VIEW(REQ_VIEW_MAIN);
5415                 if (!prepare_update(main_view, all_branches_argv, NULL))
5416                         report("Failed to load view of all branches");
5417                 else
5418                         open_view(view, REQ_VIEW_MAIN, OPEN_PREPARED | OPEN_SPLIT);
5419                 return REQ_NONE;
5420         }
5421         default:
5422                 return request;
5423         }
5426 static bool
5427 branch_read(struct view *view, char *line)
5429         static char id[SIZEOF_REV];
5430         struct branch *reference;
5431         size_t i;
5433         if (!line)
5434                 return TRUE;
5436         switch (get_line_type(line)) {
5437         case LINE_COMMIT:
5438                 string_copy_rev(id, line + STRING_SIZE("commit "));
5439                 return TRUE;
5441         case LINE_AUTHOR:
5442                 for (i = 0, reference = NULL; i < view->lines; i++) {
5443                         struct branch *branch = view->line[i].data;
5445                         if (strcmp(branch->ref->id, id))
5446                                 continue;
5448                         view->line[i].dirty = TRUE;
5449                         if (reference) {
5450                                 branch->author = reference->author;
5451                                 branch->time = reference->time;
5452                                 continue;
5453                         }
5455                         parse_author_line(line + STRING_SIZE("author "),
5456                                           &branch->author, &branch->time);
5457                         reference = branch;
5458                 }
5459                 return TRUE;
5461         default:
5462                 return TRUE;
5463         }
5467 static bool
5468 branch_open_visitor(void *data, const struct ref *ref)
5470         struct view *view = data;
5471         struct branch *branch;
5473         if (ref->tag || ref->ltag || ref->remote)
5474                 return TRUE;
5476         branch = calloc(1, sizeof(*branch));
5477         if (!branch)
5478                 return FALSE;
5480         branch->ref = ref;
5481         return !!add_line_data(view, branch, LINE_DEFAULT);
5484 static bool
5485 branch_open(struct view *view)
5487         const char *branch_log[] = {
5488                 "git", "log", "--no-color", "--pretty=raw",
5489                         "--simplify-by-decoration", "--all", NULL
5490         };
5492         if (!start_update(view, branch_log, NULL)) {
5493                 report("Failed to load branch data");
5494                 return TRUE;
5495         }
5497         setup_update(view, view->id);
5498         branch_open_visitor(view, &branch_all);
5499         foreach_ref(branch_open_visitor, view);
5500         view->p_restore = TRUE;
5502         return TRUE;
5505 static bool
5506 branch_grep(struct view *view, struct line *line)
5508         struct branch *branch = line->data;
5509         const char *text[] = {
5510                 branch->ref->name,
5511                 branch->author,
5512                 NULL
5513         };
5515         return grep_text(view, text);
5518 static void
5519 branch_select(struct view *view, struct line *line)
5521         struct branch *branch = line->data;
5523         string_copy_rev(view->ref, branch->ref->id);
5524         string_copy_rev(ref_commit, branch->ref->id);
5525         string_copy_rev(ref_head, branch->ref->id);
5526         string_copy_rev(ref_branch, branch->ref->name);
5529 static struct view_ops branch_ops = {
5530         "branch",
5531         NULL,
5532         branch_open,
5533         branch_read,
5534         branch_draw,
5535         branch_request,
5536         branch_grep,
5537         branch_select,
5538 };
5540 /*
5541  * Status backend
5542  */
5544 struct status {
5545         char status;
5546         struct {
5547                 mode_t mode;
5548                 char rev[SIZEOF_REV];
5549                 char name[SIZEOF_STR];
5550         } old;
5551         struct {
5552                 mode_t mode;
5553                 char rev[SIZEOF_REV];
5554                 char name[SIZEOF_STR];
5555         } new;
5556 };
5558 static char status_onbranch[SIZEOF_STR];
5559 static struct status stage_status;
5560 static enum line_type stage_line_type;
5561 static size_t stage_chunks;
5562 static int *stage_chunk;
5564 DEFINE_ALLOCATOR(realloc_ints, int, 32)
5566 /* This should work even for the "On branch" line. */
5567 static inline bool
5568 status_has_none(struct view *view, struct line *line)
5570         return line < view->line + view->lines && !line[1].data;
5573 /* Get fields from the diff line:
5574  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5575  */
5576 static inline bool
5577 status_get_diff(struct status *file, const char *buf, size_t bufsize)
5579         const char *old_mode = buf +  1;
5580         const char *new_mode = buf +  8;
5581         const char *old_rev  = buf + 15;
5582         const char *new_rev  = buf + 56;
5583         const char *status   = buf + 97;
5585         if (bufsize < 98 ||
5586             old_mode[-1] != ':' ||
5587             new_mode[-1] != ' ' ||
5588             old_rev[-1]  != ' ' ||
5589             new_rev[-1]  != ' ' ||
5590             status[-1]   != ' ')
5591                 return FALSE;
5593         file->status = *status;
5595         string_copy_rev(file->old.rev, old_rev);
5596         string_copy_rev(file->new.rev, new_rev);
5598         file->old.mode = strtoul(old_mode, NULL, 8);
5599         file->new.mode = strtoul(new_mode, NULL, 8);
5601         file->old.name[0] = file->new.name[0] = 0;
5603         return TRUE;
5606 static bool
5607 status_run(struct view *view, const char *argv[], char status, enum line_type type)
5609         struct status *unmerged = NULL;
5610         char *buf;
5611         struct io io;
5613         if (!io_run(&io, IO_RD, opt_cdup, argv))
5614                 return FALSE;
5616         add_line_data(view, NULL, type);
5618         while ((buf = io_get(&io, 0, TRUE))) {
5619                 struct status *file = unmerged;
5621                 if (!file) {
5622                         file = calloc(1, sizeof(*file));
5623                         if (!file || !add_line_data(view, file, type))
5624                                 goto error_out;
5625                 }
5627                 /* Parse diff info part. */
5628                 if (status) {
5629                         file->status = status;
5630                         if (status == 'A')
5631                                 string_copy(file->old.rev, NULL_ID);
5633                 } else if (!file->status || file == unmerged) {
5634                         if (!status_get_diff(file, buf, strlen(buf)))
5635                                 goto error_out;
5637                         buf = io_get(&io, 0, TRUE);
5638                         if (!buf)
5639                                 break;
5641                         /* Collapse all modified entries that follow an
5642                          * associated unmerged entry. */
5643                         if (unmerged == file) {
5644                                 unmerged->status = 'U';
5645                                 unmerged = NULL;
5646                         } else if (file->status == 'U') {
5647                                 unmerged = file;
5648                         }
5649                 }
5651                 /* Grab the old name for rename/copy. */
5652                 if (!*file->old.name &&
5653                     (file->status == 'R' || file->status == 'C')) {
5654                         string_ncopy(file->old.name, buf, strlen(buf));
5656                         buf = io_get(&io, 0, TRUE);
5657                         if (!buf)
5658                                 break;
5659                 }
5661                 /* git-ls-files just delivers a NUL separated list of
5662                  * file names similar to the second half of the
5663                  * git-diff-* output. */
5664                 string_ncopy(file->new.name, buf, strlen(buf));
5665                 if (!*file->old.name)
5666                         string_copy(file->old.name, file->new.name);
5667                 file = NULL;
5668         }
5670         if (io_error(&io)) {
5671 error_out:
5672                 io_done(&io);
5673                 return FALSE;
5674         }
5676         if (!view->line[view->lines - 1].data)
5677                 add_line_data(view, NULL, LINE_STAT_NONE);
5679         io_done(&io);
5680         return TRUE;
5683 /* Don't show unmerged entries in the staged section. */
5684 static const char *status_diff_index_argv[] = {
5685         "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5686                              "--cached", "-M", "HEAD", NULL
5687 };
5689 static const char *status_diff_files_argv[] = {
5690         "git", "diff-files", "-z", NULL
5691 };
5693 static const char *status_list_other_argv[] = {
5694         "git", "ls-files", "-z", "--others", "--exclude-standard", opt_prefix, NULL
5695 };
5697 static const char *status_list_no_head_argv[] = {
5698         "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5699 };
5701 static const char *update_index_argv[] = {
5702         "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5703 };
5705 /* Restore the previous line number to stay in the context or select a
5706  * line with something that can be updated. */
5707 static void
5708 status_restore(struct view *view)
5710         if (view->p_lineno >= view->lines)
5711                 view->p_lineno = view->lines - 1;
5712         while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
5713                 view->p_lineno++;
5714         while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
5715                 view->p_lineno--;
5717         /* If the above fails, always skip the "On branch" line. */
5718         if (view->p_lineno < view->lines)
5719                 view->lineno = view->p_lineno;
5720         else
5721                 view->lineno = 1;
5723         if (view->lineno < view->offset)
5724                 view->offset = view->lineno;
5725         else if (view->offset + view->height <= view->lineno)
5726                 view->offset = view->lineno - view->height + 1;
5728         view->p_restore = FALSE;
5731 static void
5732 status_update_onbranch(void)
5734         static const char *paths[][2] = {
5735                 { "rebase-apply/rebasing",      "Rebasing" },
5736                 { "rebase-apply/applying",      "Applying mailbox" },
5737                 { "rebase-apply/",              "Rebasing mailbox" },
5738                 { "rebase-merge/interactive",   "Interactive rebase" },
5739                 { "rebase-merge/",              "Rebase merge" },
5740                 { "MERGE_HEAD",                 "Merging" },
5741                 { "BISECT_LOG",                 "Bisecting" },
5742                 { "HEAD",                       "On branch" },
5743         };
5744         char buf[SIZEOF_STR];
5745         struct stat stat;
5746         int i;
5748         if (is_initial_commit()) {
5749                 string_copy(status_onbranch, "Initial commit");
5750                 return;
5751         }
5753         for (i = 0; i < ARRAY_SIZE(paths); i++) {
5754                 char *head = opt_head;
5756                 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5757                     lstat(buf, &stat) < 0)
5758                         continue;
5760                 if (!*opt_head) {
5761                         struct io io;
5763                         if (io_open(&io, "%s/rebase-merge/head-name", opt_git_dir) &&
5764                             io_read_buf(&io, buf, sizeof(buf))) {
5765                                 head = buf;
5766                                 if (!prefixcmp(head, "refs/heads/"))
5767                                         head += STRING_SIZE("refs/heads/");
5768                         }
5769                 }
5771                 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5772                         string_copy(status_onbranch, opt_head);
5773                 return;
5774         }
5776         string_copy(status_onbranch, "Not currently on any branch");
5779 /* First parse staged info using git-diff-index(1), then parse unstaged
5780  * info using git-diff-files(1), and finally untracked files using
5781  * git-ls-files(1). */
5782 static bool
5783 status_open(struct view *view)
5785         reset_view(view);
5787         add_line_data(view, NULL, LINE_STAT_HEAD);
5788         status_update_onbranch();
5790         io_run_bg(update_index_argv);
5792         if (is_initial_commit()) {
5793                 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5794                         return FALSE;
5795         } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5796                 return FALSE;
5797         }
5799         if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5800             !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5801                 return FALSE;
5803         /* Restore the exact position or use the specialized restore
5804          * mode? */
5805         if (!view->p_restore)
5806                 status_restore(view);
5807         return TRUE;
5810 static bool
5811 status_draw(struct view *view, struct line *line, unsigned int lineno)
5813         struct status *status = line->data;
5814         enum line_type type;
5815         const char *text;
5817         if (!status) {
5818                 switch (line->type) {
5819                 case LINE_STAT_STAGED:
5820                         type = LINE_STAT_SECTION;
5821                         text = "Changes to be committed:";
5822                         break;
5824                 case LINE_STAT_UNSTAGED:
5825                         type = LINE_STAT_SECTION;
5826                         text = "Changed but not updated:";
5827                         break;
5829                 case LINE_STAT_UNTRACKED:
5830                         type = LINE_STAT_SECTION;
5831                         text = "Untracked files:";
5832                         break;
5834                 case LINE_STAT_NONE:
5835                         type = LINE_DEFAULT;
5836                         text = "  (no files)";
5837                         break;
5839                 case LINE_STAT_HEAD:
5840                         type = LINE_STAT_HEAD;
5841                         text = status_onbranch;
5842                         break;
5844                 default:
5845                         return FALSE;
5846                 }
5847         } else {
5848                 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5850                 buf[0] = status->status;
5851                 if (draw_text(view, line->type, buf, TRUE))
5852                         return TRUE;
5853                 type = LINE_DEFAULT;
5854                 text = status->new.name;
5855         }
5857         draw_text(view, type, text, TRUE);
5858         return TRUE;
5861 static enum request
5862 status_load_error(struct view *view, struct view *stage, const char *path)
5864         if (displayed_views() == 2 || display[current_view] != view)
5865                 maximize_view(view);
5866         report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5867         return REQ_NONE;
5870 static enum request
5871 status_enter(struct view *view, struct line *line)
5873         struct status *status = line->data;
5874         const char *oldpath = status ? status->old.name : NULL;
5875         /* Diffs for unmerged entries are empty when passing the new
5876          * path, so leave it empty. */
5877         const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5878         const char *info;
5879         enum open_flags split;
5880         struct view *stage = VIEW(REQ_VIEW_STAGE);
5882         if (line->type == LINE_STAT_NONE ||
5883             (!status && line[1].type == LINE_STAT_NONE)) {
5884                 report("No file to diff");
5885                 return REQ_NONE;
5886         }
5888         switch (line->type) {
5889         case LINE_STAT_STAGED:
5890                 if (is_initial_commit()) {
5891                         const char *no_head_diff_argv[] = {
5892                                 "git", "diff", "--no-color", "--patch-with-stat",
5893                                         "--", "/dev/null", newpath, NULL
5894                         };
5896                         if (!prepare_update(stage, no_head_diff_argv, opt_cdup))
5897                                 return status_load_error(view, stage, newpath);
5898                 } else {
5899                         const char *index_show_argv[] = {
5900                                 "git", "diff-index", "--root", "--patch-with-stat",
5901                                         "-C", "-M", "--cached", "HEAD", "--",
5902                                         oldpath, newpath, NULL
5903                         };
5905                         if (!prepare_update(stage, index_show_argv, opt_cdup))
5906                                 return status_load_error(view, stage, newpath);
5907                 }
5909                 if (status)
5910                         info = "Staged changes to %s";
5911                 else
5912                         info = "Staged changes";
5913                 break;
5915         case LINE_STAT_UNSTAGED:
5916         {
5917                 const char *files_show_argv[] = {
5918                         "git", "diff-files", "--root", "--patch-with-stat",
5919                                 "-C", "-M", "--", oldpath, newpath, NULL
5920                 };
5922                 if (!prepare_update(stage, files_show_argv, opt_cdup))
5923                         return status_load_error(view, stage, newpath);
5924                 if (status)
5925                         info = "Unstaged changes to %s";
5926                 else
5927                         info = "Unstaged changes";
5928                 break;
5929         }
5930         case LINE_STAT_UNTRACKED:
5931                 if (!newpath) {
5932                         report("No file to show");
5933                         return REQ_NONE;
5934                 }
5936                 if (!suffixcmp(status->new.name, -1, "/")) {
5937                         report("Cannot display a directory");
5938                         return REQ_NONE;
5939                 }
5941                 if (!prepare_update_file(stage, newpath))
5942                         return status_load_error(view, stage, newpath);
5943                 info = "Untracked file %s";
5944                 break;
5946         case LINE_STAT_HEAD:
5947                 return REQ_NONE;
5949         default:
5950                 die("line type %d not handled in switch", line->type);
5951         }
5953         split = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
5954         open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5955         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5956                 if (status) {
5957                         stage_status = *status;
5958                 } else {
5959                         memset(&stage_status, 0, sizeof(stage_status));
5960                 }
5962                 stage_line_type = line->type;
5963                 stage_chunks = 0;
5964                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5965         }
5967         return REQ_NONE;
5970 static bool
5971 status_exists(struct status *status, enum line_type type)
5973         struct view *view = VIEW(REQ_VIEW_STATUS);
5974         unsigned long lineno;
5976         for (lineno = 0; lineno < view->lines; lineno++) {
5977                 struct line *line = &view->line[lineno];
5978                 struct status *pos = line->data;
5980                 if (line->type != type)
5981                         continue;
5982                 if (!pos && (!status || !status->status) && line[1].data) {
5983                         select_view_line(view, lineno);
5984                         return TRUE;
5985                 }
5986                 if (pos && !strcmp(status->new.name, pos->new.name)) {
5987                         select_view_line(view, lineno);
5988                         return TRUE;
5989                 }
5990         }
5992         return FALSE;
5996 static bool
5997 status_update_prepare(struct io *io, enum line_type type)
5999         const char *staged_argv[] = {
6000                 "git", "update-index", "-z", "--index-info", NULL
6001         };
6002         const char *others_argv[] = {
6003                 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
6004         };
6006         switch (type) {
6007         case LINE_STAT_STAGED:
6008                 return io_run(io, IO_WR, opt_cdup, staged_argv);
6010         case LINE_STAT_UNSTAGED:
6011         case LINE_STAT_UNTRACKED:
6012                 return io_run(io, IO_WR, opt_cdup, others_argv);
6014         default:
6015                 die("line type %d not handled in switch", type);
6016                 return FALSE;
6017         }
6020 static bool
6021 status_update_write(struct io *io, struct status *status, enum line_type type)
6023         char buf[SIZEOF_STR];
6024         size_t bufsize = 0;
6026         switch (type) {
6027         case LINE_STAT_STAGED:
6028                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
6029                                         status->old.mode,
6030                                         status->old.rev,
6031                                         status->old.name, 0))
6032                         return FALSE;
6033                 break;
6035         case LINE_STAT_UNSTAGED:
6036         case LINE_STAT_UNTRACKED:
6037                 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
6038                         return FALSE;
6039                 break;
6041         default:
6042                 die("line type %d not handled in switch", type);
6043         }
6045         return io_write(io, buf, bufsize);
6048 static bool
6049 status_update_file(struct status *status, enum line_type type)
6051         struct io io;
6052         bool result;
6054         if (!status_update_prepare(&io, type))
6055                 return FALSE;
6057         result = status_update_write(&io, status, type);
6058         return io_done(&io) && result;
6061 static bool
6062 status_update_files(struct view *view, struct line *line)
6064         char buf[sizeof(view->ref)];
6065         struct io io;
6066         bool result = TRUE;
6067         struct line *pos = view->line + view->lines;
6068         int files = 0;
6069         int file, done;
6070         int cursor_y = -1, cursor_x = -1;
6072         if (!status_update_prepare(&io, line->type))
6073                 return FALSE;
6075         for (pos = line; pos < view->line + view->lines && pos->data; pos++)
6076                 files++;
6078         string_copy(buf, view->ref);
6079         getsyx(cursor_y, cursor_x);
6080         for (file = 0, done = 5; result && file < files; line++, file++) {
6081                 int almost_done = file * 100 / files;
6083                 if (almost_done > done) {
6084                         done = almost_done;
6085                         string_format(view->ref, "updating file %u of %u (%d%% done)",
6086                                       file, files, done);
6087                         update_view_title(view);
6088                         setsyx(cursor_y, cursor_x);
6089                         doupdate();
6090                 }
6091                 result = status_update_write(&io, line->data, line->type);
6092         }
6093         string_copy(view->ref, buf);
6095         return io_done(&io) && result;
6098 static bool
6099 status_update(struct view *view)
6101         struct line *line = &view->line[view->lineno];
6103         assert(view->lines);
6105         if (!line->data) {
6106                 /* This should work even for the "On branch" line. */
6107                 if (line < view->line + view->lines && !line[1].data) {
6108                         report("Nothing to update");
6109                         return FALSE;
6110                 }
6112                 if (!status_update_files(view, line + 1)) {
6113                         report("Failed to update file status");
6114                         return FALSE;
6115                 }
6117         } else if (!status_update_file(line->data, line->type)) {
6118                 report("Failed to update file status");
6119                 return FALSE;
6120         }
6122         return TRUE;
6125 static bool
6126 status_revert(struct status *status, enum line_type type, bool has_none)
6128         if (!status || type != LINE_STAT_UNSTAGED) {
6129                 if (type == LINE_STAT_STAGED) {
6130                         report("Cannot revert changes to staged files");
6131                 } else if (type == LINE_STAT_UNTRACKED) {
6132                         report("Cannot revert changes to untracked files");
6133                 } else if (has_none) {
6134                         report("Nothing to revert");
6135                 } else {
6136                         report("Cannot revert changes to multiple files");
6137                 }
6139         } else if (prompt_yesno("Are you sure you want to revert changes?")) {
6140                 char mode[10] = "100644";
6141                 const char *reset_argv[] = {
6142                         "git", "update-index", "--cacheinfo", mode,
6143                                 status->old.rev, status->old.name, NULL
6144                 };
6145                 const char *checkout_argv[] = {
6146                         "git", "checkout", "--", status->old.name, NULL
6147                 };
6149                 if (status->status == 'U') {
6150                         string_format(mode, "%5o", status->old.mode);
6152                         if (status->old.mode == 0 && status->new.mode == 0) {
6153                                 reset_argv[2] = "--force-remove";
6154                                 reset_argv[3] = status->old.name;
6155                                 reset_argv[4] = NULL;
6156                         }
6158                         if (!io_run_fg(reset_argv, opt_cdup))
6159                                 return FALSE;
6160                         if (status->old.mode == 0 && status->new.mode == 0)
6161                                 return TRUE;
6162                 }
6164                 return io_run_fg(checkout_argv, opt_cdup);
6165         }
6167         return FALSE;
6170 static enum request
6171 status_request(struct view *view, enum request request, struct line *line)
6173         struct status *status = line->data;
6175         switch (request) {
6176         case REQ_STATUS_UPDATE:
6177                 if (!status_update(view))
6178                         return REQ_NONE;
6179                 break;
6181         case REQ_STATUS_REVERT:
6182                 if (!status_revert(status, line->type, status_has_none(view, line)))
6183                         return REQ_NONE;
6184                 break;
6186         case REQ_STATUS_MERGE:
6187                 if (!status || status->status != 'U') {
6188                         report("Merging only possible for files with unmerged status ('U').");
6189                         return REQ_NONE;
6190                 }
6191                 open_mergetool(status->new.name);
6192                 break;
6194         case REQ_EDIT:
6195                 if (!status)
6196                         return request;
6197                 if (status->status == 'D') {
6198                         report("File has been deleted.");
6199                         return REQ_NONE;
6200                 }
6202                 open_editor(status->new.name);
6203                 break;
6205         case REQ_VIEW_BLAME:
6206                 if (status)
6207                         opt_ref[0] = 0;
6208                 return request;
6210         case REQ_ENTER:
6211                 /* After returning the status view has been split to
6212                  * show the stage view. No further reloading is
6213                  * necessary. */
6214                 return status_enter(view, line);
6216         case REQ_REFRESH:
6217                 /* Simply reload the view. */
6218                 break;
6220         default:
6221                 return request;
6222         }
6224         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
6226         return REQ_NONE;
6229 static void
6230 status_select(struct view *view, struct line *line)
6232         struct status *status = line->data;
6233         char file[SIZEOF_STR] = "all files";
6234         const char *text;
6235         const char *key;
6237         if (status && !string_format(file, "'%s'", status->new.name))
6238                 return;
6240         if (!status && line[1].type == LINE_STAT_NONE)
6241                 line++;
6243         switch (line->type) {
6244         case LINE_STAT_STAGED:
6245                 text = "Press %s to unstage %s for commit";
6246                 break;
6248         case LINE_STAT_UNSTAGED:
6249                 text = "Press %s to stage %s for commit";
6250                 break;
6252         case LINE_STAT_UNTRACKED:
6253                 text = "Press %s to stage %s for addition";
6254                 break;
6256         case LINE_STAT_HEAD:
6257         case LINE_STAT_NONE:
6258                 text = "Nothing to update";
6259                 break;
6261         default:
6262                 die("line type %d not handled in switch", line->type);
6263         }
6265         if (status && status->status == 'U') {
6266                 text = "Press %s to resolve conflict in %s";
6267                 key = get_key(KEYMAP_STATUS, REQ_STATUS_MERGE);
6269         } else {
6270                 key = get_key(KEYMAP_STATUS, REQ_STATUS_UPDATE);
6271         }
6273         string_format(view->ref, text, key, file);
6274         if (status)
6275                 string_copy(opt_file, status->new.name);
6278 static bool
6279 status_grep(struct view *view, struct line *line)
6281         struct status *status = line->data;
6283         if (status) {
6284                 const char buf[2] = { status->status, 0 };
6285                 const char *text[] = { status->new.name, buf, NULL };
6287                 return grep_text(view, text);
6288         }
6290         return FALSE;
6293 static struct view_ops status_ops = {
6294         "file",
6295         NULL,
6296         status_open,
6297         NULL,
6298         status_draw,
6299         status_request,
6300         status_grep,
6301         status_select,
6302 };
6305 static bool
6306 stage_diff_write(struct io *io, struct line *line, struct line *end)
6308         while (line < end) {
6309                 if (!io_write(io, line->data, strlen(line->data)) ||
6310                     !io_write(io, "\n", 1))
6311                         return FALSE;
6312                 line++;
6313                 if (line->type == LINE_DIFF_CHUNK ||
6314                     line->type == LINE_DIFF_HEADER)
6315                         break;
6316         }
6318         return TRUE;
6321 static struct line *
6322 stage_diff_find(struct view *view, struct line *line, enum line_type type)
6324         for (; view->line < line; line--)
6325                 if (line->type == type)
6326                         return line;
6328         return NULL;
6331 static bool
6332 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
6334         const char *apply_argv[SIZEOF_ARG] = {
6335                 "git", "apply", "--whitespace=nowarn", NULL
6336         };
6337         struct line *diff_hdr;
6338         struct io io;
6339         int argc = 3;
6341         diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
6342         if (!diff_hdr)
6343                 return FALSE;
6345         if (!revert)
6346                 apply_argv[argc++] = "--cached";
6347         if (revert || stage_line_type == LINE_STAT_STAGED)
6348                 apply_argv[argc++] = "-R";
6349         apply_argv[argc++] = "-";
6350         apply_argv[argc++] = NULL;
6351         if (!io_run(&io, IO_WR, opt_cdup, apply_argv))
6352                 return FALSE;
6354         if (!stage_diff_write(&io, diff_hdr, chunk) ||
6355             !stage_diff_write(&io, chunk, view->line + view->lines))
6356                 chunk = NULL;
6358         io_done(&io);
6359         io_run_bg(update_index_argv);
6361         return chunk ? TRUE : FALSE;
6364 static bool
6365 stage_update(struct view *view, struct line *line)
6367         struct line *chunk = NULL;
6369         if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
6370                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6372         if (chunk) {
6373                 if (!stage_apply_chunk(view, chunk, FALSE)) {
6374                         report("Failed to apply chunk");
6375                         return FALSE;
6376                 }
6378         } else if (!stage_status.status) {
6379                 view = VIEW(REQ_VIEW_STATUS);
6381                 for (line = view->line; line < view->line + view->lines; line++)
6382                         if (line->type == stage_line_type)
6383                                 break;
6385                 if (!status_update_files(view, line + 1)) {
6386                         report("Failed to update files");
6387                         return FALSE;
6388                 }
6390         } else if (!status_update_file(&stage_status, stage_line_type)) {
6391                 report("Failed to update file");
6392                 return FALSE;
6393         }
6395         return TRUE;
6398 static bool
6399 stage_revert(struct view *view, struct line *line)
6401         struct line *chunk = NULL;
6403         if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
6404                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6406         if (chunk) {
6407                 if (!prompt_yesno("Are you sure you want to revert changes?"))
6408                         return FALSE;
6410                 if (!stage_apply_chunk(view, chunk, TRUE)) {
6411                         report("Failed to revert chunk");
6412                         return FALSE;
6413                 }
6414                 return TRUE;
6416         } else {
6417                 return status_revert(stage_status.status ? &stage_status : NULL,
6418                                      stage_line_type, FALSE);
6419         }
6423 static void
6424 stage_next(struct view *view, struct line *line)
6426         int i;
6428         if (!stage_chunks) {
6429                 for (line = view->line; line < view->line + view->lines; line++) {
6430                         if (line->type != LINE_DIFF_CHUNK)
6431                                 continue;
6433                         if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
6434                                 report("Allocation failure");
6435                                 return;
6436                         }
6438                         stage_chunk[stage_chunks++] = line - view->line;
6439                 }
6440         }
6442         for (i = 0; i < stage_chunks; i++) {
6443                 if (stage_chunk[i] > view->lineno) {
6444                         do_scroll_view(view, stage_chunk[i] - view->lineno);
6445                         report("Chunk %d of %d", i + 1, stage_chunks);
6446                         return;
6447                 }
6448         }
6450         report("No next chunk found");
6453 static enum request
6454 stage_request(struct view *view, enum request request, struct line *line)
6456         switch (request) {
6457         case REQ_STATUS_UPDATE:
6458                 if (!stage_update(view, line))
6459                         return REQ_NONE;
6460                 break;
6462         case REQ_STATUS_REVERT:
6463                 if (!stage_revert(view, line))
6464                         return REQ_NONE;
6465                 break;
6467         case REQ_STAGE_NEXT:
6468                 if (stage_line_type == LINE_STAT_UNTRACKED) {
6469                         report("File is untracked; press %s to add",
6470                                get_key(KEYMAP_STAGE, REQ_STATUS_UPDATE));
6471                         return REQ_NONE;
6472                 }
6473                 stage_next(view, line);
6474                 return REQ_NONE;
6476         case REQ_EDIT:
6477                 if (!stage_status.new.name[0])
6478                         return request;
6479                 if (stage_status.status == 'D') {
6480                         report("File has been deleted.");
6481                         return REQ_NONE;
6482                 }
6484                 open_editor(stage_status.new.name);
6485                 break;
6487         case REQ_REFRESH:
6488                 /* Reload everything ... */
6489                 break;
6491         case REQ_VIEW_BLAME:
6492                 if (stage_status.new.name[0]) {
6493                         string_copy(opt_file, stage_status.new.name);
6494                         opt_ref[0] = 0;
6495                 }
6496                 return request;
6498         case REQ_ENTER:
6499                 return pager_request(view, request, line);
6501         default:
6502                 return request;
6503         }
6505         VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
6506         open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
6508         /* Check whether the staged entry still exists, and close the
6509          * stage view if it doesn't. */
6510         if (!status_exists(&stage_status, stage_line_type)) {
6511                 status_restore(VIEW(REQ_VIEW_STATUS));
6512                 return REQ_VIEW_CLOSE;
6513         }
6515         if (stage_line_type == LINE_STAT_UNTRACKED) {
6516                 if (!suffixcmp(stage_status.new.name, -1, "/")) {
6517                         report("Cannot display a directory");
6518                         return REQ_NONE;
6519                 }
6521                 if (!prepare_update_file(view, stage_status.new.name)) {
6522                         report("Failed to open file: %s", strerror(errno));
6523                         return REQ_NONE;
6524                 }
6525         }
6526         open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
6528         return REQ_NONE;
6531 static struct view_ops stage_ops = {
6532         "line",
6533         NULL,
6534         NULL,
6535         pager_read,
6536         pager_draw,
6537         stage_request,
6538         pager_grep,
6539         pager_select,
6540 };
6543 /*
6544  * Revision graph
6545  */
6547 struct commit {
6548         char id[SIZEOF_REV];            /* SHA1 ID. */
6549         char title[128];                /* First line of the commit message. */
6550         const char *author;             /* Author of the commit. */
6551         struct time time;               /* Date from the author ident. */
6552         struct ref_list *refs;          /* Repository references. */
6553         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
6554         size_t graph_size;              /* The width of the graph array. */
6555         bool has_parents;               /* Rewritten --parents seen. */
6556 };
6558 /* Size of rev graph with no  "padding" columns */
6559 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6561 struct rev_graph {
6562         struct rev_graph *prev, *next, *parents;
6563         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
6564         size_t size;
6565         struct commit *commit;
6566         size_t pos;
6567         unsigned int boundary:1;
6568 };
6570 /* Parents of the commit being visualized. */
6571 static struct rev_graph graph_parents[4];
6573 /* The current stack of revisions on the graph. */
6574 static struct rev_graph graph_stacks[4] = {
6575         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
6576         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
6577         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
6578         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
6579 };
6581 static inline bool
6582 graph_parent_is_merge(struct rev_graph *graph)
6584         return graph->parents->size > 1;
6587 static inline void
6588 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
6590         struct commit *commit = graph->commit;
6592         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
6593                 commit->graph[commit->graph_size++] = symbol;
6596 static void
6597 clear_rev_graph(struct rev_graph *graph)
6599         graph->boundary = 0;
6600         graph->size = graph->pos = 0;
6601         graph->commit = NULL;
6602         memset(graph->parents, 0, sizeof(*graph->parents));
6605 static void
6606 done_rev_graph(struct rev_graph *graph)
6608         if (graph_parent_is_merge(graph) &&
6609             graph->pos < graph->size - 1 &&
6610             graph->next->size == graph->size + graph->parents->size - 1) {
6611                 size_t i = graph->pos + graph->parents->size - 1;
6613                 graph->commit->graph_size = i * 2;
6614                 while (i < graph->next->size - 1) {
6615                         append_to_rev_graph(graph, ' ');
6616                         append_to_rev_graph(graph, '\\');
6617                         i++;
6618                 }
6619         }
6621         clear_rev_graph(graph);
6624 static void
6625 push_rev_graph(struct rev_graph *graph, const char *parent)
6627         int i;
6629         /* "Collapse" duplicate parents lines.
6630          *
6631          * FIXME: This needs to also update update the drawn graph but
6632          * for now it just serves as a method for pruning graph lines. */
6633         for (i = 0; i < graph->size; i++)
6634                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
6635                         return;
6637         if (graph->size < SIZEOF_REVITEMS) {
6638                 string_copy_rev(graph->rev[graph->size++], parent);
6639         }
6642 static chtype
6643 get_rev_graph_symbol(struct rev_graph *graph)
6645         chtype symbol;
6647         if (graph->boundary)
6648                 symbol = REVGRAPH_BOUND;
6649         else if (graph->parents->size == 0)
6650                 symbol = REVGRAPH_INIT;
6651         else if (graph_parent_is_merge(graph))
6652                 symbol = REVGRAPH_MERGE;
6653         else if (graph->pos >= graph->size)
6654                 symbol = REVGRAPH_BRANCH;
6655         else
6656                 symbol = REVGRAPH_COMMIT;
6658         return symbol;
6661 static void
6662 draw_rev_graph(struct rev_graph *graph)
6664         struct rev_filler {
6665                 chtype separator, line;
6666         };
6667         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
6668         static struct rev_filler fillers[] = {
6669                 { ' ',  '|' },
6670                 { '`',  '.' },
6671                 { '\'', ' ' },
6672                 { '/',  ' ' },
6673         };
6674         chtype symbol = get_rev_graph_symbol(graph);
6675         struct rev_filler *filler;
6676         size_t i;
6678         fillers[DEFAULT].line = opt_line_graphics ? ACS_VLINE : '|';
6679         filler = &fillers[DEFAULT];
6681         for (i = 0; i < graph->pos; i++) {
6682                 append_to_rev_graph(graph, filler->line);
6683                 if (graph_parent_is_merge(graph->prev) &&
6684                     graph->prev->pos == i)
6685                         filler = &fillers[RSHARP];
6687                 append_to_rev_graph(graph, filler->separator);
6688         }
6690         /* Place the symbol for this revision. */
6691         append_to_rev_graph(graph, symbol);
6693         if (graph->prev->size > graph->size)
6694                 filler = &fillers[RDIAG];
6695         else
6696                 filler = &fillers[DEFAULT];
6698         i++;
6700         for (; i < graph->size; i++) {
6701                 append_to_rev_graph(graph, filler->separator);
6702                 append_to_rev_graph(graph, filler->line);
6703                 if (graph_parent_is_merge(graph->prev) &&
6704                     i < graph->prev->pos + graph->parents->size)
6705                         filler = &fillers[RSHARP];
6706                 if (graph->prev->size > graph->size)
6707                         filler = &fillers[LDIAG];
6708         }
6710         if (graph->prev->size > graph->size) {
6711                 append_to_rev_graph(graph, filler->separator);
6712                 if (filler->line != ' ')
6713                         append_to_rev_graph(graph, filler->line);
6714         }
6717 /* Prepare the next rev graph */
6718 static void
6719 prepare_rev_graph(struct rev_graph *graph)
6721         size_t i;
6723         /* First, traverse all lines of revisions up to the active one. */
6724         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6725                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6726                         break;
6728                 push_rev_graph(graph->next, graph->rev[graph->pos]);
6729         }
6731         /* Interleave the new revision parent(s). */
6732         for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6733                 push_rev_graph(graph->next, graph->parents->rev[i]);
6735         /* Lastly, put any remaining revisions. */
6736         for (i = graph->pos + 1; i < graph->size; i++)
6737                 push_rev_graph(graph->next, graph->rev[i]);
6740 static void
6741 update_rev_graph(struct view *view, struct rev_graph *graph)
6743         /* If this is the finalizing update ... */
6744         if (graph->commit)
6745                 prepare_rev_graph(graph);
6747         /* Graph visualization needs a one rev look-ahead,
6748          * so the first update doesn't visualize anything. */
6749         if (!graph->prev->commit)
6750                 return;
6752         if (view->lines > 2)
6753                 view->line[view->lines - 3].dirty = 1;
6754         if (view->lines > 1)
6755                 view->line[view->lines - 2].dirty = 1;
6756         draw_rev_graph(graph->prev);
6757         done_rev_graph(graph->prev->prev);
6761 /*
6762  * Main view backend
6763  */
6765 static const char *main_argv[SIZEOF_ARG] = {
6766         "git", "log", "--no-color", "--pretty=raw", "--parents",
6767                 "--topo-order", "%(diffargs)", "%(revargs)",
6768                 "--", "%(fileargs)", NULL
6769 };
6771 static bool
6772 main_draw(struct view *view, struct line *line, unsigned int lineno)
6774         struct commit *commit = line->data;
6776         if (!commit->author)
6777                 return FALSE;
6779         if (opt_date && draw_date(view, &commit->time))
6780                 return TRUE;
6782         if (opt_author && draw_author(view, commit->author))
6783                 return TRUE;
6785         if (opt_rev_graph && commit->graph_size &&
6786             draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6787                 return TRUE;
6789         if (opt_show_refs && commit->refs) {
6790                 size_t i;
6792                 for (i = 0; i < commit->refs->size; i++) {
6793                         struct ref *ref = commit->refs->refs[i];
6794                         enum line_type type;
6796                         if (ref->head)
6797                                 type = LINE_MAIN_HEAD;
6798                         else if (ref->ltag)
6799                                 type = LINE_MAIN_LOCAL_TAG;
6800                         else if (ref->tag)
6801                                 type = LINE_MAIN_TAG;
6802                         else if (ref->tracked)
6803                                 type = LINE_MAIN_TRACKED;
6804                         else if (ref->remote)
6805                                 type = LINE_MAIN_REMOTE;
6806                         else
6807                                 type = LINE_MAIN_REF;
6809                         if (draw_text(view, type, "[", TRUE) ||
6810                             draw_text(view, type, ref->name, TRUE) ||
6811                             draw_text(view, type, "]", TRUE))
6812                                 return TRUE;
6814                         if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6815                                 return TRUE;
6816                 }
6817         }
6819         draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6820         return TRUE;
6823 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6824 static bool
6825 main_read(struct view *view, char *line)
6827         static struct rev_graph *graph = graph_stacks;
6828         enum line_type type;
6829         struct commit *commit;
6831         if (!line) {
6832                 int i;
6834                 if (!view->lines && !view->prev)
6835                         die("No revisions match the given arguments.");
6836                 if (view->lines > 0) {
6837                         commit = view->line[view->lines - 1].data;
6838                         view->line[view->lines - 1].dirty = 1;
6839                         if (!commit->author) {
6840                                 view->lines--;
6841                                 free(commit);
6842                                 graph->commit = NULL;
6843                         }
6844                 }
6845                 update_rev_graph(view, graph);
6847                 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6848                         clear_rev_graph(&graph_stacks[i]);
6849                 return TRUE;
6850         }
6852         type = get_line_type(line);
6853         if (type == LINE_COMMIT) {
6854                 commit = calloc(1, sizeof(struct commit));
6855                 if (!commit)
6856                         return FALSE;
6858                 line += STRING_SIZE("commit ");
6859                 if (*line == '-') {
6860                         graph->boundary = 1;
6861                         line++;
6862                 }
6864                 string_copy_rev(commit->id, line);
6865                 commit->refs = get_ref_list(commit->id);
6866                 graph->commit = commit;
6867                 add_line_data(view, commit, LINE_MAIN_COMMIT);
6869                 while ((line = strchr(line, ' '))) {
6870                         line++;
6871                         push_rev_graph(graph->parents, line);
6872                         commit->has_parents = TRUE;
6873                 }
6874                 return TRUE;
6875         }
6877         if (!view->lines)
6878                 return TRUE;
6879         commit = view->line[view->lines - 1].data;
6881         switch (type) {
6882         case LINE_PARENT:
6883                 if (commit->has_parents)
6884                         break;
6885                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6886                 break;
6888         case LINE_AUTHOR:
6889                 parse_author_line(line + STRING_SIZE("author "),
6890                                   &commit->author, &commit->time);
6891                 update_rev_graph(view, graph);
6892                 graph = graph->next;
6893                 break;
6895         default:
6896                 /* Fill in the commit title if it has not already been set. */
6897                 if (commit->title[0])
6898                         break;
6900                 /* Require titles to start with a non-space character at the
6901                  * offset used by git log. */
6902                 if (strncmp(line, "    ", 4))
6903                         break;
6904                 line += 4;
6905                 /* Well, if the title starts with a whitespace character,
6906                  * try to be forgiving.  Otherwise we end up with no title. */
6907                 while (isspace(*line))
6908                         line++;
6909                 if (*line == '\0')
6910                         break;
6911                 /* FIXME: More graceful handling of titles; append "..." to
6912                  * shortened titles, etc. */
6914                 string_expand(commit->title, sizeof(commit->title), line, 1);
6915                 view->line[view->lines - 1].dirty = 1;
6916         }
6918         return TRUE;
6921 static enum request
6922 main_request(struct view *view, enum request request, struct line *line)
6924         enum open_flags flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
6926         switch (request) {
6927         case REQ_ENTER:
6928                 open_view(view, REQ_VIEW_DIFF, flags);
6929                 break;
6930         case REQ_REFRESH:
6931                 load_refs();
6932                 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6933                 break;
6934         default:
6935                 return request;
6936         }
6938         return REQ_NONE;
6941 static bool
6942 grep_refs(struct ref_list *list, regex_t *regex)
6944         regmatch_t pmatch;
6945         size_t i;
6947         if (!opt_show_refs || !list)
6948                 return FALSE;
6950         for (i = 0; i < list->size; i++) {
6951                 if (regexec(regex, list->refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6952                         return TRUE;
6953         }
6955         return FALSE;
6958 static bool
6959 main_grep(struct view *view, struct line *line)
6961         struct commit *commit = line->data;
6962         const char *text[] = {
6963                 commit->title,
6964                 opt_author ? commit->author : "",
6965                 mkdate(&commit->time, opt_date),
6966                 NULL
6967         };
6969         return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6972 static void
6973 main_select(struct view *view, struct line *line)
6975         struct commit *commit = line->data;
6977         string_copy_rev(view->ref, commit->id);
6978         string_copy_rev(ref_commit, view->ref);
6981 static struct view_ops main_ops = {
6982         "commit",
6983         main_argv,
6984         NULL,
6985         main_read,
6986         main_draw,
6987         main_request,
6988         main_grep,
6989         main_select,
6990 };
6993 /*
6994  * Status management
6995  */
6997 /* Whether or not the curses interface has been initialized. */
6998 static bool cursed = FALSE;
7000 /* Terminal hacks and workarounds. */
7001 static bool use_scroll_redrawwin;
7002 static bool use_scroll_status_wclear;
7004 /* The status window is used for polling keystrokes. */
7005 static WINDOW *status_win;
7007 /* Reading from the prompt? */
7008 static bool input_mode = FALSE;
7010 static bool status_empty = FALSE;
7012 /* Update status and title window. */
7013 static void
7014 report(const char *msg, ...)
7016         struct view *view = display[current_view];
7018         if (input_mode)
7019                 return;
7021         if (!view) {
7022                 char buf[SIZEOF_STR];
7023                 va_list args;
7025                 va_start(args, msg);
7026                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
7027                         buf[sizeof(buf) - 1] = 0;
7028                         buf[sizeof(buf) - 2] = '.';
7029                         buf[sizeof(buf) - 3] = '.';
7030                         buf[sizeof(buf) - 4] = '.';
7031                 }
7032                 va_end(args);
7033                 die("%s", buf);
7034         }
7036         if (!status_empty || *msg) {
7037                 va_list args;
7039                 va_start(args, msg);
7041                 wmove(status_win, 0, 0);
7042                 if (view->has_scrolled && use_scroll_status_wclear)
7043                         wclear(status_win);
7044                 if (*msg) {
7045                         vwprintw(status_win, msg, args);
7046                         status_empty = FALSE;
7047                 } else {
7048                         status_empty = TRUE;
7049                 }
7050                 wclrtoeol(status_win);
7051                 wnoutrefresh(status_win);
7053                 va_end(args);
7054         }
7056         update_view_title(view);
7059 static void
7060 init_display(void)
7062         const char *term;
7063         int x, y;
7065         /* Initialize the curses library */
7066         if (isatty(STDIN_FILENO)) {
7067                 cursed = !!initscr();
7068                 opt_tty = stdin;
7069         } else {
7070                 /* Leave stdin and stdout alone when acting as a pager. */
7071                 opt_tty = fopen("/dev/tty", "r+");
7072                 if (!opt_tty)
7073                         die("Failed to open /dev/tty");
7074                 cursed = !!newterm(NULL, opt_tty, opt_tty);
7075         }
7077         if (!cursed)
7078                 die("Failed to initialize curses");
7080         nonl();         /* Disable conversion and detect newlines from input. */
7081         cbreak();       /* Take input chars one at a time, no wait for \n */
7082         noecho();       /* Don't echo input */
7083         leaveok(stdscr, FALSE);
7085         if (has_colors())
7086                 init_colors();
7088         getmaxyx(stdscr, y, x);
7089         status_win = newwin(1, 0, y - 1, 0);
7090         if (!status_win)
7091                 die("Failed to create status window");
7093         /* Enable keyboard mapping */
7094         keypad(status_win, TRUE);
7095         wbkgdset(status_win, get_line_attr(LINE_STATUS));
7097         TABSIZE = opt_tab_size;
7099         term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
7100         if (term && !strcmp(term, "gnome-terminal")) {
7101                 /* In the gnome-terminal-emulator, the message from
7102                  * scrolling up one line when impossible followed by
7103                  * scrolling down one line causes corruption of the
7104                  * status line. This is fixed by calling wclear. */
7105                 use_scroll_status_wclear = TRUE;
7106                 use_scroll_redrawwin = FALSE;
7108         } else if (term && !strcmp(term, "xrvt-xpm")) {
7109                 /* No problems with full optimizations in xrvt-(unicode)
7110                  * and aterm. */
7111                 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
7113         } else {
7114                 /* When scrolling in (u)xterm the last line in the
7115                  * scrolling direction will update slowly. */
7116                 use_scroll_redrawwin = TRUE;
7117                 use_scroll_status_wclear = FALSE;
7118         }
7121 static int
7122 get_input(int prompt_position)
7124         struct view *view;
7125         int i, key, cursor_y, cursor_x;
7127         if (prompt_position)
7128                 input_mode = TRUE;
7130         while (TRUE) {
7131                 bool loading = FALSE;
7133                 foreach_view (view, i) {
7134                         update_view(view);
7135                         if (view_is_displayed(view) && view->has_scrolled &&
7136                             use_scroll_redrawwin)
7137                                 redrawwin(view->win);
7138                         view->has_scrolled = FALSE;
7139                         if (view->pipe)
7140                                 loading = TRUE;
7141                 }
7143                 /* Update the cursor position. */
7144                 if (prompt_position) {
7145                         getbegyx(status_win, cursor_y, cursor_x);
7146                         cursor_x = prompt_position;
7147                 } else {
7148                         view = display[current_view];
7149                         getbegyx(view->win, cursor_y, cursor_x);
7150                         cursor_x = view->width - 1;
7151                         cursor_y += view->lineno - view->offset;
7152                 }
7153                 setsyx(cursor_y, cursor_x);
7155                 /* Refresh, accept single keystroke of input */
7156                 doupdate();
7157                 nodelay(status_win, loading);
7158                 key = wgetch(status_win);
7160                 /* wgetch() with nodelay() enabled returns ERR when
7161                  * there's no input. */
7162                 if (key == ERR) {
7164                 } else if (key == KEY_RESIZE) {
7165                         int height, width;
7167                         getmaxyx(stdscr, height, width);
7169                         wresize(status_win, 1, width);
7170                         mvwin(status_win, height - 1, 0);
7171                         wnoutrefresh(status_win);
7172                         resize_display();
7173                         redraw_display(TRUE);
7175                 } else {
7176                         input_mode = FALSE;
7177                         return key;
7178                 }
7179         }
7182 static char *
7183 prompt_input(const char *prompt, input_handler handler, void *data)
7185         enum input_status status = INPUT_OK;
7186         static char buf[SIZEOF_STR];
7187         size_t pos = 0;
7189         buf[pos] = 0;
7191         while (status == INPUT_OK || status == INPUT_SKIP) {
7192                 int key;
7194                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
7195                 wclrtoeol(status_win);
7197                 key = get_input(pos + 1);
7198                 switch (key) {
7199                 case KEY_RETURN:
7200                 case KEY_ENTER:
7201                 case '\n':
7202                         status = pos ? INPUT_STOP : INPUT_CANCEL;
7203                         break;
7205                 case KEY_BACKSPACE:
7206                         if (pos > 0)
7207                                 buf[--pos] = 0;
7208                         else
7209                                 status = INPUT_CANCEL;
7210                         break;
7212                 case KEY_ESC:
7213                         status = INPUT_CANCEL;
7214                         break;
7216                 default:
7217                         if (pos >= sizeof(buf)) {
7218                                 report("Input string too long");
7219                                 return NULL;
7220                         }
7222                         status = handler(data, buf, key);
7223                         if (status == INPUT_OK)
7224                                 buf[pos++] = (char) key;
7225                 }
7226         }
7228         /* Clear the status window */
7229         status_empty = FALSE;
7230         report("");
7232         if (status == INPUT_CANCEL)
7233                 return NULL;
7235         buf[pos++] = 0;
7237         return buf;
7240 static enum input_status
7241 prompt_yesno_handler(void *data, char *buf, int c)
7243         if (c == 'y' || c == 'Y')
7244                 return INPUT_STOP;
7245         if (c == 'n' || c == 'N')
7246                 return INPUT_CANCEL;
7247         return INPUT_SKIP;
7250 static bool
7251 prompt_yesno(const char *prompt)
7253         char prompt2[SIZEOF_STR];
7255         if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
7256                 return FALSE;
7258         return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
7261 static enum input_status
7262 read_prompt_handler(void *data, char *buf, int c)
7264         return isprint(c) ? INPUT_OK : INPUT_SKIP;
7267 static char *
7268 read_prompt(const char *prompt)
7270         return prompt_input(prompt, read_prompt_handler, NULL);
7273 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected)
7275         enum input_status status = INPUT_OK;
7276         int size = 0;
7278         while (items[size].text)
7279                 size++;
7281         while (status == INPUT_OK) {
7282                 const struct menu_item *item = &items[*selected];
7283                 int key;
7284                 int i;
7286                 mvwprintw(status_win, 0, 0, "%s (%d of %d) ",
7287                           prompt, *selected + 1, size);
7288                 if (item->hotkey)
7289                         wprintw(status_win, "[%c] ", (char) item->hotkey);
7290                 wprintw(status_win, "%s", item->text);
7291                 wclrtoeol(status_win);
7293                 key = get_input(COLS - 1);
7294                 switch (key) {
7295                 case KEY_RETURN:
7296                 case KEY_ENTER:
7297                 case '\n':
7298                         status = INPUT_STOP;
7299                         break;
7301                 case KEY_LEFT:
7302                 case KEY_UP:
7303                         *selected = *selected - 1;
7304                         if (*selected < 0)
7305                                 *selected = size - 1;
7306                         break;
7308                 case KEY_RIGHT:
7309                 case KEY_DOWN:
7310                         *selected = (*selected + 1) % size;
7311                         break;
7313                 case KEY_ESC:
7314                         status = INPUT_CANCEL;
7315                         break;
7317                 default:
7318                         for (i = 0; items[i].text; i++)
7319                                 if (items[i].hotkey == key) {
7320                                         *selected = i;
7321                                         status = INPUT_STOP;
7322                                         break;
7323                                 }
7324                 }
7325         }
7327         /* Clear the status window */
7328         status_empty = FALSE;
7329         report("");
7331         return status != INPUT_CANCEL;
7334 /*
7335  * Repository properties
7336  */
7338 static struct ref **refs = NULL;
7339 static size_t refs_size = 0;
7340 static struct ref *refs_head = NULL;
7342 static struct ref_list **ref_lists = NULL;
7343 static size_t ref_lists_size = 0;
7345 DEFINE_ALLOCATOR(realloc_refs, struct ref *, 256)
7346 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
7347 DEFINE_ALLOCATOR(realloc_ref_lists, struct ref_list *, 8)
7349 static int
7350 compare_refs(const void *ref1_, const void *ref2_)
7352         const struct ref *ref1 = *(const struct ref **)ref1_;
7353         const struct ref *ref2 = *(const struct ref **)ref2_;
7355         if (ref1->tag != ref2->tag)
7356                 return ref2->tag - ref1->tag;
7357         if (ref1->ltag != ref2->ltag)
7358                 return ref2->ltag - ref2->ltag;
7359         if (ref1->head != ref2->head)
7360                 return ref2->head - ref1->head;
7361         if (ref1->tracked != ref2->tracked)
7362                 return ref2->tracked - ref1->tracked;
7363         if (ref1->remote != ref2->remote)
7364                 return ref2->remote - ref1->remote;
7365         return strcmp(ref1->name, ref2->name);
7368 static void
7369 foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data)
7371         size_t i;
7373         for (i = 0; i < refs_size; i++)
7374                 if (!visitor(data, refs[i]))
7375                         break;
7378 static struct ref *
7379 get_ref_head()
7381         return refs_head;
7384 static struct ref_list *
7385 get_ref_list(const char *id)
7387         struct ref_list *list;
7388         size_t i;
7390         for (i = 0; i < ref_lists_size; i++)
7391                 if (!strcmp(id, ref_lists[i]->id))
7392                         return ref_lists[i];
7394         if (!realloc_ref_lists(&ref_lists, ref_lists_size, 1))
7395                 return NULL;
7396         list = calloc(1, sizeof(*list));
7397         if (!list)
7398                 return NULL;
7400         for (i = 0; i < refs_size; i++) {
7401                 if (!strcmp(id, refs[i]->id) &&
7402                     realloc_refs_list(&list->refs, list->size, 1))
7403                         list->refs[list->size++] = refs[i];
7404         }
7406         if (!list->refs) {
7407                 free(list);
7408                 return NULL;
7409         }
7411         qsort(list->refs, list->size, sizeof(*list->refs), compare_refs);
7412         ref_lists[ref_lists_size++] = list;
7413         return list;
7416 static int
7417 read_ref(char *id, size_t idlen, char *name, size_t namelen)
7419         struct ref *ref = NULL;
7420         bool tag = FALSE;
7421         bool ltag = FALSE;
7422         bool remote = FALSE;
7423         bool tracked = FALSE;
7424         bool head = FALSE;
7425         int from = 0, to = refs_size - 1;
7427         if (!prefixcmp(name, "refs/tags/")) {
7428                 if (!suffixcmp(name, namelen, "^{}")) {
7429                         namelen -= 3;
7430                         name[namelen] = 0;
7431                 } else {
7432                         ltag = TRUE;
7433                 }
7435                 tag = TRUE;
7436                 namelen -= STRING_SIZE("refs/tags/");
7437                 name    += STRING_SIZE("refs/tags/");
7439         } else if (!prefixcmp(name, "refs/remotes/")) {
7440                 remote = TRUE;
7441                 namelen -= STRING_SIZE("refs/remotes/");
7442                 name    += STRING_SIZE("refs/remotes/");
7443                 tracked  = !strcmp(opt_remote, name);
7445         } else if (!prefixcmp(name, "refs/heads/")) {
7446                 namelen -= STRING_SIZE("refs/heads/");
7447                 name    += STRING_SIZE("refs/heads/");
7448                 if (!strncmp(opt_head, name, namelen))
7449                         return OK;
7451         } else if (!strcmp(name, "HEAD")) {
7452                 head     = TRUE;
7453                 if (*opt_head) {
7454                         namelen  = strlen(opt_head);
7455                         name     = opt_head;
7456                 }
7457         }
7459         /* If we are reloading or it's an annotated tag, replace the
7460          * previous SHA1 with the resolved commit id; relies on the fact
7461          * git-ls-remote lists the commit id of an annotated tag right
7462          * before the commit id it points to. */
7463         while (from <= to) {
7464                 size_t pos = (to + from) / 2;
7465                 int cmp = strcmp(name, refs[pos]->name);
7467                 if (!cmp) {
7468                         ref = refs[pos];
7469                         break;
7470                 }
7472                 if (cmp < 0)
7473                         to = pos - 1;
7474                 else
7475                         from = pos + 1;
7476         }
7478         if (!ref) {
7479                 if (!realloc_refs(&refs, refs_size, 1))
7480                         return ERR;
7481                 ref = calloc(1, sizeof(*ref) + namelen);
7482                 if (!ref)
7483                         return ERR;
7484                 memmove(refs + from + 1, refs + from,
7485                         (refs_size - from) * sizeof(*refs));
7486                 refs[from] = ref;
7487                 strncpy(ref->name, name, namelen);
7488                 refs_size++;
7489         }
7491         ref->head = head;
7492         ref->tag = tag;
7493         ref->ltag = ltag;
7494         ref->remote = remote;
7495         ref->tracked = tracked;
7496         string_copy_rev(ref->id, id);
7498         if (head)
7499                 refs_head = ref;
7500         return OK;
7503 static int
7504 load_refs(void)
7506         const char *head_argv[] = {
7507                 "git", "symbolic-ref", "HEAD", NULL
7508         };
7509         static const char *ls_remote_argv[SIZEOF_ARG] = {
7510                 "git", "ls-remote", opt_git_dir, NULL
7511         };
7512         static bool init = FALSE;
7513         size_t i;
7515         if (!init) {
7516                 if (!argv_from_env(ls_remote_argv, "TIG_LS_REMOTE"))
7517                         die("TIG_LS_REMOTE contains too many arguments");
7518                 init = TRUE;
7519         }
7521         if (!*opt_git_dir)
7522                 return OK;
7524         if (io_run_buf(head_argv, opt_head, sizeof(opt_head)) &&
7525             !prefixcmp(opt_head, "refs/heads/")) {
7526                 char *offset = opt_head + STRING_SIZE("refs/heads/");
7528                 memmove(opt_head, offset, strlen(offset) + 1);
7529         }
7531         refs_head = NULL;
7532         for (i = 0; i < refs_size; i++)
7533                 refs[i]->id[0] = 0;
7535         if (io_run_load(ls_remote_argv, "\t", read_ref) == ERR)
7536                 return ERR;
7538         /* Update the ref lists to reflect changes. */
7539         for (i = 0; i < ref_lists_size; i++) {
7540                 struct ref_list *list = ref_lists[i];
7541                 size_t old, new;
7543                 for (old = new = 0; old < list->size; old++)
7544                         if (!strcmp(list->id, list->refs[old]->id))
7545                                 list->refs[new++] = list->refs[old];
7546                 list->size = new;
7547         }
7549         return OK;
7552 static void
7553 set_remote_branch(const char *name, const char *value, size_t valuelen)
7555         if (!strcmp(name, ".remote")) {
7556                 string_ncopy(opt_remote, value, valuelen);
7558         } else if (*opt_remote && !strcmp(name, ".merge")) {
7559                 size_t from = strlen(opt_remote);
7561                 if (!prefixcmp(value, "refs/heads/"))
7562                         value += STRING_SIZE("refs/heads/");
7564                 if (!string_format_from(opt_remote, &from, "/%s", value))
7565                         opt_remote[0] = 0;
7566         }
7569 static void
7570 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
7572         const char *argv[SIZEOF_ARG] = { name, "=" };
7573         int argc = 1 + (cmd == option_set_command);
7574         int error = ERR;
7576         if (!argv_from_string(argv, &argc, value))
7577                 config_msg = "Too many option arguments";
7578         else
7579                 error = cmd(argc, argv);
7581         if (error == ERR)
7582                 warn("Option 'tig.%s': %s", name, config_msg);
7585 static bool
7586 set_environment_variable(const char *name, const char *value)
7588         size_t len = strlen(name) + 1 + strlen(value) + 1;
7589         char *env = malloc(len);
7591         if (env &&
7592             string_nformat(env, len, NULL, "%s=%s", name, value) &&
7593             putenv(env) == 0)
7594                 return TRUE;
7595         free(env);
7596         return FALSE;
7599 static void
7600 set_work_tree(const char *value)
7602         char cwd[SIZEOF_STR];
7604         if (!getcwd(cwd, sizeof(cwd)))
7605                 die("Failed to get cwd path: %s", strerror(errno));
7606         if (chdir(opt_git_dir) < 0)
7607                 die("Failed to chdir(%s): %s", strerror(errno));
7608         if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7609                 die("Failed to get git path: %s", strerror(errno));
7610         if (chdir(cwd) < 0)
7611                 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7612         if (chdir(value) < 0)
7613                 die("Failed to chdir(%s): %s", value, strerror(errno));
7614         if (!getcwd(cwd, sizeof(cwd)))
7615                 die("Failed to get cwd path: %s", strerror(errno));
7616         if (!set_environment_variable("GIT_WORK_TREE", cwd))
7617                 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7618         if (!set_environment_variable("GIT_DIR", opt_git_dir))
7619                 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7620         opt_is_inside_work_tree = TRUE;
7623 static int
7624 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
7626         if (!strcmp(name, "i18n.commitencoding"))
7627                 string_ncopy(opt_encoding, value, valuelen);
7629         else if (!strcmp(name, "core.editor"))
7630                 string_ncopy(opt_editor, value, valuelen);
7632         else if (!strcmp(name, "core.worktree"))
7633                 set_work_tree(value);
7635         else if (!prefixcmp(name, "tig.color."))
7636                 set_repo_config_option(name + 10, value, option_color_command);
7638         else if (!prefixcmp(name, "tig.bind."))
7639                 set_repo_config_option(name + 9, value, option_bind_command);
7641         else if (!prefixcmp(name, "tig."))
7642                 set_repo_config_option(name + 4, value, option_set_command);
7644         else if (*opt_head && !prefixcmp(name, "branch.") &&
7645                  !strncmp(name + 7, opt_head, strlen(opt_head)))
7646                 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7648         return OK;
7651 static int
7652 load_git_config(void)
7654         const char *config_list_argv[] = { "git", "config", "--list", NULL };
7656         return io_run_load(config_list_argv, "=", read_repo_config_option);
7659 static int
7660 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
7662         if (!opt_git_dir[0]) {
7663                 string_ncopy(opt_git_dir, name, namelen);
7665         } else if (opt_is_inside_work_tree == -1) {
7666                 /* This can be 3 different values depending on the
7667                  * version of git being used. If git-rev-parse does not
7668                  * understand --is-inside-work-tree it will simply echo
7669                  * the option else either "true" or "false" is printed.
7670                  * Default to true for the unknown case. */
7671                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7673         } else if (*name == '.') {
7674                 string_ncopy(opt_cdup, name, namelen);
7676         } else {
7677                 string_ncopy(opt_prefix, name, namelen);
7678         }
7680         return OK;
7683 static int
7684 load_repo_info(void)
7686         const char *rev_parse_argv[] = {
7687                 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7688                         "--show-cdup", "--show-prefix", NULL
7689         };
7691         return io_run_load(rev_parse_argv, "=", read_repo_info);
7695 /*
7696  * Main
7697  */
7699 static const char usage[] =
7700 "tig " TIG_VERSION " (" __DATE__ ")\n"
7701 "\n"
7702 "Usage: tig        [options] [revs] [--] [paths]\n"
7703 "   or: tig show   [options] [revs] [--] [paths]\n"
7704 "   or: tig blame  [rev] path\n"
7705 "   or: tig status\n"
7706 "   or: tig <      [git command output]\n"
7707 "\n"
7708 "Options:\n"
7709 "  -v, --version   Show version and exit\n"
7710 "  -h, --help      Show help message and exit";
7712 static void __NORETURN
7713 quit(int sig)
7715         /* XXX: Restore tty modes and let the OS cleanup the rest! */
7716         if (cursed)
7717                 endwin();
7718         exit(0);
7721 static void __NORETURN
7722 die(const char *err, ...)
7724         va_list args;
7726         endwin();
7728         va_start(args, err);
7729         fputs("tig: ", stderr);
7730         vfprintf(stderr, err, args);
7731         fputs("\n", stderr);
7732         va_end(args);
7734         exit(1);
7737 static void
7738 warn(const char *msg, ...)
7740         va_list args;
7742         va_start(args, msg);
7743         fputs("tig warning: ", stderr);
7744         vfprintf(stderr, msg, args);
7745         fputs("\n", stderr);
7746         va_end(args);
7749 static const char ***filter_args;
7751 static int
7752 read_filter_args(char *name, size_t namelen, char *value, size_t valuelen)
7754         return argv_append(filter_args, name) ? OK : ERR;
7757 static void
7758 filter_rev_parse(const char ***args, const char *arg1, const char *arg2, const char *argv[])
7760         const char *rev_parse_argv[SIZEOF_ARG] = { "git", "rev-parse", arg1, arg2 };
7761         const char **all_argv = NULL;
7763         filter_args = args;
7764         if (!argv_append_array(&all_argv, rev_parse_argv) ||
7765             !argv_append_array(&all_argv, argv) ||
7766             !io_run_load(all_argv, "\n", read_filter_args) == ERR)
7767                 die("Failed to split arguments");
7768         argv_free(all_argv);
7769         free(all_argv);
7772 static void
7773 filter_options(const char *argv[])
7775         filter_rev_parse(&opt_file_args, "--no-revs", "--no-flags", argv);
7776         filter_rev_parse(&opt_diff_args, "--no-revs", "--flags", argv);
7777         filter_rev_parse(&opt_rev_args, "--symbolic", "--revs-only", argv);
7780 static enum request
7781 parse_options(int argc, const char *argv[])
7783         enum request request = REQ_VIEW_MAIN;
7784         const char *subcommand;
7785         bool seen_dashdash = FALSE;
7786         const char **filter_argv = NULL;
7787         int i;
7789         if (!isatty(STDIN_FILENO))
7790                 return REQ_VIEW_PAGER;
7792         if (argc <= 1)
7793                 return REQ_VIEW_MAIN;
7795         subcommand = argv[1];
7796         if (!strcmp(subcommand, "status")) {
7797                 if (argc > 2)
7798                         warn("ignoring arguments after `%s'", subcommand);
7799                 return REQ_VIEW_STATUS;
7801         } else if (!strcmp(subcommand, "blame")) {
7802                 if (argc <= 2 || argc > 4)
7803                         die("invalid number of options to blame\n\n%s", usage);
7805                 i = 2;
7806                 if (argc == 4) {
7807                         string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7808                         i++;
7809                 }
7811                 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7812                 return REQ_VIEW_BLAME;
7814         } else if (!strcmp(subcommand, "show")) {
7815                 request = REQ_VIEW_DIFF;
7817         } else {
7818                 subcommand = NULL;
7819         }
7821         for (i = 1 + !!subcommand; i < argc; i++) {
7822                 const char *opt = argv[i];
7824                 if (seen_dashdash) {
7825                         argv_append(&opt_file_args, opt);
7826                         continue;
7828                 } else if (!strcmp(opt, "--")) {
7829                         seen_dashdash = TRUE;
7830                         continue;
7832                 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7833                         printf("tig version %s\n", TIG_VERSION);
7834                         quit(0);
7836                 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7837                         printf("%s\n", usage);
7838                         quit(0);
7840                 } else if (!strcmp(opt, "--all")) {
7841                         argv_append(&opt_rev_args, opt);
7842                         continue;
7843                 }
7845                 if (!argv_append(&filter_argv, opt))
7846                         die("command too long");
7847         }
7849         if (filter_argv)
7850                 filter_options(filter_argv);
7852         return request;
7855 int
7856 main(int argc, const char *argv[])
7858         const char *codeset = "UTF-8";
7859         enum request request = parse_options(argc, argv);
7860         struct view *view;
7861         size_t i;
7863         signal(SIGINT, quit);
7864         signal(SIGPIPE, SIG_IGN);
7866         if (setlocale(LC_ALL, "")) {
7867                 codeset = nl_langinfo(CODESET);
7868         }
7870         if (load_repo_info() == ERR)
7871                 die("Failed to load repo info.");
7873         if (load_options() == ERR)
7874                 die("Failed to load user config.");
7876         if (load_git_config() == ERR)
7877                 die("Failed to load repo config.");
7879         /* Require a git repository unless when running in pager mode. */
7880         if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7881                 die("Not a git repository");
7883         if (*opt_encoding && strcmp(codeset, "UTF-8")) {
7884                 opt_iconv_in = iconv_open("UTF-8", opt_encoding);
7885                 if (opt_iconv_in == ICONV_NONE)
7886                         die("Failed to initialize character set conversion");
7887         }
7889         if (codeset && strcmp(codeset, "UTF-8")) {
7890                 opt_iconv_out = iconv_open(codeset, "UTF-8");
7891                 if (opt_iconv_out == ICONV_NONE)
7892                         die("Failed to initialize character set conversion");
7893         }
7895         if (load_refs() == ERR)
7896                 die("Failed to load refs.");
7898         foreach_view (view, i) {
7899                 if (getenv(view->cmd_env))
7900                         warn("Use of the %s environment variable is deprecated,"
7901                              " use options or TIG_DIFF_ARGS instead",
7902                              view->cmd_env);
7903                 if (!argv_from_env(view->ops->argv, view->cmd_env))
7904                         die("Too many arguments in the `%s` environment variable",
7905                             view->cmd_env);
7906         }
7908         init_display();
7910         while (view_driver(display[current_view], request)) {
7911                 int key = get_input(0);
7913                 view = display[current_view];
7914                 request = get_keybinding(view->keymap, key);
7916                 /* Some low-level request handling. This keeps access to
7917                  * status_win restricted. */
7918                 switch (request) {
7919                 case REQ_NONE:
7920                         report("Unknown key, press %s for help",
7921                                get_key(view->keymap, REQ_VIEW_HELP));
7922                         break;
7923                 case REQ_PROMPT:
7924                 {
7925                         char *cmd = read_prompt(":");
7927                         if (cmd && isdigit(*cmd)) {
7928                                 int lineno = view->lineno + 1;
7930                                 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7931                                         select_view_line(view, lineno - 1);
7932                                         report("");
7933                                 } else {
7934                                         report("Unable to parse '%s' as a line number", cmd);
7935                                 }
7937                         } else if (cmd) {
7938                                 struct view *next = VIEW(REQ_VIEW_PAGER);
7939                                 const char *argv[SIZEOF_ARG] = { "git" };
7940                                 int argc = 1;
7942                                 /* When running random commands, initially show the
7943                                  * command in the title. However, it maybe later be
7944                                  * overwritten if a commit line is selected. */
7945                                 string_ncopy(next->ref, cmd, strlen(cmd));
7947                                 if (!argv_from_string(argv, &argc, cmd)) {
7948                                         report("Too many arguments");
7949                                 } else if (!prepare_update(next, argv, NULL)) {
7950                                         report("Failed to format command");
7951                                 } else {
7952                                         open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7953                                 }
7954                         }
7956                         request = REQ_NONE;
7957                         break;
7958                 }
7959                 case REQ_SEARCH:
7960                 case REQ_SEARCH_BACK:
7961                 {
7962                         const char *prompt = request == REQ_SEARCH ? "/" : "?";
7963                         char *search = read_prompt(prompt);
7965                         if (search)
7966                                 string_ncopy(opt_search, search, strlen(search));
7967                         else if (*opt_search)
7968                                 request = request == REQ_SEARCH ?
7969                                         REQ_FIND_NEXT :
7970                                         REQ_FIND_PREV;
7971                         else
7972                                 request = REQ_NONE;
7973                         break;
7974                 }
7975                 default:
7976                         break;
7977                 }
7978         }
7980         quit(0);
7982         return 0;