Code

Initialise views and screens with nonzero size.
[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_H
53 #include <ncursesw/ncurses.h>
54 #else
55 #include <ncurses.h>
56 #endif
58 #if __GNUC__ >= 3
59 #define __NORETURN __attribute__((__noreturn__))
60 #else
61 #define __NORETURN
62 #endif
64 static void __NORETURN die(const char *err, ...);
65 static void warn(const char *msg, ...);
66 static void report(const char *msg, ...);
68 #define ABS(x)          ((x) >= 0  ? (x) : -(x))
69 #define MIN(x, y)       ((x) < (y) ? (x) :  (y))
70 #define MAX(x, y)       ((x) > (y) ? (x) :  (y))
72 #define ARRAY_SIZE(x)   (sizeof(x) / sizeof(x[0]))
73 #define STRING_SIZE(x)  (sizeof(x) - 1)
75 #define SIZEOF_STR      1024    /* Default string size. */
76 #define SIZEOF_REF      256     /* Size of symbolic or SHA1 ID. */
77 #define SIZEOF_REV      41      /* Holds a SHA-1 and an ending NUL. */
78 #define SIZEOF_ARG      32      /* Default argument array size. */
80 /* Revision graph */
82 #define REVGRAPH_INIT   'I'
83 #define REVGRAPH_MERGE  'M'
84 #define REVGRAPH_BRANCH '+'
85 #define REVGRAPH_COMMIT '*'
86 #define REVGRAPH_BOUND  '^'
88 #define SIZEOF_REVGRAPH 19      /* Size of revision ancestry graphics. */
90 /* This color name can be used to refer to the default term colors. */
91 #define COLOR_DEFAULT   (-1)
93 #define ICONV_NONE      ((iconv_t) -1)
94 #ifndef ICONV_CONST
95 #define ICONV_CONST     /* nothing */
96 #endif
98 /* The format and size of the date column in the main view. */
99 #define DATE_FORMAT     "%Y-%m-%d %H:%M"
100 #define DATE_COLS       STRING_SIZE("2006-04-29 14:21 ")
101 #define DATE_SHORT_COLS STRING_SIZE("2006-04-29 ")
103 #define ID_COLS         8
104 #define AUTHOR_COLS     19
106 #define MIN_VIEW_HEIGHT 4
108 #define NULL_ID         "0000000000000000000000000000000000000000"
110 #define S_ISGITLINK(mode) (((mode) & S_IFMT) == 0160000)
112 /* Some ASCII-shorthands fitted into the ncurses namespace. */
113 #define KEY_CTL(x)      ((x) & 0x1f) /* KEY_CTL(A) == ^A == \1 */
114 #define KEY_TAB         '\t'
115 #define KEY_RETURN      '\r'
116 #define KEY_ESC         27
119 struct ref {
120         char id[SIZEOF_REV];    /* Commit SHA1 ID */
121         unsigned int head:1;    /* Is it the current HEAD? */
122         unsigned int tag:1;     /* Is it a tag? */
123         unsigned int ltag:1;    /* If so, is the tag local? */
124         unsigned int remote:1;  /* Is it a remote ref? */
125         unsigned int tracked:1; /* Is it the remote for the current HEAD? */
126         char name[1];           /* Ref name; tag or head names are shortened. */
127 };
129 struct ref_list {
130         char id[SIZEOF_REV];    /* Commit SHA1 ID */
131         size_t size;            /* Number of refs. */
132         struct ref **refs;      /* References for this ID. */
133 };
135 static struct ref *get_ref_head();
136 static struct ref_list *get_ref_list(const char *id);
137 static void foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data);
138 static int load_refs(void);
140 enum input_status {
141         INPUT_OK,
142         INPUT_SKIP,
143         INPUT_STOP,
144         INPUT_CANCEL
145 };
147 typedef enum input_status (*input_handler)(void *data, char *buf, int c);
149 static char *prompt_input(const char *prompt, input_handler handler, void *data);
150 static bool prompt_yesno(const char *prompt);
152 struct menu_item {
153         int hotkey;
154         const char *text;
155         void *data;
156 };
158 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected);
160 /*
161  * Allocation helpers ... Entering macro hell to never be seen again.
162  */
164 #define DEFINE_ALLOCATOR(name, type, chunk_size)                                \
165 static type *                                                                   \
166 name(type **mem, size_t size, size_t increase)                                  \
167 {                                                                               \
168         size_t num_chunks = (size + chunk_size - 1) / chunk_size;               \
169         size_t num_chunks_new = (size + increase + chunk_size - 1) / chunk_size;\
170         type *tmp = *mem;                                                       \
171                                                                                 \
172         if (mem == NULL || num_chunks != num_chunks_new) {                      \
173                 tmp = realloc(tmp, num_chunks_new * chunk_size * sizeof(type)); \
174                 if (tmp)                                                        \
175                         *mem = tmp;                                             \
176         }                                                                       \
177                                                                                 \
178         return tmp;                                                             \
181 /*
182  * String helpers
183  */
185 static inline void
186 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
188         if (srclen > dstlen - 1)
189                 srclen = dstlen - 1;
191         strncpy(dst, src, srclen);
192         dst[srclen] = 0;
195 /* Shorthands for safely copying into a fixed buffer. */
197 #define string_copy(dst, src) \
198         string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
200 #define string_ncopy(dst, src, srclen) \
201         string_ncopy_do(dst, sizeof(dst), src, srclen)
203 #define string_copy_rev(dst, src) \
204         string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
206 #define string_add(dst, from, src) \
207         string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
209 static size_t
210 string_expand(char *dst, size_t dstlen, const char *src, int tabsize)
212         size_t size, pos;
214         for (size = pos = 0; size < dstlen - 1 && src[pos]; pos++) {
215                 if (src[pos] == '\t') {
216                         size_t expanded = tabsize - (size % tabsize);
218                         if (expanded + size >= dstlen - 1)
219                                 expanded = dstlen - size - 1;
220                         memcpy(dst + size, "        ", expanded);
221                         size += expanded;
222                 } else {
223                         dst[size++] = src[pos];
224                 }
225         }
227         dst[size] = 0;
228         return pos;
231 static char *
232 chomp_string(char *name)
234         int namelen;
236         while (isspace(*name))
237                 name++;
239         namelen = strlen(name) - 1;
240         while (namelen > 0 && isspace(name[namelen]))
241                 name[namelen--] = 0;
243         return name;
246 static bool
247 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
249         va_list args;
250         size_t pos = bufpos ? *bufpos : 0;
252         va_start(args, fmt);
253         pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
254         va_end(args);
256         if (bufpos)
257                 *bufpos = pos;
259         return pos >= bufsize ? FALSE : TRUE;
262 #define string_format(buf, fmt, args...) \
263         string_nformat(buf, sizeof(buf), NULL, fmt, args)
265 #define string_format_from(buf, from, fmt, args...) \
266         string_nformat(buf, sizeof(buf), from, fmt, args)
268 static int
269 string_enum_compare(const char *str1, const char *str2, int len)
271         size_t i;
273 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
275         /* Diff-Header == DIFF_HEADER */
276         for (i = 0; i < len; i++) {
277                 if (toupper(str1[i]) == toupper(str2[i]))
278                         continue;
280                 if (string_enum_sep(str1[i]) &&
281                     string_enum_sep(str2[i]))
282                         continue;
284                 return str1[i] - str2[i];
285         }
287         return 0;
290 #define enum_equals(entry, str, len) \
291         ((entry).namelen == (len) && !string_enum_compare((entry).name, str, len))
293 struct enum_map {
294         const char *name;
295         int namelen;
296         int value;
297 };
299 #define ENUM_MAP(name, value) { name, STRING_SIZE(name), value }
301 static char *
302 enum_map_name(const char *name, size_t namelen)
304         static char buf[SIZEOF_STR];
305         int bufpos;
307         for (bufpos = 0; bufpos <= namelen; bufpos++) {
308                 buf[bufpos] = tolower(name[bufpos]);
309                 if (buf[bufpos] == '_')
310                         buf[bufpos] = '-';
311         }
313         buf[bufpos] = 0;
314         return buf;
317 #define enum_name(entry) enum_map_name((entry).name, (entry).namelen)
319 static bool
320 map_enum_do(const struct enum_map *map, size_t map_size, int *value, const char *name)
322         size_t namelen = strlen(name);
323         int i;
325         for (i = 0; i < map_size; i++)
326                 if (enum_equals(map[i], name, namelen)) {
327                         *value = map[i].value;
328                         return TRUE;
329                 }
331         return FALSE;
334 #define map_enum(attr, map, name) \
335         map_enum_do(map, ARRAY_SIZE(map), attr, name)
337 #define prefixcmp(str1, str2) \
338         strncmp(str1, str2, STRING_SIZE(str2))
340 static inline int
341 suffixcmp(const char *str, int slen, const char *suffix)
343         size_t len = slen >= 0 ? slen : strlen(str);
344         size_t suffixlen = strlen(suffix);
346         return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
350 /*
351  * Unicode / UTF-8 handling
352  *
353  * NOTE: Much of the following code for dealing with Unicode is derived from
354  * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
355  * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
356  */
358 static inline int
359 unicode_width(unsigned long c, int tab_size)
361         if (c >= 0x1100 &&
362            (c <= 0x115f                         /* Hangul Jamo */
363             || c == 0x2329
364             || c == 0x232a
365             || (c >= 0x2e80  && c <= 0xa4cf && c != 0x303f)
366                                                 /* CJK ... Yi */
367             || (c >= 0xac00  && c <= 0xd7a3)    /* Hangul Syllables */
368             || (c >= 0xf900  && c <= 0xfaff)    /* CJK Compatibility Ideographs */
369             || (c >= 0xfe30  && c <= 0xfe6f)    /* CJK Compatibility Forms */
370             || (c >= 0xff00  && c <= 0xff60)    /* Fullwidth Forms */
371             || (c >= 0xffe0  && c <= 0xffe6)
372             || (c >= 0x20000 && c <= 0x2fffd)
373             || (c >= 0x30000 && c <= 0x3fffd)))
374                 return 2;
376         if (c == '\t')
377                 return tab_size;
379         return 1;
382 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
383  * Illegal bytes are set one. */
384 static const unsigned char utf8_bytes[256] = {
385         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,
386         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,
387         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,
388         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,
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         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,
392         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,
393 };
395 static inline unsigned char
396 utf8_char_length(const char *string, const char *end)
398         int c = *(unsigned char *) string;
400         return utf8_bytes[c];
403 /* Decode UTF-8 multi-byte representation into a Unicode character. */
404 static inline unsigned long
405 utf8_to_unicode(const char *string, size_t length)
407         unsigned long unicode;
409         switch (length) {
410         case 1:
411                 unicode  =   string[0];
412                 break;
413         case 2:
414                 unicode  =  (string[0] & 0x1f) << 6;
415                 unicode +=  (string[1] & 0x3f);
416                 break;
417         case 3:
418                 unicode  =  (string[0] & 0x0f) << 12;
419                 unicode += ((string[1] & 0x3f) << 6);
420                 unicode +=  (string[2] & 0x3f);
421                 break;
422         case 4:
423                 unicode  =  (string[0] & 0x0f) << 18;
424                 unicode += ((string[1] & 0x3f) << 12);
425                 unicode += ((string[2] & 0x3f) << 6);
426                 unicode +=  (string[3] & 0x3f);
427                 break;
428         case 5:
429                 unicode  =  (string[0] & 0x0f) << 24;
430                 unicode += ((string[1] & 0x3f) << 18);
431                 unicode += ((string[2] & 0x3f) << 12);
432                 unicode += ((string[3] & 0x3f) << 6);
433                 unicode +=  (string[4] & 0x3f);
434                 break;
435         case 6:
436                 unicode  =  (string[0] & 0x01) << 30;
437                 unicode += ((string[1] & 0x3f) << 24);
438                 unicode += ((string[2] & 0x3f) << 18);
439                 unicode += ((string[3] & 0x3f) << 12);
440                 unicode += ((string[4] & 0x3f) << 6);
441                 unicode +=  (string[5] & 0x3f);
442                 break;
443         default:
444                 return 0;
445         }
447         /* Invalid characters could return the special 0xfffd value but NUL
448          * should be just as good. */
449         return unicode > 0xffff ? 0 : unicode;
452 /* Calculates how much of string can be shown within the given maximum width
453  * and sets trimmed parameter to non-zero value if all of string could not be
454  * shown. If the reserve flag is TRUE, it will reserve at least one
455  * trailing character, which can be useful when drawing a delimiter.
456  *
457  * Returns the number of bytes to output from string to satisfy max_width. */
458 static size_t
459 utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve, int tab_size)
461         const char *string = *start;
462         const char *end = strchr(string, '\0');
463         unsigned char last_bytes = 0;
464         size_t last_ucwidth = 0;
466         *width = 0;
467         *trimmed = 0;
469         while (string < end) {
470                 unsigned char bytes = utf8_char_length(string, end);
471                 size_t ucwidth;
472                 unsigned long unicode;
474                 if (string + bytes > end)
475                         break;
477                 /* Change representation to figure out whether
478                  * it is a single- or double-width character. */
480                 unicode = utf8_to_unicode(string, bytes);
481                 /* FIXME: Graceful handling of invalid Unicode character. */
482                 if (!unicode)
483                         break;
485                 ucwidth = unicode_width(unicode, tab_size);
486                 if (skip > 0) {
487                         skip -= ucwidth <= skip ? ucwidth : skip;
488                         *start += bytes;
489                 }
490                 *width  += ucwidth;
491                 if (*width > max_width) {
492                         *trimmed = 1;
493                         *width -= ucwidth;
494                         if (reserve && *width == max_width) {
495                                 string -= last_bytes;
496                                 *width -= last_ucwidth;
497                         }
498                         break;
499                 }
501                 string  += bytes;
502                 last_bytes = ucwidth ? bytes : 0;
503                 last_ucwidth = ucwidth;
504         }
506         return string - *start;
510 #define DATE_INFO \
511         DATE_(NO), \
512         DATE_(DEFAULT), \
513         DATE_(LOCAL), \
514         DATE_(RELATIVE), \
515         DATE_(SHORT)
517 enum date {
518 #define DATE_(name) DATE_##name
519         DATE_INFO
520 #undef  DATE_
521 };
523 static const struct enum_map date_map[] = {
524 #define DATE_(name) ENUM_MAP(#name, DATE_##name)
525         DATE_INFO
526 #undef  DATE_
527 };
529 struct time {
530         time_t sec;
531         int tz;
532 };
534 static inline int timecmp(const struct time *t1, const struct time *t2)
536         return t1->sec - t2->sec;
539 static const char *
540 mkdate(const struct time *time, enum date date)
542         static char buf[DATE_COLS + 1];
543         static const struct enum_map reldate[] = {
544                 { "second", 1,                  60 * 2 },
545                 { "minute", 60,                 60 * 60 * 2 },
546                 { "hour",   60 * 60,            60 * 60 * 24 * 2 },
547                 { "day",    60 * 60 * 24,       60 * 60 * 24 * 7 * 2 },
548                 { "week",   60 * 60 * 24 * 7,   60 * 60 * 24 * 7 * 5 },
549                 { "month",  60 * 60 * 24 * 30,  60 * 60 * 24 * 30 * 12 },
550         };
551         struct tm tm;
553         if (!date || !time || !time->sec)
554                 return "";
556         if (date == DATE_RELATIVE) {
557                 struct timeval now;
558                 time_t date = time->sec + time->tz;
559                 time_t seconds;
560                 int i;
562                 gettimeofday(&now, NULL);
563                 seconds = now.tv_sec < date ? date - now.tv_sec : now.tv_sec - date;
564                 for (i = 0; i < ARRAY_SIZE(reldate); i++) {
565                         if (seconds >= reldate[i].value)
566                                 continue;
568                         seconds /= reldate[i].namelen;
569                         if (!string_format(buf, "%ld %s%s %s",
570                                            seconds, reldate[i].name,
571                                            seconds > 1 ? "s" : "",
572                                            now.tv_sec >= date ? "ago" : "ahead"))
573                                 break;
574                         return buf;
575                 }
576         }
578         if (date == DATE_LOCAL) {
579                 time_t date = time->sec + time->tz;
580                 localtime_r(&date, &tm);
581         }
582         else {
583                 gmtime_r(&time->sec, &tm);
584         }
585         return strftime(buf, sizeof(buf), DATE_FORMAT, &tm) ? buf : NULL;
589 #define AUTHOR_VALUES \
590         AUTHOR_(NO), \
591         AUTHOR_(FULL), \
592         AUTHOR_(ABBREVIATED)
594 enum author {
595 #define AUTHOR_(name) AUTHOR_##name
596         AUTHOR_VALUES,
597 #undef  AUTHOR_
598         AUTHOR_DEFAULT = AUTHOR_FULL
599 };
601 static const struct enum_map author_map[] = {
602 #define AUTHOR_(name) ENUM_MAP(#name, AUTHOR_##name)
603         AUTHOR_VALUES
604 #undef  AUTHOR_
605 };
607 static const char *
608 get_author_initials(const char *author)
610         static char initials[AUTHOR_COLS * 6 + 1];
611         size_t pos = 0;
612         const char *end = strchr(author, '\0');
614 #define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@' || (c) == '-')
616         memset(initials, 0, sizeof(initials));
617         while (author < end) {
618                 unsigned char bytes;
619                 size_t i;
621                 while (is_initial_sep(*author))
622                         author++;
624                 bytes = utf8_char_length(author, end);
625                 if (bytes < sizeof(initials) - 1 - pos) {
626                         while (bytes--) {
627                                 initials[pos++] = *author++;
628                         }
629                 }
631                 for (i = pos; author < end && !is_initial_sep(*author); author++) {
632                         if (i < sizeof(initials) - 1)
633                                 initials[i++] = *author;
634                 }
636                 initials[i++] = 0;
637         }
639         return initials;
643 static bool
644 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
646         int valuelen;
648         while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
649                 bool advance = cmd[valuelen] != 0;
651                 cmd[valuelen] = 0;
652                 argv[(*argc)++] = chomp_string(cmd);
653                 cmd = chomp_string(cmd + valuelen + advance);
654         }
656         if (*argc < SIZEOF_ARG)
657                 argv[*argc] = NULL;
658         return *argc < SIZEOF_ARG;
661 static bool
662 argv_from_env(const char **argv, const char *name)
664         char *env = argv ? getenv(name) : NULL;
665         int argc = 0;
667         if (env && *env)
668                 env = strdup(env);
669         return !env || argv_from_string(argv, &argc, env);
672 static void
673 argv_free(const char *argv[])
675         int argc;
677         if (!argv)
678                 return;
679         for (argc = 0; argv[argc]; argc++)
680                 free((void *) argv[argc]);
681         argv[0] = NULL;
684 static size_t
685 argv_size(const char **argv)
687         int argc = 0;
689         while (argv && argv[argc])
690                 argc++;
692         return argc;
695 DEFINE_ALLOCATOR(argv_realloc, const char *, SIZEOF_ARG)
697 static bool
698 argv_append(const char ***argv, const char *arg)
700         size_t argc = argv_size(*argv);
702         if (!argv_realloc(argv, argc, 2))
703                 return FALSE;
705         (*argv)[argc++] = strdup(arg);
706         (*argv)[argc] = NULL;
707         return TRUE;
710 static bool
711 argv_append_array(const char ***dst_argv, const char *src_argv[])
713         int i;
715         for (i = 0; src_argv && src_argv[i]; i++)
716                 if (!argv_append(dst_argv, src_argv[i]))
717                         return FALSE;
718         return TRUE;
721 static bool
722 argv_copy(const char ***dst, const char *src[])
724         int argc;
726         for (argc = 0; src[argc]; argc++)
727                 if (!argv_append(dst, src[argc]))
728                         return FALSE;
729         return TRUE;
733 /*
734  * Executing external commands.
735  */
737 enum io_type {
738         IO_FD,                  /* File descriptor based IO. */
739         IO_BG,                  /* Execute command in the background. */
740         IO_FG,                  /* Execute command with same std{in,out,err}. */
741         IO_RD,                  /* Read only fork+exec IO. */
742         IO_WR,                  /* Write only fork+exec IO. */
743         IO_AP,                  /* Append fork+exec output to file. */
744 };
746 struct io {
747         int pipe;               /* Pipe end for reading or writing. */
748         pid_t pid;              /* PID of spawned process. */
749         int error;              /* Error status. */
750         char *buf;              /* Read buffer. */
751         size_t bufalloc;        /* Allocated buffer size. */
752         size_t bufsize;         /* Buffer content size. */
753         char *bufpos;           /* Current buffer position. */
754         unsigned int eof:1;     /* Has end of file been reached. */
755 };
757 static void
758 io_init(struct io *io)
760         memset(io, 0, sizeof(*io));
761         io->pipe = -1;
764 static bool
765 io_open(struct io *io, const char *fmt, ...)
767         char name[SIZEOF_STR] = "";
768         bool fits;
769         va_list args;
771         io_init(io);
773         va_start(args, fmt);
774         fits = vsnprintf(name, sizeof(name), fmt, args) < sizeof(name);
775         va_end(args);
777         if (!fits) {
778                 io->error = ENAMETOOLONG;
779                 return FALSE;
780         }
781         io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
782         if (io->pipe == -1)
783                 io->error = errno;
784         return io->pipe != -1;
787 static bool
788 io_kill(struct io *io)
790         return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
793 static bool
794 io_done(struct io *io)
796         pid_t pid = io->pid;
798         if (io->pipe != -1)
799                 close(io->pipe);
800         free(io->buf);
801         io_init(io);
803         while (pid > 0) {
804                 int status;
805                 pid_t waiting = waitpid(pid, &status, 0);
807                 if (waiting < 0) {
808                         if (errno == EINTR)
809                                 continue;
810                         io->error = errno;
811                         return FALSE;
812                 }
814                 return waiting == pid &&
815                        !WIFSIGNALED(status) &&
816                        WIFEXITED(status) &&
817                        !WEXITSTATUS(status);
818         }
820         return TRUE;
823 static bool
824 io_run(struct io *io, enum io_type type, const char *dir, const char *argv[], ...)
826         int pipefds[2] = { -1, -1 };
827         va_list args;
829         io_init(io);
831         if ((type == IO_RD || type == IO_WR) && pipe(pipefds) < 0) {
832                 io->error = errno;
833                 return FALSE;
834         } else if (type == IO_AP) {
835                 va_start(args, argv);
836                 pipefds[1] = va_arg(args, int);
837                 va_end(args);
838         }
840         if ((io->pid = fork())) {
841                 if (io->pid == -1)
842                         io->error = errno;
843                 if (pipefds[!(type == IO_WR)] != -1)
844                         close(pipefds[!(type == IO_WR)]);
845                 if (io->pid != -1) {
846                         io->pipe = pipefds[!!(type == IO_WR)];
847                         return TRUE;
848                 }
850         } else {
851                 if (type != IO_FG) {
852                         int devnull = open("/dev/null", O_RDWR);
853                         int readfd  = type == IO_WR ? pipefds[0] : devnull;
854                         int writefd = (type == IO_RD || type == IO_AP)
855                                                         ? pipefds[1] : devnull;
857                         dup2(readfd,  STDIN_FILENO);
858                         dup2(writefd, STDOUT_FILENO);
859                         dup2(devnull, STDERR_FILENO);
861                         close(devnull);
862                         if (pipefds[0] != -1)
863                                 close(pipefds[0]);
864                         if (pipefds[1] != -1)
865                                 close(pipefds[1]);
866                 }
868                 if (dir && *dir && chdir(dir) == -1)
869                         exit(errno);
871                 execvp(argv[0], (char *const*) argv);
872                 exit(errno);
873         }
875         if (pipefds[!!(type == IO_WR)] != -1)
876                 close(pipefds[!!(type == IO_WR)]);
877         return FALSE;
880 static bool
881 io_complete(enum io_type type, const char **argv, const char *dir, int fd)
883         struct io io;
885         return io_run(&io, type, dir, argv, fd) && io_done(&io);
888 static bool
889 io_run_bg(const char **argv)
891         return io_complete(IO_BG, argv, NULL, -1);
894 static bool
895 io_run_fg(const char **argv, const char *dir)
897         return io_complete(IO_FG, argv, dir, -1);
900 static bool
901 io_run_append(const char **argv, int fd)
903         return io_complete(IO_AP, argv, NULL, fd);
906 static bool
907 io_eof(struct io *io)
909         return io->eof;
912 static int
913 io_error(struct io *io)
915         return io->error;
918 static char *
919 io_strerror(struct io *io)
921         return strerror(io->error);
924 static bool
925 io_can_read(struct io *io)
927         struct timeval tv = { 0, 500 };
928         fd_set fds;
930         FD_ZERO(&fds);
931         FD_SET(io->pipe, &fds);
933         return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
936 static ssize_t
937 io_read(struct io *io, void *buf, size_t bufsize)
939         do {
940                 ssize_t readsize = read(io->pipe, buf, bufsize);
942                 if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
943                         continue;
944                 else if (readsize == -1)
945                         io->error = errno;
946                 else if (readsize == 0)
947                         io->eof = 1;
948                 return readsize;
949         } while (1);
952 DEFINE_ALLOCATOR(io_realloc_buf, char, BUFSIZ)
954 static char *
955 io_get(struct io *io, int c, bool can_read)
957         char *eol;
958         ssize_t readsize;
960         while (TRUE) {
961                 if (io->bufsize > 0) {
962                         eol = memchr(io->bufpos, c, io->bufsize);
963                         if (eol) {
964                                 char *line = io->bufpos;
966                                 *eol = 0;
967                                 io->bufpos = eol + 1;
968                                 io->bufsize -= io->bufpos - line;
969                                 return line;
970                         }
971                 }
973                 if (io_eof(io)) {
974                         if (io->bufsize) {
975                                 io->bufpos[io->bufsize] = 0;
976                                 io->bufsize = 0;
977                                 return io->bufpos;
978                         }
979                         return NULL;
980                 }
982                 if (!can_read)
983                         return NULL;
985                 if (io->bufsize > 0 && io->bufpos > io->buf)
986                         memmove(io->buf, io->bufpos, io->bufsize);
988                 if (io->bufalloc == io->bufsize) {
989                         if (!io_realloc_buf(&io->buf, io->bufalloc, BUFSIZ))
990                                 return NULL;
991                         io->bufalloc += BUFSIZ;
992                 }
994                 io->bufpos = io->buf;
995                 readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
996                 if (io_error(io))
997                         return NULL;
998                 io->bufsize += readsize;
999         }
1002 static bool
1003 io_write(struct io *io, const void *buf, size_t bufsize)
1005         size_t written = 0;
1007         while (!io_error(io) && written < bufsize) {
1008                 ssize_t size;
1010                 size = write(io->pipe, buf + written, bufsize - written);
1011                 if (size < 0 && (errno == EAGAIN || errno == EINTR))
1012                         continue;
1013                 else if (size == -1)
1014                         io->error = errno;
1015                 else
1016                         written += size;
1017         }
1019         return written == bufsize;
1022 static bool
1023 io_read_buf(struct io *io, char buf[], size_t bufsize)
1025         char *result = io_get(io, '\n', TRUE);
1027         if (result) {
1028                 result = chomp_string(result);
1029                 string_ncopy_do(buf, bufsize, result, strlen(result));
1030         }
1032         return io_done(io) && result;
1035 static bool
1036 io_run_buf(const char **argv, char buf[], size_t bufsize)
1038         struct io io;
1040         return io_run(&io, IO_RD, NULL, argv) && io_read_buf(&io, buf, bufsize);
1043 typedef int (*io_read_fn)(char *, size_t, char *, size_t, void *data);
1045 static int
1046 io_load(struct io *io, const char *separators,
1047         io_read_fn read_property, void *data)
1049         char *name;
1050         int state = OK;
1052         while (state == OK && (name = io_get(io, '\n', TRUE))) {
1053                 char *value;
1054                 size_t namelen;
1055                 size_t valuelen;
1057                 name = chomp_string(name);
1058                 namelen = strcspn(name, separators);
1060                 if (name[namelen]) {
1061                         name[namelen] = 0;
1062                         value = chomp_string(name + namelen + 1);
1063                         valuelen = strlen(value);
1065                 } else {
1066                         value = "";
1067                         valuelen = 0;
1068                 }
1070                 state = read_property(name, namelen, value, valuelen, data);
1071         }
1073         if (state != ERR && io_error(io))
1074                 state = ERR;
1075         io_done(io);
1077         return state;
1080 static int
1081 io_run_load(const char **argv, const char *separators,
1082             io_read_fn read_property, void *data)
1084         struct io io;
1086         if (!io_run(&io, IO_RD, NULL, argv))
1087                 return ERR;
1088         return io_load(&io, separators, read_property, data);
1092 /*
1093  * User requests
1094  */
1096 #define REQ_INFO \
1097         /* XXX: Keep the view request first and in sync with views[]. */ \
1098         REQ_GROUP("View switching") \
1099         REQ_(VIEW_MAIN,         "Show main view"), \
1100         REQ_(VIEW_DIFF,         "Show diff view"), \
1101         REQ_(VIEW_LOG,          "Show log view"), \
1102         REQ_(VIEW_TREE,         "Show tree view"), \
1103         REQ_(VIEW_BLOB,         "Show blob view"), \
1104         REQ_(VIEW_BLAME,        "Show blame view"), \
1105         REQ_(VIEW_BRANCH,       "Show branch view"), \
1106         REQ_(VIEW_HELP,         "Show help page"), \
1107         REQ_(VIEW_PAGER,        "Show pager view"), \
1108         REQ_(VIEW_STATUS,       "Show status view"), \
1109         REQ_(VIEW_STAGE,        "Show stage view"), \
1110         \
1111         REQ_GROUP("View manipulation") \
1112         REQ_(ENTER,             "Enter current line and scroll"), \
1113         REQ_(NEXT,              "Move to next"), \
1114         REQ_(PREVIOUS,          "Move to previous"), \
1115         REQ_(PARENT,            "Move to parent"), \
1116         REQ_(VIEW_NEXT,         "Move focus to next view"), \
1117         REQ_(REFRESH,           "Reload and refresh"), \
1118         REQ_(MAXIMIZE,          "Maximize the current view"), \
1119         REQ_(VIEW_CLOSE,        "Close the current view"), \
1120         REQ_(QUIT,              "Close all views and quit"), \
1121         \
1122         REQ_GROUP("View specific requests") \
1123         REQ_(STATUS_UPDATE,     "Update file status"), \
1124         REQ_(STATUS_REVERT,     "Revert file changes"), \
1125         REQ_(STATUS_MERGE,      "Merge file using external tool"), \
1126         REQ_(STAGE_NEXT,        "Find next chunk to stage"), \
1127         \
1128         REQ_GROUP("Cursor navigation") \
1129         REQ_(MOVE_UP,           "Move cursor one line up"), \
1130         REQ_(MOVE_DOWN,         "Move cursor one line down"), \
1131         REQ_(MOVE_PAGE_DOWN,    "Move cursor one page down"), \
1132         REQ_(MOVE_PAGE_UP,      "Move cursor one page up"), \
1133         REQ_(MOVE_FIRST_LINE,   "Move cursor to first line"), \
1134         REQ_(MOVE_LAST_LINE,    "Move cursor to last line"), \
1135         \
1136         REQ_GROUP("Scrolling") \
1137         REQ_(SCROLL_FIRST_COL,  "Scroll to the first line columns"), \
1138         REQ_(SCROLL_LEFT,       "Scroll two columns left"), \
1139         REQ_(SCROLL_RIGHT,      "Scroll two columns right"), \
1140         REQ_(SCROLL_LINE_UP,    "Scroll one line up"), \
1141         REQ_(SCROLL_LINE_DOWN,  "Scroll one line down"), \
1142         REQ_(SCROLL_PAGE_UP,    "Scroll one page up"), \
1143         REQ_(SCROLL_PAGE_DOWN,  "Scroll one page down"), \
1144         \
1145         REQ_GROUP("Searching") \
1146         REQ_(SEARCH,            "Search the view"), \
1147         REQ_(SEARCH_BACK,       "Search backwards in the view"), \
1148         REQ_(FIND_NEXT,         "Find next search match"), \
1149         REQ_(FIND_PREV,         "Find previous search match"), \
1150         \
1151         REQ_GROUP("Option manipulation") \
1152         REQ_(OPTIONS,           "Open option menu"), \
1153         REQ_(TOGGLE_LINENO,     "Toggle line numbers"), \
1154         REQ_(TOGGLE_DATE,       "Toggle date display"), \
1155         REQ_(TOGGLE_AUTHOR,     "Toggle author display"), \
1156         REQ_(TOGGLE_REV_GRAPH,  "Toggle revision graph visualization"), \
1157         REQ_(TOGGLE_REFS,       "Toggle reference display (tags/branches)"), \
1158         REQ_(TOGGLE_SORT_ORDER, "Toggle ascending/descending sort order"), \
1159         REQ_(TOGGLE_SORT_FIELD, "Toggle field to sort by"), \
1160         \
1161         REQ_GROUP("Misc") \
1162         REQ_(PROMPT,            "Bring up the prompt"), \
1163         REQ_(SCREEN_REDRAW,     "Redraw the screen"), \
1164         REQ_(SHOW_VERSION,      "Show version information"), \
1165         REQ_(STOP_LOADING,      "Stop all loading views"), \
1166         REQ_(EDIT,              "Open in editor"), \
1167         REQ_(NONE,              "Do nothing")
1170 /* User action requests. */
1171 enum request {
1172 #define REQ_GROUP(help)
1173 #define REQ_(req, help) REQ_##req
1175         /* Offset all requests to avoid conflicts with ncurses getch values. */
1176         REQ_UNKNOWN = KEY_MAX + 1,
1177         REQ_OFFSET,
1178         REQ_INFO
1180 #undef  REQ_GROUP
1181 #undef  REQ_
1182 };
1184 struct request_info {
1185         enum request request;
1186         const char *name;
1187         int namelen;
1188         const char *help;
1189 };
1191 static const struct request_info req_info[] = {
1192 #define REQ_GROUP(help) { 0, NULL, 0, (help) },
1193 #define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
1194         REQ_INFO
1195 #undef  REQ_GROUP
1196 #undef  REQ_
1197 };
1199 static enum request
1200 get_request(const char *name)
1202         int namelen = strlen(name);
1203         int i;
1205         for (i = 0; i < ARRAY_SIZE(req_info); i++)
1206                 if (enum_equals(req_info[i], name, namelen))
1207                         return req_info[i].request;
1209         return REQ_UNKNOWN;
1213 /*
1214  * Options
1215  */
1217 /* Option and state variables. */
1218 static enum date opt_date               = DATE_DEFAULT;
1219 static enum author opt_author           = AUTHOR_DEFAULT;
1220 static bool opt_line_number             = FALSE;
1221 static bool opt_line_graphics           = TRUE;
1222 static bool opt_rev_graph               = FALSE;
1223 static bool opt_show_refs               = TRUE;
1224 static bool opt_untracked_dirs_content  = TRUE;
1225 static int opt_num_interval             = 5;
1226 static double opt_hscroll               = 0.50;
1227 static double opt_scale_split_view      = 2.0 / 3.0;
1228 static int opt_tab_size                 = 8;
1229 static int opt_author_cols              = AUTHOR_COLS;
1230 static char opt_path[SIZEOF_STR]        = "";
1231 static char opt_file[SIZEOF_STR]        = "";
1232 static char opt_ref[SIZEOF_REF]         = "";
1233 static char opt_head[SIZEOF_REF]        = "";
1234 static char opt_remote[SIZEOF_REF]      = "";
1235 static char opt_encoding[20]            = "UTF-8";
1236 static iconv_t opt_iconv_in             = ICONV_NONE;
1237 static iconv_t opt_iconv_out            = ICONV_NONE;
1238 static char opt_search[SIZEOF_STR]      = "";
1239 static char opt_cdup[SIZEOF_STR]        = "";
1240 static char opt_prefix[SIZEOF_STR]      = "";
1241 static char opt_git_dir[SIZEOF_STR]     = "";
1242 static signed char opt_is_inside_work_tree      = -1; /* set to TRUE or FALSE */
1243 static char opt_editor[SIZEOF_STR]      = "";
1244 static FILE *opt_tty                    = NULL;
1245 static const char **opt_diff_argv       = NULL;
1246 static const char **opt_rev_argv        = NULL;
1247 static const char **opt_file_argv       = NULL;
1249 #define is_initial_commit()     (!get_ref_head())
1250 #define is_head_commit(rev)     (!strcmp((rev), "HEAD") || (get_ref_head() && !strcmp(rev, get_ref_head()->id)))
1253 /*
1254  * Line-oriented content detection.
1255  */
1257 #define LINE_INFO \
1258 LINE(DIFF_HEADER,  "diff --git ",       COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1259 LINE(DIFF_CHUNK,   "@@",                COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1260 LINE(DIFF_ADD,     "+",                 COLOR_GREEN,    COLOR_DEFAULT,  0), \
1261 LINE(DIFF_DEL,     "-",                 COLOR_RED,      COLOR_DEFAULT,  0), \
1262 LINE(DIFF_INDEX,        "index ",         COLOR_BLUE,   COLOR_DEFAULT,  0), \
1263 LINE(DIFF_OLDMODE,      "old file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1264 LINE(DIFF_NEWMODE,      "new file mode ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1265 LINE(DIFF_COPY_FROM,    "copy from",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
1266 LINE(DIFF_COPY_TO,      "copy to",        COLOR_YELLOW, COLOR_DEFAULT,  0), \
1267 LINE(DIFF_RENAME_FROM,  "rename from",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
1268 LINE(DIFF_RENAME_TO,    "rename to",      COLOR_YELLOW, COLOR_DEFAULT,  0), \
1269 LINE(DIFF_SIMILARITY,   "similarity ",    COLOR_YELLOW, COLOR_DEFAULT,  0), \
1270 LINE(DIFF_DISSIMILARITY,"dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT,  0), \
1271 LINE(DIFF_TREE,         "diff-tree ",     COLOR_BLUE,   COLOR_DEFAULT,  0), \
1272 LINE(PP_AUTHOR,    "Author: ",          COLOR_CYAN,     COLOR_DEFAULT,  0), \
1273 LINE(PP_COMMIT,    "Commit: ",          COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1274 LINE(PP_MERGE,     "Merge: ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
1275 LINE(PP_DATE,      "Date:   ",          COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1276 LINE(PP_ADATE,     "AuthorDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1277 LINE(PP_CDATE,     "CommitDate: ",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1278 LINE(PP_REFS,      "Refs: ",            COLOR_RED,      COLOR_DEFAULT,  0), \
1279 LINE(COMMIT,       "commit ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
1280 LINE(PARENT,       "parent ",           COLOR_BLUE,     COLOR_DEFAULT,  0), \
1281 LINE(TREE,         "tree ",             COLOR_BLUE,     COLOR_DEFAULT,  0), \
1282 LINE(AUTHOR,       "author ",           COLOR_GREEN,    COLOR_DEFAULT,  0), \
1283 LINE(COMMITTER,    "committer ",        COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1284 LINE(SIGNOFF,      "    Signed-off-by", COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1285 LINE(ACKED,        "    Acked-by",      COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1286 LINE(TESTED,       "    Tested-by",     COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1287 LINE(REVIEWED,     "    Reviewed-by",   COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1288 LINE(DEFAULT,      "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
1289 LINE(CURSOR,       "",                  COLOR_WHITE,    COLOR_GREEN,    A_BOLD), \
1290 LINE(STATUS,       "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
1291 LINE(DELIMITER,    "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1292 LINE(DATE,         "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
1293 LINE(MODE,         "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1294 LINE(LINE_NUMBER,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1295 LINE(TITLE_BLUR,   "",                  COLOR_WHITE,    COLOR_BLUE,     0), \
1296 LINE(TITLE_FOCUS,  "",                  COLOR_WHITE,    COLOR_BLUE,     A_BOLD), \
1297 LINE(MAIN_COMMIT,  "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
1298 LINE(MAIN_TAG,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
1299 LINE(MAIN_LOCAL_TAG,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1300 LINE(MAIN_REMOTE,  "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1301 LINE(MAIN_TRACKED, "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_BOLD), \
1302 LINE(MAIN_REF,     "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1303 LINE(MAIN_HEAD,    "",                  COLOR_CYAN,     COLOR_DEFAULT,  A_BOLD), \
1304 LINE(MAIN_REVGRAPH,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1305 LINE(TREE_HEAD,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_BOLD), \
1306 LINE(TREE_DIR,     "",                  COLOR_YELLOW,   COLOR_DEFAULT,  A_NORMAL), \
1307 LINE(TREE_FILE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
1308 LINE(STAT_HEAD,    "",                  COLOR_YELLOW,   COLOR_DEFAULT,  0), \
1309 LINE(STAT_SECTION, "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1310 LINE(STAT_NONE,    "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
1311 LINE(STAT_STAGED,  "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1312 LINE(STAT_UNSTAGED,"",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1313 LINE(STAT_UNTRACKED,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
1314 LINE(HELP_KEYMAP,  "",                  COLOR_CYAN,     COLOR_DEFAULT,  0), \
1315 LINE(HELP_GROUP,   "",                  COLOR_BLUE,     COLOR_DEFAULT,  0), \
1316 LINE(BLAME_ID,     "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0)
1318 enum line_type {
1319 #define LINE(type, line, fg, bg, attr) \
1320         LINE_##type
1321         LINE_INFO,
1322         LINE_NONE
1323 #undef  LINE
1324 };
1326 struct line_info {
1327         const char *name;       /* Option name. */
1328         int namelen;            /* Size of option name. */
1329         const char *line;       /* The start of line to match. */
1330         int linelen;            /* Size of string to match. */
1331         int fg, bg, attr;       /* Color and text attributes for the lines. */
1332 };
1334 static struct line_info line_info[] = {
1335 #define LINE(type, line, fg, bg, attr) \
1336         { #type, STRING_SIZE(#type), (line), STRING_SIZE(line), (fg), (bg), (attr) }
1337         LINE_INFO
1338 #undef  LINE
1339 };
1341 static enum line_type
1342 get_line_type(const char *line)
1344         int linelen = strlen(line);
1345         enum line_type type;
1347         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1348                 /* Case insensitive search matches Signed-off-by lines better. */
1349                 if (linelen >= line_info[type].linelen &&
1350                     !strncasecmp(line_info[type].line, line, line_info[type].linelen))
1351                         return type;
1353         return LINE_DEFAULT;
1356 static inline int
1357 get_line_attr(enum line_type type)
1359         assert(type < ARRAY_SIZE(line_info));
1360         return COLOR_PAIR(type) | line_info[type].attr;
1363 static struct line_info *
1364 get_line_info(const char *name)
1366         size_t namelen = strlen(name);
1367         enum line_type type;
1369         for (type = 0; type < ARRAY_SIZE(line_info); type++)
1370                 if (enum_equals(line_info[type], name, namelen))
1371                         return &line_info[type];
1373         return NULL;
1376 static void
1377 init_colors(void)
1379         int default_bg = line_info[LINE_DEFAULT].bg;
1380         int default_fg = line_info[LINE_DEFAULT].fg;
1381         enum line_type type;
1383         start_color();
1385         if (assume_default_colors(default_fg, default_bg) == ERR) {
1386                 default_bg = COLOR_BLACK;
1387                 default_fg = COLOR_WHITE;
1388         }
1390         for (type = 0; type < ARRAY_SIZE(line_info); type++) {
1391                 struct line_info *info = &line_info[type];
1392                 int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
1393                 int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
1395                 init_pair(type, fg, bg);
1396         }
1399 struct line {
1400         enum line_type type;
1402         /* State flags */
1403         unsigned int selected:1;
1404         unsigned int dirty:1;
1405         unsigned int cleareol:1;
1406         unsigned int other:16;
1408         void *data;             /* User data */
1409 };
1412 /*
1413  * Keys
1414  */
1416 struct keybinding {
1417         int alias;
1418         enum request request;
1419 };
1421 static struct keybinding default_keybindings[] = {
1422         /* View switching */
1423         { 'm',          REQ_VIEW_MAIN },
1424         { 'd',          REQ_VIEW_DIFF },
1425         { 'l',          REQ_VIEW_LOG },
1426         { 't',          REQ_VIEW_TREE },
1427         { 'f',          REQ_VIEW_BLOB },
1428         { 'B',          REQ_VIEW_BLAME },
1429         { 'H',          REQ_VIEW_BRANCH },
1430         { 'p',          REQ_VIEW_PAGER },
1431         { 'h',          REQ_VIEW_HELP },
1432         { 'S',          REQ_VIEW_STATUS },
1433         { 'c',          REQ_VIEW_STAGE },
1435         /* View manipulation */
1436         { 'q',          REQ_VIEW_CLOSE },
1437         { KEY_TAB,      REQ_VIEW_NEXT },
1438         { KEY_RETURN,   REQ_ENTER },
1439         { KEY_UP,       REQ_PREVIOUS },
1440         { KEY_CTL('P'), REQ_PREVIOUS },
1441         { KEY_DOWN,     REQ_NEXT },
1442         { KEY_CTL('N'), REQ_NEXT },
1443         { 'R',          REQ_REFRESH },
1444         { KEY_F(5),     REQ_REFRESH },
1445         { 'O',          REQ_MAXIMIZE },
1447         /* Cursor navigation */
1448         { 'k',          REQ_MOVE_UP },
1449         { 'j',          REQ_MOVE_DOWN },
1450         { KEY_HOME,     REQ_MOVE_FIRST_LINE },
1451         { KEY_END,      REQ_MOVE_LAST_LINE },
1452         { KEY_NPAGE,    REQ_MOVE_PAGE_DOWN },
1453         { KEY_CTL('D'), REQ_MOVE_PAGE_DOWN },
1454         { ' ',          REQ_MOVE_PAGE_DOWN },
1455         { KEY_PPAGE,    REQ_MOVE_PAGE_UP },
1456         { KEY_CTL('U'), REQ_MOVE_PAGE_UP },
1457         { 'b',          REQ_MOVE_PAGE_UP },
1458         { '-',          REQ_MOVE_PAGE_UP },
1460         /* Scrolling */
1461         { '|',          REQ_SCROLL_FIRST_COL },
1462         { KEY_LEFT,     REQ_SCROLL_LEFT },
1463         { KEY_RIGHT,    REQ_SCROLL_RIGHT },
1464         { KEY_IC,       REQ_SCROLL_LINE_UP },
1465         { KEY_CTL('Y'), REQ_SCROLL_LINE_UP },
1466         { KEY_DC,       REQ_SCROLL_LINE_DOWN },
1467         { KEY_CTL('E'), REQ_SCROLL_LINE_DOWN },
1468         { 'w',          REQ_SCROLL_PAGE_UP },
1469         { 's',          REQ_SCROLL_PAGE_DOWN },
1471         /* Searching */
1472         { '/',          REQ_SEARCH },
1473         { '?',          REQ_SEARCH_BACK },
1474         { 'n',          REQ_FIND_NEXT },
1475         { 'N',          REQ_FIND_PREV },
1477         /* Misc */
1478         { 'Q',          REQ_QUIT },
1479         { 'z',          REQ_STOP_LOADING },
1480         { 'v',          REQ_SHOW_VERSION },
1481         { 'r',          REQ_SCREEN_REDRAW },
1482         { KEY_CTL('L'), REQ_SCREEN_REDRAW },
1483         { 'o',          REQ_OPTIONS },
1484         { '.',          REQ_TOGGLE_LINENO },
1485         { 'D',          REQ_TOGGLE_DATE },
1486         { 'A',          REQ_TOGGLE_AUTHOR },
1487         { 'g',          REQ_TOGGLE_REV_GRAPH },
1488         { 'F',          REQ_TOGGLE_REFS },
1489         { 'I',          REQ_TOGGLE_SORT_ORDER },
1490         { 'i',          REQ_TOGGLE_SORT_FIELD },
1491         { ':',          REQ_PROMPT },
1492         { 'u',          REQ_STATUS_UPDATE },
1493         { '!',          REQ_STATUS_REVERT },
1494         { 'M',          REQ_STATUS_MERGE },
1495         { '@',          REQ_STAGE_NEXT },
1496         { ',',          REQ_PARENT },
1497         { 'e',          REQ_EDIT },
1498 };
1500 #define KEYMAP_INFO \
1501         KEYMAP_(GENERIC), \
1502         KEYMAP_(MAIN), \
1503         KEYMAP_(DIFF), \
1504         KEYMAP_(LOG), \
1505         KEYMAP_(TREE), \
1506         KEYMAP_(BLOB), \
1507         KEYMAP_(BLAME), \
1508         KEYMAP_(BRANCH), \
1509         KEYMAP_(PAGER), \
1510         KEYMAP_(HELP), \
1511         KEYMAP_(STATUS), \
1512         KEYMAP_(STAGE)
1514 enum keymap {
1515 #define KEYMAP_(name) KEYMAP_##name
1516         KEYMAP_INFO
1517 #undef  KEYMAP_
1518 };
1520 static const struct enum_map keymap_table[] = {
1521 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
1522         KEYMAP_INFO
1523 #undef  KEYMAP_
1524 };
1526 #define set_keymap(map, name) map_enum(map, keymap_table, name)
1528 struct keybinding_table {
1529         struct keybinding *data;
1530         size_t size;
1531 };
1533 static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
1535 static void
1536 add_keybinding(enum keymap keymap, enum request request, int key)
1538         struct keybinding_table *table = &keybindings[keymap];
1539         size_t i;
1541         for (i = 0; i < keybindings[keymap].size; i++) {
1542                 if (keybindings[keymap].data[i].alias == key) {
1543                         keybindings[keymap].data[i].request = request;
1544                         return;
1545                 }
1546         }
1548         table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
1549         if (!table->data)
1550                 die("Failed to allocate keybinding");
1551         table->data[table->size].alias = key;
1552         table->data[table->size++].request = request;
1554         if (request == REQ_NONE && keymap == KEYMAP_GENERIC) {
1555                 int i;
1557                 for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1558                         if (default_keybindings[i].alias == key)
1559                                 default_keybindings[i].request = REQ_NONE;
1560         }
1563 /* Looks for a key binding first in the given map, then in the generic map, and
1564  * lastly in the default keybindings. */
1565 static enum request
1566 get_keybinding(enum keymap keymap, int key)
1568         size_t i;
1570         for (i = 0; i < keybindings[keymap].size; i++)
1571                 if (keybindings[keymap].data[i].alias == key)
1572                         return keybindings[keymap].data[i].request;
1574         for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
1575                 if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
1576                         return keybindings[KEYMAP_GENERIC].data[i].request;
1578         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
1579                 if (default_keybindings[i].alias == key)
1580                         return default_keybindings[i].request;
1582         return (enum request) key;
1586 struct key {
1587         const char *name;
1588         int value;
1589 };
1591 static const struct key key_table[] = {
1592         { "Enter",      KEY_RETURN },
1593         { "Space",      ' ' },
1594         { "Backspace",  KEY_BACKSPACE },
1595         { "Tab",        KEY_TAB },
1596         { "Escape",     KEY_ESC },
1597         { "Left",       KEY_LEFT },
1598         { "Right",      KEY_RIGHT },
1599         { "Up",         KEY_UP },
1600         { "Down",       KEY_DOWN },
1601         { "Insert",     KEY_IC },
1602         { "Delete",     KEY_DC },
1603         { "Hash",       '#' },
1604         { "Home",       KEY_HOME },
1605         { "End",        KEY_END },
1606         { "PageUp",     KEY_PPAGE },
1607         { "PageDown",   KEY_NPAGE },
1608         { "F1",         KEY_F(1) },
1609         { "F2",         KEY_F(2) },
1610         { "F3",         KEY_F(3) },
1611         { "F4",         KEY_F(4) },
1612         { "F5",         KEY_F(5) },
1613         { "F6",         KEY_F(6) },
1614         { "F7",         KEY_F(7) },
1615         { "F8",         KEY_F(8) },
1616         { "F9",         KEY_F(9) },
1617         { "F10",        KEY_F(10) },
1618         { "F11",        KEY_F(11) },
1619         { "F12",        KEY_F(12) },
1620 };
1622 static int
1623 get_key_value(const char *name)
1625         int i;
1627         for (i = 0; i < ARRAY_SIZE(key_table); i++)
1628                 if (!strcasecmp(key_table[i].name, name))
1629                         return key_table[i].value;
1631         if (strlen(name) == 2 && name[0] == '^' && isprint(*name))
1632                 return (int)name[1] & 0x1f;
1633         if (strlen(name) == 1 && isprint(*name))
1634                 return (int) *name;
1635         return ERR;
1638 static const char *
1639 get_key_name(int key_value)
1641         static char key_char[] = "'X'\0";
1642         const char *seq = NULL;
1643         int key;
1645         for (key = 0; key < ARRAY_SIZE(key_table); key++)
1646                 if (key_table[key].value == key_value)
1647                         seq = key_table[key].name;
1649         if (seq == NULL && key_value < 0x7f) {
1650                 char *s = key_char + 1;
1652                 if (key_value >= 0x20) {
1653                         *s++ = key_value;
1654                 } else {
1655                         *s++ = '^';
1656                         *s++ = 0x40 | (key_value & 0x1f);
1657                 }
1658                 *s++ = '\'';
1659                 *s++ = '\0';
1660                 seq = key_char;
1661         }
1663         return seq ? seq : "(no key)";
1666 static bool
1667 append_key(char *buf, size_t *pos, const struct keybinding *keybinding)
1669         const char *sep = *pos > 0 ? ", " : "";
1670         const char *keyname = get_key_name(keybinding->alias);
1672         return string_nformat(buf, BUFSIZ, pos, "%s%s", sep, keyname);
1675 static bool
1676 append_keymap_request_keys(char *buf, size_t *pos, enum request request,
1677                            enum keymap keymap, bool all)
1679         int i;
1681         for (i = 0; i < keybindings[keymap].size; i++) {
1682                 if (keybindings[keymap].data[i].request == request) {
1683                         if (!append_key(buf, pos, &keybindings[keymap].data[i]))
1684                                 return FALSE;
1685                         if (!all)
1686                                 break;
1687                 }
1688         }
1690         return TRUE;
1693 #define get_key(keymap, request) get_keys(keymap, request, FALSE)
1695 static const char *
1696 get_keys(enum keymap keymap, enum request request, bool all)
1698         static char buf[BUFSIZ];
1699         size_t pos = 0;
1700         int i;
1702         buf[pos] = 0;
1704         if (!append_keymap_request_keys(buf, &pos, request, keymap, all))
1705                 return "Too many keybindings!";
1706         if (pos > 0 && !all)
1707                 return buf;
1709         if (keymap != KEYMAP_GENERIC) {
1710                 /* Only the generic keymap includes the default keybindings when
1711                  * listing all keys. */
1712                 if (all)
1713                         return buf;
1715                 if (!append_keymap_request_keys(buf, &pos, request, KEYMAP_GENERIC, all))
1716                         return "Too many keybindings!";
1717                 if (pos)
1718                         return buf;
1719         }
1721         for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
1722                 if (default_keybindings[i].request == request) {
1723                         if (!append_key(buf, &pos, &default_keybindings[i]))
1724                                 return "Too many keybindings!";
1725                         if (!all)
1726                                 return buf;
1727                 }
1728         }
1730         return buf;
1733 struct run_request {
1734         enum keymap keymap;
1735         int key;
1736         const char **argv;
1737 };
1739 static struct run_request *run_request;
1740 static size_t run_requests;
1742 DEFINE_ALLOCATOR(realloc_run_requests, struct run_request, 8)
1744 static enum request
1745 add_run_request(enum keymap keymap, int key, const char **argv)
1747         struct run_request *req;
1749         if (!realloc_run_requests(&run_request, run_requests, 1))
1750                 return REQ_NONE;
1752         req = &run_request[run_requests];
1753         req->keymap = keymap;
1754         req->key = key;
1755         req->argv = NULL;
1757         if (!argv_copy(&req->argv, argv))
1758                 return REQ_NONE;
1760         return REQ_NONE + ++run_requests;
1763 static struct run_request *
1764 get_run_request(enum request request)
1766         if (request <= REQ_NONE)
1767                 return NULL;
1768         return &run_request[request - REQ_NONE - 1];
1771 static void
1772 add_builtin_run_requests(void)
1774         const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
1775         const char *checkout[] = { "git", "checkout", "%(branch)", NULL };
1776         const char *commit[] = { "git", "commit", NULL };
1777         const char *gc[] = { "git", "gc", NULL };
1778         struct run_request reqs[] = {
1779                 { KEYMAP_MAIN,    'C', cherry_pick },
1780                 { KEYMAP_STATUS,  'C', commit },
1781                 { KEYMAP_BRANCH,  'C', checkout },
1782                 { KEYMAP_GENERIC, 'G', gc },
1783         };
1784         int i;
1786         for (i = 0; i < ARRAY_SIZE(reqs); i++) {
1787                 enum request req = get_keybinding(reqs[i].keymap, reqs[i].key);
1789                 if (req != reqs[i].key)
1790                         continue;
1791                 req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argv);
1792                 if (req != REQ_NONE)
1793                         add_keybinding(reqs[i].keymap, req, reqs[i].key);
1794         }
1797 /*
1798  * User config file handling.
1799  */
1801 #define OPT_ERR_INFO \
1802         OPT_ERR_(INTEGER_VALUE_OUT_OF_BOUND, "Integer value out of bound"), \
1803         OPT_ERR_(INVALID_STEP_VALUE, "Invalid step value"), \
1804         OPT_ERR_(NO_OPTION_VALUE, "No option value"), \
1805         OPT_ERR_(NO_VALUE_ASSIGNED, "No value assigned"), \
1806         OPT_ERR_(OBSOLETE_REQUEST_NAME, "Obsolete request name"), \
1807         OPT_ERR_(TOO_MANY_OPTION_ARGUMENTS, "Too many option arguments"), \
1808         OPT_ERR_(UNKNOWN_ATTRIBUTE, "Unknown attribute"), \
1809         OPT_ERR_(UNKNOWN_COLOR, "Unknown color"), \
1810         OPT_ERR_(UNKNOWN_COLOR_NAME, "Unknown color name"), \
1811         OPT_ERR_(UNKNOWN_KEY, "Unknown key"), \
1812         OPT_ERR_(UNKNOWN_KEY_MAP, "Unknown key map"), \
1813         OPT_ERR_(UNKNOWN_OPTION_COMMAND, "Unknown option command"), \
1814         OPT_ERR_(UNKNOWN_REQUEST_NAME, "Unknown request name"), \
1815         OPT_ERR_(UNKNOWN_VARIABLE_NAME, "Unknown variable name"), \
1816         OPT_ERR_(UNMATCHED_QUOTATION, "Unmatched quotation"), \
1817         OPT_ERR_(WRONG_NUMBER_OF_ARGUMENTS, "Wrong number of arguments"),
1819 enum option_code {
1820 #define OPT_ERR_(name, msg) OPT_ERR_ ## name
1821         OPT_ERR_INFO
1822 #undef  OPT_ERR_
1823         OPT_OK
1824 };
1826 static const char *option_errors[] = {
1827 #define OPT_ERR_(name, msg) msg
1828         OPT_ERR_INFO
1829 #undef  OPT_ERR_
1830 };
1832 static const struct enum_map color_map[] = {
1833 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
1834         COLOR_MAP(DEFAULT),
1835         COLOR_MAP(BLACK),
1836         COLOR_MAP(BLUE),
1837         COLOR_MAP(CYAN),
1838         COLOR_MAP(GREEN),
1839         COLOR_MAP(MAGENTA),
1840         COLOR_MAP(RED),
1841         COLOR_MAP(WHITE),
1842         COLOR_MAP(YELLOW),
1843 };
1845 static const struct enum_map attr_map[] = {
1846 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
1847         ATTR_MAP(NORMAL),
1848         ATTR_MAP(BLINK),
1849         ATTR_MAP(BOLD),
1850         ATTR_MAP(DIM),
1851         ATTR_MAP(REVERSE),
1852         ATTR_MAP(STANDOUT),
1853         ATTR_MAP(UNDERLINE),
1854 };
1856 #define set_attribute(attr, name)       map_enum(attr, attr_map, name)
1858 static enum option_code
1859 parse_step(double *opt, const char *arg)
1861         *opt = atoi(arg);
1862         if (!strchr(arg, '%'))
1863                 return OPT_OK;
1865         /* "Shift down" so 100% and 1 does not conflict. */
1866         *opt = (*opt - 1) / 100;
1867         if (*opt >= 1.0) {
1868                 *opt = 0.99;
1869                 return OPT_ERR_INVALID_STEP_VALUE;
1870         }
1871         if (*opt < 0.0) {
1872                 *opt = 1;
1873                 return OPT_ERR_INVALID_STEP_VALUE;
1874         }
1875         return OPT_OK;
1878 static enum option_code
1879 parse_int(int *opt, const char *arg, int min, int max)
1881         int value = atoi(arg);
1883         if (min <= value && value <= max) {
1884                 *opt = value;
1885                 return OPT_OK;
1886         }
1888         return OPT_ERR_INTEGER_VALUE_OUT_OF_BOUND;
1891 static bool
1892 set_color(int *color, const char *name)
1894         if (map_enum(color, color_map, name))
1895                 return TRUE;
1896         if (!prefixcmp(name, "color"))
1897                 return parse_int(color, name + 5, 0, 255) == OK;
1898         return FALSE;
1901 /* Wants: object fgcolor bgcolor [attribute] */
1902 static enum option_code
1903 option_color_command(int argc, const char *argv[])
1905         struct line_info *info;
1907         if (argc < 3)
1908                 return OPT_ERR_WRONG_NUMBER_OF_ARGUMENTS;
1910         info = get_line_info(argv[0]);
1911         if (!info) {
1912                 static const struct enum_map obsolete[] = {
1913                         ENUM_MAP("main-delim",  LINE_DELIMITER),
1914                         ENUM_MAP("main-date",   LINE_DATE),
1915                         ENUM_MAP("main-author", LINE_AUTHOR),
1916                 };
1917                 int index;
1919                 if (!map_enum(&index, obsolete, argv[0]))
1920                         return OPT_ERR_UNKNOWN_COLOR_NAME;
1921                 info = &line_info[index];
1922         }
1924         if (!set_color(&info->fg, argv[1]) ||
1925             !set_color(&info->bg, argv[2]))
1926                 return OPT_ERR_UNKNOWN_COLOR;
1928         info->attr = 0;
1929         while (argc-- > 3) {
1930                 int attr;
1932                 if (!set_attribute(&attr, argv[argc]))
1933                         return OPT_ERR_UNKNOWN_ATTRIBUTE;
1934                 info->attr |= attr;
1935         }
1937         return OPT_OK;
1940 static enum option_code
1941 parse_bool(bool *opt, const char *arg)
1943         *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
1944                 ? TRUE : FALSE;
1945         return OPT_OK;
1948 static enum option_code
1949 parse_enum_do(unsigned int *opt, const char *arg,
1950               const struct enum_map *map, size_t map_size)
1952         bool is_true;
1954         assert(map_size > 1);
1956         if (map_enum_do(map, map_size, (int *) opt, arg))
1957                 return OPT_OK;
1959         parse_bool(&is_true, arg);
1960         *opt = is_true ? map[1].value : map[0].value;
1961         return OPT_OK;
1964 #define parse_enum(opt, arg, map) \
1965         parse_enum_do(opt, arg, map, ARRAY_SIZE(map))
1967 static enum option_code
1968 parse_string(char *opt, const char *arg, size_t optsize)
1970         int arglen = strlen(arg);
1972         switch (arg[0]) {
1973         case '\"':
1974         case '\'':
1975                 if (arglen == 1 || arg[arglen - 1] != arg[0])
1976                         return OPT_ERR_UNMATCHED_QUOTATION;
1977                 arg += 1; arglen -= 2;
1978         default:
1979                 string_ncopy_do(opt, optsize, arg, arglen);
1980                 return OPT_OK;
1981         }
1984 /* Wants: name = value */
1985 static enum option_code
1986 option_set_command(int argc, const char *argv[])
1988         if (argc != 3)
1989                 return OPT_ERR_WRONG_NUMBER_OF_ARGUMENTS;
1991         if (strcmp(argv[1], "="))
1992                 return OPT_ERR_NO_VALUE_ASSIGNED;
1994         if (!strcmp(argv[0], "show-author"))
1995                 return parse_enum(&opt_author, argv[2], author_map);
1997         if (!strcmp(argv[0], "show-date"))
1998                 return parse_enum(&opt_date, argv[2], date_map);
2000         if (!strcmp(argv[0], "show-rev-graph"))
2001                 return parse_bool(&opt_rev_graph, argv[2]);
2003         if (!strcmp(argv[0], "show-refs"))
2004                 return parse_bool(&opt_show_refs, argv[2]);
2006         if (!strcmp(argv[0], "show-line-numbers"))
2007                 return parse_bool(&opt_line_number, argv[2]);
2009         if (!strcmp(argv[0], "line-graphics"))
2010                 return parse_bool(&opt_line_graphics, argv[2]);
2012         if (!strcmp(argv[0], "line-number-interval"))
2013                 return parse_int(&opt_num_interval, argv[2], 1, 1024);
2015         if (!strcmp(argv[0], "author-width"))
2016                 return parse_int(&opt_author_cols, argv[2], 0, 1024);
2018         if (!strcmp(argv[0], "horizontal-scroll"))
2019                 return parse_step(&opt_hscroll, argv[2]);
2021         if (!strcmp(argv[0], "split-view-height"))
2022                 return parse_step(&opt_scale_split_view, argv[2]);
2024         if (!strcmp(argv[0], "tab-size"))
2025                 return parse_int(&opt_tab_size, argv[2], 1, 1024);
2027         if (!strcmp(argv[0], "commit-encoding"))
2028                 return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
2030         if (!strcmp(argv[0], "status-untracked-dirs"))
2031                 return parse_bool(&opt_untracked_dirs_content, argv[2]);
2033         return OPT_ERR_UNKNOWN_VARIABLE_NAME;
2036 /* Wants: mode request key */
2037 static enum option_code
2038 option_bind_command(int argc, const char *argv[])
2040         enum request request;
2041         int keymap = -1;
2042         int key;
2044         if (argc < 3)
2045                 return OPT_ERR_WRONG_NUMBER_OF_ARGUMENTS;
2047         if (!set_keymap(&keymap, argv[0]))
2048                 return OPT_ERR_UNKNOWN_KEY_MAP;
2050         key = get_key_value(argv[1]);
2051         if (key == ERR)
2052                 return OPT_ERR_UNKNOWN_KEY;
2054         request = get_request(argv[2]);
2055         if (request == REQ_UNKNOWN) {
2056                 static const struct enum_map obsolete[] = {
2057                         ENUM_MAP("cherry-pick",         REQ_NONE),
2058                         ENUM_MAP("screen-resize",       REQ_NONE),
2059                         ENUM_MAP("tree-parent",         REQ_PARENT),
2060                 };
2061                 int alias;
2063                 if (map_enum(&alias, obsolete, argv[2])) {
2064                         if (alias != REQ_NONE)
2065                                 add_keybinding(keymap, alias, key);
2066                         return OPT_ERR_OBSOLETE_REQUEST_NAME;
2067                 }
2068         }
2069         if (request == REQ_UNKNOWN && *argv[2]++ == '!')
2070                 request = add_run_request(keymap, key, argv + 2);
2071         if (request == REQ_UNKNOWN)
2072                 return OPT_ERR_UNKNOWN_REQUEST_NAME;
2074         add_keybinding(keymap, request, key);
2076         return OPT_OK;
2079 static enum option_code
2080 set_option(const char *opt, char *value)
2082         const char *argv[SIZEOF_ARG];
2083         int argc = 0;
2085         if (!argv_from_string(argv, &argc, value))
2086                 return OPT_ERR_TOO_MANY_OPTION_ARGUMENTS;
2088         if (!strcmp(opt, "color"))
2089                 return option_color_command(argc, argv);
2091         if (!strcmp(opt, "set"))
2092                 return option_set_command(argc, argv);
2094         if (!strcmp(opt, "bind"))
2095                 return option_bind_command(argc, argv);
2097         return OPT_ERR_UNKNOWN_OPTION_COMMAND;
2100 struct config_state {
2101         int lineno;
2102         bool errors;
2103 };
2105 static int
2106 read_option(char *opt, size_t optlen, char *value, size_t valuelen, void *data)
2108         struct config_state *config = data;
2109         enum option_code status = OPT_ERR_NO_OPTION_VALUE;
2111         config->lineno++;
2113         /* Check for comment markers, since read_properties() will
2114          * only ensure opt and value are split at first " \t". */
2115         optlen = strcspn(opt, "#");
2116         if (optlen == 0)
2117                 return OK;
2119         if (opt[optlen] == 0) {
2120                 /* Look for comment endings in the value. */
2121                 size_t len = strcspn(value, "#");
2123                 if (len < valuelen) {
2124                         valuelen = len;
2125                         value[valuelen] = 0;
2126                 }
2128                 status = set_option(opt, value);
2129         }
2131         if (status != OPT_OK) {
2132                 warn("Error on line %d, near '%.*s': %s",
2133                      config->lineno, (int) optlen, opt, option_errors[status]);
2134                 config->errors = TRUE;
2135         }
2137         /* Always keep going if errors are encountered. */
2138         return OK;
2141 static void
2142 load_option_file(const char *path)
2144         struct config_state config = { 0, FALSE };
2145         struct io io;
2147         /* It's OK that the file doesn't exist. */
2148         if (!io_open(&io, "%s", path))
2149                 return;
2151         if (io_load(&io, " \t", read_option, &config) == ERR ||
2152             config.errors == TRUE)
2153                 warn("Errors while loading %s.", path);
2156 static int
2157 load_options(void)
2159         const char *home = getenv("HOME");
2160         const char *tigrc_user = getenv("TIGRC_USER");
2161         const char *tigrc_system = getenv("TIGRC_SYSTEM");
2162         const char *tig_diff_opts = getenv("TIG_DIFF_OPTS");
2163         char buf[SIZEOF_STR];
2165         if (!tigrc_system)
2166                 tigrc_system = SYSCONFDIR "/tigrc";
2167         load_option_file(tigrc_system);
2169         if (!tigrc_user) {
2170                 if (!home || !string_format(buf, "%s/.tigrc", home))
2171                         return ERR;
2172                 tigrc_user = buf;
2173         }
2174         load_option_file(tigrc_user);
2176         /* Add _after_ loading config files to avoid adding run requests
2177          * that conflict with keybindings. */
2178         add_builtin_run_requests();
2180         if (!opt_diff_argv && tig_diff_opts && *tig_diff_opts) {
2181                 static const char *diff_opts[SIZEOF_ARG] = { NULL };
2182                 int argc = 0;
2184                 if (!string_format(buf, "%s", tig_diff_opts) ||
2185                     !argv_from_string(diff_opts, &argc, buf))
2186                         die("TIG_DIFF_OPTS contains too many arguments");
2187                 else if (!argv_copy(&opt_diff_argv, diff_opts))
2188                         die("Failed to format TIG_DIFF_OPTS arguments");
2189         }
2191         return OK;
2195 /*
2196  * The viewer
2197  */
2199 struct view;
2200 struct view_ops;
2202 /* The display array of active views and the index of the current view. */
2203 static struct view *display[2];
2204 static WINDOW *display_win[2];
2205 static WINDOW *display_title[2];
2206 static unsigned int current_view;
2208 #define foreach_displayed_view(view, i) \
2209         for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
2211 #define displayed_views()       (display[1] != NULL ? 2 : 1)
2213 /* Current head and commit ID */
2214 static char ref_blob[SIZEOF_REF]        = "";
2215 static char ref_commit[SIZEOF_REF]      = "HEAD";
2216 static char ref_head[SIZEOF_REF]        = "HEAD";
2217 static char ref_branch[SIZEOF_REF]      = "";
2219 enum view_type {
2220         VIEW_MAIN,
2221         VIEW_DIFF,
2222         VIEW_LOG,
2223         VIEW_TREE,
2224         VIEW_BLOB,
2225         VIEW_BLAME,
2226         VIEW_BRANCH,
2227         VIEW_HELP,
2228         VIEW_PAGER,
2229         VIEW_STATUS,
2230         VIEW_STAGE,
2231 };
2233 struct view {
2234         enum view_type type;    /* View type */
2235         const char *name;       /* View name */
2236         const char *cmd_env;    /* Command line set via environment */
2237         const char *id;         /* Points to either of ref_{head,commit,blob} */
2239         struct view_ops *ops;   /* View operations */
2241         enum keymap keymap;     /* What keymap does this view have */
2242         bool git_dir;           /* Whether the view requires a git directory. */
2244         char ref[SIZEOF_REF];   /* Hovered commit reference */
2245         char vid[SIZEOF_REF];   /* View ID. Set to id member when updating. */
2247         int height, width;      /* The width and height of the main window */
2248         WINDOW *win;            /* The main window */
2250         /* Navigation */
2251         unsigned long offset;   /* Offset of the window top */
2252         unsigned long yoffset;  /* Offset from the window side. */
2253         unsigned long lineno;   /* Current line number */
2254         unsigned long p_offset; /* Previous offset of the window top */
2255         unsigned long p_yoffset;/* Previous offset from the window side */
2256         unsigned long p_lineno; /* Previous current line number */
2257         bool p_restore;         /* Should the previous position be restored. */
2259         /* Searching */
2260         char grep[SIZEOF_STR];  /* Search string */
2261         regex_t *regex;         /* Pre-compiled regexp */
2263         /* If non-NULL, points to the view that opened this view. If this view
2264          * is closed tig will switch back to the parent view. */
2265         struct view *parent;
2266         struct view *prev;
2268         /* Buffering */
2269         size_t lines;           /* Total number of lines */
2270         struct line *line;      /* Line index */
2271         unsigned int digits;    /* Number of digits in the lines member. */
2273         /* Drawing */
2274         struct line *curline;   /* Line currently being drawn. */
2275         enum line_type curtype; /* Attribute currently used for drawing. */
2276         unsigned long col;      /* Column when drawing. */
2277         bool has_scrolled;      /* View was scrolled. */
2279         /* Loading */
2280         const char **argv;      /* Shell command arguments. */
2281         const char *dir;        /* Directory from which to execute. */
2282         struct io io;
2283         struct io *pipe;
2284         time_t start_time;
2285         time_t update_secs;
2286 };
2288 struct view_ops {
2289         /* What type of content being displayed. Used in the title bar. */
2290         const char *type;
2291         /* Default command arguments. */
2292         const char **argv;
2293         /* Open and reads in all view content. */
2294         bool (*open)(struct view *view);
2295         /* Read one line; updates view->line. */
2296         bool (*read)(struct view *view, char *data);
2297         /* Draw one line; @lineno must be < view->height. */
2298         bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
2299         /* Depending on view handle a special requests. */
2300         enum request (*request)(struct view *view, enum request request, struct line *line);
2301         /* Search for regexp in a line. */
2302         bool (*grep)(struct view *view, struct line *line);
2303         /* Select line */
2304         void (*select)(struct view *view, struct line *line);
2305         /* Prepare view for loading */
2306         bool (*prepare)(struct view *view);
2307 };
2309 static struct view_ops blame_ops;
2310 static struct view_ops blob_ops;
2311 static struct view_ops diff_ops;
2312 static struct view_ops help_ops;
2313 static struct view_ops log_ops;
2314 static struct view_ops main_ops;
2315 static struct view_ops pager_ops;
2316 static struct view_ops stage_ops;
2317 static struct view_ops status_ops;
2318 static struct view_ops tree_ops;
2319 static struct view_ops branch_ops;
2321 #define VIEW_STR(type, name, env, ref, ops, map, git) \
2322         { type, name, #env, ref, ops, map, git }
2324 #define VIEW_(id, name, ops, git, ref) \
2325         VIEW_STR(VIEW_##id, name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
2327 static struct view views[] = {
2328         VIEW_(MAIN,   "main",   &main_ops,   TRUE,  ref_head),
2329         VIEW_(DIFF,   "diff",   &diff_ops,   TRUE,  ref_commit),
2330         VIEW_(LOG,    "log",    &log_ops,    TRUE,  ref_head),
2331         VIEW_(TREE,   "tree",   &tree_ops,   TRUE,  ref_commit),
2332         VIEW_(BLOB,   "blob",   &blob_ops,   TRUE,  ref_blob),
2333         VIEW_(BLAME,  "blame",  &blame_ops,  TRUE,  ref_commit),
2334         VIEW_(BRANCH, "branch", &branch_ops, TRUE,  ref_head),
2335         VIEW_(HELP,   "help",   &help_ops,   FALSE, ""),
2336         VIEW_(PAGER,  "pager",  &pager_ops,  FALSE, ""),
2337         VIEW_(STATUS, "status", &status_ops, TRUE,  ""),
2338         VIEW_(STAGE,  "stage",  &stage_ops,  TRUE,  ""),
2339 };
2341 #define VIEW(req)       (&views[(req) - REQ_OFFSET - 1])
2343 #define foreach_view(view, i) \
2344         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2346 #define view_is_displayed(view) \
2347         (view == display[0] || view == display[1])
2349 static enum request
2350 view_request(struct view *view, enum request request)
2352         if (!view || !view->lines)
2353                 return request;
2354         return view->ops->request(view, request, &view->line[view->lineno]);
2358 /*
2359  * View drawing.
2360  */
2362 static inline void
2363 set_view_attr(struct view *view, enum line_type type)
2365         if (!view->curline->selected && view->curtype != type) {
2366                 (void) wattrset(view->win, get_line_attr(type));
2367                 wchgat(view->win, -1, 0, type, NULL);
2368                 view->curtype = type;
2369         }
2372 static int
2373 draw_chars(struct view *view, enum line_type type, const char *string,
2374            int max_len, bool use_tilde)
2376         static char out_buffer[BUFSIZ * 2];
2377         int len = 0;
2378         int col = 0;
2379         int trimmed = FALSE;
2380         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2382         if (max_len <= 0)
2383                 return 0;
2385         len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde, opt_tab_size);
2387         set_view_attr(view, type);
2388         if (len > 0) {
2389                 if (opt_iconv_out != ICONV_NONE) {
2390                         ICONV_CONST char *inbuf = (ICONV_CONST char *) string;
2391                         size_t inlen = len + 1;
2393                         char *outbuf = out_buffer;
2394                         size_t outlen = sizeof(out_buffer);
2396                         size_t ret;
2398                         ret = iconv(opt_iconv_out, &inbuf, &inlen, &outbuf, &outlen);
2399                         if (ret != (size_t) -1) {
2400                                 string = out_buffer;
2401                                 len = sizeof(out_buffer) - outlen;
2402                         }
2403                 }
2405                 waddnstr(view->win, string, len);
2407                 if (trimmed && use_tilde) {
2408                         set_view_attr(view, LINE_DELIMITER);
2409                         waddch(view->win, '~');
2410                         col++;
2411                 }
2412         }
2414         return col;
2417 static int
2418 draw_space(struct view *view, enum line_type type, int max, int spaces)
2420         static char space[] = "                    ";
2421         int col = 0;
2423         spaces = MIN(max, spaces);
2425         while (spaces > 0) {
2426                 int len = MIN(spaces, sizeof(space) - 1);
2428                 col += draw_chars(view, type, space, len, FALSE);
2429                 spaces -= len;
2430         }
2432         return col;
2435 static bool
2436 draw_text(struct view *view, enum line_type type, const char *string)
2438         char text[SIZEOF_STR];
2440         do {
2441                 size_t pos = string_expand(text, sizeof(text), string, opt_tab_size);
2443                 view->col += draw_chars(view, type, text, view->width + view->yoffset - view->col, TRUE);
2444                 string += pos;
2445         } while (*string && view->width + view->yoffset > view->col);
2447         return view->width + view->yoffset <= view->col;
2450 static bool
2451 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
2453         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2454         int max = view->width + view->yoffset - view->col;
2455         int i;
2457         if (max < size)
2458                 size = max;
2460         set_view_attr(view, type);
2461         /* Using waddch() instead of waddnstr() ensures that
2462          * they'll be rendered correctly for the cursor line. */
2463         for (i = skip; i < size; i++)
2464                 waddch(view->win, graphic[i]);
2466         view->col += size;
2467         if (size < max && skip <= size)
2468                 waddch(view->win, ' ');
2469         view->col++;
2471         return view->width + view->yoffset <= view->col;
2474 static bool
2475 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
2477         int max = MIN(view->width + view->yoffset - view->col, len);
2478         int col;
2480         if (text)
2481                 col = draw_chars(view, type, text, max - 1, trim);
2482         else
2483                 col = draw_space(view, type, max - 1, max - 1);
2485         view->col += col;
2486         view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
2487         return view->width + view->yoffset <= view->col;
2490 static bool
2491 draw_date(struct view *view, struct time *time)
2493         const char *date = mkdate(time, opt_date);
2494         int cols = opt_date == DATE_SHORT ? DATE_SHORT_COLS : DATE_COLS;
2496         return draw_field(view, LINE_DATE, date, cols, FALSE);
2499 static bool
2500 draw_author(struct view *view, const char *author)
2502         bool trim = opt_author_cols == 0 || opt_author_cols > 5;
2503         bool abbreviate = opt_author == AUTHOR_ABBREVIATED || !trim;
2505         if (abbreviate && author)
2506                 author = get_author_initials(author);
2508         return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2511 static bool
2512 draw_mode(struct view *view, mode_t mode)
2514         const char *str;
2516         if (S_ISDIR(mode))
2517                 str = "drwxr-xr-x";
2518         else if (S_ISLNK(mode))
2519                 str = "lrwxrwxrwx";
2520         else if (S_ISGITLINK(mode))
2521                 str = "m---------";
2522         else if (S_ISREG(mode) && mode & S_IXUSR)
2523                 str = "-rwxr-xr-x";
2524         else if (S_ISREG(mode))
2525                 str = "-rw-r--r--";
2526         else
2527                 str = "----------";
2529         return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2532 static bool
2533 draw_lineno(struct view *view, unsigned int lineno)
2535         char number[10];
2536         int digits3 = view->digits < 3 ? 3 : view->digits;
2537         int max = MIN(view->width + view->yoffset - view->col, digits3);
2538         char *text = NULL;
2539         chtype separator = opt_line_graphics ? ACS_VLINE : '|';
2541         lineno += view->offset + 1;
2542         if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2543                 static char fmt[] = "%1ld";
2545                 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2546                 if (string_format(number, fmt, lineno))
2547                         text = number;
2548         }
2549         if (text)
2550                 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2551         else
2552                 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2553         return draw_graphic(view, LINE_DEFAULT, &separator, 1);
2556 static bool
2557 draw_view_line(struct view *view, unsigned int lineno)
2559         struct line *line;
2560         bool selected = (view->offset + lineno == view->lineno);
2562         assert(view_is_displayed(view));
2564         if (view->offset + lineno >= view->lines)
2565                 return FALSE;
2567         line = &view->line[view->offset + lineno];
2569         wmove(view->win, lineno, 0);
2570         if (line->cleareol)
2571                 wclrtoeol(view->win);
2572         view->col = 0;
2573         view->curline = line;
2574         view->curtype = LINE_NONE;
2575         line->selected = FALSE;
2576         line->dirty = line->cleareol = 0;
2578         if (selected) {
2579                 set_view_attr(view, LINE_CURSOR);
2580                 line->selected = TRUE;
2581                 view->ops->select(view, line);
2582         }
2584         return view->ops->draw(view, line, lineno);
2587 static void
2588 redraw_view_dirty(struct view *view)
2590         bool dirty = FALSE;
2591         int lineno;
2593         for (lineno = 0; lineno < view->height; lineno++) {
2594                 if (view->offset + lineno >= view->lines)
2595                         break;
2596                 if (!view->line[view->offset + lineno].dirty)
2597                         continue;
2598                 dirty = TRUE;
2599                 if (!draw_view_line(view, lineno))
2600                         break;
2601         }
2603         if (!dirty)
2604                 return;
2605         wnoutrefresh(view->win);
2608 static void
2609 redraw_view_from(struct view *view, int lineno)
2611         assert(0 <= lineno && lineno < view->height);
2613         for (; lineno < view->height; lineno++) {
2614                 if (!draw_view_line(view, lineno))
2615                         break;
2616         }
2618         wnoutrefresh(view->win);
2621 static void
2622 redraw_view(struct view *view)
2624         werase(view->win);
2625         redraw_view_from(view, 0);
2629 static void
2630 update_view_title(struct view *view)
2632         char buf[SIZEOF_STR];
2633         char state[SIZEOF_STR];
2634         size_t bufpos = 0, statelen = 0;
2635         WINDOW *window = display[0] == view ? display_title[0] : display_title[1];
2637         assert(view_is_displayed(view));
2639         if (view->type != VIEW_STATUS && view->lines) {
2640                 unsigned int view_lines = view->offset + view->height;
2641                 unsigned int lines = view->lines
2642                                    ? MIN(view_lines, view->lines) * 100 / view->lines
2643                                    : 0;
2645                 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2646                                    view->ops->type,
2647                                    view->lineno + 1,
2648                                    view->lines,
2649                                    lines);
2651         }
2653         if (view->pipe) {
2654                 time_t secs = time(NULL) - view->start_time;
2656                 /* Three git seconds are a long time ... */
2657                 if (secs > 2)
2658                         string_format_from(state, &statelen, " loading %lds", secs);
2659         }
2661         string_format_from(buf, &bufpos, "[%s]", view->name);
2662         if (*view->ref && bufpos < view->width) {
2663                 size_t refsize = strlen(view->ref);
2664                 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2666                 if (minsize < view->width)
2667                         refsize = view->width - minsize + 7;
2668                 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2669         }
2671         if (statelen && bufpos < view->width) {
2672                 string_format_from(buf, &bufpos, "%s", state);
2673         }
2675         if (view == display[current_view])
2676                 wbkgdset(window, get_line_attr(LINE_TITLE_FOCUS));
2677         else
2678                 wbkgdset(window, get_line_attr(LINE_TITLE_BLUR));
2680         mvwaddnstr(window, 0, 0, buf, bufpos);
2681         wclrtoeol(window);
2682         wnoutrefresh(window);
2685 static int
2686 apply_step(double step, int value)
2688         if (step >= 1)
2689                 return (int) step;
2690         value *= step + 0.01;
2691         return value ? value : 1;
2694 static void
2695 resize_display(void)
2697         int offset, i;
2698         struct view *base = display[0];
2699         struct view *view = display[1] ? display[1] : display[0];
2701         /* Setup window dimensions */
2703         getmaxyx(stdscr, base->height, base->width);
2705         /* Make room for the status window. */
2706         base->height -= 1;
2708         if (view != base) {
2709                 /* Horizontal split. */
2710                 view->width   = base->width;
2711                 view->height  = apply_step(opt_scale_split_view, base->height);
2712                 view->height  = MAX(view->height, MIN_VIEW_HEIGHT);
2713                 view->height  = MIN(view->height, base->height - MIN_VIEW_HEIGHT);
2714                 base->height -= view->height;
2716                 /* Make room for the title bar. */
2717                 view->height -= 1;
2718         }
2720         /* Make room for the title bar. */
2721         base->height -= 1;
2723         offset = 0;
2725         foreach_displayed_view (view, i) {
2726                 if (!display_win[i]) {
2727                         display_win[i] = newwin(view->height, view->width, offset, 0);
2728                         if (!display_win[i])
2729                                 die("Failed to create %s view", view->name);
2731                         scrollok(display_win[i], FALSE);
2733                         display_title[i] = newwin(1, view->width, offset + view->height, 0);
2734                         if (!display_title[i])
2735                                 die("Failed to create title window");
2737                 } else {
2738                         wresize(display_win[i], view->height, view->width);
2739                         mvwin(display_win[i],   offset, 0);
2740                         mvwin(display_title[i], offset + view->height, 0);
2741                 }
2743                 view->win = display_win[i];
2745                 offset += view->height + 1;
2746         }
2749 static void
2750 redraw_display(bool clear)
2752         struct view *view;
2753         int i;
2755         foreach_displayed_view (view, i) {
2756                 if (clear)
2757                         wclear(view->win);
2758                 redraw_view(view);
2759                 update_view_title(view);
2760         }
2764 /*
2765  * Option management
2766  */
2768 #define TOGGLE_MENU \
2769         TOGGLE_(LINENO,    '.', "line numbers",      &opt_line_number, NULL) \
2770         TOGGLE_(DATE,      'D', "dates",             &opt_date,   date_map) \
2771         TOGGLE_(AUTHOR,    'A', "author names",      &opt_author, author_map) \
2772         TOGGLE_(REV_GRAPH, 'g', "revision graph",    &opt_rev_graph, NULL) \
2773         TOGGLE_(REFS,      'F', "reference display", &opt_show_refs, NULL)
2775 static void
2776 toggle_option(enum request request)
2778         const struct {
2779                 enum request request;
2780                 const struct enum_map *map;
2781                 size_t map_size;
2782         } data[] = {            
2783 #define TOGGLE_(id, key, help, value, map) { REQ_TOGGLE_ ## id, map, ARRAY_SIZE(map) },
2784                 TOGGLE_MENU
2785 #undef  TOGGLE_
2786         };
2787         const struct menu_item menu[] = {
2788 #define TOGGLE_(id, key, help, value, map) { key, help, value },
2789                 TOGGLE_MENU
2790 #undef  TOGGLE_
2791                 { 0 }
2792         };
2793         int i = 0;
2795         if (request == REQ_OPTIONS) {
2796                 if (!prompt_menu("Toggle option", menu, &i))
2797                         return;
2798         } else {
2799                 while (i < ARRAY_SIZE(data) && data[i].request != request)
2800                         i++;
2801                 if (i >= ARRAY_SIZE(data))
2802                         die("Invalid request (%d)", request);
2803         }
2805         if (data[i].map != NULL) {
2806                 unsigned int *opt = menu[i].data;
2808                 *opt = (*opt + 1) % data[i].map_size;
2809                 redraw_display(FALSE);
2810                 report("Displaying %s %s", enum_name(data[i].map[*opt]), menu[i].text);
2812         } else {
2813                 bool *option = menu[i].data;
2815                 *option = !*option;
2816                 redraw_display(FALSE);
2817                 report("%sabling %s", *option ? "En" : "Dis", menu[i].text);
2818         }
2821 static void
2822 maximize_view(struct view *view)
2824         memset(display, 0, sizeof(display));
2825         current_view = 0;
2826         display[current_view] = view;
2827         resize_display();
2828         redraw_display(FALSE);
2829         report("");
2833 /*
2834  * Navigation
2835  */
2837 static bool
2838 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2840         if (lineno >= view->lines)
2841                 lineno = view->lines > 0 ? view->lines - 1 : 0;
2843         if (offset > lineno || offset + view->height <= lineno) {
2844                 unsigned long half = view->height / 2;
2846                 if (lineno > half)
2847                         offset = lineno - half;
2848                 else
2849                         offset = 0;
2850         }
2852         if (offset != view->offset || lineno != view->lineno) {
2853                 view->offset = offset;
2854                 view->lineno = lineno;
2855                 return TRUE;
2856         }
2858         return FALSE;
2861 /* Scrolling backend */
2862 static void
2863 do_scroll_view(struct view *view, int lines)
2865         bool redraw_current_line = FALSE;
2867         /* The rendering expects the new offset. */
2868         view->offset += lines;
2870         assert(0 <= view->offset && view->offset < view->lines);
2871         assert(lines);
2873         /* Move current line into the view. */
2874         if (view->lineno < view->offset) {
2875                 view->lineno = view->offset;
2876                 redraw_current_line = TRUE;
2877         } else if (view->lineno >= view->offset + view->height) {
2878                 view->lineno = view->offset + view->height - 1;
2879                 redraw_current_line = TRUE;
2880         }
2882         assert(view->offset <= view->lineno && view->lineno < view->lines);
2884         /* Redraw the whole screen if scrolling is pointless. */
2885         if (view->height < ABS(lines)) {
2886                 redraw_view(view);
2888         } else {
2889                 int line = lines > 0 ? view->height - lines : 0;
2890                 int end = line + ABS(lines);
2892                 scrollok(view->win, TRUE);
2893                 wscrl(view->win, lines);
2894                 scrollok(view->win, FALSE);
2896                 while (line < end && draw_view_line(view, line))
2897                         line++;
2899                 if (redraw_current_line)
2900                         draw_view_line(view, view->lineno - view->offset);
2901                 wnoutrefresh(view->win);
2902         }
2904         view->has_scrolled = TRUE;
2905         report("");
2908 /* Scroll frontend */
2909 static void
2910 scroll_view(struct view *view, enum request request)
2912         int lines = 1;
2914         assert(view_is_displayed(view));
2916         switch (request) {
2917         case REQ_SCROLL_FIRST_COL:
2918                 view->yoffset = 0;
2919                 redraw_view_from(view, 0);
2920                 report("");
2921                 return;
2922         case REQ_SCROLL_LEFT:
2923                 if (view->yoffset == 0) {
2924                         report("Cannot scroll beyond the first column");
2925                         return;
2926                 }
2927                 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2928                         view->yoffset = 0;
2929                 else
2930                         view->yoffset -= apply_step(opt_hscroll, view->width);
2931                 redraw_view_from(view, 0);
2932                 report("");
2933                 return;
2934         case REQ_SCROLL_RIGHT:
2935                 view->yoffset += apply_step(opt_hscroll, view->width);
2936                 redraw_view(view);
2937                 report("");
2938                 return;
2939         case REQ_SCROLL_PAGE_DOWN:
2940                 lines = view->height;
2941         case REQ_SCROLL_LINE_DOWN:
2942                 if (view->offset + lines > view->lines)
2943                         lines = view->lines - view->offset;
2945                 if (lines == 0 || view->offset + view->height >= view->lines) {
2946                         report("Cannot scroll beyond the last line");
2947                         return;
2948                 }
2949                 break;
2951         case REQ_SCROLL_PAGE_UP:
2952                 lines = view->height;
2953         case REQ_SCROLL_LINE_UP:
2954                 if (lines > view->offset)
2955                         lines = view->offset;
2957                 if (lines == 0) {
2958                         report("Cannot scroll beyond the first line");
2959                         return;
2960                 }
2962                 lines = -lines;
2963                 break;
2965         default:
2966                 die("request %d not handled in switch", request);
2967         }
2969         do_scroll_view(view, lines);
2972 /* Cursor moving */
2973 static void
2974 move_view(struct view *view, enum request request)
2976         int scroll_steps = 0;
2977         int steps;
2979         switch (request) {
2980         case REQ_MOVE_FIRST_LINE:
2981                 steps = -view->lineno;
2982                 break;
2984         case REQ_MOVE_LAST_LINE:
2985                 steps = view->lines - view->lineno - 1;
2986                 break;
2988         case REQ_MOVE_PAGE_UP:
2989                 steps = view->height > view->lineno
2990                       ? -view->lineno : -view->height;
2991                 break;
2993         case REQ_MOVE_PAGE_DOWN:
2994                 steps = view->lineno + view->height >= view->lines
2995                       ? view->lines - view->lineno - 1 : view->height;
2996                 break;
2998         case REQ_MOVE_UP:
2999                 steps = -1;
3000                 break;
3002         case REQ_MOVE_DOWN:
3003                 steps = 1;
3004                 break;
3006         default:
3007                 die("request %d not handled in switch", request);
3008         }
3010         if (steps <= 0 && view->lineno == 0) {
3011                 report("Cannot move beyond the first line");
3012                 return;
3014         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
3015                 report("Cannot move beyond the last line");
3016                 return;
3017         }
3019         /* Move the current line */
3020         view->lineno += steps;
3021         assert(0 <= view->lineno && view->lineno < view->lines);
3023         /* Check whether the view needs to be scrolled */
3024         if (view->lineno < view->offset ||
3025             view->lineno >= view->offset + view->height) {
3026                 scroll_steps = steps;
3027                 if (steps < 0 && -steps > view->offset) {
3028                         scroll_steps = -view->offset;
3030                 } else if (steps > 0) {
3031                         if (view->lineno == view->lines - 1 &&
3032                             view->lines > view->height) {
3033                                 scroll_steps = view->lines - view->offset - 1;
3034                                 if (scroll_steps >= view->height)
3035                                         scroll_steps -= view->height - 1;
3036                         }
3037                 }
3038         }
3040         if (!view_is_displayed(view)) {
3041                 view->offset += scroll_steps;
3042                 assert(0 <= view->offset && view->offset < view->lines);
3043                 view->ops->select(view, &view->line[view->lineno]);
3044                 return;
3045         }
3047         /* Repaint the old "current" line if we be scrolling */
3048         if (ABS(steps) < view->height)
3049                 draw_view_line(view, view->lineno - steps - view->offset);
3051         if (scroll_steps) {
3052                 do_scroll_view(view, scroll_steps);
3053                 return;
3054         }
3056         /* Draw the current line */
3057         draw_view_line(view, view->lineno - view->offset);
3059         wnoutrefresh(view->win);
3060         report("");
3064 /*
3065  * Searching
3066  */
3068 static void search_view(struct view *view, enum request request);
3070 static bool
3071 grep_text(struct view *view, const char *text[])
3073         regmatch_t pmatch;
3074         size_t i;
3076         for (i = 0; text[i]; i++)
3077                 if (*text[i] &&
3078                     regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
3079                         return TRUE;
3080         return FALSE;
3083 static void
3084 select_view_line(struct view *view, unsigned long lineno)
3086         unsigned long old_lineno = view->lineno;
3087         unsigned long old_offset = view->offset;
3089         if (goto_view_line(view, view->offset, lineno)) {
3090                 if (view_is_displayed(view)) {
3091                         if (old_offset != view->offset) {
3092                                 redraw_view(view);
3093                         } else {
3094                                 draw_view_line(view, old_lineno - view->offset);
3095                                 draw_view_line(view, view->lineno - view->offset);
3096                                 wnoutrefresh(view->win);
3097                         }
3098                 } else {
3099                         view->ops->select(view, &view->line[view->lineno]);
3100                 }
3101         }
3104 static void
3105 find_next(struct view *view, enum request request)
3107         unsigned long lineno = view->lineno;
3108         int direction;
3110         if (!*view->grep) {
3111                 if (!*opt_search)
3112                         report("No previous search");
3113                 else
3114                         search_view(view, request);
3115                 return;
3116         }
3118         switch (request) {
3119         case REQ_SEARCH:
3120         case REQ_FIND_NEXT:
3121                 direction = 1;
3122                 break;
3124         case REQ_SEARCH_BACK:
3125         case REQ_FIND_PREV:
3126                 direction = -1;
3127                 break;
3129         default:
3130                 return;
3131         }
3133         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
3134                 lineno += direction;
3136         /* Note, lineno is unsigned long so will wrap around in which case it
3137          * will become bigger than view->lines. */
3138         for (; lineno < view->lines; lineno += direction) {
3139                 if (view->ops->grep(view, &view->line[lineno])) {
3140                         select_view_line(view, lineno);
3141                         report("Line %ld matches '%s'", lineno + 1, view->grep);
3142                         return;
3143                 }
3144         }
3146         report("No match found for '%s'", view->grep);
3149 static void
3150 search_view(struct view *view, enum request request)
3152         int regex_err;
3154         if (view->regex) {
3155                 regfree(view->regex);
3156                 *view->grep = 0;
3157         } else {
3158                 view->regex = calloc(1, sizeof(*view->regex));
3159                 if (!view->regex)
3160                         return;
3161         }
3163         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
3164         if (regex_err != 0) {
3165                 char buf[SIZEOF_STR] = "unknown error";
3167                 regerror(regex_err, view->regex, buf, sizeof(buf));
3168                 report("Search failed: %s", buf);
3169                 return;
3170         }
3172         string_copy(view->grep, opt_search);
3174         find_next(view, request);
3177 /*
3178  * Incremental updating
3179  */
3181 static void
3182 reset_view(struct view *view)
3184         int i;
3186         for (i = 0; i < view->lines; i++)
3187                 free(view->line[i].data);
3188         free(view->line);
3190         view->p_offset = view->offset;
3191         view->p_yoffset = view->yoffset;
3192         view->p_lineno = view->lineno;
3194         view->line = NULL;
3195         view->offset = 0;
3196         view->yoffset = 0;
3197         view->lines  = 0;
3198         view->lineno = 0;
3199         view->vid[0] = 0;
3200         view->update_secs = 0;
3203 static const char *
3204 format_arg(const char *name)
3206         static struct {
3207                 const char *name;
3208                 size_t namelen;
3209                 const char *value;
3210                 const char *value_if_empty;
3211         } vars[] = {
3212 #define FORMAT_VAR(name, value, value_if_empty) \
3213         { name, STRING_SIZE(name), value, value_if_empty }
3214                 FORMAT_VAR("%(directory)",      opt_path,       ""),
3215                 FORMAT_VAR("%(file)",           opt_file,       ""),
3216                 FORMAT_VAR("%(ref)",            opt_ref,        "HEAD"),
3217                 FORMAT_VAR("%(head)",           ref_head,       ""),
3218                 FORMAT_VAR("%(commit)",         ref_commit,     ""),
3219                 FORMAT_VAR("%(blob)",           ref_blob,       ""),
3220                 FORMAT_VAR("%(branch)",         ref_branch,     ""),
3221         };
3222         int i;
3224         for (i = 0; i < ARRAY_SIZE(vars); i++)
3225                 if (!strncmp(name, vars[i].name, vars[i].namelen))
3226                         return *vars[i].value ? vars[i].value : vars[i].value_if_empty;
3228         report("Unknown replacement: `%s`", name);
3229         return NULL;
3232 static bool
3233 format_argv(const char ***dst_argv, const char *src_argv[], bool replace, bool first)
3235         char buf[SIZEOF_STR];
3236         int argc;
3238         argv_free(*dst_argv);
3240         for (argc = 0; src_argv[argc]; argc++) {
3241                 const char *arg = src_argv[argc];
3242                 size_t bufpos = 0;
3244                 if (!strcmp(arg, "%(fileargs)")) {
3245                         if (!argv_append_array(dst_argv, opt_file_argv))
3246                                 break;
3247                         continue;
3249                 } else if (!strcmp(arg, "%(diffargs)")) {
3250                         if (!argv_append_array(dst_argv, opt_diff_argv))
3251                                 break;
3252                         continue;
3254                 } else if (!strcmp(arg, "%(revargs)") ||
3255                            (first && !strcmp(arg, "%(commit)"))) {
3256                         if (!argv_append_array(dst_argv, opt_rev_argv))
3257                                 break;
3258                         continue;
3259                 }
3261                 while (arg) {
3262                         char *next = strstr(arg, "%(");
3263                         int len = next - arg;
3264                         const char *value;
3266                         if (!next || !replace) {
3267                                 len = strlen(arg);
3268                                 value = "";
3270                         } else {
3271                                 value = format_arg(next);
3273                                 if (!value) {
3274                                         return FALSE;
3275                                 }
3276                         }
3278                         if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
3279                                 return FALSE;
3281                         arg = next && replace ? strchr(next, ')') + 1 : NULL;
3282                 }
3284                 if (!argv_append(dst_argv, buf))
3285                         break;
3286         }
3288         return src_argv[argc] == NULL;
3291 static bool
3292 restore_view_position(struct view *view)
3294         if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
3295                 return FALSE;
3297         /* Changing the view position cancels the restoring. */
3298         /* FIXME: Changing back to the first line is not detected. */
3299         if (view->offset != 0 || view->lineno != 0) {
3300                 view->p_restore = FALSE;
3301                 return FALSE;
3302         }
3304         if (goto_view_line(view, view->p_offset, view->p_lineno) &&
3305             view_is_displayed(view))
3306                 werase(view->win);
3308         view->yoffset = view->p_yoffset;
3309         view->p_restore = FALSE;
3311         return TRUE;
3314 static void
3315 end_update(struct view *view, bool force)
3317         if (!view->pipe)
3318                 return;
3319         while (!view->ops->read(view, NULL))
3320                 if (!force)
3321                         return;
3322         if (force)
3323                 io_kill(view->pipe);
3324         io_done(view->pipe);
3325         view->pipe = NULL;
3328 static void
3329 setup_update(struct view *view, const char *vid)
3331         reset_view(view);
3332         string_copy_rev(view->vid, vid);
3333         view->pipe = &view->io;
3334         view->start_time = time(NULL);
3337 static bool
3338 prepare_io(struct view *view, const char *dir, const char *argv[], bool replace)
3340         view->dir = dir;
3341         return format_argv(&view->argv, argv, replace, !view->prev);
3344 static bool
3345 prepare_update(struct view *view, const char *argv[], const char *dir)
3347         if (view->pipe)
3348                 end_update(view, TRUE);
3349         return prepare_io(view, dir, argv, FALSE);
3352 static bool
3353 start_update(struct view *view, const char **argv, const char *dir)
3355         if (view->pipe)
3356                 io_done(view->pipe);
3357         return prepare_io(view, dir, argv, FALSE) &&
3358                io_run(&view->io, IO_RD, dir, view->argv);
3361 static bool
3362 prepare_update_file(struct view *view, const char *name)
3364         if (view->pipe)
3365                 end_update(view, TRUE);
3366         argv_free(view->argv);
3367         return io_open(&view->io, "%s/%s", opt_cdup[0] ? opt_cdup : ".", name);
3370 static bool
3371 begin_update(struct view *view, bool refresh)
3373         if (view->pipe)
3374                 end_update(view, TRUE);
3376         if (!refresh) {
3377                 if (view->ops->prepare) {
3378                         if (!view->ops->prepare(view))
3379                                 return FALSE;
3380                 } else if (!prepare_io(view, NULL, view->ops->argv, TRUE)) {
3381                         return FALSE;
3382                 }
3384                 /* Put the current ref_* value to the view title ref
3385                  * member. This is needed by the blob view. Most other
3386                  * views sets it automatically after loading because the
3387                  * first line is a commit line. */
3388                 string_copy_rev(view->ref, view->id);
3389         }
3391         if (view->argv && view->argv[0] &&
3392             !io_run(&view->io, IO_RD, view->dir, view->argv))
3393                 return FALSE;
3395         setup_update(view, view->id);
3397         return TRUE;
3400 static bool
3401 update_view(struct view *view)
3403         char out_buffer[BUFSIZ * 2];
3404         char *line;
3405         /* Clear the view and redraw everything since the tree sorting
3406          * might have rearranged things. */
3407         bool redraw = view->lines == 0;
3408         bool can_read = TRUE;
3410         if (!view->pipe)
3411                 return TRUE;
3413         if (!io_can_read(view->pipe)) {
3414                 if (view->lines == 0 && view_is_displayed(view)) {
3415                         time_t secs = time(NULL) - view->start_time;
3417                         if (secs > 1 && secs > view->update_secs) {
3418                                 if (view->update_secs == 0)
3419                                         redraw_view(view);
3420                                 update_view_title(view);
3421                                 view->update_secs = secs;
3422                         }
3423                 }
3424                 return TRUE;
3425         }
3427         for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
3428                 if (opt_iconv_in != ICONV_NONE) {
3429                         ICONV_CONST char *inbuf = line;
3430                         size_t inlen = strlen(line) + 1;
3432                         char *outbuf = out_buffer;
3433                         size_t outlen = sizeof(out_buffer);
3435                         size_t ret;
3437                         ret = iconv(opt_iconv_in, &inbuf, &inlen, &outbuf, &outlen);
3438                         if (ret != (size_t) -1)
3439                                 line = out_buffer;
3440                 }
3442                 if (!view->ops->read(view, line)) {
3443                         report("Allocation failure");
3444                         end_update(view, TRUE);
3445                         return FALSE;
3446                 }
3447         }
3449         {
3450                 unsigned long lines = view->lines;
3451                 int digits;
3453                 for (digits = 0; lines; digits++)
3454                         lines /= 10;
3456                 /* Keep the displayed view in sync with line number scaling. */
3457                 if (digits != view->digits) {
3458                         view->digits = digits;
3459                         if (opt_line_number || view->type == VIEW_BLAME)
3460                                 redraw = TRUE;
3461                 }
3462         }
3464         if (io_error(view->pipe)) {
3465                 report("Failed to read: %s", io_strerror(view->pipe));
3466                 end_update(view, TRUE);
3468         } else if (io_eof(view->pipe)) {
3469                 if (view_is_displayed(view))
3470                         report("");
3471                 end_update(view, FALSE);
3472         }
3474         if (restore_view_position(view))
3475                 redraw = TRUE;
3477         if (!view_is_displayed(view))
3478                 return TRUE;
3480         if (redraw)
3481                 redraw_view_from(view, 0);
3482         else
3483                 redraw_view_dirty(view);
3485         /* Update the title _after_ the redraw so that if the redraw picks up a
3486          * commit reference in view->ref it'll be available here. */
3487         update_view_title(view);
3488         return TRUE;
3491 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
3493 static struct line *
3494 add_line_data(struct view *view, void *data, enum line_type type)
3496         struct line *line;
3498         if (!realloc_lines(&view->line, view->lines, 1))
3499                 return NULL;
3501         line = &view->line[view->lines++];
3502         memset(line, 0, sizeof(*line));
3503         line->type = type;
3504         line->data = data;
3505         line->dirty = 1;
3507         return line;
3510 static struct line *
3511 add_line_text(struct view *view, const char *text, enum line_type type)
3513         char *data = text ? strdup(text) : NULL;
3515         return data ? add_line_data(view, data, type) : NULL;
3518 static struct line *
3519 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
3521         char buf[SIZEOF_STR];
3522         va_list args;
3524         va_start(args, fmt);
3525         if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
3526                 buf[0] = 0;
3527         va_end(args);
3529         return buf[0] ? add_line_text(view, buf, type) : NULL;
3532 /*
3533  * View opening
3534  */
3536 enum open_flags {
3537         OPEN_DEFAULT = 0,       /* Use default view switching. */
3538         OPEN_SPLIT = 1,         /* Split current view. */
3539         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
3540         OPEN_REFRESH = 16,      /* Refresh view using previous command. */
3541         OPEN_PREPARED = 32,     /* Open already prepared command. */
3542 };
3544 static void
3545 open_view(struct view *prev, enum request request, enum open_flags flags)
3547         bool split = !!(flags & OPEN_SPLIT);
3548         bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
3549         bool nomaximize = !!(flags & OPEN_REFRESH);
3550         struct view *view = VIEW(request);
3551         int nviews = displayed_views();
3552         struct view *base_view = display[0];
3554         if (view == prev && nviews == 1 && !reload) {
3555                 report("Already in %s view", view->name);
3556                 return;
3557         }
3559         if (view->git_dir && !opt_git_dir[0]) {
3560                 report("The %s view is disabled in pager view", view->name);
3561                 return;
3562         }
3564         if (split) {
3565                 display[1] = view;
3566                 current_view = 1;
3567                 view->parent = prev;
3568         } else if (!nomaximize) {
3569                 /* Maximize the current view. */
3570                 memset(display, 0, sizeof(display));
3571                 current_view = 0;
3572                 display[current_view] = view;
3573         }
3575         /* No prev signals that this is the first loaded view. */
3576         if (prev && view != prev) {
3577                 view->prev = prev;
3578         }
3580         /* Resize the view when switching between split- and full-screen,
3581          * or when switching between two different full-screen views. */
3582         if (nviews != displayed_views() ||
3583             (nviews == 1 && base_view != display[0]))
3584                 resize_display();
3586         if (view->ops->open) {
3587                 if (view->pipe)
3588                         end_update(view, TRUE);
3589                 if (!view->ops->open(view)) {
3590                         report("Failed to load %s view", view->name);
3591                         return;
3592                 }
3593                 restore_view_position(view);
3595         } else if ((reload || strcmp(view->vid, view->id)) &&
3596                    !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3597                 report("Failed to load %s view", view->name);
3598                 return;
3599         }
3601         if (split && prev->lineno - prev->offset >= prev->height) {
3602                 /* Take the title line into account. */
3603                 int lines = prev->lineno - prev->offset - prev->height + 1;
3605                 /* Scroll the view that was split if the current line is
3606                  * outside the new limited view. */
3607                 do_scroll_view(prev, lines);
3608         }
3610         if (prev && view != prev && split && view_is_displayed(prev)) {
3611                 /* "Blur" the previous view. */
3612                 update_view_title(prev);
3613         }
3615         if (view->pipe && view->lines == 0) {
3616                 /* Clear the old view and let the incremental updating refill
3617                  * the screen. */
3618                 werase(view->win);
3619                 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3620                 report("");
3621         } else if (view_is_displayed(view)) {
3622                 redraw_view(view);
3623                 report("");
3624         }
3627 static void
3628 open_external_viewer(const char *argv[], const char *dir)
3630         def_prog_mode();           /* save current tty modes */
3631         endwin();                  /* restore original tty modes */
3632         io_run_fg(argv, dir);
3633         fprintf(stderr, "Press Enter to continue");
3634         getc(opt_tty);
3635         reset_prog_mode();
3636         redraw_display(TRUE);
3639 static void
3640 open_mergetool(const char *file)
3642         const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3644         open_external_viewer(mergetool_argv, opt_cdup);
3647 static void
3648 open_editor(const char *file)
3650         const char *editor_argv[] = { "vi", file, NULL };
3651         const char *editor;
3653         editor = getenv("GIT_EDITOR");
3654         if (!editor && *opt_editor)
3655                 editor = opt_editor;
3656         if (!editor)
3657                 editor = getenv("VISUAL");
3658         if (!editor)
3659                 editor = getenv("EDITOR");
3660         if (!editor)
3661                 editor = "vi";
3663         editor_argv[0] = editor;
3664         open_external_viewer(editor_argv, opt_cdup);
3667 static void
3668 open_run_request(enum request request)
3670         struct run_request *req = get_run_request(request);
3671         const char **argv = NULL;
3673         if (!req) {
3674                 report("Unknown run request");
3675                 return;
3676         }
3678         if (format_argv(&argv, req->argv, TRUE, FALSE))
3679                 open_external_viewer(argv, NULL);
3680         if (argv)
3681                 argv_free(argv);
3682         free(argv);
3685 /*
3686  * User request switch noodle
3687  */
3689 static int
3690 view_driver(struct view *view, enum request request)
3692         int i;
3694         if (request == REQ_NONE)
3695                 return TRUE;
3697         if (request > REQ_NONE) {
3698                 open_run_request(request);
3699                 view_request(view, REQ_REFRESH);
3700                 return TRUE;
3701         }
3703         request = view_request(view, request);
3704         if (request == REQ_NONE)
3705                 return TRUE;
3707         switch (request) {
3708         case REQ_MOVE_UP:
3709         case REQ_MOVE_DOWN:
3710         case REQ_MOVE_PAGE_UP:
3711         case REQ_MOVE_PAGE_DOWN:
3712         case REQ_MOVE_FIRST_LINE:
3713         case REQ_MOVE_LAST_LINE:
3714                 move_view(view, request);
3715                 break;
3717         case REQ_SCROLL_FIRST_COL:
3718         case REQ_SCROLL_LEFT:
3719         case REQ_SCROLL_RIGHT:
3720         case REQ_SCROLL_LINE_DOWN:
3721         case REQ_SCROLL_LINE_UP:
3722         case REQ_SCROLL_PAGE_DOWN:
3723         case REQ_SCROLL_PAGE_UP:
3724                 scroll_view(view, request);
3725                 break;
3727         case REQ_VIEW_BLAME:
3728                 if (!opt_file[0]) {
3729                         report("No file chosen, press %s to open tree view",
3730                                get_key(view->keymap, REQ_VIEW_TREE));
3731                         break;
3732                 }
3733                 open_view(view, request, OPEN_DEFAULT);
3734                 break;
3736         case REQ_VIEW_BLOB:
3737                 if (!ref_blob[0]) {
3738                         report("No file chosen, press %s to open tree view",
3739                                get_key(view->keymap, REQ_VIEW_TREE));
3740                         break;
3741                 }
3742                 open_view(view, request, OPEN_DEFAULT);
3743                 break;
3745         case REQ_VIEW_PAGER:
3746                 if (view == NULL) {
3747                         if (!io_open(&VIEW(REQ_VIEW_PAGER)->io, ""))
3748                                 die("Failed to open stdin");
3749                         open_view(view, request, OPEN_PREPARED);
3750                         break;
3751                 }
3753                 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3754                         report("No pager content, press %s to run command from prompt",
3755                                get_key(view->keymap, REQ_PROMPT));
3756                         break;
3757                 }
3758                 open_view(view, request, OPEN_DEFAULT);
3759                 break;
3761         case REQ_VIEW_STAGE:
3762                 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3763                         report("No stage content, press %s to open the status view and choose file",
3764                                get_key(view->keymap, REQ_VIEW_STATUS));
3765                         break;
3766                 }
3767                 open_view(view, request, OPEN_DEFAULT);
3768                 break;
3770         case REQ_VIEW_STATUS:
3771                 if (opt_is_inside_work_tree == FALSE) {
3772                         report("The status view requires a working tree");
3773                         break;
3774                 }
3775                 open_view(view, request, OPEN_DEFAULT);
3776                 break;
3778         case REQ_VIEW_MAIN:
3779         case REQ_VIEW_DIFF:
3780         case REQ_VIEW_LOG:
3781         case REQ_VIEW_TREE:
3782         case REQ_VIEW_HELP:
3783         case REQ_VIEW_BRANCH:
3784                 open_view(view, request, OPEN_DEFAULT);
3785                 break;
3787         case REQ_NEXT:
3788         case REQ_PREVIOUS:
3789                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3791                 if (view->parent) {
3792                         int line;
3794                         view = view->parent;
3795                         line = view->lineno;
3796                         move_view(view, request);
3797                         if (view_is_displayed(view))
3798                                 update_view_title(view);
3799                         if (line != view->lineno)
3800                                 view_request(view, REQ_ENTER);
3801                 } else {
3802                         move_view(view, request);
3803                 }
3804                 break;
3806         case REQ_VIEW_NEXT:
3807         {
3808                 int nviews = displayed_views();
3809                 int next_view = (current_view + 1) % nviews;
3811                 if (next_view == current_view) {
3812                         report("Only one view is displayed");
3813                         break;
3814                 }
3816                 current_view = next_view;
3817                 /* Blur out the title of the previous view. */
3818                 update_view_title(view);
3819                 report("");
3820                 break;
3821         }
3822         case REQ_REFRESH:
3823                 report("Refreshing is not yet supported for the %s view", view->name);
3824                 break;
3826         case REQ_MAXIMIZE:
3827                 if (displayed_views() == 2)
3828                         maximize_view(view);
3829                 break;
3831         case REQ_OPTIONS:
3832         case REQ_TOGGLE_LINENO:
3833         case REQ_TOGGLE_DATE:
3834         case REQ_TOGGLE_AUTHOR:
3835         case REQ_TOGGLE_REV_GRAPH:
3836         case REQ_TOGGLE_REFS:
3837                 toggle_option(request);
3838                 break;
3840         case REQ_TOGGLE_SORT_FIELD:
3841         case REQ_TOGGLE_SORT_ORDER:
3842                 report("Sorting is not yet supported for the %s view", view->name);
3843                 break;
3845         case REQ_SEARCH:
3846         case REQ_SEARCH_BACK:
3847                 search_view(view, request);
3848                 break;
3850         case REQ_FIND_NEXT:
3851         case REQ_FIND_PREV:
3852                 find_next(view, request);
3853                 break;
3855         case REQ_STOP_LOADING:
3856                 foreach_view(view, i) {
3857                         if (view->pipe)
3858                                 report("Stopped loading the %s view", view->name),
3859                         end_update(view, TRUE);
3860                 }
3861                 break;
3863         case REQ_SHOW_VERSION:
3864                 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3865                 return TRUE;
3867         case REQ_SCREEN_REDRAW:
3868                 redraw_display(TRUE);
3869                 break;
3871         case REQ_EDIT:
3872                 report("Nothing to edit");
3873                 break;
3875         case REQ_ENTER:
3876                 report("Nothing to enter");
3877                 break;
3879         case REQ_VIEW_CLOSE:
3880                 /* XXX: Mark closed views by letting view->prev point to the
3881                  * view itself. Parents to closed view should never be
3882                  * followed. */
3883                 if (view->prev && view->prev != view) {
3884                         maximize_view(view->prev);
3885                         view->prev = view;
3886                         break;
3887                 }
3888                 /* Fall-through */
3889         case REQ_QUIT:
3890                 return FALSE;
3892         default:
3893                 report("Unknown key, press %s for help",
3894                        get_key(view->keymap, REQ_VIEW_HELP));
3895                 return TRUE;
3896         }
3898         return TRUE;
3902 /*
3903  * View backend utilities
3904  */
3906 enum sort_field {
3907         ORDERBY_NAME,
3908         ORDERBY_DATE,
3909         ORDERBY_AUTHOR,
3910 };
3912 struct sort_state {
3913         const enum sort_field *fields;
3914         size_t size, current;
3915         bool reverse;
3916 };
3918 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3919 #define get_sort_field(state) ((state).fields[(state).current])
3920 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3922 static void
3923 sort_view(struct view *view, enum request request, struct sort_state *state,
3924           int (*compare)(const void *, const void *))
3926         switch (request) {
3927         case REQ_TOGGLE_SORT_FIELD:
3928                 state->current = (state->current + 1) % state->size;
3929                 break;
3931         case REQ_TOGGLE_SORT_ORDER:
3932                 state->reverse = !state->reverse;
3933                 break;
3934         default:
3935                 die("Not a sort request");
3936         }
3938         qsort(view->line, view->lines, sizeof(*view->line), compare);
3939         redraw_view(view);
3942 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3944 /* Small author cache to reduce memory consumption. It uses binary
3945  * search to lookup or find place to position new entries. No entries
3946  * are ever freed. */
3947 static const char *
3948 get_author(const char *name)
3950         static const char **authors;
3951         static size_t authors_size;
3952         int from = 0, to = authors_size - 1;
3954         while (from <= to) {
3955                 size_t pos = (to + from) / 2;
3956                 int cmp = strcmp(name, authors[pos]);
3958                 if (!cmp)
3959                         return authors[pos];
3961                 if (cmp < 0)
3962                         to = pos - 1;
3963                 else
3964                         from = pos + 1;
3965         }
3967         if (!realloc_authors(&authors, authors_size, 1))
3968                 return NULL;
3969         name = strdup(name);
3970         if (!name)
3971                 return NULL;
3973         memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3974         authors[from] = name;
3975         authors_size++;
3977         return name;
3980 static void
3981 parse_timesec(struct time *time, const char *sec)
3983         time->sec = (time_t) atol(sec);
3986 static void
3987 parse_timezone(struct time *time, const char *zone)
3989         long tz;
3991         tz  = ('0' - zone[1]) * 60 * 60 * 10;
3992         tz += ('0' - zone[2]) * 60 * 60;
3993         tz += ('0' - zone[3]) * 60 * 10;
3994         tz += ('0' - zone[4]) * 60;
3996         if (zone[0] == '-')
3997                 tz = -tz;
3999         time->tz = tz;
4000         time->sec -= tz;
4003 /* Parse author lines where the name may be empty:
4004  *      author  <email@address.tld> 1138474660 +0100
4005  */
4006 static void
4007 parse_author_line(char *ident, const char **author, struct time *time)
4009         char *nameend = strchr(ident, '<');
4010         char *emailend = strchr(ident, '>');
4012         if (nameend && emailend)
4013                 *nameend = *emailend = 0;
4014         ident = chomp_string(ident);
4015         if (!*ident) {
4016                 if (nameend)
4017                         ident = chomp_string(nameend + 1);
4018                 if (!*ident)
4019                         ident = "Unknown";
4020         }
4022         *author = get_author(ident);
4024         /* Parse epoch and timezone */
4025         if (emailend && emailend[1] == ' ') {
4026                 char *secs = emailend + 2;
4027                 char *zone = strchr(secs, ' ');
4029                 parse_timesec(time, secs);
4031                 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
4032                         parse_timezone(time, zone + 1);
4033         }
4036 /*
4037  * Pager backend
4038  */
4040 static bool
4041 pager_draw(struct view *view, struct line *line, unsigned int lineno)
4043         if (opt_line_number && draw_lineno(view, lineno))
4044                 return TRUE;
4046         draw_text(view, line->type, line->data);
4047         return TRUE;
4050 static bool
4051 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
4053         const char *describe_argv[] = { "git", "describe", commit_id, NULL };
4054         char ref[SIZEOF_STR];
4056         if (!io_run_buf(describe_argv, ref, sizeof(ref)) || !*ref)
4057                 return TRUE;
4059         /* This is the only fatal call, since it can "corrupt" the buffer. */
4060         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
4061                 return FALSE;
4063         return TRUE;
4066 static void
4067 add_pager_refs(struct view *view, struct line *line)
4069         char buf[SIZEOF_STR];
4070         char *commit_id = (char *)line->data + STRING_SIZE("commit ");
4071         struct ref_list *list;
4072         size_t bufpos = 0, i;
4073         const char *sep = "Refs: ";
4074         bool is_tag = FALSE;
4076         assert(line->type == LINE_COMMIT);
4078         list = get_ref_list(commit_id);
4079         if (!list) {
4080                 if (view->type == VIEW_DIFF)
4081                         goto try_add_describe_ref;
4082                 return;
4083         }
4085         for (i = 0; i < list->size; i++) {
4086                 struct ref *ref = list->refs[i];
4087                 const char *fmt = ref->tag    ? "%s[%s]" :
4088                                   ref->remote ? "%s<%s>" : "%s%s";
4090                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
4091                         return;
4092                 sep = ", ";
4093                 if (ref->tag)
4094                         is_tag = TRUE;
4095         }
4097         if (!is_tag && view->type == VIEW_DIFF) {
4098 try_add_describe_ref:
4099                 /* Add <tag>-g<commit_id> "fake" reference. */
4100                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
4101                         return;
4102         }
4104         if (bufpos == 0)
4105                 return;
4107         add_line_text(view, buf, LINE_PP_REFS);
4110 static bool
4111 pager_read(struct view *view, char *data)
4113         struct line *line;
4115         if (!data)
4116                 return TRUE;
4118         line = add_line_text(view, data, get_line_type(data));
4119         if (!line)
4120                 return FALSE;
4122         if (line->type == LINE_COMMIT &&
4123             (view->type == VIEW_DIFF ||
4124              view->type == VIEW_LOG))
4125                 add_pager_refs(view, line);
4127         return TRUE;
4130 static enum request
4131 pager_request(struct view *view, enum request request, struct line *line)
4133         int split = 0;
4135         if (request != REQ_ENTER)
4136                 return request;
4138         if (line->type == LINE_COMMIT &&
4139            (view->type == VIEW_LOG ||
4140             view->type == VIEW_PAGER)) {
4141                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
4142                 split = 1;
4143         }
4145         /* Always scroll the view even if it was split. That way
4146          * you can use Enter to scroll through the log view and
4147          * split open each commit diff. */
4148         scroll_view(view, REQ_SCROLL_LINE_DOWN);
4150         /* FIXME: A minor workaround. Scrolling the view will call report("")
4151          * but if we are scrolling a non-current view this won't properly
4152          * update the view title. */
4153         if (split)
4154                 update_view_title(view);
4156         return REQ_NONE;
4159 static bool
4160 pager_grep(struct view *view, struct line *line)
4162         const char *text[] = { line->data, NULL };
4164         return grep_text(view, text);
4167 static void
4168 pager_select(struct view *view, struct line *line)
4170         if (line->type == LINE_COMMIT) {
4171                 char *text = (char *)line->data + STRING_SIZE("commit ");
4173                 if (view->type != VIEW_PAGER)
4174                         string_copy_rev(view->ref, text);
4175                 string_copy_rev(ref_commit, text);
4176         }
4179 static struct view_ops pager_ops = {
4180         "line",
4181         NULL,
4182         NULL,
4183         pager_read,
4184         pager_draw,
4185         pager_request,
4186         pager_grep,
4187         pager_select,
4188 };
4190 static const char *log_argv[SIZEOF_ARG] = {
4191         "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
4192 };
4194 static enum request
4195 log_request(struct view *view, enum request request, struct line *line)
4197         switch (request) {
4198         case REQ_REFRESH:
4199                 load_refs();
4200                 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
4201                 return REQ_NONE;
4202         default:
4203                 return pager_request(view, request, line);
4204         }
4207 static struct view_ops log_ops = {
4208         "line",
4209         log_argv,
4210         NULL,
4211         pager_read,
4212         pager_draw,
4213         log_request,
4214         pager_grep,
4215         pager_select,
4216 };
4218 static const char *diff_argv[SIZEOF_ARG] = {
4219         "git", "show", "--pretty=fuller", "--no-color", "--root",
4220                 "--patch-with-stat", "--find-copies-harder", "-C",
4221                 "%(diffargs)", "%(commit)", "--", "%(fileargs)", NULL
4222 };
4224 static bool
4225 diff_read(struct view *view, char *data)
4227         if (!data) {
4228                 /* Fall back to retry if no diff will be shown. */
4229                 if (view->lines == 0 && opt_file_argv) {
4230                         int pos = argv_size(view->argv)
4231                                 - argv_size(opt_file_argv) - 1;
4233                         if (pos > 0 && !strcmp(view->argv[pos], "--")) {
4234                                 for (; view->argv[pos]; pos++) {
4235                                         free((void *) view->argv[pos]);
4236                                         view->argv[pos] = NULL;
4237                                 }
4239                                 if (view->pipe)
4240                                         io_done(view->pipe);
4241                                 if (io_run(&view->io, IO_RD, view->dir, view->argv))
4242                                         return FALSE;
4243                         }
4244                 }
4245                 return TRUE;
4246         }
4248         return pager_read(view, data);
4251 static struct view_ops diff_ops = {
4252         "line",
4253         diff_argv,
4254         NULL,
4255         diff_read,
4256         pager_draw,
4257         pager_request,
4258         pager_grep,
4259         pager_select,
4260 };
4262 /*
4263  * Help backend
4264  */
4266 static bool help_keymap_hidden[ARRAY_SIZE(keymap_table)];
4268 static bool
4269 help_open_keymap_title(struct view *view, enum keymap keymap)
4271         struct line *line;
4273         line = add_line_format(view, LINE_HELP_KEYMAP, "[%c] %s bindings",
4274                                help_keymap_hidden[keymap] ? '+' : '-',
4275                                enum_name(keymap_table[keymap]));
4276         if (line)
4277                 line->other = keymap;
4279         return help_keymap_hidden[keymap];
4282 static void
4283 help_open_keymap(struct view *view, enum keymap keymap)
4285         const char *group = NULL;
4286         char buf[SIZEOF_STR];
4287         size_t bufpos;
4288         bool add_title = TRUE;
4289         int i;
4291         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
4292                 const char *key = NULL;
4294                 if (req_info[i].request == REQ_NONE)
4295                         continue;
4297                 if (!req_info[i].request) {
4298                         group = req_info[i].help;
4299                         continue;
4300                 }
4302                 key = get_keys(keymap, req_info[i].request, TRUE);
4303                 if (!key || !*key)
4304                         continue;
4306                 if (add_title && help_open_keymap_title(view, keymap))
4307                         return;
4308                 add_title = FALSE;
4310                 if (group) {
4311                         add_line_text(view, group, LINE_HELP_GROUP);
4312                         group = NULL;
4313                 }
4315                 add_line_format(view, LINE_DEFAULT, "    %-25s %-20s %s", key,
4316                                 enum_name(req_info[i]), req_info[i].help);
4317         }
4319         group = "External commands:";
4321         for (i = 0; i < run_requests; i++) {
4322                 struct run_request *req = get_run_request(REQ_NONE + i + 1);
4323                 const char *key;
4324                 int argc;
4326                 if (!req || req->keymap != keymap)
4327                         continue;
4329                 key = get_key_name(req->key);
4330                 if (!*key)
4331                         key = "(no key defined)";
4333                 if (add_title && help_open_keymap_title(view, keymap))
4334                         return;
4335                 if (group) {
4336                         add_line_text(view, group, LINE_HELP_GROUP);
4337                         group = NULL;
4338                 }
4340                 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
4341                         if (!string_format_from(buf, &bufpos, "%s%s",
4342                                                 argc ? " " : "", req->argv[argc]))
4343                                 return;
4345                 add_line_format(view, LINE_DEFAULT, "    %-25s `%s`", key, buf);
4346         }
4349 static bool
4350 help_open(struct view *view)
4352         enum keymap keymap;
4354         reset_view(view);
4355         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
4356         add_line_text(view, "", LINE_DEFAULT);
4358         for (keymap = 0; keymap < ARRAY_SIZE(keymap_table); keymap++)
4359                 help_open_keymap(view, keymap);
4361         return TRUE;
4364 static enum request
4365 help_request(struct view *view, enum request request, struct line *line)
4367         switch (request) {
4368         case REQ_ENTER:
4369                 if (line->type == LINE_HELP_KEYMAP) {
4370                         help_keymap_hidden[line->other] =
4371                                 !help_keymap_hidden[line->other];
4372                         view->p_restore = TRUE;
4373                         open_view(view, REQ_VIEW_HELP, OPEN_REFRESH);
4374                 }
4376                 return REQ_NONE;
4377         default:
4378                 return pager_request(view, request, line);
4379         }
4382 static struct view_ops help_ops = {
4383         "line",
4384         NULL,
4385         help_open,
4386         NULL,
4387         pager_draw,
4388         help_request,
4389         pager_grep,
4390         pager_select,
4391 };
4394 /*
4395  * Tree backend
4396  */
4398 struct tree_stack_entry {
4399         struct tree_stack_entry *prev;  /* Entry below this in the stack */
4400         unsigned long lineno;           /* Line number to restore */
4401         char *name;                     /* Position of name in opt_path */
4402 };
4404 /* The top of the path stack. */
4405 static struct tree_stack_entry *tree_stack = NULL;
4406 unsigned long tree_lineno = 0;
4408 static void
4409 pop_tree_stack_entry(void)
4411         struct tree_stack_entry *entry = tree_stack;
4413         tree_lineno = entry->lineno;
4414         entry->name[0] = 0;
4415         tree_stack = entry->prev;
4416         free(entry);
4419 static void
4420 push_tree_stack_entry(const char *name, unsigned long lineno)
4422         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
4423         size_t pathlen = strlen(opt_path);
4425         if (!entry)
4426                 return;
4428         entry->prev = tree_stack;
4429         entry->name = opt_path + pathlen;
4430         tree_stack = entry;
4432         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
4433                 pop_tree_stack_entry();
4434                 return;
4435         }
4437         /* Move the current line to the first tree entry. */
4438         tree_lineno = 1;
4439         entry->lineno = lineno;
4442 /* Parse output from git-ls-tree(1):
4443  *
4444  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4445  */
4447 #define SIZEOF_TREE_ATTR \
4448         STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4450 #define SIZEOF_TREE_MODE \
4451         STRING_SIZE("100644 ")
4453 #define TREE_ID_OFFSET \
4454         STRING_SIZE("100644 blob ")
4456 struct tree_entry {
4457         char id[SIZEOF_REV];
4458         mode_t mode;
4459         struct time time;               /* Date from the author ident. */
4460         const char *author;             /* Author of the commit. */
4461         char name[1];
4462 };
4464 static const char *
4465 tree_path(const struct line *line)
4467         return ((struct tree_entry *) line->data)->name;
4470 static int
4471 tree_compare_entry(const struct line *line1, const struct line *line2)
4473         if (line1->type != line2->type)
4474                 return line1->type == LINE_TREE_DIR ? -1 : 1;
4475         return strcmp(tree_path(line1), tree_path(line2));
4478 static const enum sort_field tree_sort_fields[] = {
4479         ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4480 };
4481 static struct sort_state tree_sort_state = SORT_STATE(tree_sort_fields);
4483 static int
4484 tree_compare(const void *l1, const void *l2)
4486         const struct line *line1 = (const struct line *) l1;
4487         const struct line *line2 = (const struct line *) l2;
4488         const struct tree_entry *entry1 = ((const struct line *) l1)->data;
4489         const struct tree_entry *entry2 = ((const struct line *) l2)->data;
4491         if (line1->type == LINE_TREE_HEAD)
4492                 return -1;
4493         if (line2->type == LINE_TREE_HEAD)
4494                 return 1;
4496         switch (get_sort_field(tree_sort_state)) {
4497         case ORDERBY_DATE:
4498                 return sort_order(tree_sort_state, timecmp(&entry1->time, &entry2->time));
4500         case ORDERBY_AUTHOR:
4501                 return sort_order(tree_sort_state, strcmp(entry1->author, entry2->author));
4503         case ORDERBY_NAME:
4504         default:
4505                 return sort_order(tree_sort_state, tree_compare_entry(line1, line2));
4506         }
4510 static struct line *
4511 tree_entry(struct view *view, enum line_type type, const char *path,
4512            const char *mode, const char *id)
4514         struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
4515         struct line *line = entry ? add_line_data(view, entry, type) : NULL;
4517         if (!entry || !line) {
4518                 free(entry);
4519                 return NULL;
4520         }
4522         strncpy(entry->name, path, strlen(path));
4523         if (mode)
4524                 entry->mode = strtoul(mode, NULL, 8);
4525         if (id)
4526                 string_copy_rev(entry->id, id);
4528         return line;
4531 static bool
4532 tree_read_date(struct view *view, char *text, bool *read_date)
4534         static const char *author_name;
4535         static struct time author_time;
4537         if (!text && *read_date) {
4538                 *read_date = FALSE;
4539                 return TRUE;
4541         } else if (!text) {
4542                 char *path = *opt_path ? opt_path : ".";
4543                 /* Find next entry to process */
4544                 const char *log_file[] = {
4545                         "git", "log", "--no-color", "--pretty=raw",
4546                                 "--cc", "--raw", view->id, "--", path, NULL
4547                 };
4549                 if (!view->lines) {
4550                         tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
4551                         report("Tree is empty");
4552                         return TRUE;
4553                 }
4555                 if (!start_update(view, log_file, opt_cdup)) {
4556                         report("Failed to load tree data");
4557                         return TRUE;
4558                 }
4560                 *read_date = TRUE;
4561                 return FALSE;
4563         } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
4564                 parse_author_line(text + STRING_SIZE("author "),
4565                                   &author_name, &author_time);
4567         } else if (*text == ':') {
4568                 char *pos;
4569                 size_t annotated = 1;
4570                 size_t i;
4572                 pos = strchr(text, '\t');
4573                 if (!pos)
4574                         return TRUE;
4575                 text = pos + 1;
4576                 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
4577                         text += strlen(opt_path);
4578                 pos = strchr(text, '/');
4579                 if (pos)
4580                         *pos = 0;
4582                 for (i = 1; i < view->lines; i++) {
4583                         struct line *line = &view->line[i];
4584                         struct tree_entry *entry = line->data;
4586                         annotated += !!entry->author;
4587                         if (entry->author || strcmp(entry->name, text))
4588                                 continue;
4590                         entry->author = author_name;
4591                         entry->time = author_time;
4592                         line->dirty = 1;
4593                         break;
4594                 }
4596                 if (annotated == view->lines)
4597                         io_kill(view->pipe);
4598         }
4599         return TRUE;
4602 static bool
4603 tree_read(struct view *view, char *text)
4605         static bool read_date = FALSE;
4606         struct tree_entry *data;
4607         struct line *entry, *line;
4608         enum line_type type;
4609         size_t textlen = text ? strlen(text) : 0;
4610         char *path = text + SIZEOF_TREE_ATTR;
4612         if (read_date || !text)
4613                 return tree_read_date(view, text, &read_date);
4615         if (textlen <= SIZEOF_TREE_ATTR)
4616                 return FALSE;
4617         if (view->lines == 0 &&
4618             !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
4619                 return FALSE;
4621         /* Strip the path part ... */
4622         if (*opt_path) {
4623                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
4624                 size_t striplen = strlen(opt_path);
4626                 if (pathlen > striplen)
4627                         memmove(path, path + striplen,
4628                                 pathlen - striplen + 1);
4630                 /* Insert "link" to parent directory. */
4631                 if (view->lines == 1 &&
4632                     !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
4633                         return FALSE;
4634         }
4636         type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
4637         entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
4638         if (!entry)
4639                 return FALSE;
4640         data = entry->data;
4642         /* Skip "Directory ..." and ".." line. */
4643         for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
4644                 if (tree_compare_entry(line, entry) <= 0)
4645                         continue;
4647                 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4649                 line->data = data;
4650                 line->type = type;
4651                 for (; line <= entry; line++)
4652                         line->dirty = line->cleareol = 1;
4653                 return TRUE;
4654         }
4656         if (tree_lineno > view->lineno) {
4657                 view->lineno = tree_lineno;
4658                 tree_lineno = 0;
4659         }
4661         return TRUE;
4664 static bool
4665 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4667         struct tree_entry *entry = line->data;
4669         if (line->type == LINE_TREE_HEAD) {
4670                 if (draw_text(view, line->type, "Directory path /"))
4671                         return TRUE;
4672         } else {
4673                 if (draw_mode(view, entry->mode))
4674                         return TRUE;
4676                 if (opt_author && draw_author(view, entry->author))
4677                         return TRUE;
4679                 if (opt_date && draw_date(view, &entry->time))
4680                         return TRUE;
4681         }
4683         draw_text(view, line->type, entry->name);
4684         return TRUE;
4687 static void
4688 open_blob_editor(const char *id)
4690         const char *blob_argv[] = { "git", "cat-file", "blob", id, NULL };
4691         char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4692         int fd = mkstemp(file);
4694         if (fd == -1)
4695                 report("Failed to create temporary file");
4696         else if (!io_run_append(blob_argv, fd))
4697                 report("Failed to save blob data to file");
4698         else
4699                 open_editor(file);
4700         if (fd != -1)
4701                 unlink(file);
4704 static enum request
4705 tree_request(struct view *view, enum request request, struct line *line)
4707         enum open_flags flags;
4708         struct tree_entry *entry = line->data;
4710         switch (request) {
4711         case REQ_VIEW_BLAME:
4712                 if (line->type != LINE_TREE_FILE) {
4713                         report("Blame only supported for files");
4714                         return REQ_NONE;
4715                 }
4717                 string_copy(opt_ref, view->vid);
4718                 return request;
4720         case REQ_EDIT:
4721                 if (line->type != LINE_TREE_FILE) {
4722                         report("Edit only supported for files");
4723                 } else if (!is_head_commit(view->vid)) {
4724                         open_blob_editor(entry->id);
4725                 } else {
4726                         open_editor(opt_file);
4727                 }
4728                 return REQ_NONE;
4730         case REQ_TOGGLE_SORT_FIELD:
4731         case REQ_TOGGLE_SORT_ORDER:
4732                 sort_view(view, request, &tree_sort_state, tree_compare);
4733                 return REQ_NONE;
4735         case REQ_PARENT:
4736                 if (!*opt_path) {
4737                         /* quit view if at top of tree */
4738                         return REQ_VIEW_CLOSE;
4739                 }
4740                 /* fake 'cd  ..' */
4741                 line = &view->line[1];
4742                 break;
4744         case REQ_ENTER:
4745                 break;
4747         default:
4748                 return request;
4749         }
4751         /* Cleanup the stack if the tree view is at a different tree. */
4752         while (!*opt_path && tree_stack)
4753                 pop_tree_stack_entry();
4755         switch (line->type) {
4756         case LINE_TREE_DIR:
4757                 /* Depending on whether it is a subdirectory or parent link
4758                  * mangle the path buffer. */
4759                 if (line == &view->line[1] && *opt_path) {
4760                         pop_tree_stack_entry();
4762                 } else {
4763                         const char *basename = tree_path(line);
4765                         push_tree_stack_entry(basename, view->lineno);
4766                 }
4768                 /* Trees and subtrees share the same ID, so they are not not
4769                  * unique like blobs. */
4770                 flags = OPEN_RELOAD;
4771                 request = REQ_VIEW_TREE;
4772                 break;
4774         case LINE_TREE_FILE:
4775                 flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
4776                 request = REQ_VIEW_BLOB;
4777                 break;
4779         default:
4780                 return REQ_NONE;
4781         }
4783         open_view(view, request, flags);
4784         if (request == REQ_VIEW_TREE)
4785                 view->lineno = tree_lineno;
4787         return REQ_NONE;
4790 static bool
4791 tree_grep(struct view *view, struct line *line)
4793         struct tree_entry *entry = line->data;
4794         const char *text[] = {
4795                 entry->name,
4796                 opt_author ? entry->author : "",
4797                 mkdate(&entry->time, opt_date),
4798                 NULL
4799         };
4801         return grep_text(view, text);
4804 static void
4805 tree_select(struct view *view, struct line *line)
4807         struct tree_entry *entry = line->data;
4809         if (line->type == LINE_TREE_FILE) {
4810                 string_copy_rev(ref_blob, entry->id);
4811                 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4813         } else if (line->type != LINE_TREE_DIR) {
4814                 return;
4815         }
4817         string_copy_rev(view->ref, entry->id);
4820 static bool
4821 tree_prepare(struct view *view)
4823         if (view->lines == 0 && opt_prefix[0]) {
4824                 char *pos = opt_prefix;
4826                 while (pos && *pos) {
4827                         char *end = strchr(pos, '/');
4829                         if (end)
4830                                 *end = 0;
4831                         push_tree_stack_entry(pos, 0);
4832                         pos = end;
4833                         if (end) {
4834                                 *end = '/';
4835                                 pos++;
4836                         }
4837                 }
4839         } else if (strcmp(view->vid, view->id)) {
4840                 opt_path[0] = 0;
4841         }
4843         return prepare_io(view, opt_cdup, view->ops->argv, TRUE);
4846 static const char *tree_argv[SIZEOF_ARG] = {
4847         "git", "ls-tree", "%(commit)", "%(directory)", NULL
4848 };
4850 static struct view_ops tree_ops = {
4851         "file",
4852         tree_argv,
4853         NULL,
4854         tree_read,
4855         tree_draw,
4856         tree_request,
4857         tree_grep,
4858         tree_select,
4859         tree_prepare,
4860 };
4862 static bool
4863 blob_read(struct view *view, char *line)
4865         if (!line)
4866                 return TRUE;
4867         return add_line_text(view, line, LINE_DEFAULT) != NULL;
4870 static enum request
4871 blob_request(struct view *view, enum request request, struct line *line)
4873         switch (request) {
4874         case REQ_EDIT:
4875                 open_blob_editor(view->vid);
4876                 return REQ_NONE;
4877         default:
4878                 return pager_request(view, request, line);
4879         }
4882 static const char *blob_argv[SIZEOF_ARG] = {
4883         "git", "cat-file", "blob", "%(blob)", NULL
4884 };
4886 static struct view_ops blob_ops = {
4887         "line",
4888         blob_argv,
4889         NULL,
4890         blob_read,
4891         pager_draw,
4892         blob_request,
4893         pager_grep,
4894         pager_select,
4895 };
4897 /*
4898  * Blame backend
4899  *
4900  * Loading the blame view is a two phase job:
4901  *
4902  *  1. File content is read either using opt_file from the
4903  *     filesystem or using git-cat-file.
4904  *  2. Then blame information is incrementally added by
4905  *     reading output from git-blame.
4906  */
4908 struct blame_commit {
4909         char id[SIZEOF_REV];            /* SHA1 ID. */
4910         char title[128];                /* First line of the commit message. */
4911         const char *author;             /* Author of the commit. */
4912         struct time time;               /* Date from the author ident. */
4913         char filename[128];             /* Name of file. */
4914         char parent_id[SIZEOF_REV];     /* Parent/previous SHA1 ID. */
4915         char parent_filename[128];      /* Parent/previous name of file. */
4916 };
4918 struct blame {
4919         struct blame_commit *commit;
4920         unsigned long lineno;
4921         char text[1];
4922 };
4924 static bool
4925 blame_open(struct view *view)
4927         char path[SIZEOF_STR];
4928         size_t i;
4930         if (!view->prev && *opt_prefix) {
4931                 string_copy(path, opt_file);
4932                 if (!string_format(opt_file, "%s%s", opt_prefix, path))
4933                         return FALSE;
4934         }
4936         if (*opt_ref || !io_open(&view->io, "%s%s", opt_cdup, opt_file)) {
4937                 const char *blame_cat_file_argv[] = {
4938                         "git", "cat-file", "blob", path, NULL
4939                 };
4941                 if (!string_format(path, "%s:%s", opt_ref, opt_file) ||
4942                     !start_update(view, blame_cat_file_argv, opt_cdup))
4943                         return FALSE;
4944         }
4946         /* First pass: remove multiple references to the same commit. */
4947         for (i = 0; i < view->lines; i++) {
4948                 struct blame *blame = view->line[i].data;
4950                 if (blame->commit && blame->commit->id[0])
4951                         blame->commit->id[0] = 0;
4952                 else
4953                         blame->commit = NULL;
4954         }
4956         /* Second pass: free existing references. */
4957         for (i = 0; i < view->lines; i++) {
4958                 struct blame *blame = view->line[i].data;
4960                 if (blame->commit)
4961                         free(blame->commit);
4962         }
4964         setup_update(view, opt_file);
4965         string_format(view->ref, "%s ...", opt_file);
4967         return TRUE;
4970 static struct blame_commit *
4971 get_blame_commit(struct view *view, const char *id)
4973         size_t i;
4975         for (i = 0; i < view->lines; i++) {
4976                 struct blame *blame = view->line[i].data;
4978                 if (!blame->commit)
4979                         continue;
4981                 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4982                         return blame->commit;
4983         }
4985         {
4986                 struct blame_commit *commit = calloc(1, sizeof(*commit));
4988                 if (commit)
4989                         string_ncopy(commit->id, id, SIZEOF_REV);
4990                 return commit;
4991         }
4994 static bool
4995 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4997         const char *pos = *posref;
4999         *posref = NULL;
5000         pos = strchr(pos + 1, ' ');
5001         if (!pos || !isdigit(pos[1]))
5002                 return FALSE;
5003         *number = atoi(pos + 1);
5004         if (*number < min || *number > max)
5005                 return FALSE;
5007         *posref = pos;
5008         return TRUE;
5011 static struct blame_commit *
5012 parse_blame_commit(struct view *view, const char *text, int *blamed)
5014         struct blame_commit *commit;
5015         struct blame *blame;
5016         const char *pos = text + SIZEOF_REV - 2;
5017         size_t orig_lineno = 0;
5018         size_t lineno;
5019         size_t group;
5021         if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
5022                 return NULL;
5024         if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
5025             !parse_number(&pos, &lineno, 1, view->lines) ||
5026             !parse_number(&pos, &group, 1, view->lines - lineno + 1))
5027                 return NULL;
5029         commit = get_blame_commit(view, text);
5030         if (!commit)
5031                 return NULL;
5033         *blamed += group;
5034         while (group--) {
5035                 struct line *line = &view->line[lineno + group - 1];
5037                 blame = line->data;
5038                 blame->commit = commit;
5039                 blame->lineno = orig_lineno + group - 1;
5040                 line->dirty = 1;
5041         }
5043         return commit;
5046 static bool
5047 blame_read_file(struct view *view, const char *line, bool *read_file)
5049         if (!line) {
5050                 const char *blame_argv[] = {
5051                         "git", "blame", "--incremental",
5052                                 *opt_ref ? opt_ref : "--incremental", "--", opt_file, NULL
5053                 };
5055                 if (view->lines == 0 && !view->prev)
5056                         die("No blame exist for %s", view->vid);
5058                 if (view->lines == 0 || !start_update(view, blame_argv, opt_cdup)) {
5059                         report("Failed to load blame data");
5060                         return TRUE;
5061                 }
5063                 *read_file = FALSE;
5064                 return FALSE;
5066         } else {
5067                 size_t linelen = strlen(line);
5068                 struct blame *blame = malloc(sizeof(*blame) + linelen);
5070                 if (!blame)
5071                         return FALSE;
5073                 blame->commit = NULL;
5074                 strncpy(blame->text, line, linelen);
5075                 blame->text[linelen] = 0;
5076                 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
5077         }
5080 static bool
5081 match_blame_header(const char *name, char **line)
5083         size_t namelen = strlen(name);
5084         bool matched = !strncmp(name, *line, namelen);
5086         if (matched)
5087                 *line += namelen;
5089         return matched;
5092 static bool
5093 blame_read(struct view *view, char *line)
5095         static struct blame_commit *commit = NULL;
5096         static int blamed = 0;
5097         static bool read_file = TRUE;
5099         if (read_file)
5100                 return blame_read_file(view, line, &read_file);
5102         if (!line) {
5103                 /* Reset all! */
5104                 commit = NULL;
5105                 blamed = 0;
5106                 read_file = TRUE;
5107                 string_format(view->ref, "%s", view->vid);
5108                 if (view_is_displayed(view)) {
5109                         update_view_title(view);
5110                         redraw_view_from(view, 0);
5111                 }
5112                 return TRUE;
5113         }
5115         if (!commit) {
5116                 commit = parse_blame_commit(view, line, &blamed);
5117                 string_format(view->ref, "%s %2d%%", view->vid,
5118                               view->lines ? blamed * 100 / view->lines : 0);
5120         } else if (match_blame_header("author ", &line)) {
5121                 commit->author = get_author(line);
5123         } else if (match_blame_header("author-time ", &line)) {
5124                 parse_timesec(&commit->time, line);
5126         } else if (match_blame_header("author-tz ", &line)) {
5127                 parse_timezone(&commit->time, line);
5129         } else if (match_blame_header("summary ", &line)) {
5130                 string_ncopy(commit->title, line, strlen(line));
5132         } else if (match_blame_header("previous ", &line)) {
5133                 if (strlen(line) <= SIZEOF_REV)
5134                         return FALSE;
5135                 string_copy_rev(commit->parent_id, line);
5136                 line += SIZEOF_REV;
5137                 string_ncopy(commit->parent_filename, line, strlen(line));
5139         } else if (match_blame_header("filename ", &line)) {
5140                 string_ncopy(commit->filename, line, strlen(line));
5141                 commit = NULL;
5142         }
5144         return TRUE;
5147 static bool
5148 blame_draw(struct view *view, struct line *line, unsigned int lineno)
5150         struct blame *blame = line->data;
5151         struct time *time = NULL;
5152         const char *id = NULL, *author = NULL;
5154         if (blame->commit && *blame->commit->filename) {
5155                 id = blame->commit->id;
5156                 author = blame->commit->author;
5157                 time = &blame->commit->time;
5158         }
5160         if (opt_date && draw_date(view, time))
5161                 return TRUE;
5163         if (opt_author && draw_author(view, author))
5164                 return TRUE;
5166         if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
5167                 return TRUE;
5169         if (draw_lineno(view, lineno))
5170                 return TRUE;
5172         draw_text(view, LINE_DEFAULT, blame->text);
5173         return TRUE;
5176 static bool
5177 check_blame_commit(struct blame *blame, bool check_null_id)
5179         if (!blame->commit)
5180                 report("Commit data not loaded yet");
5181         else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
5182                 report("No commit exist for the selected line");
5183         else
5184                 return TRUE;
5185         return FALSE;
5188 static void
5189 setup_blame_parent_line(struct view *view, struct blame *blame)
5191         char from[SIZEOF_REF + SIZEOF_STR];
5192         char to[SIZEOF_REF + SIZEOF_STR];
5193         const char *diff_tree_argv[] = {
5194                 "git", "diff", "--no-textconv", "--no-extdiff", "--no-color",
5195                         "-U0", from, to, "--", NULL
5196         };
5197         struct io io;
5198         int parent_lineno = -1;
5199         int blamed_lineno = -1;
5200         char *line;
5202         if (!string_format(from, "%s:%s", opt_ref, opt_file) ||
5203             !string_format(to, "%s:%s", blame->commit->id, blame->commit->filename) ||
5204             !io_run(&io, IO_RD, NULL, diff_tree_argv))
5205                 return;
5207         while ((line = io_get(&io, '\n', TRUE))) {
5208                 if (*line == '@') {
5209                         char *pos = strchr(line, '+');
5211                         parent_lineno = atoi(line + 4);
5212                         if (pos)
5213                                 blamed_lineno = atoi(pos + 1);
5215                 } else if (*line == '+' && parent_lineno != -1) {
5216                         if (blame->lineno == blamed_lineno - 1 &&
5217                             !strcmp(blame->text, line + 1)) {
5218                                 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
5219                                 break;
5220                         }
5221                         blamed_lineno++;
5222                 }
5223         }
5225         io_done(&io);
5228 static enum request
5229 blame_request(struct view *view, enum request request, struct line *line)
5231         enum open_flags flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
5232         struct blame *blame = line->data;
5234         switch (request) {
5235         case REQ_VIEW_BLAME:
5236                 if (check_blame_commit(blame, TRUE)) {
5237                         string_copy(opt_ref, blame->commit->id);
5238                         string_copy(opt_file, blame->commit->filename);
5239                         if (blame->lineno)
5240                                 view->lineno = blame->lineno;
5241                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5242                 }
5243                 break;
5245         case REQ_PARENT:
5246                 if (!check_blame_commit(blame, TRUE))
5247                         break;
5248                 if (!*blame->commit->parent_id) {
5249                         report("The selected commit has no parents");
5250                 } else {
5251                         string_copy_rev(opt_ref, blame->commit->parent_id);
5252                         string_copy(opt_file, blame->commit->parent_filename);
5253                         setup_blame_parent_line(view, blame);
5254                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5255                 }
5256                 break;
5258         case REQ_ENTER:
5259                 if (!check_blame_commit(blame, FALSE))
5260                         break;
5262                 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
5263                     !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
5264                         break;
5266                 if (!strcmp(blame->commit->id, NULL_ID)) {
5267                         struct view *diff = VIEW(REQ_VIEW_DIFF);
5268                         const char *diff_index_argv[] = {
5269                                 "git", "diff-index", "--root", "--patch-with-stat",
5270                                         "-C", "-M", "HEAD", "--", view->vid, NULL
5271                         };
5273                         if (!*blame->commit->parent_id) {
5274                                 diff_index_argv[1] = "diff";
5275                                 diff_index_argv[2] = "--no-color";
5276                                 diff_index_argv[6] = "--";
5277                                 diff_index_argv[7] = "/dev/null";
5278                         }
5280                         if (!prepare_update(diff, diff_index_argv, NULL)) {
5281                                 report("Failed to allocate diff command");
5282                                 break;
5283                         }
5284                         flags |= OPEN_PREPARED;
5285                 }
5287                 open_view(view, REQ_VIEW_DIFF, flags);
5288                 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
5289                         string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
5290                 break;
5292         default:
5293                 return request;
5294         }
5296         return REQ_NONE;
5299 static bool
5300 blame_grep(struct view *view, struct line *line)
5302         struct blame *blame = line->data;
5303         struct blame_commit *commit = blame->commit;
5304         const char *text[] = {
5305                 blame->text,
5306                 commit ? commit->title : "",
5307                 commit ? commit->id : "",
5308                 commit && opt_author ? commit->author : "",
5309                 commit ? mkdate(&commit->time, opt_date) : "",
5310                 NULL
5311         };
5313         return grep_text(view, text);
5316 static void
5317 blame_select(struct view *view, struct line *line)
5319         struct blame *blame = line->data;
5320         struct blame_commit *commit = blame->commit;
5322         if (!commit)
5323                 return;
5325         if (!strcmp(commit->id, NULL_ID))
5326                 string_ncopy(ref_commit, "HEAD", 4);
5327         else
5328                 string_copy_rev(ref_commit, commit->id);
5331 static struct view_ops blame_ops = {
5332         "line",
5333         NULL,
5334         blame_open,
5335         blame_read,
5336         blame_draw,
5337         blame_request,
5338         blame_grep,
5339         blame_select,
5340 };
5342 /*
5343  * Branch backend
5344  */
5346 struct branch {
5347         const char *author;             /* Author of the last commit. */
5348         struct time time;               /* Date of the last activity. */
5349         const struct ref *ref;          /* Name and commit ID information. */
5350 };
5352 static const struct ref branch_all;
5354 static const enum sort_field branch_sort_fields[] = {
5355         ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
5356 };
5357 static struct sort_state branch_sort_state = SORT_STATE(branch_sort_fields);
5359 static int
5360 branch_compare(const void *l1, const void *l2)
5362         const struct branch *branch1 = ((const struct line *) l1)->data;
5363         const struct branch *branch2 = ((const struct line *) l2)->data;
5365         switch (get_sort_field(branch_sort_state)) {
5366         case ORDERBY_DATE:
5367                 return sort_order(branch_sort_state, timecmp(&branch1->time, &branch2->time));
5369         case ORDERBY_AUTHOR:
5370                 return sort_order(branch_sort_state, strcmp(branch1->author, branch2->author));
5372         case ORDERBY_NAME:
5373         default:
5374                 return sort_order(branch_sort_state, strcmp(branch1->ref->name, branch2->ref->name));
5375         }
5378 static bool
5379 branch_draw(struct view *view, struct line *line, unsigned int lineno)
5381         struct branch *branch = line->data;
5382         enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
5384         if (opt_date && draw_date(view, &branch->time))
5385                 return TRUE;
5387         if (opt_author && draw_author(view, branch->author))
5388                 return TRUE;
5390         draw_text(view, type, branch->ref == &branch_all ? "All branches" : branch->ref->name);
5391         return TRUE;
5394 static enum request
5395 branch_request(struct view *view, enum request request, struct line *line)
5397         struct branch *branch = line->data;
5399         switch (request) {
5400         case REQ_REFRESH:
5401                 load_refs();
5402                 open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
5403                 return REQ_NONE;
5405         case REQ_TOGGLE_SORT_FIELD:
5406         case REQ_TOGGLE_SORT_ORDER:
5407                 sort_view(view, request, &branch_sort_state, branch_compare);
5408                 return REQ_NONE;
5410         case REQ_ENTER:
5411         {
5412                 const struct ref *ref = branch->ref;
5413                 const char *all_branches_argv[] = {
5414                         "git", "log", "--no-color", "--pretty=raw", "--parents",
5415                               "--topo-order",
5416                               ref == &branch_all ? "--all" : ref->name, NULL
5417                 };
5418                 struct view *main_view = VIEW(REQ_VIEW_MAIN);
5420                 if (!prepare_update(main_view, all_branches_argv, NULL))
5421                         report("Failed to load view of all branches");
5422                 else
5423                         open_view(view, REQ_VIEW_MAIN, OPEN_PREPARED | OPEN_SPLIT);
5424                 return REQ_NONE;
5425         }
5426         default:
5427                 return request;
5428         }
5431 static bool
5432 branch_read(struct view *view, char *line)
5434         static char id[SIZEOF_REV];
5435         struct branch *reference;
5436         size_t i;
5438         if (!line)
5439                 return TRUE;
5441         switch (get_line_type(line)) {
5442         case LINE_COMMIT:
5443                 string_copy_rev(id, line + STRING_SIZE("commit "));
5444                 return TRUE;
5446         case LINE_AUTHOR:
5447                 for (i = 0, reference = NULL; i < view->lines; i++) {
5448                         struct branch *branch = view->line[i].data;
5450                         if (strcmp(branch->ref->id, id))
5451                                 continue;
5453                         view->line[i].dirty = TRUE;
5454                         if (reference) {
5455                                 branch->author = reference->author;
5456                                 branch->time = reference->time;
5457                                 continue;
5458                         }
5460                         parse_author_line(line + STRING_SIZE("author "),
5461                                           &branch->author, &branch->time);
5462                         reference = branch;
5463                 }
5464                 return TRUE;
5466         default:
5467                 return TRUE;
5468         }
5472 static bool
5473 branch_open_visitor(void *data, const struct ref *ref)
5475         struct view *view = data;
5476         struct branch *branch;
5478         if (ref->tag || ref->ltag || ref->remote)
5479                 return TRUE;
5481         branch = calloc(1, sizeof(*branch));
5482         if (!branch)
5483                 return FALSE;
5485         branch->ref = ref;
5486         return !!add_line_data(view, branch, LINE_DEFAULT);
5489 static bool
5490 branch_open(struct view *view)
5492         const char *branch_log[] = {
5493                 "git", "log", "--no-color", "--pretty=raw",
5494                         "--simplify-by-decoration", "--all", NULL
5495         };
5497         if (!start_update(view, branch_log, NULL)) {
5498                 report("Failed to load branch data");
5499                 return TRUE;
5500         }
5502         setup_update(view, view->id);
5503         branch_open_visitor(view, &branch_all);
5504         foreach_ref(branch_open_visitor, view);
5505         view->p_restore = TRUE;
5507         return TRUE;
5510 static bool
5511 branch_grep(struct view *view, struct line *line)
5513         struct branch *branch = line->data;
5514         const char *text[] = {
5515                 branch->ref->name,
5516                 branch->author,
5517                 NULL
5518         };
5520         return grep_text(view, text);
5523 static void
5524 branch_select(struct view *view, struct line *line)
5526         struct branch *branch = line->data;
5528         string_copy_rev(view->ref, branch->ref->id);
5529         string_copy_rev(ref_commit, branch->ref->id);
5530         string_copy_rev(ref_head, branch->ref->id);
5531         string_copy_rev(ref_branch, branch->ref->name);
5534 static struct view_ops branch_ops = {
5535         "branch",
5536         NULL,
5537         branch_open,
5538         branch_read,
5539         branch_draw,
5540         branch_request,
5541         branch_grep,
5542         branch_select,
5543 };
5545 /*
5546  * Status backend
5547  */
5549 struct status {
5550         char status;
5551         struct {
5552                 mode_t mode;
5553                 char rev[SIZEOF_REV];
5554                 char name[SIZEOF_STR];
5555         } old;
5556         struct {
5557                 mode_t mode;
5558                 char rev[SIZEOF_REV];
5559                 char name[SIZEOF_STR];
5560         } new;
5561 };
5563 static char status_onbranch[SIZEOF_STR];
5564 static struct status stage_status;
5565 static enum line_type stage_line_type;
5566 static size_t stage_chunks;
5567 static int *stage_chunk;
5569 DEFINE_ALLOCATOR(realloc_ints, int, 32)
5571 /* This should work even for the "On branch" line. */
5572 static inline bool
5573 status_has_none(struct view *view, struct line *line)
5575         return line < view->line + view->lines && !line[1].data;
5578 /* Get fields from the diff line:
5579  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5580  */
5581 static inline bool
5582 status_get_diff(struct status *file, const char *buf, size_t bufsize)
5584         const char *old_mode = buf +  1;
5585         const char *new_mode = buf +  8;
5586         const char *old_rev  = buf + 15;
5587         const char *new_rev  = buf + 56;
5588         const char *status   = buf + 97;
5590         if (bufsize < 98 ||
5591             old_mode[-1] != ':' ||
5592             new_mode[-1] != ' ' ||
5593             old_rev[-1]  != ' ' ||
5594             new_rev[-1]  != ' ' ||
5595             status[-1]   != ' ')
5596                 return FALSE;
5598         file->status = *status;
5600         string_copy_rev(file->old.rev, old_rev);
5601         string_copy_rev(file->new.rev, new_rev);
5603         file->old.mode = strtoul(old_mode, NULL, 8);
5604         file->new.mode = strtoul(new_mode, NULL, 8);
5606         file->old.name[0] = file->new.name[0] = 0;
5608         return TRUE;
5611 static bool
5612 status_run(struct view *view, const char *argv[], char status, enum line_type type)
5614         struct status *unmerged = NULL;
5615         char *buf;
5616         struct io io;
5618         if (!io_run(&io, IO_RD, opt_cdup, argv))
5619                 return FALSE;
5621         add_line_data(view, NULL, type);
5623         while ((buf = io_get(&io, 0, TRUE))) {
5624                 struct status *file = unmerged;
5626                 if (!file) {
5627                         file = calloc(1, sizeof(*file));
5628                         if (!file || !add_line_data(view, file, type))
5629                                 goto error_out;
5630                 }
5632                 /* Parse diff info part. */
5633                 if (status) {
5634                         file->status = status;
5635                         if (status == 'A')
5636                                 string_copy(file->old.rev, NULL_ID);
5638                 } else if (!file->status || file == unmerged) {
5639                         if (!status_get_diff(file, buf, strlen(buf)))
5640                                 goto error_out;
5642                         buf = io_get(&io, 0, TRUE);
5643                         if (!buf)
5644                                 break;
5646                         /* Collapse all modified entries that follow an
5647                          * associated unmerged entry. */
5648                         if (unmerged == file) {
5649                                 unmerged->status = 'U';
5650                                 unmerged = NULL;
5651                         } else if (file->status == 'U') {
5652                                 unmerged = file;
5653                         }
5654                 }
5656                 /* Grab the old name for rename/copy. */
5657                 if (!*file->old.name &&
5658                     (file->status == 'R' || file->status == 'C')) {
5659                         string_ncopy(file->old.name, buf, strlen(buf));
5661                         buf = io_get(&io, 0, TRUE);
5662                         if (!buf)
5663                                 break;
5664                 }
5666                 /* git-ls-files just delivers a NUL separated list of
5667                  * file names similar to the second half of the
5668                  * git-diff-* output. */
5669                 string_ncopy(file->new.name, buf, strlen(buf));
5670                 if (!*file->old.name)
5671                         string_copy(file->old.name, file->new.name);
5672                 file = NULL;
5673         }
5675         if (io_error(&io)) {
5676 error_out:
5677                 io_done(&io);
5678                 return FALSE;
5679         }
5681         if (!view->line[view->lines - 1].data)
5682                 add_line_data(view, NULL, LINE_STAT_NONE);
5684         io_done(&io);
5685         return TRUE;
5688 /* Don't show unmerged entries in the staged section. */
5689 static const char *status_diff_index_argv[] = {
5690         "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5691                              "--cached", "-M", "HEAD", NULL
5692 };
5694 static const char *status_diff_files_argv[] = {
5695         "git", "diff-files", "-z", NULL
5696 };
5698 static const char *status_list_other_argv[] = {
5699         "git", "ls-files", "-z", "--others", "--exclude-standard", opt_prefix, NULL, NULL,
5700 };
5702 static const char *status_list_no_head_argv[] = {
5703         "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5704 };
5706 static const char *update_index_argv[] = {
5707         "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5708 };
5710 /* Restore the previous line number to stay in the context or select a
5711  * line with something that can be updated. */
5712 static void
5713 status_restore(struct view *view)
5715         if (view->p_lineno >= view->lines)
5716                 view->p_lineno = view->lines - 1;
5717         while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
5718                 view->p_lineno++;
5719         while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
5720                 view->p_lineno--;
5722         /* If the above fails, always skip the "On branch" line. */
5723         if (view->p_lineno < view->lines)
5724                 view->lineno = view->p_lineno;
5725         else
5726                 view->lineno = 1;
5728         if (view->lineno < view->offset)
5729                 view->offset = view->lineno;
5730         else if (view->offset + view->height <= view->lineno)
5731                 view->offset = view->lineno - view->height + 1;
5733         view->p_restore = FALSE;
5736 static void
5737 status_update_onbranch(void)
5739         static const char *paths[][2] = {
5740                 { "rebase-apply/rebasing",      "Rebasing" },
5741                 { "rebase-apply/applying",      "Applying mailbox" },
5742                 { "rebase-apply/",              "Rebasing mailbox" },
5743                 { "rebase-merge/interactive",   "Interactive rebase" },
5744                 { "rebase-merge/",              "Rebase merge" },
5745                 { "MERGE_HEAD",                 "Merging" },
5746                 { "BISECT_LOG",                 "Bisecting" },
5747                 { "HEAD",                       "On branch" },
5748         };
5749         char buf[SIZEOF_STR];
5750         struct stat stat;
5751         int i;
5753         if (is_initial_commit()) {
5754                 string_copy(status_onbranch, "Initial commit");
5755                 return;
5756         }
5758         for (i = 0; i < ARRAY_SIZE(paths); i++) {
5759                 char *head = opt_head;
5761                 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5762                     lstat(buf, &stat) < 0)
5763                         continue;
5765                 if (!*opt_head) {
5766                         struct io io;
5768                         if (io_open(&io, "%s/rebase-merge/head-name", opt_git_dir) &&
5769                             io_read_buf(&io, buf, sizeof(buf))) {
5770                                 head = buf;
5771                                 if (!prefixcmp(head, "refs/heads/"))
5772                                         head += STRING_SIZE("refs/heads/");
5773                         }
5774                 }
5776                 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5777                         string_copy(status_onbranch, opt_head);
5778                 return;
5779         }
5781         string_copy(status_onbranch, "Not currently on any branch");
5784 /* First parse staged info using git-diff-index(1), then parse unstaged
5785  * info using git-diff-files(1), and finally untracked files using
5786  * git-ls-files(1). */
5787 static bool
5788 status_open(struct view *view)
5790         reset_view(view);
5792         add_line_data(view, NULL, LINE_STAT_HEAD);
5793         status_update_onbranch();
5795         io_run_bg(update_index_argv);
5797         if (is_initial_commit()) {
5798                 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5799                         return FALSE;
5800         } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5801                 return FALSE;
5802         }
5804         if (!opt_untracked_dirs_content)
5805                 status_list_other_argv[ARRAY_SIZE(status_list_other_argv) - 2] = "--directory";
5807         if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5808             !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5809                 return FALSE;
5811         /* Restore the exact position or use the specialized restore
5812          * mode? */
5813         if (!view->p_restore)
5814                 status_restore(view);
5815         return TRUE;
5818 static bool
5819 status_draw(struct view *view, struct line *line, unsigned int lineno)
5821         struct status *status = line->data;
5822         enum line_type type;
5823         const char *text;
5825         if (!status) {
5826                 switch (line->type) {
5827                 case LINE_STAT_STAGED:
5828                         type = LINE_STAT_SECTION;
5829                         text = "Changes to be committed:";
5830                         break;
5832                 case LINE_STAT_UNSTAGED:
5833                         type = LINE_STAT_SECTION;
5834                         text = "Changed but not updated:";
5835                         break;
5837                 case LINE_STAT_UNTRACKED:
5838                         type = LINE_STAT_SECTION;
5839                         text = "Untracked files:";
5840                         break;
5842                 case LINE_STAT_NONE:
5843                         type = LINE_DEFAULT;
5844                         text = "  (no files)";
5845                         break;
5847                 case LINE_STAT_HEAD:
5848                         type = LINE_STAT_HEAD;
5849                         text = status_onbranch;
5850                         break;
5852                 default:
5853                         return FALSE;
5854                 }
5855         } else {
5856                 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5858                 buf[0] = status->status;
5859                 if (draw_text(view, line->type, buf))
5860                         return TRUE;
5861                 type = LINE_DEFAULT;
5862                 text = status->new.name;
5863         }
5865         draw_text(view, type, text);
5866         return TRUE;
5869 static enum request
5870 status_load_error(struct view *view, struct view *stage, const char *path)
5872         if (displayed_views() == 2 || display[current_view] != view)
5873                 maximize_view(view);
5874         report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5875         return REQ_NONE;
5878 static enum request
5879 status_enter(struct view *view, struct line *line)
5881         struct status *status = line->data;
5882         const char *oldpath = status ? status->old.name : NULL;
5883         /* Diffs for unmerged entries are empty when passing the new
5884          * path, so leave it empty. */
5885         const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5886         const char *info;
5887         enum open_flags split;
5888         struct view *stage = VIEW(REQ_VIEW_STAGE);
5890         if (line->type == LINE_STAT_NONE ||
5891             (!status && line[1].type == LINE_STAT_NONE)) {
5892                 report("No file to diff");
5893                 return REQ_NONE;
5894         }
5896         switch (line->type) {
5897         case LINE_STAT_STAGED:
5898                 if (is_initial_commit()) {
5899                         const char *no_head_diff_argv[] = {
5900                                 "git", "diff", "--no-color", "--patch-with-stat",
5901                                         "--", "/dev/null", newpath, NULL
5902                         };
5904                         if (!prepare_update(stage, no_head_diff_argv, opt_cdup))
5905                                 return status_load_error(view, stage, newpath);
5906                 } else {
5907                         const char *index_show_argv[] = {
5908                                 "git", "diff-index", "--root", "--patch-with-stat",
5909                                         "-C", "-M", "--cached", "HEAD", "--",
5910                                         oldpath, newpath, NULL
5911                         };
5913                         if (!prepare_update(stage, index_show_argv, opt_cdup))
5914                                 return status_load_error(view, stage, newpath);
5915                 }
5917                 if (status)
5918                         info = "Staged changes to %s";
5919                 else
5920                         info = "Staged changes";
5921                 break;
5923         case LINE_STAT_UNSTAGED:
5924         {
5925                 const char *files_show_argv[] = {
5926                         "git", "diff-files", "--root", "--patch-with-stat",
5927                                 "-C", "-M", "--", oldpath, newpath, NULL
5928                 };
5930                 if (!prepare_update(stage, files_show_argv, opt_cdup))
5931                         return status_load_error(view, stage, newpath);
5932                 if (status)
5933                         info = "Unstaged changes to %s";
5934                 else
5935                         info = "Unstaged changes";
5936                 break;
5937         }
5938         case LINE_STAT_UNTRACKED:
5939                 if (!newpath) {
5940                         report("No file to show");
5941                         return REQ_NONE;
5942                 }
5944                 if (!suffixcmp(status->new.name, -1, "/")) {
5945                         report("Cannot display a directory");
5946                         return REQ_NONE;
5947                 }
5949                 if (!prepare_update_file(stage, newpath))
5950                         return status_load_error(view, stage, newpath);
5951                 info = "Untracked file %s";
5952                 break;
5954         case LINE_STAT_HEAD:
5955                 return REQ_NONE;
5957         default:
5958                 die("line type %d not handled in switch", line->type);
5959         }
5961         split = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
5962         open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5963         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5964                 if (status) {
5965                         stage_status = *status;
5966                 } else {
5967                         memset(&stage_status, 0, sizeof(stage_status));
5968                 }
5970                 stage_line_type = line->type;
5971                 stage_chunks = 0;
5972                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5973         }
5975         return REQ_NONE;
5978 static bool
5979 status_exists(struct status *status, enum line_type type)
5981         struct view *view = VIEW(REQ_VIEW_STATUS);
5982         unsigned long lineno;
5984         for (lineno = 0; lineno < view->lines; lineno++) {
5985                 struct line *line = &view->line[lineno];
5986                 struct status *pos = line->data;
5988                 if (line->type != type)
5989                         continue;
5990                 if (!pos && (!status || !status->status) && line[1].data) {
5991                         select_view_line(view, lineno);
5992                         return TRUE;
5993                 }
5994                 if (pos && !strcmp(status->new.name, pos->new.name)) {
5995                         select_view_line(view, lineno);
5996                         return TRUE;
5997                 }
5998         }
6000         return FALSE;
6004 static bool
6005 status_update_prepare(struct io *io, enum line_type type)
6007         const char *staged_argv[] = {
6008                 "git", "update-index", "-z", "--index-info", NULL
6009         };
6010         const char *others_argv[] = {
6011                 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
6012         };
6014         switch (type) {
6015         case LINE_STAT_STAGED:
6016                 return io_run(io, IO_WR, opt_cdup, staged_argv);
6018         case LINE_STAT_UNSTAGED:
6019         case LINE_STAT_UNTRACKED:
6020                 return io_run(io, IO_WR, opt_cdup, others_argv);
6022         default:
6023                 die("line type %d not handled in switch", type);
6024                 return FALSE;
6025         }
6028 static bool
6029 status_update_write(struct io *io, struct status *status, enum line_type type)
6031         char buf[SIZEOF_STR];
6032         size_t bufsize = 0;
6034         switch (type) {
6035         case LINE_STAT_STAGED:
6036                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
6037                                         status->old.mode,
6038                                         status->old.rev,
6039                                         status->old.name, 0))
6040                         return FALSE;
6041                 break;
6043         case LINE_STAT_UNSTAGED:
6044         case LINE_STAT_UNTRACKED:
6045                 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
6046                         return FALSE;
6047                 break;
6049         default:
6050                 die("line type %d not handled in switch", type);
6051         }
6053         return io_write(io, buf, bufsize);
6056 static bool
6057 status_update_file(struct status *status, enum line_type type)
6059         struct io io;
6060         bool result;
6062         if (!status_update_prepare(&io, type))
6063                 return FALSE;
6065         result = status_update_write(&io, status, type);
6066         return io_done(&io) && result;
6069 static bool
6070 status_update_files(struct view *view, struct line *line)
6072         char buf[sizeof(view->ref)];
6073         struct io io;
6074         bool result = TRUE;
6075         struct line *pos = view->line + view->lines;
6076         int files = 0;
6077         int file, done;
6078         int cursor_y = -1, cursor_x = -1;
6080         if (!status_update_prepare(&io, line->type))
6081                 return FALSE;
6083         for (pos = line; pos < view->line + view->lines && pos->data; pos++)
6084                 files++;
6086         string_copy(buf, view->ref);
6087         getsyx(cursor_y, cursor_x);
6088         for (file = 0, done = 5; result && file < files; line++, file++) {
6089                 int almost_done = file * 100 / files;
6091                 if (almost_done > done) {
6092                         done = almost_done;
6093                         string_format(view->ref, "updating file %u of %u (%d%% done)",
6094                                       file, files, done);
6095                         update_view_title(view);
6096                         setsyx(cursor_y, cursor_x);
6097                         doupdate();
6098                 }
6099                 result = status_update_write(&io, line->data, line->type);
6100         }
6101         string_copy(view->ref, buf);
6103         return io_done(&io) && result;
6106 static bool
6107 status_update(struct view *view)
6109         struct line *line = &view->line[view->lineno];
6111         assert(view->lines);
6113         if (!line->data) {
6114                 /* This should work even for the "On branch" line. */
6115                 if (line < view->line + view->lines && !line[1].data) {
6116                         report("Nothing to update");
6117                         return FALSE;
6118                 }
6120                 if (!status_update_files(view, line + 1)) {
6121                         report("Failed to update file status");
6122                         return FALSE;
6123                 }
6125         } else if (!status_update_file(line->data, line->type)) {
6126                 report("Failed to update file status");
6127                 return FALSE;
6128         }
6130         return TRUE;
6133 static bool
6134 status_revert(struct status *status, enum line_type type, bool has_none)
6136         if (!status || type != LINE_STAT_UNSTAGED) {
6137                 if (type == LINE_STAT_STAGED) {
6138                         report("Cannot revert changes to staged files");
6139                 } else if (type == LINE_STAT_UNTRACKED) {
6140                         report("Cannot revert changes to untracked files");
6141                 } else if (has_none) {
6142                         report("Nothing to revert");
6143                 } else {
6144                         report("Cannot revert changes to multiple files");
6145                 }
6147         } else if (prompt_yesno("Are you sure you want to revert changes?")) {
6148                 char mode[10] = "100644";
6149                 const char *reset_argv[] = {
6150                         "git", "update-index", "--cacheinfo", mode,
6151                                 status->old.rev, status->old.name, NULL
6152                 };
6153                 const char *checkout_argv[] = {
6154                         "git", "checkout", "--", status->old.name, NULL
6155                 };
6157                 if (status->status == 'U') {
6158                         string_format(mode, "%5o", status->old.mode);
6160                         if (status->old.mode == 0 && status->new.mode == 0) {
6161                                 reset_argv[2] = "--force-remove";
6162                                 reset_argv[3] = status->old.name;
6163                                 reset_argv[4] = NULL;
6164                         }
6166                         if (!io_run_fg(reset_argv, opt_cdup))
6167                                 return FALSE;
6168                         if (status->old.mode == 0 && status->new.mode == 0)
6169                                 return TRUE;
6170                 }
6172                 return io_run_fg(checkout_argv, opt_cdup);
6173         }
6175         return FALSE;
6178 static enum request
6179 status_request(struct view *view, enum request request, struct line *line)
6181         struct status *status = line->data;
6183         switch (request) {
6184         case REQ_STATUS_UPDATE:
6185                 if (!status_update(view))
6186                         return REQ_NONE;
6187                 break;
6189         case REQ_STATUS_REVERT:
6190                 if (!status_revert(status, line->type, status_has_none(view, line)))
6191                         return REQ_NONE;
6192                 break;
6194         case REQ_STATUS_MERGE:
6195                 if (!status || status->status != 'U') {
6196                         report("Merging only possible for files with unmerged status ('U').");
6197                         return REQ_NONE;
6198                 }
6199                 open_mergetool(status->new.name);
6200                 break;
6202         case REQ_EDIT:
6203                 if (!status)
6204                         return request;
6205                 if (status->status == 'D') {
6206                         report("File has been deleted.");
6207                         return REQ_NONE;
6208                 }
6210                 open_editor(status->new.name);
6211                 break;
6213         case REQ_VIEW_BLAME:
6214                 if (status)
6215                         opt_ref[0] = 0;
6216                 return request;
6218         case REQ_ENTER:
6219                 /* After returning the status view has been split to
6220                  * show the stage view. No further reloading is
6221                  * necessary. */
6222                 return status_enter(view, line);
6224         case REQ_REFRESH:
6225                 /* Simply reload the view. */
6226                 break;
6228         default:
6229                 return request;
6230         }
6232         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
6234         return REQ_NONE;
6237 static void
6238 status_select(struct view *view, struct line *line)
6240         struct status *status = line->data;
6241         char file[SIZEOF_STR] = "all files";
6242         const char *text;
6243         const char *key;
6245         if (status && !string_format(file, "'%s'", status->new.name))
6246                 return;
6248         if (!status && line[1].type == LINE_STAT_NONE)
6249                 line++;
6251         switch (line->type) {
6252         case LINE_STAT_STAGED:
6253                 text = "Press %s to unstage %s for commit";
6254                 break;
6256         case LINE_STAT_UNSTAGED:
6257                 text = "Press %s to stage %s for commit";
6258                 break;
6260         case LINE_STAT_UNTRACKED:
6261                 text = "Press %s to stage %s for addition";
6262                 break;
6264         case LINE_STAT_HEAD:
6265         case LINE_STAT_NONE:
6266                 text = "Nothing to update";
6267                 break;
6269         default:
6270                 die("line type %d not handled in switch", line->type);
6271         }
6273         if (status && status->status == 'U') {
6274                 text = "Press %s to resolve conflict in %s";
6275                 key = get_key(KEYMAP_STATUS, REQ_STATUS_MERGE);
6277         } else {
6278                 key = get_key(KEYMAP_STATUS, REQ_STATUS_UPDATE);
6279         }
6281         string_format(view->ref, text, key, file);
6282         if (status)
6283                 string_copy(opt_file, status->new.name);
6286 static bool
6287 status_grep(struct view *view, struct line *line)
6289         struct status *status = line->data;
6291         if (status) {
6292                 const char buf[2] = { status->status, 0 };
6293                 const char *text[] = { status->new.name, buf, NULL };
6295                 return grep_text(view, text);
6296         }
6298         return FALSE;
6301 static struct view_ops status_ops = {
6302         "file",
6303         NULL,
6304         status_open,
6305         NULL,
6306         status_draw,
6307         status_request,
6308         status_grep,
6309         status_select,
6310 };
6313 static bool
6314 stage_diff_write(struct io *io, struct line *line, struct line *end)
6316         while (line < end) {
6317                 if (!io_write(io, line->data, strlen(line->data)) ||
6318                     !io_write(io, "\n", 1))
6319                         return FALSE;
6320                 line++;
6321                 if (line->type == LINE_DIFF_CHUNK ||
6322                     line->type == LINE_DIFF_HEADER)
6323                         break;
6324         }
6326         return TRUE;
6329 static struct line *
6330 stage_diff_find(struct view *view, struct line *line, enum line_type type)
6332         for (; view->line < line; line--)
6333                 if (line->type == type)
6334                         return line;
6336         return NULL;
6339 static bool
6340 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
6342         const char *apply_argv[SIZEOF_ARG] = {
6343                 "git", "apply", "--whitespace=nowarn", NULL
6344         };
6345         struct line *diff_hdr;
6346         struct io io;
6347         int argc = 3;
6349         diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
6350         if (!diff_hdr)
6351                 return FALSE;
6353         if (!revert)
6354                 apply_argv[argc++] = "--cached";
6355         if (revert || stage_line_type == LINE_STAT_STAGED)
6356                 apply_argv[argc++] = "-R";
6357         apply_argv[argc++] = "-";
6358         apply_argv[argc++] = NULL;
6359         if (!io_run(&io, IO_WR, opt_cdup, apply_argv))
6360                 return FALSE;
6362         if (!stage_diff_write(&io, diff_hdr, chunk) ||
6363             !stage_diff_write(&io, chunk, view->line + view->lines))
6364                 chunk = NULL;
6366         io_done(&io);
6367         io_run_bg(update_index_argv);
6369         return chunk ? TRUE : FALSE;
6372 static bool
6373 stage_update(struct view *view, struct line *line)
6375         struct line *chunk = NULL;
6377         if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
6378                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6380         if (chunk) {
6381                 if (!stage_apply_chunk(view, chunk, FALSE)) {
6382                         report("Failed to apply chunk");
6383                         return FALSE;
6384                 }
6386         } else if (!stage_status.status) {
6387                 view = VIEW(REQ_VIEW_STATUS);
6389                 for (line = view->line; line < view->line + view->lines; line++)
6390                         if (line->type == stage_line_type)
6391                                 break;
6393                 if (!status_update_files(view, line + 1)) {
6394                         report("Failed to update files");
6395                         return FALSE;
6396                 }
6398         } else if (!status_update_file(&stage_status, stage_line_type)) {
6399                 report("Failed to update file");
6400                 return FALSE;
6401         }
6403         return TRUE;
6406 static bool
6407 stage_revert(struct view *view, struct line *line)
6409         struct line *chunk = NULL;
6411         if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
6412                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6414         if (chunk) {
6415                 if (!prompt_yesno("Are you sure you want to revert changes?"))
6416                         return FALSE;
6418                 if (!stage_apply_chunk(view, chunk, TRUE)) {
6419                         report("Failed to revert chunk");
6420                         return FALSE;
6421                 }
6422                 return TRUE;
6424         } else {
6425                 return status_revert(stage_status.status ? &stage_status : NULL,
6426                                      stage_line_type, FALSE);
6427         }
6431 static void
6432 stage_next(struct view *view, struct line *line)
6434         int i;
6436         if (!stage_chunks) {
6437                 for (line = view->line; line < view->line + view->lines; line++) {
6438                         if (line->type != LINE_DIFF_CHUNK)
6439                                 continue;
6441                         if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
6442                                 report("Allocation failure");
6443                                 return;
6444                         }
6446                         stage_chunk[stage_chunks++] = line - view->line;
6447                 }
6448         }
6450         for (i = 0; i < stage_chunks; i++) {
6451                 if (stage_chunk[i] > view->lineno) {
6452                         do_scroll_view(view, stage_chunk[i] - view->lineno);
6453                         report("Chunk %d of %d", i + 1, stage_chunks);
6454                         return;
6455                 }
6456         }
6458         report("No next chunk found");
6461 static enum request
6462 stage_request(struct view *view, enum request request, struct line *line)
6464         switch (request) {
6465         case REQ_STATUS_UPDATE:
6466                 if (!stage_update(view, line))
6467                         return REQ_NONE;
6468                 break;
6470         case REQ_STATUS_REVERT:
6471                 if (!stage_revert(view, line))
6472                         return REQ_NONE;
6473                 break;
6475         case REQ_STAGE_NEXT:
6476                 if (stage_line_type == LINE_STAT_UNTRACKED) {
6477                         report("File is untracked; press %s to add",
6478                                get_key(KEYMAP_STAGE, REQ_STATUS_UPDATE));
6479                         return REQ_NONE;
6480                 }
6481                 stage_next(view, line);
6482                 return REQ_NONE;
6484         case REQ_EDIT:
6485                 if (!stage_status.new.name[0])
6486                         return request;
6487                 if (stage_status.status == 'D') {
6488                         report("File has been deleted.");
6489                         return REQ_NONE;
6490                 }
6492                 open_editor(stage_status.new.name);
6493                 break;
6495         case REQ_REFRESH:
6496                 /* Reload everything ... */
6497                 break;
6499         case REQ_VIEW_BLAME:
6500                 if (stage_status.new.name[0]) {
6501                         string_copy(opt_file, stage_status.new.name);
6502                         opt_ref[0] = 0;
6503                 }
6504                 return request;
6506         case REQ_ENTER:
6507                 return pager_request(view, request, line);
6509         default:
6510                 return request;
6511         }
6513         VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
6514         open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
6516         /* Check whether the staged entry still exists, and close the
6517          * stage view if it doesn't. */
6518         if (!status_exists(&stage_status, stage_line_type)) {
6519                 status_restore(VIEW(REQ_VIEW_STATUS));
6520                 return REQ_VIEW_CLOSE;
6521         }
6523         if (stage_line_type == LINE_STAT_UNTRACKED) {
6524                 if (!suffixcmp(stage_status.new.name, -1, "/")) {
6525                         report("Cannot display a directory");
6526                         return REQ_NONE;
6527                 }
6529                 if (!prepare_update_file(view, stage_status.new.name)) {
6530                         report("Failed to open file: %s", strerror(errno));
6531                         return REQ_NONE;
6532                 }
6533         }
6534         open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
6536         return REQ_NONE;
6539 static struct view_ops stage_ops = {
6540         "line",
6541         NULL,
6542         NULL,
6543         pager_read,
6544         pager_draw,
6545         stage_request,
6546         pager_grep,
6547         pager_select,
6548 };
6551 /*
6552  * Revision graph
6553  */
6555 struct commit {
6556         char id[SIZEOF_REV];            /* SHA1 ID. */
6557         char title[128];                /* First line of the commit message. */
6558         const char *author;             /* Author of the commit. */
6559         struct time time;               /* Date from the author ident. */
6560         struct ref_list *refs;          /* Repository references. */
6561         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
6562         size_t graph_size;              /* The width of the graph array. */
6563         bool has_parents;               /* Rewritten --parents seen. */
6564 };
6566 /* Size of rev graph with no  "padding" columns */
6567 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6569 struct rev_graph {
6570         struct rev_graph *prev, *next, *parents;
6571         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
6572         size_t size;
6573         struct commit *commit;
6574         size_t pos;
6575         unsigned int boundary:1;
6576 };
6578 /* Parents of the commit being visualized. */
6579 static struct rev_graph graph_parents[4];
6581 /* The current stack of revisions on the graph. */
6582 static struct rev_graph graph_stacks[4] = {
6583         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
6584         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
6585         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
6586         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
6587 };
6589 static inline bool
6590 graph_parent_is_merge(struct rev_graph *graph)
6592         return graph->parents->size > 1;
6595 static inline void
6596 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
6598         struct commit *commit = graph->commit;
6600         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
6601                 commit->graph[commit->graph_size++] = symbol;
6604 static void
6605 clear_rev_graph(struct rev_graph *graph)
6607         graph->boundary = 0;
6608         graph->size = graph->pos = 0;
6609         graph->commit = NULL;
6610         memset(graph->parents, 0, sizeof(*graph->parents));
6613 static void
6614 done_rev_graph(struct rev_graph *graph)
6616         if (graph_parent_is_merge(graph) &&
6617             graph->pos < graph->size - 1 &&
6618             graph->next->size == graph->size + graph->parents->size - 1) {
6619                 size_t i = graph->pos + graph->parents->size - 1;
6621                 graph->commit->graph_size = i * 2;
6622                 while (i < graph->next->size - 1) {
6623                         append_to_rev_graph(graph, ' ');
6624                         append_to_rev_graph(graph, '\\');
6625                         i++;
6626                 }
6627         }
6629         clear_rev_graph(graph);
6632 static void
6633 push_rev_graph(struct rev_graph *graph, const char *parent)
6635         int i;
6637         /* "Collapse" duplicate parents lines.
6638          *
6639          * FIXME: This needs to also update update the drawn graph but
6640          * for now it just serves as a method for pruning graph lines. */
6641         for (i = 0; i < graph->size; i++)
6642                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
6643                         return;
6645         if (graph->size < SIZEOF_REVITEMS) {
6646                 string_copy_rev(graph->rev[graph->size++], parent);
6647         }
6650 static chtype
6651 get_rev_graph_symbol(struct rev_graph *graph)
6653         chtype symbol;
6655         if (graph->boundary)
6656                 symbol = REVGRAPH_BOUND;
6657         else if (graph->parents->size == 0)
6658                 symbol = REVGRAPH_INIT;
6659         else if (graph_parent_is_merge(graph))
6660                 symbol = REVGRAPH_MERGE;
6661         else if (graph->pos >= graph->size)
6662                 symbol = REVGRAPH_BRANCH;
6663         else
6664                 symbol = REVGRAPH_COMMIT;
6666         return symbol;
6669 static void
6670 draw_rev_graph(struct rev_graph *graph)
6672         struct rev_filler {
6673                 chtype separator, line;
6674         };
6675         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
6676         static struct rev_filler fillers[] = {
6677                 { ' ',  '|' },
6678                 { '`',  '.' },
6679                 { '\'', ' ' },
6680                 { '/',  ' ' },
6681         };
6682         chtype symbol = get_rev_graph_symbol(graph);
6683         struct rev_filler *filler;
6684         size_t i;
6686         fillers[DEFAULT].line = opt_line_graphics ? ACS_VLINE : '|';
6687         filler = &fillers[DEFAULT];
6689         for (i = 0; i < graph->pos; i++) {
6690                 append_to_rev_graph(graph, filler->line);
6691                 if (graph_parent_is_merge(graph->prev) &&
6692                     graph->prev->pos == i)
6693                         filler = &fillers[RSHARP];
6695                 append_to_rev_graph(graph, filler->separator);
6696         }
6698         /* Place the symbol for this revision. */
6699         append_to_rev_graph(graph, symbol);
6701         if (graph->prev->size > graph->size)
6702                 filler = &fillers[RDIAG];
6703         else
6704                 filler = &fillers[DEFAULT];
6706         i++;
6708         for (; i < graph->size; i++) {
6709                 append_to_rev_graph(graph, filler->separator);
6710                 append_to_rev_graph(graph, filler->line);
6711                 if (graph_parent_is_merge(graph->prev) &&
6712                     i < graph->prev->pos + graph->parents->size)
6713                         filler = &fillers[RSHARP];
6714                 if (graph->prev->size > graph->size)
6715                         filler = &fillers[LDIAG];
6716         }
6718         if (graph->prev->size > graph->size) {
6719                 append_to_rev_graph(graph, filler->separator);
6720                 if (filler->line != ' ')
6721                         append_to_rev_graph(graph, filler->line);
6722         }
6725 /* Prepare the next rev graph */
6726 static void
6727 prepare_rev_graph(struct rev_graph *graph)
6729         size_t i;
6731         /* First, traverse all lines of revisions up to the active one. */
6732         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6733                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6734                         break;
6736                 push_rev_graph(graph->next, graph->rev[graph->pos]);
6737         }
6739         /* Interleave the new revision parent(s). */
6740         for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6741                 push_rev_graph(graph->next, graph->parents->rev[i]);
6743         /* Lastly, put any remaining revisions. */
6744         for (i = graph->pos + 1; i < graph->size; i++)
6745                 push_rev_graph(graph->next, graph->rev[i]);
6748 static void
6749 update_rev_graph(struct view *view, struct rev_graph *graph)
6751         /* If this is the finalizing update ... */
6752         if (graph->commit)
6753                 prepare_rev_graph(graph);
6755         /* Graph visualization needs a one rev look-ahead,
6756          * so the first update doesn't visualize anything. */
6757         if (!graph->prev->commit)
6758                 return;
6760         if (view->lines > 2)
6761                 view->line[view->lines - 3].dirty = 1;
6762         if (view->lines > 1)
6763                 view->line[view->lines - 2].dirty = 1;
6764         draw_rev_graph(graph->prev);
6765         done_rev_graph(graph->prev->prev);
6769 /*
6770  * Main view backend
6771  */
6773 static const char *main_argv[SIZEOF_ARG] = {
6774         "git", "log", "--no-color", "--pretty=raw", "--parents",
6775                 "--topo-order", "%(diffargs)", "%(revargs)",
6776                 "--", "%(fileargs)", NULL
6777 };
6779 static bool
6780 main_draw(struct view *view, struct line *line, unsigned int lineno)
6782         struct commit *commit = line->data;
6784         if (!commit->author)
6785                 return FALSE;
6787         if (opt_date && draw_date(view, &commit->time))
6788                 return TRUE;
6790         if (opt_author && draw_author(view, commit->author))
6791                 return TRUE;
6793         if (opt_rev_graph && commit->graph_size &&
6794             draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6795                 return TRUE;
6797         if (opt_show_refs && commit->refs) {
6798                 size_t i;
6800                 for (i = 0; i < commit->refs->size; i++) {
6801                         struct ref *ref = commit->refs->refs[i];
6802                         enum line_type type;
6804                         if (ref->head)
6805                                 type = LINE_MAIN_HEAD;
6806                         else if (ref->ltag)
6807                                 type = LINE_MAIN_LOCAL_TAG;
6808                         else if (ref->tag)
6809                                 type = LINE_MAIN_TAG;
6810                         else if (ref->tracked)
6811                                 type = LINE_MAIN_TRACKED;
6812                         else if (ref->remote)
6813                                 type = LINE_MAIN_REMOTE;
6814                         else
6815                                 type = LINE_MAIN_REF;
6817                         if (draw_text(view, type, "[") ||
6818                             draw_text(view, type, ref->name) ||
6819                             draw_text(view, type, "]"))
6820                                 return TRUE;
6822                         if (draw_text(view, LINE_DEFAULT, " "))
6823                                 return TRUE;
6824                 }
6825         }
6827         draw_text(view, LINE_DEFAULT, commit->title);
6828         return TRUE;
6831 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6832 static bool
6833 main_read(struct view *view, char *line)
6835         static struct rev_graph *graph = graph_stacks;
6836         enum line_type type;
6837         struct commit *commit;
6839         if (!line) {
6840                 int i;
6842                 if (!view->lines && !view->prev)
6843                         die("No revisions match the given arguments.");
6844                 if (view->lines > 0) {
6845                         commit = view->line[view->lines - 1].data;
6846                         view->line[view->lines - 1].dirty = 1;
6847                         if (!commit->author) {
6848                                 view->lines--;
6849                                 free(commit);
6850                                 graph->commit = NULL;
6851                         }
6852                 }
6853                 update_rev_graph(view, graph);
6855                 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6856                         clear_rev_graph(&graph_stacks[i]);
6857                 return TRUE;
6858         }
6860         type = get_line_type(line);
6861         if (type == LINE_COMMIT) {
6862                 commit = calloc(1, sizeof(struct commit));
6863                 if (!commit)
6864                         return FALSE;
6866                 line += STRING_SIZE("commit ");
6867                 if (*line == '-') {
6868                         graph->boundary = 1;
6869                         line++;
6870                 }
6872                 string_copy_rev(commit->id, line);
6873                 commit->refs = get_ref_list(commit->id);
6874                 graph->commit = commit;
6875                 add_line_data(view, commit, LINE_MAIN_COMMIT);
6877                 while ((line = strchr(line, ' '))) {
6878                         line++;
6879                         push_rev_graph(graph->parents, line);
6880                         commit->has_parents = TRUE;
6881                 }
6882                 return TRUE;
6883         }
6885         if (!view->lines)
6886                 return TRUE;
6887         commit = view->line[view->lines - 1].data;
6889         switch (type) {
6890         case LINE_PARENT:
6891                 if (commit->has_parents)
6892                         break;
6893                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6894                 break;
6896         case LINE_AUTHOR:
6897                 parse_author_line(line + STRING_SIZE("author "),
6898                                   &commit->author, &commit->time);
6899                 update_rev_graph(view, graph);
6900                 graph = graph->next;
6901                 break;
6903         default:
6904                 /* Fill in the commit title if it has not already been set. */
6905                 if (commit->title[0])
6906                         break;
6908                 /* Require titles to start with a non-space character at the
6909                  * offset used by git log. */
6910                 if (strncmp(line, "    ", 4))
6911                         break;
6912                 line += 4;
6913                 /* Well, if the title starts with a whitespace character,
6914                  * try to be forgiving.  Otherwise we end up with no title. */
6915                 while (isspace(*line))
6916                         line++;
6917                 if (*line == '\0')
6918                         break;
6919                 /* FIXME: More graceful handling of titles; append "..." to
6920                  * shortened titles, etc. */
6922                 string_expand(commit->title, sizeof(commit->title), line, 1);
6923                 view->line[view->lines - 1].dirty = 1;
6924         }
6926         return TRUE;
6929 static enum request
6930 main_request(struct view *view, enum request request, struct line *line)
6932         enum open_flags flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
6934         switch (request) {
6935         case REQ_ENTER:
6936                 if (view_is_displayed(view) && display[0] != view)
6937                         maximize_view(view);
6938                 open_view(view, REQ_VIEW_DIFF, flags);
6939                 break;
6940         case REQ_REFRESH:
6941                 load_refs();
6942                 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6943                 break;
6944         default:
6945                 return request;
6946         }
6948         return REQ_NONE;
6951 static bool
6952 grep_refs(struct ref_list *list, regex_t *regex)
6954         regmatch_t pmatch;
6955         size_t i;
6957         if (!opt_show_refs || !list)
6958                 return FALSE;
6960         for (i = 0; i < list->size; i++) {
6961                 if (regexec(regex, list->refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6962                         return TRUE;
6963         }
6965         return FALSE;
6968 static bool
6969 main_grep(struct view *view, struct line *line)
6971         struct commit *commit = line->data;
6972         const char *text[] = {
6973                 commit->title,
6974                 opt_author ? commit->author : "",
6975                 mkdate(&commit->time, opt_date),
6976                 NULL
6977         };
6979         return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6982 static void
6983 main_select(struct view *view, struct line *line)
6985         struct commit *commit = line->data;
6987         string_copy_rev(view->ref, commit->id);
6988         string_copy_rev(ref_commit, view->ref);
6991 static struct view_ops main_ops = {
6992         "commit",
6993         main_argv,
6994         NULL,
6995         main_read,
6996         main_draw,
6997         main_request,
6998         main_grep,
6999         main_select,
7000 };
7003 /*
7004  * Status management
7005  */
7007 /* Whether or not the curses interface has been initialized. */
7008 static bool cursed = FALSE;
7010 /* Terminal hacks and workarounds. */
7011 static bool use_scroll_redrawwin;
7012 static bool use_scroll_status_wclear;
7014 /* The status window is used for polling keystrokes. */
7015 static WINDOW *status_win;
7017 /* Reading from the prompt? */
7018 static bool input_mode = FALSE;
7020 static bool status_empty = FALSE;
7022 /* Update status and title window. */
7023 static void
7024 report(const char *msg, ...)
7026         struct view *view = display[current_view];
7028         if (input_mode)
7029                 return;
7031         if (!view) {
7032                 char buf[SIZEOF_STR];
7033                 va_list args;
7035                 va_start(args, msg);
7036                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
7037                         buf[sizeof(buf) - 1] = 0;
7038                         buf[sizeof(buf) - 2] = '.';
7039                         buf[sizeof(buf) - 3] = '.';
7040                         buf[sizeof(buf) - 4] = '.';
7041                 }
7042                 va_end(args);
7043                 die("%s", buf);
7044         }
7046         if (!status_empty || *msg) {
7047                 va_list args;
7049                 va_start(args, msg);
7051                 wmove(status_win, 0, 0);
7052                 if (view->has_scrolled && use_scroll_status_wclear)
7053                         wclear(status_win);
7054                 if (*msg) {
7055                         vwprintw(status_win, msg, args);
7056                         status_empty = FALSE;
7057                 } else {
7058                         status_empty = TRUE;
7059                 }
7060                 wclrtoeol(status_win);
7061                 wnoutrefresh(status_win);
7063                 va_end(args);
7064         }
7066         update_view_title(view);
7069 static void
7070 init_display(void)
7072         const char *term;
7073         int x, y;
7075         /* Initialize the curses library */
7076         if (isatty(STDIN_FILENO)) {
7077                 cursed = !!initscr();
7078                 opt_tty = stdin;
7079         } else {
7080                 /* Leave stdin and stdout alone when acting as a pager. */
7081                 opt_tty = fopen("/dev/tty", "r+");
7082                 if (!opt_tty)
7083                         die("Failed to open /dev/tty");
7084                 cursed = !!newterm(NULL, opt_tty, opt_tty);
7085         }
7087         if (!cursed)
7088                 die("Failed to initialize curses");
7090         nonl();         /* Disable conversion and detect newlines from input. */
7091         cbreak();       /* Take input chars one at a time, no wait for \n */
7092         noecho();       /* Don't echo input */
7093         leaveok(stdscr, FALSE);
7095         if (has_colors())
7096                 init_colors();
7098         getmaxyx(stdscr, y, x);
7099         status_win = newwin(1, x, y - 1, 0);
7100         if (!status_win)
7101                 die("Failed to create status window");
7103         /* Enable keyboard mapping */
7104         keypad(status_win, TRUE);
7105         wbkgdset(status_win, get_line_attr(LINE_STATUS));
7107 #if defined(NCURSES_VERSION_PATCH) && (NCURSES_VERSION_PATCH >= 20080119)
7108         set_tabsize(opt_tab_size);
7109 #else
7110         TABSIZE = opt_tab_size;
7111 #endif
7113         term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
7114         if (term && !strcmp(term, "gnome-terminal")) {
7115                 /* In the gnome-terminal-emulator, the message from
7116                  * scrolling up one line when impossible followed by
7117                  * scrolling down one line causes corruption of the
7118                  * status line. This is fixed by calling wclear. */
7119                 use_scroll_status_wclear = TRUE;
7120                 use_scroll_redrawwin = FALSE;
7122         } else if (term && !strcmp(term, "xrvt-xpm")) {
7123                 /* No problems with full optimizations in xrvt-(unicode)
7124                  * and aterm. */
7125                 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
7127         } else {
7128                 /* When scrolling in (u)xterm the last line in the
7129                  * scrolling direction will update slowly. */
7130                 use_scroll_redrawwin = TRUE;
7131                 use_scroll_status_wclear = FALSE;
7132         }
7135 static int
7136 get_input(int prompt_position)
7138         struct view *view;
7139         int i, key, cursor_y, cursor_x;
7141         if (prompt_position)
7142                 input_mode = TRUE;
7144         while (TRUE) {
7145                 bool loading = FALSE;
7147                 foreach_view (view, i) {
7148                         update_view(view);
7149                         if (view_is_displayed(view) && view->has_scrolled &&
7150                             use_scroll_redrawwin)
7151                                 redrawwin(view->win);
7152                         view->has_scrolled = FALSE;
7153                         if (view->pipe)
7154                                 loading = TRUE;
7155                 }
7157                 /* Update the cursor position. */
7158                 if (prompt_position) {
7159                         getbegyx(status_win, cursor_y, cursor_x);
7160                         cursor_x = prompt_position;
7161                 } else {
7162                         view = display[current_view];
7163                         getbegyx(view->win, cursor_y, cursor_x);
7164                         cursor_x = view->width - 1;
7165                         cursor_y += view->lineno - view->offset;
7166                 }
7167                 setsyx(cursor_y, cursor_x);
7169                 /* Refresh, accept single keystroke of input */
7170                 doupdate();
7171                 nodelay(status_win, loading);
7172                 key = wgetch(status_win);
7174                 /* wgetch() with nodelay() enabled returns ERR when
7175                  * there's no input. */
7176                 if (key == ERR) {
7178                 } else if (key == KEY_RESIZE) {
7179                         int height, width;
7181                         getmaxyx(stdscr, height, width);
7183                         wresize(status_win, 1, width);
7184                         mvwin(status_win, height - 1, 0);
7185                         wnoutrefresh(status_win);
7186                         resize_display();
7187                         redraw_display(TRUE);
7189                 } else {
7190                         input_mode = FALSE;
7191                         return key;
7192                 }
7193         }
7196 static char *
7197 prompt_input(const char *prompt, input_handler handler, void *data)
7199         enum input_status status = INPUT_OK;
7200         static char buf[SIZEOF_STR];
7201         size_t pos = 0;
7203         buf[pos] = 0;
7205         while (status == INPUT_OK || status == INPUT_SKIP) {
7206                 int key;
7208                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
7209                 wclrtoeol(status_win);
7211                 key = get_input(pos + 1);
7212                 switch (key) {
7213                 case KEY_RETURN:
7214                 case KEY_ENTER:
7215                 case '\n':
7216                         status = pos ? INPUT_STOP : INPUT_CANCEL;
7217                         break;
7219                 case KEY_BACKSPACE:
7220                         if (pos > 0)
7221                                 buf[--pos] = 0;
7222                         else
7223                                 status = INPUT_CANCEL;
7224                         break;
7226                 case KEY_ESC:
7227                         status = INPUT_CANCEL;
7228                         break;
7230                 default:
7231                         if (pos >= sizeof(buf)) {
7232                                 report("Input string too long");
7233                                 return NULL;
7234                         }
7236                         status = handler(data, buf, key);
7237                         if (status == INPUT_OK)
7238                                 buf[pos++] = (char) key;
7239                 }
7240         }
7242         /* Clear the status window */
7243         status_empty = FALSE;
7244         report("");
7246         if (status == INPUT_CANCEL)
7247                 return NULL;
7249         buf[pos++] = 0;
7251         return buf;
7254 static enum input_status
7255 prompt_yesno_handler(void *data, char *buf, int c)
7257         if (c == 'y' || c == 'Y')
7258                 return INPUT_STOP;
7259         if (c == 'n' || c == 'N')
7260                 return INPUT_CANCEL;
7261         return INPUT_SKIP;
7264 static bool
7265 prompt_yesno(const char *prompt)
7267         char prompt2[SIZEOF_STR];
7269         if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
7270                 return FALSE;
7272         return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
7275 static enum input_status
7276 read_prompt_handler(void *data, char *buf, int c)
7278         return isprint(c) ? INPUT_OK : INPUT_SKIP;
7281 static char *
7282 read_prompt(const char *prompt)
7284         return prompt_input(prompt, read_prompt_handler, NULL);
7287 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected)
7289         enum input_status status = INPUT_OK;
7290         int size = 0;
7292         while (items[size].text)
7293                 size++;
7295         while (status == INPUT_OK) {
7296                 const struct menu_item *item = &items[*selected];
7297                 int key;
7298                 int i;
7300                 mvwprintw(status_win, 0, 0, "%s (%d of %d) ",
7301                           prompt, *selected + 1, size);
7302                 if (item->hotkey)
7303                         wprintw(status_win, "[%c] ", (char) item->hotkey);
7304                 wprintw(status_win, "%s", item->text);
7305                 wclrtoeol(status_win);
7307                 key = get_input(COLS - 1);
7308                 switch (key) {
7309                 case KEY_RETURN:
7310                 case KEY_ENTER:
7311                 case '\n':
7312                         status = INPUT_STOP;
7313                         break;
7315                 case KEY_LEFT:
7316                 case KEY_UP:
7317                         *selected = *selected - 1;
7318                         if (*selected < 0)
7319                                 *selected = size - 1;
7320                         break;
7322                 case KEY_RIGHT:
7323                 case KEY_DOWN:
7324                         *selected = (*selected + 1) % size;
7325                         break;
7327                 case KEY_ESC:
7328                         status = INPUT_CANCEL;
7329                         break;
7331                 default:
7332                         for (i = 0; items[i].text; i++)
7333                                 if (items[i].hotkey == key) {
7334                                         *selected = i;
7335                                         status = INPUT_STOP;
7336                                         break;
7337                                 }
7338                 }
7339         }
7341         /* Clear the status window */
7342         status_empty = FALSE;
7343         report("");
7345         return status != INPUT_CANCEL;
7348 /*
7349  * Repository properties
7350  */
7352 static struct ref **refs = NULL;
7353 static size_t refs_size = 0;
7354 static struct ref *refs_head = NULL;
7356 static struct ref_list **ref_lists = NULL;
7357 static size_t ref_lists_size = 0;
7359 DEFINE_ALLOCATOR(realloc_refs, struct ref *, 256)
7360 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
7361 DEFINE_ALLOCATOR(realloc_ref_lists, struct ref_list *, 8)
7363 static int
7364 compare_refs(const void *ref1_, const void *ref2_)
7366         const struct ref *ref1 = *(const struct ref **)ref1_;
7367         const struct ref *ref2 = *(const struct ref **)ref2_;
7369         if (ref1->tag != ref2->tag)
7370                 return ref2->tag - ref1->tag;
7371         if (ref1->ltag != ref2->ltag)
7372                 return ref2->ltag - ref2->ltag;
7373         if (ref1->head != ref2->head)
7374                 return ref2->head - ref1->head;
7375         if (ref1->tracked != ref2->tracked)
7376                 return ref2->tracked - ref1->tracked;
7377         if (ref1->remote != ref2->remote)
7378                 return ref2->remote - ref1->remote;
7379         return strcmp(ref1->name, ref2->name);
7382 static void
7383 foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data)
7385         size_t i;
7387         for (i = 0; i < refs_size; i++)
7388                 if (!visitor(data, refs[i]))
7389                         break;
7392 static struct ref *
7393 get_ref_head()
7395         return refs_head;
7398 static struct ref_list *
7399 get_ref_list(const char *id)
7401         struct ref_list *list;
7402         size_t i;
7404         for (i = 0; i < ref_lists_size; i++)
7405                 if (!strcmp(id, ref_lists[i]->id))
7406                         return ref_lists[i];
7408         if (!realloc_ref_lists(&ref_lists, ref_lists_size, 1))
7409                 return NULL;
7410         list = calloc(1, sizeof(*list));
7411         if (!list)
7412                 return NULL;
7414         for (i = 0; i < refs_size; i++) {
7415                 if (!strcmp(id, refs[i]->id) &&
7416                     realloc_refs_list(&list->refs, list->size, 1))
7417                         list->refs[list->size++] = refs[i];
7418         }
7420         if (!list->refs) {
7421                 free(list);
7422                 return NULL;
7423         }
7425         qsort(list->refs, list->size, sizeof(*list->refs), compare_refs);
7426         ref_lists[ref_lists_size++] = list;
7427         return list;
7430 static int
7431 read_ref(char *id, size_t idlen, char *name, size_t namelen, void *data)
7433         struct ref *ref = NULL;
7434         bool tag = FALSE;
7435         bool ltag = FALSE;
7436         bool remote = FALSE;
7437         bool tracked = FALSE;
7438         bool head = FALSE;
7439         int from = 0, to = refs_size - 1;
7441         if (!prefixcmp(name, "refs/tags/")) {
7442                 if (!suffixcmp(name, namelen, "^{}")) {
7443                         namelen -= 3;
7444                         name[namelen] = 0;
7445                 } else {
7446                         ltag = TRUE;
7447                 }
7449                 tag = TRUE;
7450                 namelen -= STRING_SIZE("refs/tags/");
7451                 name    += STRING_SIZE("refs/tags/");
7453         } else if (!prefixcmp(name, "refs/remotes/")) {
7454                 remote = TRUE;
7455                 namelen -= STRING_SIZE("refs/remotes/");
7456                 name    += STRING_SIZE("refs/remotes/");
7457                 tracked  = !strcmp(opt_remote, name);
7459         } else if (!prefixcmp(name, "refs/heads/")) {
7460                 namelen -= STRING_SIZE("refs/heads/");
7461                 name    += STRING_SIZE("refs/heads/");
7462                 if (!strncmp(opt_head, name, namelen))
7463                         return OK;
7465         } else if (!strcmp(name, "HEAD")) {
7466                 head     = TRUE;
7467                 if (*opt_head) {
7468                         namelen  = strlen(opt_head);
7469                         name     = opt_head;
7470                 }
7471         }
7473         /* If we are reloading or it's an annotated tag, replace the
7474          * previous SHA1 with the resolved commit id; relies on the fact
7475          * git-ls-remote lists the commit id of an annotated tag right
7476          * before the commit id it points to. */
7477         while (from <= to) {
7478                 size_t pos = (to + from) / 2;
7479                 int cmp = strcmp(name, refs[pos]->name);
7481                 if (!cmp) {
7482                         ref = refs[pos];
7483                         break;
7484                 }
7486                 if (cmp < 0)
7487                         to = pos - 1;
7488                 else
7489                         from = pos + 1;
7490         }
7492         if (!ref) {
7493                 if (!realloc_refs(&refs, refs_size, 1))
7494                         return ERR;
7495                 ref = calloc(1, sizeof(*ref) + namelen);
7496                 if (!ref)
7497                         return ERR;
7498                 memmove(refs + from + 1, refs + from,
7499                         (refs_size - from) * sizeof(*refs));
7500                 refs[from] = ref;
7501                 strncpy(ref->name, name, namelen);
7502                 refs_size++;
7503         }
7505         ref->head = head;
7506         ref->tag = tag;
7507         ref->ltag = ltag;
7508         ref->remote = remote;
7509         ref->tracked = tracked;
7510         string_copy_rev(ref->id, id);
7512         if (head)
7513                 refs_head = ref;
7514         return OK;
7517 static int
7518 load_refs(void)
7520         const char *head_argv[] = {
7521                 "git", "symbolic-ref", "HEAD", NULL
7522         };
7523         static const char *ls_remote_argv[SIZEOF_ARG] = {
7524                 "git", "ls-remote", opt_git_dir, NULL
7525         };
7526         static bool init = FALSE;
7527         size_t i;
7529         if (!init) {
7530                 if (!argv_from_env(ls_remote_argv, "TIG_LS_REMOTE"))
7531                         die("TIG_LS_REMOTE contains too many arguments");
7532                 init = TRUE;
7533         }
7535         if (!*opt_git_dir)
7536                 return OK;
7538         if (io_run_buf(head_argv, opt_head, sizeof(opt_head)) &&
7539             !prefixcmp(opt_head, "refs/heads/")) {
7540                 char *offset = opt_head + STRING_SIZE("refs/heads/");
7542                 memmove(opt_head, offset, strlen(offset) + 1);
7543         }
7545         refs_head = NULL;
7546         for (i = 0; i < refs_size; i++)
7547                 refs[i]->id[0] = 0;
7549         if (io_run_load(ls_remote_argv, "\t", read_ref, NULL) == ERR)
7550                 return ERR;
7552         /* Update the ref lists to reflect changes. */
7553         for (i = 0; i < ref_lists_size; i++) {
7554                 struct ref_list *list = ref_lists[i];
7555                 size_t old, new;
7557                 for (old = new = 0; old < list->size; old++)
7558                         if (!strcmp(list->id, list->refs[old]->id))
7559                                 list->refs[new++] = list->refs[old];
7560                 list->size = new;
7561         }
7563         return OK;
7566 static void
7567 set_remote_branch(const char *name, const char *value, size_t valuelen)
7569         if (!strcmp(name, ".remote")) {
7570                 string_ncopy(opt_remote, value, valuelen);
7572         } else if (*opt_remote && !strcmp(name, ".merge")) {
7573                 size_t from = strlen(opt_remote);
7575                 if (!prefixcmp(value, "refs/heads/"))
7576                         value += STRING_SIZE("refs/heads/");
7578                 if (!string_format_from(opt_remote, &from, "/%s", value))
7579                         opt_remote[0] = 0;
7580         }
7583 static void
7584 set_repo_config_option(char *name, char *value, enum option_code (*cmd)(int, const char **))
7586         const char *argv[SIZEOF_ARG] = { name, "=" };
7587         int argc = 1 + (cmd == option_set_command);
7588         enum option_code error;
7590         if (!argv_from_string(argv, &argc, value))
7591                 error = OPT_ERR_TOO_MANY_OPTION_ARGUMENTS;
7592         else
7593                 error = cmd(argc, argv);
7595         if (error != OPT_OK)
7596                 warn("Option 'tig.%s': %s", name, option_errors[error]);
7599 static bool
7600 set_environment_variable(const char *name, const char *value)
7602         size_t len = strlen(name) + 1 + strlen(value) + 1;
7603         char *env = malloc(len);
7605         if (env &&
7606             string_nformat(env, len, NULL, "%s=%s", name, value) &&
7607             putenv(env) == 0)
7608                 return TRUE;
7609         free(env);
7610         return FALSE;
7613 static void
7614 set_work_tree(const char *value)
7616         char cwd[SIZEOF_STR];
7618         if (!getcwd(cwd, sizeof(cwd)))
7619                 die("Failed to get cwd path: %s", strerror(errno));
7620         if (chdir(opt_git_dir) < 0)
7621                 die("Failed to chdir(%s): %s", strerror(errno));
7622         if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7623                 die("Failed to get git path: %s", strerror(errno));
7624         if (chdir(cwd) < 0)
7625                 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7626         if (chdir(value) < 0)
7627                 die("Failed to chdir(%s): %s", value, strerror(errno));
7628         if (!getcwd(cwd, sizeof(cwd)))
7629                 die("Failed to get cwd path: %s", strerror(errno));
7630         if (!set_environment_variable("GIT_WORK_TREE", cwd))
7631                 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7632         if (!set_environment_variable("GIT_DIR", opt_git_dir))
7633                 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7634         opt_is_inside_work_tree = TRUE;
7637 static int
7638 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen, void *data)
7640         if (!strcmp(name, "i18n.commitencoding"))
7641                 string_ncopy(opt_encoding, value, valuelen);
7643         else if (!strcmp(name, "core.editor"))
7644                 string_ncopy(opt_editor, value, valuelen);
7646         else if (!strcmp(name, "core.worktree"))
7647                 set_work_tree(value);
7649         else if (!prefixcmp(name, "tig.color."))
7650                 set_repo_config_option(name + 10, value, option_color_command);
7652         else if (!prefixcmp(name, "tig.bind."))
7653                 set_repo_config_option(name + 9, value, option_bind_command);
7655         else if (!prefixcmp(name, "tig."))
7656                 set_repo_config_option(name + 4, value, option_set_command);
7658         else if (*opt_head && !prefixcmp(name, "branch.") &&
7659                  !strncmp(name + 7, opt_head, strlen(opt_head)))
7660                 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7662         return OK;
7665 static int
7666 load_git_config(void)
7668         const char *config_list_argv[] = { "git", "config", "--list", NULL };
7670         return io_run_load(config_list_argv, "=", read_repo_config_option, NULL);
7673 static int
7674 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen, void *data)
7676         if (!opt_git_dir[0]) {
7677                 string_ncopy(opt_git_dir, name, namelen);
7679         } else if (opt_is_inside_work_tree == -1) {
7680                 /* This can be 3 different values depending on the
7681                  * version of git being used. If git-rev-parse does not
7682                  * understand --is-inside-work-tree it will simply echo
7683                  * the option else either "true" or "false" is printed.
7684                  * Default to true for the unknown case. */
7685                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7687         } else if (*name == '.') {
7688                 string_ncopy(opt_cdup, name, namelen);
7690         } else {
7691                 string_ncopy(opt_prefix, name, namelen);
7692         }
7694         return OK;
7697 static int
7698 load_repo_info(void)
7700         const char *rev_parse_argv[] = {
7701                 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7702                         "--show-cdup", "--show-prefix", NULL
7703         };
7705         return io_run_load(rev_parse_argv, "=", read_repo_info, NULL);
7709 /*
7710  * Main
7711  */
7713 static const char usage[] =
7714 "tig " TIG_VERSION " (" __DATE__ ")\n"
7715 "\n"
7716 "Usage: tig        [options] [revs] [--] [paths]\n"
7717 "   or: tig show   [options] [revs] [--] [paths]\n"
7718 "   or: tig blame  [rev] path\n"
7719 "   or: tig status\n"
7720 "   or: tig <      [git command output]\n"
7721 "\n"
7722 "Options:\n"
7723 "  -v, --version   Show version and exit\n"
7724 "  -h, --help      Show help message and exit";
7726 static void __NORETURN
7727 quit(int sig)
7729         /* XXX: Restore tty modes and let the OS cleanup the rest! */
7730         if (cursed)
7731                 endwin();
7732         exit(0);
7735 static void __NORETURN
7736 die(const char *err, ...)
7738         va_list args;
7740         endwin();
7742         va_start(args, err);
7743         fputs("tig: ", stderr);
7744         vfprintf(stderr, err, args);
7745         fputs("\n", stderr);
7746         va_end(args);
7748         exit(1);
7751 static void
7752 warn(const char *msg, ...)
7754         va_list args;
7756         va_start(args, msg);
7757         fputs("tig warning: ", stderr);
7758         vfprintf(stderr, msg, args);
7759         fputs("\n", stderr);
7760         va_end(args);
7763 static int
7764 read_filter_args(char *name, size_t namelen, char *value, size_t valuelen, void *data)
7766         const char ***filter_args = data;
7768         return argv_append(filter_args, name) ? OK : ERR;
7771 static void
7772 filter_rev_parse(const char ***args, const char *arg1, const char *arg2, const char *argv[])
7774         const char *rev_parse_argv[SIZEOF_ARG] = { "git", "rev-parse", arg1, arg2 };
7775         const char **all_argv = NULL;
7777         if (!argv_append_array(&all_argv, rev_parse_argv) ||
7778             !argv_append_array(&all_argv, argv) ||
7779             !io_run_load(all_argv, "\n", read_filter_args, args) == ERR)
7780                 die("Failed to split arguments");
7781         argv_free(all_argv);
7782         free(all_argv);
7785 static void
7786 filter_options(const char *argv[])
7788         filter_rev_parse(&opt_file_argv, "--no-revs", "--no-flags", argv);
7789         filter_rev_parse(&opt_diff_argv, "--no-revs", "--flags", argv);
7790         filter_rev_parse(&opt_rev_argv, "--symbolic", "--revs-only", argv);
7793 static enum request
7794 parse_options(int argc, const char *argv[])
7796         enum request request = REQ_VIEW_MAIN;
7797         const char *subcommand;
7798         bool seen_dashdash = FALSE;
7799         const char **filter_argv = NULL;
7800         int i;
7802         if (!isatty(STDIN_FILENO))
7803                 return REQ_VIEW_PAGER;
7805         if (argc <= 1)
7806                 return REQ_VIEW_MAIN;
7808         subcommand = argv[1];
7809         if (!strcmp(subcommand, "status")) {
7810                 if (argc > 2)
7811                         warn("ignoring arguments after `%s'", subcommand);
7812                 return REQ_VIEW_STATUS;
7814         } else if (!strcmp(subcommand, "blame")) {
7815                 if (argc <= 2 || argc > 4)
7816                         die("invalid number of options to blame\n\n%s", usage);
7818                 i = 2;
7819                 if (argc == 4) {
7820                         string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7821                         i++;
7822                 }
7824                 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7825                 return REQ_VIEW_BLAME;
7827         } else if (!strcmp(subcommand, "show")) {
7828                 request = REQ_VIEW_DIFF;
7830         } else {
7831                 subcommand = NULL;
7832         }
7834         for (i = 1 + !!subcommand; i < argc; i++) {
7835                 const char *opt = argv[i];
7837                 if (seen_dashdash) {
7838                         argv_append(&opt_file_argv, opt);
7839                         continue;
7841                 } else if (!strcmp(opt, "--")) {
7842                         seen_dashdash = TRUE;
7843                         continue;
7845                 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7846                         printf("tig version %s\n", TIG_VERSION);
7847                         quit(0);
7849                 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7850                         printf("%s\n", usage);
7851                         quit(0);
7853                 } else if (!strcmp(opt, "--all")) {
7854                         argv_append(&opt_rev_argv, opt);
7855                         continue;
7856                 }
7858                 if (!argv_append(&filter_argv, opt))
7859                         die("command too long");
7860         }
7862         if (filter_argv)
7863                 filter_options(filter_argv);
7865         return request;
7868 int
7869 main(int argc, const char *argv[])
7871         const char *codeset = "UTF-8";
7872         enum request request = parse_options(argc, argv);
7873         struct view *view;
7874         size_t i;
7876         signal(SIGINT, quit);
7877         signal(SIGPIPE, SIG_IGN);
7879         if (setlocale(LC_ALL, "")) {
7880                 codeset = nl_langinfo(CODESET);
7881         }
7883         if (load_repo_info() == ERR)
7884                 die("Failed to load repo info.");
7886         if (load_options() == ERR)
7887                 die("Failed to load user config.");
7889         if (load_git_config() == ERR)
7890                 die("Failed to load repo config.");
7892         /* Require a git repository unless when running in pager mode. */
7893         if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7894                 die("Not a git repository");
7896         if (*opt_encoding && strcmp(codeset, "UTF-8")) {
7897                 opt_iconv_in = iconv_open("UTF-8", opt_encoding);
7898                 if (opt_iconv_in == ICONV_NONE)
7899                         die("Failed to initialize character set conversion");
7900         }
7902         if (codeset && strcmp(codeset, "UTF-8")) {
7903                 opt_iconv_out = iconv_open(codeset, "UTF-8");
7904                 if (opt_iconv_out == ICONV_NONE)
7905                         die("Failed to initialize character set conversion");
7906         }
7908         if (load_refs() == ERR)
7909                 die("Failed to load refs.");
7911         foreach_view (view, i) {
7912                 if (getenv(view->cmd_env))
7913                         warn("Use of the %s environment variable is deprecated,"
7914                              " use options or TIG_DIFF_ARGS instead",
7915                              view->cmd_env);
7916                 if (!argv_from_env(view->ops->argv, view->cmd_env))
7917                         die("Too many arguments in the `%s` environment variable",
7918                             view->cmd_env);
7919         }
7921         init_display();
7923         while (view_driver(display[current_view], request)) {
7924                 int key = get_input(0);
7926                 view = display[current_view];
7927                 request = get_keybinding(view->keymap, key);
7929                 /* Some low-level request handling. This keeps access to
7930                  * status_win restricted. */
7931                 switch (request) {
7932                 case REQ_NONE:
7933                         report("Unknown key, press %s for help",
7934                                get_key(view->keymap, REQ_VIEW_HELP));
7935                         break;
7936                 case REQ_PROMPT:
7937                 {
7938                         char *cmd = read_prompt(":");
7940                         if (cmd && isdigit(*cmd)) {
7941                                 int lineno = view->lineno + 1;
7943                                 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7944                                         select_view_line(view, lineno - 1);
7945                                         report("");
7946                                 } else {
7947                                         report("Unable to parse '%s' as a line number", cmd);
7948                                 }
7950                         } else if (cmd) {
7951                                 struct view *next = VIEW(REQ_VIEW_PAGER);
7952                                 const char *argv[SIZEOF_ARG] = { "git" };
7953                                 int argc = 1;
7955                                 /* When running random commands, initially show the
7956                                  * command in the title. However, it maybe later be
7957                                  * overwritten if a commit line is selected. */
7958                                 string_ncopy(next->ref, cmd, strlen(cmd));
7960                                 if (!argv_from_string(argv, &argc, cmd)) {
7961                                         report("Too many arguments");
7962                                 } else if (!prepare_update(next, argv, NULL)) {
7963                                         report("Failed to format command");
7964                                 } else {
7965                                         open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7966                                 }
7967                         }
7969                         request = REQ_NONE;
7970                         break;
7971                 }
7972                 case REQ_SEARCH:
7973                 case REQ_SEARCH_BACK:
7974                 {
7975                         const char *prompt = request == REQ_SEARCH ? "/" : "?";
7976                         char *search = read_prompt(prompt);
7978                         if (search)
7979                                 string_ncopy(opt_search, search, strlen(search));
7980                         else if (*opt_search)
7981                                 request = request == REQ_SEARCH ?
7982                                         REQ_FIND_NEXT :
7983                                         REQ_FIND_PREV;
7984                         else
7985                                 request = REQ_NONE;
7986                         break;
7987                 }
7988                 default:
7989                         break;
7990                 }
7991         }
7993         quit(0);
7995         return 0;