Code

fd19f5403e8351618b37e2e1a97bcc1af9a6906b
[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 typedef int (*io_read_fn)(char *, size_t, char *, size_t, void *data);
1049 static int
1050 io_load(struct io *io, const char *separators,
1051         io_read_fn read_property, void *data)
1053         char *name;
1054         int state = OK;
1056         while (state == OK && (name = io_get(io, '\n', TRUE))) {
1057                 char *value;
1058                 size_t namelen;
1059                 size_t valuelen;
1061                 name = chomp_string(name);
1062                 namelen = strcspn(name, separators);
1064                 if (name[namelen]) {
1065                         name[namelen] = 0;
1066                         value = chomp_string(name + namelen + 1);
1067                         valuelen = strlen(value);
1069                 } else {
1070                         value = "";
1071                         valuelen = 0;
1072                 }
1074                 state = read_property(name, namelen, value, valuelen, data);
1075         }
1077         if (state != ERR && io_error(io))
1078                 state = ERR;
1079         io_done(io);
1081         return state;
1084 static int
1085 io_run_load(const char **argv, const char *separators,
1086             io_read_fn read_property, void *data)
1088         struct io io;
1090         if (!io_run(&io, IO_RD, NULL, argv))
1091                 return ERR;
1092         return io_load(&io, separators, read_property, data);
1096 /*
1097  * User requests
1098  */
1100 #define REQ_INFO \
1101         /* XXX: Keep the view request first and in sync with views[]. */ \
1102         REQ_GROUP("View switching") \
1103         REQ_(VIEW_MAIN,         "Show main view"), \
1104         REQ_(VIEW_DIFF,         "Show diff view"), \
1105         REQ_(VIEW_LOG,          "Show log view"), \
1106         REQ_(VIEW_TREE,         "Show tree view"), \
1107         REQ_(VIEW_BLOB,         "Show blob view"), \
1108         REQ_(VIEW_BLAME,        "Show blame view"), \
1109         REQ_(VIEW_BRANCH,       "Show branch view"), \
1110         REQ_(VIEW_HELP,         "Show help page"), \
1111         REQ_(VIEW_PAGER,        "Show pager view"), \
1112         REQ_(VIEW_STATUS,       "Show status view"), \
1113         REQ_(VIEW_STAGE,        "Show stage view"), \
1114         \
1115         REQ_GROUP("View manipulation") \
1116         REQ_(ENTER,             "Enter current line and scroll"), \
1117         REQ_(NEXT,              "Move to next"), \
1118         REQ_(PREVIOUS,          "Move to previous"), \
1119         REQ_(PARENT,            "Move to parent"), \
1120         REQ_(VIEW_NEXT,         "Move focus to next view"), \
1121         REQ_(REFRESH,           "Reload and refresh"), \
1122         REQ_(MAXIMIZE,          "Maximize the current view"), \
1123         REQ_(VIEW_CLOSE,        "Close the current view"), \
1124         REQ_(QUIT,              "Close all views and quit"), \
1125         \
1126         REQ_GROUP("View specific requests") \
1127         REQ_(STATUS_UPDATE,     "Update file status"), \
1128         REQ_(STATUS_REVERT,     "Revert file changes"), \
1129         REQ_(STATUS_MERGE,      "Merge file using external tool"), \
1130         REQ_(STAGE_NEXT,        "Find next chunk to stage"), \
1131         \
1132         REQ_GROUP("Cursor navigation") \
1133         REQ_(MOVE_UP,           "Move cursor one line up"), \
1134         REQ_(MOVE_DOWN,         "Move cursor one line down"), \
1135         REQ_(MOVE_PAGE_DOWN,    "Move cursor one page down"), \
1136         REQ_(MOVE_PAGE_UP,      "Move cursor one page up"), \
1137         REQ_(MOVE_FIRST_LINE,   "Move cursor to first line"), \
1138         REQ_(MOVE_LAST_LINE,    "Move cursor to last line"), \
1139         \
1140         REQ_GROUP("Scrolling") \
1141         REQ_(SCROLL_FIRST_COL,  "Scroll to the first line columns"), \
1142         REQ_(SCROLL_LEFT,       "Scroll two columns left"), \
1143         REQ_(SCROLL_RIGHT,      "Scroll two columns right"), \
1144         REQ_(SCROLL_LINE_UP,    "Scroll one line up"), \
1145         REQ_(SCROLL_LINE_DOWN,  "Scroll one line down"), \
1146         REQ_(SCROLL_PAGE_UP,    "Scroll one page up"), \
1147         REQ_(SCROLL_PAGE_DOWN,  "Scroll one page down"), \
1148         \
1149         REQ_GROUP("Searching") \
1150         REQ_(SEARCH,            "Search the view"), \
1151         REQ_(SEARCH_BACK,       "Search backwards in the view"), \
1152         REQ_(FIND_NEXT,         "Find next search match"), \
1153         REQ_(FIND_PREV,         "Find previous search match"), \
1154         \
1155         REQ_GROUP("Option manipulation") \
1156         REQ_(OPTIONS,           "Open option menu"), \
1157         REQ_(TOGGLE_LINENO,     "Toggle line numbers"), \
1158         REQ_(TOGGLE_DATE,       "Toggle date display"), \
1159         REQ_(TOGGLE_AUTHOR,     "Toggle author display"), \
1160         REQ_(TOGGLE_REV_GRAPH,  "Toggle revision graph visualization"), \
1161         REQ_(TOGGLE_REFS,       "Toggle reference display (tags/branches)"), \
1162         REQ_(TOGGLE_SORT_ORDER, "Toggle ascending/descending sort order"), \
1163         REQ_(TOGGLE_SORT_FIELD, "Toggle field to sort by"), \
1164         \
1165         REQ_GROUP("Misc") \
1166         REQ_(PROMPT,            "Bring up the prompt"), \
1167         REQ_(SCREEN_REDRAW,     "Redraw the screen"), \
1168         REQ_(SHOW_VERSION,      "Show version information"), \
1169         REQ_(STOP_LOADING,      "Stop all loading views"), \
1170         REQ_(EDIT,              "Open in editor"), \
1171         REQ_(NONE,              "Do nothing")
1174 /* User action requests. */
1175 enum request {
1176 #define REQ_GROUP(help)
1177 #define REQ_(req, help) REQ_##req
1179         /* Offset all requests to avoid conflicts with ncurses getch values. */
1180         REQ_UNKNOWN = KEY_MAX + 1,
1181         REQ_OFFSET,
1182         REQ_INFO
1184 #undef  REQ_GROUP
1185 #undef  REQ_
1186 };
1188 struct request_info {
1189         enum request request;
1190         const char *name;
1191         int namelen;
1192         const char *help;
1193 };
1195 static const struct request_info req_info[] = {
1196 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
1197 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
1198         REQ_INFO
1199 #undef  REQ_GROUP
1200 #undef  REQ_
1201 };
1203 static enum request
1204 get_request(const char *name)
1206         int namelen = strlen(name);
1207         int i;
1209         for (i = 0; i < ARRAY_SIZE(req_info); i++)
1210                 if (enum_equals(req_info[i], name, namelen))
1211                         return req_info[i].request;
1213         return REQ_UNKNOWN;
1217 /*
1218  * Options
1219  */
1221 /* Option and state variables. */
1222 static enum date opt_date               = DATE_DEFAULT;
1223 static enum author opt_author           = AUTHOR_DEFAULT;
1224 static bool opt_line_number             = FALSE;
1225 static bool opt_line_graphics           = TRUE;
1226 static bool opt_rev_graph               = FALSE;
1227 static bool opt_show_refs               = TRUE;
1228 static bool opt_untracked_dirs_content  = TRUE;
1229 static int opt_num_interval             = 5;
1230 static double opt_hscroll               = 0.50;
1231 static double opt_scale_split_view      = 2.0 / 3.0;
1232 static int opt_tab_size                 = 8;
1233 static int opt_author_cols              = AUTHOR_COLS;
1234 static char opt_path[SIZEOF_STR]        = "";
1235 static char opt_file[SIZEOF_STR]        = "";
1236 static char opt_ref[SIZEOF_REF]         = "";
1237 static char opt_head[SIZEOF_REF]        = "";
1238 static char opt_remote[SIZEOF_REF]      = "";
1239 static char opt_encoding[20]            = "UTF-8";
1240 static iconv_t opt_iconv_in             = ICONV_NONE;
1241 static iconv_t opt_iconv_out            = ICONV_NONE;
1242 static char opt_search[SIZEOF_STR]      = "";
1243 static char opt_cdup[SIZEOF_STR]        = "";
1244 static char opt_prefix[SIZEOF_STR]      = "";
1245 static char opt_git_dir[SIZEOF_STR]     = "";
1246 static signed char opt_is_inside_work_tree      = -1; /* set to TRUE or FALSE */
1247 static char opt_editor[SIZEOF_STR]      = "";
1248 static FILE *opt_tty                    = NULL;
1249 static const char **opt_diff_argv       = NULL;
1250 static const char **opt_rev_argv        = NULL;
1251 static const char **opt_file_argv       = NULL;
1253 #define is_initial_commit()     (!get_ref_head())
1254 #define is_head_commit(rev)     (!strcmp((rev), "HEAD") || (get_ref_head() && !strcmp(rev, get_ref_head()->id)))
1257 /*
1258  * Line-oriented content detection.
1259  */
1261 #define LINE_INFO \
1262 LINE(DIFF_HEADER,  "diff --git ",       COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1263 LINE(DIFF_CHUNK,   "@@",                COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1264 LINE(DIFF_ADD,     "+",                 COLOR_GREEN,    COLOR_DEFAULT,  0), \
1265 LINE(DIFF_DEL,     "-",                 COLOR_RED,      COLOR_DEFAULT,  0), \
1266 LINE(DIFF_INDEX,        "index ",         COLOR_BLUE,   COLOR_DEFAULT,  0), \
1267 LINE(DIFF_OLDMODE,      "old file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1268 LINE(DIFF_NEWMODE,      "new file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1269 LINE(DIFF_COPY_FROM,    "copy from",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
1270 LINE(DIFF_COPY_TO,      "copy to",        COLOR_YELLOW, COLOR_DEFAULT,  0), \
1271 LINE(DIFF_RENAME_FROM,  "rename from",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
1272 LINE(DIFF_RENAME_TO,    "rename to",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
1273 LINE(DIFF_SIMILARITY,   "similarity ",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
1274 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1275 LINE(DIFF_TREE,         "diff-tree ",     COLOR_BLUE,   COLOR_DEFAULT,  0), \
1276 LINE(PP_AUTHOR,    "Author: ",          COLOR_CYAN,     COLOR_DEFAULT,  0), \
1277 LINE(PP_COMMIT,    "Commit: ",          COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1278 LINE(PP_MERGE,     "Merge: ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
1279 LINE(PP_DATE,      "Date:   ",          COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1280 LINE(PP_ADATE,     "AuthorDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1281 LINE(PP_CDATE,     "CommitDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1282 LINE(PP_REFS,      "Refs: ",            COLOR_RED,      COLOR_DEFAULT,  0), \
1283 LINE(COMMIT,       "commit ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
1284 LINE(PARENT,       "parent ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
1285 LINE(TREE,         "tree ",             COLOR_BLUE,     COLOR_DEFAULT,  0), \
1286 LINE(AUTHOR,       "author ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
1287 LINE(COMMITTER,    "committer ",        COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1288 LINE(SIGNOFF,      "    Signed-off-by", COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1289 LINE(ACKED,        "    Acked-by",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1290 LINE(TESTED,       "    Tested-by",     COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1291 LINE(REVIEWED,     "    Reviewed-by",   COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1292 LINE(DEFAULT,      "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
1293 LINE(CURSOR,       "",                  COLOR_WHITE,    COLOR_GREEN,    A_BOLD), \
1294 LINE(STATUS,       "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
1295 LINE(DELIMITER,    "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1296 LINE(DATE,         "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
1297 LINE(MODE,         "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1298 LINE(LINE_NUMBER,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1299 LINE(TITLE_BLUR,   "",                  COLOR_WHITE,    COLOR_BLUE,     0), \
1300 LINE(TITLE_FOCUS,  "",                  COLOR_WHITE,    COLOR_BLUE,     A_BOLD), \
1301 LINE(MAIN_COMMIT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
1302 LINE(MAIN_TAG,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
1303 LINE(MAIN_LOCAL_TAG,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1304 LINE(MAIN_REMOTE,  "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1305 LINE(MAIN_TRACKED, "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_BOLD), \
1306 LINE(MAIN_REF,     "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1307 LINE(MAIN_HEAD,    "",                  COLOR_CYAN,     COLOR_DEFAULT,  A_BOLD), \
1308 LINE(MAIN_REVGRAPH,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1309 LINE(TREE_HEAD,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_BOLD), \
1310 LINE(TREE_DIR,     "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_NORMAL), \
1311 LINE(TREE_FILE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
1312 LINE(STAT_HEAD,    "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1313 LINE(STAT_SECTION, "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1314 LINE(STAT_NONE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
1315 LINE(STAT_STAGED,  "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1316 LINE(STAT_UNSTAGED,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1317 LINE(STAT_UNTRACKED,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1318 LINE(HELP_KEYMAP,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1319 LINE(HELP_GROUP,   "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
1320 LINE(BLAME_ID,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0)
1322 enum line_type {
1323 #define LINE(type, line, fg, bg, attr) \
1324         LINE_##type
1325         LINE_INFO,
1326         LINE_NONE
1327 #undef  LINE
1328 };
1330 struct line_info {
1331         const char *name;       /* Option name. */
1332         int namelen;            /* Size of option name. */
1333         const char *line;       /* The start of line to match. */
1334         int linelen;            /* Size of string to match. */
1335         int fg, bg, attr;       /* Color and text attributes for the lines. */
1336 };
1338 static struct line_info line_info[] = {
1339 #define LINE(type, line, fg, bg, attr) \
1340         { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1341         LINE_INFO
1342 #undef  LINE
1343 };
1345 static enum line_type
1346 get_line_type(const char *line)
1348         int linelen = strlen(line);
1349         enum line_type type;
1351         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1352                 /* Case insensitive search matches Signed-off-by lines better. */
1353                 if (linelen >= line_info[type].linelen &&
1354                     !strncasecmp(line_info[type].line, line, line_info[type].linelen))
1355                         return type;
1357         return LINE_DEFAULT;
1360 static inline int
1361 get_line_attr(enum line_type type)
1363         assert(type < ARRAY_SIZE(line_info));
1364         return COLOR_PAIR(type) | line_info[type].attr;
1367 static struct line_info *
1368 get_line_info(const char *name)
1370         size_t namelen = strlen(name);
1371         enum line_type type;
1373         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1374                 if (enum_equals(line_info[type], name, namelen))
1375                         return &line_info[type];
1377         return NULL;
1380 static void
1381 init_colors(void)
1383         int default_bg = line_info[LINE_DEFAULT].bg;
1384         int default_fg = line_info[LINE_DEFAULT].fg;
1385         enum line_type type;
1387         start_color();
1389         if (assume_default_colors(default_fg, default_bg) == ERR) {
1390                 default_bg = COLOR_BLACK;
1391                 default_fg = COLOR_WHITE;
1392         }
1394         for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1395                 struct line_info *info = &line_info[type];
1396                 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1397                 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1399                 init_pair(type, fg, bg);
1400         }
1403 struct line {
1404         enum line_type type;
1406         /* State flags */
1407         unsigned int selected:1;
1408         unsigned int dirty:1;
1409         unsigned int cleareol:1;
1410         unsigned int other:16;
1412         void *data;             /* User data */
1413 };
1416 /*
1417  * Keys
1418  */
1420 struct keybinding {
1421         int alias;
1422         enum request request;
1423 };
1425 static struct keybinding default_keybindings[] = {
1426         /* View switching */
1427         { 'm',          REQ_VIEW_MAIN },
1428         { 'd',          REQ_VIEW_DIFF },
1429         { 'l',          REQ_VIEW_LOG },
1430         { 't',          REQ_VIEW_TREE },
1431         { 'f',          REQ_VIEW_BLOB },
1432         { 'B',          REQ_VIEW_BLAME },
1433         { 'H',          REQ_VIEW_BRANCH },
1434         { 'p',          REQ_VIEW_PAGER },
1435         { 'h',          REQ_VIEW_HELP },
1436         { 'S',          REQ_VIEW_STATUS },
1437         { 'c',          REQ_VIEW_STAGE },
1439         /* View manipulation */
1440         { 'q',          REQ_VIEW_CLOSE },
1441         { KEY_TAB,      REQ_VIEW_NEXT },
1442         { KEY_RETURN,   REQ_ENTER },
1443         { KEY_UP,       REQ_PREVIOUS },
1444         { KEY_CTL('P'), REQ_PREVIOUS },
1445         { KEY_DOWN,     REQ_NEXT },
1446         { KEY_CTL('N'), REQ_NEXT },
1447         { 'R',          REQ_REFRESH },
1448         { KEY_F(5),     REQ_REFRESH },
1449         { 'O',          REQ_MAXIMIZE },
1451         /* Cursor navigation */
1452         { 'k',          REQ_MOVE_UP },
1453         { 'j',          REQ_MOVE_DOWN },
1454         { KEY_HOME,     REQ_MOVE_FIRST_LINE },
1455         { KEY_END,      REQ_MOVE_LAST_LINE },
1456         { KEY_NPAGE,    REQ_MOVE_PAGE_DOWN },
1457         { KEY_CTL('D'), REQ_MOVE_PAGE_DOWN },
1458         { ' ',          REQ_MOVE_PAGE_DOWN },
1459         { KEY_PPAGE,    REQ_MOVE_PAGE_UP },
1460         { KEY_CTL('U'), REQ_MOVE_PAGE_UP },
1461         { 'b',          REQ_MOVE_PAGE_UP },
1462         { '-',          REQ_MOVE_PAGE_UP },
1464         /* Scrolling */
1465         { '|',          REQ_SCROLL_FIRST_COL },
1466         { KEY_LEFT,     REQ_SCROLL_LEFT },
1467         { KEY_RIGHT,    REQ_SCROLL_RIGHT },
1468         { KEY_IC,       REQ_SCROLL_LINE_UP },
1469         { KEY_CTL('Y'), REQ_SCROLL_LINE_UP },
1470         { KEY_DC,       REQ_SCROLL_LINE_DOWN },
1471         { KEY_CTL('E'), REQ_SCROLL_LINE_DOWN },
1472         { 'w',          REQ_SCROLL_PAGE_UP },
1473         { 's',          REQ_SCROLL_PAGE_DOWN },
1475         /* Searching */
1476         { '/',          REQ_SEARCH },
1477         { '?',          REQ_SEARCH_BACK },
1478         { 'n',          REQ_FIND_NEXT },
1479         { 'N',          REQ_FIND_PREV },
1481         /* Misc */
1482         { 'Q',          REQ_QUIT },
1483         { 'z',          REQ_STOP_LOADING },
1484         { 'v',          REQ_SHOW_VERSION },
1485         { 'r',          REQ_SCREEN_REDRAW },
1486         { KEY_CTL('L'), REQ_SCREEN_REDRAW },
1487         { 'o',          REQ_OPTIONS },
1488         { '.',          REQ_TOGGLE_LINENO },
1489         { 'D',          REQ_TOGGLE_DATE },
1490         { 'A',          REQ_TOGGLE_AUTHOR },
1491         { 'g',          REQ_TOGGLE_REV_GRAPH },
1492         { 'F',          REQ_TOGGLE_REFS },
1493         { 'I',          REQ_TOGGLE_SORT_ORDER },
1494         { 'i',          REQ_TOGGLE_SORT_FIELD },
1495         { ':',          REQ_PROMPT },
1496         { 'u',          REQ_STATUS_UPDATE },
1497         { '!',          REQ_STATUS_REVERT },
1498         { 'M',          REQ_STATUS_MERGE },
1499         { '@',          REQ_STAGE_NEXT },
1500         { ',',          REQ_PARENT },
1501         { 'e',          REQ_EDIT },
1502 };
1504 #define KEYMAP_INFO \
1505         KEYMAP_(GENERIC), \
1506         KEYMAP_(MAIN), \
1507         KEYMAP_(DIFF), \
1508         KEYMAP_(LOG), \
1509         KEYMAP_(TREE), \
1510         KEYMAP_(BLOB), \
1511         KEYMAP_(BLAME), \
1512         KEYMAP_(BRANCH), \
1513         KEYMAP_(PAGER), \
1514         KEYMAP_(HELP), \
1515         KEYMAP_(STATUS), \
1516         KEYMAP_(STAGE)
1518 enum keymap {
1519 #define KEYMAP_(name) KEYMAP_##name
1520         KEYMAP_INFO
1521 #undef  KEYMAP_
1522 };
1524 static const struct enum_map keymap_table[] = {
1525 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1526         KEYMAP_INFO
1527 #undef  KEYMAP_
1528 };
1530 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1532 struct keybinding_table {
1533         struct keybinding *data;
1534         size_t size;
1535 };
1537 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1539 static void
1540 add_keybinding(enum keymap keymap, enum request request, int key)
1542         struct keybinding_table *table = &keybindings[keymap];
1543         size_t i;
1545         for (i = 0; i < keybindings[keymap].size; i++) {
1546                 if (keybindings[keymap].data[i].alias == key) {
1547                         keybindings[keymap].data[i].request = request;
1548                         return;
1549                 }
1550         }
1552         table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1553         if (!table->data)
1554                 die("Failed to allocate keybinding");
1555         table->data[table->size].alias = key;
1556         table->data[table->size++].request = request;
1558         if (request == REQ_NONE && keymap == KEYMAP_GENERIC) {
1559                 int i;
1561                 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1562                         if (default_keybindings[i].alias == key)
1563                                 default_keybindings[i].request = REQ_NONE;
1564         }
1567 /* Looks for a key binding first in the given map, then in the generic map, and
1568  * lastly in the default keybindings. */
1569 static enum request
1570 get_keybinding(enum keymap keymap, int key)
1572         size_t i;
1574         for (i = 0; i < keybindings[keymap].size; i++)
1575                 if (keybindings[keymap].data[i].alias == key)
1576                         return keybindings[keymap].data[i].request;
1578         for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1579                 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1580                         return keybindings[KEYMAP_GENERIC].data[i].request;
1582         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1583                 if (default_keybindings[i].alias == key)
1584                         return default_keybindings[i].request;
1586         return (enum request) key;
1590 struct key {
1591         const char *name;
1592         int value;
1593 };
1595 static const struct key key_table[] = {
1596         { "Enter",      KEY_RETURN },
1597         { "Space",      ' ' },
1598         { "Backspace",  KEY_BACKSPACE },
1599         { "Tab",        KEY_TAB },
1600         { "Escape",     KEY_ESC },
1601         { "Left",       KEY_LEFT },
1602         { "Right",      KEY_RIGHT },
1603         { "Up",         KEY_UP },
1604         { "Down",       KEY_DOWN },
1605         { "Insert",     KEY_IC },
1606         { "Delete",     KEY_DC },
1607         { "Hash",       '#' },
1608         { "Home",       KEY_HOME },
1609         { "End",        KEY_END },
1610         { "PageUp",     KEY_PPAGE },
1611         { "PageDown",   KEY_NPAGE },
1612         { "F1",         KEY_F(1) },
1613         { "F2",         KEY_F(2) },
1614         { "F3",         KEY_F(3) },
1615         { "F4",         KEY_F(4) },
1616         { "F5",         KEY_F(5) },
1617         { "F6",         KEY_F(6) },
1618         { "F7",         KEY_F(7) },
1619         { "F8",         KEY_F(8) },
1620         { "F9",         KEY_F(9) },
1621         { "F10",        KEY_F(10) },
1622         { "F11",        KEY_F(11) },
1623         { "F12",        KEY_F(12) },
1624 };
1626 static int
1627 get_key_value(const char *name)
1629         int i;
1631         for (i = 0; i < ARRAY_SIZE(key_table); i++)
1632                 if (!strcasecmp(key_table[i].name, name))
1633                         return key_table[i].value;
1635         if (strlen(name) == 2 && name[0] == '^' && isprint(*name))
1636                 return (int)name[1] & 0x1f;
1637         if (strlen(name) == 1 && isprint(*name))
1638                 return (int) *name;
1639         return ERR;
1642 static const char *
1643 get_key_name(int key_value)
1645         static char key_char[] = "'X'\0";
1646         const char *seq = NULL;
1647         int key;
1649         for (key = 0; key < ARRAY_SIZE(key_table); key++)
1650                 if (key_table[key].value == key_value)
1651                         seq = key_table[key].name;
1653         if (seq == NULL && key_value < 0x7f) {
1654                 char *s = key_char + 1;
1656                 if (key_value >= 0x20) {
1657                         *s++ = key_value;
1658                 } else {
1659                         *s++ = '^';
1660                         *s++ = 0x40 | (key_value & 0x1f);
1661                 }
1662                 *s++ = '\'';
1663                 *s++ = '\0';
1664                 seq = key_char;
1665         }
1667         return seq ? seq : "(no key)";
1670 static bool
1671 append_key(char *buf, size_t *pos, const struct keybinding *keybinding)
1673         const char *sep = *pos > 0 ? ", " : "";
1674         const char *keyname = get_key_name(keybinding->alias);
1676         return string_nformat(buf, BUFSIZ, pos, "%s%s", sep, keyname);
1679 static bool
1680 append_keymap_request_keys(char *buf, size_t *pos, enum request request,
1681                            enum keymap keymap, bool all)
1683         int i;
1685         for (i = 0; i < keybindings[keymap].size; i++) {
1686                 if (keybindings[keymap].data[i].request == request) {
1687                         if (!append_key(buf, pos, &keybindings[keymap].data[i]))
1688                                 return FALSE;
1689                         if (!all)
1690                                 break;
1691                 }
1692         }
1694         return TRUE;
1697 #define get_key(keymap, request) get_keys(keymap, request, FALSE)
1699 static const char *
1700 get_keys(enum keymap keymap, enum request request, bool all)
1702         static char buf[BUFSIZ];
1703         size_t pos = 0;
1704         int i;
1706         buf[pos] = 0;
1708         if (!append_keymap_request_keys(buf, &pos, request, keymap, all))
1709                 return "Too many keybindings!";
1710         if (pos > 0 && !all)
1711                 return buf;
1713         if (keymap != KEYMAP_GENERIC) {
1714                 /* Only the generic keymap includes the default keybindings when
1715                  * listing all keys. */
1716                 if (all)
1717                         return buf;
1719                 if (!append_keymap_request_keys(buf, &pos, request, KEYMAP_GENERIC, all))
1720                         return "Too many keybindings!";
1721                 if (pos)
1722                         return buf;
1723         }
1725         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1726                 if (default_keybindings[i].request == request) {
1727                         if (!append_key(buf, &pos, &default_keybindings[i]))
1728                                 return "Too many keybindings!";
1729                         if (!all)
1730                                 return buf;
1731                 }
1732         }
1734         return buf;
1737 struct run_request {
1738         enum keymap keymap;
1739         int key;
1740         const char **argv;
1741 };
1743 static struct run_request *run_request;
1744 static size_t run_requests;
1746 DEFINE_ALLOCATOR(realloc_run_requests, struct run_request, 8)
1748 static enum request
1749 add_run_request(enum keymap keymap, int key, const char **argv)
1751         struct run_request *req;
1753         if (!realloc_run_requests(&run_request, run_requests, 1))
1754                 return REQ_NONE;
1756         req = &run_request[run_requests];
1757         req->keymap = keymap;
1758         req->key = key;
1759         req->argv = NULL;
1761         if (!argv_copy(&req->argv, argv))
1762                 return REQ_NONE;
1764         return REQ_NONE + ++run_requests;
1767 static struct run_request *
1768 get_run_request(enum request request)
1770         if (request <= REQ_NONE)
1771                 return NULL;
1772         return &run_request[request - REQ_NONE - 1];
1775 static void
1776 add_builtin_run_requests(void)
1778         const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1779         const char *checkout[] = { "git", "checkout", "%(branch)", NULL };
1780         const char *commit[] = { "git", "commit", NULL };
1781         const char *gc[] = { "git", "gc", NULL };
1782         struct run_request reqs[] = {
1783                 { KEYMAP_MAIN,    'C', cherry_pick },
1784                 { KEYMAP_STATUS,  'C', commit },
1785                 { KEYMAP_BRANCH,  'C', checkout },
1786                 { KEYMAP_GENERIC, 'G', gc },
1787         };
1788         int i;
1790         for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1791                 enum request req = get_keybinding(reqs[i].keymap, reqs[i].key);
1793                 if (req != reqs[i].key)
1794                         continue;
1795                 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argv);
1796                 if (req != REQ_NONE)
1797                         add_keybinding(reqs[i].keymap, req, reqs[i].key);
1798         }
1801 /*
1802  * User config file handling.
1803  */
1805 #define OPT_ERR_INFO \
1806         OPT_ERR_(INTEGER_VALUE_OUT_OF_BOUND, "Integer value out of bound"), \
1807         OPT_ERR_(INVALID_STEP_VALUE, "Invalid step value"), \
1808         OPT_ERR_(NO_OPTION_VALUE, "No option value"), \
1809         OPT_ERR_(NO_VALUE_ASSIGNED, "No value assigned"), \
1810         OPT_ERR_(OBSOLETE_REQUEST_NAME, "Obsolete request name"), \
1811         OPT_ERR_(TOO_MANY_OPTION_ARGUMENTS, "Too many option arguments"), \
1812         OPT_ERR_(UNKNOWN_ATTRIBUTE, "Unknown attribute"), \
1813         OPT_ERR_(UNKNOWN_COLOR, "Unknown color"), \
1814         OPT_ERR_(UNKNOWN_COLOR_NAME, "Unknown color name"), \
1815         OPT_ERR_(UNKNOWN_KEY, "Unknown key"), \
1816         OPT_ERR_(UNKNOWN_KEY_MAP, "Unknown key map"), \
1817         OPT_ERR_(UNKNOWN_OPTION_COMMAND, "Unknown option command"), \
1818         OPT_ERR_(UNKNOWN_REQUEST_NAME, "Unknown request name"), \
1819         OPT_ERR_(UNKNOWN_VARIABLE_NAME, "Unknown variable name"), \
1820         OPT_ERR_(UNMATCHED_QUOTATION, "Unmatched quotation"), \
1821         OPT_ERR_(WRONG_NUMBER_OF_ARGUMENTS, "Wrong number of arguments"),
1823 enum option_code {
1824 #define OPT_ERR_(name, msg) OPT_ERR_ ## name
1825         OPT_ERR_INFO
1826 #undef  OPT_ERR_
1827         OPT_OK
1828 };
1830 static const char *option_errors[] = {
1831 #define OPT_ERR_(name, msg) msg
1832         OPT_ERR_INFO
1833 #undef  OPT_ERR_
1834 };
1836 static const struct enum_map color_map[] = {
1837 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1838         COLOR_MAP(DEFAULT),
1839         COLOR_MAP(BLACK),
1840         COLOR_MAP(BLUE),
1841         COLOR_MAP(CYAN),
1842         COLOR_MAP(GREEN),
1843         COLOR_MAP(MAGENTA),
1844         COLOR_MAP(RED),
1845         COLOR_MAP(WHITE),
1846         COLOR_MAP(YELLOW),
1847 };
1849 static const struct enum_map attr_map[] = {
1850 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1851         ATTR_MAP(NORMAL),
1852         ATTR_MAP(BLINK),
1853         ATTR_MAP(BOLD),
1854         ATTR_MAP(DIM),
1855         ATTR_MAP(REVERSE),
1856         ATTR_MAP(STANDOUT),
1857         ATTR_MAP(UNDERLINE),
1858 };
1860 #define set_attribute(attr, name)       map_enum(attr, attr_map, name)
1862 static enum option_code
1863 parse_step(double *opt, const char *arg)
1865         *opt = atoi(arg);
1866         if (!strchr(arg, '%'))
1867                 return OPT_OK;
1869         /* "Shift down" so 100% and 1 does not conflict. */
1870         *opt = (*opt - 1) / 100;
1871         if (*opt >= 1.0) {
1872                 *opt = 0.99;
1873                 return OPT_ERR_INVALID_STEP_VALUE;
1874         }
1875         if (*opt < 0.0) {
1876                 *opt = 1;
1877                 return OPT_ERR_INVALID_STEP_VALUE;
1878         }
1879         return OPT_OK;
1882 static enum option_code
1883 parse_int(int *opt, const char *arg, int min, int max)
1885         int value = atoi(arg);
1887         if (min <= value && value <= max) {
1888                 *opt = value;
1889                 return OPT_OK;
1890         }
1892         return OPT_ERR_INTEGER_VALUE_OUT_OF_BOUND;
1895 static bool
1896 set_color(int *color, const char *name)
1898         if (map_enum(color, color_map, name))
1899                 return TRUE;
1900         if (!prefixcmp(name, "color"))
1901                 return parse_int(color, name + 5, 0, 255) == OK;
1902         return FALSE;
1905 /* Wants: object fgcolor bgcolor [attribute] */
1906 static enum option_code
1907 option_color_command(int argc, const char *argv[])
1909         struct line_info *info;
1911         if (argc < 3)
1912                 return OPT_ERR_WRONG_NUMBER_OF_ARGUMENTS;
1914         info = get_line_info(argv[0]);
1915         if (!info) {
1916                 static const struct enum_map obsolete[] = {
1917                         ENUM_MAP("main-delim",  LINE_DELIMITER),
1918                         ENUM_MAP("main-date",   LINE_DATE),
1919                         ENUM_MAP("main-author", LINE_AUTHOR),
1920                 };
1921                 int index;
1923                 if (!map_enum(&index, obsolete, argv[0])) {
1924                         return OPT_ERR_UNKNOWN_COLOR_NAME;
1925                 }
1926                 info = &line_info[index];
1927         }
1929         if (!set_color(&info->fg, argv[1]) ||
1930             !set_color(&info->bg, argv[2])) {
1931                 return OPT_ERR_UNKNOWN_COLOR;
1932         }
1934         info->attr = 0;
1935         while (argc-- > 3) {
1936                 int attr;
1938                 if (!set_attribute(&attr, argv[argc])) {
1939                         return OPT_ERR_UNKNOWN_ATTRIBUTE;
1940                 }
1941                 info->attr |= attr;
1942         }
1944         return OPT_OK;
1947 static enum option_code
1948 parse_bool(bool *opt, const char *arg)
1950         *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1951                 ? TRUE : FALSE;
1952         return OPT_OK;
1955 static enum option_code
1956 parse_enum_do(unsigned int *opt, const char *arg,
1957               const struct enum_map *map, size_t map_size)
1959         bool is_true;
1961         assert(map_size > 1);
1963         if (map_enum_do(map, map_size, (int *) opt, arg))
1964                 return OPT_OK;
1966         parse_bool(&is_true, arg);
1967         *opt = is_true ? map[1].value : map[0].value;
1968         return OPT_OK;
1971 #define parse_enum(opt, arg, map) \
1972         parse_enum_do(opt, arg, map, ARRAY_SIZE(map))
1974 static enum option_code
1975 parse_string(char *opt, const char *arg, size_t optsize)
1977         int arglen = strlen(arg);
1979         switch (arg[0]) {
1980         case '\"':
1981         case '\'':
1982                 if (arglen == 1 || arg[arglen - 1] != arg[0])
1983                         return OPT_ERR_UNMATCHED_QUOTATION;
1984                 arg += 1; arglen -= 2;
1985         default:
1986                 string_ncopy_do(opt, optsize, arg, arglen);
1987                 return OPT_OK;
1988         }
1991 /* Wants: name = value */
1992 static enum option_code
1993 option_set_command(int argc, const char *argv[])
1995         if (argc != 3)
1996                 return OPT_ERR_WRONG_NUMBER_OF_ARGUMENTS;
1998         if (strcmp(argv[1], "="))
1999                 return OPT_ERR_NO_VALUE_ASSIGNED;
2001         if (!strcmp(argv[0], "show-author"))
2002                 return parse_enum(&opt_author, argv[2], author_map);
2004         if (!strcmp(argv[0], "show-date"))
2005                 return parse_enum(&opt_date, argv[2], date_map);
2007         if (!strcmp(argv[0], "show-rev-graph"))
2008                 return parse_bool(&opt_rev_graph, argv[2]);
2010         if (!strcmp(argv[0], "show-refs"))
2011                 return parse_bool(&opt_show_refs, argv[2]);
2013         if (!strcmp(argv[0], "show-line-numbers"))
2014                 return parse_bool(&opt_line_number, argv[2]);
2016         if (!strcmp(argv[0], "line-graphics"))
2017                 return parse_bool(&opt_line_graphics, argv[2]);
2019         if (!strcmp(argv[0], "line-number-interval"))
2020                 return parse_int(&opt_num_interval, argv[2], 1, 1024);
2022         if (!strcmp(argv[0], "author-width"))
2023                 return parse_int(&opt_author_cols, argv[2], 0, 1024);
2025         if (!strcmp(argv[0], "horizontal-scroll"))
2026                 return parse_step(&opt_hscroll, argv[2]);
2028         if (!strcmp(argv[0], "split-view-height"))
2029                 return parse_step(&opt_scale_split_view, argv[2]);
2031         if (!strcmp(argv[0], "tab-size"))
2032                 return parse_int(&opt_tab_size, argv[2], 1, 1024);
2034         if (!strcmp(argv[0], "commit-encoding"))
2035                 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
2037         if (!strcmp(argv[0], "status-untracked-dirs"))
2038                 return parse_bool(&opt_untracked_dirs_content, argv[2]);
2040         return OPT_ERR_UNKNOWN_VARIABLE_NAME;
2043 /* Wants: mode request key */
2044 static enum option_code
2045 option_bind_command(int argc, const char *argv[])
2047         enum request request;
2048         int keymap = -1;
2049         int key;
2051         if (argc < 3)
2052                 return OPT_ERR_WRONG_NUMBER_OF_ARGUMENTS;
2054         if (!set_keymap(&keymap, argv[0]))
2055                 return OPT_ERR_UNKNOWN_KEY_MAP;
2057         key = get_key_value(argv[1]);
2058         if (key == ERR)
2059                 return OPT_ERR_UNKNOWN_KEY;
2061         request = get_request(argv[2]);
2062         if (request == REQ_UNKNOWN) {
2063                 static const struct enum_map obsolete[] = {
2064                         ENUM_MAP("cherry-pick",         REQ_NONE),
2065                         ENUM_MAP("screen-resize",       REQ_NONE),
2066                         ENUM_MAP("tree-parent",         REQ_PARENT),
2067                 };
2068                 int alias;
2070                 if (map_enum(&alias, obsolete, argv[2])) {
2071                         if (alias != REQ_NONE)
2072                                 add_keybinding(keymap, alias, key);
2073                         return OPT_ERR_OBSOLETE_REQUEST_NAME;
2074                 }
2075         }
2076         if (request == REQ_UNKNOWN && *argv[2]++ == '!')
2077                 request = add_run_request(keymap, key, argv + 2);
2078         if (request == REQ_UNKNOWN)
2079                 return OPT_ERR_UNKNOWN_REQUEST_NAME;
2081         add_keybinding(keymap, request, key);
2083         return OPT_OK;
2086 static enum option_code
2087 set_option(const char *opt, char *value)
2089         const char *argv[SIZEOF_ARG];
2090         int argc = 0;
2092         if (!argv_from_string(argv, &argc, value))
2093                 return OPT_ERR_TOO_MANY_OPTION_ARGUMENTS;
2095         if (!strcmp(opt, "color"))
2096                 return option_color_command(argc, argv);
2098         if (!strcmp(opt, "set"))
2099                 return option_set_command(argc, argv);
2101         if (!strcmp(opt, "bind"))
2102                 return option_bind_command(argc, argv);
2104         return OPT_ERR_UNKNOWN_OPTION_COMMAND;
2107 struct config_state {
2108         int lineno;
2109         bool errors;
2110 };
2112 static int
2113 read_option(char *opt, size_t optlen, char *value, size_t valuelen, void *data)
2115         struct config_state *config = data;
2116         enum option_code status = OPT_ERR_NO_OPTION_VALUE;
2118         config->lineno++;
2120         /* Check for comment markers, since read_properties() will
2121          * only ensure opt and value are split at first " \t". */
2122         optlen = strcspn(opt, "#");
2123         if (optlen == 0)
2124                 return OK;
2126         if (opt[optlen] == 0) {
2127                 /* Look for comment endings in the value. */
2128                 size_t len = strcspn(value, "#");
2130                 if (len < valuelen) {
2131                         valuelen = len;
2132                         value[valuelen] = 0;
2133                 }
2135                 status = set_option(opt, value);
2136         }
2138         if (status != OPT_OK) {
2139                 warn("Error on line %d, near '%.*s': %s",
2140                      config->lineno, (int) optlen, opt, option_errors[status]);
2141                 config->errors = TRUE;
2142         }
2144         /* Always keep going if errors are encountered. */
2145         return OK;
2148 static void
2149 load_option_file(const char *path)
2151         struct config_state config = { 0, FALSE };
2152         struct io io;
2154         /* It's OK that the file doesn't exist. */
2155         if (!io_open(&io, "%s", path))
2156                 return;
2158         if (io_load(&io, " \t", read_option, &config) == ERR ||
2159             config.errors == TRUE)
2160                 warn("Errors while loading %s.", path);
2163 static int
2164 load_options(void)
2166         const char *home = getenv("HOME");
2167         const char *tigrc_user = getenv("TIGRC_USER");
2168         const char *tigrc_system = getenv("TIGRC_SYSTEM");
2169         const char *tig_diff_opts = getenv("TIG_DIFF_OPTS");
2170         char buf[SIZEOF_STR];
2172         if (!tigrc_system)
2173                 tigrc_system = SYSCONFDIR "/tigrc";
2174         load_option_file(tigrc_system);
2176         if (!tigrc_user) {
2177                 if (!home || !string_format(buf, "%s/.tigrc", home))
2178                         return ERR;
2179                 tigrc_user = buf;
2180         }
2181         load_option_file(tigrc_user);
2183         /* Add _after_ loading config files to avoid adding run requests
2184          * that conflict with keybindings. */
2185         add_builtin_run_requests();
2187         if (!opt_diff_argv && tig_diff_opts && *tig_diff_opts) {
2188                 static const char *diff_opts[SIZEOF_ARG] = { NULL };
2189                 int argc = 0;
2191                 if (!string_format(buf, "%s", tig_diff_opts) ||
2192                     !argv_from_string(diff_opts, &argc, buf))
2193                         die("TIG_DIFF_OPTS contains too many arguments");
2194                 else if (!argv_copy(&opt_diff_argv, diff_opts))
2195                         die("Failed to format TIG_DIFF_OPTS arguments");
2196         }
2198         return OK;
2202 /*
2203  * The viewer
2204  */
2206 struct view;
2207 struct view_ops;
2209 /* The display array of active views and the index of the current view. */
2210 static struct view *display[2];
2211 static unsigned int current_view;
2213 #define foreach_displayed_view(view, i) \
2214         for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
2216 #define displayed_views()       (display[1] != NULL ? 2 : 1)
2218 /* Current head and commit ID */
2219 static char ref_blob[SIZEOF_REF]        = "";
2220 static char ref_commit[SIZEOF_REF]      = "HEAD";
2221 static char ref_head[SIZEOF_REF]        = "HEAD";
2222 static char ref_branch[SIZEOF_REF]      = "";
2224 enum view_type {
2225         VIEW_MAIN,
2226         VIEW_DIFF,
2227         VIEW_LOG,
2228         VIEW_TREE,
2229         VIEW_BLOB,
2230         VIEW_BLAME,
2231         VIEW_BRANCH,
2232         VIEW_HELP,
2233         VIEW_PAGER,
2234         VIEW_STATUS,
2235         VIEW_STAGE,
2236 };
2238 struct view {
2239         enum view_type type;    /* View type */
2240         const char *name;       /* View name */
2241         const char *cmd_env;    /* Command line set via environment */
2242         const char *id;         /* Points to either of ref_{head,commit,blob} */
2244         struct view_ops *ops;   /* View operations */
2246         enum keymap keymap;     /* What keymap does this view have */
2247         bool git_dir;           /* Whether the view requires a git directory. */
2249         char ref[SIZEOF_REF];   /* Hovered commit reference */
2250         char vid[SIZEOF_REF];   /* View ID. Set to id member when updating. */
2252         int height, width;      /* The width and height of the main window */
2253         WINDOW *win;            /* The main window */
2254         WINDOW *title;          /* The title window living below the main window */
2256         /* Navigation */
2257         unsigned long offset;   /* Offset of the window top */
2258         unsigned long yoffset;  /* Offset from the window side. */
2259         unsigned long lineno;   /* Current line number */
2260         unsigned long p_offset; /* Previous offset of the window top */
2261         unsigned long p_yoffset;/* Previous offset from the window side */
2262         unsigned long p_lineno; /* Previous current line number */
2263         bool p_restore;         /* Should the previous position be restored. */
2265         /* Searching */
2266         char grep[SIZEOF_STR];  /* Search string */
2267         regex_t *regex;         /* Pre-compiled regexp */
2269         /* If non-NULL, points to the view that opened this view. If this view
2270          * is closed tig will switch back to the parent view. */
2271         struct view *parent;
2272         struct view *prev;
2274         /* Buffering */
2275         size_t lines;           /* Total number of lines */
2276         struct line *line;      /* Line index */
2277         unsigned int digits;    /* Number of digits in the lines member. */
2279         /* Drawing */
2280         struct line *curline;   /* Line currently being drawn. */
2281         enum line_type curtype; /* Attribute currently used for drawing. */
2282         unsigned long col;      /* Column when drawing. */
2283         bool has_scrolled;      /* View was scrolled. */
2285         /* Loading */
2286         const char **argv;      /* Shell command arguments. */
2287         const char *dir;        /* Directory from which to execute. */
2288         struct io io;
2289         struct io *pipe;
2290         time_t start_time;
2291         time_t update_secs;
2292 };
2294 struct view_ops {
2295         /* What type of content being displayed. Used in the title bar. */
2296         const char *type;
2297         /* Default command arguments. */
2298         const char **argv;
2299         /* Open and reads in all view content. */
2300         bool (*open)(struct view *view);
2301         /* Read one line; updates view->line. */
2302         bool (*read)(struct view *view, char *data);
2303         /* Draw one line; @lineno must be < view->height. */
2304         bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
2305         /* Depending on view handle a special requests. */
2306         enum request (*request)(struct view *view, enum request request, struct line *line);
2307         /* Search for regexp in a line. */
2308         bool (*grep)(struct view *view, struct line *line);
2309         /* Select line */
2310         void (*select)(struct view *view, struct line *line);
2311         /* Prepare view for loading */
2312         bool (*prepare)(struct view *view);
2313 };
2315 static struct view_ops blame_ops;
2316 static struct view_ops blob_ops;
2317 static struct view_ops diff_ops;
2318 static struct view_ops help_ops;
2319 static struct view_ops log_ops;
2320 static struct view_ops main_ops;
2321 static struct view_ops pager_ops;
2322 static struct view_ops stage_ops;
2323 static struct view_ops status_ops;
2324 static struct view_ops tree_ops;
2325 static struct view_ops branch_ops;
2327 #define VIEW_STR(type, name, env, ref, ops, map, git) \
2328         { type, name, #env, ref, ops, map, git }
2330 #define VIEW_(id, name, ops, git, ref) \
2331         VIEW_STR(VIEW_##id, name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
2333 static struct view views[] = {
2334         VIEW_(MAIN,   "main",   &main_ops,   TRUE,  ref_head),
2335         VIEW_(DIFF,   "diff",   &diff_ops,   TRUE,  ref_commit),
2336         VIEW_(LOG,    "log",    &log_ops,    TRUE,  ref_head),
2337         VIEW_(TREE,   "tree",   &tree_ops,   TRUE,  ref_commit),
2338         VIEW_(BLOB,   "blob",   &blob_ops,   TRUE,  ref_blob),
2339         VIEW_(BLAME,  "blame",  &blame_ops,  TRUE,  ref_commit),
2340         VIEW_(BRANCH, "branch", &branch_ops, TRUE,  ref_head),
2341         VIEW_(HELP,   "help",   &help_ops,   FALSE, ""),
2342         VIEW_(PAGER,  "pager",  &pager_ops,  FALSE, ""),
2343         VIEW_(STATUS, "status", &status_ops, TRUE,  ""),
2344         VIEW_(STAGE,  "stage",  &stage_ops,  TRUE,  ""),
2345 };
2347 #define VIEW(req)       (&views[(req) - REQ_OFFSET - 1])
2349 #define foreach_view(view, i) \
2350         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2352 #define view_is_displayed(view) \
2353         (view == display[0] || view == display[1])
2355 static enum request
2356 view_request(struct view *view, enum request request)
2358         if (!view || !view->lines)
2359                 return request;
2360         return view->ops->request(view, request, &view->line[view->lineno]);
2364 /*
2365  * View drawing.
2366  */
2368 static inline void
2369 set_view_attr(struct view *view, enum line_type type)
2371         if (!view->curline->selected && view->curtype != type) {
2372                 (void) wattrset(view->win, get_line_attr(type));
2373                 wchgat(view->win, -1, 0, type, NULL);
2374                 view->curtype = type;
2375         }
2378 static int
2379 draw_chars(struct view *view, enum line_type type, const char *string,
2380            int max_len, bool use_tilde)
2382         static char out_buffer[BUFSIZ * 2];
2383         int len = 0;
2384         int col = 0;
2385         int trimmed = FALSE;
2386         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2388         if (max_len <= 0)
2389                 return 0;
2391         len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde, opt_tab_size);
2393         set_view_attr(view, type);
2394         if (len > 0) {
2395                 if (opt_iconv_out != ICONV_NONE) {
2396                         ICONV_CONST char *inbuf = (ICONV_CONST char *) string;
2397                         size_t inlen = len + 1;
2399                         char *outbuf = out_buffer;
2400                         size_t outlen = sizeof(out_buffer);
2402                         size_t ret;
2404                         ret = iconv(opt_iconv_out, &inbuf, &inlen, &outbuf, &outlen);
2405                         if (ret != (size_t) -1) {
2406                                 string = out_buffer;
2407                                 len = sizeof(out_buffer) - outlen;
2408                         }
2409                 }
2411                 waddnstr(view->win, string, len);
2413                 if (trimmed && use_tilde) {
2414                         set_view_attr(view, LINE_DELIMITER);
2415                         waddch(view->win, '~');
2416                         col++;
2417                 }
2418         }
2420         return col;
2423 static int
2424 draw_space(struct view *view, enum line_type type, int max, int spaces)
2426         static char space[] = "                    ";
2427         int col = 0;
2429         spaces = MIN(max, spaces);
2431         while (spaces > 0) {
2432                 int len = MIN(spaces, sizeof(space) - 1);
2434                 col += draw_chars(view, type, space, len, FALSE);
2435                 spaces -= len;
2436         }
2438         return col;
2441 static bool
2442 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
2444         char text[SIZEOF_STR];
2446         do {
2447                 size_t pos = string_expand(text, sizeof(text), string, opt_tab_size);
2449                 view->col += draw_chars(view, type, text, view->width + view->yoffset - view->col, trim);
2450                 string += pos;
2451         } while (*string && view->width + view->yoffset > view->col);
2453         return view->width + view->yoffset <= view->col;
2456 static bool
2457 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
2459         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2460         int max = view->width + view->yoffset - view->col;
2461         int i;
2463         if (max < size)
2464                 size = max;
2466         set_view_attr(view, type);
2467         /* Using waddch() instead of waddnstr() ensures that
2468          * they'll be rendered correctly for the cursor line. */
2469         for (i = skip; i < size; i++)
2470                 waddch(view->win, graphic[i]);
2472         view->col += size;
2473         if (size < max && skip <= size)
2474                 waddch(view->win, ' ');
2475         view->col++;
2477         return view->width + view->yoffset <= view->col;
2480 static bool
2481 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
2483         int max = MIN(view->width + view->yoffset - view->col, len);
2484         int col;
2486         if (text)
2487                 col = draw_chars(view, type, text, max - 1, trim);
2488         else
2489                 col = draw_space(view, type, max - 1, max - 1);
2491         view->col += col;
2492         view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
2493         return view->width + view->yoffset <= view->col;
2496 static bool
2497 draw_date(struct view *view, struct time *time)
2499         const char *date = mkdate(time, opt_date);
2500         int cols = opt_date == DATE_SHORT ? DATE_SHORT_COLS : DATE_COLS;
2502         return draw_field(view, LINE_DATE, date, cols, FALSE);
2505 static bool
2506 draw_author(struct view *view, const char *author)
2508         bool trim = opt_author_cols == 0 || opt_author_cols > 5;
2509         bool abbreviate = opt_author == AUTHOR_ABBREVIATED || !trim;
2511         if (abbreviate && author)
2512                 author = get_author_initials(author);
2514         return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2517 static bool
2518 draw_mode(struct view *view, mode_t mode)
2520         const char *str;
2522         if (S_ISDIR(mode))
2523                 str = "drwxr-xr-x";
2524         else if (S_ISLNK(mode))
2525                 str = "lrwxrwxrwx";
2526         else if (S_ISGITLINK(mode))
2527                 str = "m---------";
2528         else if (S_ISREG(mode) && mode & S_IXUSR)
2529                 str = "-rwxr-xr-x";
2530         else if (S_ISREG(mode))
2531                 str = "-rw-r--r--";
2532         else
2533                 str = "----------";
2535         return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2538 static bool
2539 draw_lineno(struct view *view, unsigned int lineno)
2541         char number[10];
2542         int digits3 = view->digits < 3 ? 3 : view->digits;
2543         int max = MIN(view->width + view->yoffset - view->col, digits3);
2544         char *text = NULL;
2545         chtype separator = opt_line_graphics ? ACS_VLINE : '|';
2547         lineno += view->offset + 1;
2548         if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2549                 static char fmt[] = "%1ld";
2551                 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2552                 if (string_format(number, fmt, lineno))
2553                         text = number;
2554         }
2555         if (text)
2556                 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2557         else
2558                 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2559         return draw_graphic(view, LINE_DEFAULT, &separator, 1);
2562 static bool
2563 draw_view_line(struct view *view, unsigned int lineno)
2565         struct line *line;
2566         bool selected = (view->offset + lineno == view->lineno);
2568         assert(view_is_displayed(view));
2570         if (view->offset + lineno >= view->lines)
2571                 return FALSE;
2573         line = &view->line[view->offset + lineno];
2575         wmove(view->win, lineno, 0);
2576         if (line->cleareol)
2577                 wclrtoeol(view->win);
2578         view->col = 0;
2579         view->curline = line;
2580         view->curtype = LINE_NONE;
2581         line->selected = FALSE;
2582         line->dirty = line->cleareol = 0;
2584         if (selected) {
2585                 set_view_attr(view, LINE_CURSOR);
2586                 line->selected = TRUE;
2587                 view->ops->select(view, line);
2588         }
2590         return view->ops->draw(view, line, lineno);
2593 static void
2594 redraw_view_dirty(struct view *view)
2596         bool dirty = FALSE;
2597         int lineno;
2599         for (lineno = 0; lineno < view->height; lineno++) {
2600                 if (view->offset + lineno >= view->lines)
2601                         break;
2602                 if (!view->line[view->offset + lineno].dirty)
2603                         continue;
2604                 dirty = TRUE;
2605                 if (!draw_view_line(view, lineno))
2606                         break;
2607         }
2609         if (!dirty)
2610                 return;
2611         wnoutrefresh(view->win);
2614 static void
2615 redraw_view_from(struct view *view, int lineno)
2617         assert(0 <= lineno && lineno < view->height);
2619         for (; lineno < view->height; lineno++) {
2620                 if (!draw_view_line(view, lineno))
2621                         break;
2622         }
2624         wnoutrefresh(view->win);
2627 static void
2628 redraw_view(struct view *view)
2630         werase(view->win);
2631         redraw_view_from(view, 0);
2635 static void
2636 update_view_title(struct view *view)
2638         char buf[SIZEOF_STR];
2639         char state[SIZEOF_STR];
2640         size_t bufpos = 0, statelen = 0;
2642         assert(view_is_displayed(view));
2644         if (view->type != VIEW_STATUS && view->lines) {
2645                 unsigned int view_lines = view->offset + view->height;
2646                 unsigned int lines = view->lines
2647                                    ? MIN(view_lines, view->lines) * 100 / view->lines
2648                                    : 0;
2650                 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2651                                    view->ops->type,
2652                                    view->lineno + 1,
2653                                    view->lines,
2654                                    lines);
2656         }
2658         if (view->pipe) {
2659                 time_t secs = time(NULL) - view->start_time;
2661                 /* Three git seconds are a long time ... */
2662                 if (secs > 2)
2663                         string_format_from(state, &statelen, " loading %lds", secs);
2664         }
2666         string_format_from(buf, &bufpos, "[%s]", view->name);
2667         if (*view->ref && bufpos < view->width) {
2668                 size_t refsize = strlen(view->ref);
2669                 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2671                 if (minsize < view->width)
2672                         refsize = view->width - minsize + 7;
2673                 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2674         }
2676         if (statelen && bufpos < view->width) {
2677                 string_format_from(buf, &bufpos, "%s", state);
2678         }
2680         if (view == display[current_view])
2681                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2682         else
2683                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2685         mvwaddnstr(view->title, 0, 0, buf, bufpos);
2686         wclrtoeol(view->title);
2687         wnoutrefresh(view->title);
2690 static int
2691 apply_step(double step, int value)
2693         if (step >= 1)
2694                 return (int) step;
2695         value *= step + 0.01;
2696         return value ? value : 1;
2699 static void
2700 resize_display(void)
2702         int offset, i;
2703         struct view *base = display[0];
2704         struct view *view = display[1] ? display[1] : display[0];
2706         /* Setup window dimensions */
2708         getmaxyx(stdscr, base->height, base->width);
2710         /* Make room for the status window. */
2711         base->height -= 1;
2713         if (view != base) {
2714                 /* Horizontal split. */
2715                 view->width   = base->width;
2716                 view->height  = apply_step(opt_scale_split_view, base->height);
2717                 view->height  = MAX(view->height, MIN_VIEW_HEIGHT);
2718                 view->height  = MIN(view->height, base->height - MIN_VIEW_HEIGHT);
2719                 base->height -= view->height;
2721                 /* Make room for the title bar. */
2722                 view->height -= 1;
2723         }
2725         /* Make room for the title bar. */
2726         base->height -= 1;
2728         offset = 0;
2730         foreach_displayed_view (view, i) {
2731                 if (!view->win) {
2732                         view->win = newwin(view->height, 0, offset, 0);
2733                         if (!view->win)
2734                                 die("Failed to create %s view", view->name);
2736                         scrollok(view->win, FALSE);
2738                         view->title = newwin(1, 0, offset + view->height, 0);
2739                         if (!view->title)
2740                                 die("Failed to create title window");
2742                 } else {
2743                         wresize(view->win, view->height, view->width);
2744                         mvwin(view->win,   offset, 0);
2745                         mvwin(view->title, offset + view->height, 0);
2746                 }
2748                 offset += view->height + 1;
2749         }
2752 static void
2753 redraw_display(bool clear)
2755         struct view *view;
2756         int i;
2758         foreach_displayed_view (view, i) {
2759                 if (clear)
2760                         wclear(view->win);
2761                 redraw_view(view);
2762                 update_view_title(view);
2763         }
2767 /*
2768  * Option management
2769  */
2771 #define TOGGLE_MENU \
2772         TOGGLE_(LINENO,    '.', "line numbers",      &opt_line_number, NULL) \
2773         TOGGLE_(DATE,      'D', "dates",             &opt_date,   date_map) \
2774         TOGGLE_(AUTHOR,    'A', "author names",      &opt_author, author_map) \
2775         TOGGLE_(REV_GRAPH, 'g', "revision graph",    &opt_rev_graph, NULL) \
2776         TOGGLE_(REFS,      'F', "reference display", &opt_show_refs, NULL)
2778 static void
2779 toggle_option(enum request request)
2781         const struct {
2782                 enum request request;
2783                 const struct enum_map *map;
2784                 size_t map_size;
2785         } data[] = {            
2786 #define TOGGLE_(id, key, help, value, map) { REQ_TOGGLE_ ## id, map, ARRAY_SIZE(map) },
2787                 TOGGLE_MENU
2788 #undef  TOGGLE_
2789         };
2790         const struct menu_item menu[] = {
2791 #define TOGGLE_(id, key, help, value, map) { key, help, value },
2792                 TOGGLE_MENU
2793 #undef  TOGGLE_
2794                 { 0 }
2795         };
2796         int i = 0;
2798         if (request == REQ_OPTIONS) {
2799                 if (!prompt_menu("Toggle option", menu, &i))
2800                         return;
2801         } else {
2802                 while (i < ARRAY_SIZE(data) && data[i].request != request)
2803                         i++;
2804                 if (i >= ARRAY_SIZE(data))
2805                         die("Invalid request (%d)", request);
2806         }
2808         if (data[i].map != NULL) {
2809                 unsigned int *opt = menu[i].data;
2811                 *opt = (*opt + 1) % data[i].map_size;
2812                 redraw_display(FALSE);
2813                 report("Displaying %s %s", enum_name(data[i].map[*opt]), menu[i].text);
2815         } else {
2816                 bool *option = menu[i].data;
2818                 *option = !*option;
2819                 redraw_display(FALSE);
2820                 report("%sabling %s", *option ? "En" : "Dis", menu[i].text);
2821         }
2824 static void
2825 maximize_view(struct view *view)
2827         memset(display, 0, sizeof(display));
2828         current_view = 0;
2829         display[current_view] = view;
2830         resize_display();
2831         redraw_display(FALSE);
2832         report("");
2836 /*
2837  * Navigation
2838  */
2840 static bool
2841 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2843         if (lineno >= view->lines)
2844                 lineno = view->lines > 0 ? view->lines - 1 : 0;
2846         if (offset > lineno || offset + view->height <= lineno) {
2847                 unsigned long half = view->height / 2;
2849                 if (lineno > half)
2850                         offset = lineno - half;
2851                 else
2852                         offset = 0;
2853         }
2855         if (offset != view->offset || lineno != view->lineno) {
2856                 view->offset = offset;
2857                 view->lineno = lineno;
2858                 return TRUE;
2859         }
2861         return FALSE;
2864 /* Scrolling backend */
2865 static void
2866 do_scroll_view(struct view *view, int lines)
2868         bool redraw_current_line = FALSE;
2870         /* The rendering expects the new offset. */
2871         view->offset += lines;
2873         assert(0 <= view->offset && view->offset < view->lines);
2874         assert(lines);
2876         /* Move current line into the view. */
2877         if (view->lineno < view->offset) {
2878                 view->lineno = view->offset;
2879                 redraw_current_line = TRUE;
2880         } else if (view->lineno >= view->offset + view->height) {
2881                 view->lineno = view->offset + view->height - 1;
2882                 redraw_current_line = TRUE;
2883         }
2885         assert(view->offset <= view->lineno && view->lineno < view->lines);
2887         /* Redraw the whole screen if scrolling is pointless. */
2888         if (view->height < ABS(lines)) {
2889                 redraw_view(view);
2891         } else {
2892                 int line = lines > 0 ? view->height - lines : 0;
2893                 int end = line + ABS(lines);
2895                 scrollok(view->win, TRUE);
2896                 wscrl(view->win, lines);
2897                 scrollok(view->win, FALSE);
2899                 while (line < end && draw_view_line(view, line))
2900                         line++;
2902                 if (redraw_current_line)
2903                         draw_view_line(view, view->lineno - view->offset);
2904                 wnoutrefresh(view->win);
2905         }
2907         view->has_scrolled = TRUE;
2908         report("");
2911 /* Scroll frontend */
2912 static void
2913 scroll_view(struct view *view, enum request request)
2915         int lines = 1;
2917         assert(view_is_displayed(view));
2919         switch (request) {
2920         case REQ_SCROLL_FIRST_COL:
2921                 view->yoffset = 0;
2922                 redraw_view_from(view, 0);
2923                 report("");
2924                 return;
2925         case REQ_SCROLL_LEFT:
2926                 if (view->yoffset == 0) {
2927                         report("Cannot scroll beyond the first column");
2928                         return;
2929                 }
2930                 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2931                         view->yoffset = 0;
2932                 else
2933                         view->yoffset -= apply_step(opt_hscroll, view->width);
2934                 redraw_view_from(view, 0);
2935                 report("");
2936                 return;
2937         case REQ_SCROLL_RIGHT:
2938                 view->yoffset += apply_step(opt_hscroll, view->width);
2939                 redraw_view(view);
2940                 report("");
2941                 return;
2942         case REQ_SCROLL_PAGE_DOWN:
2943                 lines = view->height;
2944         case REQ_SCROLL_LINE_DOWN:
2945                 if (view->offset + lines > view->lines)
2946                         lines = view->lines - view->offset;
2948                 if (lines == 0 || view->offset + view->height >= view->lines) {
2949                         report("Cannot scroll beyond the last line");
2950                         return;
2951                 }
2952                 break;
2954         case REQ_SCROLL_PAGE_UP:
2955                 lines = view->height;
2956         case REQ_SCROLL_LINE_UP:
2957                 if (lines > view->offset)
2958                         lines = view->offset;
2960                 if (lines == 0) {
2961                         report("Cannot scroll beyond the first line");
2962                         return;
2963                 }
2965                 lines = -lines;
2966                 break;
2968         default:
2969                 die("request %d not handled in switch", request);
2970         }
2972         do_scroll_view(view, lines);
2975 /* Cursor moving */
2976 static void
2977 move_view(struct view *view, enum request request)
2979         int scroll_steps = 0;
2980         int steps;
2982         switch (request) {
2983         case REQ_MOVE_FIRST_LINE:
2984                 steps = -view->lineno;
2985                 break;
2987         case REQ_MOVE_LAST_LINE:
2988                 steps = view->lines - view->lineno - 1;
2989                 break;
2991         case REQ_MOVE_PAGE_UP:
2992                 steps = view->height > view->lineno
2993                       ? -view->lineno : -view->height;
2994                 break;
2996         case REQ_MOVE_PAGE_DOWN:
2997                 steps = view->lineno + view->height >= view->lines
2998                       ? view->lines - view->lineno - 1 : view->height;
2999                 break;
3001         case REQ_MOVE_UP:
3002                 steps = -1;
3003                 break;
3005         case REQ_MOVE_DOWN:
3006                 steps = 1;
3007                 break;
3009         default:
3010                 die("request %d not handled in switch", request);
3011         }
3013         if (steps <= 0 && view->lineno == 0) {
3014                 report("Cannot move beyond the first line");
3015                 return;
3017         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
3018                 report("Cannot move beyond the last line");
3019                 return;
3020         }
3022         /* Move the current line */
3023         view->lineno += steps;
3024         assert(0 <= view->lineno && view->lineno < view->lines);
3026         /* Check whether the view needs to be scrolled */
3027         if (view->lineno < view->offset ||
3028             view->lineno >= view->offset + view->height) {
3029                 scroll_steps = steps;
3030                 if (steps < 0 && -steps > view->offset) {
3031                         scroll_steps = -view->offset;
3033                 } else if (steps > 0) {
3034                         if (view->lineno == view->lines - 1 &&
3035                             view->lines > view->height) {
3036                                 scroll_steps = view->lines - view->offset - 1;
3037                                 if (scroll_steps >= view->height)
3038                                         scroll_steps -= view->height - 1;
3039                         }
3040                 }
3041         }
3043         if (!view_is_displayed(view)) {
3044                 view->offset += scroll_steps;
3045                 assert(0 <= view->offset && view->offset < view->lines);
3046                 view->ops->select(view, &view->line[view->lineno]);
3047                 return;
3048         }
3050         /* Repaint the old "current" line if we be scrolling */
3051         if (ABS(steps) < view->height)
3052                 draw_view_line(view, view->lineno - steps - view->offset);
3054         if (scroll_steps) {
3055                 do_scroll_view(view, scroll_steps);
3056                 return;
3057         }
3059         /* Draw the current line */
3060         draw_view_line(view, view->lineno - view->offset);
3062         wnoutrefresh(view->win);
3063         report("");
3067 /*
3068  * Searching
3069  */
3071 static void search_view(struct view *view, enum request request);
3073 static bool
3074 grep_text(struct view *view, const char *text[])
3076         regmatch_t pmatch;
3077         size_t i;
3079         for (i = 0; text[i]; i++)
3080                 if (*text[i] &&
3081                     regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
3082                         return TRUE;
3083         return FALSE;
3086 static void
3087 select_view_line(struct view *view, unsigned long lineno)
3089         unsigned long old_lineno = view->lineno;
3090         unsigned long old_offset = view->offset;
3092         if (goto_view_line(view, view->offset, lineno)) {
3093                 if (view_is_displayed(view)) {
3094                         if (old_offset != view->offset) {
3095                                 redraw_view(view);
3096                         } else {
3097                                 draw_view_line(view, old_lineno - view->offset);
3098                                 draw_view_line(view, view->lineno - view->offset);
3099                                 wnoutrefresh(view->win);
3100                         }
3101                 } else {
3102                         view->ops->select(view, &view->line[view->lineno]);
3103                 }
3104         }
3107 static void
3108 find_next(struct view *view, enum request request)
3110         unsigned long lineno = view->lineno;
3111         int direction;
3113         if (!*view->grep) {
3114                 if (!*opt_search)
3115                         report("No previous search");
3116                 else
3117                         search_view(view, request);
3118                 return;
3119         }
3121         switch (request) {
3122         case REQ_SEARCH:
3123         case REQ_FIND_NEXT:
3124                 direction = 1;
3125                 break;
3127         case REQ_SEARCH_BACK:
3128         case REQ_FIND_PREV:
3129                 direction = -1;
3130                 break;
3132         default:
3133                 return;
3134         }
3136         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
3137                 lineno += direction;
3139         /* Note, lineno is unsigned long so will wrap around in which case it
3140          * will become bigger than view->lines. */
3141         for (; lineno < view->lines; lineno += direction) {
3142                 if (view->ops->grep(view, &view->line[lineno])) {
3143                         select_view_line(view, lineno);
3144                         report("Line %ld matches '%s'", lineno + 1, view->grep);
3145                         return;
3146                 }
3147         }
3149         report("No match found for '%s'", view->grep);
3152 static void
3153 search_view(struct view *view, enum request request)
3155         int regex_err;
3157         if (view->regex) {
3158                 regfree(view->regex);
3159                 *view->grep = 0;
3160         } else {
3161                 view->regex = calloc(1, sizeof(*view->regex));
3162                 if (!view->regex)
3163                         return;
3164         }
3166         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
3167         if (regex_err != 0) {
3168                 char buf[SIZEOF_STR] = "unknown error";
3170                 regerror(regex_err, view->regex, buf, sizeof(buf));
3171                 report("Search failed: %s", buf);
3172                 return;
3173         }
3175         string_copy(view->grep, opt_search);
3177         find_next(view, request);
3180 /*
3181  * Incremental updating
3182  */
3184 static void
3185 reset_view(struct view *view)
3187         int i;
3189         for (i = 0; i < view->lines; i++)
3190                 free(view->line[i].data);
3191         free(view->line);
3193         view->p_offset = view->offset;
3194         view->p_yoffset = view->yoffset;
3195         view->p_lineno = view->lineno;
3197         view->line = NULL;
3198         view->offset = 0;
3199         view->yoffset = 0;
3200         view->lines  = 0;
3201         view->lineno = 0;
3202         view->vid[0] = 0;
3203         view->update_secs = 0;
3206 static const char *
3207 format_arg(const char *name)
3209         static struct {
3210                 const char *name;
3211                 size_t namelen;
3212                 const char *value;
3213                 const char *value_if_empty;
3214         } vars[] = {
3215 #define FORMAT_VAR(name, value, value_if_empty) \
3216         { name, STRING_SIZE(name), value, value_if_empty }
3217                 FORMAT_VAR("%(directory)",      opt_path,       ""),
3218                 FORMAT_VAR("%(file)",           opt_file,       ""),
3219                 FORMAT_VAR("%(ref)",            opt_ref,        "HEAD"),
3220                 FORMAT_VAR("%(head)",           ref_head,       ""),
3221                 FORMAT_VAR("%(commit)",         ref_commit,     ""),
3222                 FORMAT_VAR("%(blob)",           ref_blob,       ""),
3223                 FORMAT_VAR("%(branch)",         ref_branch,     ""),
3224         };
3225         int i;
3227         for (i = 0; i < ARRAY_SIZE(vars); i++)
3228                 if (!strncmp(name, vars[i].name, vars[i].namelen))
3229                         return *vars[i].value ? vars[i].value : vars[i].value_if_empty;
3231         report("Unknown replacement: `%s`", name);
3232         return NULL;
3235 static bool
3236 format_argv(const char ***dst_argv, const char *src_argv[], bool replace, bool first)
3238         char buf[SIZEOF_STR];
3239         int argc;
3241         argv_free(*dst_argv);
3243         for (argc = 0; src_argv[argc]; argc++) {
3244                 const char *arg = src_argv[argc];
3245                 size_t bufpos = 0;
3247                 if (!strcmp(arg, "%(fileargs)")) {
3248                         if (!argv_append_array(dst_argv, opt_file_argv))
3249                                 break;
3250                         continue;
3252                 } else if (!strcmp(arg, "%(diffargs)")) {
3253                         if (!argv_append_array(dst_argv, opt_diff_argv))
3254                                 break;
3255                         continue;
3257                 } else if (!strcmp(arg, "%(revargs)") ||
3258                            (first && !strcmp(arg, "%(commit)"))) {
3259                         if (!argv_append_array(dst_argv, opt_rev_argv))
3260                                 break;
3261                         continue;
3262                 }
3264                 while (arg) {
3265                         char *next = strstr(arg, "%(");
3266                         int len = next - arg;
3267                         const char *value;
3269                         if (!next || !replace) {
3270                                 len = strlen(arg);
3271                                 value = "";
3273                         } else {
3274                                 value = format_arg(next);
3276                                 if (!value) {
3277                                         return FALSE;
3278                                 }
3279                         }
3281                         if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
3282                                 return FALSE;
3284                         arg = next && replace ? strchr(next, ')') + 1 : NULL;
3285                 }
3287                 if (!argv_append(dst_argv, buf))
3288                         break;
3289         }
3291         return src_argv[argc] == NULL;
3294 static bool
3295 restore_view_position(struct view *view)
3297         if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
3298                 return FALSE;
3300         /* Changing the view position cancels the restoring. */
3301         /* FIXME: Changing back to the first line is not detected. */
3302         if (view->offset != 0 || view->lineno != 0) {
3303                 view->p_restore = FALSE;
3304                 return FALSE;
3305         }
3307         if (goto_view_line(view, view->p_offset, view->p_lineno) &&
3308             view_is_displayed(view))
3309                 werase(view->win);
3311         view->yoffset = view->p_yoffset;
3312         view->p_restore = FALSE;
3314         return TRUE;
3317 static void
3318 end_update(struct view *view, bool force)
3320         if (!view->pipe)
3321                 return;
3322         while (!view->ops->read(view, NULL))
3323                 if (!force)
3324                         return;
3325         if (force)
3326                 io_kill(view->pipe);
3327         io_done(view->pipe);
3328         view->pipe = NULL;
3331 static void
3332 setup_update(struct view *view, const char *vid)
3334         reset_view(view);
3335         string_copy_rev(view->vid, vid);
3336         view->pipe = &view->io;
3337         view->start_time = time(NULL);
3340 static bool
3341 prepare_io(struct view *view, const char *dir, const char *argv[], bool replace)
3343         view->dir = dir;
3344         return format_argv(&view->argv, argv, replace, !view->prev);
3347 static bool
3348 prepare_update(struct view *view, const char *argv[], const char *dir)
3350         if (view->pipe)
3351                 end_update(view, TRUE);
3352         return prepare_io(view, dir, argv, FALSE);
3355 static bool
3356 start_update(struct view *view, const char **argv, const char *dir)
3358         if (view->pipe)
3359                 io_done(view->pipe);
3360         return prepare_io(view, dir, argv, FALSE) &&
3361                io_run(&view->io, IO_RD, dir, view->argv);
3364 static bool
3365 prepare_update_file(struct view *view, const char *name)
3367         if (view->pipe)
3368                 end_update(view, TRUE);
3369         argv_free(view->argv);
3370         return io_open(&view->io, "%s/%s", opt_cdup[0] ? opt_cdup : ".", name);
3373 static bool
3374 begin_update(struct view *view, bool refresh)
3376         if (view->pipe)
3377                 end_update(view, TRUE);
3379         if (!refresh) {
3380                 if (view->ops->prepare) {
3381                         if (!view->ops->prepare(view))
3382                                 return FALSE;
3383                 } else if (!prepare_io(view, NULL, view->ops->argv, TRUE)) {
3384                         return FALSE;
3385                 }
3387                 /* Put the current ref_* value to the view title ref
3388                  * member. This is needed by the blob view. Most other
3389                  * views sets it automatically after loading because the
3390                  * first line is a commit line. */
3391                 string_copy_rev(view->ref, view->id);
3392         }
3394         if (view->argv && view->argv[0] &&
3395             !io_run(&view->io, IO_RD, view->dir, view->argv))
3396                 return FALSE;
3398         setup_update(view, view->id);
3400         return TRUE;
3403 static bool
3404 update_view(struct view *view)
3406         char out_buffer[BUFSIZ * 2];
3407         char *line;
3408         /* Clear the view and redraw everything since the tree sorting
3409          * might have rearranged things. */
3410         bool redraw = view->lines == 0;
3411         bool can_read = TRUE;
3413         if (!view->pipe)
3414                 return TRUE;
3416         if (!io_can_read(view->pipe)) {
3417                 if (view->lines == 0 && view_is_displayed(view)) {
3418                         time_t secs = time(NULL) - view->start_time;
3420                         if (secs > 1 && secs > view->update_secs) {
3421                                 if (view->update_secs == 0)
3422                                         redraw_view(view);
3423                                 update_view_title(view);
3424                                 view->update_secs = secs;
3425                         }
3426                 }
3427                 return TRUE;
3428         }
3430         for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
3431                 if (opt_iconv_in != ICONV_NONE) {
3432                         ICONV_CONST char *inbuf = line;
3433                         size_t inlen = strlen(line) + 1;
3435                         char *outbuf = out_buffer;
3436                         size_t outlen = sizeof(out_buffer);
3438                         size_t ret;
3440                         ret = iconv(opt_iconv_in, &inbuf, &inlen, &outbuf, &outlen);
3441                         if (ret != (size_t) -1)
3442                                 line = out_buffer;
3443                 }
3445                 if (!view->ops->read(view, line)) {
3446                         report("Allocation failure");
3447                         end_update(view, TRUE);
3448                         return FALSE;
3449                 }
3450         }
3452         {
3453                 unsigned long lines = view->lines;
3454                 int digits;
3456                 for (digits = 0; lines; digits++)
3457                         lines /= 10;
3459                 /* Keep the displayed view in sync with line number scaling. */
3460                 if (digits != view->digits) {
3461                         view->digits = digits;
3462                         if (opt_line_number || view->type == VIEW_BLAME)
3463                                 redraw = TRUE;
3464                 }
3465         }
3467         if (io_error(view->pipe)) {
3468                 report("Failed to read: %s", io_strerror(view->pipe));
3469                 end_update(view, TRUE);
3471         } else if (io_eof(view->pipe)) {
3472                 if (view_is_displayed(view))
3473                         report("");
3474                 end_update(view, FALSE);
3475         }
3477         if (restore_view_position(view))
3478                 redraw = TRUE;
3480         if (!view_is_displayed(view))
3481                 return TRUE;
3483         if (redraw)
3484                 redraw_view_from(view, 0);
3485         else
3486                 redraw_view_dirty(view);
3488         /* Update the title _after_ the redraw so that if the redraw picks up a
3489          * commit reference in view->ref it'll be available here. */
3490         update_view_title(view);
3491         return TRUE;
3494 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
3496 static struct line *
3497 add_line_data(struct view *view, void *data, enum line_type type)
3499         struct line *line;
3501         if (!realloc_lines(&view->line, view->lines, 1))
3502                 return NULL;
3504         line = &view->line[view->lines++];
3505         memset(line, 0, sizeof(*line));
3506         line->type = type;
3507         line->data = data;
3508         line->dirty = 1;
3510         return line;
3513 static struct line *
3514 add_line_text(struct view *view, const char *text, enum line_type type)
3516         char *data = text ? strdup(text) : NULL;
3518         return data ? add_line_data(view, data, type) : NULL;
3521 static struct line *
3522 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
3524         char buf[SIZEOF_STR];
3525         va_list args;
3527         va_start(args, fmt);
3528         if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
3529                 buf[0] = 0;
3530         va_end(args);
3532         return buf[0] ? add_line_text(view, buf, type) : NULL;
3535 /*
3536  * View opening
3537  */
3539 enum open_flags {
3540         OPEN_DEFAULT = 0,       /* Use default view switching. */
3541         OPEN_SPLIT = 1,         /* Split current view. */
3542         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
3543         OPEN_REFRESH = 16,      /* Refresh view using previous command. */
3544         OPEN_PREPARED = 32,     /* Open already prepared command. */
3545 };
3547 static void
3548 open_view(struct view *prev, enum request request, enum open_flags flags)
3550         bool split = !!(flags & OPEN_SPLIT);
3551         bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
3552         bool nomaximize = !!(flags & OPEN_REFRESH);
3553         struct view *view = VIEW(request);
3554         int nviews = displayed_views();
3555         struct view *base_view = display[0];
3557         if (view == prev && nviews == 1 && !reload) {
3558                 report("Already in %s view", view->name);
3559                 return;
3560         }
3562         if (view->git_dir && !opt_git_dir[0]) {
3563                 report("The %s view is disabled in pager view", view->name);
3564                 return;
3565         }
3567         if (split) {
3568                 display[1] = view;
3569                 current_view = 1;
3570                 view->parent = prev;
3571         } else if (!nomaximize) {
3572                 /* Maximize the current view. */
3573                 memset(display, 0, sizeof(display));
3574                 current_view = 0;
3575                 display[current_view] = view;
3576         }
3578         /* No prev signals that this is the first loaded view. */
3579         if (prev && view != prev) {
3580                 view->prev = prev;
3581         }
3583         /* Resize the view when switching between split- and full-screen,
3584          * or when switching between two different full-screen views. */
3585         if (nviews != displayed_views() ||
3586             (nviews == 1 && base_view != display[0]))
3587                 resize_display();
3589         if (view->ops->open) {
3590                 if (view->pipe)
3591                         end_update(view, TRUE);
3592                 if (!view->ops->open(view)) {
3593                         report("Failed to load %s view", view->name);
3594                         return;
3595                 }
3596                 restore_view_position(view);
3598         } else if ((reload || strcmp(view->vid, view->id)) &&
3599                    !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3600                 report("Failed to load %s view", view->name);
3601                 return;
3602         }
3604         if (split && prev->lineno - prev->offset >= prev->height) {
3605                 /* Take the title line into account. */
3606                 int lines = prev->lineno - prev->offset - prev->height + 1;
3608                 /* Scroll the view that was split if the current line is
3609                  * outside the new limited view. */
3610                 do_scroll_view(prev, lines);
3611         }
3613         if (prev && view != prev && split && view_is_displayed(prev)) {
3614                 /* "Blur" the previous view. */
3615                 update_view_title(prev);
3616         }
3618         if (view->pipe && view->lines == 0) {
3619                 /* Clear the old view and let the incremental updating refill
3620                  * the screen. */
3621                 werase(view->win);
3622                 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3623                 report("");
3624         } else if (view_is_displayed(view)) {
3625                 redraw_view(view);
3626                 report("");
3627         }
3630 static void
3631 open_external_viewer(const char *argv[], const char *dir)
3633         def_prog_mode();           /* save current tty modes */
3634         endwin();                  /* restore original tty modes */
3635         io_run_fg(argv, dir);
3636         fprintf(stderr, "Press Enter to continue");
3637         getc(opt_tty);
3638         reset_prog_mode();
3639         redraw_display(TRUE);
3642 static void
3643 open_mergetool(const char *file)
3645         const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3647         open_external_viewer(mergetool_argv, opt_cdup);
3650 static void
3651 open_editor(const char *file)
3653         const char *editor_argv[] = { "vi", file, NULL };
3654         const char *editor;
3656         editor = getenv("GIT_EDITOR");
3657         if (!editor && *opt_editor)
3658                 editor = opt_editor;
3659         if (!editor)
3660                 editor = getenv("VISUAL");
3661         if (!editor)
3662                 editor = getenv("EDITOR");
3663         if (!editor)
3664                 editor = "vi";
3666         editor_argv[0] = editor;
3667         open_external_viewer(editor_argv, opt_cdup);
3670 static void
3671 open_run_request(enum request request)
3673         struct run_request *req = get_run_request(request);
3674         const char **argv = NULL;
3676         if (!req) {
3677                 report("Unknown run request");
3678                 return;
3679         }
3681         if (format_argv(&argv, req->argv, TRUE, FALSE))
3682                 open_external_viewer(argv, NULL);
3683         if (argv)
3684                 argv_free(argv);
3685         free(argv);
3688 /*
3689  * User request switch noodle
3690  */
3692 static int
3693 view_driver(struct view *view, enum request request)
3695         int i;
3697         if (request == REQ_NONE)
3698                 return TRUE;
3700         if (request > REQ_NONE) {
3701                 open_run_request(request);
3702                 view_request(view, REQ_REFRESH);
3703                 return TRUE;
3704         }
3706         request = view_request(view, request);
3707         if (request == REQ_NONE)
3708                 return TRUE;
3710         switch (request) {
3711         case REQ_MOVE_UP:
3712         case REQ_MOVE_DOWN:
3713         case REQ_MOVE_PAGE_UP:
3714         case REQ_MOVE_PAGE_DOWN:
3715         case REQ_MOVE_FIRST_LINE:
3716         case REQ_MOVE_LAST_LINE:
3717                 move_view(view, request);
3718                 break;
3720         case REQ_SCROLL_FIRST_COL:
3721         case REQ_SCROLL_LEFT:
3722         case REQ_SCROLL_RIGHT:
3723         case REQ_SCROLL_LINE_DOWN:
3724         case REQ_SCROLL_LINE_UP:
3725         case REQ_SCROLL_PAGE_DOWN:
3726         case REQ_SCROLL_PAGE_UP:
3727                 scroll_view(view, request);
3728                 break;
3730         case REQ_VIEW_BLAME:
3731                 if (!opt_file[0]) {
3732                         report("No file chosen, press %s to open tree view",
3733                                get_key(view->keymap, REQ_VIEW_TREE));
3734                         break;
3735                 }
3736                 open_view(view, request, OPEN_DEFAULT);
3737                 break;
3739         case REQ_VIEW_BLOB:
3740                 if (!ref_blob[0]) {
3741                         report("No file chosen, press %s to open tree view",
3742                                get_key(view->keymap, REQ_VIEW_TREE));
3743                         break;
3744                 }
3745                 open_view(view, request, OPEN_DEFAULT);
3746                 break;
3748         case REQ_VIEW_PAGER:
3749                 if (view == NULL) {
3750                         if (!io_open(&VIEW(REQ_VIEW_PAGER)->io, ""))
3751                                 die("Failed to open stdin");
3752                         open_view(view, request, OPEN_PREPARED);
3753                         break;
3754                 }
3756                 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3757                         report("No pager content, press %s to run command from prompt",
3758                                get_key(view->keymap, REQ_PROMPT));
3759                         break;
3760                 }
3761                 open_view(view, request, OPEN_DEFAULT);
3762                 break;
3764         case REQ_VIEW_STAGE:
3765                 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3766                         report("No stage content, press %s to open the status view and choose file",
3767                                get_key(view->keymap, REQ_VIEW_STATUS));
3768                         break;
3769                 }
3770                 open_view(view, request, OPEN_DEFAULT);
3771                 break;
3773         case REQ_VIEW_STATUS:
3774                 if (opt_is_inside_work_tree == FALSE) {
3775                         report("The status view requires a working tree");
3776                         break;
3777                 }
3778                 open_view(view, request, OPEN_DEFAULT);
3779                 break;
3781         case REQ_VIEW_MAIN:
3782         case REQ_VIEW_DIFF:
3783         case REQ_VIEW_LOG:
3784         case REQ_VIEW_TREE:
3785         case REQ_VIEW_HELP:
3786         case REQ_VIEW_BRANCH:
3787                 open_view(view, request, OPEN_DEFAULT);
3788                 break;
3790         case REQ_NEXT:
3791         case REQ_PREVIOUS:
3792                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3794                 if (view->parent) {
3795                         int line;
3797                         view = view->parent;
3798                         line = view->lineno;
3799                         move_view(view, request);
3800                         if (view_is_displayed(view))
3801                                 update_view_title(view);
3802                         if (line != view->lineno)
3803                                 view_request(view, REQ_ENTER);
3804                 } else {
3805                         move_view(view, request);
3806                 }
3807                 break;
3809         case REQ_VIEW_NEXT:
3810         {
3811                 int nviews = displayed_views();
3812                 int next_view = (current_view + 1) % nviews;
3814                 if (next_view == current_view) {
3815                         report("Only one view is displayed");
3816                         break;
3817                 }
3819                 current_view = next_view;
3820                 /* Blur out the title of the previous view. */
3821                 update_view_title(view);
3822                 report("");
3823                 break;
3824         }
3825         case REQ_REFRESH:
3826                 report("Refreshing is not yet supported for the %s view", view->name);
3827                 break;
3829         case REQ_MAXIMIZE:
3830                 if (displayed_views() == 2)
3831                         maximize_view(view);
3832                 break;
3834         case REQ_OPTIONS:
3835         case REQ_TOGGLE_LINENO:
3836         case REQ_TOGGLE_DATE:
3837         case REQ_TOGGLE_AUTHOR:
3838         case REQ_TOGGLE_REV_GRAPH:
3839         case REQ_TOGGLE_REFS:
3840                 toggle_option(request);
3841                 break;
3843         case REQ_TOGGLE_SORT_FIELD:
3844         case REQ_TOGGLE_SORT_ORDER:
3845                 report("Sorting is not yet supported for the %s view", view->name);
3846                 break;
3848         case REQ_SEARCH:
3849         case REQ_SEARCH_BACK:
3850                 search_view(view, request);
3851                 break;
3853         case REQ_FIND_NEXT:
3854         case REQ_FIND_PREV:
3855                 find_next(view, request);
3856                 break;
3858         case REQ_STOP_LOADING:
3859                 foreach_view(view, i) {
3860                         if (view->pipe)
3861                                 report("Stopped loading the %s view", view->name),
3862                         end_update(view, TRUE);
3863                 }
3864                 break;
3866         case REQ_SHOW_VERSION:
3867                 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3868                 return TRUE;
3870         case REQ_SCREEN_REDRAW:
3871                 redraw_display(TRUE);
3872                 break;
3874         case REQ_EDIT:
3875                 report("Nothing to edit");
3876                 break;
3878         case REQ_ENTER:
3879                 report("Nothing to enter");
3880                 break;
3882         case REQ_VIEW_CLOSE:
3883                 /* XXX: Mark closed views by letting view->prev point to the
3884                  * view itself. Parents to closed view should never be
3885                  * followed. */
3886                 if (view->prev && view->prev != view) {
3887                         maximize_view(view->prev);
3888                         view->prev = view;
3889                         break;
3890                 }
3891                 /* Fall-through */
3892         case REQ_QUIT:
3893                 return FALSE;
3895         default:
3896                 report("Unknown key, press %s for help",
3897                        get_key(view->keymap, REQ_VIEW_HELP));
3898                 return TRUE;
3899         }
3901         return TRUE;
3905 /*
3906  * View backend utilities
3907  */
3909 enum sort_field {
3910         ORDERBY_NAME,
3911         ORDERBY_DATE,
3912         ORDERBY_AUTHOR,
3913 };
3915 struct sort_state {
3916         const enum sort_field *fields;
3917         size_t size, current;
3918         bool reverse;
3919 };
3921 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3922 #define get_sort_field(state) ((state).fields[(state).current])
3923 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3925 static void
3926 sort_view(struct view *view, enum request request, struct sort_state *state,
3927           int (*compare)(const void *, const void *))
3929         switch (request) {
3930         case REQ_TOGGLE_SORT_FIELD:
3931                 state->current = (state->current + 1) % state->size;
3932                 break;
3934         case REQ_TOGGLE_SORT_ORDER:
3935                 state->reverse = !state->reverse;
3936                 break;
3937         default:
3938                 die("Not a sort request");
3939         }
3941         qsort(view->line, view->lines, sizeof(*view->line), compare);
3942         redraw_view(view);
3945 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3947 /* Small author cache to reduce memory consumption. It uses binary
3948  * search to lookup or find place to position new entries. No entries
3949  * are ever freed. */
3950 static const char *
3951 get_author(const char *name)
3953         static const char **authors;
3954         static size_t authors_size;
3955         int from = 0, to = authors_size - 1;
3957         while (from <= to) {
3958                 size_t pos = (to + from) / 2;
3959                 int cmp = strcmp(name, authors[pos]);
3961                 if (!cmp)
3962                         return authors[pos];
3964                 if (cmp < 0)
3965                         to = pos - 1;
3966                 else
3967                         from = pos + 1;
3968         }
3970         if (!realloc_authors(&authors, authors_size, 1))
3971                 return NULL;
3972         name = strdup(name);
3973         if (!name)
3974                 return NULL;
3976         memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3977         authors[from] = name;
3978         authors_size++;
3980         return name;
3983 static void
3984 parse_timesec(struct time *time, const char *sec)
3986         time->sec = (time_t) atol(sec);
3989 static void
3990 parse_timezone(struct time *time, const char *zone)
3992         long tz;
3994         tz  = ('0' - zone[1]) * 60 * 60 * 10;
3995         tz += ('0' - zone[2]) * 60 * 60;
3996         tz += ('0' - zone[3]) * 60 * 10;
3997         tz += ('0' - zone[4]) * 60;
3999         if (zone[0] == '-')
4000                 tz = -tz;
4002         time->tz = tz;
4003         time->sec -= tz;
4006 /* Parse author lines where the name may be empty:
4007  *      author  <email@address.tld> 1138474660 +0100
4008  */
4009 static void
4010 parse_author_line(char *ident, const char **author, struct time *time)
4012         char *nameend = strchr(ident, '<');
4013         char *emailend = strchr(ident, '>');
4015         if (nameend && emailend)
4016                 *nameend = *emailend = 0;
4017         ident = chomp_string(ident);
4018         if (!*ident) {
4019                 if (nameend)
4020                         ident = chomp_string(nameend + 1);
4021                 if (!*ident)
4022                         ident = "Unknown";
4023         }
4025         *author = get_author(ident);
4027         /* Parse epoch and timezone */
4028         if (emailend && emailend[1] == ' ') {
4029                 char *secs = emailend + 2;
4030                 char *zone = strchr(secs, ' ');
4032                 parse_timesec(time, secs);
4034                 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
4035                         parse_timezone(time, zone + 1);
4036         }
4039 /*
4040  * Pager backend
4041  */
4043 static bool
4044 pager_draw(struct view *view, struct line *line, unsigned int lineno)
4046         if (opt_line_number && draw_lineno(view, lineno))
4047                 return TRUE;
4049         draw_text(view, line->type, line->data, TRUE);
4050         return TRUE;
4053 static bool
4054 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
4056         const char *describe_argv[] = { "git", "describe", commit_id, NULL };
4057         char ref[SIZEOF_STR];
4059         if (!io_run_buf(describe_argv, ref, sizeof(ref)) || !*ref)
4060                 return TRUE;
4062         /* This is the only fatal call, since it can "corrupt" the buffer. */
4063         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
4064                 return FALSE;
4066         return TRUE;
4069 static void
4070 add_pager_refs(struct view *view, struct line *line)
4072         char buf[SIZEOF_STR];
4073         char *commit_id = (char *)line->data + STRING_SIZE("commit ");
4074         struct ref_list *list;
4075         size_t bufpos = 0, i;
4076         const char *sep = "Refs: ";
4077         bool is_tag = FALSE;
4079         assert(line->type == LINE_COMMIT);
4081         list = get_ref_list(commit_id);
4082         if (!list) {
4083                 if (view->type == VIEW_DIFF)
4084                         goto try_add_describe_ref;
4085                 return;
4086         }
4088         for (i = 0; i < list->size; i++) {
4089                 struct ref *ref = list->refs[i];
4090                 const char *fmt = ref->tag    ? "%s[%s]" :
4091                                   ref->remote ? "%s<%s>" : "%s%s";
4093                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
4094                         return;
4095                 sep = ", ";
4096                 if (ref->tag)
4097                         is_tag = TRUE;
4098         }
4100         if (!is_tag && view->type == VIEW_DIFF) {
4101 try_add_describe_ref:
4102                 /* Add <tag>-g<commit_id> "fake" reference. */
4103                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
4104                         return;
4105         }
4107         if (bufpos == 0)
4108                 return;
4110         add_line_text(view, buf, LINE_PP_REFS);
4113 static bool
4114 pager_read(struct view *view, char *data)
4116         struct line *line;
4118         if (!data)
4119                 return TRUE;
4121         line = add_line_text(view, data, get_line_type(data));
4122         if (!line)
4123                 return FALSE;
4125         if (line->type == LINE_COMMIT &&
4126             (view->type == VIEW_DIFF ||
4127              view->type == VIEW_LOG))
4128                 add_pager_refs(view, line);
4130         return TRUE;
4133 static enum request
4134 pager_request(struct view *view, enum request request, struct line *line)
4136         int split = 0;
4138         if (request != REQ_ENTER)
4139                 return request;
4141         if (line->type == LINE_COMMIT &&
4142            (view->type == VIEW_LOG ||
4143             view->type == VIEW_PAGER)) {
4144                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
4145                 split = 1;
4146         }
4148         /* Always scroll the view even if it was split. That way
4149          * you can use Enter to scroll through the log view and
4150          * split open each commit diff. */
4151         scroll_view(view, REQ_SCROLL_LINE_DOWN);
4153         /* FIXME: A minor workaround. Scrolling the view will call report("")
4154          * but if we are scrolling a non-current view this won't properly
4155          * update the view title. */
4156         if (split)
4157                 update_view_title(view);
4159         return REQ_NONE;
4162 static bool
4163 pager_grep(struct view *view, struct line *line)
4165         const char *text[] = { line->data, NULL };
4167         return grep_text(view, text);
4170 static void
4171 pager_select(struct view *view, struct line *line)
4173         if (line->type == LINE_COMMIT) {
4174                 char *text = (char *)line->data + STRING_SIZE("commit ");
4176                 if (view->type != VIEW_PAGER)
4177                         string_copy_rev(view->ref, text);
4178                 string_copy_rev(ref_commit, text);
4179         }
4182 static struct view_ops pager_ops = {
4183         "line",
4184         NULL,
4185         NULL,
4186         pager_read,
4187         pager_draw,
4188         pager_request,
4189         pager_grep,
4190         pager_select,
4191 };
4193 static const char *log_argv[SIZEOF_ARG] = {
4194         "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
4195 };
4197 static enum request
4198 log_request(struct view *view, enum request request, struct line *line)
4200         switch (request) {
4201         case REQ_REFRESH:
4202                 load_refs();
4203                 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
4204                 return REQ_NONE;
4205         default:
4206                 return pager_request(view, request, line);
4207         }
4210 static struct view_ops log_ops = {
4211         "line",
4212         log_argv,
4213         NULL,
4214         pager_read,
4215         pager_draw,
4216         log_request,
4217         pager_grep,
4218         pager_select,
4219 };
4221 static const char *diff_argv[SIZEOF_ARG] = {
4222         "git", "show", "--pretty=fuller", "--no-color", "--root",
4223                 "--patch-with-stat", "--find-copies-harder", "-C",
4224                 "%(diffargs)", "%(commit)", "--", "%(fileargs)", NULL
4225 };
4227 static bool
4228 diff_read(struct view *view, char *data)
4230         if (!data) {
4231                 /* Fall back to retry if no diff will be shown. */
4232                 if (view->lines == 0 && opt_file_argv) {
4233                         int pos = argv_size(view->argv)
4234                                 - argv_size(opt_file_argv) - 1;
4236                         if (pos > 0 && !strcmp(view->argv[pos], "--")) {
4237                                 for (; view->argv[pos]; pos++) {
4238                                         free((void *) view->argv[pos]);
4239                                         view->argv[pos] = NULL;
4240                                 }
4242                                 if (view->pipe)
4243                                         io_done(view->pipe);
4244                                 if (io_run(&view->io, IO_RD, view->dir, view->argv))
4245                                         return FALSE;
4246                         }
4247                 }
4248                 return TRUE;
4249         }
4251         return pager_read(view, data);
4254 static struct view_ops diff_ops = {
4255         "line",
4256         diff_argv,
4257         NULL,
4258         diff_read,
4259         pager_draw,
4260         pager_request,
4261         pager_grep,
4262         pager_select,
4263 };
4265 /*
4266  * Help backend
4267  */
4269 static bool help_keymap_hidden[ARRAY_SIZE(keymap_table)];
4271 static bool
4272 help_open_keymap_title(struct view *view, enum keymap keymap)
4274         struct line *line;
4276         line = add_line_format(view, LINE_HELP_KEYMAP, "[%c] %s bindings",
4277                                help_keymap_hidden[keymap] ? '+' : '-',
4278                                enum_name(keymap_table[keymap]));
4279         if (line)
4280                 line->other = keymap;
4282         return help_keymap_hidden[keymap];
4285 static void
4286 help_open_keymap(struct view *view, enum keymap keymap)
4288         const char *group = NULL;
4289         char buf[SIZEOF_STR];
4290         size_t bufpos;
4291         bool add_title = TRUE;
4292         int i;
4294         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
4295                 const char *key = NULL;
4297                 if (req_info[i].request == REQ_NONE)
4298                         continue;
4300                 if (!req_info[i].request) {
4301                         group = req_info[i].help;
4302                         continue;
4303                 }
4305                 key = get_keys(keymap, req_info[i].request, TRUE);
4306                 if (!key || !*key)
4307                         continue;
4309                 if (add_title && help_open_keymap_title(view, keymap))
4310                         return;
4311                 add_title = FALSE;
4313                 if (group) {
4314                         add_line_text(view, group, LINE_HELP_GROUP);
4315                         group = NULL;
4316                 }
4318                 add_line_format(view, LINE_DEFAULT, "    %-25s %-20s %s", key,
4319                                 enum_name(req_info[i]), req_info[i].help);
4320         }
4322         group = "External commands:";
4324         for (i = 0; i < run_requests; i++) {
4325                 struct run_request *req = get_run_request(REQ_NONE + i + 1);
4326                 const char *key;
4327                 int argc;
4329                 if (!req || req->keymap != keymap)
4330                         continue;
4332                 key = get_key_name(req->key);
4333                 if (!*key)
4334                         key = "(no key defined)";
4336                 if (add_title && help_open_keymap_title(view, keymap))
4337                         return;
4338                 if (group) {
4339                         add_line_text(view, group, LINE_HELP_GROUP);
4340                         group = NULL;
4341                 }
4343                 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
4344                         if (!string_format_from(buf, &bufpos, "%s%s",
4345                                                 argc ? " " : "", req->argv[argc]))
4346                                 return;
4348                 add_line_format(view, LINE_DEFAULT, "    %-25s `%s`", key, buf);
4349         }
4352 static bool
4353 help_open(struct view *view)
4355         enum keymap keymap;
4357         reset_view(view);
4358         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
4359         add_line_text(view, "", LINE_DEFAULT);
4361         for (keymap = 0; keymap < ARRAY_SIZE(keymap_table); keymap++)
4362                 help_open_keymap(view, keymap);
4364         return TRUE;
4367 static enum request
4368 help_request(struct view *view, enum request request, struct line *line)
4370         switch (request) {
4371         case REQ_ENTER:
4372                 if (line->type == LINE_HELP_KEYMAP) {
4373                         help_keymap_hidden[line->other] =
4374                                 !help_keymap_hidden[line->other];
4375                         view->p_restore = TRUE;
4376                         open_view(view, REQ_VIEW_HELP, OPEN_REFRESH);
4377                 }
4379                 return REQ_NONE;
4380         default:
4381                 return pager_request(view, request, line);
4382         }
4385 static struct view_ops help_ops = {
4386         "line",
4387         NULL,
4388         help_open,
4389         NULL,
4390         pager_draw,
4391         help_request,
4392         pager_grep,
4393         pager_select,
4394 };
4397 /*
4398  * Tree backend
4399  */
4401 struct tree_stack_entry {
4402         struct tree_stack_entry *prev;  /* Entry below this in the stack */
4403         unsigned long lineno;           /* Line number to restore */
4404         char *name;                     /* Position of name in opt_path */
4405 };
4407 /* The top of the path stack. */
4408 static struct tree_stack_entry *tree_stack = NULL;
4409 unsigned long tree_lineno = 0;
4411 static void
4412 pop_tree_stack_entry(void)
4414         struct tree_stack_entry *entry = tree_stack;
4416         tree_lineno = entry->lineno;
4417         entry->name[0] = 0;
4418         tree_stack = entry->prev;
4419         free(entry);
4422 static void
4423 push_tree_stack_entry(const char *name, unsigned long lineno)
4425         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
4426         size_t pathlen = strlen(opt_path);
4428         if (!entry)
4429                 return;
4431         entry->prev = tree_stack;
4432         entry->name = opt_path + pathlen;
4433         tree_stack = entry;
4435         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
4436                 pop_tree_stack_entry();
4437                 return;
4438         }
4440         /* Move the current line to the first tree entry. */
4441         tree_lineno = 1;
4442         entry->lineno = lineno;
4445 /* Parse output from git-ls-tree(1):
4446  *
4447  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4448  */
4450 #define SIZEOF_TREE_ATTR \
4451         STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4453 #define SIZEOF_TREE_MODE \
4454         STRING_SIZE("100644 ")
4456 #define TREE_ID_OFFSET \
4457         STRING_SIZE("100644 blob ")
4459 struct tree_entry {
4460         char id[SIZEOF_REV];
4461         mode_t mode;
4462         struct time time;               /* Date from the author ident. */
4463         const char *author;             /* Author of the commit. */
4464         char name[1];
4465 };
4467 static const char *
4468 tree_path(const struct line *line)
4470         return ((struct tree_entry *) line->data)->name;
4473 static int
4474 tree_compare_entry(const struct line *line1, const struct line *line2)
4476         if (line1->type != line2->type)
4477                 return line1->type == LINE_TREE_DIR ? -1 : 1;
4478         return strcmp(tree_path(line1), tree_path(line2));
4481 static const enum sort_field tree_sort_fields[] = {
4482         ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4483 };
4484 static struct sort_state tree_sort_state = SORT_STATE(tree_sort_fields);
4486 static int
4487 tree_compare(const void *l1, const void *l2)
4489         const struct line *line1 = (const struct line *) l1;
4490         const struct line *line2 = (const struct line *) l2;
4491         const struct tree_entry *entry1 = ((const struct line *) l1)->data;
4492         const struct tree_entry *entry2 = ((const struct line *) l2)->data;
4494         if (line1->type == LINE_TREE_HEAD)
4495                 return -1;
4496         if (line2->type == LINE_TREE_HEAD)
4497                 return 1;
4499         switch (get_sort_field(tree_sort_state)) {
4500         case ORDERBY_DATE:
4501                 return sort_order(tree_sort_state, timecmp(&entry1->time, &entry2->time));
4503         case ORDERBY_AUTHOR:
4504                 return sort_order(tree_sort_state, strcmp(entry1->author, entry2->author));
4506         case ORDERBY_NAME:
4507         default:
4508                 return sort_order(tree_sort_state, tree_compare_entry(line1, line2));
4509         }
4513 static struct line *
4514 tree_entry(struct view *view, enum line_type type, const char *path,
4515            const char *mode, const char *id)
4517         struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
4518         struct line *line = entry ? add_line_data(view, entry, type) : NULL;
4520         if (!entry || !line) {
4521                 free(entry);
4522                 return NULL;
4523         }
4525         strncpy(entry->name, path, strlen(path));
4526         if (mode)
4527                 entry->mode = strtoul(mode, NULL, 8);
4528         if (id)
4529                 string_copy_rev(entry->id, id);
4531         return line;
4534 static bool
4535 tree_read_date(struct view *view, char *text, bool *read_date)
4537         static const char *author_name;
4538         static struct time author_time;
4540         if (!text && *read_date) {
4541                 *read_date = FALSE;
4542                 return TRUE;
4544         } else if (!text) {
4545                 char *path = *opt_path ? opt_path : ".";
4546                 /* Find next entry to process */
4547                 const char *log_file[] = {
4548                         "git", "log", "--no-color", "--pretty=raw",
4549                                 "--cc", "--raw", view->id, "--", path, NULL
4550                 };
4552                 if (!view->lines) {
4553                         tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
4554                         report("Tree is empty");
4555                         return TRUE;
4556                 }
4558                 if (!start_update(view, log_file, opt_cdup)) {
4559                         report("Failed to load tree data");
4560                         return TRUE;
4561                 }
4563                 *read_date = TRUE;
4564                 return FALSE;
4566         } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
4567                 parse_author_line(text + STRING_SIZE("author "),
4568                                   &author_name, &author_time);
4570         } else if (*text == ':') {
4571                 char *pos;
4572                 size_t annotated = 1;
4573                 size_t i;
4575                 pos = strchr(text, '\t');
4576                 if (!pos)
4577                         return TRUE;
4578                 text = pos + 1;
4579                 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
4580                         text += strlen(opt_path);
4581                 pos = strchr(text, '/');
4582                 if (pos)
4583                         *pos = 0;
4585                 for (i = 1; i < view->lines; i++) {
4586                         struct line *line = &view->line[i];
4587                         struct tree_entry *entry = line->data;
4589                         annotated += !!entry->author;
4590                         if (entry->author || strcmp(entry->name, text))
4591                                 continue;
4593                         entry->author = author_name;
4594                         entry->time = author_time;
4595                         line->dirty = 1;
4596                         break;
4597                 }
4599                 if (annotated == view->lines)
4600                         io_kill(view->pipe);
4601         }
4602         return TRUE;
4605 static bool
4606 tree_read(struct view *view, char *text)
4608         static bool read_date = FALSE;
4609         struct tree_entry *data;
4610         struct line *entry, *line;
4611         enum line_type type;
4612         size_t textlen = text ? strlen(text) : 0;
4613         char *path = text + SIZEOF_TREE_ATTR;
4615         if (read_date || !text)
4616                 return tree_read_date(view, text, &read_date);
4618         if (textlen <= SIZEOF_TREE_ATTR)
4619                 return FALSE;
4620         if (view->lines == 0 &&
4621             !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
4622                 return FALSE;
4624         /* Strip the path part ... */
4625         if (*opt_path) {
4626                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
4627                 size_t striplen = strlen(opt_path);
4629                 if (pathlen > striplen)
4630                         memmove(path, path + striplen,
4631                                 pathlen - striplen + 1);
4633                 /* Insert "link" to parent directory. */
4634                 if (view->lines == 1 &&
4635                     !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
4636                         return FALSE;
4637         }
4639         type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
4640         entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
4641         if (!entry)
4642                 return FALSE;
4643         data = entry->data;
4645         /* Skip "Directory ..." and ".." line. */
4646         for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
4647                 if (tree_compare_entry(line, entry) <= 0)
4648                         continue;
4650                 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4652                 line->data = data;
4653                 line->type = type;
4654                 for (; line <= entry; line++)
4655                         line->dirty = line->cleareol = 1;
4656                 return TRUE;
4657         }
4659         if (tree_lineno > view->lineno) {
4660                 view->lineno = tree_lineno;
4661                 tree_lineno = 0;
4662         }
4664         return TRUE;
4667 static bool
4668 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4670         struct tree_entry *entry = line->data;
4672         if (line->type == LINE_TREE_HEAD) {
4673                 if (draw_text(view, line->type, "Directory path /", TRUE))
4674                         return TRUE;
4675         } else {
4676                 if (draw_mode(view, entry->mode))
4677                         return TRUE;
4679                 if (opt_author && draw_author(view, entry->author))
4680                         return TRUE;
4682                 if (opt_date && draw_date(view, &entry->time))
4683                         return TRUE;
4684         }
4685         if (draw_text(view, line->type, entry->name, TRUE))
4686                 return TRUE;
4687         return TRUE;
4690 static void
4691 open_blob_editor(const char *id)
4693         const char *blob_argv[] = { "git", "cat-file", "blob", id, NULL };
4694         char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4695         int fd = mkstemp(file);
4697         if (fd == -1)
4698                 report("Failed to create temporary file");
4699         else if (!io_run_append(blob_argv, fd))
4700                 report("Failed to save blob data to file");
4701         else
4702                 open_editor(file);
4703         if (fd != -1)
4704                 unlink(file);
4707 static enum request
4708 tree_request(struct view *view, enum request request, struct line *line)
4710         enum open_flags flags;
4711         struct tree_entry *entry = line->data;
4713         switch (request) {
4714         case REQ_VIEW_BLAME:
4715                 if (line->type != LINE_TREE_FILE) {
4716                         report("Blame only supported for files");
4717                         return REQ_NONE;
4718                 }
4720                 string_copy(opt_ref, view->vid);
4721                 return request;
4723         case REQ_EDIT:
4724                 if (line->type != LINE_TREE_FILE) {
4725                         report("Edit only supported for files");
4726                 } else if (!is_head_commit(view->vid)) {
4727                         open_blob_editor(entry->id);
4728                 } else {
4729                         open_editor(opt_file);
4730                 }
4731                 return REQ_NONE;
4733         case REQ_TOGGLE_SORT_FIELD:
4734         case REQ_TOGGLE_SORT_ORDER:
4735                 sort_view(view, request, &tree_sort_state, tree_compare);
4736                 return REQ_NONE;
4738         case REQ_PARENT:
4739                 if (!*opt_path) {
4740                         /* quit view if at top of tree */
4741                         return REQ_VIEW_CLOSE;
4742                 }
4743                 /* fake 'cd  ..' */
4744                 line = &view->line[1];
4745                 break;
4747         case REQ_ENTER:
4748                 break;
4750         default:
4751                 return request;
4752         }
4754         /* Cleanup the stack if the tree view is at a different tree. */
4755         while (!*opt_path && tree_stack)
4756                 pop_tree_stack_entry();
4758         switch (line->type) {
4759         case LINE_TREE_DIR:
4760                 /* Depending on whether it is a subdirectory or parent link
4761                  * mangle the path buffer. */
4762                 if (line == &view->line[1] && *opt_path) {
4763                         pop_tree_stack_entry();
4765                 } else {
4766                         const char *basename = tree_path(line);
4768                         push_tree_stack_entry(basename, view->lineno);
4769                 }
4771                 /* Trees and subtrees share the same ID, so they are not not
4772                  * unique like blobs. */
4773                 flags = OPEN_RELOAD;
4774                 request = REQ_VIEW_TREE;
4775                 break;
4777         case LINE_TREE_FILE:
4778                 flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
4779                 request = REQ_VIEW_BLOB;
4780                 break;
4782         default:
4783                 return REQ_NONE;
4784         }
4786         open_view(view, request, flags);
4787         if (request == REQ_VIEW_TREE)
4788                 view->lineno = tree_lineno;
4790         return REQ_NONE;
4793 static bool
4794 tree_grep(struct view *view, struct line *line)
4796         struct tree_entry *entry = line->data;
4797         const char *text[] = {
4798                 entry->name,
4799                 opt_author ? entry->author : "",
4800                 mkdate(&entry->time, opt_date),
4801                 NULL
4802         };
4804         return grep_text(view, text);
4807 static void
4808 tree_select(struct view *view, struct line *line)
4810         struct tree_entry *entry = line->data;
4812         if (line->type == LINE_TREE_FILE) {
4813                 string_copy_rev(ref_blob, entry->id);
4814                 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4816         } else if (line->type != LINE_TREE_DIR) {
4817                 return;
4818         }
4820         string_copy_rev(view->ref, entry->id);
4823 static bool
4824 tree_prepare(struct view *view)
4826         if (view->lines == 0 && opt_prefix[0]) {
4827                 char *pos = opt_prefix;
4829                 while (pos && *pos) {
4830                         char *end = strchr(pos, '/');
4832                         if (end)
4833                                 *end = 0;
4834                         push_tree_stack_entry(pos, 0);
4835                         pos = end;
4836                         if (end) {
4837                                 *end = '/';
4838                                 pos++;
4839                         }
4840                 }
4842         } else if (strcmp(view->vid, view->id)) {
4843                 opt_path[0] = 0;
4844         }
4846         return prepare_io(view, opt_cdup, view->ops->argv, TRUE);
4849 static const char *tree_argv[SIZEOF_ARG] = {
4850         "git", "ls-tree", "%(commit)", "%(directory)", NULL
4851 };
4853 static struct view_ops tree_ops = {
4854         "file",
4855         tree_argv,
4856         NULL,
4857         tree_read,
4858         tree_draw,
4859         tree_request,
4860         tree_grep,
4861         tree_select,
4862         tree_prepare,
4863 };
4865 static bool
4866 blob_read(struct view *view, char *line)
4868         if (!line)
4869                 return TRUE;
4870         return add_line_text(view, line, LINE_DEFAULT) != NULL;
4873 static enum request
4874 blob_request(struct view *view, enum request request, struct line *line)
4876         switch (request) {
4877         case REQ_EDIT:
4878                 open_blob_editor(view->vid);
4879                 return REQ_NONE;
4880         default:
4881                 return pager_request(view, request, line);
4882         }
4885 static const char *blob_argv[SIZEOF_ARG] = {
4886         "git", "cat-file", "blob", "%(blob)", NULL
4887 };
4889 static struct view_ops blob_ops = {
4890         "line",
4891         blob_argv,
4892         NULL,
4893         blob_read,
4894         pager_draw,
4895         blob_request,
4896         pager_grep,
4897         pager_select,
4898 };
4900 /*
4901  * Blame backend
4902  *
4903  * Loading the blame view is a two phase job:
4904  *
4905  *  1. File content is read either using opt_file from the
4906  *     filesystem or using git-cat-file.
4907  *  2. Then blame information is incrementally added by
4908  *     reading output from git-blame.
4909  */
4911 struct blame_commit {
4912         char id[SIZEOF_REV];            /* SHA1 ID. */
4913         char title[128];                /* First line of the commit message. */
4914         const char *author;             /* Author of the commit. */
4915         struct time time;               /* Date from the author ident. */
4916         char filename[128];             /* Name of file. */
4917         char parent_id[SIZEOF_REV];     /* Parent/previous SHA1 ID. */
4918         char parent_filename[128];      /* Parent/previous name of file. */
4919 };
4921 struct blame {
4922         struct blame_commit *commit;
4923         unsigned long lineno;
4924         char text[1];
4925 };
4927 static bool
4928 blame_open(struct view *view)
4930         char path[SIZEOF_STR];
4931         size_t i;
4933         if (!view->prev && *opt_prefix) {
4934                 string_copy(path, opt_file);
4935                 if (!string_format(opt_file, "%s%s", opt_prefix, path))
4936                         return FALSE;
4937         }
4939         if (*opt_ref || !io_open(&view->io, "%s%s", opt_cdup, opt_file)) {
4940                 const char *blame_cat_file_argv[] = {
4941                         "git", "cat-file", "blob", path, NULL
4942                 };
4944                 if (!string_format(path, "%s:%s", opt_ref, opt_file) ||
4945                     !start_update(view, blame_cat_file_argv, opt_cdup))
4946                         return FALSE;
4947         }
4949         /* First pass: remove multiple references to the same commit. */
4950         for (i = 0; i < view->lines; i++) {
4951                 struct blame *blame = view->line[i].data;
4953                 if (blame->commit && blame->commit->id[0])
4954                         blame->commit->id[0] = 0;
4955                 else
4956                         blame->commit = NULL;
4957         }
4959         /* Second pass: free existing references. */
4960         for (i = 0; i < view->lines; i++) {
4961                 struct blame *blame = view->line[i].data;
4963                 if (blame->commit)
4964                         free(blame->commit);
4965         }
4967         setup_update(view, opt_file);
4968         string_format(view->ref, "%s ...", opt_file);
4970         return TRUE;
4973 static struct blame_commit *
4974 get_blame_commit(struct view *view, const char *id)
4976         size_t i;
4978         for (i = 0; i < view->lines; i++) {
4979                 struct blame *blame = view->line[i].data;
4981                 if (!blame->commit)
4982                         continue;
4984                 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4985                         return blame->commit;
4986         }
4988         {
4989                 struct blame_commit *commit = calloc(1, sizeof(*commit));
4991                 if (commit)
4992                         string_ncopy(commit->id, id, SIZEOF_REV);
4993                 return commit;
4994         }
4997 static bool
4998 parse_number(const char **posref, size_t *number, size_t min, size_t max)
5000         const char *pos = *posref;
5002         *posref = NULL;
5003         pos = strchr(pos + 1, ' ');
5004         if (!pos || !isdigit(pos[1]))
5005                 return FALSE;
5006         *number = atoi(pos + 1);
5007         if (*number < min || *number > max)
5008                 return FALSE;
5010         *posref = pos;
5011         return TRUE;
5014 static struct blame_commit *
5015 parse_blame_commit(struct view *view, const char *text, int *blamed)
5017         struct blame_commit *commit;
5018         struct blame *blame;
5019         const char *pos = text + SIZEOF_REV - 2;
5020         size_t orig_lineno = 0;
5021         size_t lineno;
5022         size_t group;
5024         if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
5025                 return NULL;
5027         if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
5028             !parse_number(&pos, &lineno, 1, view->lines) ||
5029             !parse_number(&pos, &group, 1, view->lines - lineno + 1))
5030                 return NULL;
5032         commit = get_blame_commit(view, text);
5033         if (!commit)
5034                 return NULL;
5036         *blamed += group;
5037         while (group--) {
5038                 struct line *line = &view->line[lineno + group - 1];
5040                 blame = line->data;
5041                 blame->commit = commit;
5042                 blame->lineno = orig_lineno + group - 1;
5043                 line->dirty = 1;
5044         }
5046         return commit;
5049 static bool
5050 blame_read_file(struct view *view, const char *line, bool *read_file)
5052         if (!line) {
5053                 const char *blame_argv[] = {
5054                         "git", "blame", "--incremental",
5055                                 *opt_ref ? opt_ref : "--incremental", "--", opt_file, NULL
5056                 };
5058                 if (view->lines == 0 && !view->prev)
5059                         die("No blame exist for %s", view->vid);
5061                 if (view->lines == 0 || !start_update(view, blame_argv, opt_cdup)) {
5062                         report("Failed to load blame data");
5063                         return TRUE;
5064                 }
5066                 *read_file = FALSE;
5067                 return FALSE;
5069         } else {
5070                 size_t linelen = strlen(line);
5071                 struct blame *blame = malloc(sizeof(*blame) + linelen);
5073                 if (!blame)
5074                         return FALSE;
5076                 blame->commit = NULL;
5077                 strncpy(blame->text, line, linelen);
5078                 blame->text[linelen] = 0;
5079                 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
5080         }
5083 static bool
5084 match_blame_header(const char *name, char **line)
5086         size_t namelen = strlen(name);
5087         bool matched = !strncmp(name, *line, namelen);
5089         if (matched)
5090                 *line += namelen;
5092         return matched;
5095 static bool
5096 blame_read(struct view *view, char *line)
5098         static struct blame_commit *commit = NULL;
5099         static int blamed = 0;
5100         static bool read_file = TRUE;
5102         if (read_file)
5103                 return blame_read_file(view, line, &read_file);
5105         if (!line) {
5106                 /* Reset all! */
5107                 commit = NULL;
5108                 blamed = 0;
5109                 read_file = TRUE;
5110                 string_format(view->ref, "%s", view->vid);
5111                 if (view_is_displayed(view)) {
5112                         update_view_title(view);
5113                         redraw_view_from(view, 0);
5114                 }
5115                 return TRUE;
5116         }
5118         if (!commit) {
5119                 commit = parse_blame_commit(view, line, &blamed);
5120                 string_format(view->ref, "%s %2d%%", view->vid,
5121                               view->lines ? blamed * 100 / view->lines : 0);
5123         } else if (match_blame_header("author ", &line)) {
5124                 commit->author = get_author(line);
5126         } else if (match_blame_header("author-time ", &line)) {
5127                 parse_timesec(&commit->time, line);
5129         } else if (match_blame_header("author-tz ", &line)) {
5130                 parse_timezone(&commit->time, line);
5132         } else if (match_blame_header("summary ", &line)) {
5133                 string_ncopy(commit->title, line, strlen(line));
5135         } else if (match_blame_header("previous ", &line)) {
5136                 if (strlen(line) <= SIZEOF_REV)
5137                         return FALSE;
5138                 string_copy_rev(commit->parent_id, line);
5139                 line += SIZEOF_REV;
5140                 string_ncopy(commit->parent_filename, line, strlen(line));
5142         } else if (match_blame_header("filename ", &line)) {
5143                 string_ncopy(commit->filename, line, strlen(line));
5144                 commit = NULL;
5145         }
5147         return TRUE;
5150 static bool
5151 blame_draw(struct view *view, struct line *line, unsigned int lineno)
5153         struct blame *blame = line->data;
5154         struct time *time = NULL;
5155         const char *id = NULL, *author = NULL;
5157         if (blame->commit && *blame->commit->filename) {
5158                 id = blame->commit->id;
5159                 author = blame->commit->author;
5160                 time = &blame->commit->time;
5161         }
5163         if (opt_date && draw_date(view, time))
5164                 return TRUE;
5166         if (opt_author && draw_author(view, author))
5167                 return TRUE;
5169         if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
5170                 return TRUE;
5172         if (draw_lineno(view, lineno))
5173                 return TRUE;
5175         draw_text(view, LINE_DEFAULT, blame->text, TRUE);
5176         return TRUE;
5179 static bool
5180 check_blame_commit(struct blame *blame, bool check_null_id)
5182         if (!blame->commit)
5183                 report("Commit data not loaded yet");
5184         else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
5185                 report("No commit exist for the selected line");
5186         else
5187                 return TRUE;
5188         return FALSE;
5191 static void
5192 setup_blame_parent_line(struct view *view, struct blame *blame)
5194         char from[SIZEOF_REF + SIZEOF_STR];
5195         char to[SIZEOF_REF + SIZEOF_STR];
5196         const char *diff_tree_argv[] = {
5197                 "git", "diff", "--no-textconv", "--no-extdiff", "--no-color",
5198                         "-U0", from, to, "--", NULL
5199         };
5200         struct io io;
5201         int parent_lineno = -1;
5202         int blamed_lineno = -1;
5203         char *line;
5205         if (!string_format(from, "%s:%s", opt_ref, opt_file) ||
5206             !string_format(to, "%s:%s", blame->commit->id, blame->commit->filename) ||
5207             !io_run(&io, IO_RD, NULL, diff_tree_argv))
5208                 return;
5210         while ((line = io_get(&io, '\n', TRUE))) {
5211                 if (*line == '@') {
5212                         char *pos = strchr(line, '+');
5214                         parent_lineno = atoi(line + 4);
5215                         if (pos)
5216                                 blamed_lineno = atoi(pos + 1);
5218                 } else if (*line == '+' && parent_lineno != -1) {
5219                         if (blame->lineno == blamed_lineno - 1 &&
5220                             !strcmp(blame->text, line + 1)) {
5221                                 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
5222                                 break;
5223                         }
5224                         blamed_lineno++;
5225                 }
5226         }
5228         io_done(&io);
5231 static enum request
5232 blame_request(struct view *view, enum request request, struct line *line)
5234         enum open_flags flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
5235         struct blame *blame = line->data;
5237         switch (request) {
5238         case REQ_VIEW_BLAME:
5239                 if (check_blame_commit(blame, TRUE)) {
5240                         string_copy(opt_ref, blame->commit->id);
5241                         string_copy(opt_file, blame->commit->filename);
5242                         if (blame->lineno)
5243                                 view->lineno = blame->lineno;
5244                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5245                 }
5246                 break;
5248         case REQ_PARENT:
5249                 if (!check_blame_commit(blame, TRUE))
5250                         break;
5251                 if (!*blame->commit->parent_id) {
5252                         report("The selected commit has no parents");
5253                 } else {
5254                         string_copy_rev(opt_ref, blame->commit->parent_id);
5255                         string_copy(opt_file, blame->commit->parent_filename);
5256                         setup_blame_parent_line(view, blame);
5257                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5258                 }
5259                 break;
5261         case REQ_ENTER:
5262                 if (!check_blame_commit(blame, FALSE))
5263                         break;
5265                 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
5266                     !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
5267                         break;
5269                 if (!strcmp(blame->commit->id, NULL_ID)) {
5270                         struct view *diff = VIEW(REQ_VIEW_DIFF);
5271                         const char *diff_index_argv[] = {
5272                                 "git", "diff-index", "--root", "--patch-with-stat",
5273                                         "-C", "-M", "HEAD", "--", view->vid, NULL
5274                         };
5276                         if (!*blame->commit->parent_id) {
5277                                 diff_index_argv[1] = "diff";
5278                                 diff_index_argv[2] = "--no-color";
5279                                 diff_index_argv[6] = "--";
5280                                 diff_index_argv[7] = "/dev/null";
5281                         }
5283                         if (!prepare_update(diff, diff_index_argv, NULL)) {
5284                                 report("Failed to allocate diff command");
5285                                 break;
5286                         }
5287                         flags |= OPEN_PREPARED;
5288                 }
5290                 open_view(view, REQ_VIEW_DIFF, flags);
5291                 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
5292                         string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
5293                 break;
5295         default:
5296                 return request;
5297         }
5299         return REQ_NONE;
5302 static bool
5303 blame_grep(struct view *view, struct line *line)
5305         struct blame *blame = line->data;
5306         struct blame_commit *commit = blame->commit;
5307         const char *text[] = {
5308                 blame->text,
5309                 commit ? commit->title : "",
5310                 commit ? commit->id : "",
5311                 commit && opt_author ? commit->author : "",
5312                 commit ? mkdate(&commit->time, opt_date) : "",
5313                 NULL
5314         };
5316         return grep_text(view, text);
5319 static void
5320 blame_select(struct view *view, struct line *line)
5322         struct blame *blame = line->data;
5323         struct blame_commit *commit = blame->commit;
5325         if (!commit)
5326                 return;
5328         if (!strcmp(commit->id, NULL_ID))
5329                 string_ncopy(ref_commit, "HEAD", 4);
5330         else
5331                 string_copy_rev(ref_commit, commit->id);
5334 static struct view_ops blame_ops = {
5335         "line",
5336         NULL,
5337         blame_open,
5338         blame_read,
5339         blame_draw,
5340         blame_request,
5341         blame_grep,
5342         blame_select,
5343 };
5345 /*
5346  * Branch backend
5347  */
5349 struct branch {
5350         const char *author;             /* Author of the last commit. */
5351         struct time time;               /* Date of the last activity. */
5352         const struct ref *ref;          /* Name and commit ID information. */
5353 };
5355 static const struct ref branch_all;
5357 static const enum sort_field branch_sort_fields[] = {
5358         ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
5359 };
5360 static struct sort_state branch_sort_state = SORT_STATE(branch_sort_fields);
5362 static int
5363 branch_compare(const void *l1, const void *l2)
5365         const struct branch *branch1 = ((const struct line *) l1)->data;
5366         const struct branch *branch2 = ((const struct line *) l2)->data;
5368         switch (get_sort_field(branch_sort_state)) {
5369         case ORDERBY_DATE:
5370                 return sort_order(branch_sort_state, timecmp(&branch1->time, &branch2->time));
5372         case ORDERBY_AUTHOR:
5373                 return sort_order(branch_sort_state, strcmp(branch1->author, branch2->author));
5375         case ORDERBY_NAME:
5376         default:
5377                 return sort_order(branch_sort_state, strcmp(branch1->ref->name, branch2->ref->name));
5378         }
5381 static bool
5382 branch_draw(struct view *view, struct line *line, unsigned int lineno)
5384         struct branch *branch = line->data;
5385         enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
5387         if (opt_date && draw_date(view, &branch->time))
5388                 return TRUE;
5390         if (opt_author && draw_author(view, branch->author))
5391                 return TRUE;
5393         draw_text(view, type, branch->ref == &branch_all ? "All branches" : branch->ref->name, TRUE);
5394         return TRUE;
5397 static enum request
5398 branch_request(struct view *view, enum request request, struct line *line)
5400         struct branch *branch = line->data;
5402         switch (request) {
5403         case REQ_REFRESH:
5404                 load_refs();
5405                 open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
5406                 return REQ_NONE;
5408         case REQ_TOGGLE_SORT_FIELD:
5409         case REQ_TOGGLE_SORT_ORDER:
5410                 sort_view(view, request, &branch_sort_state, branch_compare);
5411                 return REQ_NONE;
5413         case REQ_ENTER:
5414         {
5415                 const struct ref *ref = branch->ref;
5416                 const char *all_branches_argv[] = {
5417                         "git", "log", "--no-color", "--pretty=raw", "--parents",
5418                               "--topo-order",
5419                               ref == &branch_all ? "--all" : ref->name, NULL
5420                 };
5421                 struct view *main_view = VIEW(REQ_VIEW_MAIN);
5423                 if (!prepare_update(main_view, all_branches_argv, NULL))
5424                         report("Failed to load view of all branches");
5425                 else
5426                         open_view(view, REQ_VIEW_MAIN, OPEN_PREPARED | OPEN_SPLIT);
5427                 return REQ_NONE;
5428         }
5429         default:
5430                 return request;
5431         }
5434 static bool
5435 branch_read(struct view *view, char *line)
5437         static char id[SIZEOF_REV];
5438         struct branch *reference;
5439         size_t i;
5441         if (!line)
5442                 return TRUE;
5444         switch (get_line_type(line)) {
5445         case LINE_COMMIT:
5446                 string_copy_rev(id, line + STRING_SIZE("commit "));
5447                 return TRUE;
5449         case LINE_AUTHOR:
5450                 for (i = 0, reference = NULL; i < view->lines; i++) {
5451                         struct branch *branch = view->line[i].data;
5453                         if (strcmp(branch->ref->id, id))
5454                                 continue;
5456                         view->line[i].dirty = TRUE;
5457                         if (reference) {
5458                                 branch->author = reference->author;
5459                                 branch->time = reference->time;
5460                                 continue;
5461                         }
5463                         parse_author_line(line + STRING_SIZE("author "),
5464                                           &branch->author, &branch->time);
5465                         reference = branch;
5466                 }
5467                 return TRUE;
5469         default:
5470                 return TRUE;
5471         }
5475 static bool
5476 branch_open_visitor(void *data, const struct ref *ref)
5478         struct view *view = data;
5479         struct branch *branch;
5481         if (ref->tag || ref->ltag || ref->remote)
5482                 return TRUE;
5484         branch = calloc(1, sizeof(*branch));
5485         if (!branch)
5486                 return FALSE;
5488         branch->ref = ref;
5489         return !!add_line_data(view, branch, LINE_DEFAULT);
5492 static bool
5493 branch_open(struct view *view)
5495         const char *branch_log[] = {
5496                 "git", "log", "--no-color", "--pretty=raw",
5497                         "--simplify-by-decoration", "--all", NULL
5498         };
5500         if (!start_update(view, branch_log, NULL)) {
5501                 report("Failed to load branch data");
5502                 return TRUE;
5503         }
5505         setup_update(view, view->id);
5506         branch_open_visitor(view, &branch_all);
5507         foreach_ref(branch_open_visitor, view);
5508         view->p_restore = TRUE;
5510         return TRUE;
5513 static bool
5514 branch_grep(struct view *view, struct line *line)
5516         struct branch *branch = line->data;
5517         const char *text[] = {
5518                 branch->ref->name,
5519                 branch->author,
5520                 NULL
5521         };
5523         return grep_text(view, text);
5526 static void
5527 branch_select(struct view *view, struct line *line)
5529         struct branch *branch = line->data;
5531         string_copy_rev(view->ref, branch->ref->id);
5532         string_copy_rev(ref_commit, branch->ref->id);
5533         string_copy_rev(ref_head, branch->ref->id);
5534         string_copy_rev(ref_branch, branch->ref->name);
5537 static struct view_ops branch_ops = {
5538         "branch",
5539         NULL,
5540         branch_open,
5541         branch_read,
5542         branch_draw,
5543         branch_request,
5544         branch_grep,
5545         branch_select,
5546 };
5548 /*
5549  * Status backend
5550  */
5552 struct status {
5553         char status;
5554         struct {
5555                 mode_t mode;
5556                 char rev[SIZEOF_REV];
5557                 char name[SIZEOF_STR];
5558         } old;
5559         struct {
5560                 mode_t mode;
5561                 char rev[SIZEOF_REV];
5562                 char name[SIZEOF_STR];
5563         } new;
5564 };
5566 static char status_onbranch[SIZEOF_STR];
5567 static struct status stage_status;
5568 static enum line_type stage_line_type;
5569 static size_t stage_chunks;
5570 static int *stage_chunk;
5572 DEFINE_ALLOCATOR(realloc_ints, int, 32)
5574 /* This should work even for the "On branch" line. */
5575 static inline bool
5576 status_has_none(struct view *view, struct line *line)
5578         return line < view->line + view->lines && !line[1].data;
5581 /* Get fields from the diff line:
5582  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5583  */
5584 static inline bool
5585 status_get_diff(struct status *file, const char *buf, size_t bufsize)
5587         const char *old_mode = buf +  1;
5588         const char *new_mode = buf +  8;
5589         const char *old_rev  = buf + 15;
5590         const char *new_rev  = buf + 56;
5591         const char *status   = buf + 97;
5593         if (bufsize < 98 ||
5594             old_mode[-1] != ':' ||
5595             new_mode[-1] != ' ' ||
5596             old_rev[-1]  != ' ' ||
5597             new_rev[-1]  != ' ' ||
5598             status[-1]   != ' ')
5599                 return FALSE;
5601         file->status = *status;
5603         string_copy_rev(file->old.rev, old_rev);
5604         string_copy_rev(file->new.rev, new_rev);
5606         file->old.mode = strtoul(old_mode, NULL, 8);
5607         file->new.mode = strtoul(new_mode, NULL, 8);
5609         file->old.name[0] = file->new.name[0] = 0;
5611         return TRUE;
5614 static bool
5615 status_run(struct view *view, const char *argv[], char status, enum line_type type)
5617         struct status *unmerged = NULL;
5618         char *buf;
5619         struct io io;
5621         if (!io_run(&io, IO_RD, opt_cdup, argv))
5622                 return FALSE;
5624         add_line_data(view, NULL, type);
5626         while ((buf = io_get(&io, 0, TRUE))) {
5627                 struct status *file = unmerged;
5629                 if (!file) {
5630                         file = calloc(1, sizeof(*file));
5631                         if (!file || !add_line_data(view, file, type))
5632                                 goto error_out;
5633                 }
5635                 /* Parse diff info part. */
5636                 if (status) {
5637                         file->status = status;
5638                         if (status == 'A')
5639                                 string_copy(file->old.rev, NULL_ID);
5641                 } else if (!file->status || file == unmerged) {
5642                         if (!status_get_diff(file, buf, strlen(buf)))
5643                                 goto error_out;
5645                         buf = io_get(&io, 0, TRUE);
5646                         if (!buf)
5647                                 break;
5649                         /* Collapse all modified entries that follow an
5650                          * associated unmerged entry. */
5651                         if (unmerged == file) {
5652                                 unmerged->status = 'U';
5653                                 unmerged = NULL;
5654                         } else if (file->status == 'U') {
5655                                 unmerged = file;
5656                         }
5657                 }
5659                 /* Grab the old name for rename/copy. */
5660                 if (!*file->old.name &&
5661                     (file->status == 'R' || file->status == 'C')) {
5662                         string_ncopy(file->old.name, buf, strlen(buf));
5664                         buf = io_get(&io, 0, TRUE);
5665                         if (!buf)
5666                                 break;
5667                 }
5669                 /* git-ls-files just delivers a NUL separated list of
5670                  * file names similar to the second half of the
5671                  * git-diff-* output. */
5672                 string_ncopy(file->new.name, buf, strlen(buf));
5673                 if (!*file->old.name)
5674                         string_copy(file->old.name, file->new.name);
5675                 file = NULL;
5676         }
5678         if (io_error(&io)) {
5679 error_out:
5680                 io_done(&io);
5681                 return FALSE;
5682         }
5684         if (!view->line[view->lines - 1].data)
5685                 add_line_data(view, NULL, LINE_STAT_NONE);
5687         io_done(&io);
5688         return TRUE;
5691 /* Don't show unmerged entries in the staged section. */
5692 static const char *status_diff_index_argv[] = {
5693         "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5694                              "--cached", "-M", "HEAD", NULL
5695 };
5697 static const char *status_diff_files_argv[] = {
5698         "git", "diff-files", "-z", NULL
5699 };
5701 static const char *status_list_other_argv[] = {
5702         "git", "ls-files", "-z", "--others", "--exclude-standard", opt_prefix, NULL, NULL,
5703 };
5705 static const char *status_list_no_head_argv[] = {
5706         "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5707 };
5709 static const char *update_index_argv[] = {
5710         "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5711 };
5713 /* Restore the previous line number to stay in the context or select a
5714  * line with something that can be updated. */
5715 static void
5716 status_restore(struct view *view)
5718         if (view->p_lineno >= view->lines)
5719                 view->p_lineno = view->lines - 1;
5720         while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
5721                 view->p_lineno++;
5722         while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
5723                 view->p_lineno--;
5725         /* If the above fails, always skip the "On branch" line. */
5726         if (view->p_lineno < view->lines)
5727                 view->lineno = view->p_lineno;
5728         else
5729                 view->lineno = 1;
5731         if (view->lineno < view->offset)
5732                 view->offset = view->lineno;
5733         else if (view->offset + view->height <= view->lineno)
5734                 view->offset = view->lineno - view->height + 1;
5736         view->p_restore = FALSE;
5739 static void
5740 status_update_onbranch(void)
5742         static const char *paths[][2] = {
5743                 { "rebase-apply/rebasing",      "Rebasing" },
5744                 { "rebase-apply/applying",      "Applying mailbox" },
5745                 { "rebase-apply/",              "Rebasing mailbox" },
5746                 { "rebase-merge/interactive",   "Interactive rebase" },
5747                 { "rebase-merge/",              "Rebase merge" },
5748                 { "MERGE_HEAD",                 "Merging" },
5749                 { "BISECT_LOG",                 "Bisecting" },
5750                 { "HEAD",                       "On branch" },
5751         };
5752         char buf[SIZEOF_STR];
5753         struct stat stat;
5754         int i;
5756         if (is_initial_commit()) {
5757                 string_copy(status_onbranch, "Initial commit");
5758                 return;
5759         }
5761         for (i = 0; i < ARRAY_SIZE(paths); i++) {
5762                 char *head = opt_head;
5764                 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5765                     lstat(buf, &stat) < 0)
5766                         continue;
5768                 if (!*opt_head) {
5769                         struct io io;
5771                         if (io_open(&io, "%s/rebase-merge/head-name", opt_git_dir) &&
5772                             io_read_buf(&io, buf, sizeof(buf))) {
5773                                 head = buf;
5774                                 if (!prefixcmp(head, "refs/heads/"))
5775                                         head += STRING_SIZE("refs/heads/");
5776                         }
5777                 }
5779                 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5780                         string_copy(status_onbranch, opt_head);
5781                 return;
5782         }
5784         string_copy(status_onbranch, "Not currently on any branch");
5787 /* First parse staged info using git-diff-index(1), then parse unstaged
5788  * info using git-diff-files(1), and finally untracked files using
5789  * git-ls-files(1). */
5790 static bool
5791 status_open(struct view *view)
5793         reset_view(view);
5795         add_line_data(view, NULL, LINE_STAT_HEAD);
5796         status_update_onbranch();
5798         io_run_bg(update_index_argv);
5800         if (is_initial_commit()) {
5801                 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5802                         return FALSE;
5803         } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5804                 return FALSE;
5805         }
5807         if (!opt_untracked_dirs_content)
5808                 status_list_other_argv[ARRAY_SIZE(status_list_other_argv) - 2] = "--directory";
5810         if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5811             !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5812                 return FALSE;
5814         /* Restore the exact position or use the specialized restore
5815          * mode? */
5816         if (!view->p_restore)
5817                 status_restore(view);
5818         return TRUE;
5821 static bool
5822 status_draw(struct view *view, struct line *line, unsigned int lineno)
5824         struct status *status = line->data;
5825         enum line_type type;
5826         const char *text;
5828         if (!status) {
5829                 switch (line->type) {
5830                 case LINE_STAT_STAGED:
5831                         type = LINE_STAT_SECTION;
5832                         text = "Changes to be committed:";
5833                         break;
5835                 case LINE_STAT_UNSTAGED:
5836                         type = LINE_STAT_SECTION;
5837                         text = "Changed but not updated:";
5838                         break;
5840                 case LINE_STAT_UNTRACKED:
5841                         type = LINE_STAT_SECTION;
5842                         text = "Untracked files:";
5843                         break;
5845                 case LINE_STAT_NONE:
5846                         type = LINE_DEFAULT;
5847                         text = "  (no files)";
5848                         break;
5850                 case LINE_STAT_HEAD:
5851                         type = LINE_STAT_HEAD;
5852                         text = status_onbranch;
5853                         break;
5855                 default:
5856                         return FALSE;
5857                 }
5858         } else {
5859                 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5861                 buf[0] = status->status;
5862                 if (draw_text(view, line->type, buf, TRUE))
5863                         return TRUE;
5864                 type = LINE_DEFAULT;
5865                 text = status->new.name;
5866         }
5868         draw_text(view, type, text, TRUE);
5869         return TRUE;
5872 static enum request
5873 status_load_error(struct view *view, struct view *stage, const char *path)
5875         if (displayed_views() == 2 || display[current_view] != view)
5876                 maximize_view(view);
5877         report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5878         return REQ_NONE;
5881 static enum request
5882 status_enter(struct view *view, struct line *line)
5884         struct status *status = line->data;
5885         const char *oldpath = status ? status->old.name : NULL;
5886         /* Diffs for unmerged entries are empty when passing the new
5887          * path, so leave it empty. */
5888         const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5889         const char *info;
5890         enum open_flags split;
5891         struct view *stage = VIEW(REQ_VIEW_STAGE);
5893         if (line->type == LINE_STAT_NONE ||
5894             (!status && line[1].type == LINE_STAT_NONE)) {
5895                 report("No file to diff");
5896                 return REQ_NONE;
5897         }
5899         switch (line->type) {
5900         case LINE_STAT_STAGED:
5901                 if (is_initial_commit()) {
5902                         const char *no_head_diff_argv[] = {
5903                                 "git", "diff", "--no-color", "--patch-with-stat",
5904                                         "--", "/dev/null", newpath, NULL
5905                         };
5907                         if (!prepare_update(stage, no_head_diff_argv, opt_cdup))
5908                                 return status_load_error(view, stage, newpath);
5909                 } else {
5910                         const char *index_show_argv[] = {
5911                                 "git", "diff-index", "--root", "--patch-with-stat",
5912                                         "-C", "-M", "--cached", "HEAD", "--",
5913                                         oldpath, newpath, NULL
5914                         };
5916                         if (!prepare_update(stage, index_show_argv, opt_cdup))
5917                                 return status_load_error(view, stage, newpath);
5918                 }
5920                 if (status)
5921                         info = "Staged changes to %s";
5922                 else
5923                         info = "Staged changes";
5924                 break;
5926         case LINE_STAT_UNSTAGED:
5927         {
5928                 const char *files_show_argv[] = {
5929                         "git", "diff-files", "--root", "--patch-with-stat",
5930                                 "-C", "-M", "--", oldpath, newpath, NULL
5931                 };
5933                 if (!prepare_update(stage, files_show_argv, opt_cdup))
5934                         return status_load_error(view, stage, newpath);
5935                 if (status)
5936                         info = "Unstaged changes to %s";
5937                 else
5938                         info = "Unstaged changes";
5939                 break;
5940         }
5941         case LINE_STAT_UNTRACKED:
5942                 if (!newpath) {
5943                         report("No file to show");
5944                         return REQ_NONE;
5945                 }
5947                 if (!suffixcmp(status->new.name, -1, "/")) {
5948                         report("Cannot display a directory");
5949                         return REQ_NONE;
5950                 }
5952                 if (!prepare_update_file(stage, newpath))
5953                         return status_load_error(view, stage, newpath);
5954                 info = "Untracked file %s";
5955                 break;
5957         case LINE_STAT_HEAD:
5958                 return REQ_NONE;
5960         default:
5961                 die("line type %d not handled in switch", line->type);
5962         }
5964         split = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
5965         open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5966         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5967                 if (status) {
5968                         stage_status = *status;
5969                 } else {
5970                         memset(&stage_status, 0, sizeof(stage_status));
5971                 }
5973                 stage_line_type = line->type;
5974                 stage_chunks = 0;
5975                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5976         }
5978         return REQ_NONE;
5981 static bool
5982 status_exists(struct status *status, enum line_type type)
5984         struct view *view = VIEW(REQ_VIEW_STATUS);
5985         unsigned long lineno;
5987         for (lineno = 0; lineno < view->lines; lineno++) {
5988                 struct line *line = &view->line[lineno];
5989                 struct status *pos = line->data;
5991                 if (line->type != type)
5992                         continue;
5993                 if (!pos && (!status || !status->status) && line[1].data) {
5994                         select_view_line(view, lineno);
5995                         return TRUE;
5996                 }
5997                 if (pos && !strcmp(status->new.name, pos->new.name)) {
5998                         select_view_line(view, lineno);
5999                         return TRUE;
6000                 }
6001         }
6003         return FALSE;
6007 static bool
6008 status_update_prepare(struct io *io, enum line_type type)
6010         const char *staged_argv[] = {
6011                 "git", "update-index", "-z", "--index-info", NULL
6012         };
6013         const char *others_argv[] = {
6014                 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
6015         };
6017         switch (type) {
6018         case LINE_STAT_STAGED:
6019                 return io_run(io, IO_WR, opt_cdup, staged_argv);
6021         case LINE_STAT_UNSTAGED:
6022         case LINE_STAT_UNTRACKED:
6023                 return io_run(io, IO_WR, opt_cdup, others_argv);
6025         default:
6026                 die("line type %d not handled in switch", type);
6027                 return FALSE;
6028         }
6031 static bool
6032 status_update_write(struct io *io, struct status *status, enum line_type type)
6034         char buf[SIZEOF_STR];
6035         size_t bufsize = 0;
6037         switch (type) {
6038         case LINE_STAT_STAGED:
6039                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
6040                                         status->old.mode,
6041                                         status->old.rev,
6042                                         status->old.name, 0))
6043                         return FALSE;
6044                 break;
6046         case LINE_STAT_UNSTAGED:
6047         case LINE_STAT_UNTRACKED:
6048                 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
6049                         return FALSE;
6050                 break;
6052         default:
6053                 die("line type %d not handled in switch", type);
6054         }
6056         return io_write(io, buf, bufsize);
6059 static bool
6060 status_update_file(struct status *status, enum line_type type)
6062         struct io io;
6063         bool result;
6065         if (!status_update_prepare(&io, type))
6066                 return FALSE;
6068         result = status_update_write(&io, status, type);
6069         return io_done(&io) && result;
6072 static bool
6073 status_update_files(struct view *view, struct line *line)
6075         char buf[sizeof(view->ref)];
6076         struct io io;
6077         bool result = TRUE;
6078         struct line *pos = view->line + view->lines;
6079         int files = 0;
6080         int file, done;
6081         int cursor_y = -1, cursor_x = -1;
6083         if (!status_update_prepare(&io, line->type))
6084                 return FALSE;
6086         for (pos = line; pos < view->line + view->lines && pos->data; pos++)
6087                 files++;
6089         string_copy(buf, view->ref);
6090         getsyx(cursor_y, cursor_x);
6091         for (file = 0, done = 5; result && file < files; line++, file++) {
6092                 int almost_done = file * 100 / files;
6094                 if (almost_done > done) {
6095                         done = almost_done;
6096                         string_format(view->ref, "updating file %u of %u (%d%% done)",
6097                                       file, files, done);
6098                         update_view_title(view);
6099                         setsyx(cursor_y, cursor_x);
6100                         doupdate();
6101                 }
6102                 result = status_update_write(&io, line->data, line->type);
6103         }
6104         string_copy(view->ref, buf);
6106         return io_done(&io) && result;
6109 static bool
6110 status_update(struct view *view)
6112         struct line *line = &view->line[view->lineno];
6114         assert(view->lines);
6116         if (!line->data) {
6117                 /* This should work even for the "On branch" line. */
6118                 if (line < view->line + view->lines && !line[1].data) {
6119                         report("Nothing to update");
6120                         return FALSE;
6121                 }
6123                 if (!status_update_files(view, line + 1)) {
6124                         report("Failed to update file status");
6125                         return FALSE;
6126                 }
6128         } else if (!status_update_file(line->data, line->type)) {
6129                 report("Failed to update file status");
6130                 return FALSE;
6131         }
6133         return TRUE;
6136 static bool
6137 status_revert(struct status *status, enum line_type type, bool has_none)
6139         if (!status || type != LINE_STAT_UNSTAGED) {
6140                 if (type == LINE_STAT_STAGED) {
6141                         report("Cannot revert changes to staged files");
6142                 } else if (type == LINE_STAT_UNTRACKED) {
6143                         report("Cannot revert changes to untracked files");
6144                 } else if (has_none) {
6145                         report("Nothing to revert");
6146                 } else {
6147                         report("Cannot revert changes to multiple files");
6148                 }
6150         } else if (prompt_yesno("Are you sure you want to revert changes?")) {
6151                 char mode[10] = "100644";
6152                 const char *reset_argv[] = {
6153                         "git", "update-index", "--cacheinfo", mode,
6154                                 status->old.rev, status->old.name, NULL
6155                 };
6156                 const char *checkout_argv[] = {
6157                         "git", "checkout", "--", status->old.name, NULL
6158                 };
6160                 if (status->status == 'U') {
6161                         string_format(mode, "%5o", status->old.mode);
6163                         if (status->old.mode == 0 && status->new.mode == 0) {
6164                                 reset_argv[2] = "--force-remove";
6165                                 reset_argv[3] = status->old.name;
6166                                 reset_argv[4] = NULL;
6167                         }
6169                         if (!io_run_fg(reset_argv, opt_cdup))
6170                                 return FALSE;
6171                         if (status->old.mode == 0 && status->new.mode == 0)
6172                                 return TRUE;
6173                 }
6175                 return io_run_fg(checkout_argv, opt_cdup);
6176         }
6178         return FALSE;
6181 static enum request
6182 status_request(struct view *view, enum request request, struct line *line)
6184         struct status *status = line->data;
6186         switch (request) {
6187         case REQ_STATUS_UPDATE:
6188                 if (!status_update(view))
6189                         return REQ_NONE;
6190                 break;
6192         case REQ_STATUS_REVERT:
6193                 if (!status_revert(status, line->type, status_has_none(view, line)))
6194                         return REQ_NONE;
6195                 break;
6197         case REQ_STATUS_MERGE:
6198                 if (!status || status->status != 'U') {
6199                         report("Merging only possible for files with unmerged status ('U').");
6200                         return REQ_NONE;
6201                 }
6202                 open_mergetool(status->new.name);
6203                 break;
6205         case REQ_EDIT:
6206                 if (!status)
6207                         return request;
6208                 if (status->status == 'D') {
6209                         report("File has been deleted.");
6210                         return REQ_NONE;
6211                 }
6213                 open_editor(status->new.name);
6214                 break;
6216         case REQ_VIEW_BLAME:
6217                 if (status)
6218                         opt_ref[0] = 0;
6219                 return request;
6221         case REQ_ENTER:
6222                 /* After returning the status view has been split to
6223                  * show the stage view. No further reloading is
6224                  * necessary. */
6225                 return status_enter(view, line);
6227         case REQ_REFRESH:
6228                 /* Simply reload the view. */
6229                 break;
6231         default:
6232                 return request;
6233         }
6235         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
6237         return REQ_NONE;
6240 static void
6241 status_select(struct view *view, struct line *line)
6243         struct status *status = line->data;
6244         char file[SIZEOF_STR] = "all files";
6245         const char *text;
6246         const char *key;
6248         if (status && !string_format(file, "'%s'", status->new.name))
6249                 return;
6251         if (!status && line[1].type == LINE_STAT_NONE)
6252                 line++;
6254         switch (line->type) {
6255         case LINE_STAT_STAGED:
6256                 text = "Press %s to unstage %s for commit";
6257                 break;
6259         case LINE_STAT_UNSTAGED:
6260                 text = "Press %s to stage %s for commit";
6261                 break;
6263         case LINE_STAT_UNTRACKED:
6264                 text = "Press %s to stage %s for addition";
6265                 break;
6267         case LINE_STAT_HEAD:
6268         case LINE_STAT_NONE:
6269                 text = "Nothing to update";
6270                 break;
6272         default:
6273                 die("line type %d not handled in switch", line->type);
6274         }
6276         if (status && status->status == 'U') {
6277                 text = "Press %s to resolve conflict in %s";
6278                 key = get_key(KEYMAP_STATUS, REQ_STATUS_MERGE);
6280         } else {
6281                 key = get_key(KEYMAP_STATUS, REQ_STATUS_UPDATE);
6282         }
6284         string_format(view->ref, text, key, file);
6285         if (status)
6286                 string_copy(opt_file, status->new.name);
6289 static bool
6290 status_grep(struct view *view, struct line *line)
6292         struct status *status = line->data;
6294         if (status) {
6295                 const char buf[2] = { status->status, 0 };
6296                 const char *text[] = { status->new.name, buf, NULL };
6298                 return grep_text(view, text);
6299         }
6301         return FALSE;
6304 static struct view_ops status_ops = {
6305         "file",
6306         NULL,
6307         status_open,
6308         NULL,
6309         status_draw,
6310         status_request,
6311         status_grep,
6312         status_select,
6313 };
6316 static bool
6317 stage_diff_write(struct io *io, struct line *line, struct line *end)
6319         while (line < end) {
6320                 if (!io_write(io, line->data, strlen(line->data)) ||
6321                     !io_write(io, "\n", 1))
6322                         return FALSE;
6323                 line++;
6324                 if (line->type == LINE_DIFF_CHUNK ||
6325                     line->type == LINE_DIFF_HEADER)
6326                         break;
6327         }
6329         return TRUE;
6332 static struct line *
6333 stage_diff_find(struct view *view, struct line *line, enum line_type type)
6335         for (; view->line < line; line--)
6336                 if (line->type == type)
6337                         return line;
6339         return NULL;
6342 static bool
6343 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
6345         const char *apply_argv[SIZEOF_ARG] = {
6346                 "git", "apply", "--whitespace=nowarn", NULL
6347         };
6348         struct line *diff_hdr;
6349         struct io io;
6350         int argc = 3;
6352         diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
6353         if (!diff_hdr)
6354                 return FALSE;
6356         if (!revert)
6357                 apply_argv[argc++] = "--cached";
6358         if (revert || stage_line_type == LINE_STAT_STAGED)
6359                 apply_argv[argc++] = "-R";
6360         apply_argv[argc++] = "-";
6361         apply_argv[argc++] = NULL;
6362         if (!io_run(&io, IO_WR, opt_cdup, apply_argv))
6363                 return FALSE;
6365         if (!stage_diff_write(&io, diff_hdr, chunk) ||
6366             !stage_diff_write(&io, chunk, view->line + view->lines))
6367                 chunk = NULL;
6369         io_done(&io);
6370         io_run_bg(update_index_argv);
6372         return chunk ? TRUE : FALSE;
6375 static bool
6376 stage_update(struct view *view, struct line *line)
6378         struct line *chunk = NULL;
6380         if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
6381                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6383         if (chunk) {
6384                 if (!stage_apply_chunk(view, chunk, FALSE)) {
6385                         report("Failed to apply chunk");
6386                         return FALSE;
6387                 }
6389         } else if (!stage_status.status) {
6390                 view = VIEW(REQ_VIEW_STATUS);
6392                 for (line = view->line; line < view->line + view->lines; line++)
6393                         if (line->type == stage_line_type)
6394                                 break;
6396                 if (!status_update_files(view, line + 1)) {
6397                         report("Failed to update files");
6398                         return FALSE;
6399                 }
6401         } else if (!status_update_file(&stage_status, stage_line_type)) {
6402                 report("Failed to update file");
6403                 return FALSE;
6404         }
6406         return TRUE;
6409 static bool
6410 stage_revert(struct view *view, struct line *line)
6412         struct line *chunk = NULL;
6414         if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
6415                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6417         if (chunk) {
6418                 if (!prompt_yesno("Are you sure you want to revert changes?"))
6419                         return FALSE;
6421                 if (!stage_apply_chunk(view, chunk, TRUE)) {
6422                         report("Failed to revert chunk");
6423                         return FALSE;
6424                 }
6425                 return TRUE;
6427         } else {
6428                 return status_revert(stage_status.status ? &stage_status : NULL,
6429                                      stage_line_type, FALSE);
6430         }
6434 static void
6435 stage_next(struct view *view, struct line *line)
6437         int i;
6439         if (!stage_chunks) {
6440                 for (line = view->line; line < view->line + view->lines; line++) {
6441                         if (line->type != LINE_DIFF_CHUNK)
6442                                 continue;
6444                         if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
6445                                 report("Allocation failure");
6446                                 return;
6447                         }
6449                         stage_chunk[stage_chunks++] = line - view->line;
6450                 }
6451         }
6453         for (i = 0; i < stage_chunks; i++) {
6454                 if (stage_chunk[i] > view->lineno) {
6455                         do_scroll_view(view, stage_chunk[i] - view->lineno);
6456                         report("Chunk %d of %d", i + 1, stage_chunks);
6457                         return;
6458                 }
6459         }
6461         report("No next chunk found");
6464 static enum request
6465 stage_request(struct view *view, enum request request, struct line *line)
6467         switch (request) {
6468         case REQ_STATUS_UPDATE:
6469                 if (!stage_update(view, line))
6470                         return REQ_NONE;
6471                 break;
6473         case REQ_STATUS_REVERT:
6474                 if (!stage_revert(view, line))
6475                         return REQ_NONE;
6476                 break;
6478         case REQ_STAGE_NEXT:
6479                 if (stage_line_type == LINE_STAT_UNTRACKED) {
6480                         report("File is untracked; press %s to add",
6481                                get_key(KEYMAP_STAGE, REQ_STATUS_UPDATE));
6482                         return REQ_NONE;
6483                 }
6484                 stage_next(view, line);
6485                 return REQ_NONE;
6487         case REQ_EDIT:
6488                 if (!stage_status.new.name[0])
6489                         return request;
6490                 if (stage_status.status == 'D') {
6491                         report("File has been deleted.");
6492                         return REQ_NONE;
6493                 }
6495                 open_editor(stage_status.new.name);
6496                 break;
6498         case REQ_REFRESH:
6499                 /* Reload everything ... */
6500                 break;
6502         case REQ_VIEW_BLAME:
6503                 if (stage_status.new.name[0]) {
6504                         string_copy(opt_file, stage_status.new.name);
6505                         opt_ref[0] = 0;
6506                 }
6507                 return request;
6509         case REQ_ENTER:
6510                 return pager_request(view, request, line);
6512         default:
6513                 return request;
6514         }
6516         VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
6517         open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
6519         /* Check whether the staged entry still exists, and close the
6520          * stage view if it doesn't. */
6521         if (!status_exists(&stage_status, stage_line_type)) {
6522                 status_restore(VIEW(REQ_VIEW_STATUS));
6523                 return REQ_VIEW_CLOSE;
6524         }
6526         if (stage_line_type == LINE_STAT_UNTRACKED) {
6527                 if (!suffixcmp(stage_status.new.name, -1, "/")) {
6528                         report("Cannot display a directory");
6529                         return REQ_NONE;
6530                 }
6532                 if (!prepare_update_file(view, stage_status.new.name)) {
6533                         report("Failed to open file: %s", strerror(errno));
6534                         return REQ_NONE;
6535                 }
6536         }
6537         open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
6539         return REQ_NONE;
6542 static struct view_ops stage_ops = {
6543         "line",
6544         NULL,
6545         NULL,
6546         pager_read,
6547         pager_draw,
6548         stage_request,
6549         pager_grep,
6550         pager_select,
6551 };
6554 /*
6555  * Revision graph
6556  */
6558 struct commit {
6559         char id[SIZEOF_REV];            /* SHA1 ID. */
6560         char title[128];                /* First line of the commit message. */
6561         const char *author;             /* Author of the commit. */
6562         struct time time;               /* Date from the author ident. */
6563         struct ref_list *refs;          /* Repository references. */
6564         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
6565         size_t graph_size;              /* The width of the graph array. */
6566         bool has_parents;               /* Rewritten --parents seen. */
6567 };
6569 /* Size of rev graph with no  "padding" columns */
6570 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6572 struct rev_graph {
6573         struct rev_graph *prev, *next, *parents;
6574         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
6575         size_t size;
6576         struct commit *commit;
6577         size_t pos;
6578         unsigned int boundary:1;
6579 };
6581 /* Parents of the commit being visualized. */
6582 static struct rev_graph graph_parents[4];
6584 /* The current stack of revisions on the graph. */
6585 static struct rev_graph graph_stacks[4] = {
6586         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
6587         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
6588         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
6589         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
6590 };
6592 static inline bool
6593 graph_parent_is_merge(struct rev_graph *graph)
6595         return graph->parents->size > 1;
6598 static inline void
6599 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
6601         struct commit *commit = graph->commit;
6603         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
6604                 commit->graph[commit->graph_size++] = symbol;
6607 static void
6608 clear_rev_graph(struct rev_graph *graph)
6610         graph->boundary = 0;
6611         graph->size = graph->pos = 0;
6612         graph->commit = NULL;
6613         memset(graph->parents, 0, sizeof(*graph->parents));
6616 static void
6617 done_rev_graph(struct rev_graph *graph)
6619         if (graph_parent_is_merge(graph) &&
6620             graph->pos < graph->size - 1 &&
6621             graph->next->size == graph->size + graph->parents->size - 1) {
6622                 size_t i = graph->pos + graph->parents->size - 1;
6624                 graph->commit->graph_size = i * 2;
6625                 while (i < graph->next->size - 1) {
6626                         append_to_rev_graph(graph, ' ');
6627                         append_to_rev_graph(graph, '\\');
6628                         i++;
6629                 }
6630         }
6632         clear_rev_graph(graph);
6635 static void
6636 push_rev_graph(struct rev_graph *graph, const char *parent)
6638         int i;
6640         /* "Collapse" duplicate parents lines.
6641          *
6642          * FIXME: This needs to also update update the drawn graph but
6643          * for now it just serves as a method for pruning graph lines. */
6644         for (i = 0; i < graph->size; i++)
6645                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
6646                         return;
6648         if (graph->size < SIZEOF_REVITEMS) {
6649                 string_copy_rev(graph->rev[graph->size++], parent);
6650         }
6653 static chtype
6654 get_rev_graph_symbol(struct rev_graph *graph)
6656         chtype symbol;
6658         if (graph->boundary)
6659                 symbol = REVGRAPH_BOUND;
6660         else if (graph->parents->size == 0)
6661                 symbol = REVGRAPH_INIT;
6662         else if (graph_parent_is_merge(graph))
6663                 symbol = REVGRAPH_MERGE;
6664         else if (graph->pos >= graph->size)
6665                 symbol = REVGRAPH_BRANCH;
6666         else
6667                 symbol = REVGRAPH_COMMIT;
6669         return symbol;
6672 static void
6673 draw_rev_graph(struct rev_graph *graph)
6675         struct rev_filler {
6676                 chtype separator, line;
6677         };
6678         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
6679         static struct rev_filler fillers[] = {
6680                 { ' ',  '|' },
6681                 { '`',  '.' },
6682                 { '\'', ' ' },
6683                 { '/',  ' ' },
6684         };
6685         chtype symbol = get_rev_graph_symbol(graph);
6686         struct rev_filler *filler;
6687         size_t i;
6689         fillers[DEFAULT].line = opt_line_graphics ? ACS_VLINE : '|';
6690         filler = &fillers[DEFAULT];
6692         for (i = 0; i < graph->pos; i++) {
6693                 append_to_rev_graph(graph, filler->line);
6694                 if (graph_parent_is_merge(graph->prev) &&
6695                     graph->prev->pos == i)
6696                         filler = &fillers[RSHARP];
6698                 append_to_rev_graph(graph, filler->separator);
6699         }
6701         /* Place the symbol for this revision. */
6702         append_to_rev_graph(graph, symbol);
6704         if (graph->prev->size > graph->size)
6705                 filler = &fillers[RDIAG];
6706         else
6707                 filler = &fillers[DEFAULT];
6709         i++;
6711         for (; i < graph->size; i++) {
6712                 append_to_rev_graph(graph, filler->separator);
6713                 append_to_rev_graph(graph, filler->line);
6714                 if (graph_parent_is_merge(graph->prev) &&
6715                     i < graph->prev->pos + graph->parents->size)
6716                         filler = &fillers[RSHARP];
6717                 if (graph->prev->size > graph->size)
6718                         filler = &fillers[LDIAG];
6719         }
6721         if (graph->prev->size > graph->size) {
6722                 append_to_rev_graph(graph, filler->separator);
6723                 if (filler->line != ' ')
6724                         append_to_rev_graph(graph, filler->line);
6725         }
6728 /* Prepare the next rev graph */
6729 static void
6730 prepare_rev_graph(struct rev_graph *graph)
6732         size_t i;
6734         /* First, traverse all lines of revisions up to the active one. */
6735         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6736                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6737                         break;
6739                 push_rev_graph(graph->next, graph->rev[graph->pos]);
6740         }
6742         /* Interleave the new revision parent(s). */
6743         for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6744                 push_rev_graph(graph->next, graph->parents->rev[i]);
6746         /* Lastly, put any remaining revisions. */
6747         for (i = graph->pos + 1; i < graph->size; i++)
6748                 push_rev_graph(graph->next, graph->rev[i]);
6751 static void
6752 update_rev_graph(struct view *view, struct rev_graph *graph)
6754         /* If this is the finalizing update ... */
6755         if (graph->commit)
6756                 prepare_rev_graph(graph);
6758         /* Graph visualization needs a one rev look-ahead,
6759          * so the first update doesn't visualize anything. */
6760         if (!graph->prev->commit)
6761                 return;
6763         if (view->lines > 2)
6764                 view->line[view->lines - 3].dirty = 1;
6765         if (view->lines > 1)
6766                 view->line[view->lines - 2].dirty = 1;
6767         draw_rev_graph(graph->prev);
6768         done_rev_graph(graph->prev->prev);
6772 /*
6773  * Main view backend
6774  */
6776 static const char *main_argv[SIZEOF_ARG] = {
6777         "git", "log", "--no-color", "--pretty=raw", "--parents",
6778                 "--topo-order", "%(diffargs)", "%(revargs)",
6779                 "--", "%(fileargs)", NULL
6780 };
6782 static bool
6783 main_draw(struct view *view, struct line *line, unsigned int lineno)
6785         struct commit *commit = line->data;
6787         if (!commit->author)
6788                 return FALSE;
6790         if (opt_date && draw_date(view, &commit->time))
6791                 return TRUE;
6793         if (opt_author && draw_author(view, commit->author))
6794                 return TRUE;
6796         if (opt_rev_graph && commit->graph_size &&
6797             draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6798                 return TRUE;
6800         if (opt_show_refs && commit->refs) {
6801                 size_t i;
6803                 for (i = 0; i < commit->refs->size; i++) {
6804                         struct ref *ref = commit->refs->refs[i];
6805                         enum line_type type;
6807                         if (ref->head)
6808                                 type = LINE_MAIN_HEAD;
6809                         else if (ref->ltag)
6810                                 type = LINE_MAIN_LOCAL_TAG;
6811                         else if (ref->tag)
6812                                 type = LINE_MAIN_TAG;
6813                         else if (ref->tracked)
6814                                 type = LINE_MAIN_TRACKED;
6815                         else if (ref->remote)
6816                                 type = LINE_MAIN_REMOTE;
6817                         else
6818                                 type = LINE_MAIN_REF;
6820                         if (draw_text(view, type, "[", TRUE) ||
6821                             draw_text(view, type, ref->name, TRUE) ||
6822                             draw_text(view, type, "]", TRUE))
6823                                 return TRUE;
6825                         if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6826                                 return TRUE;
6827                 }
6828         }
6830         draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6831         return TRUE;
6834 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6835 static bool
6836 main_read(struct view *view, char *line)
6838         static struct rev_graph *graph = graph_stacks;
6839         enum line_type type;
6840         struct commit *commit;
6842         if (!line) {
6843                 int i;
6845                 if (!view->lines && !view->prev)
6846                         die("No revisions match the given arguments.");
6847                 if (view->lines > 0) {
6848                         commit = view->line[view->lines - 1].data;
6849                         view->line[view->lines - 1].dirty = 1;
6850                         if (!commit->author) {
6851                                 view->lines--;
6852                                 free(commit);
6853                                 graph->commit = NULL;
6854                         }
6855                 }
6856                 update_rev_graph(view, graph);
6858                 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6859                         clear_rev_graph(&graph_stacks[i]);
6860                 return TRUE;
6861         }
6863         type = get_line_type(line);
6864         if (type == LINE_COMMIT) {
6865                 commit = calloc(1, sizeof(struct commit));
6866                 if (!commit)
6867                         return FALSE;
6869                 line += STRING_SIZE("commit ");
6870                 if (*line == '-') {
6871                         graph->boundary = 1;
6872                         line++;
6873                 }
6875                 string_copy_rev(commit->id, line);
6876                 commit->refs = get_ref_list(commit->id);
6877                 graph->commit = commit;
6878                 add_line_data(view, commit, LINE_MAIN_COMMIT);
6880                 while ((line = strchr(line, ' '))) {
6881                         line++;
6882                         push_rev_graph(graph->parents, line);
6883                         commit->has_parents = TRUE;
6884                 }
6885                 return TRUE;
6886         }
6888         if (!view->lines)
6889                 return TRUE;
6890         commit = view->line[view->lines - 1].data;
6892         switch (type) {
6893         case LINE_PARENT:
6894                 if (commit->has_parents)
6895                         break;
6896                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6897                 break;
6899         case LINE_AUTHOR:
6900                 parse_author_line(line + STRING_SIZE("author "),
6901                                   &commit->author, &commit->time);
6902                 update_rev_graph(view, graph);
6903                 graph = graph->next;
6904                 break;
6906         default:
6907                 /* Fill in the commit title if it has not already been set. */
6908                 if (commit->title[0])
6909                         break;
6911                 /* Require titles to start with a non-space character at the
6912                  * offset used by git log. */
6913                 if (strncmp(line, "    ", 4))
6914                         break;
6915                 line += 4;
6916                 /* Well, if the title starts with a whitespace character,
6917                  * try to be forgiving.  Otherwise we end up with no title. */
6918                 while (isspace(*line))
6919                         line++;
6920                 if (*line == '\0')
6921                         break;
6922                 /* FIXME: More graceful handling of titles; append "..." to
6923                  * shortened titles, etc. */
6925                 string_expand(commit->title, sizeof(commit->title), line, 1);
6926                 view->line[view->lines - 1].dirty = 1;
6927         }
6929         return TRUE;
6932 static enum request
6933 main_request(struct view *view, enum request request, struct line *line)
6935         enum open_flags flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
6937         switch (request) {
6938         case REQ_ENTER:
6939                 if (view_is_displayed(view) && display[0] != view)
6940                         maximize_view(view);
6941                 open_view(view, REQ_VIEW_DIFF, flags);
6942                 break;
6943         case REQ_REFRESH:
6944                 load_refs();
6945                 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6946                 break;
6947         default:
6948                 return request;
6949         }
6951         return REQ_NONE;
6954 static bool
6955 grep_refs(struct ref_list *list, regex_t *regex)
6957         regmatch_t pmatch;
6958         size_t i;
6960         if (!opt_show_refs || !list)
6961                 return FALSE;
6963         for (i = 0; i < list->size; i++) {
6964                 if (regexec(regex, list->refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6965                         return TRUE;
6966         }
6968         return FALSE;
6971 static bool
6972 main_grep(struct view *view, struct line *line)
6974         struct commit *commit = line->data;
6975         const char *text[] = {
6976                 commit->title,
6977                 opt_author ? commit->author : "",
6978                 mkdate(&commit->time, opt_date),
6979                 NULL
6980         };
6982         return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6985 static void
6986 main_select(struct view *view, struct line *line)
6988         struct commit *commit = line->data;
6990         string_copy_rev(view->ref, commit->id);
6991         string_copy_rev(ref_commit, view->ref);
6994 static struct view_ops main_ops = {
6995         "commit",
6996         main_argv,
6997         NULL,
6998         main_read,
6999         main_draw,
7000         main_request,
7001         main_grep,
7002         main_select,
7003 };
7006 /*
7007  * Status management
7008  */
7010 /* Whether or not the curses interface has been initialized. */
7011 static bool cursed = FALSE;
7013 /* Terminal hacks and workarounds. */
7014 static bool use_scroll_redrawwin;
7015 static bool use_scroll_status_wclear;
7017 /* The status window is used for polling keystrokes. */
7018 static WINDOW *status_win;
7020 /* Reading from the prompt? */
7021 static bool input_mode = FALSE;
7023 static bool status_empty = FALSE;
7025 /* Update status and title window. */
7026 static void
7027 report(const char *msg, ...)
7029         struct view *view = display[current_view];
7031         if (input_mode)
7032                 return;
7034         if (!view) {
7035                 char buf[SIZEOF_STR];
7036                 va_list args;
7038                 va_start(args, msg);
7039                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
7040                         buf[sizeof(buf) - 1] = 0;
7041                         buf[sizeof(buf) - 2] = '.';
7042                         buf[sizeof(buf) - 3] = '.';
7043                         buf[sizeof(buf) - 4] = '.';
7044                 }
7045                 va_end(args);
7046                 die("%s", buf);
7047         }
7049         if (!status_empty || *msg) {
7050                 va_list args;
7052                 va_start(args, msg);
7054                 wmove(status_win, 0, 0);
7055                 if (view->has_scrolled && use_scroll_status_wclear)
7056                         wclear(status_win);
7057                 if (*msg) {
7058                         vwprintw(status_win, msg, args);
7059                         status_empty = FALSE;
7060                 } else {
7061                         status_empty = TRUE;
7062                 }
7063                 wclrtoeol(status_win);
7064                 wnoutrefresh(status_win);
7066                 va_end(args);
7067         }
7069         update_view_title(view);
7072 static void
7073 init_display(void)
7075         const char *term;
7076         int x, y;
7078         /* Initialize the curses library */
7079         if (isatty(STDIN_FILENO)) {
7080                 cursed = !!initscr();
7081                 opt_tty = stdin;
7082         } else {
7083                 /* Leave stdin and stdout alone when acting as a pager. */
7084                 opt_tty = fopen("/dev/tty", "r+");
7085                 if (!opt_tty)
7086                         die("Failed to open /dev/tty");
7087                 cursed = !!newterm(NULL, opt_tty, opt_tty);
7088         }
7090         if (!cursed)
7091                 die("Failed to initialize curses");
7093         nonl();         /* Disable conversion and detect newlines from input. */
7094         cbreak();       /* Take input chars one at a time, no wait for \n */
7095         noecho();       /* Don't echo input */
7096         leaveok(stdscr, FALSE);
7098         if (has_colors())
7099                 init_colors();
7101         getmaxyx(stdscr, y, x);
7102         status_win = newwin(1, 0, y - 1, 0);
7103         if (!status_win)
7104                 die("Failed to create status window");
7106         /* Enable keyboard mapping */
7107         keypad(status_win, TRUE);
7108         wbkgdset(status_win, get_line_attr(LINE_STATUS));
7110 #if defined(NCURSES_VERSION_PATCH) && (NCURSES_VERSION_PATCH >= 20080119)
7111         set_tabsize(opt_tab_size);
7112 #else
7113         TABSIZE = opt_tab_size;
7114 #endif
7116         term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
7117         if (term && !strcmp(term, "gnome-terminal")) {
7118                 /* In the gnome-terminal-emulator, the message from
7119                  * scrolling up one line when impossible followed by
7120                  * scrolling down one line causes corruption of the
7121                  * status line. This is fixed by calling wclear. */
7122                 use_scroll_status_wclear = TRUE;
7123                 use_scroll_redrawwin = FALSE;
7125         } else if (term && !strcmp(term, "xrvt-xpm")) {
7126                 /* No problems with full optimizations in xrvt-(unicode)
7127                  * and aterm. */
7128                 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
7130         } else {
7131                 /* When scrolling in (u)xterm the last line in the
7132                  * scrolling direction will update slowly. */
7133                 use_scroll_redrawwin = TRUE;
7134                 use_scroll_status_wclear = FALSE;
7135         }
7138 static int
7139 get_input(int prompt_position)
7141         struct view *view;
7142         int i, key, cursor_y, cursor_x;
7144         if (prompt_position)
7145                 input_mode = TRUE;
7147         while (TRUE) {
7148                 bool loading = FALSE;
7150                 foreach_view (view, i) {
7151                         update_view(view);
7152                         if (view_is_displayed(view) && view->has_scrolled &&
7153                             use_scroll_redrawwin)
7154                                 redrawwin(view->win);
7155                         view->has_scrolled = FALSE;
7156                         if (view->pipe)
7157                                 loading = TRUE;
7158                 }
7160                 /* Update the cursor position. */
7161                 if (prompt_position) {
7162                         getbegyx(status_win, cursor_y, cursor_x);
7163                         cursor_x = prompt_position;
7164                 } else {
7165                         view = display[current_view];
7166                         getbegyx(view->win, cursor_y, cursor_x);
7167                         cursor_x = view->width - 1;
7168                         cursor_y += view->lineno - view->offset;
7169                 }
7170                 setsyx(cursor_y, cursor_x);
7172                 /* Refresh, accept single keystroke of input */
7173                 doupdate();
7174                 nodelay(status_win, loading);
7175                 key = wgetch(status_win);
7177                 /* wgetch() with nodelay() enabled returns ERR when
7178                  * there's no input. */
7179                 if (key == ERR) {
7181                 } else if (key == KEY_RESIZE) {
7182                         int height, width;
7184                         getmaxyx(stdscr, height, width);
7186                         wresize(status_win, 1, width);
7187                         mvwin(status_win, height - 1, 0);
7188                         wnoutrefresh(status_win);
7189                         resize_display();
7190                         redraw_display(TRUE);
7192                 } else {
7193                         input_mode = FALSE;
7194                         return key;
7195                 }
7196         }
7199 static char *
7200 prompt_input(const char *prompt, input_handler handler, void *data)
7202         enum input_status status = INPUT_OK;
7203         static char buf[SIZEOF_STR];
7204         size_t pos = 0;
7206         buf[pos] = 0;
7208         while (status == INPUT_OK || status == INPUT_SKIP) {
7209                 int key;
7211                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
7212                 wclrtoeol(status_win);
7214                 key = get_input(pos + 1);
7215                 switch (key) {
7216                 case KEY_RETURN:
7217                 case KEY_ENTER:
7218                 case '\n':
7219                         status = pos ? INPUT_STOP : INPUT_CANCEL;
7220                         break;
7222                 case KEY_BACKSPACE:
7223                         if (pos > 0)
7224                                 buf[--pos] = 0;
7225                         else
7226                                 status = INPUT_CANCEL;
7227                         break;
7229                 case KEY_ESC:
7230                         status = INPUT_CANCEL;
7231                         break;
7233                 default:
7234                         if (pos >= sizeof(buf)) {
7235                                 report("Input string too long");
7236                                 return NULL;
7237                         }
7239                         status = handler(data, buf, key);
7240                         if (status == INPUT_OK)
7241                                 buf[pos++] = (char) key;
7242                 }
7243         }
7245         /* Clear the status window */
7246         status_empty = FALSE;
7247         report("");
7249         if (status == INPUT_CANCEL)
7250                 return NULL;
7252         buf[pos++] = 0;
7254         return buf;
7257 static enum input_status
7258 prompt_yesno_handler(void *data, char *buf, int c)
7260         if (c == 'y' || c == 'Y')
7261                 return INPUT_STOP;
7262         if (c == 'n' || c == 'N')
7263                 return INPUT_CANCEL;
7264         return INPUT_SKIP;
7267 static bool
7268 prompt_yesno(const char *prompt)
7270         char prompt2[SIZEOF_STR];
7272         if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
7273                 return FALSE;
7275         return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
7278 static enum input_status
7279 read_prompt_handler(void *data, char *buf, int c)
7281         return isprint(c) ? INPUT_OK : INPUT_SKIP;
7284 static char *
7285 read_prompt(const char *prompt)
7287         return prompt_input(prompt, read_prompt_handler, NULL);
7290 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected)
7292         enum input_status status = INPUT_OK;
7293         int size = 0;
7295         while (items[size].text)
7296                 size++;
7298         while (status == INPUT_OK) {
7299                 const struct menu_item *item = &items[*selected];
7300                 int key;
7301                 int i;
7303                 mvwprintw(status_win, 0, 0, "%s (%d of %d) ",
7304                           prompt, *selected + 1, size);
7305                 if (item->hotkey)
7306                         wprintw(status_win, "[%c] ", (char) item->hotkey);
7307                 wprintw(status_win, "%s", item->text);
7308                 wclrtoeol(status_win);
7310                 key = get_input(COLS - 1);
7311                 switch (key) {
7312                 case KEY_RETURN:
7313                 case KEY_ENTER:
7314                 case '\n':
7315                         status = INPUT_STOP;
7316                         break;
7318                 case KEY_LEFT:
7319                 case KEY_UP:
7320                         *selected = *selected - 1;
7321                         if (*selected < 0)
7322                                 *selected = size - 1;
7323                         break;
7325                 case KEY_RIGHT:
7326                 case KEY_DOWN:
7327                         *selected = (*selected + 1) % size;
7328                         break;
7330                 case KEY_ESC:
7331                         status = INPUT_CANCEL;
7332                         break;
7334                 default:
7335                         for (i = 0; items[i].text; i++)
7336                                 if (items[i].hotkey == key) {
7337                                         *selected = i;
7338                                         status = INPUT_STOP;
7339                                         break;
7340                                 }
7341                 }
7342         }
7344         /* Clear the status window */
7345         status_empty = FALSE;
7346         report("");
7348         return status != INPUT_CANCEL;
7351 /*
7352  * Repository properties
7353  */
7355 static struct ref **refs = NULL;
7356 static size_t refs_size = 0;
7357 static struct ref *refs_head = NULL;
7359 static struct ref_list **ref_lists = NULL;
7360 static size_t ref_lists_size = 0;
7362 DEFINE_ALLOCATOR(realloc_refs, struct ref *, 256)
7363 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
7364 DEFINE_ALLOCATOR(realloc_ref_lists, struct ref_list *, 8)
7366 static int
7367 compare_refs(const void *ref1_, const void *ref2_)
7369         const struct ref *ref1 = *(const struct ref **)ref1_;
7370         const struct ref *ref2 = *(const struct ref **)ref2_;
7372         if (ref1->tag != ref2->tag)
7373                 return ref2->tag - ref1->tag;
7374         if (ref1->ltag != ref2->ltag)
7375                 return ref2->ltag - ref2->ltag;
7376         if (ref1->head != ref2->head)
7377                 return ref2->head - ref1->head;
7378         if (ref1->tracked != ref2->tracked)
7379                 return ref2->tracked - ref1->tracked;
7380         if (ref1->remote != ref2->remote)
7381                 return ref2->remote - ref1->remote;
7382         return strcmp(ref1->name, ref2->name);
7385 static void
7386 foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data)
7388         size_t i;
7390         for (i = 0; i < refs_size; i++)
7391                 if (!visitor(data, refs[i]))
7392                         break;
7395 static struct ref *
7396 get_ref_head()
7398         return refs_head;
7401 static struct ref_list *
7402 get_ref_list(const char *id)
7404         struct ref_list *list;
7405         size_t i;
7407         for (i = 0; i < ref_lists_size; i++)
7408                 if (!strcmp(id, ref_lists[i]->id))
7409                         return ref_lists[i];
7411         if (!realloc_ref_lists(&ref_lists, ref_lists_size, 1))
7412                 return NULL;
7413         list = calloc(1, sizeof(*list));
7414         if (!list)
7415                 return NULL;
7417         for (i = 0; i < refs_size; i++) {
7418                 if (!strcmp(id, refs[i]->id) &&
7419                     realloc_refs_list(&list->refs, list->size, 1))
7420                         list->refs[list->size++] = refs[i];
7421         }
7423         if (!list->refs) {
7424                 free(list);
7425                 return NULL;
7426         }
7428         qsort(list->refs, list->size, sizeof(*list->refs), compare_refs);
7429         ref_lists[ref_lists_size++] = list;
7430         return list;
7433 static int
7434 read_ref(char *id, size_t idlen, char *name, size_t namelen, void *data)
7436         struct ref *ref = NULL;
7437         bool tag = FALSE;
7438         bool ltag = FALSE;
7439         bool remote = FALSE;
7440         bool tracked = FALSE;
7441         bool head = FALSE;
7442         int from = 0, to = refs_size - 1;
7444         if (!prefixcmp(name, "refs/tags/")) {
7445                 if (!suffixcmp(name, namelen, "^{}")) {
7446                         namelen -= 3;
7447                         name[namelen] = 0;
7448                 } else {
7449                         ltag = TRUE;
7450                 }
7452                 tag = TRUE;
7453                 namelen -= STRING_SIZE("refs/tags/");
7454                 name    += STRING_SIZE("refs/tags/");
7456         } else if (!prefixcmp(name, "refs/remotes/")) {
7457                 remote = TRUE;
7458                 namelen -= STRING_SIZE("refs/remotes/");
7459                 name    += STRING_SIZE("refs/remotes/");
7460                 tracked  = !strcmp(opt_remote, name);
7462         } else if (!prefixcmp(name, "refs/heads/")) {
7463                 namelen -= STRING_SIZE("refs/heads/");
7464                 name    += STRING_SIZE("refs/heads/");
7465                 if (!strncmp(opt_head, name, namelen))
7466                         return OK;
7468         } else if (!strcmp(name, "HEAD")) {
7469                 head     = TRUE;
7470                 if (*opt_head) {
7471                         namelen  = strlen(opt_head);
7472                         name     = opt_head;
7473                 }
7474         }
7476         /* If we are reloading or it's an annotated tag, replace the
7477          * previous SHA1 with the resolved commit id; relies on the fact
7478          * git-ls-remote lists the commit id of an annotated tag right
7479          * before the commit id it points to. */
7480         while (from <= to) {
7481                 size_t pos = (to + from) / 2;
7482                 int cmp = strcmp(name, refs[pos]->name);
7484                 if (!cmp) {
7485                         ref = refs[pos];
7486                         break;
7487                 }
7489                 if (cmp < 0)
7490                         to = pos - 1;
7491                 else
7492                         from = pos + 1;
7493         }
7495         if (!ref) {
7496                 if (!realloc_refs(&refs, refs_size, 1))
7497                         return ERR;
7498                 ref = calloc(1, sizeof(*ref) + namelen);
7499                 if (!ref)
7500                         return ERR;
7501                 memmove(refs + from + 1, refs + from,
7502                         (refs_size - from) * sizeof(*refs));
7503                 refs[from] = ref;
7504                 strncpy(ref->name, name, namelen);
7505                 refs_size++;
7506         }
7508         ref->head = head;
7509         ref->tag = tag;
7510         ref->ltag = ltag;
7511         ref->remote = remote;
7512         ref->tracked = tracked;
7513         string_copy_rev(ref->id, id);
7515         if (head)
7516                 refs_head = ref;
7517         return OK;
7520 static int
7521 load_refs(void)
7523         const char *head_argv[] = {
7524                 "git", "symbolic-ref", "HEAD", NULL
7525         };
7526         static const char *ls_remote_argv[SIZEOF_ARG] = {
7527                 "git", "ls-remote", opt_git_dir, NULL
7528         };
7529         static bool init = FALSE;
7530         size_t i;
7532         if (!init) {
7533                 if (!argv_from_env(ls_remote_argv, "TIG_LS_REMOTE"))
7534                         die("TIG_LS_REMOTE contains too many arguments");
7535                 init = TRUE;
7536         }
7538         if (!*opt_git_dir)
7539                 return OK;
7541         if (io_run_buf(head_argv, opt_head, sizeof(opt_head)) &&
7542             !prefixcmp(opt_head, "refs/heads/")) {
7543                 char *offset = opt_head + STRING_SIZE("refs/heads/");
7545                 memmove(opt_head, offset, strlen(offset) + 1);
7546         }
7548         refs_head = NULL;
7549         for (i = 0; i < refs_size; i++)
7550                 refs[i]->id[0] = 0;
7552         if (io_run_load(ls_remote_argv, "\t", read_ref, NULL) == ERR)
7553                 return ERR;
7555         /* Update the ref lists to reflect changes. */
7556         for (i = 0; i < ref_lists_size; i++) {
7557                 struct ref_list *list = ref_lists[i];
7558                 size_t old, new;
7560                 for (old = new = 0; old < list->size; old++)
7561                         if (!strcmp(list->id, list->refs[old]->id))
7562                                 list->refs[new++] = list->refs[old];
7563                 list->size = new;
7564         }
7566         return OK;
7569 static void
7570 set_remote_branch(const char *name, const char *value, size_t valuelen)
7572         if (!strcmp(name, ".remote")) {
7573                 string_ncopy(opt_remote, value, valuelen);
7575         } else if (*opt_remote && !strcmp(name, ".merge")) {
7576                 size_t from = strlen(opt_remote);
7578                 if (!prefixcmp(value, "refs/heads/"))
7579                         value += STRING_SIZE("refs/heads/");
7581                 if (!string_format_from(opt_remote, &from, "/%s", value))
7582                         opt_remote[0] = 0;
7583         }
7586 static void
7587 set_repo_config_option(char *name, char *value, enum option_code (*cmd)(int, const char **))
7589         const char *argv[SIZEOF_ARG] = { name, "=" };
7590         int argc = 1 + (cmd == option_set_command);
7591         enum option_code error;
7593         if (!argv_from_string(argv, &argc, value))
7594                 error = OPT_ERR_TOO_MANY_OPTION_ARGUMENTS;
7595         else
7596                 error = cmd(argc, argv);
7598         if (error != OPT_OK)
7599                 warn("Option 'tig.%s': %s", name, option_errors[error]);
7602 static bool
7603 set_environment_variable(const char *name, const char *value)
7605         size_t len = strlen(name) + 1 + strlen(value) + 1;
7606         char *env = malloc(len);
7608         if (env &&
7609             string_nformat(env, len, NULL, "%s=%s", name, value) &&
7610             putenv(env) == 0)
7611                 return TRUE;
7612         free(env);
7613         return FALSE;
7616 static void
7617 set_work_tree(const char *value)
7619         char cwd[SIZEOF_STR];
7621         if (!getcwd(cwd, sizeof(cwd)))
7622                 die("Failed to get cwd path: %s", strerror(errno));
7623         if (chdir(opt_git_dir) < 0)
7624                 die("Failed to chdir(%s): %s", strerror(errno));
7625         if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7626                 die("Failed to get git path: %s", strerror(errno));
7627         if (chdir(cwd) < 0)
7628                 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7629         if (chdir(value) < 0)
7630                 die("Failed to chdir(%s): %s", value, strerror(errno));
7631         if (!getcwd(cwd, sizeof(cwd)))
7632                 die("Failed to get cwd path: %s", strerror(errno));
7633         if (!set_environment_variable("GIT_WORK_TREE", cwd))
7634                 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7635         if (!set_environment_variable("GIT_DIR", opt_git_dir))
7636                 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7637         opt_is_inside_work_tree = TRUE;
7640 static int
7641 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen, void *data)
7643         if (!strcmp(name, "i18n.commitencoding"))
7644                 string_ncopy(opt_encoding, value, valuelen);
7646         else if (!strcmp(name, "core.editor"))
7647                 string_ncopy(opt_editor, value, valuelen);
7649         else if (!strcmp(name, "core.worktree"))
7650                 set_work_tree(value);
7652         else if (!prefixcmp(name, "tig.color."))
7653                 set_repo_config_option(name + 10, value, option_color_command);
7655         else if (!prefixcmp(name, "tig.bind."))
7656                 set_repo_config_option(name + 9, value, option_bind_command);
7658         else if (!prefixcmp(name, "tig."))
7659                 set_repo_config_option(name + 4, value, option_set_command);
7661         else if (*opt_head && !prefixcmp(name, "branch.") &&
7662                  !strncmp(name + 7, opt_head, strlen(opt_head)))
7663                 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7665         return OK;
7668 static int
7669 load_git_config(void)
7671         const char *config_list_argv[] = { "git", "config", "--list", NULL };
7673         return io_run_load(config_list_argv, "=", read_repo_config_option, NULL);
7676 static int
7677 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen, void *data)
7679         if (!opt_git_dir[0]) {
7680                 string_ncopy(opt_git_dir, name, namelen);
7682         } else if (opt_is_inside_work_tree == -1) {
7683                 /* This can be 3 different values depending on the
7684                  * version of git being used. If git-rev-parse does not
7685                  * understand --is-inside-work-tree it will simply echo
7686                  * the option else either "true" or "false" is printed.
7687                  * Default to true for the unknown case. */
7688                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7690         } else if (*name == '.') {
7691                 string_ncopy(opt_cdup, name, namelen);
7693         } else {
7694                 string_ncopy(opt_prefix, name, namelen);
7695         }
7697         return OK;
7700 static int
7701 load_repo_info(void)
7703         const char *rev_parse_argv[] = {
7704                 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7705                         "--show-cdup", "--show-prefix", NULL
7706         };
7708         return io_run_load(rev_parse_argv, "=", read_repo_info, NULL);
7712 /*
7713  * Main
7714  */
7716 static const char usage[] =
7717 "tig " TIG_VERSION " (" __DATE__ ")\n"
7718 "\n"
7719 "Usage: tig        [options] [revs] [--] [paths]\n"
7720 "   or: tig show   [options] [revs] [--] [paths]\n"
7721 "   or: tig blame  [rev] path\n"
7722 "   or: tig status\n"
7723 "   or: tig <      [git command output]\n"
7724 "\n"
7725 "Options:\n"
7726 "  -v, --version   Show version and exit\n"
7727 "  -h, --help      Show help message and exit";
7729 static void __NORETURN
7730 quit(int sig)
7732         /* XXX: Restore tty modes and let the OS cleanup the rest! */
7733         if (cursed)
7734                 endwin();
7735         exit(0);
7738 static void __NORETURN
7739 die(const char *err, ...)
7741         va_list args;
7743         endwin();
7745         va_start(args, err);
7746         fputs("tig: ", stderr);
7747         vfprintf(stderr, err, args);
7748         fputs("\n", stderr);
7749         va_end(args);
7751         exit(1);
7754 static void
7755 warn(const char *msg, ...)
7757         va_list args;
7759         va_start(args, msg);
7760         fputs("tig warning: ", stderr);
7761         vfprintf(stderr, msg, args);
7762         fputs("\n", stderr);
7763         va_end(args);
7766 static int
7767 read_filter_args(char *name, size_t namelen, char *value, size_t valuelen, void *data)
7769         const char ***filter_args = data;
7771         return argv_append(filter_args, name) ? OK : ERR;
7774 static void
7775 filter_rev_parse(const char ***args, const char *arg1, const char *arg2, const char *argv[])
7777         const char *rev_parse_argv[SIZEOF_ARG] = { "git", "rev-parse", arg1, arg2 };
7778         const char **all_argv = NULL;
7780         if (!argv_append_array(&all_argv, rev_parse_argv) ||
7781             !argv_append_array(&all_argv, argv) ||
7782             !io_run_load(all_argv, "\n", read_filter_args, args) == ERR)
7783                 die("Failed to split arguments");
7784         argv_free(all_argv);
7785         free(all_argv);
7788 static void
7789 filter_options(const char *argv[])
7791         filter_rev_parse(&opt_file_argv, "--no-revs", "--no-flags", argv);
7792         filter_rev_parse(&opt_diff_argv, "--no-revs", "--flags", argv);
7793         filter_rev_parse(&opt_rev_argv, "--symbolic", "--revs-only", argv);
7796 static enum request
7797 parse_options(int argc, const char *argv[])
7799         enum request request = REQ_VIEW_MAIN;
7800         const char *subcommand;
7801         bool seen_dashdash = FALSE;
7802         const char **filter_argv = NULL;
7803         int i;
7805         if (!isatty(STDIN_FILENO))
7806                 return REQ_VIEW_PAGER;
7808         if (argc <= 1)
7809                 return REQ_VIEW_MAIN;
7811         subcommand = argv[1];
7812         if (!strcmp(subcommand, "status")) {
7813                 if (argc > 2)
7814                         warn("ignoring arguments after `%s'", subcommand);
7815                 return REQ_VIEW_STATUS;
7817         } else if (!strcmp(subcommand, "blame")) {
7818                 if (argc <= 2 || argc > 4)
7819                         die("invalid number of options to blame\n\n%s", usage);
7821                 i = 2;
7822                 if (argc == 4) {
7823                         string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7824                         i++;
7825                 }
7827                 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7828                 return REQ_VIEW_BLAME;
7830         } else if (!strcmp(subcommand, "show")) {
7831                 request = REQ_VIEW_DIFF;
7833         } else {
7834                 subcommand = NULL;
7835         }
7837         for (i = 1 + !!subcommand; i < argc; i++) {
7838                 const char *opt = argv[i];
7840                 if (seen_dashdash) {
7841                         argv_append(&opt_file_argv, opt);
7842                         continue;
7844                 } else if (!strcmp(opt, "--")) {
7845                         seen_dashdash = TRUE;
7846                         continue;
7848                 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7849                         printf("tig version %s\n", TIG_VERSION);
7850                         quit(0);
7852                 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7853                         printf("%s\n", usage);
7854                         quit(0);
7856                 } else if (!strcmp(opt, "--all")) {
7857                         argv_append(&opt_rev_argv, opt);
7858                         continue;
7859                 }
7861                 if (!argv_append(&filter_argv, opt))
7862                         die("command too long");
7863         }
7865         if (filter_argv)
7866                 filter_options(filter_argv);
7868         return request;
7871 int
7872 main(int argc, const char *argv[])
7874         const char *codeset = "UTF-8";
7875         enum request request = parse_options(argc, argv);
7876         struct view *view;
7877         size_t i;
7879         signal(SIGINT, quit);
7880         signal(SIGPIPE, SIG_IGN);
7882         if (setlocale(LC_ALL, "")) {
7883                 codeset = nl_langinfo(CODESET);
7884         }
7886         if (load_repo_info() == ERR)
7887                 die("Failed to load repo info.");
7889         if (load_options() == ERR)
7890                 die("Failed to load user config.");
7892         if (load_git_config() == ERR)
7893                 die("Failed to load repo config.");
7895         /* Require a git repository unless when running in pager mode. */
7896         if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7897                 die("Not a git repository");
7899         if (*opt_encoding && strcmp(codeset, "UTF-8")) {
7900                 opt_iconv_in = iconv_open("UTF-8", opt_encoding);
7901                 if (opt_iconv_in == ICONV_NONE)
7902                         die("Failed to initialize character set conversion");
7903         }
7905         if (codeset && strcmp(codeset, "UTF-8")) {
7906                 opt_iconv_out = iconv_open(codeset, "UTF-8");
7907                 if (opt_iconv_out == ICONV_NONE)
7908                         die("Failed to initialize character set conversion");
7909         }
7911         if (load_refs() == ERR)
7912                 die("Failed to load refs.");
7914         foreach_view (view, i) {
7915                 if (getenv(view->cmd_env))
7916                         warn("Use of the %s environment variable is deprecated,"
7917                              " use options or TIG_DIFF_ARGS instead",
7918                              view->cmd_env);
7919                 if (!argv_from_env(view->ops->argv, view->cmd_env))
7920                         die("Too many arguments in the `%s` environment variable",
7921                             view->cmd_env);
7922         }
7924         init_display();
7926         while (view_driver(display[current_view], request)) {
7927                 int key = get_input(0);
7929                 view = display[current_view];
7930                 request = get_keybinding(view->keymap, key);
7932                 /* Some low-level request handling. This keeps access to
7933                  * status_win restricted. */
7934                 switch (request) {
7935                 case REQ_NONE:
7936                         report("Unknown key, press %s for help",
7937                                get_key(view->keymap, REQ_VIEW_HELP));
7938                         break;
7939                 case REQ_PROMPT:
7940                 {
7941                         char *cmd = read_prompt(":");
7943                         if (cmd && isdigit(*cmd)) {
7944                                 int lineno = view->lineno + 1;
7946                                 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7947                                         select_view_line(view, lineno - 1);
7948                                         report("");
7949                                 } else {
7950                                         report("Unable to parse '%s' as a line number", cmd);
7951                                 }
7953                         } else if (cmd) {
7954                                 struct view *next = VIEW(REQ_VIEW_PAGER);
7955                                 const char *argv[SIZEOF_ARG] = { "git" };
7956                                 int argc = 1;
7958                                 /* When running random commands, initially show the
7959                                  * command in the title. However, it maybe later be
7960                                  * overwritten if a commit line is selected. */
7961                                 string_ncopy(next->ref, cmd, strlen(cmd));
7963                                 if (!argv_from_string(argv, &argc, cmd)) {
7964                                         report("Too many arguments");
7965                                 } else if (!prepare_update(next, argv, NULL)) {
7966                                         report("Failed to format command");
7967                                 } else {
7968                                         open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7969                                 }
7970                         }
7972                         request = REQ_NONE;
7973                         break;
7974                 }
7975                 case REQ_SEARCH:
7976                 case REQ_SEARCH_BACK:
7977                 {
7978                         const char *prompt = request == REQ_SEARCH ? "/" : "?";
7979                         char *search = read_prompt(prompt);
7981                         if (search)
7982                                 string_ncopy(opt_search, search, strlen(search));
7983                         else if (*opt_search)
7984                                 request = request == REQ_SEARCH ?
7985                                         REQ_FIND_NEXT :
7986                                         REQ_FIND_PREV;
7987                         else
7988                                 request = REQ_NONE;
7989                         break;
7990                 }
7991                 default:
7992                         break;
7993                 }
7994         }
7996         quit(0);
7998         return 0;