Code

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