Code

Actually add the ^D binding to move-page-down
[tig.git] / tig.c
1 /* Copyright (c) 2006-2010 Jonas Fonseca <fonseca@diku.dk>
2  *
3  * This program is free software; you can redistribute it and/or
4  * modify it under the terms of the GNU General Public License as
5  * published by the Free Software Foundation; either version 2 of
6  * the License, or (at your option) any later version.
7  *
8  * This program is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11  * GNU General Public License for more details.
12  */
14 #ifdef HAVE_CONFIG_H
15 #include "config.h"
16 #endif
18 #ifndef TIG_VERSION
19 #define TIG_VERSION "unknown-version"
20 #endif
22 #ifndef DEBUG
23 #define NDEBUG
24 #endif
26 #include <assert.h>
27 #include <errno.h>
28 #include <ctype.h>
29 #include <signal.h>
30 #include <stdarg.h>
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <sys/types.h>
35 #include <sys/wait.h>
36 #include <sys/stat.h>
37 #include <sys/select.h>
38 #include <unistd.h>
39 #include <sys/time.h>
40 #include <time.h>
41 #include <fcntl.h>
43 #include <regex.h>
45 #include <locale.h>
46 #include <langinfo.h>
47 #include <iconv.h>
49 /* ncurses(3): Must be defined to have extended wide-character functions. */
50 #define _XOPEN_SOURCE_EXTENDED
52 #ifdef HAVE_NCURSESW_NCURSES_H
53 #include <ncursesw/ncurses.h>
54 #else
55 #ifdef HAVE_NCURSES_NCURSES_H
56 #include <ncurses/ncurses.h>
57 #else
58 #include <ncurses.h>
59 #endif
60 #endif
62 #if __GNUC__ >= 3
63 #define __NORETURN __attribute__((__noreturn__))
64 #else
65 #define __NORETURN
66 #endif
68 static void __NORETURN die(const char *err, ...);
69 static void warn(const char *msg, ...);
70 static void report(const char *msg, ...);
72 #define ABS(x)          ((x) >= 0  ? (x) : -(x))
73 #define MIN(x, y)       ((x) < (y) ? (x) :  (y))
74 #define MAX(x, y)       ((x) > (y) ? (x) :  (y))
76 #define ARRAY_SIZE(x)   (sizeof(x) / sizeof(x[0]))
77 #define STRING_SIZE(x)  (sizeof(x) - 1)
79 #define SIZEOF_STR      1024    /* Default string size. */
80 #define SIZEOF_REF      256     /* Size of symbolic or SHA1 ID. */
81 #define SIZEOF_REV      41      /* Holds a SHA-1 and an ending NUL. */
82 #define SIZEOF_ARG      32      /* Default argument array size. */
84 /* Revision graph */
86 #define REVGRAPH_INIT   'I'
87 #define REVGRAPH_MERGE  'M'
88 #define REVGRAPH_BRANCH '+'
89 #define REVGRAPH_COMMIT '*'
90 #define REVGRAPH_BOUND  '^'
92 #define SIZEOF_REVGRAPH 19      /* Size of revision ancestry graphics. */
94 /* This color name can be used to refer to the default term colors. */
95 #define COLOR_DEFAULT   (-1)
97 #define ICONV_NONE      ((iconv_t) -1)
98 #ifndef ICONV_CONST
99 #define ICONV_CONST     /* nothing */
100 #endif
102 /* The format and size of the date column in the main view. */
103 #define DATE_FORMAT     "%Y-%m-%d %H:%M"
104 #define DATE_COLS       STRING_SIZE("2006-04-29 14:21 ")
105 #define DATE_SHORT_COLS STRING_SIZE("2006-04-29 ")
107 #define ID_COLS         8
108 #define AUTHOR_COLS     19
110 #define MIN_VIEW_HEIGHT 4
112 #define NULL_ID         "0000000000000000000000000000000000000000"
114 #define S_ISGITLINK(mode) (((mode) & S_IFMT) == 0160000)
116 /* Some ASCII-shorthands fitted into the ncurses namespace. */
117 #define KEY_CTL(x)      ((x) & 0x1f) /* KEY_CTL(A) == ^A == \1 */
118 #define KEY_TAB         '\t'
119 #define KEY_RETURN      '\r'
120 #define KEY_ESC         27
123 struct ref {
124         char id[SIZEOF_REV];    /* Commit SHA1 ID */
125         unsigned int head:1;    /* Is it the current HEAD? */
126         unsigned int tag:1;     /* Is it a tag? */
127         unsigned int ltag:1;    /* If so, is the tag local? */
128         unsigned int remote:1;  /* Is it a remote ref? */
129         unsigned int tracked:1; /* Is it the remote for the current HEAD? */
130         char name[1];           /* Ref name; tag or head names are shortened. */
131 };
133 struct ref_list {
134         char id[SIZEOF_REV];    /* Commit SHA1 ID */
135         size_t size;            /* Number of refs. */
136         struct ref **refs;      /* References for this ID. */
137 };
139 static struct ref *get_ref_head();
140 static struct ref_list *get_ref_list(const char *id);
141 static void foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data);
142 static int load_refs(void);
144 enum input_status {
145         INPUT_OK,
146         INPUT_SKIP,
147         INPUT_STOP,
148         INPUT_CANCEL
149 };
151 typedef enum input_status (*input_handler)(void *data, char *buf, int c);
153 static char *prompt_input(const char *prompt, input_handler handler, void *data);
154 static bool prompt_yesno(const char *prompt);
156 struct menu_item {
157         int hotkey;
158         const char *text;
159         void *data;
160 };
162 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected);
164 /*
165  * Allocation helpers ... Entering macro hell to never be seen again.
166  */
168 #define DEFINE_ALLOCATOR(name, type, chunk_size)                                \
169 static type *                                                                   \
170 name(type **mem, size_t size, size_t increase)                                  \
171 {                                                                               \
172         size_t num_chunks = (size + chunk_size - 1) / chunk_size;               \
173         size_t num_chunks_new = (size + increase + chunk_size - 1) / chunk_size;\
174         type *tmp = *mem;                                                       \
175                                                                                 \
176         if (mem == NULL || num_chunks != num_chunks_new) {                      \
177                 tmp = realloc(tmp, num_chunks_new * chunk_size * sizeof(type)); \
178                 if (tmp)                                                        \
179                         *mem = tmp;                                             \
180         }                                                                       \
181                                                                                 \
182         return tmp;                                                             \
185 /*
186  * String helpers
187  */
189 static inline void
190 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
192         if (srclen > dstlen - 1)
193                 srclen = dstlen - 1;
195         strncpy(dst, src, srclen);
196         dst[srclen] = 0;
199 /* Shorthands for safely copying into a fixed buffer. */
201 #define string_copy(dst, src) \
202         string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
204 #define string_ncopy(dst, src, srclen) \
205         string_ncopy_do(dst, sizeof(dst), src, srclen)
207 #define string_copy_rev(dst, src) \
208         string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
210 #define string_add(dst, from, src) \
211         string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
213 static size_t
214 string_expand(char *dst, size_t dstlen, const char *src, int tabsize)
216         size_t size, pos;
218         for (size = pos = 0; size < dstlen - 1 && src[pos]; pos++) {
219                 if (src[pos] == '\t') {
220                         size_t expanded = tabsize - (size % tabsize);
222                         if (expanded + size >= dstlen - 1)
223                                 expanded = dstlen - size - 1;
224                         memcpy(dst + size, "        ", expanded);
225                         size += expanded;
226                 } else {
227                         dst[size++] = src[pos];
228                 }
229         }
231         dst[size] = 0;
232         return pos;
235 static char *
236 chomp_string(char *name)
238         int namelen;
240         while (isspace(*name))
241                 name++;
243         namelen = strlen(name) - 1;
244         while (namelen > 0 && isspace(name[namelen]))
245                 name[namelen--] = 0;
247         return name;
250 static bool
251 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
253         va_list args;
254         size_t pos = bufpos ? *bufpos : 0;
256         va_start(args, fmt);
257         pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
258         va_end(args);
260         if (bufpos)
261                 *bufpos = pos;
263         return pos >= bufsize ? FALSE : TRUE;
266 #define string_format(buf, fmt, args...) \
267         string_nformat(buf, sizeof(buf), NULL, fmt, args)
269 #define string_format_from(buf, from, fmt, args...) \
270         string_nformat(buf, sizeof(buf), from, fmt, args)
272 static int
273 string_enum_compare(const char *str1, const char *str2, int len)
275         size_t i;
277 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
279         /* Diff-Header == DIFF_HEADER */
280         for (i = 0; i < len; i++) {
281                 if (toupper(str1[i]) == toupper(str2[i]))
282                         continue;
284                 if (string_enum_sep(str1[i]) &&
285                     string_enum_sep(str2[i]))
286                         continue;
288                 return str1[i] - str2[i];
289         }
291         return 0;
294 #define enum_equals(entry, str, len) \
295         ((entry).namelen == (len) && !string_enum_compare((entry).name, str, len))
297 struct enum_map {
298         const char *name;
299         int namelen;
300         int value;
301 };
303 #define ENUM_MAP(name, value) { name, STRING_SIZE(name), value }
305 static char *
306 enum_map_name(const char *name, size_t namelen)
308         static char buf[SIZEOF_STR];
309         int bufpos;
311         for (bufpos = 0; bufpos <= namelen; bufpos++) {
312                 buf[bufpos] = tolower(name[bufpos]);
313                 if (buf[bufpos] == '_')
314                         buf[bufpos] = '-';
315         }
317         buf[bufpos] = 0;
318         return buf;
321 #define enum_name(entry) enum_map_name((entry).name, (entry).namelen)
323 static bool
324 map_enum_do(const struct enum_map *map, size_t map_size, int *value, const char *name)
326         size_t namelen = strlen(name);
327         int i;
329         for (i = 0; i < map_size; i++)
330                 if (enum_equals(map[i], name, namelen)) {
331                         *value = map[i].value;
332                         return TRUE;
333                 }
335         return FALSE;
338 #define map_enum(attr, map, name) \
339         map_enum_do(map, ARRAY_SIZE(map), attr, name)
341 #define prefixcmp(str1, str2) \
342         strncmp(str1, str2, STRING_SIZE(str2))
344 static inline int
345 suffixcmp(const char *str, int slen, const char *suffix)
347         size_t len = slen >= 0 ? slen : strlen(str);
348         size_t suffixlen = strlen(suffix);
350         return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
354 /*
355  * Unicode / UTF-8 handling
356  *
357  * NOTE: Much of the following code for dealing with Unicode is derived from
358  * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
359  * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
360  */
362 static inline int
363 unicode_width(unsigned long c, int tab_size)
365         if (c >= 0x1100 &&
366            (c <= 0x115f                         /* Hangul Jamo */
367             || c == 0x2329
368             || c == 0x232a
369             || (c >= 0x2e80  && c <= 0xa4cf && c != 0x303f)
370                                                 /* CJK ... Yi */
371             || (c >= 0xac00  && c <= 0xd7a3)    /* Hangul Syllables */
372             || (c >= 0xf900  && c <= 0xfaff)    /* CJK Compatibility Ideographs */
373             || (c >= 0xfe30  && c <= 0xfe6f)    /* CJK Compatibility Forms */
374             || (c >= 0xff00  && c <= 0xff60)    /* Fullwidth Forms */
375             || (c >= 0xffe0  && c <= 0xffe6)
376             || (c >= 0x20000 && c <= 0x2fffd)
377             || (c >= 0x30000 && c <= 0x3fffd)))
378                 return 2;
380         if (c == '\t')
381                 return tab_size;
383         return 1;
386 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
387  * Illegal bytes are set one. */
388 static const unsigned char utf8_bytes[256] = {
389         1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
390         1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
391         1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
392         1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
393         1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
394         1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
395         2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2,
396         3,3,3,3,3,3,3,3, 3,3,3,3,3,3,3,3, 4,4,4,4,4,4,4,4, 5,5,5,5,6,6,1,1,
397 };
399 static inline unsigned char
400 utf8_char_length(const char *string, const char *end)
402         int c = *(unsigned char *) string;
404         return utf8_bytes[c];
407 /* Decode UTF-8 multi-byte representation into a Unicode character. */
408 static inline unsigned long
409 utf8_to_unicode(const char *string, size_t length)
411         unsigned long unicode;
413         switch (length) {
414         case 1:
415                 unicode  =   string[0];
416                 break;
417         case 2:
418                 unicode  =  (string[0] & 0x1f) << 6;
419                 unicode +=  (string[1] & 0x3f);
420                 break;
421         case 3:
422                 unicode  =  (string[0] & 0x0f) << 12;
423                 unicode += ((string[1] & 0x3f) << 6);
424                 unicode +=  (string[2] & 0x3f);
425                 break;
426         case 4:
427                 unicode  =  (string[0] & 0x0f) << 18;
428                 unicode += ((string[1] & 0x3f) << 12);
429                 unicode += ((string[2] & 0x3f) << 6);
430                 unicode +=  (string[3] & 0x3f);
431                 break;
432         case 5:
433                 unicode  =  (string[0] & 0x0f) << 24;
434                 unicode += ((string[1] & 0x3f) << 18);
435                 unicode += ((string[2] & 0x3f) << 12);
436                 unicode += ((string[3] & 0x3f) << 6);
437                 unicode +=  (string[4] & 0x3f);
438                 break;
439         case 6:
440                 unicode  =  (string[0] & 0x01) << 30;
441                 unicode += ((string[1] & 0x3f) << 24);
442                 unicode += ((string[2] & 0x3f) << 18);
443                 unicode += ((string[3] & 0x3f) << 12);
444                 unicode += ((string[4] & 0x3f) << 6);
445                 unicode +=  (string[5] & 0x3f);
446                 break;
447         default:
448                 return 0;
449         }
451         /* Invalid characters could return the special 0xfffd value but NUL
452          * should be just as good. */
453         return unicode > 0xffff ? 0 : unicode;
456 /* Calculates how much of string can be shown within the given maximum width
457  * and sets trimmed parameter to non-zero value if all of string could not be
458  * shown. If the reserve flag is TRUE, it will reserve at least one
459  * trailing character, which can be useful when drawing a delimiter.
460  *
461  * Returns the number of bytes to output from string to satisfy max_width. */
462 static size_t
463 utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve, int tab_size)
465         const char *string = *start;
466         const char *end = strchr(string, '\0');
467         unsigned char last_bytes = 0;
468         size_t last_ucwidth = 0;
470         *width = 0;
471         *trimmed = 0;
473         while (string < end) {
474                 unsigned char bytes = utf8_char_length(string, end);
475                 size_t ucwidth;
476                 unsigned long unicode;
478                 if (string + bytes > end)
479                         break;
481                 /* Change representation to figure out whether
482                  * it is a single- or double-width character. */
484                 unicode = utf8_to_unicode(string, bytes);
485                 /* FIXME: Graceful handling of invalid Unicode character. */
486                 if (!unicode)
487                         break;
489                 ucwidth = unicode_width(unicode, tab_size);
490                 if (skip > 0) {
491                         skip -= ucwidth <= skip ? ucwidth : skip;
492                         *start += bytes;
493                 }
494                 *width  += ucwidth;
495                 if (*width > max_width) {
496                         *trimmed = 1;
497                         *width -= ucwidth;
498                         if (reserve && *width == max_width) {
499                                 string -= last_bytes;
500                                 *width -= last_ucwidth;
501                         }
502                         break;
503                 }
505                 string  += bytes;
506                 last_bytes = ucwidth ? bytes : 0;
507                 last_ucwidth = ucwidth;
508         }
510         return string - *start;
514 #define DATE_INFO \
515         DATE_(NO), \
516         DATE_(DEFAULT), \
517         DATE_(LOCAL), \
518         DATE_(RELATIVE), \
519         DATE_(SHORT)
521 enum date {
522 #define DATE_(name) DATE_##name
523         DATE_INFO
524 #undef  DATE_
525 };
527 static const struct enum_map date_map[] = {
528 #define DATE_(name) ENUM_MAP(#name, DATE_##name)
529         DATE_INFO
530 #undef  DATE_
531 };
533 struct time {
534         time_t sec;
535         int tz;
536 };
538 static inline int timecmp(const struct time *t1, const struct time *t2)
540         return t1->sec - t2->sec;
543 static const char *
544 mkdate(const struct time *time, enum date date)
546         static char buf[DATE_COLS + 1];
547         static const struct enum_map reldate[] = {
548                 { "second", 1,                  60 * 2 },
549                 { "minute", 60,                 60 * 60 * 2 },
550                 { "hour",   60 * 60,            60 * 60 * 24 * 2 },
551                 { "day",    60 * 60 * 24,       60 * 60 * 24 * 7 * 2 },
552                 { "week",   60 * 60 * 24 * 7,   60 * 60 * 24 * 7 * 5 },
553                 { "month",  60 * 60 * 24 * 30,  60 * 60 * 24 * 30 * 12 },
554         };
555         struct tm tm;
557         if (!date || !time || !time->sec)
558                 return "";
560         if (date == DATE_RELATIVE) {
561                 struct timeval now;
562                 time_t date = time->sec + time->tz;
563                 time_t seconds;
564                 int i;
566                 gettimeofday(&now, NULL);
567                 seconds = now.tv_sec < date ? date - now.tv_sec : now.tv_sec - date;
568                 for (i = 0; i < ARRAY_SIZE(reldate); i++) {
569                         if (seconds >= reldate[i].value)
570                                 continue;
572                         seconds /= reldate[i].namelen;
573                         if (!string_format(buf, "%ld %s%s %s",
574                                            seconds, reldate[i].name,
575                                            seconds > 1 ? "s" : "",
576                                            now.tv_sec >= date ? "ago" : "ahead"))
577                                 break;
578                         return buf;
579                 }
580         }
582         if (date == DATE_LOCAL) {
583                 time_t date = time->sec + time->tz;
584                 localtime_r(&date, &tm);
585         }
586         else {
587                 gmtime_r(&time->sec, &tm);
588         }
589         return strftime(buf, sizeof(buf), DATE_FORMAT, &tm) ? buf : NULL;
593 #define AUTHOR_VALUES \
594         AUTHOR_(NO), \
595         AUTHOR_(FULL), \
596         AUTHOR_(ABBREVIATED)
598 enum author {
599 #define AUTHOR_(name) AUTHOR_##name
600         AUTHOR_VALUES,
601 #undef  AUTHOR_
602         AUTHOR_DEFAULT = AUTHOR_FULL
603 };
605 static const struct enum_map author_map[] = {
606 #define AUTHOR_(name) ENUM_MAP(#name, AUTHOR_##name)
607         AUTHOR_VALUES
608 #undef  AUTHOR_
609 };
611 static const char *
612 get_author_initials(const char *author)
614         static char initials[AUTHOR_COLS * 6 + 1];
615         size_t pos = 0;
616         const char *end = strchr(author, '\0');
618 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@' || (c) == '-')
620         memset(initials, 0, sizeof(initials));
621         while (author < end) {
622                 unsigned char bytes;
623                 size_t i;
625                 while (is_initial_sep(*author))
626                         author++;
628                 bytes = utf8_char_length(author, end);
629                 if (bytes < sizeof(initials) - 1 - pos) {
630                         while (bytes--) {
631                                 initials[pos++] = *author++;
632                         }
633                 }
635                 for (i = pos; author < end && !is_initial_sep(*author); author++) {
636                         if (i < sizeof(initials) - 1)
637                                 initials[i++] = *author;
638                 }
640                 initials[i++] = 0;
641         }
643         return initials;
647 static bool
648 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
650         int valuelen;
652         while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
653                 bool advance = cmd[valuelen] != 0;
655                 cmd[valuelen] = 0;
656                 argv[(*argc)++] = chomp_string(cmd);
657                 cmd = chomp_string(cmd + valuelen + advance);
658         }
660         if (*argc < SIZEOF_ARG)
661                 argv[*argc] = NULL;
662         return *argc < SIZEOF_ARG;
665 static bool
666 argv_from_env(const char **argv, const char *name)
668         char *env = argv ? getenv(name) : NULL;
669         int argc = 0;
671         if (env && *env)
672                 env = strdup(env);
673         return !env || argv_from_string(argv, &argc, env);
676 static void
677 argv_free(const char *argv[])
679         int argc;
681         if (!argv)
682                 return;
683         for (argc = 0; argv[argc]; argc++)
684                 free((void *) argv[argc]);
685         argv[0] = NULL;
688 static size_t
689 argv_size(const char **argv)
691         int argc = 0;
693         while (argv && argv[argc])
694                 argc++;
696         return argc;
699 DEFINE_ALLOCATOR(argv_realloc, const char *, SIZEOF_ARG)
701 static bool
702 argv_append(const char ***argv, const char *arg)
704         size_t argc = argv_size(*argv);
706         if (!argv_realloc(argv, argc, 2))
707                 return FALSE;
709         (*argv)[argc++] = strdup(arg);
710         (*argv)[argc] = NULL;
711         return TRUE;
714 static bool
715 argv_append_array(const char ***dst_argv, const char *src_argv[])
717         int i;
719         for (i = 0; src_argv && src_argv[i]; i++)
720                 if (!argv_append(dst_argv, src_argv[i]))
721                         return FALSE;
722         return TRUE;
725 static bool
726 argv_copy(const char ***dst, const char *src[])
728         int argc;
730         for (argc = 0; src[argc]; argc++)
731                 if (!argv_append(dst, src[argc]))
732                         return FALSE;
733         return TRUE;
737 /*
738  * Executing external commands.
739  */
741 enum io_type {
742         IO_FD,                  /* File descriptor based IO. */
743         IO_BG,                  /* Execute command in the background. */
744         IO_FG,                  /* Execute command with same std{in,out,err}. */
745         IO_RD,                  /* Read only fork+exec IO. */
746         IO_WR,                  /* Write only fork+exec IO. */
747         IO_AP,                  /* Append fork+exec output to file. */
748 };
750 struct io {
751         int pipe;               /* Pipe end for reading or writing. */
752         pid_t pid;              /* PID of spawned process. */
753         int error;              /* Error status. */
754         char *buf;              /* Read buffer. */
755         size_t bufalloc;        /* Allocated buffer size. */
756         size_t bufsize;         /* Buffer content size. */
757         char *bufpos;           /* Current buffer position. */
758         unsigned int eof:1;     /* Has end of file been reached. */
759 };
761 static void
762 io_init(struct io *io)
764         memset(io, 0, sizeof(*io));
765         io->pipe = -1;
768 static bool
769 io_open(struct io *io, const char *fmt, ...)
771         char name[SIZEOF_STR] = "";
772         bool fits;
773         va_list args;
775         io_init(io);
777         va_start(args, fmt);
778         fits = vsnprintf(name, sizeof(name), fmt, args) < sizeof(name);
779         va_end(args);
781         if (!fits) {
782                 io->error = ENAMETOOLONG;
783                 return FALSE;
784         }
785         io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
786         if (io->pipe == -1)
787                 io->error = errno;
788         return io->pipe != -1;
791 static bool
792 io_kill(struct io *io)
794         return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
797 static bool
798 io_done(struct io *io)
800         pid_t pid = io->pid;
802         if (io->pipe != -1)
803                 close(io->pipe);
804         free(io->buf);
805         io_init(io);
807         while (pid > 0) {
808                 int status;
809                 pid_t waiting = waitpid(pid, &status, 0);
811                 if (waiting < 0) {
812                         if (errno == EINTR)
813                                 continue;
814                         io->error = errno;
815                         return FALSE;
816                 }
818                 return waiting == pid &&
819                        !WIFSIGNALED(status) &&
820                        WIFEXITED(status) &&
821                        !WEXITSTATUS(status);
822         }
824         return TRUE;
827 static bool
828 io_run(struct io *io, enum io_type type, const char *dir, const char *argv[], ...)
830         int pipefds[2] = { -1, -1 };
831         va_list args;
833         io_init(io);
835         if ((type == IO_RD || type == IO_WR) && pipe(pipefds) < 0) {
836                 io->error = errno;
837                 return FALSE;
838         } else if (type == IO_AP) {
839                 va_start(args, argv);
840                 pipefds[1] = va_arg(args, int);
841                 va_end(args);
842         }
844         if ((io->pid = fork())) {
845                 if (io->pid == -1)
846                         io->error = errno;
847                 if (pipefds[!(type == IO_WR)] != -1)
848                         close(pipefds[!(type == IO_WR)]);
849                 if (io->pid != -1) {
850                         io->pipe = pipefds[!!(type == IO_WR)];
851                         return TRUE;
852                 }
854         } else {
855                 if (type != IO_FG) {
856                         int devnull = open("/dev/null", O_RDWR);
857                         int readfd  = type == IO_WR ? pipefds[0] : devnull;
858                         int writefd = (type == IO_RD || type == IO_AP)
859                                                         ? pipefds[1] : devnull;
861                         dup2(readfd,  STDIN_FILENO);
862                         dup2(writefd, STDOUT_FILENO);
863                         dup2(devnull, STDERR_FILENO);
865                         close(devnull);
866                         if (pipefds[0] != -1)
867                                 close(pipefds[0]);
868                         if (pipefds[1] != -1)
869                                 close(pipefds[1]);
870                 }
872                 if (dir && *dir && chdir(dir) == -1)
873                         exit(errno);
875                 execvp(argv[0], (char *const*) argv);
876                 exit(errno);
877         }
879         if (pipefds[!!(type == IO_WR)] != -1)
880                 close(pipefds[!!(type == IO_WR)]);
881         return FALSE;
884 static bool
885 io_complete(enum io_type type, const char **argv, const char *dir, int fd)
887         struct io io;
889         return io_run(&io, type, dir, argv, fd) && io_done(&io);
892 static bool
893 io_run_bg(const char **argv)
895         return io_complete(IO_BG, argv, NULL, -1);
898 static bool
899 io_run_fg(const char **argv, const char *dir)
901         return io_complete(IO_FG, argv, dir, -1);
904 static bool
905 io_run_append(const char **argv, int fd)
907         return io_complete(IO_AP, argv, NULL, fd);
910 static bool
911 io_eof(struct io *io)
913         return io->eof;
916 static int
917 io_error(struct io *io)
919         return io->error;
922 static char *
923 io_strerror(struct io *io)
925         return strerror(io->error);
928 static bool
929 io_can_read(struct io *io)
931         struct timeval tv = { 0, 500 };
932         fd_set fds;
934         FD_ZERO(&fds);
935         FD_SET(io->pipe, &fds);
937         return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
940 static ssize_t
941 io_read(struct io *io, void *buf, size_t bufsize)
943         do {
944                 ssize_t readsize = read(io->pipe, buf, bufsize);
946                 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
947                         continue;
948                 else if (readsize == -1)
949                         io->error = errno;
950                 else if (readsize == 0)
951                         io->eof = 1;
952                 return readsize;
953         } while (1);
956 DEFINE_ALLOCATOR(io_realloc_buf, char, BUFSIZ)
958 static char *
959 io_get(struct io *io, int c, bool can_read)
961         char *eol;
962         ssize_t readsize;
964         while (TRUE) {
965                 if (io->bufsize > 0) {
966                         eol = memchr(io->bufpos, c, io->bufsize);
967                         if (eol) {
968                                 char *line = io->bufpos;
970                                 *eol = 0;
971                                 io->bufpos = eol + 1;
972                                 io->bufsize -= io->bufpos - line;
973                                 return line;
974                         }
975                 }
977                 if (io_eof(io)) {
978                         if (io->bufsize) {
979                                 io->bufpos[io->bufsize] = 0;
980                                 io->bufsize = 0;
981                                 return io->bufpos;
982                         }
983                         return NULL;
984                 }
986                 if (!can_read)
987                         return NULL;
989                 if (io->bufsize > 0 && io->bufpos > io->buf)
990                         memmove(io->buf, io->bufpos, io->bufsize);
992                 if (io->bufalloc == io->bufsize) {
993                         if (!io_realloc_buf(&io->buf, io->bufalloc, BUFSIZ))
994                                 return NULL;
995                         io->bufalloc += BUFSIZ;
996                 }
998                 io->bufpos = io->buf;
999                 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
1000                 if (io_error(io))
1001                         return NULL;
1002                 io->bufsize += readsize;
1003         }
1006 static bool
1007 io_write(struct io *io, const void *buf, size_t bufsize)
1009         size_t written = 0;
1011         while (!io_error(io) && written < bufsize) {
1012                 ssize_t size;
1014                 size = write(io->pipe, buf + written, bufsize - written);
1015                 if (size < 0 && (errno == EAGAIN || errno == EINTR))
1016                         continue;
1017                 else if (size == -1)
1018                         io->error = errno;
1019                 else
1020                         written += size;
1021         }
1023         return written == bufsize;
1026 static bool
1027 io_read_buf(struct io *io, char buf[], size_t bufsize)
1029         char *result = io_get(io, '\n', TRUE);
1031         if (result) {
1032                 result = chomp_string(result);
1033                 string_ncopy_do(buf, bufsize, result, strlen(result));
1034         }
1036         return io_done(io) && result;
1039 static bool
1040 io_run_buf(const char **argv, char buf[], size_t bufsize)
1042         struct io io;
1044         return io_run(&io, IO_RD, NULL, argv) && io_read_buf(&io, buf, bufsize);
1047 static int
1048 io_load(struct io *io, const char *separators,
1049         int (*read_property)(char *, size_t, char *, size_t))
1051         char *name;
1052         int state = OK;
1054         while (state == OK && (name = io_get(io, '\n', TRUE))) {
1055                 char *value;
1056                 size_t namelen;
1057                 size_t valuelen;
1059                 name = chomp_string(name);
1060                 namelen = strcspn(name, separators);
1062                 if (name[namelen]) {
1063                         name[namelen] = 0;
1064                         value = chomp_string(name + namelen + 1);
1065                         valuelen = strlen(value);
1067                 } else {
1068                         value = "";
1069                         valuelen = 0;
1070                 }
1072                 state = read_property(name, namelen, value, valuelen);
1073         }
1075         if (state != ERR && io_error(io))
1076                 state = ERR;
1077         io_done(io);
1079         return state;
1082 static int
1083 io_run_load(const char **argv, const char *separators,
1084             int (*read_property)(char *, size_t, char *, size_t))
1086         struct io io;
1088         if (!io_run(&io, IO_RD, NULL, argv))
1089                 return ERR;
1090         return io_load(&io, separators, read_property);
1094 /*
1095  * User requests
1096  */
1098 #define REQ_INFO \
1099         /* XXX: Keep the view request first and in sync with views[]. */ \
1100         REQ_GROUP("View switching") \
1101         REQ_(VIEW_MAIN,         "Show main view"), \
1102         REQ_(VIEW_DIFF,         "Show diff view"), \
1103         REQ_(VIEW_LOG,          "Show log view"), \
1104         REQ_(VIEW_TREE,         "Show tree view"), \
1105         REQ_(VIEW_BLOB,         "Show blob view"), \
1106         REQ_(VIEW_BLAME,        "Show blame view"), \
1107         REQ_(VIEW_BRANCH,       "Show branch view"), \
1108         REQ_(VIEW_HELP,         "Show help page"), \
1109         REQ_(VIEW_PAGER,        "Show pager view"), \
1110         REQ_(VIEW_STATUS,       "Show status view"), \
1111         REQ_(VIEW_STAGE,        "Show stage view"), \
1112         \
1113         REQ_GROUP("View manipulation") \
1114         REQ_(ENTER,             "Enter current line and scroll"), \
1115         REQ_(NEXT,              "Move to next"), \
1116         REQ_(PREVIOUS,          "Move to previous"), \
1117         REQ_(PARENT,            "Move to parent"), \
1118         REQ_(VIEW_NEXT,         "Move focus to next view"), \
1119         REQ_(REFRESH,           "Reload and refresh"), \
1120         REQ_(MAXIMIZE,          "Maximize the current view"), \
1121         REQ_(VIEW_CLOSE,        "Close the current view"), \
1122         REQ_(QUIT,              "Close all views and quit"), \
1123         \
1124         REQ_GROUP("View specific requests") \
1125         REQ_(STATUS_UPDATE,     "Update file status"), \
1126         REQ_(STATUS_REVERT,     "Revert file changes"), \
1127         REQ_(STATUS_MERGE,      "Merge file using external tool"), \
1128         REQ_(STAGE_NEXT,        "Find next chunk to stage"), \
1129         \
1130         REQ_GROUP("Cursor navigation") \
1131         REQ_(MOVE_UP,           "Move cursor one line up"), \
1132         REQ_(MOVE_DOWN,         "Move cursor one line down"), \
1133         REQ_(MOVE_PAGE_DOWN,    "Move cursor one page down"), \
1134         REQ_(MOVE_PAGE_UP,      "Move cursor one page up"), \
1135         REQ_(MOVE_FIRST_LINE,   "Move cursor to first line"), \
1136         REQ_(MOVE_LAST_LINE,    "Move cursor to last line"), \
1137         \
1138         REQ_GROUP("Scrolling") \
1139         REQ_(SCROLL_FIRST_COL,  "Scroll to the first line columns"), \
1140         REQ_(SCROLL_LEFT,       "Scroll two columns left"), \
1141         REQ_(SCROLL_RIGHT,      "Scroll two columns right"), \
1142         REQ_(SCROLL_LINE_UP,    "Scroll one line up"), \
1143         REQ_(SCROLL_LINE_DOWN,  "Scroll one line down"), \
1144         REQ_(SCROLL_PAGE_UP,    "Scroll one page up"), \
1145         REQ_(SCROLL_PAGE_DOWN,  "Scroll one page down"), \
1146         \
1147         REQ_GROUP("Searching") \
1148         REQ_(SEARCH,            "Search the view"), \
1149         REQ_(SEARCH_BACK,       "Search backwards in the view"), \
1150         REQ_(FIND_NEXT,         "Find next search match"), \
1151         REQ_(FIND_PREV,         "Find previous search match"), \
1152         \
1153         REQ_GROUP("Option manipulation") \
1154         REQ_(OPTIONS,           "Open option menu"), \
1155         REQ_(TOGGLE_LINENO,     "Toggle line numbers"), \
1156         REQ_(TOGGLE_DATE,       "Toggle date display"), \
1157         REQ_(TOGGLE_DATE_SHORT, "Toggle short (date-only) dates"), \
1158         REQ_(TOGGLE_AUTHOR,     "Toggle author display"), \
1159         REQ_(TOGGLE_REV_GRAPH,  "Toggle revision graph visualization"), \
1160         REQ_(TOGGLE_REFS,       "Toggle reference display (tags/branches)"), \
1161         REQ_(TOGGLE_SORT_ORDER, "Toggle ascending/descending sort order"), \
1162         REQ_(TOGGLE_SORT_FIELD, "Toggle field to sort by"), \
1163         \
1164         REQ_GROUP("Misc") \
1165         REQ_(PROMPT,            "Bring up the prompt"), \
1166         REQ_(SCREEN_REDRAW,     "Redraw the screen"), \
1167         REQ_(SHOW_VERSION,      "Show version information"), \
1168         REQ_(STOP_LOADING,      "Stop all loading views"), \
1169         REQ_(EDIT,              "Open in editor"), \
1170         REQ_(NONE,              "Do nothing")
1173 /* User action requests. */
1174 enum request {
1175 #define REQ_GROUP(help)
1176 #define REQ_(req, help) REQ_##req
1178         /* Offset all requests to avoid conflicts with ncurses getch values. */
1179         REQ_UNKNOWN = KEY_MAX + 1,
1180         REQ_OFFSET,
1181         REQ_INFO
1183 #undef  REQ_GROUP
1184 #undef  REQ_
1185 };
1187 struct request_info {
1188         enum request request;
1189         const char *name;
1190         int namelen;
1191         const char *help;
1192 };
1194 static const struct request_info req_info[] = {
1195 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
1196 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
1197         REQ_INFO
1198 #undef  REQ_GROUP
1199 #undef  REQ_
1200 };
1202 static enum request
1203 get_request(const char *name)
1205         int namelen = strlen(name);
1206         int i;
1208         for (i = 0; i < ARRAY_SIZE(req_info); i++)
1209                 if (enum_equals(req_info[i], name, namelen))
1210                         return req_info[i].request;
1212         return REQ_UNKNOWN;
1216 /*
1217  * Options
1218  */
1220 /* Option and state variables. */
1221 static enum date opt_date               = DATE_DEFAULT;
1222 static enum author opt_author           = AUTHOR_DEFAULT;
1223 static bool opt_line_number             = FALSE;
1224 static bool opt_line_graphics           = TRUE;
1225 static bool opt_rev_graph               = FALSE;
1226 static bool opt_show_refs               = TRUE;
1227 static int opt_num_interval             = 5;
1228 static double opt_hscroll               = 0.50;
1229 static double opt_scale_split_view      = 2.0 / 3.0;
1230 static int opt_tab_size                 = 8;
1231 static int opt_author_cols              = AUTHOR_COLS;
1232 static char opt_path[SIZEOF_STR]        = "";
1233 static char opt_file[SIZEOF_STR]        = "";
1234 static char opt_ref[SIZEOF_REF]         = "";
1235 static char opt_head[SIZEOF_REF]        = "";
1236 static char opt_remote[SIZEOF_REF]      = "";
1237 static char opt_encoding[20]            = "UTF-8";
1238 static iconv_t opt_iconv_in             = ICONV_NONE;
1239 static iconv_t opt_iconv_out            = ICONV_NONE;
1240 static char opt_search[SIZEOF_STR]      = "";
1241 static char opt_cdup[SIZEOF_STR]        = "";
1242 static char opt_prefix[SIZEOF_STR]      = "";
1243 static char opt_git_dir[SIZEOF_STR]     = "";
1244 static signed char opt_is_inside_work_tree      = -1; /* set to TRUE or FALSE */
1245 static char opt_editor[SIZEOF_STR]      = "";
1246 static FILE *opt_tty                    = NULL;
1247 static const char **opt_diff_args       = NULL;
1248 static const char **opt_rev_args        = NULL;
1249 static const char **opt_file_args       = NULL;
1251 #define is_initial_commit()     (!get_ref_head())
1252 #define is_head_commit(rev)     (!strcmp((rev), "HEAD") || (get_ref_head() && !strcmp(rev, get_ref_head()->id)))
1255 /*
1256  * Line-oriented content detection.
1257  */
1259 #define LINE_INFO \
1260 LINE(DIFF_HEADER,  "diff --git ",       COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1261 LINE(DIFF_CHUNK,   "@@",                COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1262 LINE(DIFF_ADD,     "+",                 COLOR_GREEN,    COLOR_DEFAULT,  0), \
1263 LINE(DIFF_DEL,     "-",                 COLOR_RED,      COLOR_DEFAULT,  0), \
1264 LINE(DIFF_INDEX,        "index ",         COLOR_BLUE,   COLOR_DEFAULT,  0), \
1265 LINE(DIFF_OLDMODE,      "old file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1266 LINE(DIFF_NEWMODE,      "new file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1267 LINE(DIFF_COPY_FROM,    "copy from",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
1268 LINE(DIFF_COPY_TO,      "copy to",        COLOR_YELLOW, COLOR_DEFAULT,  0), \
1269 LINE(DIFF_RENAME_FROM,  "rename from",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
1270 LINE(DIFF_RENAME_TO,    "rename to",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
1271 LINE(DIFF_SIMILARITY,   "similarity ",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
1272 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1273 LINE(DIFF_TREE,         "diff-tree ",     COLOR_BLUE,   COLOR_DEFAULT,  0), \
1274 LINE(PP_AUTHOR,    "Author: ",          COLOR_CYAN,     COLOR_DEFAULT,  0), \
1275 LINE(PP_COMMIT,    "Commit: ",          COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1276 LINE(PP_MERGE,     "Merge: ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
1277 LINE(PP_DATE,      "Date:   ",          COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1278 LINE(PP_ADATE,     "AuthorDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1279 LINE(PP_CDATE,     "CommitDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1280 LINE(PP_REFS,      "Refs: ",            COLOR_RED,      COLOR_DEFAULT,  0), \
1281 LINE(COMMIT,       "commit ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
1282 LINE(PARENT,       "parent ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
1283 LINE(TREE,         "tree ",             COLOR_BLUE,     COLOR_DEFAULT,  0), \
1284 LINE(AUTHOR,       "author ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
1285 LINE(COMMITTER,    "committer ",        COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1286 LINE(SIGNOFF,      "    Signed-off-by", COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1287 LINE(ACKED,        "    Acked-by",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1288 LINE(TESTED,       "    Tested-by",     COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1289 LINE(REVIEWED,     "    Reviewed-by",   COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1290 LINE(DEFAULT,      "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
1291 LINE(CURSOR,       "",                  COLOR_WHITE,    COLOR_GREEN,    A_BOLD), \
1292 LINE(STATUS,       "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
1293 LINE(DELIMITER,    "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1294 LINE(DATE,         "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
1295 LINE(MODE,         "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1296 LINE(LINE_NUMBER,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1297 LINE(TITLE_BLUR,   "",                  COLOR_WHITE,    COLOR_BLUE,     0), \
1298 LINE(TITLE_FOCUS,  "",                  COLOR_WHITE,    COLOR_BLUE,     A_BOLD), \
1299 LINE(MAIN_COMMIT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
1300 LINE(MAIN_TAG,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
1301 LINE(MAIN_LOCAL_TAG,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1302 LINE(MAIN_REMOTE,  "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1303 LINE(MAIN_TRACKED, "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_BOLD), \
1304 LINE(MAIN_REF,     "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1305 LINE(MAIN_HEAD,    "",                  COLOR_CYAN,     COLOR_DEFAULT,  A_BOLD), \
1306 LINE(MAIN_REVGRAPH,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1307 LINE(TREE_HEAD,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_BOLD), \
1308 LINE(TREE_DIR,     "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_NORMAL), \
1309 LINE(TREE_FILE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
1310 LINE(STAT_HEAD,    "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1311 LINE(STAT_SECTION, "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1312 LINE(STAT_NONE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
1313 LINE(STAT_STAGED,  "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1314 LINE(STAT_UNSTAGED,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1315 LINE(STAT_UNTRACKED,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1316 LINE(HELP_KEYMAP,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1317 LINE(HELP_GROUP,   "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
1318 LINE(BLAME_ID,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0)
1320 enum line_type {
1321 #define LINE(type, line, fg, bg, attr) \
1322         LINE_##type
1323         LINE_INFO,
1324         LINE_NONE
1325 #undef  LINE
1326 };
1328 struct line_info {
1329         const char *name;       /* Option name. */
1330         int namelen;            /* Size of option name. */
1331         const char *line;       /* The start of line to match. */
1332         int linelen;            /* Size of string to match. */
1333         int fg, bg, attr;       /* Color and text attributes for the lines. */
1334 };
1336 static struct line_info line_info[] = {
1337 #define LINE(type, line, fg, bg, attr) \
1338         { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1339         LINE_INFO
1340 #undef  LINE
1341 };
1343 static enum line_type
1344 get_line_type(const char *line)
1346         int linelen = strlen(line);
1347         enum line_type type;
1349         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1350                 /* Case insensitive search matches Signed-off-by lines better. */
1351                 if (linelen >= line_info[type].linelen &&
1352                     !strncasecmp(line_info[type].line, line, line_info[type].linelen))
1353                         return type;
1355         return LINE_DEFAULT;
1358 static inline int
1359 get_line_attr(enum line_type type)
1361         assert(type < ARRAY_SIZE(line_info));
1362         return COLOR_PAIR(type) | line_info[type].attr;
1365 static struct line_info *
1366 get_line_info(const char *name)
1368         size_t namelen = strlen(name);
1369         enum line_type type;
1371         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1372                 if (enum_equals(line_info[type], name, namelen))
1373                         return &line_info[type];
1375         return NULL;
1378 static void
1379 init_colors(void)
1381         int default_bg = line_info[LINE_DEFAULT].bg;
1382         int default_fg = line_info[LINE_DEFAULT].fg;
1383         enum line_type type;
1385         start_color();
1387         if (assume_default_colors(default_fg, default_bg) == ERR) {
1388                 default_bg = COLOR_BLACK;
1389                 default_fg = COLOR_WHITE;
1390         }
1392         for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1393                 struct line_info *info = &line_info[type];
1394                 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1395                 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1397                 init_pair(type, fg, bg);
1398         }
1401 struct line {
1402         enum line_type type;
1404         /* State flags */
1405         unsigned int selected:1;
1406         unsigned int dirty:1;
1407         unsigned int cleareol:1;
1408         unsigned int other:16;
1410         void *data;             /* User data */
1411 };
1414 /*
1415  * Keys
1416  */
1418 struct keybinding {
1419         int alias;
1420         enum request request;
1421 };
1423 static struct keybinding default_keybindings[] = {
1424         /* View switching */
1425         { 'm',          REQ_VIEW_MAIN },
1426         { 'd',          REQ_VIEW_DIFF },
1427         { 'l',          REQ_VIEW_LOG },
1428         { 't',          REQ_VIEW_TREE },
1429         { 'f',          REQ_VIEW_BLOB },
1430         { 'B',          REQ_VIEW_BLAME },
1431         { 'H',          REQ_VIEW_BRANCH },
1432         { 'p',          REQ_VIEW_PAGER },
1433         { 'h',          REQ_VIEW_HELP },
1434         { 'S',          REQ_VIEW_STATUS },
1435         { 'c',          REQ_VIEW_STAGE },
1437         /* View manipulation */
1438         { 'q',          REQ_VIEW_CLOSE },
1439         { KEY_TAB,      REQ_VIEW_NEXT },
1440         { KEY_RETURN,   REQ_ENTER },
1441         { KEY_UP,       REQ_PREVIOUS },
1442         { KEY_CTL('P'), REQ_PREVIOUS },
1443         { KEY_DOWN,     REQ_NEXT },
1444         { KEY_CTL('N'), REQ_NEXT },
1445         { 'R',          REQ_REFRESH },
1446         { KEY_F(5),     REQ_REFRESH },
1447         { 'O',          REQ_MAXIMIZE },
1449         /* Cursor navigation */
1450         { 'k',          REQ_MOVE_UP },
1451         { 'j',          REQ_MOVE_DOWN },
1452         { KEY_HOME,     REQ_MOVE_FIRST_LINE },
1453         { KEY_END,      REQ_MOVE_LAST_LINE },
1454         { KEY_NPAGE,    REQ_MOVE_PAGE_DOWN },
1455         { KEY_CTL('D'), REQ_MOVE_PAGE_DOWN },
1456         { ' ',          REQ_MOVE_PAGE_DOWN },
1457         { KEY_PPAGE,    REQ_MOVE_PAGE_UP },
1458         { KEY_CTL('U'), REQ_MOVE_PAGE_UP },
1459         { 'b',          REQ_MOVE_PAGE_UP },
1460         { '-',          REQ_MOVE_PAGE_UP },
1462         /* Scrolling */
1463         { '|',          REQ_SCROLL_FIRST_COL },
1464         { KEY_LEFT,     REQ_SCROLL_LEFT },
1465         { KEY_RIGHT,    REQ_SCROLL_RIGHT },
1466         { KEY_IC,       REQ_SCROLL_LINE_UP },
1467         { KEY_CTL('Y'), REQ_SCROLL_LINE_UP },
1468         { KEY_DC,       REQ_SCROLL_LINE_DOWN },
1469         { KEY_CTL('E'), REQ_SCROLL_LINE_DOWN },
1470         { 'w',          REQ_SCROLL_PAGE_UP },
1471         { 's',          REQ_SCROLL_PAGE_DOWN },
1473         /* Searching */
1474         { '/',          REQ_SEARCH },
1475         { '?',          REQ_SEARCH_BACK },
1476         { 'n',          REQ_FIND_NEXT },
1477         { 'N',          REQ_FIND_PREV },
1479         /* Misc */
1480         { 'Q',          REQ_QUIT },
1481         { 'z',          REQ_STOP_LOADING },
1482         { 'v',          REQ_SHOW_VERSION },
1483         { 'r',          REQ_SCREEN_REDRAW },
1484         { KEY_CTL('L'), REQ_SCREEN_REDRAW },
1485         { 'o',          REQ_OPTIONS },
1486         { '.',          REQ_TOGGLE_LINENO },
1487         { 'D',          REQ_TOGGLE_DATE },
1488         { 'A',          REQ_TOGGLE_AUTHOR },
1489         { 'g',          REQ_TOGGLE_REV_GRAPH },
1490         { 'F',          REQ_TOGGLE_REFS },
1491         { 'I',          REQ_TOGGLE_SORT_ORDER },
1492         { 'i',          REQ_TOGGLE_SORT_FIELD },
1493         { ':',          REQ_PROMPT },
1494         { 'u',          REQ_STATUS_UPDATE },
1495         { '!',          REQ_STATUS_REVERT },
1496         { 'M',          REQ_STATUS_MERGE },
1497         { '@',          REQ_STAGE_NEXT },
1498         { ',',          REQ_PARENT },
1499         { 'e',          REQ_EDIT },
1500 };
1502 #define KEYMAP_INFO \
1503         KEYMAP_(GENERIC), \
1504         KEYMAP_(MAIN), \
1505         KEYMAP_(DIFF), \
1506         KEYMAP_(LOG), \
1507         KEYMAP_(TREE), \
1508         KEYMAP_(BLOB), \
1509         KEYMAP_(BLAME), \
1510         KEYMAP_(BRANCH), \
1511         KEYMAP_(PAGER), \
1512         KEYMAP_(HELP), \
1513         KEYMAP_(STATUS), \
1514         KEYMAP_(STAGE)
1516 enum keymap {
1517 #define KEYMAP_(name) KEYMAP_##name
1518         KEYMAP_INFO
1519 #undef  KEYMAP_
1520 };
1522 static const struct enum_map keymap_table[] = {
1523 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1524         KEYMAP_INFO
1525 #undef  KEYMAP_
1526 };
1528 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1530 struct keybinding_table {
1531         struct keybinding *data;
1532         size_t size;
1533 };
1535 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1537 static void
1538 add_keybinding(enum keymap keymap, enum request request, int key)
1540         struct keybinding_table *table = &keybindings[keymap];
1541         size_t i;
1543         for (i = 0; i < keybindings[keymap].size; i++) {
1544                 if (keybindings[keymap].data[i].alias == key) {
1545                         keybindings[keymap].data[i].request = request;
1546                         return;
1547                 }
1548         }
1550         table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1551         if (!table->data)
1552                 die("Failed to allocate keybinding");
1553         table->data[table->size].alias = key;
1554         table->data[table->size++].request = request;
1556         if (request == REQ_NONE && keymap == KEYMAP_GENERIC) {
1557                 int i;
1559                 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1560                         if (default_keybindings[i].alias == key)
1561                                 default_keybindings[i].request = REQ_NONE;
1562         }
1565 /* Looks for a key binding first in the given map, then in the generic map, and
1566  * lastly in the default keybindings. */
1567 static enum request
1568 get_keybinding(enum keymap keymap, int key)
1570         size_t i;
1572         for (i = 0; i < keybindings[keymap].size; i++)
1573                 if (keybindings[keymap].data[i].alias == key)
1574                         return keybindings[keymap].data[i].request;
1576         for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1577                 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1578                         return keybindings[KEYMAP_GENERIC].data[i].request;
1580         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1581                 if (default_keybindings[i].alias == key)
1582                         return default_keybindings[i].request;
1584         return (enum request) key;
1588 struct key {
1589         const char *name;
1590         int value;
1591 };
1593 static const struct key key_table[] = {
1594         { "Enter",      KEY_RETURN },
1595         { "Space",      ' ' },
1596         { "Backspace",  KEY_BACKSPACE },
1597         { "Tab",        KEY_TAB },
1598         { "Escape",     KEY_ESC },
1599         { "Left",       KEY_LEFT },
1600         { "Right",      KEY_RIGHT },
1601         { "Up",         KEY_UP },
1602         { "Down",       KEY_DOWN },
1603         { "Insert",     KEY_IC },
1604         { "Delete",     KEY_DC },
1605         { "Hash",       '#' },
1606         { "Home",       KEY_HOME },
1607         { "End",        KEY_END },
1608         { "PageUp",     KEY_PPAGE },
1609         { "PageDown",   KEY_NPAGE },
1610         { "F1",         KEY_F(1) },
1611         { "F2",         KEY_F(2) },
1612         { "F3",         KEY_F(3) },
1613         { "F4",         KEY_F(4) },
1614         { "F5",         KEY_F(5) },
1615         { "F6",         KEY_F(6) },
1616         { "F7",         KEY_F(7) },
1617         { "F8",         KEY_F(8) },
1618         { "F9",         KEY_F(9) },
1619         { "F10",        KEY_F(10) },
1620         { "F11",        KEY_F(11) },
1621         { "F12",        KEY_F(12) },
1622 };
1624 static int
1625 get_key_value(const char *name)
1627         int i;
1629         for (i = 0; i < ARRAY_SIZE(key_table); i++)
1630                 if (!strcasecmp(key_table[i].name, name))
1631                         return key_table[i].value;
1633         if (strlen(name) == 2 && name[0] == '^' && isprint(*name))
1634                 return (int)name[1] & 0x1f;
1635         if (strlen(name) == 1 && isprint(*name))
1636                 return (int) *name;
1637         return ERR;
1640 static const char *
1641 get_key_name(int key_value)
1643         static char key_char[] = "'X'\0";
1644         const char *seq = NULL;
1645         int key;
1647         for (key = 0; key < ARRAY_SIZE(key_table); key++)
1648                 if (key_table[key].value == key_value)
1649                         seq = key_table[key].name;
1651         if (seq == NULL && key_value < 0x7f) {
1652                 char *s = key_char + 1;
1654                 if (key_value >= 0x20) {
1655                         *s++ = key_value;
1656                 } else {
1657                         *s++ = '^';
1658                         *s++ = 0x40 | (key_value & 0x1f);
1659                 }
1660                 *s++ = '\'';
1661                 *s++ = '\0';
1662                 seq = key_char;
1663         }
1665         return seq ? seq : "(no key)";
1668 static bool
1669 append_key(char *buf, size_t *pos, const struct keybinding *keybinding)
1671         const char *sep = *pos > 0 ? ", " : "";
1672         const char *keyname = get_key_name(keybinding->alias);
1674         return string_nformat(buf, BUFSIZ, pos, "%s%s", sep, keyname);
1677 static bool
1678 append_keymap_request_keys(char *buf, size_t *pos, enum request request,
1679                            enum keymap keymap, bool all)
1681         int i;
1683         for (i = 0; i < keybindings[keymap].size; i++) {
1684                 if (keybindings[keymap].data[i].request == request) {
1685                         if (!append_key(buf, pos, &keybindings[keymap].data[i]))
1686                                 return FALSE;
1687                         if (!all)
1688                                 break;
1689                 }
1690         }
1692         return TRUE;
1695 #define get_key(keymap, request) get_keys(keymap, request, FALSE)
1697 static const char *
1698 get_keys(enum keymap keymap, enum request request, bool all)
1700         static char buf[BUFSIZ];
1701         size_t pos = 0;
1702         int i;
1704         buf[pos] = 0;
1706         if (!append_keymap_request_keys(buf, &pos, request, keymap, all))
1707                 return "Too many keybindings!";
1708         if (pos > 0 && !all)
1709                 return buf;
1711         if (keymap != KEYMAP_GENERIC) {
1712                 /* Only the generic keymap includes the default keybindings when
1713                  * listing all keys. */
1714                 if (all)
1715                         return buf;
1717                 if (!append_keymap_request_keys(buf, &pos, request, KEYMAP_GENERIC, all))
1718                         return "Too many keybindings!";
1719                 if (pos)
1720                         return buf;
1721         }
1723         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1724                 if (default_keybindings[i].request == request) {
1725                         if (!append_key(buf, &pos, &default_keybindings[i]))
1726                                 return "Too many keybindings!";
1727                         if (!all)
1728                                 return buf;
1729                 }
1730         }
1732         return buf;
1735 struct run_request {
1736         enum keymap keymap;
1737         int key;
1738         const char **argv;
1739 };
1741 static struct run_request *run_request;
1742 static size_t run_requests;
1744 DEFINE_ALLOCATOR(realloc_run_requests, struct run_request, 8)
1746 static enum request
1747 add_run_request(enum keymap keymap, int key, const char **argv)
1749         struct run_request *req;
1751         if (!realloc_run_requests(&run_request, run_requests, 1))
1752                 return REQ_NONE;
1754         req = &run_request[run_requests];
1755         req->keymap = keymap;
1756         req->key = key;
1757         req->argv = NULL;
1759         if (!argv_copy(&req->argv, argv))
1760                 return REQ_NONE;
1762         return REQ_NONE + ++run_requests;
1765 static struct run_request *
1766 get_run_request(enum request request)
1768         if (request <= REQ_NONE)
1769                 return NULL;
1770         return &run_request[request - REQ_NONE - 1];
1773 static void
1774 add_builtin_run_requests(void)
1776         const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1777         const char *checkout[] = { "git", "checkout", "%(branch)", NULL };
1778         const char *commit[] = { "git", "commit", NULL };
1779         const char *gc[] = { "git", "gc", NULL };
1780         struct run_request reqs[] = {
1781                 { KEYMAP_MAIN,    'C', cherry_pick },
1782                 { KEYMAP_STATUS,  'C', commit },
1783                 { KEYMAP_BRANCH,  'C', checkout },
1784                 { KEYMAP_GENERIC, 'G', gc },
1785         };
1786         int i;
1788         for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1789                 enum request req = get_keybinding(reqs[i].keymap, reqs[i].key);
1791                 if (req != reqs[i].key)
1792                         continue;
1793                 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argv);
1794                 if (req != REQ_NONE)
1795                         add_keybinding(reqs[i].keymap, req, reqs[i].key);
1796         }
1799 /*
1800  * User config file handling.
1801  */
1803 static int   config_lineno;
1804 static bool  config_errors;
1805 static const char *config_msg;
1807 static const struct enum_map color_map[] = {
1808 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1809         COLOR_MAP(DEFAULT),
1810         COLOR_MAP(BLACK),
1811         COLOR_MAP(BLUE),
1812         COLOR_MAP(CYAN),
1813         COLOR_MAP(GREEN),
1814         COLOR_MAP(MAGENTA),
1815         COLOR_MAP(RED),
1816         COLOR_MAP(WHITE),
1817         COLOR_MAP(YELLOW),
1818 };
1820 static const struct enum_map attr_map[] = {
1821 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1822         ATTR_MAP(NORMAL),
1823         ATTR_MAP(BLINK),
1824         ATTR_MAP(BOLD),
1825         ATTR_MAP(DIM),
1826         ATTR_MAP(REVERSE),
1827         ATTR_MAP(STANDOUT),
1828         ATTR_MAP(UNDERLINE),
1829 };
1831 #define set_attribute(attr, name)       map_enum(attr, attr_map, name)
1833 static int parse_step(double *opt, const char *arg)
1835         *opt = atoi(arg);
1836         if (!strchr(arg, '%'))
1837                 return OK;
1839         /* "Shift down" so 100% and 1 does not conflict. */
1840         *opt = (*opt - 1) / 100;
1841         if (*opt >= 1.0) {
1842                 *opt = 0.99;
1843                 config_msg = "Step value larger than 100%";
1844                 return ERR;
1845         }
1846         if (*opt < 0.0) {
1847                 *opt = 1;
1848                 config_msg = "Invalid step value";
1849                 return ERR;
1850         }
1851         return OK;
1854 static int
1855 parse_int(int *opt, const char *arg, int min, int max)
1857         int value = atoi(arg);
1859         if (min <= value && value <= max) {
1860                 *opt = value;
1861                 return OK;
1862         }
1864         config_msg = "Integer value out of bound";
1865         return ERR;
1868 static bool
1869 set_color(int *color, const char *name)
1871         if (map_enum(color, color_map, name))
1872                 return TRUE;
1873         if (!prefixcmp(name, "color"))
1874                 return parse_int(color, name + 5, 0, 255) == OK;
1875         return FALSE;
1878 /* Wants: object fgcolor bgcolor [attribute] */
1879 static int
1880 option_color_command(int argc, const char *argv[])
1882         struct line_info *info;
1884         if (argc < 3) {
1885                 config_msg = "Wrong number of arguments given to color command";
1886                 return ERR;
1887         }
1889         info = get_line_info(argv[0]);
1890         if (!info) {
1891                 static const struct enum_map obsolete[] = {
1892                         ENUM_MAP("main-delim",  LINE_DELIMITER),
1893                         ENUM_MAP("main-date",   LINE_DATE),
1894                         ENUM_MAP("main-author", LINE_AUTHOR),
1895                 };
1896                 int index;
1898                 if (!map_enum(&index, obsolete, argv[0])) {
1899                         config_msg = "Unknown color name";
1900                         return ERR;
1901                 }
1902                 info = &line_info[index];
1903         }
1905         if (!set_color(&info->fg, argv[1]) ||
1906             !set_color(&info->bg, argv[2])) {
1907                 config_msg = "Unknown color";
1908                 return ERR;
1909         }
1911         info->attr = 0;
1912         while (argc-- > 3) {
1913                 int attr;
1915                 if (!set_attribute(&attr, argv[argc])) {
1916                         config_msg = "Unknown attribute";
1917                         return ERR;
1918                 }
1919                 info->attr |= attr;
1920         }
1922         return OK;
1925 static int parse_bool(bool *opt, const char *arg)
1927         *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1928                 ? TRUE : FALSE;
1929         return OK;
1932 static int parse_enum_do(unsigned int *opt, const char *arg,
1933                          const struct enum_map *map, size_t map_size)
1935         bool is_true;
1937         assert(map_size > 1);
1939         if (map_enum_do(map, map_size, (int *) opt, arg))
1940                 return OK;
1942         if (parse_bool(&is_true, arg) != OK)
1943                 return ERR;
1945         *opt = is_true ? map[1].value : map[0].value;
1946         return OK;
1949 #define parse_enum(opt, arg, map) \
1950         parse_enum_do(opt, arg, map, ARRAY_SIZE(map))
1952 static int
1953 parse_string(char *opt, const char *arg, size_t optsize)
1955         int arglen = strlen(arg);
1957         switch (arg[0]) {
1958         case '\"':
1959         case '\'':
1960                 if (arglen == 1 || arg[arglen - 1] != arg[0]) {
1961                         config_msg = "Unmatched quotation";
1962                         return ERR;
1963                 }
1964                 arg += 1; arglen -= 2;
1965         default:
1966                 string_ncopy_do(opt, optsize, arg, arglen);
1967                 return OK;
1968         }
1971 /* Wants: name = value */
1972 static int
1973 option_set_command(int argc, const char *argv[])
1975         if (argc != 3) {
1976                 config_msg = "Wrong number of arguments given to set command";
1977                 return ERR;
1978         }
1980         if (strcmp(argv[1], "=")) {
1981                 config_msg = "No value assigned";
1982                 return ERR;
1983         }
1985         if (!strcmp(argv[0], "show-author"))
1986                 return parse_enum(&opt_author, argv[2], author_map);
1988         if (!strcmp(argv[0], "show-date"))
1989                 return parse_enum(&opt_date, argv[2], date_map);
1991         if (!strcmp(argv[0], "show-rev-graph"))
1992                 return parse_bool(&opt_rev_graph, argv[2]);
1994         if (!strcmp(argv[0], "show-refs"))
1995                 return parse_bool(&opt_show_refs, argv[2]);
1997         if (!strcmp(argv[0], "show-line-numbers"))
1998                 return parse_bool(&opt_line_number, argv[2]);
2000         if (!strcmp(argv[0], "line-graphics"))
2001                 return parse_bool(&opt_line_graphics, argv[2]);
2003         if (!strcmp(argv[0], "line-number-interval"))
2004                 return parse_int(&opt_num_interval, argv[2], 1, 1024);
2006         if (!strcmp(argv[0], "author-width"))
2007                 return parse_int(&opt_author_cols, argv[2], 0, 1024);
2009         if (!strcmp(argv[0], "horizontal-scroll"))
2010                 return parse_step(&opt_hscroll, argv[2]);
2012         if (!strcmp(argv[0], "split-view-height"))
2013                 return parse_step(&opt_scale_split_view, argv[2]);
2015         if (!strcmp(argv[0], "tab-size"))
2016                 return parse_int(&opt_tab_size, argv[2], 1, 1024);
2018         if (!strcmp(argv[0], "commit-encoding"))
2019                 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
2021         config_msg = "Unknown variable name";
2022         return ERR;
2025 /* Wants: mode request key */
2026 static int
2027 option_bind_command(int argc, const char *argv[])
2029         enum request request;
2030         int keymap = -1;
2031         int key;
2033         if (argc < 3) {
2034                 config_msg = "Wrong number of arguments given to bind command";
2035                 return ERR;
2036         }
2038         if (!set_keymap(&keymap, argv[0])) {
2039                 config_msg = "Unknown key map";
2040                 return ERR;
2041         }
2043         key = get_key_value(argv[1]);
2044         if (key == ERR) {
2045                 config_msg = "Unknown key";
2046                 return ERR;
2047         }
2049         request = get_request(argv[2]);
2050         if (request == REQ_UNKNOWN) {
2051                 static const struct enum_map obsolete[] = {
2052                         ENUM_MAP("cherry-pick",         REQ_NONE),
2053                         ENUM_MAP("screen-resize",       REQ_NONE),
2054                         ENUM_MAP("tree-parent",         REQ_PARENT),
2055                 };
2056                 int alias;
2058                 if (map_enum(&alias, obsolete, argv[2])) {
2059                         if (alias != REQ_NONE)
2060                                 add_keybinding(keymap, alias, key);
2061                         config_msg = "Obsolete request name";
2062                         return ERR;
2063                 }
2064         }
2065         if (request == REQ_UNKNOWN && *argv[2]++ == '!')
2066                 request = add_run_request(keymap, key, argv + 2);
2067         if (request == REQ_UNKNOWN) {
2068                 config_msg = "Unknown request name";
2069                 return ERR;
2070         }
2072         add_keybinding(keymap, request, key);
2074         return OK;
2077 static int
2078 set_option(const char *opt, char *value)
2080         const char *argv[SIZEOF_ARG];
2081         int argc = 0;
2083         if (!argv_from_string(argv, &argc, value)) {
2084                 config_msg = "Too many option arguments";
2085                 return ERR;
2086         }
2088         if (!strcmp(opt, "color"))
2089                 return option_color_command(argc, argv);
2091         if (!strcmp(opt, "set"))
2092                 return option_set_command(argc, argv);
2094         if (!strcmp(opt, "bind"))
2095                 return option_bind_command(argc, argv);
2097         config_msg = "Unknown option command";
2098         return ERR;
2101 static int
2102 read_option(char *opt, size_t optlen, char *value, size_t valuelen)
2104         int status = OK;
2106         config_lineno++;
2107         config_msg = "Internal error";
2109         /* Check for comment markers, since read_properties() will
2110          * only ensure opt and value are split at first " \t". */
2111         optlen = strcspn(opt, "#");
2112         if (optlen == 0)
2113                 return OK;
2115         if (opt[optlen] != 0) {
2116                 config_msg = "No option value";
2117                 status = ERR;
2119         }  else {
2120                 /* Look for comment endings in the value. */
2121                 size_t len = strcspn(value, "#");
2123                 if (len < valuelen) {
2124                         valuelen = len;
2125                         value[valuelen] = 0;
2126                 }
2128                 status = set_option(opt, value);
2129         }
2131         if (status == ERR) {
2132                 warn("Error on line %d, near '%.*s': %s",
2133                      config_lineno, (int) optlen, opt, config_msg);
2134                 config_errors = TRUE;
2135         }
2137         /* Always keep going if errors are encountered. */
2138         return OK;
2141 static void
2142 load_option_file(const char *path)
2144         struct io io;
2146         /* It's OK that the file doesn't exist. */
2147         if (!io_open(&io, "%s", path))
2148                 return;
2150         config_lineno = 0;
2151         config_errors = FALSE;
2153         if (io_load(&io, " \t", read_option) == ERR ||
2154             config_errors == TRUE)
2155                 warn("Errors while loading %s.", path);
2158 static int
2159 load_options(void)
2161         const char *home = getenv("HOME");
2162         const char *tigrc_user = getenv("TIGRC_USER");
2163         const char *tigrc_system = getenv("TIGRC_SYSTEM");
2164         const char *tig_diff_opts = getenv("TIG_DIFF_OPTS");
2165         char buf[SIZEOF_STR];
2167         if (!tigrc_system)
2168                 tigrc_system = SYSCONFDIR "/tigrc";
2169         load_option_file(tigrc_system);
2171         if (!tigrc_user) {
2172                 if (!home || !string_format(buf, "%s/.tigrc", home))
2173                         return ERR;
2174                 tigrc_user = buf;
2175         }
2176         load_option_file(tigrc_user);
2178         /* Add _after_ loading config files to avoid adding run requests
2179          * that conflict with keybindings. */
2180         add_builtin_run_requests();
2182         if (!opt_diff_args && tig_diff_opts && *tig_diff_opts) {
2183                 static const char *diff_opts[SIZEOF_ARG] = { NULL };
2184                 int argc = 0;
2186                 if (!string_format(buf, "%s", tig_diff_opts) ||
2187                     !argv_from_string(diff_opts, &argc, buf))
2188                         die("TIG_DIFF_OPTS contains too many arguments");
2189                 else if (!argv_copy(&opt_diff_args, diff_opts))
2190                         die("Failed to format TIG_DIFF_OPTS arguments");
2191         }
2193         return OK;
2197 /*
2198  * The viewer
2199  */
2201 struct view;
2202 struct view_ops;
2204 /* The display array of active views and the index of the current view. */
2205 static struct view *display[2];
2206 static unsigned int current_view;
2208 #define foreach_displayed_view(view, i) \
2209         for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
2211 #define displayed_views()       (display[1] != NULL ? 2 : 1)
2213 /* Current head and commit ID */
2214 static char ref_blob[SIZEOF_REF]        = "";
2215 static char ref_commit[SIZEOF_REF]      = "HEAD";
2216 static char ref_head[SIZEOF_REF]        = "HEAD";
2217 static char ref_branch[SIZEOF_REF]      = "";
2219 enum view_type {
2220         VIEW_MAIN,
2221         VIEW_DIFF,
2222         VIEW_LOG,
2223         VIEW_TREE,
2224         VIEW_BLOB,
2225         VIEW_BLAME,
2226         VIEW_BRANCH,
2227         VIEW_HELP,
2228         VIEW_PAGER,
2229         VIEW_STATUS,
2230         VIEW_STAGE,
2231 };
2233 struct view {
2234         enum view_type type;    /* View type */
2235         const char *name;       /* View name */
2236         const char *cmd_env;    /* Command line set via environment */
2237         const char *id;         /* Points to either of ref_{head,commit,blob} */
2239         struct view_ops *ops;   /* View operations */
2241         enum keymap keymap;     /* What keymap does this view have */
2242         bool git_dir;           /* Whether the view requires a git directory. */
2244         char ref[SIZEOF_REF];   /* Hovered commit reference */
2245         char vid[SIZEOF_REF];   /* View ID. Set to id member when updating. */
2247         int height, width;      /* The width and height of the main window */
2248         WINDOW *win;            /* The main window */
2249         WINDOW *title;          /* The title window living below the main window */
2251         /* Navigation */
2252         unsigned long offset;   /* Offset of the window top */
2253         unsigned long yoffset;  /* Offset from the window side. */
2254         unsigned long lineno;   /* Current line number */
2255         unsigned long p_offset; /* Previous offset of the window top */
2256         unsigned long p_yoffset;/* Previous offset from the window side */
2257         unsigned long p_lineno; /* Previous current line number */
2258         bool p_restore;         /* Should the previous position be restored. */
2260         /* Searching */
2261         char grep[SIZEOF_STR];  /* Search string */
2262         regex_t *regex;         /* Pre-compiled regexp */
2264         /* If non-NULL, points to the view that opened this view. If this view
2265          * is closed tig will switch back to the parent view. */
2266         struct view *parent;
2267         struct view *prev;
2269         /* Buffering */
2270         size_t lines;           /* Total number of lines */
2271         struct line *line;      /* Line index */
2272         unsigned int digits;    /* Number of digits in the lines member. */
2274         /* Drawing */
2275         struct line *curline;   /* Line currently being drawn. */
2276         enum line_type curtype; /* Attribute currently used for drawing. */
2277         unsigned long col;      /* Column when drawing. */
2278         bool has_scrolled;      /* View was scrolled. */
2280         /* Loading */
2281         const char **argv;      /* Shell command arguments. */
2282         const char *dir;        /* Directory from which to execute. */
2283         struct io io;
2284         struct io *pipe;
2285         time_t start_time;
2286         time_t update_secs;
2287 };
2289 struct view_ops {
2290         /* What type of content being displayed. Used in the title bar. */
2291         const char *type;
2292         /* Default command arguments. */
2293         const char **argv;
2294         /* Open and reads in all view content. */
2295         bool (*open)(struct view *view);
2296         /* Read one line; updates view->line. */
2297         bool (*read)(struct view *view, char *data);
2298         /* Draw one line; @lineno must be < view->height. */
2299         bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
2300         /* Depending on view handle a special requests. */
2301         enum request (*request)(struct view *view, enum request request, struct line *line);
2302         /* Search for regexp in a line. */
2303         bool (*grep)(struct view *view, struct line *line);
2304         /* Select line */
2305         void (*select)(struct view *view, struct line *line);
2306         /* Prepare view for loading */
2307         bool (*prepare)(struct view *view);
2308 };
2310 static struct view_ops blame_ops;
2311 static struct view_ops blob_ops;
2312 static struct view_ops diff_ops;
2313 static struct view_ops help_ops;
2314 static struct view_ops log_ops;
2315 static struct view_ops main_ops;
2316 static struct view_ops pager_ops;
2317 static struct view_ops stage_ops;
2318 static struct view_ops status_ops;
2319 static struct view_ops tree_ops;
2320 static struct view_ops branch_ops;
2322 #define VIEW_STR(type, name, env, ref, ops, map, git) \
2323         { type, name, #env, ref, ops, map, git }
2325 #define VIEW_(id, name, ops, git, ref) \
2326         VIEW_STR(VIEW_##id, name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
2328 static struct view views[] = {
2329         VIEW_(MAIN,   "main",   &main_ops,   TRUE,  ref_head),
2330         VIEW_(DIFF,   "diff",   &diff_ops,   TRUE,  ref_commit),
2331         VIEW_(LOG,    "log",    &log_ops,    TRUE,  ref_head),
2332         VIEW_(TREE,   "tree",   &tree_ops,   TRUE,  ref_commit),
2333         VIEW_(BLOB,   "blob",   &blob_ops,   TRUE,  ref_blob),
2334         VIEW_(BLAME,  "blame",  &blame_ops,  TRUE,  ref_commit),
2335         VIEW_(BRANCH, "branch", &branch_ops, TRUE,  ref_head),
2336         VIEW_(HELP,   "help",   &help_ops,   FALSE, ""),
2337         VIEW_(PAGER,  "pager",  &pager_ops,  FALSE, ""),
2338         VIEW_(STATUS, "status", &status_ops, TRUE,  ""),
2339         VIEW_(STAGE,  "stage",  &stage_ops,  TRUE,  ""),
2340 };
2342 #define VIEW(req)       (&views[(req) - REQ_OFFSET - 1])
2344 #define foreach_view(view, i) \
2345         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2347 #define view_is_displayed(view) \
2348         (view == display[0] || view == display[1])
2350 static enum request
2351 view_request(struct view *view, enum request request)
2353         if (!view || !view->lines)
2354                 return request;
2355         return view->ops->request(view, request, &view->line[view->lineno]);
2359 /*
2360  * View drawing.
2361  */
2363 static inline void
2364 set_view_attr(struct view *view, enum line_type type)
2366         if (!view->curline->selected && view->curtype != type) {
2367                 (void) wattrset(view->win, get_line_attr(type));
2368                 wchgat(view->win, -1, 0, type, NULL);
2369                 view->curtype = type;
2370         }
2373 static int
2374 draw_chars(struct view *view, enum line_type type, const char *string,
2375            int max_len, bool use_tilde)
2377         static char out_buffer[BUFSIZ * 2];
2378         int len = 0;
2379         int col = 0;
2380         int trimmed = FALSE;
2381         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2383         if (max_len <= 0)
2384                 return 0;
2386         len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde, opt_tab_size);
2388         set_view_attr(view, type);
2389         if (len > 0) {
2390                 if (opt_iconv_out != ICONV_NONE) {
2391                         ICONV_CONST char *inbuf = (ICONV_CONST char *) string;
2392                         size_t inlen = len + 1;
2394                         char *outbuf = out_buffer;
2395                         size_t outlen = sizeof(out_buffer);
2397                         size_t ret;
2399                         ret = iconv(opt_iconv_out, &inbuf, &inlen, &outbuf, &outlen);
2400                         if (ret != (size_t) -1) {
2401                                 string = out_buffer;
2402                                 len = sizeof(out_buffer) - outlen;
2403                         }
2404                 }
2406                 waddnstr(view->win, string, len);
2407         }
2408         if (trimmed && use_tilde) {
2409                 set_view_attr(view, LINE_DELIMITER);
2410                 waddch(view->win, '~');
2411                 col++;
2412         }
2414         return col;
2417 static int
2418 draw_space(struct view *view, enum line_type type, int max, int spaces)
2420         static char space[] = "                    ";
2421         int col = 0;
2423         spaces = MIN(max, spaces);
2425         while (spaces > 0) {
2426                 int len = MIN(spaces, sizeof(space) - 1);
2428                 col += draw_chars(view, type, space, len, FALSE);
2429                 spaces -= len;
2430         }
2432         return col;
2435 static bool
2436 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
2438         char text[SIZEOF_STR];
2440         do {
2441                 size_t pos = string_expand(text, sizeof(text), string, opt_tab_size);
2443                 view->col += draw_chars(view, type, text, view->width + view->yoffset - view->col, trim);
2444                 string += pos;
2445         } while (*string && view->width + view->yoffset > view->col);
2447         return view->width + view->yoffset <= view->col;
2450 static bool
2451 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
2453         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2454         int max = view->width + view->yoffset - view->col;
2455         int i;
2457         if (max < size)
2458                 size = max;
2460         set_view_attr(view, type);
2461         /* Using waddch() instead of waddnstr() ensures that
2462          * they'll be rendered correctly for the cursor line. */
2463         for (i = skip; i < size; i++)
2464                 waddch(view->win, graphic[i]);
2466         view->col += size;
2467         if (size < max && skip <= size)
2468                 waddch(view->win, ' ');
2469         view->col++;
2471         return view->width + view->yoffset <= view->col;
2474 static bool
2475 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
2477         int max = MIN(view->width + view->yoffset - view->col, len);
2478         int col;
2480         if (text)
2481                 col = draw_chars(view, type, text, max - 1, trim);
2482         else
2483                 col = draw_space(view, type, max - 1, max - 1);
2485         view->col += col;
2486         view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
2487         return view->width + view->yoffset <= view->col;
2490 static bool
2491 draw_date(struct view *view, struct time *time)
2493         const char *date = mkdate(time, opt_date);
2494         int cols = opt_date == DATE_SHORT ? DATE_SHORT_COLS : DATE_COLS;
2496         return draw_field(view, LINE_DATE, date, cols, FALSE);
2499 static bool
2500 draw_author(struct view *view, const char *author)
2502         bool trim = opt_author_cols == 0 || opt_author_cols > 5;
2503         bool abbreviate = opt_author == AUTHOR_ABBREVIATED || !trim;
2505         if (abbreviate && author)
2506                 author = get_author_initials(author);
2508         return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2511 static bool
2512 draw_mode(struct view *view, mode_t mode)
2514         const char *str;
2516         if (S_ISDIR(mode))
2517                 str = "drwxr-xr-x";
2518         else if (S_ISLNK(mode))
2519                 str = "lrwxrwxrwx";
2520         else if (S_ISGITLINK(mode))
2521                 str = "m---------";
2522         else if (S_ISREG(mode) && mode & S_IXUSR)
2523                 str = "-rwxr-xr-x";
2524         else if (S_ISREG(mode))
2525                 str = "-rw-r--r--";
2526         else
2527                 str = "----------";
2529         return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2532 static bool
2533 draw_lineno(struct view *view, unsigned int lineno)
2535         char number[10];
2536         int digits3 = view->digits < 3 ? 3 : view->digits;
2537         int max = MIN(view->width + view->yoffset - view->col, digits3);
2538         char *text = NULL;
2539         chtype separator = opt_line_graphics ? ACS_VLINE : '|';
2541         lineno += view->offset + 1;
2542         if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2543                 static char fmt[] = "%1ld";
2545                 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2546                 if (string_format(number, fmt, lineno))
2547                         text = number;
2548         }
2549         if (text)
2550                 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2551         else
2552                 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2553         return draw_graphic(view, LINE_DEFAULT, &separator, 1);
2556 static bool
2557 draw_view_line(struct view *view, unsigned int lineno)
2559         struct line *line;
2560         bool selected = (view->offset + lineno == view->lineno);
2562         assert(view_is_displayed(view));
2564         if (view->offset + lineno >= view->lines)
2565                 return FALSE;
2567         line = &view->line[view->offset + lineno];
2569         wmove(view->win, lineno, 0);
2570         if (line->cleareol)
2571                 wclrtoeol(view->win);
2572         view->col = 0;
2573         view->curline = line;
2574         view->curtype = LINE_NONE;
2575         line->selected = FALSE;
2576         line->dirty = line->cleareol = 0;
2578         if (selected) {
2579                 set_view_attr(view, LINE_CURSOR);
2580                 line->selected = TRUE;
2581                 view->ops->select(view, line);
2582         }
2584         return view->ops->draw(view, line, lineno);
2587 static void
2588 redraw_view_dirty(struct view *view)
2590         bool dirty = FALSE;
2591         int lineno;
2593         for (lineno = 0; lineno < view->height; lineno++) {
2594                 if (view->offset + lineno >= view->lines)
2595                         break;
2596                 if (!view->line[view->offset + lineno].dirty)
2597                         continue;
2598                 dirty = TRUE;
2599                 if (!draw_view_line(view, lineno))
2600                         break;
2601         }
2603         if (!dirty)
2604                 return;
2605         wnoutrefresh(view->win);
2608 static void
2609 redraw_view_from(struct view *view, int lineno)
2611         assert(0 <= lineno && lineno < view->height);
2613         for (; lineno < view->height; lineno++) {
2614                 if (!draw_view_line(view, lineno))
2615                         break;
2616         }
2618         wnoutrefresh(view->win);
2621 static void
2622 redraw_view(struct view *view)
2624         werase(view->win);
2625         redraw_view_from(view, 0);
2629 static void
2630 update_view_title(struct view *view)
2632         char buf[SIZEOF_STR];
2633         char state[SIZEOF_STR];
2634         size_t bufpos = 0, statelen = 0;
2636         assert(view_is_displayed(view));
2638         if (view->type != VIEW_STATUS && view->lines) {
2639                 unsigned int view_lines = view->offset + view->height;
2640                 unsigned int lines = view->lines
2641                                    ? MIN(view_lines, view->lines) * 100 / view->lines
2642                                    : 0;
2644                 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2645                                    view->ops->type,
2646                                    view->lineno + 1,
2647                                    view->lines,
2648                                    lines);
2650         }
2652         if (view->pipe) {
2653                 time_t secs = time(NULL) - view->start_time;
2655                 /* Three git seconds are a long time ... */
2656                 if (secs > 2)
2657                         string_format_from(state, &statelen, " loading %lds", secs);
2658         }
2660         string_format_from(buf, &bufpos, "[%s]", view->name);
2661         if (*view->ref && bufpos < view->width) {
2662                 size_t refsize = strlen(view->ref);
2663                 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2665                 if (minsize < view->width)
2666                         refsize = view->width - minsize + 7;
2667                 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2668         }
2670         if (statelen && bufpos < view->width) {
2671                 string_format_from(buf, &bufpos, "%s", state);
2672         }
2674         if (view == display[current_view])
2675                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2676         else
2677                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2679         mvwaddnstr(view->title, 0, 0, buf, bufpos);
2680         wclrtoeol(view->title);
2681         wnoutrefresh(view->title);
2684 static int
2685 apply_step(double step, int value)
2687         if (step >= 1)
2688                 return (int) step;
2689         value *= step + 0.01;
2690         return value ? value : 1;
2693 static void
2694 resize_display(void)
2696         int offset, i;
2697         struct view *base = display[0];
2698         struct view *view = display[1] ? display[1] : display[0];
2700         /* Setup window dimensions */
2702         getmaxyx(stdscr, base->height, base->width);
2704         /* Make room for the status window. */
2705         base->height -= 1;
2707         if (view != base) {
2708                 /* Horizontal split. */
2709                 view->width   = base->width;
2710                 view->height  = apply_step(opt_scale_split_view, base->height);
2711                 view->height  = MAX(view->height, MIN_VIEW_HEIGHT);
2712                 view->height  = MIN(view->height, base->height - MIN_VIEW_HEIGHT);
2713                 base->height -= view->height;
2715                 /* Make room for the title bar. */
2716                 view->height -= 1;
2717         }
2719         /* Make room for the title bar. */
2720         base->height -= 1;
2722         offset = 0;
2724         foreach_displayed_view (view, i) {
2725                 if (!view->win) {
2726                         view->win = newwin(view->height, 0, offset, 0);
2727                         if (!view->win)
2728                                 die("Failed to create %s view", view->name);
2730                         scrollok(view->win, FALSE);
2732                         view->title = newwin(1, 0, offset + view->height, 0);
2733                         if (!view->title)
2734                                 die("Failed to create title window");
2736                 } else {
2737                         wresize(view->win, view->height, view->width);
2738                         mvwin(view->win,   offset, 0);
2739                         mvwin(view->title, offset + view->height, 0);
2740                 }
2742                 offset += view->height + 1;
2743         }
2746 static void
2747 redraw_display(bool clear)
2749         struct view *view;
2750         int i;
2752         foreach_displayed_view (view, i) {
2753                 if (clear)
2754                         wclear(view->win);
2755                 redraw_view(view);
2756                 update_view_title(view);
2757         }
2761 /*
2762  * Option management
2763  */
2765 static void
2766 toggle_enum_option_do(unsigned int *opt, const char *help,
2767                       const struct enum_map *map, size_t size)
2769         *opt = (*opt + 1) % size;
2770         redraw_display(FALSE);
2771         report("Displaying %s %s", enum_name(map[*opt]), help);
2774 #define toggle_enum_option(opt, help, map) \
2775         toggle_enum_option_do(opt, help, map, ARRAY_SIZE(map))
2777 #define toggle_date() toggle_enum_option(&opt_date, "dates", date_map)
2778 #define toggle_author() toggle_enum_option(&opt_author, "author names", author_map)
2780 static void
2781 toggle_view_option(bool *option, const char *help)
2783         *option = !*option;
2784         redraw_display(FALSE);
2785         report("%sabling %s", *option ? "En" : "Dis", help);
2788 static void
2789 open_option_menu(void)
2791         const struct menu_item menu[] = {
2792                 { '.', "line numbers", &opt_line_number },
2793                 { 'D', "date display", &opt_date },
2794                 { 'A', "author display", &opt_author },
2795                 { 'g', "revision graph display", &opt_rev_graph },
2796                 { 'F', "reference display", &opt_show_refs },
2797                 { 0 }
2798         };
2799         int selected = 0;
2801         if (prompt_menu("Toggle option", menu, &selected)) {
2802                 if (menu[selected].data == &opt_date)
2803                         toggle_date();
2804                 else if (menu[selected].data == &opt_author)
2805                         toggle_author();
2806                 else
2807                         toggle_view_option(menu[selected].data, menu[selected].text);
2808         }
2811 static void
2812 maximize_view(struct view *view)
2814         memset(display, 0, sizeof(display));
2815         current_view = 0;
2816         display[current_view] = view;
2817         resize_display();
2818         redraw_display(FALSE);
2819         report("");
2823 /*
2824  * Navigation
2825  */
2827 static bool
2828 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2830         if (lineno >= view->lines)
2831                 lineno = view->lines > 0 ? view->lines - 1 : 0;
2833         if (offset > lineno || offset + view->height <= lineno) {
2834                 unsigned long half = view->height / 2;
2836                 if (lineno > half)
2837                         offset = lineno - half;
2838                 else
2839                         offset = 0;
2840         }
2842         if (offset != view->offset || lineno != view->lineno) {
2843                 view->offset = offset;
2844                 view->lineno = lineno;
2845                 return TRUE;
2846         }
2848         return FALSE;
2851 /* Scrolling backend */
2852 static void
2853 do_scroll_view(struct view *view, int lines)
2855         bool redraw_current_line = FALSE;
2857         /* The rendering expects the new offset. */
2858         view->offset += lines;
2860         assert(0 <= view->offset && view->offset < view->lines);
2861         assert(lines);
2863         /* Move current line into the view. */
2864         if (view->lineno < view->offset) {
2865                 view->lineno = view->offset;
2866                 redraw_current_line = TRUE;
2867         } else if (view->lineno >= view->offset + view->height) {
2868                 view->lineno = view->offset + view->height - 1;
2869                 redraw_current_line = TRUE;
2870         }
2872         assert(view->offset <= view->lineno && view->lineno < view->lines);
2874         /* Redraw the whole screen if scrolling is pointless. */
2875         if (view->height < ABS(lines)) {
2876                 redraw_view(view);
2878         } else {
2879                 int line = lines > 0 ? view->height - lines : 0;
2880                 int end = line + ABS(lines);
2882                 scrollok(view->win, TRUE);
2883                 wscrl(view->win, lines);
2884                 scrollok(view->win, FALSE);
2886                 while (line < end && draw_view_line(view, line))
2887                         line++;
2889                 if (redraw_current_line)
2890                         draw_view_line(view, view->lineno - view->offset);
2891                 wnoutrefresh(view->win);
2892         }
2894         view->has_scrolled = TRUE;
2895         report("");
2898 /* Scroll frontend */
2899 static void
2900 scroll_view(struct view *view, enum request request)
2902         int lines = 1;
2904         assert(view_is_displayed(view));
2906         switch (request) {
2907         case REQ_SCROLL_FIRST_COL:
2908                 view->yoffset = 0;
2909                 redraw_view_from(view, 0);
2910                 report("");
2911                 return;
2912         case REQ_SCROLL_LEFT:
2913                 if (view->yoffset == 0) {
2914                         report("Cannot scroll beyond the first column");
2915                         return;
2916                 }
2917                 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2918                         view->yoffset = 0;
2919                 else
2920                         view->yoffset -= apply_step(opt_hscroll, view->width);
2921                 redraw_view_from(view, 0);
2922                 report("");
2923                 return;
2924         case REQ_SCROLL_RIGHT:
2925                 view->yoffset += apply_step(opt_hscroll, view->width);
2926                 redraw_view(view);
2927                 report("");
2928                 return;
2929         case REQ_SCROLL_PAGE_DOWN:
2930                 lines = view->height;
2931         case REQ_SCROLL_LINE_DOWN:
2932                 if (view->offset + lines > view->lines)
2933                         lines = view->lines - view->offset;
2935                 if (lines == 0 || view->offset + view->height >= view->lines) {
2936                         report("Cannot scroll beyond the last line");
2937                         return;
2938                 }
2939                 break;
2941         case REQ_SCROLL_PAGE_UP:
2942                 lines = view->height;
2943         case REQ_SCROLL_LINE_UP:
2944                 if (lines > view->offset)
2945                         lines = view->offset;
2947                 if (lines == 0) {
2948                         report("Cannot scroll beyond the first line");
2949                         return;
2950                 }
2952                 lines = -lines;
2953                 break;
2955         default:
2956                 die("request %d not handled in switch", request);
2957         }
2959         do_scroll_view(view, lines);
2962 /* Cursor moving */
2963 static void
2964 move_view(struct view *view, enum request request)
2966         int scroll_steps = 0;
2967         int steps;
2969         switch (request) {
2970         case REQ_MOVE_FIRST_LINE:
2971                 steps = -view->lineno;
2972                 break;
2974         case REQ_MOVE_LAST_LINE:
2975                 steps = view->lines - view->lineno - 1;
2976                 break;
2978         case REQ_MOVE_PAGE_UP:
2979                 steps = view->height > view->lineno
2980                       ? -view->lineno : -view->height;
2981                 break;
2983         case REQ_MOVE_PAGE_DOWN:
2984                 steps = view->lineno + view->height >= view->lines
2985                       ? view->lines - view->lineno - 1 : view->height;
2986                 break;
2988         case REQ_MOVE_UP:
2989                 steps = -1;
2990                 break;
2992         case REQ_MOVE_DOWN:
2993                 steps = 1;
2994                 break;
2996         default:
2997                 die("request %d not handled in switch", request);
2998         }
3000         if (steps <= 0 && view->lineno == 0) {
3001                 report("Cannot move beyond the first line");
3002                 return;
3004         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
3005                 report("Cannot move beyond the last line");
3006                 return;
3007         }
3009         /* Move the current line */
3010         view->lineno += steps;
3011         assert(0 <= view->lineno && view->lineno < view->lines);
3013         /* Check whether the view needs to be scrolled */
3014         if (view->lineno < view->offset ||
3015             view->lineno >= view->offset + view->height) {
3016                 scroll_steps = steps;
3017                 if (steps < 0 && -steps > view->offset) {
3018                         scroll_steps = -view->offset;
3020                 } else if (steps > 0) {
3021                         if (view->lineno == view->lines - 1 &&
3022                             view->lines > view->height) {
3023                                 scroll_steps = view->lines - view->offset - 1;
3024                                 if (scroll_steps >= view->height)
3025                                         scroll_steps -= view->height - 1;
3026                         }
3027                 }
3028         }
3030         if (!view_is_displayed(view)) {
3031                 view->offset += scroll_steps;
3032                 assert(0 <= view->offset && view->offset < view->lines);
3033                 view->ops->select(view, &view->line[view->lineno]);
3034                 return;
3035         }
3037         /* Repaint the old "current" line if we be scrolling */
3038         if (ABS(steps) < view->height)
3039                 draw_view_line(view, view->lineno - steps - view->offset);
3041         if (scroll_steps) {
3042                 do_scroll_view(view, scroll_steps);
3043                 return;
3044         }
3046         /* Draw the current line */
3047         draw_view_line(view, view->lineno - view->offset);
3049         wnoutrefresh(view->win);
3050         report("");
3054 /*
3055  * Searching
3056  */
3058 static void search_view(struct view *view, enum request request);
3060 static bool
3061 grep_text(struct view *view, const char *text[])
3063         regmatch_t pmatch;
3064         size_t i;
3066         for (i = 0; text[i]; i++)
3067                 if (*text[i] &&
3068                     regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
3069                         return TRUE;
3070         return FALSE;
3073 static void
3074 select_view_line(struct view *view, unsigned long lineno)
3076         unsigned long old_lineno = view->lineno;
3077         unsigned long old_offset = view->offset;
3079         if (goto_view_line(view, view->offset, lineno)) {
3080                 if (view_is_displayed(view)) {
3081                         if (old_offset != view->offset) {
3082                                 redraw_view(view);
3083                         } else {
3084                                 draw_view_line(view, old_lineno - view->offset);
3085                                 draw_view_line(view, view->lineno - view->offset);
3086                                 wnoutrefresh(view->win);
3087                         }
3088                 } else {
3089                         view->ops->select(view, &view->line[view->lineno]);
3090                 }
3091         }
3094 static void
3095 find_next(struct view *view, enum request request)
3097         unsigned long lineno = view->lineno;
3098         int direction;
3100         if (!*view->grep) {
3101                 if (!*opt_search)
3102                         report("No previous search");
3103                 else
3104                         search_view(view, request);
3105                 return;
3106         }
3108         switch (request) {
3109         case REQ_SEARCH:
3110         case REQ_FIND_NEXT:
3111                 direction = 1;
3112                 break;
3114         case REQ_SEARCH_BACK:
3115         case REQ_FIND_PREV:
3116                 direction = -1;
3117                 break;
3119         default:
3120                 return;
3121         }
3123         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
3124                 lineno += direction;
3126         /* Note, lineno is unsigned long so will wrap around in which case it
3127          * will become bigger than view->lines. */
3128         for (; lineno < view->lines; lineno += direction) {
3129                 if (view->ops->grep(view, &view->line[lineno])) {
3130                         select_view_line(view, lineno);
3131                         report("Line %ld matches '%s'", lineno + 1, view->grep);
3132                         return;
3133                 }
3134         }
3136         report("No match found for '%s'", view->grep);
3139 static void
3140 search_view(struct view *view, enum request request)
3142         int regex_err;
3144         if (view->regex) {
3145                 regfree(view->regex);
3146                 *view->grep = 0;
3147         } else {
3148                 view->regex = calloc(1, sizeof(*view->regex));
3149                 if (!view->regex)
3150                         return;
3151         }
3153         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
3154         if (regex_err != 0) {
3155                 char buf[SIZEOF_STR] = "unknown error";
3157                 regerror(regex_err, view->regex, buf, sizeof(buf));
3158                 report("Search failed: %s", buf);
3159                 return;
3160         }
3162         string_copy(view->grep, opt_search);
3164         find_next(view, request);
3167 /*
3168  * Incremental updating
3169  */
3171 static void
3172 reset_view(struct view *view)
3174         int i;
3176         for (i = 0; i < view->lines; i++)
3177                 free(view->line[i].data);
3178         free(view->line);
3180         view->p_offset = view->offset;
3181         view->p_yoffset = view->yoffset;
3182         view->p_lineno = view->lineno;
3184         view->line = NULL;
3185         view->offset = 0;
3186         view->yoffset = 0;
3187         view->lines  = 0;
3188         view->lineno = 0;
3189         view->vid[0] = 0;
3190         view->update_secs = 0;
3193 static const char *
3194 format_arg(const char *name)
3196         static struct {
3197                 const char *name;
3198                 size_t namelen;
3199                 const char *value;
3200                 const char *value_if_empty;
3201         } vars[] = {
3202 #define FORMAT_VAR(name, value, value_if_empty) \
3203         { name, STRING_SIZE(name), value, value_if_empty }
3204                 FORMAT_VAR("%(directory)",      opt_path,       ""),
3205                 FORMAT_VAR("%(file)",           opt_file,       ""),
3206                 FORMAT_VAR("%(ref)",            opt_ref,        "HEAD"),
3207                 FORMAT_VAR("%(head)",           ref_head,       ""),
3208                 FORMAT_VAR("%(commit)",         ref_commit,     ""),
3209                 FORMAT_VAR("%(blob)",           ref_blob,       ""),
3210                 FORMAT_VAR("%(branch)",         ref_branch,     ""),
3211         };
3212         int i;
3214         for (i = 0; i < ARRAY_SIZE(vars); i++)
3215                 if (!strncmp(name, vars[i].name, vars[i].namelen))
3216                         return *vars[i].value ? vars[i].value : vars[i].value_if_empty;
3218         report("Unknown replacement: `%s`", name);
3219         return NULL;
3222 static bool
3223 format_argv(const char ***dst_argv, const char *src_argv[], bool replace)
3225         char buf[SIZEOF_STR];
3226         int argc;
3228         argv_free(*dst_argv);
3230         for (argc = 0; src_argv[argc]; argc++) {
3231                 const char *arg = src_argv[argc];
3232                 size_t bufpos = 0;
3234                 if (!strcmp(arg, "%(fileargs)")) {
3235                         if (!argv_append_array(dst_argv, opt_file_args))
3236                                 break;
3237                         continue;
3239                 } else if (!strcmp(arg, "%(diffargs)")) {
3240                         if (!argv_append_array(dst_argv, opt_diff_args))
3241                                 break;
3242                         continue;
3244                 } else if (!strcmp(arg, "%(revargs)")) {
3245                         if (!argv_append_array(dst_argv, opt_rev_args))
3246                                 break;
3247                         continue;
3248                 }
3250                 while (arg) {
3251                         char *next = strstr(arg, "%(");
3252                         int len = next - arg;
3253                         const char *value;
3255                         if (!next || !replace) {
3256                                 len = strlen(arg);
3257                                 value = "";
3259                         } else {
3260                                 value = format_arg(next);
3262                                 if (!value) {
3263                                         return FALSE;
3264                                 }
3265                         }
3267                         if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
3268                                 return FALSE;
3270                         arg = next && replace ? strchr(next, ')') + 1 : NULL;
3271                 }
3273                 if (!argv_append(dst_argv, buf))
3274                         break;
3275         }
3277         return src_argv[argc] == NULL;
3280 static bool
3281 restore_view_position(struct view *view)
3283         if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
3284                 return FALSE;
3286         /* Changing the view position cancels the restoring. */
3287         /* FIXME: Changing back to the first line is not detected. */
3288         if (view->offset != 0 || view->lineno != 0) {
3289                 view->p_restore = FALSE;
3290                 return FALSE;
3291         }
3293         if (goto_view_line(view, view->p_offset, view->p_lineno) &&
3294             view_is_displayed(view))
3295                 werase(view->win);
3297         view->yoffset = view->p_yoffset;
3298         view->p_restore = FALSE;
3300         return TRUE;
3303 static void
3304 end_update(struct view *view, bool force)
3306         if (!view->pipe)
3307                 return;
3308         while (!view->ops->read(view, NULL))
3309                 if (!force)
3310                         return;
3311         if (force)
3312                 io_kill(view->pipe);
3313         io_done(view->pipe);
3314         view->pipe = NULL;
3317 static void
3318 setup_update(struct view *view, const char *vid)
3320         reset_view(view);
3321         string_copy_rev(view->vid, vid);
3322         view->pipe = &view->io;
3323         view->start_time = time(NULL);
3326 static bool
3327 prepare_io(struct view *view, const char *dir, const char *argv[], bool replace)
3329         view->dir = dir;
3330         return format_argv(&view->argv, argv, replace);
3333 static bool
3334 prepare_update(struct view *view, const char *argv[], const char *dir)
3336         if (view->pipe)
3337                 end_update(view, TRUE);
3338         return prepare_io(view, dir, argv, FALSE);
3341 static bool
3342 start_update(struct view *view, const char **argv, const char *dir)
3344         if (view->pipe)
3345                 io_done(view->pipe);
3346         return prepare_io(view, dir, argv, FALSE) &&
3347                io_run(&view->io, IO_RD, dir, view->argv);
3350 static bool
3351 prepare_update_file(struct view *view, const char *name)
3353         if (view->pipe)
3354                 end_update(view, TRUE);
3355         argv_free(view->argv);
3356         return io_open(&view->io, "%s/%s", opt_cdup[0] ? opt_cdup : ".", name);
3359 static bool
3360 begin_update(struct view *view, bool refresh)
3362         if (view->pipe)
3363                 end_update(view, TRUE);
3365         if (!refresh) {
3366                 if (view->ops->prepare) {
3367                         if (!view->ops->prepare(view))
3368                                 return FALSE;
3369                 } else if (!prepare_io(view, NULL, view->ops->argv, TRUE)) {
3370                         return FALSE;
3371                 }
3373                 /* Put the current ref_* value to the view title ref
3374                  * member. This is needed by the blob view. Most other
3375                  * views sets it automatically after loading because the
3376                  * first line is a commit line. */
3377                 string_copy_rev(view->ref, view->id);
3378         }
3380         if (view->argv && view->argv[0] &&
3381             !io_run(&view->io, IO_RD, view->dir, view->argv))
3382                 return FALSE;
3384         setup_update(view, view->id);
3386         return TRUE;
3389 static bool
3390 update_view(struct view *view)
3392         char out_buffer[BUFSIZ * 2];
3393         char *line;
3394         /* Clear the view and redraw everything since the tree sorting
3395          * might have rearranged things. */
3396         bool redraw = view->lines == 0;
3397         bool can_read = TRUE;
3399         if (!view->pipe)
3400                 return TRUE;
3402         if (!io_can_read(view->pipe)) {
3403                 if (view->lines == 0 && view_is_displayed(view)) {
3404                         time_t secs = time(NULL) - view->start_time;
3406                         if (secs > 1 && secs > view->update_secs) {
3407                                 if (view->update_secs == 0)
3408                                         redraw_view(view);
3409                                 update_view_title(view);
3410                                 view->update_secs = secs;
3411                         }
3412                 }
3413                 return TRUE;
3414         }
3416         for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
3417                 if (opt_iconv_in != ICONV_NONE) {
3418                         ICONV_CONST char *inbuf = line;
3419                         size_t inlen = strlen(line) + 1;
3421                         char *outbuf = out_buffer;
3422                         size_t outlen = sizeof(out_buffer);
3424                         size_t ret;
3426                         ret = iconv(opt_iconv_in, &inbuf, &inlen, &outbuf, &outlen);
3427                         if (ret != (size_t) -1)
3428                                 line = out_buffer;
3429                 }
3431                 if (!view->ops->read(view, line)) {
3432                         report("Allocation failure");
3433                         end_update(view, TRUE);
3434                         return FALSE;
3435                 }
3436         }
3438         {
3439                 unsigned long lines = view->lines;
3440                 int digits;
3442                 for (digits = 0; lines; digits++)
3443                         lines /= 10;
3445                 /* Keep the displayed view in sync with line number scaling. */
3446                 if (digits != view->digits) {
3447                         view->digits = digits;
3448                         if (opt_line_number || view->type == VIEW_BLAME)
3449                                 redraw = TRUE;
3450                 }
3451         }
3453         if (io_error(view->pipe)) {
3454                 report("Failed to read: %s", io_strerror(view->pipe));
3455                 end_update(view, TRUE);
3457         } else if (io_eof(view->pipe)) {
3458                 if (view_is_displayed(view))
3459                         report("");
3460                 end_update(view, FALSE);
3461         }
3463         if (restore_view_position(view))
3464                 redraw = TRUE;
3466         if (!view_is_displayed(view))
3467                 return TRUE;
3469         if (redraw)
3470                 redraw_view_from(view, 0);
3471         else
3472                 redraw_view_dirty(view);
3474         /* Update the title _after_ the redraw so that if the redraw picks up a
3475          * commit reference in view->ref it'll be available here. */
3476         update_view_title(view);
3477         return TRUE;
3480 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
3482 static struct line *
3483 add_line_data(struct view *view, void *data, enum line_type type)
3485         struct line *line;
3487         if (!realloc_lines(&view->line, view->lines, 1))
3488                 return NULL;
3490         line = &view->line[view->lines++];
3491         memset(line, 0, sizeof(*line));
3492         line->type = type;
3493         line->data = data;
3494         line->dirty = 1;
3496         return line;
3499 static struct line *
3500 add_line_text(struct view *view, const char *text, enum line_type type)
3502         char *data = text ? strdup(text) : NULL;
3504         return data ? add_line_data(view, data, type) : NULL;
3507 static struct line *
3508 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
3510         char buf[SIZEOF_STR];
3511         va_list args;
3513         va_start(args, fmt);
3514         if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
3515                 buf[0] = 0;
3516         va_end(args);
3518         return buf[0] ? add_line_text(view, buf, type) : NULL;
3521 /*
3522  * View opening
3523  */
3525 enum open_flags {
3526         OPEN_DEFAULT = 0,       /* Use default view switching. */
3527         OPEN_SPLIT = 1,         /* Split current view. */
3528         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
3529         OPEN_REFRESH = 16,      /* Refresh view using previous command. */
3530         OPEN_PREPARED = 32,     /* Open already prepared command. */
3531 };
3533 static void
3534 open_view(struct view *prev, enum request request, enum open_flags flags)
3536         bool split = !!(flags & OPEN_SPLIT);
3537         bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
3538         bool nomaximize = !!(flags & OPEN_REFRESH);
3539         struct view *view = VIEW(request);
3540         int nviews = displayed_views();
3541         struct view *base_view = display[0];
3543         if (view == prev && nviews == 1 && !reload) {
3544                 report("Already in %s view", view->name);
3545                 return;
3546         }
3548         if (view->git_dir && !opt_git_dir[0]) {
3549                 report("The %s view is disabled in pager view", view->name);
3550                 return;
3551         }
3553         if (split) {
3554                 display[1] = view;
3555                 current_view = 1;
3556                 view->parent = prev;
3557         } else if (!nomaximize) {
3558                 /* Maximize the current view. */
3559                 memset(display, 0, sizeof(display));
3560                 current_view = 0;
3561                 display[current_view] = view;
3562         }
3564         /* No prev signals that this is the first loaded view. */
3565         if (prev && view != prev) {
3566                 view->prev = prev;
3567         }
3569         /* Resize the view when switching between split- and full-screen,
3570          * or when switching between two different full-screen views. */
3571         if (nviews != displayed_views() ||
3572             (nviews == 1 && base_view != display[0]))
3573                 resize_display();
3575         if (view->ops->open) {
3576                 if (view->pipe)
3577                         end_update(view, TRUE);
3578                 if (!view->ops->open(view)) {
3579                         report("Failed to load %s view", view->name);
3580                         return;
3581                 }
3582                 restore_view_position(view);
3584         } else if ((reload || strcmp(view->vid, view->id)) &&
3585                    !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3586                 report("Failed to load %s view", view->name);
3587                 return;
3588         }
3590         if (split && prev->lineno - prev->offset >= prev->height) {
3591                 /* Take the title line into account. */
3592                 int lines = prev->lineno - prev->offset - prev->height + 1;
3594                 /* Scroll the view that was split if the current line is
3595                  * outside the new limited view. */
3596                 do_scroll_view(prev, lines);
3597         }
3599         if (prev && view != prev && split && view_is_displayed(prev)) {
3600                 /* "Blur" the previous view. */
3601                 update_view_title(prev);
3602         }
3604         if (view->pipe && view->lines == 0) {
3605                 /* Clear the old view and let the incremental updating refill
3606                  * the screen. */
3607                 werase(view->win);
3608                 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3609                 report("");
3610         } else if (view_is_displayed(view)) {
3611                 redraw_view(view);
3612                 report("");
3613         }
3616 static void
3617 open_external_viewer(const char *argv[], const char *dir)
3619         def_prog_mode();           /* save current tty modes */
3620         endwin();                  /* restore original tty modes */
3621         io_run_fg(argv, dir);
3622         fprintf(stderr, "Press Enter to continue");
3623         getc(opt_tty);
3624         reset_prog_mode();
3625         redraw_display(TRUE);
3628 static void
3629 open_mergetool(const char *file)
3631         const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3633         open_external_viewer(mergetool_argv, opt_cdup);
3636 static void
3637 open_editor(const char *file)
3639         const char *editor_argv[] = { "vi", file, NULL };
3640         const char *editor;
3642         editor = getenv("GIT_EDITOR");
3643         if (!editor && *opt_editor)
3644                 editor = opt_editor;
3645         if (!editor)
3646                 editor = getenv("VISUAL");
3647         if (!editor)
3648                 editor = getenv("EDITOR");
3649         if (!editor)
3650                 editor = "vi";
3652         editor_argv[0] = editor;
3653         open_external_viewer(editor_argv, opt_cdup);
3656 static void
3657 open_run_request(enum request request)
3659         struct run_request *req = get_run_request(request);
3660         const char **argv = NULL;
3662         if (!req) {
3663                 report("Unknown run request");
3664                 return;
3665         }
3667         if (format_argv(&argv, req->argv, TRUE))
3668                 open_external_viewer(argv, NULL);
3669         if (argv)
3670                 argv_free(argv);
3671         free(argv);
3674 /*
3675  * User request switch noodle
3676  */
3678 static int
3679 view_driver(struct view *view, enum request request)
3681         int i;
3683         if (request == REQ_NONE)
3684                 return TRUE;
3686         if (request > REQ_NONE) {
3687                 open_run_request(request);
3688                 view_request(view, REQ_REFRESH);
3689                 return TRUE;
3690         }
3692         request = view_request(view, request);
3693         if (request == REQ_NONE)
3694                 return TRUE;
3696         switch (request) {
3697         case REQ_MOVE_UP:
3698         case REQ_MOVE_DOWN:
3699         case REQ_MOVE_PAGE_UP:
3700         case REQ_MOVE_PAGE_DOWN:
3701         case REQ_MOVE_FIRST_LINE:
3702         case REQ_MOVE_LAST_LINE:
3703                 move_view(view, request);
3704                 break;
3706         case REQ_SCROLL_FIRST_COL:
3707         case REQ_SCROLL_LEFT:
3708         case REQ_SCROLL_RIGHT:
3709         case REQ_SCROLL_LINE_DOWN:
3710         case REQ_SCROLL_LINE_UP:
3711         case REQ_SCROLL_PAGE_DOWN:
3712         case REQ_SCROLL_PAGE_UP:
3713                 scroll_view(view, request);
3714                 break;
3716         case REQ_VIEW_BLAME:
3717                 if (!opt_file[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_BLOB:
3726                 if (!ref_blob[0]) {
3727                         report("No file chosen, press %s to open tree view",
3728                                get_key(view->keymap, REQ_VIEW_TREE));
3729                         break;
3730                 }
3731                 open_view(view, request, OPEN_DEFAULT);
3732                 break;
3734         case REQ_VIEW_PAGER:
3735                 if (view == NULL) {
3736                         if (!io_open(&VIEW(REQ_VIEW_PAGER)->io, ""))
3737                                 die("Failed to open stdin");
3738                         open_view(view, request, OPEN_PREPARED);
3739                         break;
3740                 }
3742                 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3743                         report("No pager content, press %s to run command from prompt",
3744                                get_key(view->keymap, REQ_PROMPT));
3745                         break;
3746                 }
3747                 open_view(view, request, OPEN_DEFAULT);
3748                 break;
3750         case REQ_VIEW_STAGE:
3751                 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3752                         report("No stage content, press %s to open the status view and choose file",
3753                                get_key(view->keymap, REQ_VIEW_STATUS));
3754                         break;
3755                 }
3756                 open_view(view, request, OPEN_DEFAULT);
3757                 break;
3759         case REQ_VIEW_STATUS:
3760                 if (opt_is_inside_work_tree == FALSE) {
3761                         report("The status view requires a working tree");
3762                         break;
3763                 }
3764                 open_view(view, request, OPEN_DEFAULT);
3765                 break;
3767         case REQ_VIEW_MAIN:
3768         case REQ_VIEW_DIFF:
3769         case REQ_VIEW_LOG:
3770         case REQ_VIEW_TREE:
3771         case REQ_VIEW_HELP:
3772         case REQ_VIEW_BRANCH:
3773                 open_view(view, request, OPEN_DEFAULT);
3774                 break;
3776         case REQ_NEXT:
3777         case REQ_PREVIOUS:
3778                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3780                 if (view->parent) {
3781                         int line;
3783                         view = view->parent;
3784                         line = view->lineno;
3785                         move_view(view, request);
3786                         if (view_is_displayed(view))
3787                                 update_view_title(view);
3788                         if (line != view->lineno)
3789                                 view_request(view, REQ_ENTER);
3790                 } else {
3791                         move_view(view, request);
3792                 }
3793                 break;
3795         case REQ_VIEW_NEXT:
3796         {
3797                 int nviews = displayed_views();
3798                 int next_view = (current_view + 1) % nviews;
3800                 if (next_view == current_view) {
3801                         report("Only one view is displayed");
3802                         break;
3803                 }
3805                 current_view = next_view;
3806                 /* Blur out the title of the previous view. */
3807                 update_view_title(view);
3808                 report("");
3809                 break;
3810         }
3811         case REQ_REFRESH:
3812                 report("Refreshing is not yet supported for the %s view", view->name);
3813                 break;
3815         case REQ_MAXIMIZE:
3816                 if (displayed_views() == 2)
3817                         maximize_view(view);
3818                 break;
3820         case REQ_OPTIONS:
3821                 open_option_menu();
3822                 break;
3824         case REQ_TOGGLE_LINENO:
3825                 toggle_view_option(&opt_line_number, "line numbers");
3826                 break;
3828         case REQ_TOGGLE_DATE:
3829                 toggle_date();
3830                 break;
3832         case REQ_TOGGLE_AUTHOR:
3833                 toggle_author();
3834                 break;
3836         case REQ_TOGGLE_REV_GRAPH:
3837                 toggle_view_option(&opt_rev_graph, "revision graph display");
3838                 break;
3840         case REQ_TOGGLE_REFS:
3841                 toggle_view_option(&opt_show_refs, "reference display");
3842                 break;
3844         case REQ_TOGGLE_SORT_FIELD:
3845         case REQ_TOGGLE_SORT_ORDER:
3846                 report("Sorting is not yet supported for the %s view", view->name);
3847                 break;
3849         case REQ_SEARCH:
3850         case REQ_SEARCH_BACK:
3851                 search_view(view, request);
3852                 break;
3854         case REQ_FIND_NEXT:
3855         case REQ_FIND_PREV:
3856                 find_next(view, request);
3857                 break;
3859         case REQ_STOP_LOADING:
3860                 foreach_view(view, i) {
3861                         if (view->pipe)
3862                                 report("Stopped loading the %s view", view->name),
3863                         end_update(view, TRUE);
3864                 }
3865                 break;
3867         case REQ_SHOW_VERSION:
3868                 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3869                 return TRUE;
3871         case REQ_SCREEN_REDRAW:
3872                 redraw_display(TRUE);
3873                 break;
3875         case REQ_EDIT:
3876                 report("Nothing to edit");
3877                 break;
3879         case REQ_ENTER:
3880                 report("Nothing to enter");
3881                 break;
3883         case REQ_VIEW_CLOSE:
3884                 /* XXX: Mark closed views by letting view->prev point to the
3885                  * view itself. Parents to closed view should never be
3886                  * followed. */
3887                 if (view->prev && view->prev != view) {
3888                         maximize_view(view->prev);
3889                         view->prev = view;
3890                         break;
3891                 }
3892                 /* Fall-through */
3893         case REQ_QUIT:
3894                 return FALSE;
3896         default:
3897                 report("Unknown key, press %s for help",
3898                        get_key(view->keymap, REQ_VIEW_HELP));
3899                 return TRUE;
3900         }
3902         return TRUE;
3906 /*
3907  * View backend utilities
3908  */
3910 enum sort_field {
3911         ORDERBY_NAME,
3912         ORDERBY_DATE,
3913         ORDERBY_AUTHOR,
3914 };
3916 struct sort_state {
3917         const enum sort_field *fields;
3918         size_t size, current;
3919         bool reverse;
3920 };
3922 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3923 #define get_sort_field(state) ((state).fields[(state).current])
3924 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3926 static void
3927 sort_view(struct view *view, enum request request, struct sort_state *state,
3928           int (*compare)(const void *, const void *))
3930         switch (request) {
3931         case REQ_TOGGLE_SORT_FIELD:
3932                 state->current = (state->current + 1) % state->size;
3933                 break;
3935         case REQ_TOGGLE_SORT_ORDER:
3936                 state->reverse = !state->reverse;
3937                 break;
3938         default:
3939                 die("Not a sort request");
3940         }
3942         qsort(view->line, view->lines, sizeof(*view->line), compare);
3943         redraw_view(view);
3946 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3948 /* Small author cache to reduce memory consumption. It uses binary
3949  * search to lookup or find place to position new entries. No entries
3950  * are ever freed. */
3951 static const char *
3952 get_author(const char *name)
3954         static const char **authors;
3955         static size_t authors_size;
3956         int from = 0, to = authors_size - 1;
3958         while (from <= to) {
3959                 size_t pos = (to + from) / 2;
3960                 int cmp = strcmp(name, authors[pos]);
3962                 if (!cmp)
3963                         return authors[pos];
3965                 if (cmp < 0)
3966                         to = pos - 1;
3967                 else
3968                         from = pos + 1;
3969         }
3971         if (!realloc_authors(&authors, authors_size, 1))
3972                 return NULL;
3973         name = strdup(name);
3974         if (!name)
3975                 return NULL;
3977         memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3978         authors[from] = name;
3979         authors_size++;
3981         return name;
3984 static void
3985 parse_timesec(struct time *time, const char *sec)
3987         time->sec = (time_t) atol(sec);
3990 static void
3991 parse_timezone(struct time *time, const char *zone)
3993         long tz;
3995         tz  = ('0' - zone[1]) * 60 * 60 * 10;
3996         tz += ('0' - zone[2]) * 60 * 60;
3997         tz += ('0' - zone[3]) * 60 * 10;
3998         tz += ('0' - zone[4]) * 60;
4000         if (zone[0] == '-')
4001                 tz = -tz;
4003         time->tz = tz;
4004         time->sec -= tz;
4007 /* Parse author lines where the name may be empty:
4008  *      author  <email@address.tld> 1138474660 +0100
4009  */
4010 static void
4011 parse_author_line(char *ident, const char **author, struct time *time)
4013         char *nameend = strchr(ident, '<');
4014         char *emailend = strchr(ident, '>');
4016         if (nameend && emailend)
4017                 *nameend = *emailend = 0;
4018         ident = chomp_string(ident);
4019         if (!*ident) {
4020                 if (nameend)
4021                         ident = chomp_string(nameend + 1);
4022                 if (!*ident)
4023                         ident = "Unknown";
4024         }
4026         *author = get_author(ident);
4028         /* Parse epoch and timezone */
4029         if (emailend && emailend[1] == ' ') {
4030                 char *secs = emailend + 2;
4031                 char *zone = strchr(secs, ' ');
4033                 parse_timesec(time, secs);
4035                 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
4036                         parse_timezone(time, zone + 1);
4037         }
4040 /*
4041  * Pager backend
4042  */
4044 static bool
4045 pager_draw(struct view *view, struct line *line, unsigned int lineno)
4047         if (opt_line_number && draw_lineno(view, lineno))
4048                 return TRUE;
4050         draw_text(view, line->type, line->data, TRUE);
4051         return TRUE;
4054 static bool
4055 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
4057         const char *describe_argv[] = { "git", "describe", commit_id, NULL };
4058         char ref[SIZEOF_STR];
4060         if (!io_run_buf(describe_argv, ref, sizeof(ref)) || !*ref)
4061                 return TRUE;
4063         /* This is the only fatal call, since it can "corrupt" the buffer. */
4064         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
4065                 return FALSE;
4067         return TRUE;
4070 static void
4071 add_pager_refs(struct view *view, struct line *line)
4073         char buf[SIZEOF_STR];
4074         char *commit_id = (char *)line->data + STRING_SIZE("commit ");
4075         struct ref_list *list;
4076         size_t bufpos = 0, i;
4077         const char *sep = "Refs: ";
4078         bool is_tag = FALSE;
4080         assert(line->type == LINE_COMMIT);
4082         list = get_ref_list(commit_id);
4083         if (!list) {
4084                 if (view->type == VIEW_DIFF)
4085                         goto try_add_describe_ref;
4086                 return;
4087         }
4089         for (i = 0; i < list->size; i++) {
4090                 struct ref *ref = list->refs[i];
4091                 const char *fmt = ref->tag    ? "%s[%s]" :
4092                                   ref->remote ? "%s<%s>" : "%s%s";
4094                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
4095                         return;
4096                 sep = ", ";
4097                 if (ref->tag)
4098                         is_tag = TRUE;
4099         }
4101         if (!is_tag && view->type == VIEW_DIFF) {
4102 try_add_describe_ref:
4103                 /* Add <tag>-g<commit_id> "fake" reference. */
4104                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
4105                         return;
4106         }
4108         if (bufpos == 0)
4109                 return;
4111         add_line_text(view, buf, LINE_PP_REFS);
4114 static bool
4115 pager_read(struct view *view, char *data)
4117         struct line *line;
4119         if (!data)
4120                 return TRUE;
4122         line = add_line_text(view, data, get_line_type(data));
4123         if (!line)
4124                 return FALSE;
4126         if (line->type == LINE_COMMIT &&
4127             (view->type == VIEW_DIFF ||
4128              view->type == VIEW_LOG))
4129                 add_pager_refs(view, line);
4131         return TRUE;
4134 static enum request
4135 pager_request(struct view *view, enum request request, struct line *line)
4137         int split = 0;
4139         if (request != REQ_ENTER)
4140                 return request;
4142         if (line->type == LINE_COMMIT &&
4143            (view->type == VIEW_LOG ||
4144             view->type == VIEW_PAGER)) {
4145                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
4146                 split = 1;
4147         }
4149         /* Always scroll the view even if it was split. That way
4150          * you can use Enter to scroll through the log view and
4151          * split open each commit diff. */
4152         scroll_view(view, REQ_SCROLL_LINE_DOWN);
4154         /* FIXME: A minor workaround. Scrolling the view will call report("")
4155          * but if we are scrolling a non-current view this won't properly
4156          * update the view title. */
4157         if (split)
4158                 update_view_title(view);
4160         return REQ_NONE;
4163 static bool
4164 pager_grep(struct view *view, struct line *line)
4166         const char *text[] = { line->data, NULL };
4168         return grep_text(view, text);
4171 static void
4172 pager_select(struct view *view, struct line *line)
4174         if (line->type == LINE_COMMIT) {
4175                 char *text = (char *)line->data + STRING_SIZE("commit ");
4177                 if (view->type != VIEW_PAGER)
4178                         string_copy_rev(view->ref, text);
4179                 string_copy_rev(ref_commit, text);
4180         }
4183 static struct view_ops pager_ops = {
4184         "line",
4185         NULL,
4186         NULL,
4187         pager_read,
4188         pager_draw,
4189         pager_request,
4190         pager_grep,
4191         pager_select,
4192 };
4194 static const char *log_argv[SIZEOF_ARG] = {
4195         "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
4196 };
4198 static enum request
4199 log_request(struct view *view, enum request request, struct line *line)
4201         switch (request) {
4202         case REQ_REFRESH:
4203                 load_refs();
4204                 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
4205                 return REQ_NONE;
4206         default:
4207                 return pager_request(view, request, line);
4208         }
4211 static struct view_ops log_ops = {
4212         "line",
4213         log_argv,
4214         NULL,
4215         pager_read,
4216         pager_draw,
4217         log_request,
4218         pager_grep,
4219         pager_select,
4220 };
4222 static const char *diff_argv[SIZEOF_ARG] = {
4223         "git", "show", "--pretty=fuller", "--no-color", "--root",
4224                 "--patch-with-stat", "--find-copies-harder", "-C",
4225                 "%(diffargs)", "%(commit)", "--", "%(fileargs)", NULL
4226 };
4228 static bool
4229 diff_read(struct view *view, char *data)
4231         if (!data) {
4232                 /* Fall back to retry if no diff will be shown. */
4233                 if (view->lines == 0 && opt_file_args) {
4234                         int pos = argv_size(view->argv)
4235                                 - argv_size(opt_file_args) - 1;
4237                         if (pos > 0 && !strcmp(view->argv[pos], "--")) {
4238                                 for (; view->argv[pos]; pos++) {
4239                                         free((void *) view->argv[pos]);
4240                                         view->argv[pos] = NULL;
4241                                 }
4243                                 if (view->pipe)
4244                                         io_done(view->pipe);
4245                                 if (io_run(&view->io, IO_RD, view->dir, view->argv))
4246                                         return FALSE;
4247                         }
4248                 }
4249                 return TRUE;
4250         }
4252         return pager_read(view, data);
4255 static struct view_ops diff_ops = {
4256         "line",
4257         diff_argv,
4258         NULL,
4259         diff_read,
4260         pager_draw,
4261         pager_request,
4262         pager_grep,
4263         pager_select,
4264 };
4266 /*
4267  * Help backend
4268  */
4270 static bool help_keymap_hidden[ARRAY_SIZE(keymap_table)];
4272 static bool
4273 help_open_keymap_title(struct view *view, enum keymap keymap)
4275         struct line *line;
4277         line = add_line_format(view, LINE_HELP_KEYMAP, "[%c] %s bindings",
4278                                help_keymap_hidden[keymap] ? '+' : '-',
4279                                enum_name(keymap_table[keymap]));
4280         if (line)
4281                 line->other = keymap;
4283         return help_keymap_hidden[keymap];
4286 static void
4287 help_open_keymap(struct view *view, enum keymap keymap)
4289         const char *group = NULL;
4290         char buf[SIZEOF_STR];
4291         size_t bufpos;
4292         bool add_title = TRUE;
4293         int i;
4295         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
4296                 const char *key = NULL;
4298                 if (req_info[i].request == REQ_NONE)
4299                         continue;
4301                 if (!req_info[i].request) {
4302                         group = req_info[i].help;
4303                         continue;
4304                 }
4306                 key = get_keys(keymap, req_info[i].request, TRUE);
4307                 if (!key || !*key)
4308                         continue;
4310                 if (add_title && help_open_keymap_title(view, keymap))
4311                         return;
4312                 add_title = FALSE;
4314                 if (group) {
4315                         add_line_text(view, group, LINE_HELP_GROUP);
4316                         group = NULL;
4317                 }
4319                 add_line_format(view, LINE_DEFAULT, "    %-25s %-20s %s", key,
4320                                 enum_name(req_info[i]), req_info[i].help);
4321         }
4323         group = "External commands:";
4325         for (i = 0; i < run_requests; i++) {
4326                 struct run_request *req = get_run_request(REQ_NONE + i + 1);
4327                 const char *key;
4328                 int argc;
4330                 if (!req || req->keymap != keymap)
4331                         continue;
4333                 key = get_key_name(req->key);
4334                 if (!*key)
4335                         key = "(no key defined)";
4337                 if (add_title && help_open_keymap_title(view, keymap))
4338                         return;
4339                 if (group) {
4340                         add_line_text(view, group, LINE_HELP_GROUP);
4341                         group = NULL;
4342                 }
4344                 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
4345                         if (!string_format_from(buf, &bufpos, "%s%s",
4346                                                 argc ? " " : "", req->argv[argc]))
4347                                 return;
4349                 add_line_format(view, LINE_DEFAULT, "    %-25s `%s`", key, buf);
4350         }
4353 static bool
4354 help_open(struct view *view)
4356         enum keymap keymap;
4358         reset_view(view);
4359         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
4360         add_line_text(view, "", LINE_DEFAULT);
4362         for (keymap = 0; keymap < ARRAY_SIZE(keymap_table); keymap++)
4363                 help_open_keymap(view, keymap);
4365         return TRUE;
4368 static enum request
4369 help_request(struct view *view, enum request request, struct line *line)
4371         switch (request) {
4372         case REQ_ENTER:
4373                 if (line->type == LINE_HELP_KEYMAP) {
4374                         help_keymap_hidden[line->other] =
4375                                 !help_keymap_hidden[line->other];
4376                         view->p_restore = TRUE;
4377                         open_view(view, REQ_VIEW_HELP, OPEN_REFRESH);
4378                 }
4380                 return REQ_NONE;
4381         default:
4382                 return pager_request(view, request, line);
4383         }
4386 static struct view_ops help_ops = {
4387         "line",
4388         NULL,
4389         help_open,
4390         NULL,
4391         pager_draw,
4392         help_request,
4393         pager_grep,
4394         pager_select,
4395 };
4398 /*
4399  * Tree backend
4400  */
4402 struct tree_stack_entry {
4403         struct tree_stack_entry *prev;  /* Entry below this in the stack */
4404         unsigned long lineno;           /* Line number to restore */
4405         char *name;                     /* Position of name in opt_path */
4406 };
4408 /* The top of the path stack. */
4409 static struct tree_stack_entry *tree_stack = NULL;
4410 unsigned long tree_lineno = 0;
4412 static void
4413 pop_tree_stack_entry(void)
4415         struct tree_stack_entry *entry = tree_stack;
4417         tree_lineno = entry->lineno;
4418         entry->name[0] = 0;
4419         tree_stack = entry->prev;
4420         free(entry);
4423 static void
4424 push_tree_stack_entry(const char *name, unsigned long lineno)
4426         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
4427         size_t pathlen = strlen(opt_path);
4429         if (!entry)
4430                 return;
4432         entry->prev = tree_stack;
4433         entry->name = opt_path + pathlen;
4434         tree_stack = entry;
4436         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
4437                 pop_tree_stack_entry();
4438                 return;
4439         }
4441         /* Move the current line to the first tree entry. */
4442         tree_lineno = 1;
4443         entry->lineno = lineno;
4446 /* Parse output from git-ls-tree(1):
4447  *
4448  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4449  */
4451 #define SIZEOF_TREE_ATTR \
4452         STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4454 #define SIZEOF_TREE_MODE \
4455         STRING_SIZE("100644 ")
4457 #define TREE_ID_OFFSET \
4458         STRING_SIZE("100644 blob ")
4460 struct tree_entry {
4461         char id[SIZEOF_REV];
4462         mode_t mode;
4463         struct time time;               /* Date from the author ident. */
4464         const char *author;             /* Author of the commit. */
4465         char name[1];
4466 };
4468 static const char *
4469 tree_path(const struct line *line)
4471         return ((struct tree_entry *) line->data)->name;
4474 static int
4475 tree_compare_entry(const struct line *line1, const struct line *line2)
4477         if (line1->type != line2->type)
4478                 return line1->type == LINE_TREE_DIR ? -1 : 1;
4479         return strcmp(tree_path(line1), tree_path(line2));
4482 static const enum sort_field tree_sort_fields[] = {
4483         ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4484 };
4485 static struct sort_state tree_sort_state = SORT_STATE(tree_sort_fields);
4487 static int
4488 tree_compare(const void *l1, const void *l2)
4490         const struct line *line1 = (const struct line *) l1;
4491         const struct line *line2 = (const struct line *) l2;
4492         const struct tree_entry *entry1 = ((const struct line *) l1)->data;
4493         const struct tree_entry *entry2 = ((const struct line *) l2)->data;
4495         if (line1->type == LINE_TREE_HEAD)
4496                 return -1;
4497         if (line2->type == LINE_TREE_HEAD)
4498                 return 1;
4500         switch (get_sort_field(tree_sort_state)) {
4501         case ORDERBY_DATE:
4502                 return sort_order(tree_sort_state, timecmp(&entry1->time, &entry2->time));
4504         case ORDERBY_AUTHOR:
4505                 return sort_order(tree_sort_state, strcmp(entry1->author, entry2->author));
4507         case ORDERBY_NAME:
4508         default:
4509                 return sort_order(tree_sort_state, tree_compare_entry(line1, line2));
4510         }
4514 static struct line *
4515 tree_entry(struct view *view, enum line_type type, const char *path,
4516            const char *mode, const char *id)
4518         struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
4519         struct line *line = entry ? add_line_data(view, entry, type) : NULL;
4521         if (!entry || !line) {
4522                 free(entry);
4523                 return NULL;
4524         }
4526         strncpy(entry->name, path, strlen(path));
4527         if (mode)
4528                 entry->mode = strtoul(mode, NULL, 8);
4529         if (id)
4530                 string_copy_rev(entry->id, id);
4532         return line;
4535 static bool
4536 tree_read_date(struct view *view, char *text, bool *read_date)
4538         static const char *author_name;
4539         static struct time author_time;
4541         if (!text && *read_date) {
4542                 *read_date = FALSE;
4543                 return TRUE;
4545         } else if (!text) {
4546                 char *path = *opt_path ? opt_path : ".";
4547                 /* Find next entry to process */
4548                 const char *log_file[] = {
4549                         "git", "log", "--no-color", "--pretty=raw",
4550                                 "--cc", "--raw", view->id, "--", path, NULL
4551                 };
4553                 if (!view->lines) {
4554                         tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
4555                         report("Tree is empty");
4556                         return TRUE;
4557                 }
4559                 if (!start_update(view, log_file, opt_cdup)) {
4560                         report("Failed to load tree data");
4561                         return TRUE;
4562                 }
4564                 *read_date = TRUE;
4565                 return FALSE;
4567         } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
4568                 parse_author_line(text + STRING_SIZE("author "),
4569                                   &author_name, &author_time);
4571         } else if (*text == ':') {
4572                 char *pos;
4573                 size_t annotated = 1;
4574                 size_t i;
4576                 pos = strchr(text, '\t');
4577                 if (!pos)
4578                         return TRUE;
4579                 text = pos + 1;
4580                 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
4581                         text += strlen(opt_path);
4582                 pos = strchr(text, '/');
4583                 if (pos)
4584                         *pos = 0;
4586                 for (i = 1; i < view->lines; i++) {
4587                         struct line *line = &view->line[i];
4588                         struct tree_entry *entry = line->data;
4590                         annotated += !!entry->author;
4591                         if (entry->author || strcmp(entry->name, text))
4592                                 continue;
4594                         entry->author = author_name;
4595                         entry->time = author_time;
4596                         line->dirty = 1;
4597                         break;
4598                 }
4600                 if (annotated == view->lines)
4601                         io_kill(view->pipe);
4602         }
4603         return TRUE;
4606 static bool
4607 tree_read(struct view *view, char *text)
4609         static bool read_date = FALSE;
4610         struct tree_entry *data;
4611         struct line *entry, *line;
4612         enum line_type type;
4613         size_t textlen = text ? strlen(text) : 0;
4614         char *path = text + SIZEOF_TREE_ATTR;
4616         if (read_date || !text)
4617                 return tree_read_date(view, text, &read_date);
4619         if (textlen <= SIZEOF_TREE_ATTR)
4620                 return FALSE;
4621         if (view->lines == 0 &&
4622             !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
4623                 return FALSE;
4625         /* Strip the path part ... */
4626         if (*opt_path) {
4627                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
4628                 size_t striplen = strlen(opt_path);
4630                 if (pathlen > striplen)
4631                         memmove(path, path + striplen,
4632                                 pathlen - striplen + 1);
4634                 /* Insert "link" to parent directory. */
4635                 if (view->lines == 1 &&
4636                     !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
4637                         return FALSE;
4638         }
4640         type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
4641         entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
4642         if (!entry)
4643                 return FALSE;
4644         data = entry->data;
4646         /* Skip "Directory ..." and ".." line. */
4647         for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
4648                 if (tree_compare_entry(line, entry) <= 0)
4649                         continue;
4651                 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4653                 line->data = data;
4654                 line->type = type;
4655                 for (; line <= entry; line++)
4656                         line->dirty = line->cleareol = 1;
4657                 return TRUE;
4658         }
4660         if (tree_lineno > view->lineno) {
4661                 view->lineno = tree_lineno;
4662                 tree_lineno = 0;
4663         }
4665         return TRUE;
4668 static bool
4669 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4671         struct tree_entry *entry = line->data;
4673         if (line->type == LINE_TREE_HEAD) {
4674                 if (draw_text(view, line->type, "Directory path /", TRUE))
4675                         return TRUE;
4676         } else {
4677                 if (draw_mode(view, entry->mode))
4678                         return TRUE;
4680                 if (opt_author && draw_author(view, entry->author))
4681                         return TRUE;
4683                 if (opt_date && draw_date(view, &entry->time))
4684                         return TRUE;
4685         }
4686         if (draw_text(view, line->type, entry->name, TRUE))
4687                 return TRUE;
4688         return TRUE;
4691 static void
4692 open_blob_editor(const char *id)
4694         const char *blob_argv[] = { "git", "cat-file", "blob", id, NULL };
4695         char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4696         int fd = mkstemp(file);
4698         if (fd == -1)
4699                 report("Failed to create temporary file");
4700         else if (!io_run_append(blob_argv, fd))
4701                 report("Failed to save blob data to file");
4702         else
4703                 open_editor(file);
4704         if (fd != -1)
4705                 unlink(file);
4708 static enum request
4709 tree_request(struct view *view, enum request request, struct line *line)
4711         enum open_flags flags;
4712         struct tree_entry *entry = line->data;
4714         switch (request) {
4715         case REQ_VIEW_BLAME:
4716                 if (line->type != LINE_TREE_FILE) {
4717                         report("Blame only supported for files");
4718                         return REQ_NONE;
4719                 }
4721                 string_copy(opt_ref, view->vid);
4722                 return request;
4724         case REQ_EDIT:
4725                 if (line->type != LINE_TREE_FILE) {
4726                         report("Edit only supported for files");
4727                 } else if (!is_head_commit(view->vid)) {
4728                         open_blob_editor(entry->id);
4729                 } else {
4730                         open_editor(opt_file);
4731                 }
4732                 return REQ_NONE;
4734         case REQ_TOGGLE_SORT_FIELD:
4735         case REQ_TOGGLE_SORT_ORDER:
4736                 sort_view(view, request, &tree_sort_state, tree_compare);
4737                 return REQ_NONE;
4739         case REQ_PARENT:
4740                 if (!*opt_path) {
4741                         /* quit view if at top of tree */
4742                         return REQ_VIEW_CLOSE;
4743                 }
4744                 /* fake 'cd  ..' */
4745                 line = &view->line[1];
4746                 break;
4748         case REQ_ENTER:
4749                 break;
4751         default:
4752                 return request;
4753         }
4755         /* Cleanup the stack if the tree view is at a different tree. */
4756         while (!*opt_path && tree_stack)
4757                 pop_tree_stack_entry();
4759         switch (line->type) {
4760         case LINE_TREE_DIR:
4761                 /* Depending on whether it is a subdirectory or parent link
4762                  * mangle the path buffer. */
4763                 if (line == &view->line[1] && *opt_path) {
4764                         pop_tree_stack_entry();
4766                 } else {
4767                         const char *basename = tree_path(line);
4769                         push_tree_stack_entry(basename, view->lineno);
4770                 }
4772                 /* Trees and subtrees share the same ID, so they are not not
4773                  * unique like blobs. */
4774                 flags = OPEN_RELOAD;
4775                 request = REQ_VIEW_TREE;
4776                 break;
4778         case LINE_TREE_FILE:
4779                 flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
4780                 request = REQ_VIEW_BLOB;
4781                 break;
4783         default:
4784                 return REQ_NONE;
4785         }
4787         open_view(view, request, flags);
4788         if (request == REQ_VIEW_TREE)
4789                 view->lineno = tree_lineno;
4791         return REQ_NONE;
4794 static bool
4795 tree_grep(struct view *view, struct line *line)
4797         struct tree_entry *entry = line->data;
4798         const char *text[] = {
4799                 entry->name,
4800                 opt_author ? entry->author : "",
4801                 mkdate(&entry->time, opt_date),
4802                 NULL
4803         };
4805         return grep_text(view, text);
4808 static void
4809 tree_select(struct view *view, struct line *line)
4811         struct tree_entry *entry = line->data;
4813         if (line->type == LINE_TREE_FILE) {
4814                 string_copy_rev(ref_blob, entry->id);
4815                 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4817         } else if (line->type != LINE_TREE_DIR) {
4818                 return;
4819         }
4821         string_copy_rev(view->ref, entry->id);
4824 static bool
4825 tree_prepare(struct view *view)
4827         if (view->lines == 0 && opt_prefix[0]) {
4828                 char *pos = opt_prefix;
4830                 while (pos && *pos) {
4831                         char *end = strchr(pos, '/');
4833                         if (end)
4834                                 *end = 0;
4835                         push_tree_stack_entry(pos, 0);
4836                         pos = end;
4837                         if (end) {
4838                                 *end = '/';
4839                                 pos++;
4840                         }
4841                 }
4843         } else if (strcmp(view->vid, view->id)) {
4844                 opt_path[0] = 0;
4845         }
4847         return prepare_io(view, opt_cdup, view->ops->argv, TRUE);
4850 static const char *tree_argv[SIZEOF_ARG] = {
4851         "git", "ls-tree", "%(commit)", "%(directory)", NULL
4852 };
4854 static struct view_ops tree_ops = {
4855         "file",
4856         tree_argv,
4857         NULL,
4858         tree_read,
4859         tree_draw,
4860         tree_request,
4861         tree_grep,
4862         tree_select,
4863         tree_prepare,
4864 };
4866 static bool
4867 blob_read(struct view *view, char *line)
4869         if (!line)
4870                 return TRUE;
4871         return add_line_text(view, line, LINE_DEFAULT) != NULL;
4874 static enum request
4875 blob_request(struct view *view, enum request request, struct line *line)
4877         switch (request) {
4878         case REQ_EDIT:
4879                 open_blob_editor(view->vid);
4880                 return REQ_NONE;
4881         default:
4882                 return pager_request(view, request, line);
4883         }
4886 static const char *blob_argv[SIZEOF_ARG] = {
4887         "git", "cat-file", "blob", "%(blob)", NULL
4888 };
4890 static struct view_ops blob_ops = {
4891         "line",
4892         blob_argv,
4893         NULL,
4894         blob_read,
4895         pager_draw,
4896         blob_request,
4897         pager_grep,
4898         pager_select,
4899 };
4901 /*
4902  * Blame backend
4903  *
4904  * Loading the blame view is a two phase job:
4905  *
4906  *  1. File content is read either using opt_file from the
4907  *     filesystem or using git-cat-file.
4908  *  2. Then blame information is incrementally added by
4909  *     reading output from git-blame.
4910  */
4912 struct blame_commit {
4913         char id[SIZEOF_REV];            /* SHA1 ID. */
4914         char title[128];                /* First line of the commit message. */
4915         const char *author;             /* Author of the commit. */
4916         struct time time;               /* Date from the author ident. */
4917         char filename[128];             /* Name of file. */
4918         char parent_id[SIZEOF_REV];     /* Parent/previous SHA1 ID. */
4919         char parent_filename[128];      /* Parent/previous name of file. */
4920 };
4922 struct blame {
4923         struct blame_commit *commit;
4924         unsigned long lineno;
4925         char text[1];
4926 };
4928 static bool
4929 blame_open(struct view *view)
4931         char path[SIZEOF_STR];
4932         size_t i;
4934         if (!view->prev && *opt_prefix) {
4935                 string_copy(path, opt_file);
4936                 if (!string_format(opt_file, "%s%s", opt_prefix, path))
4937                         return FALSE;
4938         }
4940         if (*opt_ref || !io_open(&view->io, "%s%s", opt_cdup, opt_file)) {
4941                 const char *blame_cat_file_argv[] = {
4942                         "git", "cat-file", "blob", path, NULL
4943                 };
4945                 if (!string_format(path, "%s:%s", opt_ref, opt_file) ||
4946                     !start_update(view, blame_cat_file_argv, opt_cdup))
4947                         return FALSE;
4948         }
4950         /* First pass: remove multiple references to the same commit. */
4951         for (i = 0; i < view->lines; i++) {
4952                 struct blame *blame = view->line[i].data;
4954                 if (blame->commit && blame->commit->id[0])
4955                         blame->commit->id[0] = 0;
4956                 else
4957                         blame->commit = NULL;
4958         }
4960         /* Second pass: free existing references. */
4961         for (i = 0; i < view->lines; i++) {
4962                 struct blame *blame = view->line[i].data;
4964                 if (blame->commit)
4965                         free(blame->commit);
4966         }
4968         setup_update(view, opt_file);
4969         string_format(view->ref, "%s ...", opt_file);
4971         return TRUE;
4974 static struct blame_commit *
4975 get_blame_commit(struct view *view, const char *id)
4977         size_t i;
4979         for (i = 0; i < view->lines; i++) {
4980                 struct blame *blame = view->line[i].data;
4982                 if (!blame->commit)
4983                         continue;
4985                 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4986                         return blame->commit;
4987         }
4989         {
4990                 struct blame_commit *commit = calloc(1, sizeof(*commit));
4992                 if (commit)
4993                         string_ncopy(commit->id, id, SIZEOF_REV);
4994                 return commit;
4995         }
4998 static bool
4999 parse_number(const char **posref, size_t *number, size_t min, size_t max)
5001         const char *pos = *posref;
5003         *posref = NULL;
5004         pos = strchr(pos + 1, ' ');
5005         if (!pos || !isdigit(pos[1]))
5006                 return FALSE;
5007         *number = atoi(pos + 1);
5008         if (*number < min || *number > max)
5009                 return FALSE;
5011         *posref = pos;
5012         return TRUE;
5015 static struct blame_commit *
5016 parse_blame_commit(struct view *view, const char *text, int *blamed)
5018         struct blame_commit *commit;
5019         struct blame *blame;
5020         const char *pos = text + SIZEOF_REV - 2;
5021         size_t orig_lineno = 0;
5022         size_t lineno;
5023         size_t group;
5025         if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
5026                 return NULL;
5028         if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
5029             !parse_number(&pos, &lineno, 1, view->lines) ||
5030             !parse_number(&pos, &group, 1, view->lines - lineno + 1))
5031                 return NULL;
5033         commit = get_blame_commit(view, text);
5034         if (!commit)
5035                 return NULL;
5037         *blamed += group;
5038         while (group--) {
5039                 struct line *line = &view->line[lineno + group - 1];
5041                 blame = line->data;
5042                 blame->commit = commit;
5043                 blame->lineno = orig_lineno + group - 1;
5044                 line->dirty = 1;
5045         }
5047         return commit;
5050 static bool
5051 blame_read_file(struct view *view, const char *line, bool *read_file)
5053         if (!line) {
5054                 const char *blame_argv[] = {
5055                         "git", "blame", "--incremental",
5056                                 *opt_ref ? opt_ref : "--incremental", "--", opt_file, NULL
5057                 };
5059                 if (view->lines == 0 && !view->prev)
5060                         die("No blame exist for %s", view->vid);
5062                 if (view->lines == 0 || !start_update(view, blame_argv, opt_cdup)) {
5063                         report("Failed to load blame data");
5064                         return TRUE;
5065                 }
5067                 *read_file = FALSE;
5068                 return FALSE;
5070         } else {
5071                 size_t linelen = strlen(line);
5072                 struct blame *blame = malloc(sizeof(*blame) + linelen);
5074                 if (!blame)
5075                         return FALSE;
5077                 blame->commit = NULL;
5078                 strncpy(blame->text, line, linelen);
5079                 blame->text[linelen] = 0;
5080                 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
5081         }
5084 static bool
5085 match_blame_header(const char *name, char **line)
5087         size_t namelen = strlen(name);
5088         bool matched = !strncmp(name, *line, namelen);
5090         if (matched)
5091                 *line += namelen;
5093         return matched;
5096 static bool
5097 blame_read(struct view *view, char *line)
5099         static struct blame_commit *commit = NULL;
5100         static int blamed = 0;
5101         static bool read_file = TRUE;
5103         if (read_file)
5104                 return blame_read_file(view, line, &read_file);
5106         if (!line) {
5107                 /* Reset all! */
5108                 commit = NULL;
5109                 blamed = 0;
5110                 read_file = TRUE;
5111                 string_format(view->ref, "%s", view->vid);
5112                 if (view_is_displayed(view)) {
5113                         update_view_title(view);
5114                         redraw_view_from(view, 0);
5115                 }
5116                 return TRUE;
5117         }
5119         if (!commit) {
5120                 commit = parse_blame_commit(view, line, &blamed);
5121                 string_format(view->ref, "%s %2d%%", view->vid,
5122                               view->lines ? blamed * 100 / view->lines : 0);
5124         } else if (match_blame_header("author ", &line)) {
5125                 commit->author = get_author(line);
5127         } else if (match_blame_header("author-time ", &line)) {
5128                 parse_timesec(&commit->time, line);
5130         } else if (match_blame_header("author-tz ", &line)) {
5131                 parse_timezone(&commit->time, line);
5133         } else if (match_blame_header("summary ", &line)) {
5134                 string_ncopy(commit->title, line, strlen(line));
5136         } else if (match_blame_header("previous ", &line)) {
5137                 if (strlen(line) <= SIZEOF_REV)
5138                         return FALSE;
5139                 string_copy_rev(commit->parent_id, line);
5140                 line += SIZEOF_REV;
5141                 string_ncopy(commit->parent_filename, line, strlen(line));
5143         } else if (match_blame_header("filename ", &line)) {
5144                 string_ncopy(commit->filename, line, strlen(line));
5145                 commit = NULL;
5146         }
5148         return TRUE;
5151 static bool
5152 blame_draw(struct view *view, struct line *line, unsigned int lineno)
5154         struct blame *blame = line->data;
5155         struct time *time = NULL;
5156         const char *id = NULL, *author = NULL;
5158         if (blame->commit && *blame->commit->filename) {
5159                 id = blame->commit->id;
5160                 author = blame->commit->author;
5161                 time = &blame->commit->time;
5162         }
5164         if (opt_date && draw_date(view, time))
5165                 return TRUE;
5167         if (opt_author && draw_author(view, author))
5168                 return TRUE;
5170         if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
5171                 return TRUE;
5173         if (draw_lineno(view, lineno))
5174                 return TRUE;
5176         draw_text(view, LINE_DEFAULT, blame->text, TRUE);
5177         return TRUE;
5180 static bool
5181 check_blame_commit(struct blame *blame, bool check_null_id)
5183         if (!blame->commit)
5184                 report("Commit data not loaded yet");
5185         else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
5186                 report("No commit exist for the selected line");
5187         else
5188                 return TRUE;
5189         return FALSE;
5192 static void
5193 setup_blame_parent_line(struct view *view, struct blame *blame)
5195         char from[SIZEOF_REF + SIZEOF_STR];
5196         char to[SIZEOF_REF + SIZEOF_STR];
5197         const char *diff_tree_argv[] = {
5198                 "git", "diff", "--no-textconv", "--no-extdiff", "--no-color",
5199                         "-U0", from, to, "--", NULL
5200         };
5201         struct io io;
5202         int parent_lineno = -1;
5203         int blamed_lineno = -1;
5204         char *line;
5206         if (!string_format(from, "%s:%s", opt_ref, opt_file) ||
5207             !string_format(to, "%s:%s", blame->commit->id, blame->commit->filename) ||
5208             !io_run(&io, IO_RD, NULL, diff_tree_argv))
5209                 return;
5211         while ((line = io_get(&io, '\n', TRUE))) {
5212                 if (*line == '@') {
5213                         char *pos = strchr(line, '+');
5215                         parent_lineno = atoi(line + 4);
5216                         if (pos)
5217                                 blamed_lineno = atoi(pos + 1);
5219                 } else if (*line == '+' && parent_lineno != -1) {
5220                         if (blame->lineno == blamed_lineno - 1 &&
5221                             !strcmp(blame->text, line + 1)) {
5222                                 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
5223                                 break;
5224                         }
5225                         blamed_lineno++;
5226                 }
5227         }
5229         io_done(&io);
5232 static enum request
5233 blame_request(struct view *view, enum request request, struct line *line)
5235         enum open_flags flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
5236         struct blame *blame = line->data;
5238         switch (request) {
5239         case REQ_VIEW_BLAME:
5240                 if (check_blame_commit(blame, TRUE)) {
5241                         string_copy(opt_ref, blame->commit->id);
5242                         string_copy(opt_file, blame->commit->filename);
5243                         if (blame->lineno)
5244                                 view->lineno = blame->lineno;
5245                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5246                 }
5247                 break;
5249         case REQ_PARENT:
5250                 if (!check_blame_commit(blame, TRUE))
5251                         break;
5252                 if (!*blame->commit->parent_id) {
5253                         report("The selected commit has no parents");
5254                 } else {
5255                         string_copy_rev(opt_ref, blame->commit->parent_id);
5256                         string_copy(opt_file, blame->commit->parent_filename);
5257                         setup_blame_parent_line(view, blame);
5258                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5259                 }
5260                 break;
5262         case REQ_ENTER:
5263                 if (!check_blame_commit(blame, FALSE))
5264                         break;
5266                 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
5267                     !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
5268                         break;
5270                 if (!strcmp(blame->commit->id, NULL_ID)) {
5271                         struct view *diff = VIEW(REQ_VIEW_DIFF);
5272                         const char *diff_index_argv[] = {
5273                                 "git", "diff-index", "--root", "--patch-with-stat",
5274                                         "-C", "-M", "HEAD", "--", view->vid, NULL
5275                         };
5277                         if (!*blame->commit->parent_id) {
5278                                 diff_index_argv[1] = "diff";
5279                                 diff_index_argv[2] = "--no-color";
5280                                 diff_index_argv[6] = "--";
5281                                 diff_index_argv[7] = "/dev/null";
5282                         }
5284                         if (!prepare_update(diff, diff_index_argv, NULL)) {
5285                                 report("Failed to allocate diff command");
5286                                 break;
5287                         }
5288                         flags |= OPEN_PREPARED;
5289                 }
5291                 open_view(view, REQ_VIEW_DIFF, flags);
5292                 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
5293                         string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
5294                 break;
5296         default:
5297                 return request;
5298         }
5300         return REQ_NONE;
5303 static bool
5304 blame_grep(struct view *view, struct line *line)
5306         struct blame *blame = line->data;
5307         struct blame_commit *commit = blame->commit;
5308         const char *text[] = {
5309                 blame->text,
5310                 commit ? commit->title : "",
5311                 commit ? commit->id : "",
5312                 commit && opt_author ? commit->author : "",
5313                 commit ? mkdate(&commit->time, opt_date) : "",
5314                 NULL
5315         };
5317         return grep_text(view, text);
5320 static void
5321 blame_select(struct view *view, struct line *line)
5323         struct blame *blame = line->data;
5324         struct blame_commit *commit = blame->commit;
5326         if (!commit)
5327                 return;
5329         if (!strcmp(commit->id, NULL_ID))
5330                 string_ncopy(ref_commit, "HEAD", 4);
5331         else
5332                 string_copy_rev(ref_commit, commit->id);
5335 static struct view_ops blame_ops = {
5336         "line",
5337         NULL,
5338         blame_open,
5339         blame_read,
5340         blame_draw,
5341         blame_request,
5342         blame_grep,
5343         blame_select,
5344 };
5346 /*
5347  * Branch backend
5348  */
5350 struct branch {
5351         const char *author;             /* Author of the last commit. */
5352         struct time time;               /* Date of the last activity. */
5353         const struct ref *ref;          /* Name and commit ID information. */
5354 };
5356 static const struct ref branch_all;
5358 static const enum sort_field branch_sort_fields[] = {
5359         ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
5360 };
5361 static struct sort_state branch_sort_state = SORT_STATE(branch_sort_fields);
5363 static int
5364 branch_compare(const void *l1, const void *l2)
5366         const struct branch *branch1 = ((const struct line *) l1)->data;
5367         const struct branch *branch2 = ((const struct line *) l2)->data;
5369         switch (get_sort_field(branch_sort_state)) {
5370         case ORDERBY_DATE:
5371                 return sort_order(branch_sort_state, timecmp(&branch1->time, &branch2->time));
5373         case ORDERBY_AUTHOR:
5374                 return sort_order(branch_sort_state, strcmp(branch1->author, branch2->author));
5376         case ORDERBY_NAME:
5377         default:
5378                 return sort_order(branch_sort_state, strcmp(branch1->ref->name, branch2->ref->name));
5379         }
5382 static bool
5383 branch_draw(struct view *view, struct line *line, unsigned int lineno)
5385         struct branch *branch = line->data;
5386         enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
5388         if (opt_date && draw_date(view, &branch->time))
5389                 return TRUE;
5391         if (opt_author && draw_author(view, branch->author))
5392                 return TRUE;
5394         draw_text(view, type, branch->ref == &branch_all ? "All branches" : branch->ref->name, TRUE);
5395         return TRUE;
5398 static enum request
5399 branch_request(struct view *view, enum request request, struct line *line)
5401         struct branch *branch = line->data;
5403         switch (request) {
5404         case REQ_REFRESH:
5405                 load_refs();
5406                 open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
5407                 return REQ_NONE;
5409         case REQ_TOGGLE_SORT_FIELD:
5410         case REQ_TOGGLE_SORT_ORDER:
5411                 sort_view(view, request, &branch_sort_state, branch_compare);
5412                 return REQ_NONE;
5414         case REQ_ENTER:
5415         {
5416                 const struct ref *ref = branch->ref;
5417                 const char *all_branches_argv[] = {
5418                         "git", "log", "--no-color", "--pretty=raw", "--parents",
5419                               "--topo-order",
5420                               ref == &branch_all ? "--all" : ref->name, NULL
5421                 };
5422                 struct view *main_view = VIEW(REQ_VIEW_MAIN);
5424                 if (!prepare_update(main_view, all_branches_argv, NULL))
5425                         report("Failed to load view of all branches");
5426                 else
5427                         open_view(view, REQ_VIEW_MAIN, OPEN_PREPARED | OPEN_SPLIT);
5428                 return REQ_NONE;
5429         }
5430         default:
5431                 return request;
5432         }
5435 static bool
5436 branch_read(struct view *view, char *line)
5438         static char id[SIZEOF_REV];
5439         struct branch *reference;
5440         size_t i;
5442         if (!line)
5443                 return TRUE;
5445         switch (get_line_type(line)) {
5446         case LINE_COMMIT:
5447                 string_copy_rev(id, line + STRING_SIZE("commit "));
5448                 return TRUE;
5450         case LINE_AUTHOR:
5451                 for (i = 0, reference = NULL; i < view->lines; i++) {
5452                         struct branch *branch = view->line[i].data;
5454                         if (strcmp(branch->ref->id, id))
5455                                 continue;
5457                         view->line[i].dirty = TRUE;
5458                         if (reference) {
5459                                 branch->author = reference->author;
5460                                 branch->time = reference->time;
5461                                 continue;
5462                         }
5464                         parse_author_line(line + STRING_SIZE("author "),
5465                                           &branch->author, &branch->time);
5466                         reference = branch;
5467                 }
5468                 return TRUE;
5470         default:
5471                 return TRUE;
5472         }
5476 static bool
5477 branch_open_visitor(void *data, const struct ref *ref)
5479         struct view *view = data;
5480         struct branch *branch;
5482         if (ref->tag || ref->ltag || ref->remote)
5483                 return TRUE;
5485         branch = calloc(1, sizeof(*branch));
5486         if (!branch)
5487                 return FALSE;
5489         branch->ref = ref;
5490         return !!add_line_data(view, branch, LINE_DEFAULT);
5493 static bool
5494 branch_open(struct view *view)
5496         const char *branch_log[] = {
5497                 "git", "log", "--no-color", "--pretty=raw",
5498                         "--simplify-by-decoration", "--all", NULL
5499         };
5501         if (!start_update(view, branch_log, NULL)) {
5502                 report("Failed to load branch data");
5503                 return TRUE;
5504         }
5506         setup_update(view, view->id);
5507         branch_open_visitor(view, &branch_all);
5508         foreach_ref(branch_open_visitor, view);
5509         view->p_restore = TRUE;
5511         return TRUE;
5514 static bool
5515 branch_grep(struct view *view, struct line *line)
5517         struct branch *branch = line->data;
5518         const char *text[] = {
5519                 branch->ref->name,
5520                 branch->author,
5521                 NULL
5522         };
5524         return grep_text(view, text);
5527 static void
5528 branch_select(struct view *view, struct line *line)
5530         struct branch *branch = line->data;
5532         string_copy_rev(view->ref, branch->ref->id);
5533         string_copy_rev(ref_commit, branch->ref->id);
5534         string_copy_rev(ref_head, branch->ref->id);
5535         string_copy_rev(ref_branch, branch->ref->name);
5538 static struct view_ops branch_ops = {
5539         "branch",
5540         NULL,
5541         branch_open,
5542         branch_read,
5543         branch_draw,
5544         branch_request,
5545         branch_grep,
5546         branch_select,
5547 };
5549 /*
5550  * Status backend
5551  */
5553 struct status {
5554         char status;
5555         struct {
5556                 mode_t mode;
5557                 char rev[SIZEOF_REV];
5558                 char name[SIZEOF_STR];
5559         } old;
5560         struct {
5561                 mode_t mode;
5562                 char rev[SIZEOF_REV];
5563                 char name[SIZEOF_STR];
5564         } new;
5565 };
5567 static char status_onbranch[SIZEOF_STR];
5568 static struct status stage_status;
5569 static enum line_type stage_line_type;
5570 static size_t stage_chunks;
5571 static int *stage_chunk;
5573 DEFINE_ALLOCATOR(realloc_ints, int, 32)
5575 /* This should work even for the "On branch" line. */
5576 static inline bool
5577 status_has_none(struct view *view, struct line *line)
5579         return line < view->line + view->lines && !line[1].data;
5582 /* Get fields from the diff line:
5583  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5584  */
5585 static inline bool
5586 status_get_diff(struct status *file, const char *buf, size_t bufsize)
5588         const char *old_mode = buf +  1;
5589         const char *new_mode = buf +  8;
5590         const char *old_rev  = buf + 15;
5591         const char *new_rev  = buf + 56;
5592         const char *status   = buf + 97;
5594         if (bufsize < 98 ||
5595             old_mode[-1] != ':' ||
5596             new_mode[-1] != ' ' ||
5597             old_rev[-1]  != ' ' ||
5598             new_rev[-1]  != ' ' ||
5599             status[-1]   != ' ')
5600                 return FALSE;
5602         file->status = *status;
5604         string_copy_rev(file->old.rev, old_rev);
5605         string_copy_rev(file->new.rev, new_rev);
5607         file->old.mode = strtoul(old_mode, NULL, 8);
5608         file->new.mode = strtoul(new_mode, NULL, 8);
5610         file->old.name[0] = file->new.name[0] = 0;
5612         return TRUE;
5615 static bool
5616 status_run(struct view *view, const char *argv[], char status, enum line_type type)
5618         struct status *unmerged = NULL;
5619         char *buf;
5620         struct io io;
5622         if (!io_run(&io, IO_RD, opt_cdup, argv))
5623                 return FALSE;
5625         add_line_data(view, NULL, type);
5627         while ((buf = io_get(&io, 0, TRUE))) {
5628                 struct status *file = unmerged;
5630                 if (!file) {
5631                         file = calloc(1, sizeof(*file));
5632                         if (!file || !add_line_data(view, file, type))
5633                                 goto error_out;
5634                 }
5636                 /* Parse diff info part. */
5637                 if (status) {
5638                         file->status = status;
5639                         if (status == 'A')
5640                                 string_copy(file->old.rev, NULL_ID);
5642                 } else if (!file->status || file == unmerged) {
5643                         if (!status_get_diff(file, buf, strlen(buf)))
5644                                 goto error_out;
5646                         buf = io_get(&io, 0, TRUE);
5647                         if (!buf)
5648                                 break;
5650                         /* Collapse all modified entries that follow an
5651                          * associated unmerged entry. */
5652                         if (unmerged == file) {
5653                                 unmerged->status = 'U';
5654                                 unmerged = NULL;
5655                         } else if (file->status == 'U') {
5656                                 unmerged = file;
5657                         }
5658                 }
5660                 /* Grab the old name for rename/copy. */
5661                 if (!*file->old.name &&
5662                     (file->status == 'R' || file->status == 'C')) {
5663                         string_ncopy(file->old.name, buf, strlen(buf));
5665                         buf = io_get(&io, 0, TRUE);
5666                         if (!buf)
5667                                 break;
5668                 }
5670                 /* git-ls-files just delivers a NUL separated list of
5671                  * file names similar to the second half of the
5672                  * git-diff-* output. */
5673                 string_ncopy(file->new.name, buf, strlen(buf));
5674                 if (!*file->old.name)
5675                         string_copy(file->old.name, file->new.name);
5676                 file = NULL;
5677         }
5679         if (io_error(&io)) {
5680 error_out:
5681                 io_done(&io);
5682                 return FALSE;
5683         }
5685         if (!view->line[view->lines - 1].data)
5686                 add_line_data(view, NULL, LINE_STAT_NONE);
5688         io_done(&io);
5689         return TRUE;
5692 /* Don't show unmerged entries in the staged section. */
5693 static const char *status_diff_index_argv[] = {
5694         "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5695                              "--cached", "-M", "HEAD", NULL
5696 };
5698 static const char *status_diff_files_argv[] = {
5699         "git", "diff-files", "-z", NULL
5700 };
5702 static const char *status_list_other_argv[] = {
5703         "git", "ls-files", "-z", "--others", "--exclude-standard", opt_prefix, NULL
5704 };
5706 static const char *status_list_no_head_argv[] = {
5707         "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5708 };
5710 static const char *update_index_argv[] = {
5711         "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5712 };
5714 /* Restore the previous line number to stay in the context or select a
5715  * line with something that can be updated. */
5716 static void
5717 status_restore(struct view *view)
5719         if (view->p_lineno >= view->lines)
5720                 view->p_lineno = view->lines - 1;
5721         while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
5722                 view->p_lineno++;
5723         while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
5724                 view->p_lineno--;
5726         /* If the above fails, always skip the "On branch" line. */
5727         if (view->p_lineno < view->lines)
5728                 view->lineno = view->p_lineno;
5729         else
5730                 view->lineno = 1;
5732         if (view->lineno < view->offset)
5733                 view->offset = view->lineno;
5734         else if (view->offset + view->height <= view->lineno)
5735                 view->offset = view->lineno - view->height + 1;
5737         view->p_restore = FALSE;
5740 static void
5741 status_update_onbranch(void)
5743         static const char *paths[][2] = {
5744                 { "rebase-apply/rebasing",      "Rebasing" },
5745                 { "rebase-apply/applying",      "Applying mailbox" },
5746                 { "rebase-apply/",              "Rebasing mailbox" },
5747                 { "rebase-merge/interactive",   "Interactive rebase" },
5748                 { "rebase-merge/",              "Rebase merge" },
5749                 { "MERGE_HEAD",                 "Merging" },
5750                 { "BISECT_LOG",                 "Bisecting" },
5751                 { "HEAD",                       "On branch" },
5752         };
5753         char buf[SIZEOF_STR];
5754         struct stat stat;
5755         int i;
5757         if (is_initial_commit()) {
5758                 string_copy(status_onbranch, "Initial commit");
5759                 return;
5760         }
5762         for (i = 0; i < ARRAY_SIZE(paths); i++) {
5763                 char *head = opt_head;
5765                 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5766                     lstat(buf, &stat) < 0)
5767                         continue;
5769                 if (!*opt_head) {
5770                         struct io io;
5772                         if (io_open(&io, "%s/rebase-merge/head-name", opt_git_dir) &&
5773                             io_read_buf(&io, buf, sizeof(buf))) {
5774                                 head = buf;
5775                                 if (!prefixcmp(head, "refs/heads/"))
5776                                         head += STRING_SIZE("refs/heads/");
5777                         }
5778                 }
5780                 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5781                         string_copy(status_onbranch, opt_head);
5782                 return;
5783         }
5785         string_copy(status_onbranch, "Not currently on any branch");
5788 /* First parse staged info using git-diff-index(1), then parse unstaged
5789  * info using git-diff-files(1), and finally untracked files using
5790  * git-ls-files(1). */
5791 static bool
5792 status_open(struct view *view)
5794         reset_view(view);
5796         add_line_data(view, NULL, LINE_STAT_HEAD);
5797         status_update_onbranch();
5799         io_run_bg(update_index_argv);
5801         if (is_initial_commit()) {
5802                 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5803                         return FALSE;
5804         } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5805                 return FALSE;
5806         }
5808         if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5809             !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5810                 return FALSE;
5812         /* Restore the exact position or use the specialized restore
5813          * mode? */
5814         if (!view->p_restore)
5815                 status_restore(view);
5816         return TRUE;
5819 static bool
5820 status_draw(struct view *view, struct line *line, unsigned int lineno)
5822         struct status *status = line->data;
5823         enum line_type type;
5824         const char *text;
5826         if (!status) {
5827                 switch (line->type) {
5828                 case LINE_STAT_STAGED:
5829                         type = LINE_STAT_SECTION;
5830                         text = "Changes to be committed:";
5831                         break;
5833                 case LINE_STAT_UNSTAGED:
5834                         type = LINE_STAT_SECTION;
5835                         text = "Changed but not updated:";
5836                         break;
5838                 case LINE_STAT_UNTRACKED:
5839                         type = LINE_STAT_SECTION;
5840                         text = "Untracked files:";
5841                         break;
5843                 case LINE_STAT_NONE:
5844                         type = LINE_DEFAULT;
5845                         text = "  (no files)";
5846                         break;
5848                 case LINE_STAT_HEAD:
5849                         type = LINE_STAT_HEAD;
5850                         text = status_onbranch;
5851                         break;
5853                 default:
5854                         return FALSE;
5855                 }
5856         } else {
5857                 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5859                 buf[0] = status->status;
5860                 if (draw_text(view, line->type, buf, TRUE))
5861                         return TRUE;
5862                 type = LINE_DEFAULT;
5863                 text = status->new.name;
5864         }
5866         draw_text(view, type, text, TRUE);
5867         return TRUE;
5870 static enum request
5871 status_load_error(struct view *view, struct view *stage, const char *path)
5873         if (displayed_views() == 2 || display[current_view] != view)
5874                 maximize_view(view);
5875         report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5876         return REQ_NONE;
5879 static enum request
5880 status_enter(struct view *view, struct line *line)
5882         struct status *status = line->data;
5883         const char *oldpath = status ? status->old.name : NULL;
5884         /* Diffs for unmerged entries are empty when passing the new
5885          * path, so leave it empty. */
5886         const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5887         const char *info;
5888         enum open_flags split;
5889         struct view *stage = VIEW(REQ_VIEW_STAGE);
5891         if (line->type == LINE_STAT_NONE ||
5892             (!status && line[1].type == LINE_STAT_NONE)) {
5893                 report("No file to diff");
5894                 return REQ_NONE;
5895         }
5897         switch (line->type) {
5898         case LINE_STAT_STAGED:
5899                 if (is_initial_commit()) {
5900                         const char *no_head_diff_argv[] = {
5901                                 "git", "diff", "--no-color", "--patch-with-stat",
5902                                         "--", "/dev/null", newpath, NULL
5903                         };
5905                         if (!prepare_update(stage, no_head_diff_argv, opt_cdup))
5906                                 return status_load_error(view, stage, newpath);
5907                 } else {
5908                         const char *index_show_argv[] = {
5909                                 "git", "diff-index", "--root", "--patch-with-stat",
5910                                         "-C", "-M", "--cached", "HEAD", "--",
5911                                         oldpath, newpath, NULL
5912                         };
5914                         if (!prepare_update(stage, index_show_argv, opt_cdup))
5915                                 return status_load_error(view, stage, newpath);
5916                 }
5918                 if (status)
5919                         info = "Staged changes to %s";
5920                 else
5921                         info = "Staged changes";
5922                 break;
5924         case LINE_STAT_UNSTAGED:
5925         {
5926                 const char *files_show_argv[] = {
5927                         "git", "diff-files", "--root", "--patch-with-stat",
5928                                 "-C", "-M", "--", oldpath, newpath, NULL
5929                 };
5931                 if (!prepare_update(stage, files_show_argv, opt_cdup))
5932                         return status_load_error(view, stage, newpath);
5933                 if (status)
5934                         info = "Unstaged changes to %s";
5935                 else
5936                         info = "Unstaged changes";
5937                 break;
5938         }
5939         case LINE_STAT_UNTRACKED:
5940                 if (!newpath) {
5941                         report("No file to show");
5942                         return REQ_NONE;
5943                 }
5945                 if (!suffixcmp(status->new.name, -1, "/")) {
5946                         report("Cannot display a directory");
5947                         return REQ_NONE;
5948                 }
5950                 if (!prepare_update_file(stage, newpath))
5951                         return status_load_error(view, stage, newpath);
5952                 info = "Untracked file %s";
5953                 break;
5955         case LINE_STAT_HEAD:
5956                 return REQ_NONE;
5958         default:
5959                 die("line type %d not handled in switch", line->type);
5960         }
5962         split = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
5963         open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5964         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5965                 if (status) {
5966                         stage_status = *status;
5967                 } else {
5968                         memset(&stage_status, 0, sizeof(stage_status));
5969                 }
5971                 stage_line_type = line->type;
5972                 stage_chunks = 0;
5973                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5974         }
5976         return REQ_NONE;
5979 static bool
5980 status_exists(struct status *status, enum line_type type)
5982         struct view *view = VIEW(REQ_VIEW_STATUS);
5983         unsigned long lineno;
5985         for (lineno = 0; lineno < view->lines; lineno++) {
5986                 struct line *line = &view->line[lineno];
5987                 struct status *pos = line->data;
5989                 if (line->type != type)
5990                         continue;
5991                 if (!pos && (!status || !status->status) && line[1].data) {
5992                         select_view_line(view, lineno);
5993                         return TRUE;
5994                 }
5995                 if (pos && !strcmp(status->new.name, pos->new.name)) {
5996                         select_view_line(view, lineno);
5997                         return TRUE;
5998                 }
5999         }
6001         return FALSE;
6005 static bool
6006 status_update_prepare(struct io *io, enum line_type type)
6008         const char *staged_argv[] = {
6009                 "git", "update-index", "-z", "--index-info", NULL
6010         };
6011         const char *others_argv[] = {
6012                 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
6013         };
6015         switch (type) {
6016         case LINE_STAT_STAGED:
6017                 return io_run(io, IO_WR, opt_cdup, staged_argv);
6019         case LINE_STAT_UNSTAGED:
6020         case LINE_STAT_UNTRACKED:
6021                 return io_run(io, IO_WR, opt_cdup, others_argv);
6023         default:
6024                 die("line type %d not handled in switch", type);
6025                 return FALSE;
6026         }
6029 static bool
6030 status_update_write(struct io *io, struct status *status, enum line_type type)
6032         char buf[SIZEOF_STR];
6033         size_t bufsize = 0;
6035         switch (type) {
6036         case LINE_STAT_STAGED:
6037                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
6038                                         status->old.mode,
6039                                         status->old.rev,
6040                                         status->old.name, 0))
6041                         return FALSE;
6042                 break;
6044         case LINE_STAT_UNSTAGED:
6045         case LINE_STAT_UNTRACKED:
6046                 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
6047                         return FALSE;
6048                 break;
6050         default:
6051                 die("line type %d not handled in switch", type);
6052         }
6054         return io_write(io, buf, bufsize);
6057 static bool
6058 status_update_file(struct status *status, enum line_type type)
6060         struct io io;
6061         bool result;
6063         if (!status_update_prepare(&io, type))
6064                 return FALSE;
6066         result = status_update_write(&io, status, type);
6067         return io_done(&io) && result;
6070 static bool
6071 status_update_files(struct view *view, struct line *line)
6073         char buf[sizeof(view->ref)];
6074         struct io io;
6075         bool result = TRUE;
6076         struct line *pos = view->line + view->lines;
6077         int files = 0;
6078         int file, done;
6079         int cursor_y = -1, cursor_x = -1;
6081         if (!status_update_prepare(&io, line->type))
6082                 return FALSE;
6084         for (pos = line; pos < view->line + view->lines && pos->data; pos++)
6085                 files++;
6087         string_copy(buf, view->ref);
6088         getsyx(cursor_y, cursor_x);
6089         for (file = 0, done = 5; result && file < files; line++, file++) {
6090                 int almost_done = file * 100 / files;
6092                 if (almost_done > done) {
6093                         done = almost_done;
6094                         string_format(view->ref, "updating file %u of %u (%d%% done)",
6095                                       file, files, done);
6096                         update_view_title(view);
6097                         setsyx(cursor_y, cursor_x);
6098                         doupdate();
6099                 }
6100                 result = status_update_write(&io, line->data, line->type);
6101         }
6102         string_copy(view->ref, buf);
6104         return io_done(&io) && result;
6107 static bool
6108 status_update(struct view *view)
6110         struct line *line = &view->line[view->lineno];
6112         assert(view->lines);
6114         if (!line->data) {
6115                 /* This should work even for the "On branch" line. */
6116                 if (line < view->line + view->lines && !line[1].data) {
6117                         report("Nothing to update");
6118                         return FALSE;
6119                 }
6121                 if (!status_update_files(view, line + 1)) {
6122                         report("Failed to update file status");
6123                         return FALSE;
6124                 }
6126         } else if (!status_update_file(line->data, line->type)) {
6127                 report("Failed to update file status");
6128                 return FALSE;
6129         }
6131         return TRUE;
6134 static bool
6135 status_revert(struct status *status, enum line_type type, bool has_none)
6137         if (!status || type != LINE_STAT_UNSTAGED) {
6138                 if (type == LINE_STAT_STAGED) {
6139                         report("Cannot revert changes to staged files");
6140                 } else if (type == LINE_STAT_UNTRACKED) {
6141                         report("Cannot revert changes to untracked files");
6142                 } else if (has_none) {
6143                         report("Nothing to revert");
6144                 } else {
6145                         report("Cannot revert changes to multiple files");
6146                 }
6148         } else if (prompt_yesno("Are you sure you want to revert changes?")) {
6149                 char mode[10] = "100644";
6150                 const char *reset_argv[] = {
6151                         "git", "update-index", "--cacheinfo", mode,
6152                                 status->old.rev, status->old.name, NULL
6153                 };
6154                 const char *checkout_argv[] = {
6155                         "git", "checkout", "--", status->old.name, NULL
6156                 };
6158                 if (status->status == 'U') {
6159                         string_format(mode, "%5o", status->old.mode);
6161                         if (status->old.mode == 0 && status->new.mode == 0) {
6162                                 reset_argv[2] = "--force-remove";
6163                                 reset_argv[3] = status->old.name;
6164                                 reset_argv[4] = NULL;
6165                         }
6167                         if (!io_run_fg(reset_argv, opt_cdup))
6168                                 return FALSE;
6169                         if (status->old.mode == 0 && status->new.mode == 0)
6170                                 return TRUE;
6171                 }
6173                 return io_run_fg(checkout_argv, opt_cdup);
6174         }
6176         return FALSE;
6179 static enum request
6180 status_request(struct view *view, enum request request, struct line *line)
6182         struct status *status = line->data;
6184         switch (request) {
6185         case REQ_STATUS_UPDATE:
6186                 if (!status_update(view))
6187                         return REQ_NONE;
6188                 break;
6190         case REQ_STATUS_REVERT:
6191                 if (!status_revert(status, line->type, status_has_none(view, line)))
6192                         return REQ_NONE;
6193                 break;
6195         case REQ_STATUS_MERGE:
6196                 if (!status || status->status != 'U') {
6197                         report("Merging only possible for files with unmerged status ('U').");
6198                         return REQ_NONE;
6199                 }
6200                 open_mergetool(status->new.name);
6201                 break;
6203         case REQ_EDIT:
6204                 if (!status)
6205                         return request;
6206                 if (status->status == 'D') {
6207                         report("File has been deleted.");
6208                         return REQ_NONE;
6209                 }
6211                 open_editor(status->new.name);
6212                 break;
6214         case REQ_VIEW_BLAME:
6215                 if (status)
6216                         opt_ref[0] = 0;
6217                 return request;
6219         case REQ_ENTER:
6220                 /* After returning the status view has been split to
6221                  * show the stage view. No further reloading is
6222                  * necessary. */
6223                 return status_enter(view, line);
6225         case REQ_REFRESH:
6226                 /* Simply reload the view. */
6227                 break;
6229         default:
6230                 return request;
6231         }
6233         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
6235         return REQ_NONE;
6238 static void
6239 status_select(struct view *view, struct line *line)
6241         struct status *status = line->data;
6242         char file[SIZEOF_STR] = "all files";
6243         const char *text;
6244         const char *key;
6246         if (status && !string_format(file, "'%s'", status->new.name))
6247                 return;
6249         if (!status && line[1].type == LINE_STAT_NONE)
6250                 line++;
6252         switch (line->type) {
6253         case LINE_STAT_STAGED:
6254                 text = "Press %s to unstage %s for commit";
6255                 break;
6257         case LINE_STAT_UNSTAGED:
6258                 text = "Press %s to stage %s for commit";
6259                 break;
6261         case LINE_STAT_UNTRACKED:
6262                 text = "Press %s to stage %s for addition";
6263                 break;
6265         case LINE_STAT_HEAD:
6266         case LINE_STAT_NONE:
6267                 text = "Nothing to update";
6268                 break;
6270         default:
6271                 die("line type %d not handled in switch", line->type);
6272         }
6274         if (status && status->status == 'U') {
6275                 text = "Press %s to resolve conflict in %s";
6276                 key = get_key(KEYMAP_STATUS, REQ_STATUS_MERGE);
6278         } else {
6279                 key = get_key(KEYMAP_STATUS, REQ_STATUS_UPDATE);
6280         }
6282         string_format(view->ref, text, key, file);
6283         if (status)
6284                 string_copy(opt_file, status->new.name);
6287 static bool
6288 status_grep(struct view *view, struct line *line)
6290         struct status *status = line->data;
6292         if (status) {
6293                 const char buf[2] = { status->status, 0 };
6294                 const char *text[] = { status->new.name, buf, NULL };
6296                 return grep_text(view, text);
6297         }
6299         return FALSE;
6302 static struct view_ops status_ops = {
6303         "file",
6304         NULL,
6305         status_open,
6306         NULL,
6307         status_draw,
6308         status_request,
6309         status_grep,
6310         status_select,
6311 };
6314 static bool
6315 stage_diff_write(struct io *io, struct line *line, struct line *end)
6317         while (line < end) {
6318                 if (!io_write(io, line->data, strlen(line->data)) ||
6319                     !io_write(io, "\n", 1))
6320                         return FALSE;
6321                 line++;
6322                 if (line->type == LINE_DIFF_CHUNK ||
6323                     line->type == LINE_DIFF_HEADER)
6324                         break;
6325         }
6327         return TRUE;
6330 static struct line *
6331 stage_diff_find(struct view *view, struct line *line, enum line_type type)
6333         for (; view->line < line; line--)
6334                 if (line->type == type)
6335                         return line;
6337         return NULL;
6340 static bool
6341 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
6343         const char *apply_argv[SIZEOF_ARG] = {
6344                 "git", "apply", "--whitespace=nowarn", NULL
6345         };
6346         struct line *diff_hdr;
6347         struct io io;
6348         int argc = 3;
6350         diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
6351         if (!diff_hdr)
6352                 return FALSE;
6354         if (!revert)
6355                 apply_argv[argc++] = "--cached";
6356         if (revert || stage_line_type == LINE_STAT_STAGED)
6357                 apply_argv[argc++] = "-R";
6358         apply_argv[argc++] = "-";
6359         apply_argv[argc++] = NULL;
6360         if (!io_run(&io, IO_WR, opt_cdup, apply_argv))
6361                 return FALSE;
6363         if (!stage_diff_write(&io, diff_hdr, chunk) ||
6364             !stage_diff_write(&io, chunk, view->line + view->lines))
6365                 chunk = NULL;
6367         io_done(&io);
6368         io_run_bg(update_index_argv);
6370         return chunk ? TRUE : FALSE;
6373 static bool
6374 stage_update(struct view *view, struct line *line)
6376         struct line *chunk = NULL;
6378         if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
6379                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6381         if (chunk) {
6382                 if (!stage_apply_chunk(view, chunk, FALSE)) {
6383                         report("Failed to apply chunk");
6384                         return FALSE;
6385                 }
6387         } else if (!stage_status.status) {
6388                 view = VIEW(REQ_VIEW_STATUS);
6390                 for (line = view->line; line < view->line + view->lines; line++)
6391                         if (line->type == stage_line_type)
6392                                 break;
6394                 if (!status_update_files(view, line + 1)) {
6395                         report("Failed to update files");
6396                         return FALSE;
6397                 }
6399         } else if (!status_update_file(&stage_status, stage_line_type)) {
6400                 report("Failed to update file");
6401                 return FALSE;
6402         }
6404         return TRUE;
6407 static bool
6408 stage_revert(struct view *view, struct line *line)
6410         struct line *chunk = NULL;
6412         if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
6413                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6415         if (chunk) {
6416                 if (!prompt_yesno("Are you sure you want to revert changes?"))
6417                         return FALSE;
6419                 if (!stage_apply_chunk(view, chunk, TRUE)) {
6420                         report("Failed to revert chunk");
6421                         return FALSE;
6422                 }
6423                 return TRUE;
6425         } else {
6426                 return status_revert(stage_status.status ? &stage_status : NULL,
6427                                      stage_line_type, FALSE);
6428         }
6432 static void
6433 stage_next(struct view *view, struct line *line)
6435         int i;
6437         if (!stage_chunks) {
6438                 for (line = view->line; line < view->line + view->lines; line++) {
6439                         if (line->type != LINE_DIFF_CHUNK)
6440                                 continue;
6442                         if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
6443                                 report("Allocation failure");
6444                                 return;
6445                         }
6447                         stage_chunk[stage_chunks++] = line - view->line;
6448                 }
6449         }
6451         for (i = 0; i < stage_chunks; i++) {
6452                 if (stage_chunk[i] > view->lineno) {
6453                         do_scroll_view(view, stage_chunk[i] - view->lineno);
6454                         report("Chunk %d of %d", i + 1, stage_chunks);
6455                         return;
6456                 }
6457         }
6459         report("No next chunk found");
6462 static enum request
6463 stage_request(struct view *view, enum request request, struct line *line)
6465         switch (request) {
6466         case REQ_STATUS_UPDATE:
6467                 if (!stage_update(view, line))
6468                         return REQ_NONE;
6469                 break;
6471         case REQ_STATUS_REVERT:
6472                 if (!stage_revert(view, line))
6473                         return REQ_NONE;
6474                 break;
6476         case REQ_STAGE_NEXT:
6477                 if (stage_line_type == LINE_STAT_UNTRACKED) {
6478                         report("File is untracked; press %s to add",
6479                                get_key(KEYMAP_STAGE, REQ_STATUS_UPDATE));
6480                         return REQ_NONE;
6481                 }
6482                 stage_next(view, line);
6483                 return REQ_NONE;
6485         case REQ_EDIT:
6486                 if (!stage_status.new.name[0])
6487                         return request;
6488                 if (stage_status.status == 'D') {
6489                         report("File has been deleted.");
6490                         return REQ_NONE;
6491                 }
6493                 open_editor(stage_status.new.name);
6494                 break;
6496         case REQ_REFRESH:
6497                 /* Reload everything ... */
6498                 break;
6500         case REQ_VIEW_BLAME:
6501                 if (stage_status.new.name[0]) {
6502                         string_copy(opt_file, stage_status.new.name);
6503                         opt_ref[0] = 0;
6504                 }
6505                 return request;
6507         case REQ_ENTER:
6508                 return pager_request(view, request, line);
6510         default:
6511                 return request;
6512         }
6514         VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
6515         open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
6517         /* Check whether the staged entry still exists, and close the
6518          * stage view if it doesn't. */
6519         if (!status_exists(&stage_status, stage_line_type)) {
6520                 status_restore(VIEW(REQ_VIEW_STATUS));
6521                 return REQ_VIEW_CLOSE;
6522         }
6524         if (stage_line_type == LINE_STAT_UNTRACKED) {
6525                 if (!suffixcmp(stage_status.new.name, -1, "/")) {
6526                         report("Cannot display a directory");
6527                         return REQ_NONE;
6528                 }
6530                 if (!prepare_update_file(view, stage_status.new.name)) {
6531                         report("Failed to open file: %s", strerror(errno));
6532                         return REQ_NONE;
6533                 }
6534         }
6535         open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
6537         return REQ_NONE;
6540 static struct view_ops stage_ops = {
6541         "line",
6542         NULL,
6543         NULL,
6544         pager_read,
6545         pager_draw,
6546         stage_request,
6547         pager_grep,
6548         pager_select,
6549 };
6552 /*
6553  * Revision graph
6554  */
6556 struct commit {
6557         char id[SIZEOF_REV];            /* SHA1 ID. */
6558         char title[128];                /* First line of the commit message. */
6559         const char *author;             /* Author of the commit. */
6560         struct time time;               /* Date from the author ident. */
6561         struct ref_list *refs;          /* Repository references. */
6562         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
6563         size_t graph_size;              /* The width of the graph array. */
6564         bool has_parents;               /* Rewritten --parents seen. */
6565 };
6567 /* Size of rev graph with no  "padding" columns */
6568 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6570 struct rev_graph {
6571         struct rev_graph *prev, *next, *parents;
6572         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
6573         size_t size;
6574         struct commit *commit;
6575         size_t pos;
6576         unsigned int boundary:1;
6577 };
6579 /* Parents of the commit being visualized. */
6580 static struct rev_graph graph_parents[4];
6582 /* The current stack of revisions on the graph. */
6583 static struct rev_graph graph_stacks[4] = {
6584         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
6585         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
6586         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
6587         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
6588 };
6590 static inline bool
6591 graph_parent_is_merge(struct rev_graph *graph)
6593         return graph->parents->size > 1;
6596 static inline void
6597 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
6599         struct commit *commit = graph->commit;
6601         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
6602                 commit->graph[commit->graph_size++] = symbol;
6605 static void
6606 clear_rev_graph(struct rev_graph *graph)
6608         graph->boundary = 0;
6609         graph->size = graph->pos = 0;
6610         graph->commit = NULL;
6611         memset(graph->parents, 0, sizeof(*graph->parents));
6614 static void
6615 done_rev_graph(struct rev_graph *graph)
6617         if (graph_parent_is_merge(graph) &&
6618             graph->pos < graph->size - 1 &&
6619             graph->next->size == graph->size + graph->parents->size - 1) {
6620                 size_t i = graph->pos + graph->parents->size - 1;
6622                 graph->commit->graph_size = i * 2;
6623                 while (i < graph->next->size - 1) {
6624                         append_to_rev_graph(graph, ' ');
6625                         append_to_rev_graph(graph, '\\');
6626                         i++;
6627                 }
6628         }
6630         clear_rev_graph(graph);
6633 static void
6634 push_rev_graph(struct rev_graph *graph, const char *parent)
6636         int i;
6638         /* "Collapse" duplicate parents lines.
6639          *
6640          * FIXME: This needs to also update update the drawn graph but
6641          * for now it just serves as a method for pruning graph lines. */
6642         for (i = 0; i < graph->size; i++)
6643                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
6644                         return;
6646         if (graph->size < SIZEOF_REVITEMS) {
6647                 string_copy_rev(graph->rev[graph->size++], parent);
6648         }
6651 static chtype
6652 get_rev_graph_symbol(struct rev_graph *graph)
6654         chtype symbol;
6656         if (graph->boundary)
6657                 symbol = REVGRAPH_BOUND;
6658         else if (graph->parents->size == 0)
6659                 symbol = REVGRAPH_INIT;
6660         else if (graph_parent_is_merge(graph))
6661                 symbol = REVGRAPH_MERGE;
6662         else if (graph->pos >= graph->size)
6663                 symbol = REVGRAPH_BRANCH;
6664         else
6665                 symbol = REVGRAPH_COMMIT;
6667         return symbol;
6670 static void
6671 draw_rev_graph(struct rev_graph *graph)
6673         struct rev_filler {
6674                 chtype separator, line;
6675         };
6676         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
6677         static struct rev_filler fillers[] = {
6678                 { ' ',  '|' },
6679                 { '`',  '.' },
6680                 { '\'', ' ' },
6681                 { '/',  ' ' },
6682         };
6683         chtype symbol = get_rev_graph_symbol(graph);
6684         struct rev_filler *filler;
6685         size_t i;
6687         fillers[DEFAULT].line = opt_line_graphics ? ACS_VLINE : '|';
6688         filler = &fillers[DEFAULT];
6690         for (i = 0; i < graph->pos; i++) {
6691                 append_to_rev_graph(graph, filler->line);
6692                 if (graph_parent_is_merge(graph->prev) &&
6693                     graph->prev->pos == i)
6694                         filler = &fillers[RSHARP];
6696                 append_to_rev_graph(graph, filler->separator);
6697         }
6699         /* Place the symbol for this revision. */
6700         append_to_rev_graph(graph, symbol);
6702         if (graph->prev->size > graph->size)
6703                 filler = &fillers[RDIAG];
6704         else
6705                 filler = &fillers[DEFAULT];
6707         i++;
6709         for (; i < graph->size; i++) {
6710                 append_to_rev_graph(graph, filler->separator);
6711                 append_to_rev_graph(graph, filler->line);
6712                 if (graph_parent_is_merge(graph->prev) &&
6713                     i < graph->prev->pos + graph->parents->size)
6714                         filler = &fillers[RSHARP];
6715                 if (graph->prev->size > graph->size)
6716                         filler = &fillers[LDIAG];
6717         }
6719         if (graph->prev->size > graph->size) {
6720                 append_to_rev_graph(graph, filler->separator);
6721                 if (filler->line != ' ')
6722                         append_to_rev_graph(graph, filler->line);
6723         }
6726 /* Prepare the next rev graph */
6727 static void
6728 prepare_rev_graph(struct rev_graph *graph)
6730         size_t i;
6732         /* First, traverse all lines of revisions up to the active one. */
6733         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6734                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6735                         break;
6737                 push_rev_graph(graph->next, graph->rev[graph->pos]);
6738         }
6740         /* Interleave the new revision parent(s). */
6741         for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6742                 push_rev_graph(graph->next, graph->parents->rev[i]);
6744         /* Lastly, put any remaining revisions. */
6745         for (i = graph->pos + 1; i < graph->size; i++)
6746                 push_rev_graph(graph->next, graph->rev[i]);
6749 static void
6750 update_rev_graph(struct view *view, struct rev_graph *graph)
6752         /* If this is the finalizing update ... */
6753         if (graph->commit)
6754                 prepare_rev_graph(graph);
6756         /* Graph visualization needs a one rev look-ahead,
6757          * so the first update doesn't visualize anything. */
6758         if (!graph->prev->commit)
6759                 return;
6761         if (view->lines > 2)
6762                 view->line[view->lines - 3].dirty = 1;
6763         if (view->lines > 1)
6764                 view->line[view->lines - 2].dirty = 1;
6765         draw_rev_graph(graph->prev);
6766         done_rev_graph(graph->prev->prev);
6770 /*
6771  * Main view backend
6772  */
6774 static const char *main_argv[SIZEOF_ARG] = {
6775         "git", "log", "--no-color", "--pretty=raw", "--parents",
6776                 "--topo-order", "%(diffargs)", "%(revargs)",
6777                 "--", "%(fileargs)", NULL
6778 };
6780 static bool
6781 main_draw(struct view *view, struct line *line, unsigned int lineno)
6783         struct commit *commit = line->data;
6785         if (!commit->author)
6786                 return FALSE;
6788         if (opt_date && draw_date(view, &commit->time))
6789                 return TRUE;
6791         if (opt_author && draw_author(view, commit->author))
6792                 return TRUE;
6794         if (opt_rev_graph && commit->graph_size &&
6795             draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6796                 return TRUE;
6798         if (opt_show_refs && commit->refs) {
6799                 size_t i;
6801                 for (i = 0; i < commit->refs->size; i++) {
6802                         struct ref *ref = commit->refs->refs[i];
6803                         enum line_type type;
6805                         if (ref->head)
6806                                 type = LINE_MAIN_HEAD;
6807                         else if (ref->ltag)
6808                                 type = LINE_MAIN_LOCAL_TAG;
6809                         else if (ref->tag)
6810                                 type = LINE_MAIN_TAG;
6811                         else if (ref->tracked)
6812                                 type = LINE_MAIN_TRACKED;
6813                         else if (ref->remote)
6814                                 type = LINE_MAIN_REMOTE;
6815                         else
6816                                 type = LINE_MAIN_REF;
6818                         if (draw_text(view, type, "[", TRUE) ||
6819                             draw_text(view, type, ref->name, TRUE) ||
6820                             draw_text(view, type, "]", TRUE))
6821                                 return TRUE;
6823                         if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6824                                 return TRUE;
6825                 }
6826         }
6828         draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6829         return TRUE;
6832 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6833 static bool
6834 main_read(struct view *view, char *line)
6836         static struct rev_graph *graph = graph_stacks;
6837         enum line_type type;
6838         struct commit *commit;
6840         if (!line) {
6841                 int i;
6843                 if (!view->lines && !view->prev)
6844                         die("No revisions match the given arguments.");
6845                 if (view->lines > 0) {
6846                         commit = view->line[view->lines - 1].data;
6847                         view->line[view->lines - 1].dirty = 1;
6848                         if (!commit->author) {
6849                                 view->lines--;
6850                                 free(commit);
6851                                 graph->commit = NULL;
6852                         }
6853                 }
6854                 update_rev_graph(view, graph);
6856                 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6857                         clear_rev_graph(&graph_stacks[i]);
6858                 return TRUE;
6859         }
6861         type = get_line_type(line);
6862         if (type == LINE_COMMIT) {
6863                 commit = calloc(1, sizeof(struct commit));
6864                 if (!commit)
6865                         return FALSE;
6867                 line += STRING_SIZE("commit ");
6868                 if (*line == '-') {
6869                         graph->boundary = 1;
6870                         line++;
6871                 }
6873                 string_copy_rev(commit->id, line);
6874                 commit->refs = get_ref_list(commit->id);
6875                 graph->commit = commit;
6876                 add_line_data(view, commit, LINE_MAIN_COMMIT);
6878                 while ((line = strchr(line, ' '))) {
6879                         line++;
6880                         push_rev_graph(graph->parents, line);
6881                         commit->has_parents = TRUE;
6882                 }
6883                 return TRUE;
6884         }
6886         if (!view->lines)
6887                 return TRUE;
6888         commit = view->line[view->lines - 1].data;
6890         switch (type) {
6891         case LINE_PARENT:
6892                 if (commit->has_parents)
6893                         break;
6894                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6895                 break;
6897         case LINE_AUTHOR:
6898                 parse_author_line(line + STRING_SIZE("author "),
6899                                   &commit->author, &commit->time);
6900                 update_rev_graph(view, graph);
6901                 graph = graph->next;
6902                 break;
6904         default:
6905                 /* Fill in the commit title if it has not already been set. */
6906                 if (commit->title[0])
6907                         break;
6909                 /* Require titles to start with a non-space character at the
6910                  * offset used by git log. */
6911                 if (strncmp(line, "    ", 4))
6912                         break;
6913                 line += 4;
6914                 /* Well, if the title starts with a whitespace character,
6915                  * try to be forgiving.  Otherwise we end up with no title. */
6916                 while (isspace(*line))
6917                         line++;
6918                 if (*line == '\0')
6919                         break;
6920                 /* FIXME: More graceful handling of titles; append "..." to
6921                  * shortened titles, etc. */
6923                 string_expand(commit->title, sizeof(commit->title), line, 1);
6924                 view->line[view->lines - 1].dirty = 1;
6925         }
6927         return TRUE;
6930 static enum request
6931 main_request(struct view *view, enum request request, struct line *line)
6933         enum open_flags flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
6935         switch (request) {
6936         case REQ_ENTER:
6937                 open_view(view, REQ_VIEW_DIFF, flags);
6938                 break;
6939         case REQ_REFRESH:
6940                 load_refs();
6941                 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6942                 break;
6943         default:
6944                 return request;
6945         }
6947         return REQ_NONE;
6950 static bool
6951 grep_refs(struct ref_list *list, regex_t *regex)
6953         regmatch_t pmatch;
6954         size_t i;
6956         if (!opt_show_refs || !list)
6957                 return FALSE;
6959         for (i = 0; i < list->size; i++) {
6960                 if (regexec(regex, list->refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6961                         return TRUE;
6962         }
6964         return FALSE;
6967 static bool
6968 main_grep(struct view *view, struct line *line)
6970         struct commit *commit = line->data;
6971         const char *text[] = {
6972                 commit->title,
6973                 opt_author ? commit->author : "",
6974                 mkdate(&commit->time, opt_date),
6975                 NULL
6976         };
6978         return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6981 static void
6982 main_select(struct view *view, struct line *line)
6984         struct commit *commit = line->data;
6986         string_copy_rev(view->ref, commit->id);
6987         string_copy_rev(ref_commit, view->ref);
6990 static struct view_ops main_ops = {
6991         "commit",
6992         main_argv,
6993         NULL,
6994         main_read,
6995         main_draw,
6996         main_request,
6997         main_grep,
6998         main_select,
6999 };
7002 /*
7003  * Status management
7004  */
7006 /* Whether or not the curses interface has been initialized. */
7007 static bool cursed = FALSE;
7009 /* Terminal hacks and workarounds. */
7010 static bool use_scroll_redrawwin;
7011 static bool use_scroll_status_wclear;
7013 /* The status window is used for polling keystrokes. */
7014 static WINDOW *status_win;
7016 /* Reading from the prompt? */
7017 static bool input_mode = FALSE;
7019 static bool status_empty = FALSE;
7021 /* Update status and title window. */
7022 static void
7023 report(const char *msg, ...)
7025         struct view *view = display[current_view];
7027         if (input_mode)
7028                 return;
7030         if (!view) {
7031                 char buf[SIZEOF_STR];
7032                 va_list args;
7034                 va_start(args, msg);
7035                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
7036                         buf[sizeof(buf) - 1] = 0;
7037                         buf[sizeof(buf) - 2] = '.';
7038                         buf[sizeof(buf) - 3] = '.';
7039                         buf[sizeof(buf) - 4] = '.';
7040                 }
7041                 va_end(args);
7042                 die("%s", buf);
7043         }
7045         if (!status_empty || *msg) {
7046                 va_list args;
7048                 va_start(args, msg);
7050                 wmove(status_win, 0, 0);
7051                 if (view->has_scrolled && use_scroll_status_wclear)
7052                         wclear(status_win);
7053                 if (*msg) {
7054                         vwprintw(status_win, msg, args);
7055                         status_empty = FALSE;
7056                 } else {
7057                         status_empty = TRUE;
7058                 }
7059                 wclrtoeol(status_win);
7060                 wnoutrefresh(status_win);
7062                 va_end(args);
7063         }
7065         update_view_title(view);
7068 static void
7069 init_display(void)
7071         const char *term;
7072         int x, y;
7074         /* Initialize the curses library */
7075         if (isatty(STDIN_FILENO)) {
7076                 cursed = !!initscr();
7077                 opt_tty = stdin;
7078         } else {
7079                 /* Leave stdin and stdout alone when acting as a pager. */
7080                 opt_tty = fopen("/dev/tty", "r+");
7081                 if (!opt_tty)
7082                         die("Failed to open /dev/tty");
7083                 cursed = !!newterm(NULL, opt_tty, opt_tty);
7084         }
7086         if (!cursed)
7087                 die("Failed to initialize curses");
7089         nonl();         /* Disable conversion and detect newlines from input. */
7090         cbreak();       /* Take input chars one at a time, no wait for \n */
7091         noecho();       /* Don't echo input */
7092         leaveok(stdscr, FALSE);
7094         if (has_colors())
7095                 init_colors();
7097         getmaxyx(stdscr, y, x);
7098         status_win = newwin(1, 0, y - 1, 0);
7099         if (!status_win)
7100                 die("Failed to create status window");
7102         /* Enable keyboard mapping */
7103         keypad(status_win, TRUE);
7104         wbkgdset(status_win, get_line_attr(LINE_STATUS));
7106         TABSIZE = opt_tab_size;
7108         term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
7109         if (term && !strcmp(term, "gnome-terminal")) {
7110                 /* In the gnome-terminal-emulator, the message from
7111                  * scrolling up one line when impossible followed by
7112                  * scrolling down one line causes corruption of the
7113                  * status line. This is fixed by calling wclear. */
7114                 use_scroll_status_wclear = TRUE;
7115                 use_scroll_redrawwin = FALSE;
7117         } else if (term && !strcmp(term, "xrvt-xpm")) {
7118                 /* No problems with full optimizations in xrvt-(unicode)
7119                  * and aterm. */
7120                 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
7122         } else {
7123                 /* When scrolling in (u)xterm the last line in the
7124                  * scrolling direction will update slowly. */
7125                 use_scroll_redrawwin = TRUE;
7126                 use_scroll_status_wclear = FALSE;
7127         }
7130 static int
7131 get_input(int prompt_position)
7133         struct view *view;
7134         int i, key, cursor_y, cursor_x;
7136         if (prompt_position)
7137                 input_mode = TRUE;
7139         while (TRUE) {
7140                 bool loading = FALSE;
7142                 foreach_view (view, i) {
7143                         update_view(view);
7144                         if (view_is_displayed(view) && view->has_scrolled &&
7145                             use_scroll_redrawwin)
7146                                 redrawwin(view->win);
7147                         view->has_scrolled = FALSE;
7148                         if (view->pipe)
7149                                 loading = TRUE;
7150                 }
7152                 /* Update the cursor position. */
7153                 if (prompt_position) {
7154                         getbegyx(status_win, cursor_y, cursor_x);
7155                         cursor_x = prompt_position;
7156                 } else {
7157                         view = display[current_view];
7158                         getbegyx(view->win, cursor_y, cursor_x);
7159                         cursor_x = view->width - 1;
7160                         cursor_y += view->lineno - view->offset;
7161                 }
7162                 setsyx(cursor_y, cursor_x);
7164                 /* Refresh, accept single keystroke of input */
7165                 doupdate();
7166                 nodelay(status_win, loading);
7167                 key = wgetch(status_win);
7169                 /* wgetch() with nodelay() enabled returns ERR when
7170                  * there's no input. */
7171                 if (key == ERR) {
7173                 } else if (key == KEY_RESIZE) {
7174                         int height, width;
7176                         getmaxyx(stdscr, height, width);
7178                         wresize(status_win, 1, width);
7179                         mvwin(status_win, height - 1, 0);
7180                         wnoutrefresh(status_win);
7181                         resize_display();
7182                         redraw_display(TRUE);
7184                 } else {
7185                         input_mode = FALSE;
7186                         return key;
7187                 }
7188         }
7191 static char *
7192 prompt_input(const char *prompt, input_handler handler, void *data)
7194         enum input_status status = INPUT_OK;
7195         static char buf[SIZEOF_STR];
7196         size_t pos = 0;
7198         buf[pos] = 0;
7200         while (status == INPUT_OK || status == INPUT_SKIP) {
7201                 int key;
7203                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
7204                 wclrtoeol(status_win);
7206                 key = get_input(pos + 1);
7207                 switch (key) {
7208                 case KEY_RETURN:
7209                 case KEY_ENTER:
7210                 case '\n':
7211                         status = pos ? INPUT_STOP : INPUT_CANCEL;
7212                         break;
7214                 case KEY_BACKSPACE:
7215                         if (pos > 0)
7216                                 buf[--pos] = 0;
7217                         else
7218                                 status = INPUT_CANCEL;
7219                         break;
7221                 case KEY_ESC:
7222                         status = INPUT_CANCEL;
7223                         break;
7225                 default:
7226                         if (pos >= sizeof(buf)) {
7227                                 report("Input string too long");
7228                                 return NULL;
7229                         }
7231                         status = handler(data, buf, key);
7232                         if (status == INPUT_OK)
7233                                 buf[pos++] = (char) key;
7234                 }
7235         }
7237         /* Clear the status window */
7238         status_empty = FALSE;
7239         report("");
7241         if (status == INPUT_CANCEL)
7242                 return NULL;
7244         buf[pos++] = 0;
7246         return buf;
7249 static enum input_status
7250 prompt_yesno_handler(void *data, char *buf, int c)
7252         if (c == 'y' || c == 'Y')
7253                 return INPUT_STOP;
7254         if (c == 'n' || c == 'N')
7255                 return INPUT_CANCEL;
7256         return INPUT_SKIP;
7259 static bool
7260 prompt_yesno(const char *prompt)
7262         char prompt2[SIZEOF_STR];
7264         if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
7265                 return FALSE;
7267         return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
7270 static enum input_status
7271 read_prompt_handler(void *data, char *buf, int c)
7273         return isprint(c) ? INPUT_OK : INPUT_SKIP;
7276 static char *
7277 read_prompt(const char *prompt)
7279         return prompt_input(prompt, read_prompt_handler, NULL);
7282 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected)
7284         enum input_status status = INPUT_OK;
7285         int size = 0;
7287         while (items[size].text)
7288                 size++;
7290         while (status == INPUT_OK) {
7291                 const struct menu_item *item = &items[*selected];
7292                 int key;
7293                 int i;
7295                 mvwprintw(status_win, 0, 0, "%s (%d of %d) ",
7296                           prompt, *selected + 1, size);
7297                 if (item->hotkey)
7298                         wprintw(status_win, "[%c] ", (char) item->hotkey);
7299                 wprintw(status_win, "%s", item->text);
7300                 wclrtoeol(status_win);
7302                 key = get_input(COLS - 1);
7303                 switch (key) {
7304                 case KEY_RETURN:
7305                 case KEY_ENTER:
7306                 case '\n':
7307                         status = INPUT_STOP;
7308                         break;
7310                 case KEY_LEFT:
7311                 case KEY_UP:
7312                         *selected = *selected - 1;
7313                         if (*selected < 0)
7314                                 *selected = size - 1;
7315                         break;
7317                 case KEY_RIGHT:
7318                 case KEY_DOWN:
7319                         *selected = (*selected + 1) % size;
7320                         break;
7322                 case KEY_ESC:
7323                         status = INPUT_CANCEL;
7324                         break;
7326                 default:
7327                         for (i = 0; items[i].text; i++)
7328                                 if (items[i].hotkey == key) {
7329                                         *selected = i;
7330                                         status = INPUT_STOP;
7331                                         break;
7332                                 }
7333                 }
7334         }
7336         /* Clear the status window */
7337         status_empty = FALSE;
7338         report("");
7340         return status != INPUT_CANCEL;
7343 /*
7344  * Repository properties
7345  */
7347 static struct ref **refs = NULL;
7348 static size_t refs_size = 0;
7349 static struct ref *refs_head = NULL;
7351 static struct ref_list **ref_lists = NULL;
7352 static size_t ref_lists_size = 0;
7354 DEFINE_ALLOCATOR(realloc_refs, struct ref *, 256)
7355 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
7356 DEFINE_ALLOCATOR(realloc_ref_lists, struct ref_list *, 8)
7358 static int
7359 compare_refs(const void *ref1_, const void *ref2_)
7361         const struct ref *ref1 = *(const struct ref **)ref1_;
7362         const struct ref *ref2 = *(const struct ref **)ref2_;
7364         if (ref1->tag != ref2->tag)
7365                 return ref2->tag - ref1->tag;
7366         if (ref1->ltag != ref2->ltag)
7367                 return ref2->ltag - ref2->ltag;
7368         if (ref1->head != ref2->head)
7369                 return ref2->head - ref1->head;
7370         if (ref1->tracked != ref2->tracked)
7371                 return ref2->tracked - ref1->tracked;
7372         if (ref1->remote != ref2->remote)
7373                 return ref2->remote - ref1->remote;
7374         return strcmp(ref1->name, ref2->name);
7377 static void
7378 foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data)
7380         size_t i;
7382         for (i = 0; i < refs_size; i++)
7383                 if (!visitor(data, refs[i]))
7384                         break;
7387 static struct ref *
7388 get_ref_head()
7390         return refs_head;
7393 static struct ref_list *
7394 get_ref_list(const char *id)
7396         struct ref_list *list;
7397         size_t i;
7399         for (i = 0; i < ref_lists_size; i++)
7400                 if (!strcmp(id, ref_lists[i]->id))
7401                         return ref_lists[i];
7403         if (!realloc_ref_lists(&ref_lists, ref_lists_size, 1))
7404                 return NULL;
7405         list = calloc(1, sizeof(*list));
7406         if (!list)
7407                 return NULL;
7409         for (i = 0; i < refs_size; i++) {
7410                 if (!strcmp(id, refs[i]->id) &&
7411                     realloc_refs_list(&list->refs, list->size, 1))
7412                         list->refs[list->size++] = refs[i];
7413         }
7415         if (!list->refs) {
7416                 free(list);
7417                 return NULL;
7418         }
7420         qsort(list->refs, list->size, sizeof(*list->refs), compare_refs);
7421         ref_lists[ref_lists_size++] = list;
7422         return list;
7425 static int
7426 read_ref(char *id, size_t idlen, char *name, size_t namelen)
7428         struct ref *ref = NULL;
7429         bool tag = FALSE;
7430         bool ltag = FALSE;
7431         bool remote = FALSE;
7432         bool tracked = FALSE;
7433         bool head = FALSE;
7434         int from = 0, to = refs_size - 1;
7436         if (!prefixcmp(name, "refs/tags/")) {
7437                 if (!suffixcmp(name, namelen, "^{}")) {
7438                         namelen -= 3;
7439                         name[namelen] = 0;
7440                 } else {
7441                         ltag = TRUE;
7442                 }
7444                 tag = TRUE;
7445                 namelen -= STRING_SIZE("refs/tags/");
7446                 name    += STRING_SIZE("refs/tags/");
7448         } else if (!prefixcmp(name, "refs/remotes/")) {
7449                 remote = TRUE;
7450                 namelen -= STRING_SIZE("refs/remotes/");
7451                 name    += STRING_SIZE("refs/remotes/");
7452                 tracked  = !strcmp(opt_remote, name);
7454         } else if (!prefixcmp(name, "refs/heads/")) {
7455                 namelen -= STRING_SIZE("refs/heads/");
7456                 name    += STRING_SIZE("refs/heads/");
7457                 if (!strncmp(opt_head, name, namelen))
7458                         return OK;
7460         } else if (!strcmp(name, "HEAD")) {
7461                 head     = TRUE;
7462                 if (*opt_head) {
7463                         namelen  = strlen(opt_head);
7464                         name     = opt_head;
7465                 }
7466         }
7468         /* If we are reloading or it's an annotated tag, replace the
7469          * previous SHA1 with the resolved commit id; relies on the fact
7470          * git-ls-remote lists the commit id of an annotated tag right
7471          * before the commit id it points to. */
7472         while (from <= to) {
7473                 size_t pos = (to + from) / 2;
7474                 int cmp = strcmp(name, refs[pos]->name);
7476                 if (!cmp) {
7477                         ref = refs[pos];
7478                         break;
7479                 }
7481                 if (cmp < 0)
7482                         to = pos - 1;
7483                 else
7484                         from = pos + 1;
7485         }
7487         if (!ref) {
7488                 if (!realloc_refs(&refs, refs_size, 1))
7489                         return ERR;
7490                 ref = calloc(1, sizeof(*ref) + namelen);
7491                 if (!ref)
7492                         return ERR;
7493                 memmove(refs + from + 1, refs + from,
7494                         (refs_size - from) * sizeof(*refs));
7495                 refs[from] = ref;
7496                 strncpy(ref->name, name, namelen);
7497                 refs_size++;
7498         }
7500         ref->head = head;
7501         ref->tag = tag;
7502         ref->ltag = ltag;
7503         ref->remote = remote;
7504         ref->tracked = tracked;
7505         string_copy_rev(ref->id, id);
7507         if (head)
7508                 refs_head = ref;
7509         return OK;
7512 static int
7513 load_refs(void)
7515         const char *head_argv[] = {
7516                 "git", "symbolic-ref", "HEAD", NULL
7517         };
7518         static const char *ls_remote_argv[SIZEOF_ARG] = {
7519                 "git", "ls-remote", opt_git_dir, NULL
7520         };
7521         static bool init = FALSE;
7522         size_t i;
7524         if (!init) {
7525                 if (!argv_from_env(ls_remote_argv, "TIG_LS_REMOTE"))
7526                         die("TIG_LS_REMOTE contains too many arguments");
7527                 init = TRUE;
7528         }
7530         if (!*opt_git_dir)
7531                 return OK;
7533         if (io_run_buf(head_argv, opt_head, sizeof(opt_head)) &&
7534             !prefixcmp(opt_head, "refs/heads/")) {
7535                 char *offset = opt_head + STRING_SIZE("refs/heads/");
7537                 memmove(opt_head, offset, strlen(offset) + 1);
7538         }
7540         refs_head = NULL;
7541         for (i = 0; i < refs_size; i++)
7542                 refs[i]->id[0] = 0;
7544         if (io_run_load(ls_remote_argv, "\t", read_ref) == ERR)
7545                 return ERR;
7547         /* Update the ref lists to reflect changes. */
7548         for (i = 0; i < ref_lists_size; i++) {
7549                 struct ref_list *list = ref_lists[i];
7550                 size_t old, new;
7552                 for (old = new = 0; old < list->size; old++)
7553                         if (!strcmp(list->id, list->refs[old]->id))
7554                                 list->refs[new++] = list->refs[old];
7555                 list->size = new;
7556         }
7558         return OK;
7561 static void
7562 set_remote_branch(const char *name, const char *value, size_t valuelen)
7564         if (!strcmp(name, ".remote")) {
7565                 string_ncopy(opt_remote, value, valuelen);
7567         } else if (*opt_remote && !strcmp(name, ".merge")) {
7568                 size_t from = strlen(opt_remote);
7570                 if (!prefixcmp(value, "refs/heads/"))
7571                         value += STRING_SIZE("refs/heads/");
7573                 if (!string_format_from(opt_remote, &from, "/%s", value))
7574                         opt_remote[0] = 0;
7575         }
7578 static void
7579 set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
7581         const char *argv[SIZEOF_ARG] = { name, "=" };
7582         int argc = 1 + (cmd == option_set_command);
7583         int error = ERR;
7585         if (!argv_from_string(argv, &argc, value))
7586                 config_msg = "Too many option arguments";
7587         else
7588                 error = cmd(argc, argv);
7590         if (error == ERR)
7591                 warn("Option 'tig.%s': %s", name, config_msg);
7594 static bool
7595 set_environment_variable(const char *name, const char *value)
7597         size_t len = strlen(name) + 1 + strlen(value) + 1;
7598         char *env = malloc(len);
7600         if (env &&
7601             string_nformat(env, len, NULL, "%s=%s", name, value) &&
7602             putenv(env) == 0)
7603                 return TRUE;
7604         free(env);
7605         return FALSE;
7608 static void
7609 set_work_tree(const char *value)
7611         char cwd[SIZEOF_STR];
7613         if (!getcwd(cwd, sizeof(cwd)))
7614                 die("Failed to get cwd path: %s", strerror(errno));
7615         if (chdir(opt_git_dir) < 0)
7616                 die("Failed to chdir(%s): %s", strerror(errno));
7617         if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7618                 die("Failed to get git path: %s", strerror(errno));
7619         if (chdir(cwd) < 0)
7620                 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7621         if (chdir(value) < 0)
7622                 die("Failed to chdir(%s): %s", value, strerror(errno));
7623         if (!getcwd(cwd, sizeof(cwd)))
7624                 die("Failed to get cwd path: %s", strerror(errno));
7625         if (!set_environment_variable("GIT_WORK_TREE", cwd))
7626                 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7627         if (!set_environment_variable("GIT_DIR", opt_git_dir))
7628                 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7629         opt_is_inside_work_tree = TRUE;
7632 static int
7633 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
7635         if (!strcmp(name, "i18n.commitencoding"))
7636                 string_ncopy(opt_encoding, value, valuelen);
7638         else if (!strcmp(name, "core.editor"))
7639                 string_ncopy(opt_editor, value, valuelen);
7641         else if (!strcmp(name, "core.worktree"))
7642                 set_work_tree(value);
7644         else if (!prefixcmp(name, "tig.color."))
7645                 set_repo_config_option(name + 10, value, option_color_command);
7647         else if (!prefixcmp(name, "tig.bind."))
7648                 set_repo_config_option(name + 9, value, option_bind_command);
7650         else if (!prefixcmp(name, "tig."))
7651                 set_repo_config_option(name + 4, value, option_set_command);
7653         else if (*opt_head && !prefixcmp(name, "branch.") &&
7654                  !strncmp(name + 7, opt_head, strlen(opt_head)))
7655                 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7657         return OK;
7660 static int
7661 load_git_config(void)
7663         const char *config_list_argv[] = { "git", "config", "--list", NULL };
7665         return io_run_load(config_list_argv, "=", read_repo_config_option);
7668 static int
7669 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
7671         if (!opt_git_dir[0]) {
7672                 string_ncopy(opt_git_dir, name, namelen);
7674         } else if (opt_is_inside_work_tree == -1) {
7675                 /* This can be 3 different values depending on the
7676                  * version of git being used. If git-rev-parse does not
7677                  * understand --is-inside-work-tree it will simply echo
7678                  * the option else either "true" or "false" is printed.
7679                  * Default to true for the unknown case. */
7680                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7682         } else if (*name == '.') {
7683                 string_ncopy(opt_cdup, name, namelen);
7685         } else {
7686                 string_ncopy(opt_prefix, name, namelen);
7687         }
7689         return OK;
7692 static int
7693 load_repo_info(void)
7695         const char *rev_parse_argv[] = {
7696                 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7697                         "--show-cdup", "--show-prefix", NULL
7698         };
7700         return io_run_load(rev_parse_argv, "=", read_repo_info);
7704 /*
7705  * Main
7706  */
7708 static const char usage[] =
7709 "tig " TIG_VERSION " (" __DATE__ ")\n"
7710 "\n"
7711 "Usage: tig        [options] [revs] [--] [paths]\n"
7712 "   or: tig show   [options] [revs] [--] [paths]\n"
7713 "   or: tig blame  [rev] path\n"
7714 "   or: tig status\n"
7715 "   or: tig <      [git command output]\n"
7716 "\n"
7717 "Options:\n"
7718 "  -v, --version   Show version and exit\n"
7719 "  -h, --help      Show help message and exit";
7721 static void __NORETURN
7722 quit(int sig)
7724         /* XXX: Restore tty modes and let the OS cleanup the rest! */
7725         if (cursed)
7726                 endwin();
7727         exit(0);
7730 static void __NORETURN
7731 die(const char *err, ...)
7733         va_list args;
7735         endwin();
7737         va_start(args, err);
7738         fputs("tig: ", stderr);
7739         vfprintf(stderr, err, args);
7740         fputs("\n", stderr);
7741         va_end(args);
7743         exit(1);
7746 static void
7747 warn(const char *msg, ...)
7749         va_list args;
7751         va_start(args, msg);
7752         fputs("tig warning: ", stderr);
7753         vfprintf(stderr, msg, args);
7754         fputs("\n", stderr);
7755         va_end(args);
7758 static const char ***filter_args;
7760 static int
7761 read_filter_args(char *name, size_t namelen, char *value, size_t valuelen)
7763         return argv_append(filter_args, name) ? OK : ERR;
7766 static void
7767 filter_rev_parse(const char ***args, const char *arg1, const char *arg2, const char *argv[])
7769         const char *rev_parse_argv[SIZEOF_ARG] = { "git", "rev-parse", arg1, arg2 };
7770         const char **all_argv = NULL;
7772         filter_args = args;
7773         if (!argv_append_array(&all_argv, rev_parse_argv) ||
7774             !argv_append_array(&all_argv, argv) ||
7775             !io_run_load(all_argv, "\n", read_filter_args) == ERR)
7776                 die("Failed to split arguments");
7777         argv_free(all_argv);
7778         free(all_argv);
7781 static void
7782 filter_options(const char *argv[])
7784         filter_rev_parse(&opt_file_args, "--no-revs", "--no-flags", argv);
7785         filter_rev_parse(&opt_diff_args, "--no-revs", "--flags", argv);
7786         filter_rev_parse(&opt_rev_args, "--symbolic", "--revs-only", argv);
7789 static enum request
7790 parse_options(int argc, const char *argv[])
7792         enum request request = REQ_VIEW_MAIN;
7793         const char *subcommand;
7794         bool seen_dashdash = FALSE;
7795         const char **filter_argv = NULL;
7796         int i;
7798         if (!isatty(STDIN_FILENO))
7799                 return REQ_VIEW_PAGER;
7801         if (argc <= 1)
7802                 return REQ_VIEW_MAIN;
7804         subcommand = argv[1];
7805         if (!strcmp(subcommand, "status")) {
7806                 if (argc > 2)
7807                         warn("ignoring arguments after `%s'", subcommand);
7808                 return REQ_VIEW_STATUS;
7810         } else if (!strcmp(subcommand, "blame")) {
7811                 if (argc <= 2 || argc > 4)
7812                         die("invalid number of options to blame\n\n%s", usage);
7814                 i = 2;
7815                 if (argc == 4) {
7816                         string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7817                         i++;
7818                 }
7820                 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7821                 return REQ_VIEW_BLAME;
7823         } else if (!strcmp(subcommand, "show")) {
7824                 request = REQ_VIEW_DIFF;
7826         } else {
7827                 subcommand = NULL;
7828         }
7830         for (i = 1 + !!subcommand; i < argc; i++) {
7831                 const char *opt = argv[i];
7833                 if (seen_dashdash) {
7834                         argv_append(&opt_file_args, opt);
7835                         continue;
7837                 } else if (!strcmp(opt, "--")) {
7838                         seen_dashdash = TRUE;
7839                         continue;
7841                 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7842                         printf("tig version %s\n", TIG_VERSION);
7843                         quit(0);
7845                 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7846                         printf("%s\n", usage);
7847                         quit(0);
7849                 } else if (!strcmp(opt, "--all")) {
7850                         argv_append(&opt_rev_args, opt);
7851                         continue;
7852                 }
7854                 if (!argv_append(&filter_argv, opt))
7855                         die("command too long");
7856         }
7858         if (filter_argv)
7859                 filter_options(filter_argv);
7861         return request;
7864 int
7865 main(int argc, const char *argv[])
7867         const char *codeset = "UTF-8";
7868         enum request request = parse_options(argc, argv);
7869         struct view *view;
7870         size_t i;
7872         signal(SIGINT, quit);
7873         signal(SIGPIPE, SIG_IGN);
7875         if (setlocale(LC_ALL, "")) {
7876                 codeset = nl_langinfo(CODESET);
7877         }
7879         if (load_repo_info() == ERR)
7880                 die("Failed to load repo info.");
7882         if (load_options() == ERR)
7883                 die("Failed to load user config.");
7885         if (load_git_config() == ERR)
7886                 die("Failed to load repo config.");
7888         /* Require a git repository unless when running in pager mode. */
7889         if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7890                 die("Not a git repository");
7892         if (*opt_encoding && strcmp(codeset, "UTF-8")) {
7893                 opt_iconv_in = iconv_open("UTF-8", opt_encoding);
7894                 if (opt_iconv_in == ICONV_NONE)
7895                         die("Failed to initialize character set conversion");
7896         }
7898         if (codeset && strcmp(codeset, "UTF-8")) {
7899                 opt_iconv_out = iconv_open(codeset, "UTF-8");
7900                 if (opt_iconv_out == ICONV_NONE)
7901                         die("Failed to initialize character set conversion");
7902         }
7904         if (load_refs() == ERR)
7905                 die("Failed to load refs.");
7907         foreach_view (view, i) {
7908                 if (getenv(view->cmd_env))
7909                         warn("Use of the %s environment variable is deprecated,"
7910                              " use options or TIG_DIFF_ARGS instead",
7911                              view->cmd_env);
7912                 if (!argv_from_env(view->ops->argv, view->cmd_env))
7913                         die("Too many arguments in the `%s` environment variable",
7914                             view->cmd_env);
7915         }
7917         init_display();
7919         while (view_driver(display[current_view], request)) {
7920                 int key = get_input(0);
7922                 view = display[current_view];
7923                 request = get_keybinding(view->keymap, key);
7925                 /* Some low-level request handling. This keeps access to
7926                  * status_win restricted. */
7927                 switch (request) {
7928                 case REQ_NONE:
7929                         report("Unknown key, press %s for help",
7930                                get_key(view->keymap, REQ_VIEW_HELP));
7931                         break;
7932                 case REQ_PROMPT:
7933                 {
7934                         char *cmd = read_prompt(":");
7936                         if (cmd && isdigit(*cmd)) {
7937                                 int lineno = view->lineno + 1;
7939                                 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7940                                         select_view_line(view, lineno - 1);
7941                                         report("");
7942                                 } else {
7943                                         report("Unable to parse '%s' as a line number", cmd);
7944                                 }
7946                         } else if (cmd) {
7947                                 struct view *next = VIEW(REQ_VIEW_PAGER);
7948                                 const char *argv[SIZEOF_ARG] = { "git" };
7949                                 int argc = 1;
7951                                 /* When running random commands, initially show the
7952                                  * command in the title. However, it maybe later be
7953                                  * overwritten if a commit line is selected. */
7954                                 string_ncopy(next->ref, cmd, strlen(cmd));
7956                                 if (!argv_from_string(argv, &argc, cmd)) {
7957                                         report("Too many arguments");
7958                                 } else if (!prepare_update(next, argv, NULL)) {
7959                                         report("Failed to format command");
7960                                 } else {
7961                                         open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7962                                 }
7963                         }
7965                         request = REQ_NONE;
7966                         break;
7967                 }
7968                 case REQ_SEARCH:
7969                 case REQ_SEARCH_BACK:
7970                 {
7971                         const char *prompt = request == REQ_SEARCH ? "/" : "?";
7972                         char *search = read_prompt(prompt);
7974                         if (search)
7975                                 string_ncopy(opt_search, search, strlen(search));
7976                         else if (*opt_search)
7977                                 request = request == REQ_SEARCH ?
7978                                         REQ_FIND_NEXT :
7979                                         REQ_FIND_PREV;
7980                         else
7981                                 request = REQ_NONE;
7982                         break;
7983                 }
7984                 default:
7985                         break;
7986                 }
7987         }
7989         quit(0);
7991         return 0;