Code

Use AX_WITH_CURSES from GNU autoconf archive to detect ncurses
[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 unsigned int current_view;
2209 #define foreach_displayed_view(view, i) \
2210         for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
2212 #define displayed_views()       (display[1] != NULL ? 2 : 1)
2214 /* Current head and commit ID */
2215 static char ref_blob[SIZEOF_REF]        = "";
2216 static char ref_commit[SIZEOF_REF]      = "HEAD";
2217 static char ref_head[SIZEOF_REF]        = "HEAD";
2218 static char ref_branch[SIZEOF_REF]      = "";
2220 enum view_type {
2221         VIEW_MAIN,
2222         VIEW_DIFF,
2223         VIEW_LOG,
2224         VIEW_TREE,
2225         VIEW_BLOB,
2226         VIEW_BLAME,
2227         VIEW_BRANCH,
2228         VIEW_HELP,
2229         VIEW_PAGER,
2230         VIEW_STATUS,
2231         VIEW_STAGE,
2232 };
2234 struct view {
2235         enum view_type type;    /* View type */
2236         const char *name;       /* View name */
2237         const char *cmd_env;    /* Command line set via environment */
2238         const char *id;         /* Points to either of ref_{head,commit,blob} */
2240         struct view_ops *ops;   /* View operations */
2242         enum keymap keymap;     /* What keymap does this view have */
2243         bool git_dir;           /* Whether the view requires a git directory. */
2245         char ref[SIZEOF_REF];   /* Hovered commit reference */
2246         char vid[SIZEOF_REF];   /* View ID. Set to id member when updating. */
2248         int height, width;      /* The width and height of the main window */
2249         WINDOW *win;            /* The main window */
2250         WINDOW *title;          /* The title window living below the main window */
2252         /* Navigation */
2253         unsigned long offset;   /* Offset of the window top */
2254         unsigned long yoffset;  /* Offset from the window side. */
2255         unsigned long lineno;   /* Current line number */
2256         unsigned long p_offset; /* Previous offset of the window top */
2257         unsigned long p_yoffset;/* Previous offset from the window side */
2258         unsigned long p_lineno; /* Previous current line number */
2259         bool p_restore;         /* Should the previous position be restored. */
2261         /* Searching */
2262         char grep[SIZEOF_STR];  /* Search string */
2263         regex_t *regex;         /* Pre-compiled regexp */
2265         /* If non-NULL, points to the view that opened this view. If this view
2266          * is closed tig will switch back to the parent view. */
2267         struct view *parent;
2268         struct view *prev;
2270         /* Buffering */
2271         size_t lines;           /* Total number of lines */
2272         struct line *line;      /* Line index */
2273         unsigned int digits;    /* Number of digits in the lines member. */
2275         /* Drawing */
2276         struct line *curline;   /* Line currently being drawn. */
2277         enum line_type curtype; /* Attribute currently used for drawing. */
2278         unsigned long col;      /* Column when drawing. */
2279         bool has_scrolled;      /* View was scrolled. */
2281         /* Loading */
2282         const char **argv;      /* Shell command arguments. */
2283         const char *dir;        /* Directory from which to execute. */
2284         struct io io;
2285         struct io *pipe;
2286         time_t start_time;
2287         time_t update_secs;
2288 };
2290 struct view_ops {
2291         /* What type of content being displayed. Used in the title bar. */
2292         const char *type;
2293         /* Default command arguments. */
2294         const char **argv;
2295         /* Open and reads in all view content. */
2296         bool (*open)(struct view *view);
2297         /* Read one line; updates view->line. */
2298         bool (*read)(struct view *view, char *data);
2299         /* Draw one line; @lineno must be < view->height. */
2300         bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
2301         /* Depending on view handle a special requests. */
2302         enum request (*request)(struct view *view, enum request request, struct line *line);
2303         /* Search for regexp in a line. */
2304         bool (*grep)(struct view *view, struct line *line);
2305         /* Select line */
2306         void (*select)(struct view *view, struct line *line);
2307         /* Prepare view for loading */
2308         bool (*prepare)(struct view *view);
2309 };
2311 static struct view_ops blame_ops;
2312 static struct view_ops blob_ops;
2313 static struct view_ops diff_ops;
2314 static struct view_ops help_ops;
2315 static struct view_ops log_ops;
2316 static struct view_ops main_ops;
2317 static struct view_ops pager_ops;
2318 static struct view_ops stage_ops;
2319 static struct view_ops status_ops;
2320 static struct view_ops tree_ops;
2321 static struct view_ops branch_ops;
2323 #define VIEW_STR(type, name, env, ref, ops, map, git) \
2324         { type, name, #env, ref, ops, map, git }
2326 #define VIEW_(id, name, ops, git, ref) \
2327         VIEW_STR(VIEW_##id, name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
2329 static struct view views[] = {
2330         VIEW_(MAIN,   "main",   &main_ops,   TRUE,  ref_head),
2331         VIEW_(DIFF,   "diff",   &diff_ops,   TRUE,  ref_commit),
2332         VIEW_(LOG,    "log",    &log_ops,    TRUE,  ref_head),
2333         VIEW_(TREE,   "tree",   &tree_ops,   TRUE,  ref_commit),
2334         VIEW_(BLOB,   "blob",   &blob_ops,   TRUE,  ref_blob),
2335         VIEW_(BLAME,  "blame",  &blame_ops,  TRUE,  ref_commit),
2336         VIEW_(BRANCH, "branch", &branch_ops, TRUE,  ref_head),
2337         VIEW_(HELP,   "help",   &help_ops,   FALSE, ""),
2338         VIEW_(PAGER,  "pager",  &pager_ops,  FALSE, ""),
2339         VIEW_(STATUS, "status", &status_ops, TRUE,  ""),
2340         VIEW_(STAGE,  "stage",  &stage_ops,  TRUE,  ""),
2341 };
2343 #define VIEW(req)       (&views[(req) - REQ_OFFSET - 1])
2345 #define foreach_view(view, i) \
2346         for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
2348 #define view_is_displayed(view) \
2349         (view == display[0] || view == display[1])
2351 static enum request
2352 view_request(struct view *view, enum request request)
2354         if (!view || !view->lines)
2355                 return request;
2356         return view->ops->request(view, request, &view->line[view->lineno]);
2360 /*
2361  * View drawing.
2362  */
2364 static inline void
2365 set_view_attr(struct view *view, enum line_type type)
2367         if (!view->curline->selected && view->curtype != type) {
2368                 (void) wattrset(view->win, get_line_attr(type));
2369                 wchgat(view->win, -1, 0, type, NULL);
2370                 view->curtype = type;
2371         }
2374 static int
2375 draw_chars(struct view *view, enum line_type type, const char *string,
2376            int max_len, bool use_tilde)
2378         static char out_buffer[BUFSIZ * 2];
2379         int len = 0;
2380         int col = 0;
2381         int trimmed = FALSE;
2382         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2384         if (max_len <= 0)
2385                 return 0;
2387         len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde, opt_tab_size);
2389         set_view_attr(view, type);
2390         if (len > 0) {
2391                 if (opt_iconv_out != ICONV_NONE) {
2392                         ICONV_CONST char *inbuf = (ICONV_CONST char *) string;
2393                         size_t inlen = len + 1;
2395                         char *outbuf = out_buffer;
2396                         size_t outlen = sizeof(out_buffer);
2398                         size_t ret;
2400                         ret = iconv(opt_iconv_out, &inbuf, &inlen, &outbuf, &outlen);
2401                         if (ret != (size_t) -1) {
2402                                 string = out_buffer;
2403                                 len = sizeof(out_buffer) - outlen;
2404                         }
2405                 }
2407                 waddnstr(view->win, string, len);
2409                 if (trimmed && use_tilde) {
2410                         set_view_attr(view, LINE_DELIMITER);
2411                         waddch(view->win, '~');
2412                         col++;
2413                 }
2414         }
2416         return col;
2419 static int
2420 draw_space(struct view *view, enum line_type type, int max, int spaces)
2422         static char space[] = "                    ";
2423         int col = 0;
2425         spaces = MIN(max, spaces);
2427         while (spaces > 0) {
2428                 int len = MIN(spaces, sizeof(space) - 1);
2430                 col += draw_chars(view, type, space, len, FALSE);
2431                 spaces -= len;
2432         }
2434         return col;
2437 static bool
2438 draw_text(struct view *view, enum line_type type, const char *string, bool trim)
2440         char text[SIZEOF_STR];
2442         do {
2443                 size_t pos = string_expand(text, sizeof(text), string, opt_tab_size);
2445                 view->col += draw_chars(view, type, text, view->width + view->yoffset - view->col, trim);
2446                 string += pos;
2447         } while (*string && view->width + view->yoffset > view->col);
2449         return view->width + view->yoffset <= view->col;
2452 static bool
2453 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
2455         size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
2456         int max = view->width + view->yoffset - view->col;
2457         int i;
2459         if (max < size)
2460                 size = max;
2462         set_view_attr(view, type);
2463         /* Using waddch() instead of waddnstr() ensures that
2464          * they'll be rendered correctly for the cursor line. */
2465         for (i = skip; i < size; i++)
2466                 waddch(view->win, graphic[i]);
2468         view->col += size;
2469         if (size < max && skip <= size)
2470                 waddch(view->win, ' ');
2471         view->col++;
2473         return view->width + view->yoffset <= view->col;
2476 static bool
2477 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
2479         int max = MIN(view->width + view->yoffset - view->col, len);
2480         int col;
2482         if (text)
2483                 col = draw_chars(view, type, text, max - 1, trim);
2484         else
2485                 col = draw_space(view, type, max - 1, max - 1);
2487         view->col += col;
2488         view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
2489         return view->width + view->yoffset <= view->col;
2492 static bool
2493 draw_date(struct view *view, struct time *time)
2495         const char *date = mkdate(time, opt_date);
2496         int cols = opt_date == DATE_SHORT ? DATE_SHORT_COLS : DATE_COLS;
2498         return draw_field(view, LINE_DATE, date, cols, FALSE);
2501 static bool
2502 draw_author(struct view *view, const char *author)
2504         bool trim = opt_author_cols == 0 || opt_author_cols > 5;
2505         bool abbreviate = opt_author == AUTHOR_ABBREVIATED || !trim;
2507         if (abbreviate && author)
2508                 author = get_author_initials(author);
2510         return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
2513 static bool
2514 draw_mode(struct view *view, mode_t mode)
2516         const char *str;
2518         if (S_ISDIR(mode))
2519                 str = "drwxr-xr-x";
2520         else if (S_ISLNK(mode))
2521                 str = "lrwxrwxrwx";
2522         else if (S_ISGITLINK(mode))
2523                 str = "m---------";
2524         else if (S_ISREG(mode) && mode & S_IXUSR)
2525                 str = "-rwxr-xr-x";
2526         else if (S_ISREG(mode))
2527                 str = "-rw-r--r--";
2528         else
2529                 str = "----------";
2531         return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
2534 static bool
2535 draw_lineno(struct view *view, unsigned int lineno)
2537         char number[10];
2538         int digits3 = view->digits < 3 ? 3 : view->digits;
2539         int max = MIN(view->width + view->yoffset - view->col, digits3);
2540         char *text = NULL;
2541         chtype separator = opt_line_graphics ? ACS_VLINE : '|';
2543         lineno += view->offset + 1;
2544         if (lineno == 1 || (lineno % opt_num_interval) == 0) {
2545                 static char fmt[] = "%1ld";
2547                 fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
2548                 if (string_format(number, fmt, lineno))
2549                         text = number;
2550         }
2551         if (text)
2552                 view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
2553         else
2554                 view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
2555         return draw_graphic(view, LINE_DEFAULT, &separator, 1);
2558 static bool
2559 draw_view_line(struct view *view, unsigned int lineno)
2561         struct line *line;
2562         bool selected = (view->offset + lineno == view->lineno);
2564         assert(view_is_displayed(view));
2566         if (view->offset + lineno >= view->lines)
2567                 return FALSE;
2569         line = &view->line[view->offset + lineno];
2571         wmove(view->win, lineno, 0);
2572         if (line->cleareol)
2573                 wclrtoeol(view->win);
2574         view->col = 0;
2575         view->curline = line;
2576         view->curtype = LINE_NONE;
2577         line->selected = FALSE;
2578         line->dirty = line->cleareol = 0;
2580         if (selected) {
2581                 set_view_attr(view, LINE_CURSOR);
2582                 line->selected = TRUE;
2583                 view->ops->select(view, line);
2584         }
2586         return view->ops->draw(view, line, lineno);
2589 static void
2590 redraw_view_dirty(struct view *view)
2592         bool dirty = FALSE;
2593         int lineno;
2595         for (lineno = 0; lineno < view->height; lineno++) {
2596                 if (view->offset + lineno >= view->lines)
2597                         break;
2598                 if (!view->line[view->offset + lineno].dirty)
2599                         continue;
2600                 dirty = TRUE;
2601                 if (!draw_view_line(view, lineno))
2602                         break;
2603         }
2605         if (!dirty)
2606                 return;
2607         wnoutrefresh(view->win);
2610 static void
2611 redraw_view_from(struct view *view, int lineno)
2613         assert(0 <= lineno && lineno < view->height);
2615         for (; lineno < view->height; lineno++) {
2616                 if (!draw_view_line(view, lineno))
2617                         break;
2618         }
2620         wnoutrefresh(view->win);
2623 static void
2624 redraw_view(struct view *view)
2626         werase(view->win);
2627         redraw_view_from(view, 0);
2631 static void
2632 update_view_title(struct view *view)
2634         char buf[SIZEOF_STR];
2635         char state[SIZEOF_STR];
2636         size_t bufpos = 0, statelen = 0;
2638         assert(view_is_displayed(view));
2640         if (view->type != VIEW_STATUS && view->lines) {
2641                 unsigned int view_lines = view->offset + view->height;
2642                 unsigned int lines = view->lines
2643                                    ? MIN(view_lines, view->lines) * 100 / view->lines
2644                                    : 0;
2646                 string_format_from(state, &statelen, " - %s %d of %d (%d%%)",
2647                                    view->ops->type,
2648                                    view->lineno + 1,
2649                                    view->lines,
2650                                    lines);
2652         }
2654         if (view->pipe) {
2655                 time_t secs = time(NULL) - view->start_time;
2657                 /* Three git seconds are a long time ... */
2658                 if (secs > 2)
2659                         string_format_from(state, &statelen, " loading %lds", secs);
2660         }
2662         string_format_from(buf, &bufpos, "[%s]", view->name);
2663         if (*view->ref && bufpos < view->width) {
2664                 size_t refsize = strlen(view->ref);
2665                 size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
2667                 if (minsize < view->width)
2668                         refsize = view->width - minsize + 7;
2669                 string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
2670         }
2672         if (statelen && bufpos < view->width) {
2673                 string_format_from(buf, &bufpos, "%s", state);
2674         }
2676         if (view == display[current_view])
2677                 wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
2678         else
2679                 wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
2681         mvwaddnstr(view->title, 0, 0, buf, bufpos);
2682         wclrtoeol(view->title);
2683         wnoutrefresh(view->title);
2686 static int
2687 apply_step(double step, int value)
2689         if (step >= 1)
2690                 return (int) step;
2691         value *= step + 0.01;
2692         return value ? value : 1;
2695 static void
2696 resize_display(void)
2698         int offset, i;
2699         struct view *base = display[0];
2700         struct view *view = display[1] ? display[1] : display[0];
2702         /* Setup window dimensions */
2704         getmaxyx(stdscr, base->height, base->width);
2706         /* Make room for the status window. */
2707         base->height -= 1;
2709         if (view != base) {
2710                 /* Horizontal split. */
2711                 view->width   = base->width;
2712                 view->height  = apply_step(opt_scale_split_view, base->height);
2713                 view->height  = MAX(view->height, MIN_VIEW_HEIGHT);
2714                 view->height  = MIN(view->height, base->height - MIN_VIEW_HEIGHT);
2715                 base->height -= view->height;
2717                 /* Make room for the title bar. */
2718                 view->height -= 1;
2719         }
2721         /* Make room for the title bar. */
2722         base->height -= 1;
2724         offset = 0;
2726         foreach_displayed_view (view, i) {
2727                 if (!view->win) {
2728                         view->win = newwin(view->height, 0, offset, 0);
2729                         if (!view->win)
2730                                 die("Failed to create %s view", view->name);
2732                         scrollok(view->win, FALSE);
2734                         view->title = newwin(1, 0, offset + view->height, 0);
2735                         if (!view->title)
2736                                 die("Failed to create title window");
2738                 } else {
2739                         wresize(view->win, view->height, view->width);
2740                         mvwin(view->win,   offset, 0);
2741                         mvwin(view->title, offset + view->height, 0);
2742                 }
2744                 offset += view->height + 1;
2745         }
2748 static void
2749 redraw_display(bool clear)
2751         struct view *view;
2752         int i;
2754         foreach_displayed_view (view, i) {
2755                 if (clear)
2756                         wclear(view->win);
2757                 redraw_view(view);
2758                 update_view_title(view);
2759         }
2763 /*
2764  * Option management
2765  */
2767 #define TOGGLE_MENU \
2768         TOGGLE_(LINENO,    '.', "line numbers",      &opt_line_number, NULL) \
2769         TOGGLE_(DATE,      'D', "dates",             &opt_date,   date_map) \
2770         TOGGLE_(AUTHOR,    'A', "author names",      &opt_author, author_map) \
2771         TOGGLE_(REV_GRAPH, 'g', "revision graph",    &opt_rev_graph, NULL) \
2772         TOGGLE_(REFS,      'F', "reference display", &opt_show_refs, NULL)
2774 static void
2775 toggle_option(enum request request)
2777         const struct {
2778                 enum request request;
2779                 const struct enum_map *map;
2780                 size_t map_size;
2781         } data[] = {            
2782 #define TOGGLE_(id, key, help, value, map) { REQ_TOGGLE_ ## id, map, ARRAY_SIZE(map) },
2783                 TOGGLE_MENU
2784 #undef  TOGGLE_
2785         };
2786         const struct menu_item menu[] = {
2787 #define TOGGLE_(id, key, help, value, map) { key, help, value },
2788                 TOGGLE_MENU
2789 #undef  TOGGLE_
2790                 { 0 }
2791         };
2792         int i = 0;
2794         if (request == REQ_OPTIONS) {
2795                 if (!prompt_menu("Toggle option", menu, &i))
2796                         return;
2797         } else {
2798                 while (i < ARRAY_SIZE(data) && data[i].request != request)
2799                         i++;
2800                 if (i >= ARRAY_SIZE(data))
2801                         die("Invalid request (%d)", request);
2802         }
2804         if (data[i].map != NULL) {
2805                 unsigned int *opt = menu[i].data;
2807                 *opt = (*opt + 1) % data[i].map_size;
2808                 redraw_display(FALSE);
2809                 report("Displaying %s %s", enum_name(data[i].map[*opt]), menu[i].text);
2811         } else {
2812                 bool *option = menu[i].data;
2814                 *option = !*option;
2815                 redraw_display(FALSE);
2816                 report("%sabling %s", *option ? "En" : "Dis", menu[i].text);
2817         }
2820 static void
2821 maximize_view(struct view *view)
2823         memset(display, 0, sizeof(display));
2824         current_view = 0;
2825         display[current_view] = view;
2826         resize_display();
2827         redraw_display(FALSE);
2828         report("");
2832 /*
2833  * Navigation
2834  */
2836 static bool
2837 goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
2839         if (lineno >= view->lines)
2840                 lineno = view->lines > 0 ? view->lines - 1 : 0;
2842         if (offset > lineno || offset + view->height <= lineno) {
2843                 unsigned long half = view->height / 2;
2845                 if (lineno > half)
2846                         offset = lineno - half;
2847                 else
2848                         offset = 0;
2849         }
2851         if (offset != view->offset || lineno != view->lineno) {
2852                 view->offset = offset;
2853                 view->lineno = lineno;
2854                 return TRUE;
2855         }
2857         return FALSE;
2860 /* Scrolling backend */
2861 static void
2862 do_scroll_view(struct view *view, int lines)
2864         bool redraw_current_line = FALSE;
2866         /* The rendering expects the new offset. */
2867         view->offset += lines;
2869         assert(0 <= view->offset && view->offset < view->lines);
2870         assert(lines);
2872         /* Move current line into the view. */
2873         if (view->lineno < view->offset) {
2874                 view->lineno = view->offset;
2875                 redraw_current_line = TRUE;
2876         } else if (view->lineno >= view->offset + view->height) {
2877                 view->lineno = view->offset + view->height - 1;
2878                 redraw_current_line = TRUE;
2879         }
2881         assert(view->offset <= view->lineno && view->lineno < view->lines);
2883         /* Redraw the whole screen if scrolling is pointless. */
2884         if (view->height < ABS(lines)) {
2885                 redraw_view(view);
2887         } else {
2888                 int line = lines > 0 ? view->height - lines : 0;
2889                 int end = line + ABS(lines);
2891                 scrollok(view->win, TRUE);
2892                 wscrl(view->win, lines);
2893                 scrollok(view->win, FALSE);
2895                 while (line < end && draw_view_line(view, line))
2896                         line++;
2898                 if (redraw_current_line)
2899                         draw_view_line(view, view->lineno - view->offset);
2900                 wnoutrefresh(view->win);
2901         }
2903         view->has_scrolled = TRUE;
2904         report("");
2907 /* Scroll frontend */
2908 static void
2909 scroll_view(struct view *view, enum request request)
2911         int lines = 1;
2913         assert(view_is_displayed(view));
2915         switch (request) {
2916         case REQ_SCROLL_FIRST_COL:
2917                 view->yoffset = 0;
2918                 redraw_view_from(view, 0);
2919                 report("");
2920                 return;
2921         case REQ_SCROLL_LEFT:
2922                 if (view->yoffset == 0) {
2923                         report("Cannot scroll beyond the first column");
2924                         return;
2925                 }
2926                 if (view->yoffset <= apply_step(opt_hscroll, view->width))
2927                         view->yoffset = 0;
2928                 else
2929                         view->yoffset -= apply_step(opt_hscroll, view->width);
2930                 redraw_view_from(view, 0);
2931                 report("");
2932                 return;
2933         case REQ_SCROLL_RIGHT:
2934                 view->yoffset += apply_step(opt_hscroll, view->width);
2935                 redraw_view(view);
2936                 report("");
2937                 return;
2938         case REQ_SCROLL_PAGE_DOWN:
2939                 lines = view->height;
2940         case REQ_SCROLL_LINE_DOWN:
2941                 if (view->offset + lines > view->lines)
2942                         lines = view->lines - view->offset;
2944                 if (lines == 0 || view->offset + view->height >= view->lines) {
2945                         report("Cannot scroll beyond the last line");
2946                         return;
2947                 }
2948                 break;
2950         case REQ_SCROLL_PAGE_UP:
2951                 lines = view->height;
2952         case REQ_SCROLL_LINE_UP:
2953                 if (lines > view->offset)
2954                         lines = view->offset;
2956                 if (lines == 0) {
2957                         report("Cannot scroll beyond the first line");
2958                         return;
2959                 }
2961                 lines = -lines;
2962                 break;
2964         default:
2965                 die("request %d not handled in switch", request);
2966         }
2968         do_scroll_view(view, lines);
2971 /* Cursor moving */
2972 static void
2973 move_view(struct view *view, enum request request)
2975         int scroll_steps = 0;
2976         int steps;
2978         switch (request) {
2979         case REQ_MOVE_FIRST_LINE:
2980                 steps = -view->lineno;
2981                 break;
2983         case REQ_MOVE_LAST_LINE:
2984                 steps = view->lines - view->lineno - 1;
2985                 break;
2987         case REQ_MOVE_PAGE_UP:
2988                 steps = view->height > view->lineno
2989                       ? -view->lineno : -view->height;
2990                 break;
2992         case REQ_MOVE_PAGE_DOWN:
2993                 steps = view->lineno + view->height >= view->lines
2994                       ? view->lines - view->lineno - 1 : view->height;
2995                 break;
2997         case REQ_MOVE_UP:
2998                 steps = -1;
2999                 break;
3001         case REQ_MOVE_DOWN:
3002                 steps = 1;
3003                 break;
3005         default:
3006                 die("request %d not handled in switch", request);
3007         }
3009         if (steps <= 0 && view->lineno == 0) {
3010                 report("Cannot move beyond the first line");
3011                 return;
3013         } else if (steps >= 0 && view->lineno + 1 >= view->lines) {
3014                 report("Cannot move beyond the last line");
3015                 return;
3016         }
3018         /* Move the current line */
3019         view->lineno += steps;
3020         assert(0 <= view->lineno && view->lineno < view->lines);
3022         /* Check whether the view needs to be scrolled */
3023         if (view->lineno < view->offset ||
3024             view->lineno >= view->offset + view->height) {
3025                 scroll_steps = steps;
3026                 if (steps < 0 && -steps > view->offset) {
3027                         scroll_steps = -view->offset;
3029                 } else if (steps > 0) {
3030                         if (view->lineno == view->lines - 1 &&
3031                             view->lines > view->height) {
3032                                 scroll_steps = view->lines - view->offset - 1;
3033                                 if (scroll_steps >= view->height)
3034                                         scroll_steps -= view->height - 1;
3035                         }
3036                 }
3037         }
3039         if (!view_is_displayed(view)) {
3040                 view->offset += scroll_steps;
3041                 assert(0 <= view->offset && view->offset < view->lines);
3042                 view->ops->select(view, &view->line[view->lineno]);
3043                 return;
3044         }
3046         /* Repaint the old "current" line if we be scrolling */
3047         if (ABS(steps) < view->height)
3048                 draw_view_line(view, view->lineno - steps - view->offset);
3050         if (scroll_steps) {
3051                 do_scroll_view(view, scroll_steps);
3052                 return;
3053         }
3055         /* Draw the current line */
3056         draw_view_line(view, view->lineno - view->offset);
3058         wnoutrefresh(view->win);
3059         report("");
3063 /*
3064  * Searching
3065  */
3067 static void search_view(struct view *view, enum request request);
3069 static bool
3070 grep_text(struct view *view, const char *text[])
3072         regmatch_t pmatch;
3073         size_t i;
3075         for (i = 0; text[i]; i++)
3076                 if (*text[i] &&
3077                     regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
3078                         return TRUE;
3079         return FALSE;
3082 static void
3083 select_view_line(struct view *view, unsigned long lineno)
3085         unsigned long old_lineno = view->lineno;
3086         unsigned long old_offset = view->offset;
3088         if (goto_view_line(view, view->offset, lineno)) {
3089                 if (view_is_displayed(view)) {
3090                         if (old_offset != view->offset) {
3091                                 redraw_view(view);
3092                         } else {
3093                                 draw_view_line(view, old_lineno - view->offset);
3094                                 draw_view_line(view, view->lineno - view->offset);
3095                                 wnoutrefresh(view->win);
3096                         }
3097                 } else {
3098                         view->ops->select(view, &view->line[view->lineno]);
3099                 }
3100         }
3103 static void
3104 find_next(struct view *view, enum request request)
3106         unsigned long lineno = view->lineno;
3107         int direction;
3109         if (!*view->grep) {
3110                 if (!*opt_search)
3111                         report("No previous search");
3112                 else
3113                         search_view(view, request);
3114                 return;
3115         }
3117         switch (request) {
3118         case REQ_SEARCH:
3119         case REQ_FIND_NEXT:
3120                 direction = 1;
3121                 break;
3123         case REQ_SEARCH_BACK:
3124         case REQ_FIND_PREV:
3125                 direction = -1;
3126                 break;
3128         default:
3129                 return;
3130         }
3132         if (request == REQ_FIND_NEXT || request == REQ_FIND_PREV)
3133                 lineno += direction;
3135         /* Note, lineno is unsigned long so will wrap around in which case it
3136          * will become bigger than view->lines. */
3137         for (; lineno < view->lines; lineno += direction) {
3138                 if (view->ops->grep(view, &view->line[lineno])) {
3139                         select_view_line(view, lineno);
3140                         report("Line %ld matches '%s'", lineno + 1, view->grep);
3141                         return;
3142                 }
3143         }
3145         report("No match found for '%s'", view->grep);
3148 static void
3149 search_view(struct view *view, enum request request)
3151         int regex_err;
3153         if (view->regex) {
3154                 regfree(view->regex);
3155                 *view->grep = 0;
3156         } else {
3157                 view->regex = calloc(1, sizeof(*view->regex));
3158                 if (!view->regex)
3159                         return;
3160         }
3162         regex_err = regcomp(view->regex, opt_search, REG_EXTENDED);
3163         if (regex_err != 0) {
3164                 char buf[SIZEOF_STR] = "unknown error";
3166                 regerror(regex_err, view->regex, buf, sizeof(buf));
3167                 report("Search failed: %s", buf);
3168                 return;
3169         }
3171         string_copy(view->grep, opt_search);
3173         find_next(view, request);
3176 /*
3177  * Incremental updating
3178  */
3180 static void
3181 reset_view(struct view *view)
3183         int i;
3185         for (i = 0; i < view->lines; i++)
3186                 free(view->line[i].data);
3187         free(view->line);
3189         view->p_offset = view->offset;
3190         view->p_yoffset = view->yoffset;
3191         view->p_lineno = view->lineno;
3193         view->line = NULL;
3194         view->offset = 0;
3195         view->yoffset = 0;
3196         view->lines  = 0;
3197         view->lineno = 0;
3198         view->vid[0] = 0;
3199         view->update_secs = 0;
3202 static const char *
3203 format_arg(const char *name)
3205         static struct {
3206                 const char *name;
3207                 size_t namelen;
3208                 const char *value;
3209                 const char *value_if_empty;
3210         } vars[] = {
3211 #define FORMAT_VAR(name, value, value_if_empty) \
3212         { name, STRING_SIZE(name), value, value_if_empty }
3213                 FORMAT_VAR("%(directory)",      opt_path,       ""),
3214                 FORMAT_VAR("%(file)",           opt_file,       ""),
3215                 FORMAT_VAR("%(ref)",            opt_ref,        "HEAD"),
3216                 FORMAT_VAR("%(head)",           ref_head,       ""),
3217                 FORMAT_VAR("%(commit)",         ref_commit,     ""),
3218                 FORMAT_VAR("%(blob)",           ref_blob,       ""),
3219                 FORMAT_VAR("%(branch)",         ref_branch,     ""),
3220         };
3221         int i;
3223         for (i = 0; i < ARRAY_SIZE(vars); i++)
3224                 if (!strncmp(name, vars[i].name, vars[i].namelen))
3225                         return *vars[i].value ? vars[i].value : vars[i].value_if_empty;
3227         report("Unknown replacement: `%s`", name);
3228         return NULL;
3231 static bool
3232 format_argv(const char ***dst_argv, const char *src_argv[], bool replace, bool first)
3234         char buf[SIZEOF_STR];
3235         int argc;
3237         argv_free(*dst_argv);
3239         for (argc = 0; src_argv[argc]; argc++) {
3240                 const char *arg = src_argv[argc];
3241                 size_t bufpos = 0;
3243                 if (!strcmp(arg, "%(fileargs)")) {
3244                         if (!argv_append_array(dst_argv, opt_file_argv))
3245                                 break;
3246                         continue;
3248                 } else if (!strcmp(arg, "%(diffargs)")) {
3249                         if (!argv_append_array(dst_argv, opt_diff_argv))
3250                                 break;
3251                         continue;
3253                 } else if (!strcmp(arg, "%(revargs)") ||
3254                            (first && !strcmp(arg, "%(commit)"))) {
3255                         if (!argv_append_array(dst_argv, opt_rev_argv))
3256                                 break;
3257                         continue;
3258                 }
3260                 while (arg) {
3261                         char *next = strstr(arg, "%(");
3262                         int len = next - arg;
3263                         const char *value;
3265                         if (!next || !replace) {
3266                                 len = strlen(arg);
3267                                 value = "";
3269                         } else {
3270                                 value = format_arg(next);
3272                                 if (!value) {
3273                                         return FALSE;
3274                                 }
3275                         }
3277                         if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
3278                                 return FALSE;
3280                         arg = next && replace ? strchr(next, ')') + 1 : NULL;
3281                 }
3283                 if (!argv_append(dst_argv, buf))
3284                         break;
3285         }
3287         return src_argv[argc] == NULL;
3290 static bool
3291 restore_view_position(struct view *view)
3293         if (!view->p_restore || (view->pipe && view->lines <= view->p_lineno))
3294                 return FALSE;
3296         /* Changing the view position cancels the restoring. */
3297         /* FIXME: Changing back to the first line is not detected. */
3298         if (view->offset != 0 || view->lineno != 0) {
3299                 view->p_restore = FALSE;
3300                 return FALSE;
3301         }
3303         if (goto_view_line(view, view->p_offset, view->p_lineno) &&
3304             view_is_displayed(view))
3305                 werase(view->win);
3307         view->yoffset = view->p_yoffset;
3308         view->p_restore = FALSE;
3310         return TRUE;
3313 static void
3314 end_update(struct view *view, bool force)
3316         if (!view->pipe)
3317                 return;
3318         while (!view->ops->read(view, NULL))
3319                 if (!force)
3320                         return;
3321         if (force)
3322                 io_kill(view->pipe);
3323         io_done(view->pipe);
3324         view->pipe = NULL;
3327 static void
3328 setup_update(struct view *view, const char *vid)
3330         reset_view(view);
3331         string_copy_rev(view->vid, vid);
3332         view->pipe = &view->io;
3333         view->start_time = time(NULL);
3336 static bool
3337 prepare_io(struct view *view, const char *dir, const char *argv[], bool replace)
3339         view->dir = dir;
3340         return format_argv(&view->argv, argv, replace, !view->prev);
3343 static bool
3344 prepare_update(struct view *view, const char *argv[], const char *dir)
3346         if (view->pipe)
3347                 end_update(view, TRUE);
3348         return prepare_io(view, dir, argv, FALSE);
3351 static bool
3352 start_update(struct view *view, const char **argv, const char *dir)
3354         if (view->pipe)
3355                 io_done(view->pipe);
3356         return prepare_io(view, dir, argv, FALSE) &&
3357                io_run(&view->io, IO_RD, dir, view->argv);
3360 static bool
3361 prepare_update_file(struct view *view, const char *name)
3363         if (view->pipe)
3364                 end_update(view, TRUE);
3365         argv_free(view->argv);
3366         return io_open(&view->io, "%s/%s", opt_cdup[0] ? opt_cdup : ".", name);
3369 static bool
3370 begin_update(struct view *view, bool refresh)
3372         if (view->pipe)
3373                 end_update(view, TRUE);
3375         if (!refresh) {
3376                 if (view->ops->prepare) {
3377                         if (!view->ops->prepare(view))
3378                                 return FALSE;
3379                 } else if (!prepare_io(view, NULL, view->ops->argv, TRUE)) {
3380                         return FALSE;
3381                 }
3383                 /* Put the current ref_* value to the view title ref
3384                  * member. This is needed by the blob view. Most other
3385                  * views sets it automatically after loading because the
3386                  * first line is a commit line. */
3387                 string_copy_rev(view->ref, view->id);
3388         }
3390         if (view->argv && view->argv[0] &&
3391             !io_run(&view->io, IO_RD, view->dir, view->argv))
3392                 return FALSE;
3394         setup_update(view, view->id);
3396         return TRUE;
3399 static bool
3400 update_view(struct view *view)
3402         char out_buffer[BUFSIZ * 2];
3403         char *line;
3404         /* Clear the view and redraw everything since the tree sorting
3405          * might have rearranged things. */
3406         bool redraw = view->lines == 0;
3407         bool can_read = TRUE;
3409         if (!view->pipe)
3410                 return TRUE;
3412         if (!io_can_read(view->pipe)) {
3413                 if (view->lines == 0 && view_is_displayed(view)) {
3414                         time_t secs = time(NULL) - view->start_time;
3416                         if (secs > 1 && secs > view->update_secs) {
3417                                 if (view->update_secs == 0)
3418                                         redraw_view(view);
3419                                 update_view_title(view);
3420                                 view->update_secs = secs;
3421                         }
3422                 }
3423                 return TRUE;
3424         }
3426         for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) {
3427                 if (opt_iconv_in != ICONV_NONE) {
3428                         ICONV_CONST char *inbuf = line;
3429                         size_t inlen = strlen(line) + 1;
3431                         char *outbuf = out_buffer;
3432                         size_t outlen = sizeof(out_buffer);
3434                         size_t ret;
3436                         ret = iconv(opt_iconv_in, &inbuf, &inlen, &outbuf, &outlen);
3437                         if (ret != (size_t) -1)
3438                                 line = out_buffer;
3439                 }
3441                 if (!view->ops->read(view, line)) {
3442                         report("Allocation failure");
3443                         end_update(view, TRUE);
3444                         return FALSE;
3445                 }
3446         }
3448         {
3449                 unsigned long lines = view->lines;
3450                 int digits;
3452                 for (digits = 0; lines; digits++)
3453                         lines /= 10;
3455                 /* Keep the displayed view in sync with line number scaling. */
3456                 if (digits != view->digits) {
3457                         view->digits = digits;
3458                         if (opt_line_number || view->type == VIEW_BLAME)
3459                                 redraw = TRUE;
3460                 }
3461         }
3463         if (io_error(view->pipe)) {
3464                 report("Failed to read: %s", io_strerror(view->pipe));
3465                 end_update(view, TRUE);
3467         } else if (io_eof(view->pipe)) {
3468                 if (view_is_displayed(view))
3469                         report("");
3470                 end_update(view, FALSE);
3471         }
3473         if (restore_view_position(view))
3474                 redraw = TRUE;
3476         if (!view_is_displayed(view))
3477                 return TRUE;
3479         if (redraw)
3480                 redraw_view_from(view, 0);
3481         else
3482                 redraw_view_dirty(view);
3484         /* Update the title _after_ the redraw so that if the redraw picks up a
3485          * commit reference in view->ref it'll be available here. */
3486         update_view_title(view);
3487         return TRUE;
3490 DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
3492 static struct line *
3493 add_line_data(struct view *view, void *data, enum line_type type)
3495         struct line *line;
3497         if (!realloc_lines(&view->line, view->lines, 1))
3498                 return NULL;
3500         line = &view->line[view->lines++];
3501         memset(line, 0, sizeof(*line));
3502         line->type = type;
3503         line->data = data;
3504         line->dirty = 1;
3506         return line;
3509 static struct line *
3510 add_line_text(struct view *view, const char *text, enum line_type type)
3512         char *data = text ? strdup(text) : NULL;
3514         return data ? add_line_data(view, data, type) : NULL;
3517 static struct line *
3518 add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
3520         char buf[SIZEOF_STR];
3521         va_list args;
3523         va_start(args, fmt);
3524         if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
3525                 buf[0] = 0;
3526         va_end(args);
3528         return buf[0] ? add_line_text(view, buf, type) : NULL;
3531 /*
3532  * View opening
3533  */
3535 enum open_flags {
3536         OPEN_DEFAULT = 0,       /* Use default view switching. */
3537         OPEN_SPLIT = 1,         /* Split current view. */
3538         OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
3539         OPEN_REFRESH = 16,      /* Refresh view using previous command. */
3540         OPEN_PREPARED = 32,     /* Open already prepared command. */
3541 };
3543 static void
3544 open_view(struct view *prev, enum request request, enum open_flags flags)
3546         bool split = !!(flags & OPEN_SPLIT);
3547         bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
3548         bool nomaximize = !!(flags & OPEN_REFRESH);
3549         struct view *view = VIEW(request);
3550         int nviews = displayed_views();
3551         struct view *base_view = display[0];
3553         if (view == prev && nviews == 1 && !reload) {
3554                 report("Already in %s view", view->name);
3555                 return;
3556         }
3558         if (view->git_dir && !opt_git_dir[0]) {
3559                 report("The %s view is disabled in pager view", view->name);
3560                 return;
3561         }
3563         if (split) {
3564                 display[1] = view;
3565                 current_view = 1;
3566                 view->parent = prev;
3567         } else if (!nomaximize) {
3568                 /* Maximize the current view. */
3569                 memset(display, 0, sizeof(display));
3570                 current_view = 0;
3571                 display[current_view] = view;
3572         }
3574         /* No prev signals that this is the first loaded view. */
3575         if (prev && view != prev) {
3576                 view->prev = prev;
3577         }
3579         /* Resize the view when switching between split- and full-screen,
3580          * or when switching between two different full-screen views. */
3581         if (nviews != displayed_views() ||
3582             (nviews == 1 && base_view != display[0]))
3583                 resize_display();
3585         if (view->ops->open) {
3586                 if (view->pipe)
3587                         end_update(view, TRUE);
3588                 if (!view->ops->open(view)) {
3589                         report("Failed to load %s view", view->name);
3590                         return;
3591                 }
3592                 restore_view_position(view);
3594         } else if ((reload || strcmp(view->vid, view->id)) &&
3595                    !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
3596                 report("Failed to load %s view", view->name);
3597                 return;
3598         }
3600         if (split && prev->lineno - prev->offset >= prev->height) {
3601                 /* Take the title line into account. */
3602                 int lines = prev->lineno - prev->offset - prev->height + 1;
3604                 /* Scroll the view that was split if the current line is
3605                  * outside the new limited view. */
3606                 do_scroll_view(prev, lines);
3607         }
3609         if (prev && view != prev && split && view_is_displayed(prev)) {
3610                 /* "Blur" the previous view. */
3611                 update_view_title(prev);
3612         }
3614         if (view->pipe && view->lines == 0) {
3615                 /* Clear the old view and let the incremental updating refill
3616                  * the screen. */
3617                 werase(view->win);
3618                 view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
3619                 report("");
3620         } else if (view_is_displayed(view)) {
3621                 redraw_view(view);
3622                 report("");
3623         }
3626 static void
3627 open_external_viewer(const char *argv[], const char *dir)
3629         def_prog_mode();           /* save current tty modes */
3630         endwin();                  /* restore original tty modes */
3631         io_run_fg(argv, dir);
3632         fprintf(stderr, "Press Enter to continue");
3633         getc(opt_tty);
3634         reset_prog_mode();
3635         redraw_display(TRUE);
3638 static void
3639 open_mergetool(const char *file)
3641         const char *mergetool_argv[] = { "git", "mergetool", file, NULL };
3643         open_external_viewer(mergetool_argv, opt_cdup);
3646 static void
3647 open_editor(const char *file)
3649         const char *editor_argv[] = { "vi", file, NULL };
3650         const char *editor;
3652         editor = getenv("GIT_EDITOR");
3653         if (!editor && *opt_editor)
3654                 editor = opt_editor;
3655         if (!editor)
3656                 editor = getenv("VISUAL");
3657         if (!editor)
3658                 editor = getenv("EDITOR");
3659         if (!editor)
3660                 editor = "vi";
3662         editor_argv[0] = editor;
3663         open_external_viewer(editor_argv, opt_cdup);
3666 static void
3667 open_run_request(enum request request)
3669         struct run_request *req = get_run_request(request);
3670         const char **argv = NULL;
3672         if (!req) {
3673                 report("Unknown run request");
3674                 return;
3675         }
3677         if (format_argv(&argv, req->argv, TRUE, FALSE))
3678                 open_external_viewer(argv, NULL);
3679         if (argv)
3680                 argv_free(argv);
3681         free(argv);
3684 /*
3685  * User request switch noodle
3686  */
3688 static int
3689 view_driver(struct view *view, enum request request)
3691         int i;
3693         if (request == REQ_NONE)
3694                 return TRUE;
3696         if (request > REQ_NONE) {
3697                 open_run_request(request);
3698                 view_request(view, REQ_REFRESH);
3699                 return TRUE;
3700         }
3702         request = view_request(view, request);
3703         if (request == REQ_NONE)
3704                 return TRUE;
3706         switch (request) {
3707         case REQ_MOVE_UP:
3708         case REQ_MOVE_DOWN:
3709         case REQ_MOVE_PAGE_UP:
3710         case REQ_MOVE_PAGE_DOWN:
3711         case REQ_MOVE_FIRST_LINE:
3712         case REQ_MOVE_LAST_LINE:
3713                 move_view(view, request);
3714                 break;
3716         case REQ_SCROLL_FIRST_COL:
3717         case REQ_SCROLL_LEFT:
3718         case REQ_SCROLL_RIGHT:
3719         case REQ_SCROLL_LINE_DOWN:
3720         case REQ_SCROLL_LINE_UP:
3721         case REQ_SCROLL_PAGE_DOWN:
3722         case REQ_SCROLL_PAGE_UP:
3723                 scroll_view(view, request);
3724                 break;
3726         case REQ_VIEW_BLAME:
3727                 if (!opt_file[0]) {
3728                         report("No file chosen, press %s to open tree view",
3729                                get_key(view->keymap, REQ_VIEW_TREE));
3730                         break;
3731                 }
3732                 open_view(view, request, OPEN_DEFAULT);
3733                 break;
3735         case REQ_VIEW_BLOB:
3736                 if (!ref_blob[0]) {
3737                         report("No file chosen, press %s to open tree view",
3738                                get_key(view->keymap, REQ_VIEW_TREE));
3739                         break;
3740                 }
3741                 open_view(view, request, OPEN_DEFAULT);
3742                 break;
3744         case REQ_VIEW_PAGER:
3745                 if (view == NULL) {
3746                         if (!io_open(&VIEW(REQ_VIEW_PAGER)->io, ""))
3747                                 die("Failed to open stdin");
3748                         open_view(view, request, OPEN_PREPARED);
3749                         break;
3750                 }
3752                 if (!VIEW(REQ_VIEW_PAGER)->pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
3753                         report("No pager content, press %s to run command from prompt",
3754                                get_key(view->keymap, REQ_PROMPT));
3755                         break;
3756                 }
3757                 open_view(view, request, OPEN_DEFAULT);
3758                 break;
3760         case REQ_VIEW_STAGE:
3761                 if (!VIEW(REQ_VIEW_STAGE)->lines) {
3762                         report("No stage content, press %s to open the status view and choose file",
3763                                get_key(view->keymap, REQ_VIEW_STATUS));
3764                         break;
3765                 }
3766                 open_view(view, request, OPEN_DEFAULT);
3767                 break;
3769         case REQ_VIEW_STATUS:
3770                 if (opt_is_inside_work_tree == FALSE) {
3771                         report("The status view requires a working tree");
3772                         break;
3773                 }
3774                 open_view(view, request, OPEN_DEFAULT);
3775                 break;
3777         case REQ_VIEW_MAIN:
3778         case REQ_VIEW_DIFF:
3779         case REQ_VIEW_LOG:
3780         case REQ_VIEW_TREE:
3781         case REQ_VIEW_HELP:
3782         case REQ_VIEW_BRANCH:
3783                 open_view(view, request, OPEN_DEFAULT);
3784                 break;
3786         case REQ_NEXT:
3787         case REQ_PREVIOUS:
3788                 request = request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
3790                 if (view->parent) {
3791                         int line;
3793                         view = view->parent;
3794                         line = view->lineno;
3795                         move_view(view, request);
3796                         if (view_is_displayed(view))
3797                                 update_view_title(view);
3798                         if (line != view->lineno)
3799                                 view_request(view, REQ_ENTER);
3800                 } else {
3801                         move_view(view, request);
3802                 }
3803                 break;
3805         case REQ_VIEW_NEXT:
3806         {
3807                 int nviews = displayed_views();
3808                 int next_view = (current_view + 1) % nviews;
3810                 if (next_view == current_view) {
3811                         report("Only one view is displayed");
3812                         break;
3813                 }
3815                 current_view = next_view;
3816                 /* Blur out the title of the previous view. */
3817                 update_view_title(view);
3818                 report("");
3819                 break;
3820         }
3821         case REQ_REFRESH:
3822                 report("Refreshing is not yet supported for the %s view", view->name);
3823                 break;
3825         case REQ_MAXIMIZE:
3826                 if (displayed_views() == 2)
3827                         maximize_view(view);
3828                 break;
3830         case REQ_OPTIONS:
3831         case REQ_TOGGLE_LINENO:
3832         case REQ_TOGGLE_DATE:
3833         case REQ_TOGGLE_AUTHOR:
3834         case REQ_TOGGLE_REV_GRAPH:
3835         case REQ_TOGGLE_REFS:
3836                 toggle_option(request);
3837                 break;
3839         case REQ_TOGGLE_SORT_FIELD:
3840         case REQ_TOGGLE_SORT_ORDER:
3841                 report("Sorting is not yet supported for the %s view", view->name);
3842                 break;
3844         case REQ_SEARCH:
3845         case REQ_SEARCH_BACK:
3846                 search_view(view, request);
3847                 break;
3849         case REQ_FIND_NEXT:
3850         case REQ_FIND_PREV:
3851                 find_next(view, request);
3852                 break;
3854         case REQ_STOP_LOADING:
3855                 foreach_view(view, i) {
3856                         if (view->pipe)
3857                                 report("Stopped loading the %s view", view->name),
3858                         end_update(view, TRUE);
3859                 }
3860                 break;
3862         case REQ_SHOW_VERSION:
3863                 report("tig-%s (built %s)", TIG_VERSION, __DATE__);
3864                 return TRUE;
3866         case REQ_SCREEN_REDRAW:
3867                 redraw_display(TRUE);
3868                 break;
3870         case REQ_EDIT:
3871                 report("Nothing to edit");
3872                 break;
3874         case REQ_ENTER:
3875                 report("Nothing to enter");
3876                 break;
3878         case REQ_VIEW_CLOSE:
3879                 /* XXX: Mark closed views by letting view->prev point to the
3880                  * view itself. Parents to closed view should never be
3881                  * followed. */
3882                 if (view->prev && view->prev != view) {
3883                         maximize_view(view->prev);
3884                         view->prev = view;
3885                         break;
3886                 }
3887                 /* Fall-through */
3888         case REQ_QUIT:
3889                 return FALSE;
3891         default:
3892                 report("Unknown key, press %s for help",
3893                        get_key(view->keymap, REQ_VIEW_HELP));
3894                 return TRUE;
3895         }
3897         return TRUE;
3901 /*
3902  * View backend utilities
3903  */
3905 enum sort_field {
3906         ORDERBY_NAME,
3907         ORDERBY_DATE,
3908         ORDERBY_AUTHOR,
3909 };
3911 struct sort_state {
3912         const enum sort_field *fields;
3913         size_t size, current;
3914         bool reverse;
3915 };
3917 #define SORT_STATE(fields) { fields, ARRAY_SIZE(fields), 0 }
3918 #define get_sort_field(state) ((state).fields[(state).current])
3919 #define sort_order(state, result) ((state).reverse ? -(result) : (result))
3921 static void
3922 sort_view(struct view *view, enum request request, struct sort_state *state,
3923           int (*compare)(const void *, const void *))
3925         switch (request) {
3926         case REQ_TOGGLE_SORT_FIELD:
3927                 state->current = (state->current + 1) % state->size;
3928                 break;
3930         case REQ_TOGGLE_SORT_ORDER:
3931                 state->reverse = !state->reverse;
3932                 break;
3933         default:
3934                 die("Not a sort request");
3935         }
3937         qsort(view->line, view->lines, sizeof(*view->line), compare);
3938         redraw_view(view);
3941 DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
3943 /* Small author cache to reduce memory consumption. It uses binary
3944  * search to lookup or find place to position new entries. No entries
3945  * are ever freed. */
3946 static const char *
3947 get_author(const char *name)
3949         static const char **authors;
3950         static size_t authors_size;
3951         int from = 0, to = authors_size - 1;
3953         while (from <= to) {
3954                 size_t pos = (to + from) / 2;
3955                 int cmp = strcmp(name, authors[pos]);
3957                 if (!cmp)
3958                         return authors[pos];
3960                 if (cmp < 0)
3961                         to = pos - 1;
3962                 else
3963                         from = pos + 1;
3964         }
3966         if (!realloc_authors(&authors, authors_size, 1))
3967                 return NULL;
3968         name = strdup(name);
3969         if (!name)
3970                 return NULL;
3972         memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
3973         authors[from] = name;
3974         authors_size++;
3976         return name;
3979 static void
3980 parse_timesec(struct time *time, const char *sec)
3982         time->sec = (time_t) atol(sec);
3985 static void
3986 parse_timezone(struct time *time, const char *zone)
3988         long tz;
3990         tz  = ('0' - zone[1]) * 60 * 60 * 10;
3991         tz += ('0' - zone[2]) * 60 * 60;
3992         tz += ('0' - zone[3]) * 60 * 10;
3993         tz += ('0' - zone[4]) * 60;
3995         if (zone[0] == '-')
3996                 tz = -tz;
3998         time->tz = tz;
3999         time->sec -= tz;
4002 /* Parse author lines where the name may be empty:
4003  *      author  <email@address.tld> 1138474660 +0100
4004  */
4005 static void
4006 parse_author_line(char *ident, const char **author, struct time *time)
4008         char *nameend = strchr(ident, '<');
4009         char *emailend = strchr(ident, '>');
4011         if (nameend && emailend)
4012                 *nameend = *emailend = 0;
4013         ident = chomp_string(ident);
4014         if (!*ident) {
4015                 if (nameend)
4016                         ident = chomp_string(nameend + 1);
4017                 if (!*ident)
4018                         ident = "Unknown";
4019         }
4021         *author = get_author(ident);
4023         /* Parse epoch and timezone */
4024         if (emailend && emailend[1] == ' ') {
4025                 char *secs = emailend + 2;
4026                 char *zone = strchr(secs, ' ');
4028                 parse_timesec(time, secs);
4030                 if (zone && strlen(zone) == STRING_SIZE(" +0700"))
4031                         parse_timezone(time, zone + 1);
4032         }
4035 /*
4036  * Pager backend
4037  */
4039 static bool
4040 pager_draw(struct view *view, struct line *line, unsigned int lineno)
4042         if (opt_line_number && draw_lineno(view, lineno))
4043                 return TRUE;
4045         draw_text(view, line->type, line->data, TRUE);
4046         return TRUE;
4049 static bool
4050 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
4052         const char *describe_argv[] = { "git", "describe", commit_id, NULL };
4053         char ref[SIZEOF_STR];
4055         if (!io_run_buf(describe_argv, ref, sizeof(ref)) || !*ref)
4056                 return TRUE;
4058         /* This is the only fatal call, since it can "corrupt" the buffer. */
4059         if (!string_nformat(buf, SIZEOF_STR, bufpos, "%s%s", sep, ref))
4060                 return FALSE;
4062         return TRUE;
4065 static void
4066 add_pager_refs(struct view *view, struct line *line)
4068         char buf[SIZEOF_STR];
4069         char *commit_id = (char *)line->data + STRING_SIZE("commit ");
4070         struct ref_list *list;
4071         size_t bufpos = 0, i;
4072         const char *sep = "Refs: ";
4073         bool is_tag = FALSE;
4075         assert(line->type == LINE_COMMIT);
4077         list = get_ref_list(commit_id);
4078         if (!list) {
4079                 if (view->type == VIEW_DIFF)
4080                         goto try_add_describe_ref;
4081                 return;
4082         }
4084         for (i = 0; i < list->size; i++) {
4085                 struct ref *ref = list->refs[i];
4086                 const char *fmt = ref->tag    ? "%s[%s]" :
4087                                   ref->remote ? "%s<%s>" : "%s%s";
4089                 if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
4090                         return;
4091                 sep = ", ";
4092                 if (ref->tag)
4093                         is_tag = TRUE;
4094         }
4096         if (!is_tag && view->type == VIEW_DIFF) {
4097 try_add_describe_ref:
4098                 /* Add <tag>-g<commit_id> "fake" reference. */
4099                 if (!add_describe_ref(buf, &bufpos, commit_id, sep))
4100                         return;
4101         }
4103         if (bufpos == 0)
4104                 return;
4106         add_line_text(view, buf, LINE_PP_REFS);
4109 static bool
4110 pager_read(struct view *view, char *data)
4112         struct line *line;
4114         if (!data)
4115                 return TRUE;
4117         line = add_line_text(view, data, get_line_type(data));
4118         if (!line)
4119                 return FALSE;
4121         if (line->type == LINE_COMMIT &&
4122             (view->type == VIEW_DIFF ||
4123              view->type == VIEW_LOG))
4124                 add_pager_refs(view, line);
4126         return TRUE;
4129 static enum request
4130 pager_request(struct view *view, enum request request, struct line *line)
4132         int split = 0;
4134         if (request != REQ_ENTER)
4135                 return request;
4137         if (line->type == LINE_COMMIT &&
4138            (view->type == VIEW_LOG ||
4139             view->type == VIEW_PAGER)) {
4140                 open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT);
4141                 split = 1;
4142         }
4144         /* Always scroll the view even if it was split. That way
4145          * you can use Enter to scroll through the log view and
4146          * split open each commit diff. */
4147         scroll_view(view, REQ_SCROLL_LINE_DOWN);
4149         /* FIXME: A minor workaround. Scrolling the view will call report("")
4150          * but if we are scrolling a non-current view this won't properly
4151          * update the view title. */
4152         if (split)
4153                 update_view_title(view);
4155         return REQ_NONE;
4158 static bool
4159 pager_grep(struct view *view, struct line *line)
4161         const char *text[] = { line->data, NULL };
4163         return grep_text(view, text);
4166 static void
4167 pager_select(struct view *view, struct line *line)
4169         if (line->type == LINE_COMMIT) {
4170                 char *text = (char *)line->data + STRING_SIZE("commit ");
4172                 if (view->type != VIEW_PAGER)
4173                         string_copy_rev(view->ref, text);
4174                 string_copy_rev(ref_commit, text);
4175         }
4178 static struct view_ops pager_ops = {
4179         "line",
4180         NULL,
4181         NULL,
4182         pager_read,
4183         pager_draw,
4184         pager_request,
4185         pager_grep,
4186         pager_select,
4187 };
4189 static const char *log_argv[SIZEOF_ARG] = {
4190         "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
4191 };
4193 static enum request
4194 log_request(struct view *view, enum request request, struct line *line)
4196         switch (request) {
4197         case REQ_REFRESH:
4198                 load_refs();
4199                 open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
4200                 return REQ_NONE;
4201         default:
4202                 return pager_request(view, request, line);
4203         }
4206 static struct view_ops log_ops = {
4207         "line",
4208         log_argv,
4209         NULL,
4210         pager_read,
4211         pager_draw,
4212         log_request,
4213         pager_grep,
4214         pager_select,
4215 };
4217 static const char *diff_argv[SIZEOF_ARG] = {
4218         "git", "show", "--pretty=fuller", "--no-color", "--root",
4219                 "--patch-with-stat", "--find-copies-harder", "-C",
4220                 "%(diffargs)", "%(commit)", "--", "%(fileargs)", NULL
4221 };
4223 static bool
4224 diff_read(struct view *view, char *data)
4226         if (!data) {
4227                 /* Fall back to retry if no diff will be shown. */
4228                 if (view->lines == 0 && opt_file_argv) {
4229                         int pos = argv_size(view->argv)
4230                                 - argv_size(opt_file_argv) - 1;
4232                         if (pos > 0 && !strcmp(view->argv[pos], "--")) {
4233                                 for (; view->argv[pos]; pos++) {
4234                                         free((void *) view->argv[pos]);
4235                                         view->argv[pos] = NULL;
4236                                 }
4238                                 if (view->pipe)
4239                                         io_done(view->pipe);
4240                                 if (io_run(&view->io, IO_RD, view->dir, view->argv))
4241                                         return FALSE;
4242                         }
4243                 }
4244                 return TRUE;
4245         }
4247         return pager_read(view, data);
4250 static struct view_ops diff_ops = {
4251         "line",
4252         diff_argv,
4253         NULL,
4254         diff_read,
4255         pager_draw,
4256         pager_request,
4257         pager_grep,
4258         pager_select,
4259 };
4261 /*
4262  * Help backend
4263  */
4265 static bool help_keymap_hidden[ARRAY_SIZE(keymap_table)];
4267 static bool
4268 help_open_keymap_title(struct view *view, enum keymap keymap)
4270         struct line *line;
4272         line = add_line_format(view, LINE_HELP_KEYMAP, "[%c] %s bindings",
4273                                help_keymap_hidden[keymap] ? '+' : '-',
4274                                enum_name(keymap_table[keymap]));
4275         if (line)
4276                 line->other = keymap;
4278         return help_keymap_hidden[keymap];
4281 static void
4282 help_open_keymap(struct view *view, enum keymap keymap)
4284         const char *group = NULL;
4285         char buf[SIZEOF_STR];
4286         size_t bufpos;
4287         bool add_title = TRUE;
4288         int i;
4290         for (i = 0; i < ARRAY_SIZE(req_info); i++) {
4291                 const char *key = NULL;
4293                 if (req_info[i].request == REQ_NONE)
4294                         continue;
4296                 if (!req_info[i].request) {
4297                         group = req_info[i].help;
4298                         continue;
4299                 }
4301                 key = get_keys(keymap, req_info[i].request, TRUE);
4302                 if (!key || !*key)
4303                         continue;
4305                 if (add_title && help_open_keymap_title(view, keymap))
4306                         return;
4307                 add_title = FALSE;
4309                 if (group) {
4310                         add_line_text(view, group, LINE_HELP_GROUP);
4311                         group = NULL;
4312                 }
4314                 add_line_format(view, LINE_DEFAULT, "    %-25s %-20s %s", key,
4315                                 enum_name(req_info[i]), req_info[i].help);
4316         }
4318         group = "External commands:";
4320         for (i = 0; i < run_requests; i++) {
4321                 struct run_request *req = get_run_request(REQ_NONE + i + 1);
4322                 const char *key;
4323                 int argc;
4325                 if (!req || req->keymap != keymap)
4326                         continue;
4328                 key = get_key_name(req->key);
4329                 if (!*key)
4330                         key = "(no key defined)";
4332                 if (add_title && help_open_keymap_title(view, keymap))
4333                         return;
4334                 if (group) {
4335                         add_line_text(view, group, LINE_HELP_GROUP);
4336                         group = NULL;
4337                 }
4339                 for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
4340                         if (!string_format_from(buf, &bufpos, "%s%s",
4341                                                 argc ? " " : "", req->argv[argc]))
4342                                 return;
4344                 add_line_format(view, LINE_DEFAULT, "    %-25s `%s`", key, buf);
4345         }
4348 static bool
4349 help_open(struct view *view)
4351         enum keymap keymap;
4353         reset_view(view);
4354         add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
4355         add_line_text(view, "", LINE_DEFAULT);
4357         for (keymap = 0; keymap < ARRAY_SIZE(keymap_table); keymap++)
4358                 help_open_keymap(view, keymap);
4360         return TRUE;
4363 static enum request
4364 help_request(struct view *view, enum request request, struct line *line)
4366         switch (request) {
4367         case REQ_ENTER:
4368                 if (line->type == LINE_HELP_KEYMAP) {
4369                         help_keymap_hidden[line->other] =
4370                                 !help_keymap_hidden[line->other];
4371                         view->p_restore = TRUE;
4372                         open_view(view, REQ_VIEW_HELP, OPEN_REFRESH);
4373                 }
4375                 return REQ_NONE;
4376         default:
4377                 return pager_request(view, request, line);
4378         }
4381 static struct view_ops help_ops = {
4382         "line",
4383         NULL,
4384         help_open,
4385         NULL,
4386         pager_draw,
4387         help_request,
4388         pager_grep,
4389         pager_select,
4390 };
4393 /*
4394  * Tree backend
4395  */
4397 struct tree_stack_entry {
4398         struct tree_stack_entry *prev;  /* Entry below this in the stack */
4399         unsigned long lineno;           /* Line number to restore */
4400         char *name;                     /* Position of name in opt_path */
4401 };
4403 /* The top of the path stack. */
4404 static struct tree_stack_entry *tree_stack = NULL;
4405 unsigned long tree_lineno = 0;
4407 static void
4408 pop_tree_stack_entry(void)
4410         struct tree_stack_entry *entry = tree_stack;
4412         tree_lineno = entry->lineno;
4413         entry->name[0] = 0;
4414         tree_stack = entry->prev;
4415         free(entry);
4418 static void
4419 push_tree_stack_entry(const char *name, unsigned long lineno)
4421         struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
4422         size_t pathlen = strlen(opt_path);
4424         if (!entry)
4425                 return;
4427         entry->prev = tree_stack;
4428         entry->name = opt_path + pathlen;
4429         tree_stack = entry;
4431         if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
4432                 pop_tree_stack_entry();
4433                 return;
4434         }
4436         /* Move the current line to the first tree entry. */
4437         tree_lineno = 1;
4438         entry->lineno = lineno;
4441 /* Parse output from git-ls-tree(1):
4442  *
4443  * 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
4444  */
4446 #define SIZEOF_TREE_ATTR \
4447         STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
4449 #define SIZEOF_TREE_MODE \
4450         STRING_SIZE("100644 ")
4452 #define TREE_ID_OFFSET \
4453         STRING_SIZE("100644 blob ")
4455 struct tree_entry {
4456         char id[SIZEOF_REV];
4457         mode_t mode;
4458         struct time time;               /* Date from the author ident. */
4459         const char *author;             /* Author of the commit. */
4460         char name[1];
4461 };
4463 static const char *
4464 tree_path(const struct line *line)
4466         return ((struct tree_entry *) line->data)->name;
4469 static int
4470 tree_compare_entry(const struct line *line1, const struct line *line2)
4472         if (line1->type != line2->type)
4473                 return line1->type == LINE_TREE_DIR ? -1 : 1;
4474         return strcmp(tree_path(line1), tree_path(line2));
4477 static const enum sort_field tree_sort_fields[] = {
4478         ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
4479 };
4480 static struct sort_state tree_sort_state = SORT_STATE(tree_sort_fields);
4482 static int
4483 tree_compare(const void *l1, const void *l2)
4485         const struct line *line1 = (const struct line *) l1;
4486         const struct line *line2 = (const struct line *) l2;
4487         const struct tree_entry *entry1 = ((const struct line *) l1)->data;
4488         const struct tree_entry *entry2 = ((const struct line *) l2)->data;
4490         if (line1->type == LINE_TREE_HEAD)
4491                 return -1;
4492         if (line2->type == LINE_TREE_HEAD)
4493                 return 1;
4495         switch (get_sort_field(tree_sort_state)) {
4496         case ORDERBY_DATE:
4497                 return sort_order(tree_sort_state, timecmp(&entry1->time, &entry2->time));
4499         case ORDERBY_AUTHOR:
4500                 return sort_order(tree_sort_state, strcmp(entry1->author, entry2->author));
4502         case ORDERBY_NAME:
4503         default:
4504                 return sort_order(tree_sort_state, tree_compare_entry(line1, line2));
4505         }
4509 static struct line *
4510 tree_entry(struct view *view, enum line_type type, const char *path,
4511            const char *mode, const char *id)
4513         struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
4514         struct line *line = entry ? add_line_data(view, entry, type) : NULL;
4516         if (!entry || !line) {
4517                 free(entry);
4518                 return NULL;
4519         }
4521         strncpy(entry->name, path, strlen(path));
4522         if (mode)
4523                 entry->mode = strtoul(mode, NULL, 8);
4524         if (id)
4525                 string_copy_rev(entry->id, id);
4527         return line;
4530 static bool
4531 tree_read_date(struct view *view, char *text, bool *read_date)
4533         static const char *author_name;
4534         static struct time author_time;
4536         if (!text && *read_date) {
4537                 *read_date = FALSE;
4538                 return TRUE;
4540         } else if (!text) {
4541                 char *path = *opt_path ? opt_path : ".";
4542                 /* Find next entry to process */
4543                 const char *log_file[] = {
4544                         "git", "log", "--no-color", "--pretty=raw",
4545                                 "--cc", "--raw", view->id, "--", path, NULL
4546                 };
4548                 if (!view->lines) {
4549                         tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
4550                         report("Tree is empty");
4551                         return TRUE;
4552                 }
4554                 if (!start_update(view, log_file, opt_cdup)) {
4555                         report("Failed to load tree data");
4556                         return TRUE;
4557                 }
4559                 *read_date = TRUE;
4560                 return FALSE;
4562         } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
4563                 parse_author_line(text + STRING_SIZE("author "),
4564                                   &author_name, &author_time);
4566         } else if (*text == ':') {
4567                 char *pos;
4568                 size_t annotated = 1;
4569                 size_t i;
4571                 pos = strchr(text, '\t');
4572                 if (!pos)
4573                         return TRUE;
4574                 text = pos + 1;
4575                 if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
4576                         text += strlen(opt_path);
4577                 pos = strchr(text, '/');
4578                 if (pos)
4579                         *pos = 0;
4581                 for (i = 1; i < view->lines; i++) {
4582                         struct line *line = &view->line[i];
4583                         struct tree_entry *entry = line->data;
4585                         annotated += !!entry->author;
4586                         if (entry->author || strcmp(entry->name, text))
4587                                 continue;
4589                         entry->author = author_name;
4590                         entry->time = author_time;
4591                         line->dirty = 1;
4592                         break;
4593                 }
4595                 if (annotated == view->lines)
4596                         io_kill(view->pipe);
4597         }
4598         return TRUE;
4601 static bool
4602 tree_read(struct view *view, char *text)
4604         static bool read_date = FALSE;
4605         struct tree_entry *data;
4606         struct line *entry, *line;
4607         enum line_type type;
4608         size_t textlen = text ? strlen(text) : 0;
4609         char *path = text + SIZEOF_TREE_ATTR;
4611         if (read_date || !text)
4612                 return tree_read_date(view, text, &read_date);
4614         if (textlen <= SIZEOF_TREE_ATTR)
4615                 return FALSE;
4616         if (view->lines == 0 &&
4617             !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
4618                 return FALSE;
4620         /* Strip the path part ... */
4621         if (*opt_path) {
4622                 size_t pathlen = textlen - SIZEOF_TREE_ATTR;
4623                 size_t striplen = strlen(opt_path);
4625                 if (pathlen > striplen)
4626                         memmove(path, path + striplen,
4627                                 pathlen - striplen + 1);
4629                 /* Insert "link" to parent directory. */
4630                 if (view->lines == 1 &&
4631                     !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
4632                         return FALSE;
4633         }
4635         type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
4636         entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
4637         if (!entry)
4638                 return FALSE;
4639         data = entry->data;
4641         /* Skip "Directory ..." and ".." line. */
4642         for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
4643                 if (tree_compare_entry(line, entry) <= 0)
4644                         continue;
4646                 memmove(line + 1, line, (entry - line) * sizeof(*entry));
4648                 line->data = data;
4649                 line->type = type;
4650                 for (; line <= entry; line++)
4651                         line->dirty = line->cleareol = 1;
4652                 return TRUE;
4653         }
4655         if (tree_lineno > view->lineno) {
4656                 view->lineno = tree_lineno;
4657                 tree_lineno = 0;
4658         }
4660         return TRUE;
4663 static bool
4664 tree_draw(struct view *view, struct line *line, unsigned int lineno)
4666         struct tree_entry *entry = line->data;
4668         if (line->type == LINE_TREE_HEAD) {
4669                 if (draw_text(view, line->type, "Directory path /", TRUE))
4670                         return TRUE;
4671         } else {
4672                 if (draw_mode(view, entry->mode))
4673                         return TRUE;
4675                 if (opt_author && draw_author(view, entry->author))
4676                         return TRUE;
4678                 if (opt_date && draw_date(view, &entry->time))
4679                         return TRUE;
4680         }
4681         if (draw_text(view, line->type, entry->name, TRUE))
4682                 return TRUE;
4683         return TRUE;
4686 static void
4687 open_blob_editor(const char *id)
4689         const char *blob_argv[] = { "git", "cat-file", "blob", id, NULL };
4690         char file[SIZEOF_STR] = "/tmp/tigblob.XXXXXX";
4691         int fd = mkstemp(file);
4693         if (fd == -1)
4694                 report("Failed to create temporary file");
4695         else if (!io_run_append(blob_argv, fd))
4696                 report("Failed to save blob data to file");
4697         else
4698                 open_editor(file);
4699         if (fd != -1)
4700                 unlink(file);
4703 static enum request
4704 tree_request(struct view *view, enum request request, struct line *line)
4706         enum open_flags flags;
4707         struct tree_entry *entry = line->data;
4709         switch (request) {
4710         case REQ_VIEW_BLAME:
4711                 if (line->type != LINE_TREE_FILE) {
4712                         report("Blame only supported for files");
4713                         return REQ_NONE;
4714                 }
4716                 string_copy(opt_ref, view->vid);
4717                 return request;
4719         case REQ_EDIT:
4720                 if (line->type != LINE_TREE_FILE) {
4721                         report("Edit only supported for files");
4722                 } else if (!is_head_commit(view->vid)) {
4723                         open_blob_editor(entry->id);
4724                 } else {
4725                         open_editor(opt_file);
4726                 }
4727                 return REQ_NONE;
4729         case REQ_TOGGLE_SORT_FIELD:
4730         case REQ_TOGGLE_SORT_ORDER:
4731                 sort_view(view, request, &tree_sort_state, tree_compare);
4732                 return REQ_NONE;
4734         case REQ_PARENT:
4735                 if (!*opt_path) {
4736                         /* quit view if at top of tree */
4737                         return REQ_VIEW_CLOSE;
4738                 }
4739                 /* fake 'cd  ..' */
4740                 line = &view->line[1];
4741                 break;
4743         case REQ_ENTER:
4744                 break;
4746         default:
4747                 return request;
4748         }
4750         /* Cleanup the stack if the tree view is at a different tree. */
4751         while (!*opt_path && tree_stack)
4752                 pop_tree_stack_entry();
4754         switch (line->type) {
4755         case LINE_TREE_DIR:
4756                 /* Depending on whether it is a subdirectory or parent link
4757                  * mangle the path buffer. */
4758                 if (line == &view->line[1] && *opt_path) {
4759                         pop_tree_stack_entry();
4761                 } else {
4762                         const char *basename = tree_path(line);
4764                         push_tree_stack_entry(basename, view->lineno);
4765                 }
4767                 /* Trees and subtrees share the same ID, so they are not not
4768                  * unique like blobs. */
4769                 flags = OPEN_RELOAD;
4770                 request = REQ_VIEW_TREE;
4771                 break;
4773         case LINE_TREE_FILE:
4774                 flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
4775                 request = REQ_VIEW_BLOB;
4776                 break;
4778         default:
4779                 return REQ_NONE;
4780         }
4782         open_view(view, request, flags);
4783         if (request == REQ_VIEW_TREE)
4784                 view->lineno = tree_lineno;
4786         return REQ_NONE;
4789 static bool
4790 tree_grep(struct view *view, struct line *line)
4792         struct tree_entry *entry = line->data;
4793         const char *text[] = {
4794                 entry->name,
4795                 opt_author ? entry->author : "",
4796                 mkdate(&entry->time, opt_date),
4797                 NULL
4798         };
4800         return grep_text(view, text);
4803 static void
4804 tree_select(struct view *view, struct line *line)
4806         struct tree_entry *entry = line->data;
4808         if (line->type == LINE_TREE_FILE) {
4809                 string_copy_rev(ref_blob, entry->id);
4810                 string_format(opt_file, "%s%s", opt_path, tree_path(line));
4812         } else if (line->type != LINE_TREE_DIR) {
4813                 return;
4814         }
4816         string_copy_rev(view->ref, entry->id);
4819 static bool
4820 tree_prepare(struct view *view)
4822         if (view->lines == 0 && opt_prefix[0]) {
4823                 char *pos = opt_prefix;
4825                 while (pos && *pos) {
4826                         char *end = strchr(pos, '/');
4828                         if (end)
4829                                 *end = 0;
4830                         push_tree_stack_entry(pos, 0);
4831                         pos = end;
4832                         if (end) {
4833                                 *end = '/';
4834                                 pos++;
4835                         }
4836                 }
4838         } else if (strcmp(view->vid, view->id)) {
4839                 opt_path[0] = 0;
4840         }
4842         return prepare_io(view, opt_cdup, view->ops->argv, TRUE);
4845 static const char *tree_argv[SIZEOF_ARG] = {
4846         "git", "ls-tree", "%(commit)", "%(directory)", NULL
4847 };
4849 static struct view_ops tree_ops = {
4850         "file",
4851         tree_argv,
4852         NULL,
4853         tree_read,
4854         tree_draw,
4855         tree_request,
4856         tree_grep,
4857         tree_select,
4858         tree_prepare,
4859 };
4861 static bool
4862 blob_read(struct view *view, char *line)
4864         if (!line)
4865                 return TRUE;
4866         return add_line_text(view, line, LINE_DEFAULT) != NULL;
4869 static enum request
4870 blob_request(struct view *view, enum request request, struct line *line)
4872         switch (request) {
4873         case REQ_EDIT:
4874                 open_blob_editor(view->vid);
4875                 return REQ_NONE;
4876         default:
4877                 return pager_request(view, request, line);
4878         }
4881 static const char *blob_argv[SIZEOF_ARG] = {
4882         "git", "cat-file", "blob", "%(blob)", NULL
4883 };
4885 static struct view_ops blob_ops = {
4886         "line",
4887         blob_argv,
4888         NULL,
4889         blob_read,
4890         pager_draw,
4891         blob_request,
4892         pager_grep,
4893         pager_select,
4894 };
4896 /*
4897  * Blame backend
4898  *
4899  * Loading the blame view is a two phase job:
4900  *
4901  *  1. File content is read either using opt_file from the
4902  *     filesystem or using git-cat-file.
4903  *  2. Then blame information is incrementally added by
4904  *     reading output from git-blame.
4905  */
4907 struct blame_commit {
4908         char id[SIZEOF_REV];            /* SHA1 ID. */
4909         char title[128];                /* First line of the commit message. */
4910         const char *author;             /* Author of the commit. */
4911         struct time time;               /* Date from the author ident. */
4912         char filename[128];             /* Name of file. */
4913         char parent_id[SIZEOF_REV];     /* Parent/previous SHA1 ID. */
4914         char parent_filename[128];      /* Parent/previous name of file. */
4915 };
4917 struct blame {
4918         struct blame_commit *commit;
4919         unsigned long lineno;
4920         char text[1];
4921 };
4923 static bool
4924 blame_open(struct view *view)
4926         char path[SIZEOF_STR];
4927         size_t i;
4929         if (!view->prev && *opt_prefix) {
4930                 string_copy(path, opt_file);
4931                 if (!string_format(opt_file, "%s%s", opt_prefix, path))
4932                         return FALSE;
4933         }
4935         if (*opt_ref || !io_open(&view->io, "%s%s", opt_cdup, opt_file)) {
4936                 const char *blame_cat_file_argv[] = {
4937                         "git", "cat-file", "blob", path, NULL
4938                 };
4940                 if (!string_format(path, "%s:%s", opt_ref, opt_file) ||
4941                     !start_update(view, blame_cat_file_argv, opt_cdup))
4942                         return FALSE;
4943         }
4945         /* First pass: remove multiple references to the same commit. */
4946         for (i = 0; i < view->lines; i++) {
4947                 struct blame *blame = view->line[i].data;
4949                 if (blame->commit && blame->commit->id[0])
4950                         blame->commit->id[0] = 0;
4951                 else
4952                         blame->commit = NULL;
4953         }
4955         /* Second pass: free existing references. */
4956         for (i = 0; i < view->lines; i++) {
4957                 struct blame *blame = view->line[i].data;
4959                 if (blame->commit)
4960                         free(blame->commit);
4961         }
4963         setup_update(view, opt_file);
4964         string_format(view->ref, "%s ...", opt_file);
4966         return TRUE;
4969 static struct blame_commit *
4970 get_blame_commit(struct view *view, const char *id)
4972         size_t i;
4974         for (i = 0; i < view->lines; i++) {
4975                 struct blame *blame = view->line[i].data;
4977                 if (!blame->commit)
4978                         continue;
4980                 if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1))
4981                         return blame->commit;
4982         }
4984         {
4985                 struct blame_commit *commit = calloc(1, sizeof(*commit));
4987                 if (commit)
4988                         string_ncopy(commit->id, id, SIZEOF_REV);
4989                 return commit;
4990         }
4993 static bool
4994 parse_number(const char **posref, size_t *number, size_t min, size_t max)
4996         const char *pos = *posref;
4998         *posref = NULL;
4999         pos = strchr(pos + 1, ' ');
5000         if (!pos || !isdigit(pos[1]))
5001                 return FALSE;
5002         *number = atoi(pos + 1);
5003         if (*number < min || *number > max)
5004                 return FALSE;
5006         *posref = pos;
5007         return TRUE;
5010 static struct blame_commit *
5011 parse_blame_commit(struct view *view, const char *text, int *blamed)
5013         struct blame_commit *commit;
5014         struct blame *blame;
5015         const char *pos = text + SIZEOF_REV - 2;
5016         size_t orig_lineno = 0;
5017         size_t lineno;
5018         size_t group;
5020         if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
5021                 return NULL;
5023         if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
5024             !parse_number(&pos, &lineno, 1, view->lines) ||
5025             !parse_number(&pos, &group, 1, view->lines - lineno + 1))
5026                 return NULL;
5028         commit = get_blame_commit(view, text);
5029         if (!commit)
5030                 return NULL;
5032         *blamed += group;
5033         while (group--) {
5034                 struct line *line = &view->line[lineno + group - 1];
5036                 blame = line->data;
5037                 blame->commit = commit;
5038                 blame->lineno = orig_lineno + group - 1;
5039                 line->dirty = 1;
5040         }
5042         return commit;
5045 static bool
5046 blame_read_file(struct view *view, const char *line, bool *read_file)
5048         if (!line) {
5049                 const char *blame_argv[] = {
5050                         "git", "blame", "--incremental",
5051                                 *opt_ref ? opt_ref : "--incremental", "--", opt_file, NULL
5052                 };
5054                 if (view->lines == 0 && !view->prev)
5055                         die("No blame exist for %s", view->vid);
5057                 if (view->lines == 0 || !start_update(view, blame_argv, opt_cdup)) {
5058                         report("Failed to load blame data");
5059                         return TRUE;
5060                 }
5062                 *read_file = FALSE;
5063                 return FALSE;
5065         } else {
5066                 size_t linelen = strlen(line);
5067                 struct blame *blame = malloc(sizeof(*blame) + linelen);
5069                 if (!blame)
5070                         return FALSE;
5072                 blame->commit = NULL;
5073                 strncpy(blame->text, line, linelen);
5074                 blame->text[linelen] = 0;
5075                 return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
5076         }
5079 static bool
5080 match_blame_header(const char *name, char **line)
5082         size_t namelen = strlen(name);
5083         bool matched = !strncmp(name, *line, namelen);
5085         if (matched)
5086                 *line += namelen;
5088         return matched;
5091 static bool
5092 blame_read(struct view *view, char *line)
5094         static struct blame_commit *commit = NULL;
5095         static int blamed = 0;
5096         static bool read_file = TRUE;
5098         if (read_file)
5099                 return blame_read_file(view, line, &read_file);
5101         if (!line) {
5102                 /* Reset all! */
5103                 commit = NULL;
5104                 blamed = 0;
5105                 read_file = TRUE;
5106                 string_format(view->ref, "%s", view->vid);
5107                 if (view_is_displayed(view)) {
5108                         update_view_title(view);
5109                         redraw_view_from(view, 0);
5110                 }
5111                 return TRUE;
5112         }
5114         if (!commit) {
5115                 commit = parse_blame_commit(view, line, &blamed);
5116                 string_format(view->ref, "%s %2d%%", view->vid,
5117                               view->lines ? blamed * 100 / view->lines : 0);
5119         } else if (match_blame_header("author ", &line)) {
5120                 commit->author = get_author(line);
5122         } else if (match_blame_header("author-time ", &line)) {
5123                 parse_timesec(&commit->time, line);
5125         } else if (match_blame_header("author-tz ", &line)) {
5126                 parse_timezone(&commit->time, line);
5128         } else if (match_blame_header("summary ", &line)) {
5129                 string_ncopy(commit->title, line, strlen(line));
5131         } else if (match_blame_header("previous ", &line)) {
5132                 if (strlen(line) <= SIZEOF_REV)
5133                         return FALSE;
5134                 string_copy_rev(commit->parent_id, line);
5135                 line += SIZEOF_REV;
5136                 string_ncopy(commit->parent_filename, line, strlen(line));
5138         } else if (match_blame_header("filename ", &line)) {
5139                 string_ncopy(commit->filename, line, strlen(line));
5140                 commit = NULL;
5141         }
5143         return TRUE;
5146 static bool
5147 blame_draw(struct view *view, struct line *line, unsigned int lineno)
5149         struct blame *blame = line->data;
5150         struct time *time = NULL;
5151         const char *id = NULL, *author = NULL;
5153         if (blame->commit && *blame->commit->filename) {
5154                 id = blame->commit->id;
5155                 author = blame->commit->author;
5156                 time = &blame->commit->time;
5157         }
5159         if (opt_date && draw_date(view, time))
5160                 return TRUE;
5162         if (opt_author && draw_author(view, author))
5163                 return TRUE;
5165         if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
5166                 return TRUE;
5168         if (draw_lineno(view, lineno))
5169                 return TRUE;
5171         draw_text(view, LINE_DEFAULT, blame->text, TRUE);
5172         return TRUE;
5175 static bool
5176 check_blame_commit(struct blame *blame, bool check_null_id)
5178         if (!blame->commit)
5179                 report("Commit data not loaded yet");
5180         else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
5181                 report("No commit exist for the selected line");
5182         else
5183                 return TRUE;
5184         return FALSE;
5187 static void
5188 setup_blame_parent_line(struct view *view, struct blame *blame)
5190         char from[SIZEOF_REF + SIZEOF_STR];
5191         char to[SIZEOF_REF + SIZEOF_STR];
5192         const char *diff_tree_argv[] = {
5193                 "git", "diff", "--no-textconv", "--no-extdiff", "--no-color",
5194                         "-U0", from, to, "--", NULL
5195         };
5196         struct io io;
5197         int parent_lineno = -1;
5198         int blamed_lineno = -1;
5199         char *line;
5201         if (!string_format(from, "%s:%s", opt_ref, opt_file) ||
5202             !string_format(to, "%s:%s", blame->commit->id, blame->commit->filename) ||
5203             !io_run(&io, IO_RD, NULL, diff_tree_argv))
5204                 return;
5206         while ((line = io_get(&io, '\n', TRUE))) {
5207                 if (*line == '@') {
5208                         char *pos = strchr(line, '+');
5210                         parent_lineno = atoi(line + 4);
5211                         if (pos)
5212                                 blamed_lineno = atoi(pos + 1);
5214                 } else if (*line == '+' && parent_lineno != -1) {
5215                         if (blame->lineno == blamed_lineno - 1 &&
5216                             !strcmp(blame->text, line + 1)) {
5217                                 view->lineno = parent_lineno ? parent_lineno - 1 : 0;
5218                                 break;
5219                         }
5220                         blamed_lineno++;
5221                 }
5222         }
5224         io_done(&io);
5227 static enum request
5228 blame_request(struct view *view, enum request request, struct line *line)
5230         enum open_flags flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
5231         struct blame *blame = line->data;
5233         switch (request) {
5234         case REQ_VIEW_BLAME:
5235                 if (check_blame_commit(blame, TRUE)) {
5236                         string_copy(opt_ref, blame->commit->id);
5237                         string_copy(opt_file, blame->commit->filename);
5238                         if (blame->lineno)
5239                                 view->lineno = blame->lineno;
5240                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5241                 }
5242                 break;
5244         case REQ_PARENT:
5245                 if (!check_blame_commit(blame, TRUE))
5246                         break;
5247                 if (!*blame->commit->parent_id) {
5248                         report("The selected commit has no parents");
5249                 } else {
5250                         string_copy_rev(opt_ref, blame->commit->parent_id);
5251                         string_copy(opt_file, blame->commit->parent_filename);
5252                         setup_blame_parent_line(view, blame);
5253                         open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
5254                 }
5255                 break;
5257         case REQ_ENTER:
5258                 if (!check_blame_commit(blame, FALSE))
5259                         break;
5261                 if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
5262                     !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
5263                         break;
5265                 if (!strcmp(blame->commit->id, NULL_ID)) {
5266                         struct view *diff = VIEW(REQ_VIEW_DIFF);
5267                         const char *diff_index_argv[] = {
5268                                 "git", "diff-index", "--root", "--patch-with-stat",
5269                                         "-C", "-M", "HEAD", "--", view->vid, NULL
5270                         };
5272                         if (!*blame->commit->parent_id) {
5273                                 diff_index_argv[1] = "diff";
5274                                 diff_index_argv[2] = "--no-color";
5275                                 diff_index_argv[6] = "--";
5276                                 diff_index_argv[7] = "/dev/null";
5277                         }
5279                         if (!prepare_update(diff, diff_index_argv, NULL)) {
5280                                 report("Failed to allocate diff command");
5281                                 break;
5282                         }
5283                         flags |= OPEN_PREPARED;
5284                 }
5286                 open_view(view, REQ_VIEW_DIFF, flags);
5287                 if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
5288                         string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
5289                 break;
5291         default:
5292                 return request;
5293         }
5295         return REQ_NONE;
5298 static bool
5299 blame_grep(struct view *view, struct line *line)
5301         struct blame *blame = line->data;
5302         struct blame_commit *commit = blame->commit;
5303         const char *text[] = {
5304                 blame->text,
5305                 commit ? commit->title : "",
5306                 commit ? commit->id : "",
5307                 commit && opt_author ? commit->author : "",
5308                 commit ? mkdate(&commit->time, opt_date) : "",
5309                 NULL
5310         };
5312         return grep_text(view, text);
5315 static void
5316 blame_select(struct view *view, struct line *line)
5318         struct blame *blame = line->data;
5319         struct blame_commit *commit = blame->commit;
5321         if (!commit)
5322                 return;
5324         if (!strcmp(commit->id, NULL_ID))
5325                 string_ncopy(ref_commit, "HEAD", 4);
5326         else
5327                 string_copy_rev(ref_commit, commit->id);
5330 static struct view_ops blame_ops = {
5331         "line",
5332         NULL,
5333         blame_open,
5334         blame_read,
5335         blame_draw,
5336         blame_request,
5337         blame_grep,
5338         blame_select,
5339 };
5341 /*
5342  * Branch backend
5343  */
5345 struct branch {
5346         const char *author;             /* Author of the last commit. */
5347         struct time time;               /* Date of the last activity. */
5348         const struct ref *ref;          /* Name and commit ID information. */
5349 };
5351 static const struct ref branch_all;
5353 static const enum sort_field branch_sort_fields[] = {
5354         ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR
5355 };
5356 static struct sort_state branch_sort_state = SORT_STATE(branch_sort_fields);
5358 static int
5359 branch_compare(const void *l1, const void *l2)
5361         const struct branch *branch1 = ((const struct line *) l1)->data;
5362         const struct branch *branch2 = ((const struct line *) l2)->data;
5364         switch (get_sort_field(branch_sort_state)) {
5365         case ORDERBY_DATE:
5366                 return sort_order(branch_sort_state, timecmp(&branch1->time, &branch2->time));
5368         case ORDERBY_AUTHOR:
5369                 return sort_order(branch_sort_state, strcmp(branch1->author, branch2->author));
5371         case ORDERBY_NAME:
5372         default:
5373                 return sort_order(branch_sort_state, strcmp(branch1->ref->name, branch2->ref->name));
5374         }
5377 static bool
5378 branch_draw(struct view *view, struct line *line, unsigned int lineno)
5380         struct branch *branch = line->data;
5381         enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
5383         if (opt_date && draw_date(view, &branch->time))
5384                 return TRUE;
5386         if (opt_author && draw_author(view, branch->author))
5387                 return TRUE;
5389         draw_text(view, type, branch->ref == &branch_all ? "All branches" : branch->ref->name, TRUE);
5390         return TRUE;
5393 static enum request
5394 branch_request(struct view *view, enum request request, struct line *line)
5396         struct branch *branch = line->data;
5398         switch (request) {
5399         case REQ_REFRESH:
5400                 load_refs();
5401                 open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
5402                 return REQ_NONE;
5404         case REQ_TOGGLE_SORT_FIELD:
5405         case REQ_TOGGLE_SORT_ORDER:
5406                 sort_view(view, request, &branch_sort_state, branch_compare);
5407                 return REQ_NONE;
5409         case REQ_ENTER:
5410         {
5411                 const struct ref *ref = branch->ref;
5412                 const char *all_branches_argv[] = {
5413                         "git", "log", "--no-color", "--pretty=raw", "--parents",
5414                               "--topo-order",
5415                               ref == &branch_all ? "--all" : ref->name, NULL
5416                 };
5417                 struct view *main_view = VIEW(REQ_VIEW_MAIN);
5419                 if (!prepare_update(main_view, all_branches_argv, NULL))
5420                         report("Failed to load view of all branches");
5421                 else
5422                         open_view(view, REQ_VIEW_MAIN, OPEN_PREPARED | OPEN_SPLIT);
5423                 return REQ_NONE;
5424         }
5425         default:
5426                 return request;
5427         }
5430 static bool
5431 branch_read(struct view *view, char *line)
5433         static char id[SIZEOF_REV];
5434         struct branch *reference;
5435         size_t i;
5437         if (!line)
5438                 return TRUE;
5440         switch (get_line_type(line)) {
5441         case LINE_COMMIT:
5442                 string_copy_rev(id, line + STRING_SIZE("commit "));
5443                 return TRUE;
5445         case LINE_AUTHOR:
5446                 for (i = 0, reference = NULL; i < view->lines; i++) {
5447                         struct branch *branch = view->line[i].data;
5449                         if (strcmp(branch->ref->id, id))
5450                                 continue;
5452                         view->line[i].dirty = TRUE;
5453                         if (reference) {
5454                                 branch->author = reference->author;
5455                                 branch->time = reference->time;
5456                                 continue;
5457                         }
5459                         parse_author_line(line + STRING_SIZE("author "),
5460                                           &branch->author, &branch->time);
5461                         reference = branch;
5462                 }
5463                 return TRUE;
5465         default:
5466                 return TRUE;
5467         }
5471 static bool
5472 branch_open_visitor(void *data, const struct ref *ref)
5474         struct view *view = data;
5475         struct branch *branch;
5477         if (ref->tag || ref->ltag || ref->remote)
5478                 return TRUE;
5480         branch = calloc(1, sizeof(*branch));
5481         if (!branch)
5482                 return FALSE;
5484         branch->ref = ref;
5485         return !!add_line_data(view, branch, LINE_DEFAULT);
5488 static bool
5489 branch_open(struct view *view)
5491         const char *branch_log[] = {
5492                 "git", "log", "--no-color", "--pretty=raw",
5493                         "--simplify-by-decoration", "--all", NULL
5494         };
5496         if (!start_update(view, branch_log, NULL)) {
5497                 report("Failed to load branch data");
5498                 return TRUE;
5499         }
5501         setup_update(view, view->id);
5502         branch_open_visitor(view, &branch_all);
5503         foreach_ref(branch_open_visitor, view);
5504         view->p_restore = TRUE;
5506         return TRUE;
5509 static bool
5510 branch_grep(struct view *view, struct line *line)
5512         struct branch *branch = line->data;
5513         const char *text[] = {
5514                 branch->ref->name,
5515                 branch->author,
5516                 NULL
5517         };
5519         return grep_text(view, text);
5522 static void
5523 branch_select(struct view *view, struct line *line)
5525         struct branch *branch = line->data;
5527         string_copy_rev(view->ref, branch->ref->id);
5528         string_copy_rev(ref_commit, branch->ref->id);
5529         string_copy_rev(ref_head, branch->ref->id);
5530         string_copy_rev(ref_branch, branch->ref->name);
5533 static struct view_ops branch_ops = {
5534         "branch",
5535         NULL,
5536         branch_open,
5537         branch_read,
5538         branch_draw,
5539         branch_request,
5540         branch_grep,
5541         branch_select,
5542 };
5544 /*
5545  * Status backend
5546  */
5548 struct status {
5549         char status;
5550         struct {
5551                 mode_t mode;
5552                 char rev[SIZEOF_REV];
5553                 char name[SIZEOF_STR];
5554         } old;
5555         struct {
5556                 mode_t mode;
5557                 char rev[SIZEOF_REV];
5558                 char name[SIZEOF_STR];
5559         } new;
5560 };
5562 static char status_onbranch[SIZEOF_STR];
5563 static struct status stage_status;
5564 static enum line_type stage_line_type;
5565 static size_t stage_chunks;
5566 static int *stage_chunk;
5568 DEFINE_ALLOCATOR(realloc_ints, int, 32)
5570 /* This should work even for the "On branch" line. */
5571 static inline bool
5572 status_has_none(struct view *view, struct line *line)
5574         return line < view->line + view->lines && !line[1].data;
5577 /* Get fields from the diff line:
5578  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
5579  */
5580 static inline bool
5581 status_get_diff(struct status *file, const char *buf, size_t bufsize)
5583         const char *old_mode = buf +  1;
5584         const char *new_mode = buf +  8;
5585         const char *old_rev  = buf + 15;
5586         const char *new_rev  = buf + 56;
5587         const char *status   = buf + 97;
5589         if (bufsize < 98 ||
5590             old_mode[-1] != ':' ||
5591             new_mode[-1] != ' ' ||
5592             old_rev[-1]  != ' ' ||
5593             new_rev[-1]  != ' ' ||
5594             status[-1]   != ' ')
5595                 return FALSE;
5597         file->status = *status;
5599         string_copy_rev(file->old.rev, old_rev);
5600         string_copy_rev(file->new.rev, new_rev);
5602         file->old.mode = strtoul(old_mode, NULL, 8);
5603         file->new.mode = strtoul(new_mode, NULL, 8);
5605         file->old.name[0] = file->new.name[0] = 0;
5607         return TRUE;
5610 static bool
5611 status_run(struct view *view, const char *argv[], char status, enum line_type type)
5613         struct status *unmerged = NULL;
5614         char *buf;
5615         struct io io;
5617         if (!io_run(&io, IO_RD, opt_cdup, argv))
5618                 return FALSE;
5620         add_line_data(view, NULL, type);
5622         while ((buf = io_get(&io, 0, TRUE))) {
5623                 struct status *file = unmerged;
5625                 if (!file) {
5626                         file = calloc(1, sizeof(*file));
5627                         if (!file || !add_line_data(view, file, type))
5628                                 goto error_out;
5629                 }
5631                 /* Parse diff info part. */
5632                 if (status) {
5633                         file->status = status;
5634                         if (status == 'A')
5635                                 string_copy(file->old.rev, NULL_ID);
5637                 } else if (!file->status || file == unmerged) {
5638                         if (!status_get_diff(file, buf, strlen(buf)))
5639                                 goto error_out;
5641                         buf = io_get(&io, 0, TRUE);
5642                         if (!buf)
5643                                 break;
5645                         /* Collapse all modified entries that follow an
5646                          * associated unmerged entry. */
5647                         if (unmerged == file) {
5648                                 unmerged->status = 'U';
5649                                 unmerged = NULL;
5650                         } else if (file->status == 'U') {
5651                                 unmerged = file;
5652                         }
5653                 }
5655                 /* Grab the old name for rename/copy. */
5656                 if (!*file->old.name &&
5657                     (file->status == 'R' || file->status == 'C')) {
5658                         string_ncopy(file->old.name, buf, strlen(buf));
5660                         buf = io_get(&io, 0, TRUE);
5661                         if (!buf)
5662                                 break;
5663                 }
5665                 /* git-ls-files just delivers a NUL separated list of
5666                  * file names similar to the second half of the
5667                  * git-diff-* output. */
5668                 string_ncopy(file->new.name, buf, strlen(buf));
5669                 if (!*file->old.name)
5670                         string_copy(file->old.name, file->new.name);
5671                 file = NULL;
5672         }
5674         if (io_error(&io)) {
5675 error_out:
5676                 io_done(&io);
5677                 return FALSE;
5678         }
5680         if (!view->line[view->lines - 1].data)
5681                 add_line_data(view, NULL, LINE_STAT_NONE);
5683         io_done(&io);
5684         return TRUE;
5687 /* Don't show unmerged entries in the staged section. */
5688 static const char *status_diff_index_argv[] = {
5689         "git", "diff-index", "-z", "--diff-filter=ACDMRTXB",
5690                              "--cached", "-M", "HEAD", NULL
5691 };
5693 static const char *status_diff_files_argv[] = {
5694         "git", "diff-files", "-z", NULL
5695 };
5697 static const char *status_list_other_argv[] = {
5698         "git", "ls-files", "-z", "--others", "--exclude-standard", opt_prefix, NULL, NULL,
5699 };
5701 static const char *status_list_no_head_argv[] = {
5702         "git", "ls-files", "-z", "--cached", "--exclude-standard", NULL
5703 };
5705 static const char *update_index_argv[] = {
5706         "git", "update-index", "-q", "--unmerged", "--refresh", NULL
5707 };
5709 /* Restore the previous line number to stay in the context or select a
5710  * line with something that can be updated. */
5711 static void
5712 status_restore(struct view *view)
5714         if (view->p_lineno >= view->lines)
5715                 view->p_lineno = view->lines - 1;
5716         while (view->p_lineno < view->lines && !view->line[view->p_lineno].data)
5717                 view->p_lineno++;
5718         while (view->p_lineno > 0 && !view->line[view->p_lineno].data)
5719                 view->p_lineno--;
5721         /* If the above fails, always skip the "On branch" line. */
5722         if (view->p_lineno < view->lines)
5723                 view->lineno = view->p_lineno;
5724         else
5725                 view->lineno = 1;
5727         if (view->lineno < view->offset)
5728                 view->offset = view->lineno;
5729         else if (view->offset + view->height <= view->lineno)
5730                 view->offset = view->lineno - view->height + 1;
5732         view->p_restore = FALSE;
5735 static void
5736 status_update_onbranch(void)
5738         static const char *paths[][2] = {
5739                 { "rebase-apply/rebasing",      "Rebasing" },
5740                 { "rebase-apply/applying",      "Applying mailbox" },
5741                 { "rebase-apply/",              "Rebasing mailbox" },
5742                 { "rebase-merge/interactive",   "Interactive rebase" },
5743                 { "rebase-merge/",              "Rebase merge" },
5744                 { "MERGE_HEAD",                 "Merging" },
5745                 { "BISECT_LOG",                 "Bisecting" },
5746                 { "HEAD",                       "On branch" },
5747         };
5748         char buf[SIZEOF_STR];
5749         struct stat stat;
5750         int i;
5752         if (is_initial_commit()) {
5753                 string_copy(status_onbranch, "Initial commit");
5754                 return;
5755         }
5757         for (i = 0; i < ARRAY_SIZE(paths); i++) {
5758                 char *head = opt_head;
5760                 if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
5761                     lstat(buf, &stat) < 0)
5762                         continue;
5764                 if (!*opt_head) {
5765                         struct io io;
5767                         if (io_open(&io, "%s/rebase-merge/head-name", opt_git_dir) &&
5768                             io_read_buf(&io, buf, sizeof(buf))) {
5769                                 head = buf;
5770                                 if (!prefixcmp(head, "refs/heads/"))
5771                                         head += STRING_SIZE("refs/heads/");
5772                         }
5773                 }
5775                 if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
5776                         string_copy(status_onbranch, opt_head);
5777                 return;
5778         }
5780         string_copy(status_onbranch, "Not currently on any branch");
5783 /* First parse staged info using git-diff-index(1), then parse unstaged
5784  * info using git-diff-files(1), and finally untracked files using
5785  * git-ls-files(1). */
5786 static bool
5787 status_open(struct view *view)
5789         reset_view(view);
5791         add_line_data(view, NULL, LINE_STAT_HEAD);
5792         status_update_onbranch();
5794         io_run_bg(update_index_argv);
5796         if (is_initial_commit()) {
5797                 if (!status_run(view, status_list_no_head_argv, 'A', LINE_STAT_STAGED))
5798                         return FALSE;
5799         } else if (!status_run(view, status_diff_index_argv, 0, LINE_STAT_STAGED)) {
5800                 return FALSE;
5801         }
5803         if (!opt_untracked_dirs_content)
5804                 status_list_other_argv[ARRAY_SIZE(status_list_other_argv) - 2] = "--directory";
5806         if (!status_run(view, status_diff_files_argv, 0, LINE_STAT_UNSTAGED) ||
5807             !status_run(view, status_list_other_argv, '?', LINE_STAT_UNTRACKED))
5808                 return FALSE;
5810         /* Restore the exact position or use the specialized restore
5811          * mode? */
5812         if (!view->p_restore)
5813                 status_restore(view);
5814         return TRUE;
5817 static bool
5818 status_draw(struct view *view, struct line *line, unsigned int lineno)
5820         struct status *status = line->data;
5821         enum line_type type;
5822         const char *text;
5824         if (!status) {
5825                 switch (line->type) {
5826                 case LINE_STAT_STAGED:
5827                         type = LINE_STAT_SECTION;
5828                         text = "Changes to be committed:";
5829                         break;
5831                 case LINE_STAT_UNSTAGED:
5832                         type = LINE_STAT_SECTION;
5833                         text = "Changed but not updated:";
5834                         break;
5836                 case LINE_STAT_UNTRACKED:
5837                         type = LINE_STAT_SECTION;
5838                         text = "Untracked files:";
5839                         break;
5841                 case LINE_STAT_NONE:
5842                         type = LINE_DEFAULT;
5843                         text = "  (no files)";
5844                         break;
5846                 case LINE_STAT_HEAD:
5847                         type = LINE_STAT_HEAD;
5848                         text = status_onbranch;
5849                         break;
5851                 default:
5852                         return FALSE;
5853                 }
5854         } else {
5855                 static char buf[] = { '?', ' ', ' ', ' ', 0 };
5857                 buf[0] = status->status;
5858                 if (draw_text(view, line->type, buf, TRUE))
5859                         return TRUE;
5860                 type = LINE_DEFAULT;
5861                 text = status->new.name;
5862         }
5864         draw_text(view, type, text, TRUE);
5865         return TRUE;
5868 static enum request
5869 status_load_error(struct view *view, struct view *stage, const char *path)
5871         if (displayed_views() == 2 || display[current_view] != view)
5872                 maximize_view(view);
5873         report("Failed to load '%s': %s", path, io_strerror(&stage->io));
5874         return REQ_NONE;
5877 static enum request
5878 status_enter(struct view *view, struct line *line)
5880         struct status *status = line->data;
5881         const char *oldpath = status ? status->old.name : NULL;
5882         /* Diffs for unmerged entries are empty when passing the new
5883          * path, so leave it empty. */
5884         const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
5885         const char *info;
5886         enum open_flags split;
5887         struct view *stage = VIEW(REQ_VIEW_STAGE);
5889         if (line->type == LINE_STAT_NONE ||
5890             (!status && line[1].type == LINE_STAT_NONE)) {
5891                 report("No file to diff");
5892                 return REQ_NONE;
5893         }
5895         switch (line->type) {
5896         case LINE_STAT_STAGED:
5897                 if (is_initial_commit()) {
5898                         const char *no_head_diff_argv[] = {
5899                                 "git", "diff", "--no-color", "--patch-with-stat",
5900                                         "--", "/dev/null", newpath, NULL
5901                         };
5903                         if (!prepare_update(stage, no_head_diff_argv, opt_cdup))
5904                                 return status_load_error(view, stage, newpath);
5905                 } else {
5906                         const char *index_show_argv[] = {
5907                                 "git", "diff-index", "--root", "--patch-with-stat",
5908                                         "-C", "-M", "--cached", "HEAD", "--",
5909                                         oldpath, newpath, NULL
5910                         };
5912                         if (!prepare_update(stage, index_show_argv, opt_cdup))
5913                                 return status_load_error(view, stage, newpath);
5914                 }
5916                 if (status)
5917                         info = "Staged changes to %s";
5918                 else
5919                         info = "Staged changes";
5920                 break;
5922         case LINE_STAT_UNSTAGED:
5923         {
5924                 const char *files_show_argv[] = {
5925                         "git", "diff-files", "--root", "--patch-with-stat",
5926                                 "-C", "-M", "--", oldpath, newpath, NULL
5927                 };
5929                 if (!prepare_update(stage, files_show_argv, opt_cdup))
5930                         return status_load_error(view, stage, newpath);
5931                 if (status)
5932                         info = "Unstaged changes to %s";
5933                 else
5934                         info = "Unstaged changes";
5935                 break;
5936         }
5937         case LINE_STAT_UNTRACKED:
5938                 if (!newpath) {
5939                         report("No file to show");
5940                         return REQ_NONE;
5941                 }
5943                 if (!suffixcmp(status->new.name, -1, "/")) {
5944                         report("Cannot display a directory");
5945                         return REQ_NONE;
5946                 }
5948                 if (!prepare_update_file(stage, newpath))
5949                         return status_load_error(view, stage, newpath);
5950                 info = "Untracked file %s";
5951                 break;
5953         case LINE_STAT_HEAD:
5954                 return REQ_NONE;
5956         default:
5957                 die("line type %d not handled in switch", line->type);
5958         }
5960         split = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
5961         open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
5962         if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
5963                 if (status) {
5964                         stage_status = *status;
5965                 } else {
5966                         memset(&stage_status, 0, sizeof(stage_status));
5967                 }
5969                 stage_line_type = line->type;
5970                 stage_chunks = 0;
5971                 string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.new.name);
5972         }
5974         return REQ_NONE;
5977 static bool
5978 status_exists(struct status *status, enum line_type type)
5980         struct view *view = VIEW(REQ_VIEW_STATUS);
5981         unsigned long lineno;
5983         for (lineno = 0; lineno < view->lines; lineno++) {
5984                 struct line *line = &view->line[lineno];
5985                 struct status *pos = line->data;
5987                 if (line->type != type)
5988                         continue;
5989                 if (!pos && (!status || !status->status) && line[1].data) {
5990                         select_view_line(view, lineno);
5991                         return TRUE;
5992                 }
5993                 if (pos && !strcmp(status->new.name, pos->new.name)) {
5994                         select_view_line(view, lineno);
5995                         return TRUE;
5996                 }
5997         }
5999         return FALSE;
6003 static bool
6004 status_update_prepare(struct io *io, enum line_type type)
6006         const char *staged_argv[] = {
6007                 "git", "update-index", "-z", "--index-info", NULL
6008         };
6009         const char *others_argv[] = {
6010                 "git", "update-index", "-z", "--add", "--remove", "--stdin", NULL
6011         };
6013         switch (type) {
6014         case LINE_STAT_STAGED:
6015                 return io_run(io, IO_WR, opt_cdup, staged_argv);
6017         case LINE_STAT_UNSTAGED:
6018         case LINE_STAT_UNTRACKED:
6019                 return io_run(io, IO_WR, opt_cdup, others_argv);
6021         default:
6022                 die("line type %d not handled in switch", type);
6023                 return FALSE;
6024         }
6027 static bool
6028 status_update_write(struct io *io, struct status *status, enum line_type type)
6030         char buf[SIZEOF_STR];
6031         size_t bufsize = 0;
6033         switch (type) {
6034         case LINE_STAT_STAGED:
6035                 if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
6036                                         status->old.mode,
6037                                         status->old.rev,
6038                                         status->old.name, 0))
6039                         return FALSE;
6040                 break;
6042         case LINE_STAT_UNSTAGED:
6043         case LINE_STAT_UNTRACKED:
6044                 if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0))
6045                         return FALSE;
6046                 break;
6048         default:
6049                 die("line type %d not handled in switch", type);
6050         }
6052         return io_write(io, buf, bufsize);
6055 static bool
6056 status_update_file(struct status *status, enum line_type type)
6058         struct io io;
6059         bool result;
6061         if (!status_update_prepare(&io, type))
6062                 return FALSE;
6064         result = status_update_write(&io, status, type);
6065         return io_done(&io) && result;
6068 static bool
6069 status_update_files(struct view *view, struct line *line)
6071         char buf[sizeof(view->ref)];
6072         struct io io;
6073         bool result = TRUE;
6074         struct line *pos = view->line + view->lines;
6075         int files = 0;
6076         int file, done;
6077         int cursor_y = -1, cursor_x = -1;
6079         if (!status_update_prepare(&io, line->type))
6080                 return FALSE;
6082         for (pos = line; pos < view->line + view->lines && pos->data; pos++)
6083                 files++;
6085         string_copy(buf, view->ref);
6086         getsyx(cursor_y, cursor_x);
6087         for (file = 0, done = 5; result && file < files; line++, file++) {
6088                 int almost_done = file * 100 / files;
6090                 if (almost_done > done) {
6091                         done = almost_done;
6092                         string_format(view->ref, "updating file %u of %u (%d%% done)",
6093                                       file, files, done);
6094                         update_view_title(view);
6095                         setsyx(cursor_y, cursor_x);
6096                         doupdate();
6097                 }
6098                 result = status_update_write(&io, line->data, line->type);
6099         }
6100         string_copy(view->ref, buf);
6102         return io_done(&io) && result;
6105 static bool
6106 status_update(struct view *view)
6108         struct line *line = &view->line[view->lineno];
6110         assert(view->lines);
6112         if (!line->data) {
6113                 /* This should work even for the "On branch" line. */
6114                 if (line < view->line + view->lines && !line[1].data) {
6115                         report("Nothing to update");
6116                         return FALSE;
6117                 }
6119                 if (!status_update_files(view, line + 1)) {
6120                         report("Failed to update file status");
6121                         return FALSE;
6122                 }
6124         } else if (!status_update_file(line->data, line->type)) {
6125                 report("Failed to update file status");
6126                 return FALSE;
6127         }
6129         return TRUE;
6132 static bool
6133 status_revert(struct status *status, enum line_type type, bool has_none)
6135         if (!status || type != LINE_STAT_UNSTAGED) {
6136                 if (type == LINE_STAT_STAGED) {
6137                         report("Cannot revert changes to staged files");
6138                 } else if (type == LINE_STAT_UNTRACKED) {
6139                         report("Cannot revert changes to untracked files");
6140                 } else if (has_none) {
6141                         report("Nothing to revert");
6142                 } else {
6143                         report("Cannot revert changes to multiple files");
6144                 }
6146         } else if (prompt_yesno("Are you sure you want to revert changes?")) {
6147                 char mode[10] = "100644";
6148                 const char *reset_argv[] = {
6149                         "git", "update-index", "--cacheinfo", mode,
6150                                 status->old.rev, status->old.name, NULL
6151                 };
6152                 const char *checkout_argv[] = {
6153                         "git", "checkout", "--", status->old.name, NULL
6154                 };
6156                 if (status->status == 'U') {
6157                         string_format(mode, "%5o", status->old.mode);
6159                         if (status->old.mode == 0 && status->new.mode == 0) {
6160                                 reset_argv[2] = "--force-remove";
6161                                 reset_argv[3] = status->old.name;
6162                                 reset_argv[4] = NULL;
6163                         }
6165                         if (!io_run_fg(reset_argv, opt_cdup))
6166                                 return FALSE;
6167                         if (status->old.mode == 0 && status->new.mode == 0)
6168                                 return TRUE;
6169                 }
6171                 return io_run_fg(checkout_argv, opt_cdup);
6172         }
6174         return FALSE;
6177 static enum request
6178 status_request(struct view *view, enum request request, struct line *line)
6180         struct status *status = line->data;
6182         switch (request) {
6183         case REQ_STATUS_UPDATE:
6184                 if (!status_update(view))
6185                         return REQ_NONE;
6186                 break;
6188         case REQ_STATUS_REVERT:
6189                 if (!status_revert(status, line->type, status_has_none(view, line)))
6190                         return REQ_NONE;
6191                 break;
6193         case REQ_STATUS_MERGE:
6194                 if (!status || status->status != 'U') {
6195                         report("Merging only possible for files with unmerged status ('U').");
6196                         return REQ_NONE;
6197                 }
6198                 open_mergetool(status->new.name);
6199                 break;
6201         case REQ_EDIT:
6202                 if (!status)
6203                         return request;
6204                 if (status->status == 'D') {
6205                         report("File has been deleted.");
6206                         return REQ_NONE;
6207                 }
6209                 open_editor(status->new.name);
6210                 break;
6212         case REQ_VIEW_BLAME:
6213                 if (status)
6214                         opt_ref[0] = 0;
6215                 return request;
6217         case REQ_ENTER:
6218                 /* After returning the status view has been split to
6219                  * show the stage view. No further reloading is
6220                  * necessary. */
6221                 return status_enter(view, line);
6223         case REQ_REFRESH:
6224                 /* Simply reload the view. */
6225                 break;
6227         default:
6228                 return request;
6229         }
6231         open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
6233         return REQ_NONE;
6236 static void
6237 status_select(struct view *view, struct line *line)
6239         struct status *status = line->data;
6240         char file[SIZEOF_STR] = "all files";
6241         const char *text;
6242         const char *key;
6244         if (status && !string_format(file, "'%s'", status->new.name))
6245                 return;
6247         if (!status && line[1].type == LINE_STAT_NONE)
6248                 line++;
6250         switch (line->type) {
6251         case LINE_STAT_STAGED:
6252                 text = "Press %s to unstage %s for commit";
6253                 break;
6255         case LINE_STAT_UNSTAGED:
6256                 text = "Press %s to stage %s for commit";
6257                 break;
6259         case LINE_STAT_UNTRACKED:
6260                 text = "Press %s to stage %s for addition";
6261                 break;
6263         case LINE_STAT_HEAD:
6264         case LINE_STAT_NONE:
6265                 text = "Nothing to update";
6266                 break;
6268         default:
6269                 die("line type %d not handled in switch", line->type);
6270         }
6272         if (status && status->status == 'U') {
6273                 text = "Press %s to resolve conflict in %s";
6274                 key = get_key(KEYMAP_STATUS, REQ_STATUS_MERGE);
6276         } else {
6277                 key = get_key(KEYMAP_STATUS, REQ_STATUS_UPDATE);
6278         }
6280         string_format(view->ref, text, key, file);
6281         if (status)
6282                 string_copy(opt_file, status->new.name);
6285 static bool
6286 status_grep(struct view *view, struct line *line)
6288         struct status *status = line->data;
6290         if (status) {
6291                 const char buf[2] = { status->status, 0 };
6292                 const char *text[] = { status->new.name, buf, NULL };
6294                 return grep_text(view, text);
6295         }
6297         return FALSE;
6300 static struct view_ops status_ops = {
6301         "file",
6302         NULL,
6303         status_open,
6304         NULL,
6305         status_draw,
6306         status_request,
6307         status_grep,
6308         status_select,
6309 };
6312 static bool
6313 stage_diff_write(struct io *io, struct line *line, struct line *end)
6315         while (line < end) {
6316                 if (!io_write(io, line->data, strlen(line->data)) ||
6317                     !io_write(io, "\n", 1))
6318                         return FALSE;
6319                 line++;
6320                 if (line->type == LINE_DIFF_CHUNK ||
6321                     line->type == LINE_DIFF_HEADER)
6322                         break;
6323         }
6325         return TRUE;
6328 static struct line *
6329 stage_diff_find(struct view *view, struct line *line, enum line_type type)
6331         for (; view->line < line; line--)
6332                 if (line->type == type)
6333                         return line;
6335         return NULL;
6338 static bool
6339 stage_apply_chunk(struct view *view, struct line *chunk, bool revert)
6341         const char *apply_argv[SIZEOF_ARG] = {
6342                 "git", "apply", "--whitespace=nowarn", NULL
6343         };
6344         struct line *diff_hdr;
6345         struct io io;
6346         int argc = 3;
6348         diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
6349         if (!diff_hdr)
6350                 return FALSE;
6352         if (!revert)
6353                 apply_argv[argc++] = "--cached";
6354         if (revert || stage_line_type == LINE_STAT_STAGED)
6355                 apply_argv[argc++] = "-R";
6356         apply_argv[argc++] = "-";
6357         apply_argv[argc++] = NULL;
6358         if (!io_run(&io, IO_WR, opt_cdup, apply_argv))
6359                 return FALSE;
6361         if (!stage_diff_write(&io, diff_hdr, chunk) ||
6362             !stage_diff_write(&io, chunk, view->line + view->lines))
6363                 chunk = NULL;
6365         io_done(&io);
6366         io_run_bg(update_index_argv);
6368         return chunk ? TRUE : FALSE;
6371 static bool
6372 stage_update(struct view *view, struct line *line)
6374         struct line *chunk = NULL;
6376         if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
6377                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6379         if (chunk) {
6380                 if (!stage_apply_chunk(view, chunk, FALSE)) {
6381                         report("Failed to apply chunk");
6382                         return FALSE;
6383                 }
6385         } else if (!stage_status.status) {
6386                 view = VIEW(REQ_VIEW_STATUS);
6388                 for (line = view->line; line < view->line + view->lines; line++)
6389                         if (line->type == stage_line_type)
6390                                 break;
6392                 if (!status_update_files(view, line + 1)) {
6393                         report("Failed to update files");
6394                         return FALSE;
6395                 }
6397         } else if (!status_update_file(&stage_status, stage_line_type)) {
6398                 report("Failed to update file");
6399                 return FALSE;
6400         }
6402         return TRUE;
6405 static bool
6406 stage_revert(struct view *view, struct line *line)
6408         struct line *chunk = NULL;
6410         if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
6411                 chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
6413         if (chunk) {
6414                 if (!prompt_yesno("Are you sure you want to revert changes?"))
6415                         return FALSE;
6417                 if (!stage_apply_chunk(view, chunk, TRUE)) {
6418                         report("Failed to revert chunk");
6419                         return FALSE;
6420                 }
6421                 return TRUE;
6423         } else {
6424                 return status_revert(stage_status.status ? &stage_status : NULL,
6425                                      stage_line_type, FALSE);
6426         }
6430 static void
6431 stage_next(struct view *view, struct line *line)
6433         int i;
6435         if (!stage_chunks) {
6436                 for (line = view->line; line < view->line + view->lines; line++) {
6437                         if (line->type != LINE_DIFF_CHUNK)
6438                                 continue;
6440                         if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
6441                                 report("Allocation failure");
6442                                 return;
6443                         }
6445                         stage_chunk[stage_chunks++] = line - view->line;
6446                 }
6447         }
6449         for (i = 0; i < stage_chunks; i++) {
6450                 if (stage_chunk[i] > view->lineno) {
6451                         do_scroll_view(view, stage_chunk[i] - view->lineno);
6452                         report("Chunk %d of %d", i + 1, stage_chunks);
6453                         return;
6454                 }
6455         }
6457         report("No next chunk found");
6460 static enum request
6461 stage_request(struct view *view, enum request request, struct line *line)
6463         switch (request) {
6464         case REQ_STATUS_UPDATE:
6465                 if (!stage_update(view, line))
6466                         return REQ_NONE;
6467                 break;
6469         case REQ_STATUS_REVERT:
6470                 if (!stage_revert(view, line))
6471                         return REQ_NONE;
6472                 break;
6474         case REQ_STAGE_NEXT:
6475                 if (stage_line_type == LINE_STAT_UNTRACKED) {
6476                         report("File is untracked; press %s to add",
6477                                get_key(KEYMAP_STAGE, REQ_STATUS_UPDATE));
6478                         return REQ_NONE;
6479                 }
6480                 stage_next(view, line);
6481                 return REQ_NONE;
6483         case REQ_EDIT:
6484                 if (!stage_status.new.name[0])
6485                         return request;
6486                 if (stage_status.status == 'D') {
6487                         report("File has been deleted.");
6488                         return REQ_NONE;
6489                 }
6491                 open_editor(stage_status.new.name);
6492                 break;
6494         case REQ_REFRESH:
6495                 /* Reload everything ... */
6496                 break;
6498         case REQ_VIEW_BLAME:
6499                 if (stage_status.new.name[0]) {
6500                         string_copy(opt_file, stage_status.new.name);
6501                         opt_ref[0] = 0;
6502                 }
6503                 return request;
6505         case REQ_ENTER:
6506                 return pager_request(view, request, line);
6508         default:
6509                 return request;
6510         }
6512         VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
6513         open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
6515         /* Check whether the staged entry still exists, and close the
6516          * stage view if it doesn't. */
6517         if (!status_exists(&stage_status, stage_line_type)) {
6518                 status_restore(VIEW(REQ_VIEW_STATUS));
6519                 return REQ_VIEW_CLOSE;
6520         }
6522         if (stage_line_type == LINE_STAT_UNTRACKED) {
6523                 if (!suffixcmp(stage_status.new.name, -1, "/")) {
6524                         report("Cannot display a directory");
6525                         return REQ_NONE;
6526                 }
6528                 if (!prepare_update_file(view, stage_status.new.name)) {
6529                         report("Failed to open file: %s", strerror(errno));
6530                         return REQ_NONE;
6531                 }
6532         }
6533         open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
6535         return REQ_NONE;
6538 static struct view_ops stage_ops = {
6539         "line",
6540         NULL,
6541         NULL,
6542         pager_read,
6543         pager_draw,
6544         stage_request,
6545         pager_grep,
6546         pager_select,
6547 };
6550 /*
6551  * Revision graph
6552  */
6554 struct commit {
6555         char id[SIZEOF_REV];            /* SHA1 ID. */
6556         char title[128];                /* First line of the commit message. */
6557         const char *author;             /* Author of the commit. */
6558         struct time time;               /* Date from the author ident. */
6559         struct ref_list *refs;          /* Repository references. */
6560         chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
6561         size_t graph_size;              /* The width of the graph array. */
6562         bool has_parents;               /* Rewritten --parents seen. */
6563 };
6565 /* Size of rev graph with no  "padding" columns */
6566 #define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
6568 struct rev_graph {
6569         struct rev_graph *prev, *next, *parents;
6570         char rev[SIZEOF_REVITEMS][SIZEOF_REV];
6571         size_t size;
6572         struct commit *commit;
6573         size_t pos;
6574         unsigned int boundary:1;
6575 };
6577 /* Parents of the commit being visualized. */
6578 static struct rev_graph graph_parents[4];
6580 /* The current stack of revisions on the graph. */
6581 static struct rev_graph graph_stacks[4] = {
6582         { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
6583         { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
6584         { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
6585         { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
6586 };
6588 static inline bool
6589 graph_parent_is_merge(struct rev_graph *graph)
6591         return graph->parents->size > 1;
6594 static inline void
6595 append_to_rev_graph(struct rev_graph *graph, chtype symbol)
6597         struct commit *commit = graph->commit;
6599         if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
6600                 commit->graph[commit->graph_size++] = symbol;
6603 static void
6604 clear_rev_graph(struct rev_graph *graph)
6606         graph->boundary = 0;
6607         graph->size = graph->pos = 0;
6608         graph->commit = NULL;
6609         memset(graph->parents, 0, sizeof(*graph->parents));
6612 static void
6613 done_rev_graph(struct rev_graph *graph)
6615         if (graph_parent_is_merge(graph) &&
6616             graph->pos < graph->size - 1 &&
6617             graph->next->size == graph->size + graph->parents->size - 1) {
6618                 size_t i = graph->pos + graph->parents->size - 1;
6620                 graph->commit->graph_size = i * 2;
6621                 while (i < graph->next->size - 1) {
6622                         append_to_rev_graph(graph, ' ');
6623                         append_to_rev_graph(graph, '\\');
6624                         i++;
6625                 }
6626         }
6628         clear_rev_graph(graph);
6631 static void
6632 push_rev_graph(struct rev_graph *graph, const char *parent)
6634         int i;
6636         /* "Collapse" duplicate parents lines.
6637          *
6638          * FIXME: This needs to also update update the drawn graph but
6639          * for now it just serves as a method for pruning graph lines. */
6640         for (i = 0; i < graph->size; i++)
6641                 if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
6642                         return;
6644         if (graph->size < SIZEOF_REVITEMS) {
6645                 string_copy_rev(graph->rev[graph->size++], parent);
6646         }
6649 static chtype
6650 get_rev_graph_symbol(struct rev_graph *graph)
6652         chtype symbol;
6654         if (graph->boundary)
6655                 symbol = REVGRAPH_BOUND;
6656         else if (graph->parents->size == 0)
6657                 symbol = REVGRAPH_INIT;
6658         else if (graph_parent_is_merge(graph))
6659                 symbol = REVGRAPH_MERGE;
6660         else if (graph->pos >= graph->size)
6661                 symbol = REVGRAPH_BRANCH;
6662         else
6663                 symbol = REVGRAPH_COMMIT;
6665         return symbol;
6668 static void
6669 draw_rev_graph(struct rev_graph *graph)
6671         struct rev_filler {
6672                 chtype separator, line;
6673         };
6674         enum { DEFAULT, RSHARP, RDIAG, LDIAG };
6675         static struct rev_filler fillers[] = {
6676                 { ' ',  '|' },
6677                 { '`',  '.' },
6678                 { '\'', ' ' },
6679                 { '/',  ' ' },
6680         };
6681         chtype symbol = get_rev_graph_symbol(graph);
6682         struct rev_filler *filler;
6683         size_t i;
6685         fillers[DEFAULT].line = opt_line_graphics ? ACS_VLINE : '|';
6686         filler = &fillers[DEFAULT];
6688         for (i = 0; i < graph->pos; i++) {
6689                 append_to_rev_graph(graph, filler->line);
6690                 if (graph_parent_is_merge(graph->prev) &&
6691                     graph->prev->pos == i)
6692                         filler = &fillers[RSHARP];
6694                 append_to_rev_graph(graph, filler->separator);
6695         }
6697         /* Place the symbol for this revision. */
6698         append_to_rev_graph(graph, symbol);
6700         if (graph->prev->size > graph->size)
6701                 filler = &fillers[RDIAG];
6702         else
6703                 filler = &fillers[DEFAULT];
6705         i++;
6707         for (; i < graph->size; i++) {
6708                 append_to_rev_graph(graph, filler->separator);
6709                 append_to_rev_graph(graph, filler->line);
6710                 if (graph_parent_is_merge(graph->prev) &&
6711                     i < graph->prev->pos + graph->parents->size)
6712                         filler = &fillers[RSHARP];
6713                 if (graph->prev->size > graph->size)
6714                         filler = &fillers[LDIAG];
6715         }
6717         if (graph->prev->size > graph->size) {
6718                 append_to_rev_graph(graph, filler->separator);
6719                 if (filler->line != ' ')
6720                         append_to_rev_graph(graph, filler->line);
6721         }
6724 /* Prepare the next rev graph */
6725 static void
6726 prepare_rev_graph(struct rev_graph *graph)
6728         size_t i;
6730         /* First, traverse all lines of revisions up to the active one. */
6731         for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
6732                 if (!strcmp(graph->rev[graph->pos], graph->commit->id))
6733                         break;
6735                 push_rev_graph(graph->next, graph->rev[graph->pos]);
6736         }
6738         /* Interleave the new revision parent(s). */
6739         for (i = 0; !graph->boundary && i < graph->parents->size; i++)
6740                 push_rev_graph(graph->next, graph->parents->rev[i]);
6742         /* Lastly, put any remaining revisions. */
6743         for (i = graph->pos + 1; i < graph->size; i++)
6744                 push_rev_graph(graph->next, graph->rev[i]);
6747 static void
6748 update_rev_graph(struct view *view, struct rev_graph *graph)
6750         /* If this is the finalizing update ... */
6751         if (graph->commit)
6752                 prepare_rev_graph(graph);
6754         /* Graph visualization needs a one rev look-ahead,
6755          * so the first update doesn't visualize anything. */
6756         if (!graph->prev->commit)
6757                 return;
6759         if (view->lines > 2)
6760                 view->line[view->lines - 3].dirty = 1;
6761         if (view->lines > 1)
6762                 view->line[view->lines - 2].dirty = 1;
6763         draw_rev_graph(graph->prev);
6764         done_rev_graph(graph->prev->prev);
6768 /*
6769  * Main view backend
6770  */
6772 static const char *main_argv[SIZEOF_ARG] = {
6773         "git", "log", "--no-color", "--pretty=raw", "--parents",
6774                 "--topo-order", "%(diffargs)", "%(revargs)",
6775                 "--", "%(fileargs)", NULL
6776 };
6778 static bool
6779 main_draw(struct view *view, struct line *line, unsigned int lineno)
6781         struct commit *commit = line->data;
6783         if (!commit->author)
6784                 return FALSE;
6786         if (opt_date && draw_date(view, &commit->time))
6787                 return TRUE;
6789         if (opt_author && draw_author(view, commit->author))
6790                 return TRUE;
6792         if (opt_rev_graph && commit->graph_size &&
6793             draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
6794                 return TRUE;
6796         if (opt_show_refs && commit->refs) {
6797                 size_t i;
6799                 for (i = 0; i < commit->refs->size; i++) {
6800                         struct ref *ref = commit->refs->refs[i];
6801                         enum line_type type;
6803                         if (ref->head)
6804                                 type = LINE_MAIN_HEAD;
6805                         else if (ref->ltag)
6806                                 type = LINE_MAIN_LOCAL_TAG;
6807                         else if (ref->tag)
6808                                 type = LINE_MAIN_TAG;
6809                         else if (ref->tracked)
6810                                 type = LINE_MAIN_TRACKED;
6811                         else if (ref->remote)
6812                                 type = LINE_MAIN_REMOTE;
6813                         else
6814                                 type = LINE_MAIN_REF;
6816                         if (draw_text(view, type, "[", TRUE) ||
6817                             draw_text(view, type, ref->name, TRUE) ||
6818                             draw_text(view, type, "]", TRUE))
6819                                 return TRUE;
6821                         if (draw_text(view, LINE_DEFAULT, " ", TRUE))
6822                                 return TRUE;
6823                 }
6824         }
6826         draw_text(view, LINE_DEFAULT, commit->title, TRUE);
6827         return TRUE;
6830 /* Reads git log --pretty=raw output and parses it into the commit struct. */
6831 static bool
6832 main_read(struct view *view, char *line)
6834         static struct rev_graph *graph = graph_stacks;
6835         enum line_type type;
6836         struct commit *commit;
6838         if (!line) {
6839                 int i;
6841                 if (!view->lines && !view->prev)
6842                         die("No revisions match the given arguments.");
6843                 if (view->lines > 0) {
6844                         commit = view->line[view->lines - 1].data;
6845                         view->line[view->lines - 1].dirty = 1;
6846                         if (!commit->author) {
6847                                 view->lines--;
6848                                 free(commit);
6849                                 graph->commit = NULL;
6850                         }
6851                 }
6852                 update_rev_graph(view, graph);
6854                 for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
6855                         clear_rev_graph(&graph_stacks[i]);
6856                 return TRUE;
6857         }
6859         type = get_line_type(line);
6860         if (type == LINE_COMMIT) {
6861                 commit = calloc(1, sizeof(struct commit));
6862                 if (!commit)
6863                         return FALSE;
6865                 line += STRING_SIZE("commit ");
6866                 if (*line == '-') {
6867                         graph->boundary = 1;
6868                         line++;
6869                 }
6871                 string_copy_rev(commit->id, line);
6872                 commit->refs = get_ref_list(commit->id);
6873                 graph->commit = commit;
6874                 add_line_data(view, commit, LINE_MAIN_COMMIT);
6876                 while ((line = strchr(line, ' '))) {
6877                         line++;
6878                         push_rev_graph(graph->parents, line);
6879                         commit->has_parents = TRUE;
6880                 }
6881                 return TRUE;
6882         }
6884         if (!view->lines)
6885                 return TRUE;
6886         commit = view->line[view->lines - 1].data;
6888         switch (type) {
6889         case LINE_PARENT:
6890                 if (commit->has_parents)
6891                         break;
6892                 push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
6893                 break;
6895         case LINE_AUTHOR:
6896                 parse_author_line(line + STRING_SIZE("author "),
6897                                   &commit->author, &commit->time);
6898                 update_rev_graph(view, graph);
6899                 graph = graph->next;
6900                 break;
6902         default:
6903                 /* Fill in the commit title if it has not already been set. */
6904                 if (commit->title[0])
6905                         break;
6907                 /* Require titles to start with a non-space character at the
6908                  * offset used by git log. */
6909                 if (strncmp(line, "    ", 4))
6910                         break;
6911                 line += 4;
6912                 /* Well, if the title starts with a whitespace character,
6913                  * try to be forgiving.  Otherwise we end up with no title. */
6914                 while (isspace(*line))
6915                         line++;
6916                 if (*line == '\0')
6917                         break;
6918                 /* FIXME: More graceful handling of titles; append "..." to
6919                  * shortened titles, etc. */
6921                 string_expand(commit->title, sizeof(commit->title), line, 1);
6922                 view->line[view->lines - 1].dirty = 1;
6923         }
6925         return TRUE;
6928 static enum request
6929 main_request(struct view *view, enum request request, struct line *line)
6931         enum open_flags flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
6933         switch (request) {
6934         case REQ_ENTER:
6935                 if (view_is_displayed(view) && display[0] != view)
6936                         maximize_view(view);
6937                 open_view(view, REQ_VIEW_DIFF, flags);
6938                 break;
6939         case REQ_REFRESH:
6940                 load_refs();
6941                 open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
6942                 break;
6943         default:
6944                 return request;
6945         }
6947         return REQ_NONE;
6950 static bool
6951 grep_refs(struct ref_list *list, regex_t *regex)
6953         regmatch_t pmatch;
6954         size_t i;
6956         if (!opt_show_refs || !list)
6957                 return FALSE;
6959         for (i = 0; i < list->size; i++) {
6960                 if (regexec(regex, list->refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
6961                         return TRUE;
6962         }
6964         return FALSE;
6967 static bool
6968 main_grep(struct view *view, struct line *line)
6970         struct commit *commit = line->data;
6971         const char *text[] = {
6972                 commit->title,
6973                 opt_author ? commit->author : "",
6974                 mkdate(&commit->time, opt_date),
6975                 NULL
6976         };
6978         return grep_text(view, text) || grep_refs(commit->refs, view->regex);
6981 static void
6982 main_select(struct view *view, struct line *line)
6984         struct commit *commit = line->data;
6986         string_copy_rev(view->ref, commit->id);
6987         string_copy_rev(ref_commit, view->ref);
6990 static struct view_ops main_ops = {
6991         "commit",
6992         main_argv,
6993         NULL,
6994         main_read,
6995         main_draw,
6996         main_request,
6997         main_grep,
6998         main_select,
6999 };
7002 /*
7003  * Status management
7004  */
7006 /* Whether or not the curses interface has been initialized. */
7007 static bool cursed = FALSE;
7009 /* Terminal hacks and workarounds. */
7010 static bool use_scroll_redrawwin;
7011 static bool use_scroll_status_wclear;
7013 /* The status window is used for polling keystrokes. */
7014 static WINDOW *status_win;
7016 /* Reading from the prompt? */
7017 static bool input_mode = FALSE;
7019 static bool status_empty = FALSE;
7021 /* Update status and title window. */
7022 static void
7023 report(const char *msg, ...)
7025         struct view *view = display[current_view];
7027         if (input_mode)
7028                 return;
7030         if (!view) {
7031                 char buf[SIZEOF_STR];
7032                 va_list args;
7034                 va_start(args, msg);
7035                 if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
7036                         buf[sizeof(buf) - 1] = 0;
7037                         buf[sizeof(buf) - 2] = '.';
7038                         buf[sizeof(buf) - 3] = '.';
7039                         buf[sizeof(buf) - 4] = '.';
7040                 }
7041                 va_end(args);
7042                 die("%s", buf);
7043         }
7045         if (!status_empty || *msg) {
7046                 va_list args;
7048                 va_start(args, msg);
7050                 wmove(status_win, 0, 0);
7051                 if (view->has_scrolled && use_scroll_status_wclear)
7052                         wclear(status_win);
7053                 if (*msg) {
7054                         vwprintw(status_win, msg, args);
7055                         status_empty = FALSE;
7056                 } else {
7057                         status_empty = TRUE;
7058                 }
7059                 wclrtoeol(status_win);
7060                 wnoutrefresh(status_win);
7062                 va_end(args);
7063         }
7065         update_view_title(view);
7068 static void
7069 init_display(void)
7071         const char *term;
7072         int x, y;
7074         /* Initialize the curses library */
7075         if (isatty(STDIN_FILENO)) {
7076                 cursed = !!initscr();
7077                 opt_tty = stdin;
7078         } else {
7079                 /* Leave stdin and stdout alone when acting as a pager. */
7080                 opt_tty = fopen("/dev/tty", "r+");
7081                 if (!opt_tty)
7082                         die("Failed to open /dev/tty");
7083                 cursed = !!newterm(NULL, opt_tty, opt_tty);
7084         }
7086         if (!cursed)
7087                 die("Failed to initialize curses");
7089         nonl();         /* Disable conversion and detect newlines from input. */
7090         cbreak();       /* Take input chars one at a time, no wait for \n */
7091         noecho();       /* Don't echo input */
7092         leaveok(stdscr, FALSE);
7094         if (has_colors())
7095                 init_colors();
7097         getmaxyx(stdscr, y, x);
7098         status_win = newwin(1, 0, y - 1, 0);
7099         if (!status_win)
7100                 die("Failed to create status window");
7102         /* Enable keyboard mapping */
7103         keypad(status_win, TRUE);
7104         wbkgdset(status_win, get_line_attr(LINE_STATUS));
7106 #if defined(NCURSES_VERSION_PATCH) && (NCURSES_VERSION_PATCH >= 20080119)
7107         set_tabsize(opt_tab_size);
7108 #else
7109         TABSIZE = opt_tab_size;
7110 #endif
7112         term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
7113         if (term && !strcmp(term, "gnome-terminal")) {
7114                 /* In the gnome-terminal-emulator, the message from
7115                  * scrolling up one line when impossible followed by
7116                  * scrolling down one line causes corruption of the
7117                  * status line. This is fixed by calling wclear. */
7118                 use_scroll_status_wclear = TRUE;
7119                 use_scroll_redrawwin = FALSE;
7121         } else if (term && !strcmp(term, "xrvt-xpm")) {
7122                 /* No problems with full optimizations in xrvt-(unicode)
7123                  * and aterm. */
7124                 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
7126         } else {
7127                 /* When scrolling in (u)xterm the last line in the
7128                  * scrolling direction will update slowly. */
7129                 use_scroll_redrawwin = TRUE;
7130                 use_scroll_status_wclear = FALSE;
7131         }
7134 static int
7135 get_input(int prompt_position)
7137         struct view *view;
7138         int i, key, cursor_y, cursor_x;
7140         if (prompt_position)
7141                 input_mode = TRUE;
7143         while (TRUE) {
7144                 bool loading = FALSE;
7146                 foreach_view (view, i) {
7147                         update_view(view);
7148                         if (view_is_displayed(view) && view->has_scrolled &&
7149                             use_scroll_redrawwin)
7150                                 redrawwin(view->win);
7151                         view->has_scrolled = FALSE;
7152                         if (view->pipe)
7153                                 loading = TRUE;
7154                 }
7156                 /* Update the cursor position. */
7157                 if (prompt_position) {
7158                         getbegyx(status_win, cursor_y, cursor_x);
7159                         cursor_x = prompt_position;
7160                 } else {
7161                         view = display[current_view];
7162                         getbegyx(view->win, cursor_y, cursor_x);
7163                         cursor_x = view->width - 1;
7164                         cursor_y += view->lineno - view->offset;
7165                 }
7166                 setsyx(cursor_y, cursor_x);
7168                 /* Refresh, accept single keystroke of input */
7169                 doupdate();
7170                 nodelay(status_win, loading);
7171                 key = wgetch(status_win);
7173                 /* wgetch() with nodelay() enabled returns ERR when
7174                  * there's no input. */
7175                 if (key == ERR) {
7177                 } else if (key == KEY_RESIZE) {
7178                         int height, width;
7180                         getmaxyx(stdscr, height, width);
7182                         wresize(status_win, 1, width);
7183                         mvwin(status_win, height - 1, 0);
7184                         wnoutrefresh(status_win);
7185                         resize_display();
7186                         redraw_display(TRUE);
7188                 } else {
7189                         input_mode = FALSE;
7190                         return key;
7191                 }
7192         }
7195 static char *
7196 prompt_input(const char *prompt, input_handler handler, void *data)
7198         enum input_status status = INPUT_OK;
7199         static char buf[SIZEOF_STR];
7200         size_t pos = 0;
7202         buf[pos] = 0;
7204         while (status == INPUT_OK || status == INPUT_SKIP) {
7205                 int key;
7207                 mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
7208                 wclrtoeol(status_win);
7210                 key = get_input(pos + 1);
7211                 switch (key) {
7212                 case KEY_RETURN:
7213                 case KEY_ENTER:
7214                 case '\n':
7215                         status = pos ? INPUT_STOP : INPUT_CANCEL;
7216                         break;
7218                 case KEY_BACKSPACE:
7219                         if (pos > 0)
7220                                 buf[--pos] = 0;
7221                         else
7222                                 status = INPUT_CANCEL;
7223                         break;
7225                 case KEY_ESC:
7226                         status = INPUT_CANCEL;
7227                         break;
7229                 default:
7230                         if (pos >= sizeof(buf)) {
7231                                 report("Input string too long");
7232                                 return NULL;
7233                         }
7235                         status = handler(data, buf, key);
7236                         if (status == INPUT_OK)
7237                                 buf[pos++] = (char) key;
7238                 }
7239         }
7241         /* Clear the status window */
7242         status_empty = FALSE;
7243         report("");
7245         if (status == INPUT_CANCEL)
7246                 return NULL;
7248         buf[pos++] = 0;
7250         return buf;
7253 static enum input_status
7254 prompt_yesno_handler(void *data, char *buf, int c)
7256         if (c == 'y' || c == 'Y')
7257                 return INPUT_STOP;
7258         if (c == 'n' || c == 'N')
7259                 return INPUT_CANCEL;
7260         return INPUT_SKIP;
7263 static bool
7264 prompt_yesno(const char *prompt)
7266         char prompt2[SIZEOF_STR];
7268         if (!string_format(prompt2, "%s [Yy/Nn]", prompt))
7269                 return FALSE;
7271         return !!prompt_input(prompt2, prompt_yesno_handler, NULL);
7274 static enum input_status
7275 read_prompt_handler(void *data, char *buf, int c)
7277         return isprint(c) ? INPUT_OK : INPUT_SKIP;
7280 static char *
7281 read_prompt(const char *prompt)
7283         return prompt_input(prompt, read_prompt_handler, NULL);
7286 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected)
7288         enum input_status status = INPUT_OK;
7289         int size = 0;
7291         while (items[size].text)
7292                 size++;
7294         while (status == INPUT_OK) {
7295                 const struct menu_item *item = &items[*selected];
7296                 int key;
7297                 int i;
7299                 mvwprintw(status_win, 0, 0, "%s (%d of %d) ",
7300                           prompt, *selected + 1, size);
7301                 if (item->hotkey)
7302                         wprintw(status_win, "[%c] ", (char) item->hotkey);
7303                 wprintw(status_win, "%s", item->text);
7304                 wclrtoeol(status_win);
7306                 key = get_input(COLS - 1);
7307                 switch (key) {
7308                 case KEY_RETURN:
7309                 case KEY_ENTER:
7310                 case '\n':
7311                         status = INPUT_STOP;
7312                         break;
7314                 case KEY_LEFT:
7315                 case KEY_UP:
7316                         *selected = *selected - 1;
7317                         if (*selected < 0)
7318                                 *selected = size - 1;
7319                         break;
7321                 case KEY_RIGHT:
7322                 case KEY_DOWN:
7323                         *selected = (*selected + 1) % size;
7324                         break;
7326                 case KEY_ESC:
7327                         status = INPUT_CANCEL;
7328                         break;
7330                 default:
7331                         for (i = 0; items[i].text; i++)
7332                                 if (items[i].hotkey == key) {
7333                                         *selected = i;
7334                                         status = INPUT_STOP;
7335                                         break;
7336                                 }
7337                 }
7338         }
7340         /* Clear the status window */
7341         status_empty = FALSE;
7342         report("");
7344         return status != INPUT_CANCEL;
7347 /*
7348  * Repository properties
7349  */
7351 static struct ref **refs = NULL;
7352 static size_t refs_size = 0;
7353 static struct ref *refs_head = NULL;
7355 static struct ref_list **ref_lists = NULL;
7356 static size_t ref_lists_size = 0;
7358 DEFINE_ALLOCATOR(realloc_refs, struct ref *, 256)
7359 DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
7360 DEFINE_ALLOCATOR(realloc_ref_lists, struct ref_list *, 8)
7362 static int
7363 compare_refs(const void *ref1_, const void *ref2_)
7365         const struct ref *ref1 = *(const struct ref **)ref1_;
7366         const struct ref *ref2 = *(const struct ref **)ref2_;
7368         if (ref1->tag != ref2->tag)
7369                 return ref2->tag - ref1->tag;
7370         if (ref1->ltag != ref2->ltag)
7371                 return ref2->ltag - ref2->ltag;
7372         if (ref1->head != ref2->head)
7373                 return ref2->head - ref1->head;
7374         if (ref1->tracked != ref2->tracked)
7375                 return ref2->tracked - ref1->tracked;
7376         if (ref1->remote != ref2->remote)
7377                 return ref2->remote - ref1->remote;
7378         return strcmp(ref1->name, ref2->name);
7381 static void
7382 foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data)
7384         size_t i;
7386         for (i = 0; i < refs_size; i++)
7387                 if (!visitor(data, refs[i]))
7388                         break;
7391 static struct ref *
7392 get_ref_head()
7394         return refs_head;
7397 static struct ref_list *
7398 get_ref_list(const char *id)
7400         struct ref_list *list;
7401         size_t i;
7403         for (i = 0; i < ref_lists_size; i++)
7404                 if (!strcmp(id, ref_lists[i]->id))
7405                         return ref_lists[i];
7407         if (!realloc_ref_lists(&ref_lists, ref_lists_size, 1))
7408                 return NULL;
7409         list = calloc(1, sizeof(*list));
7410         if (!list)
7411                 return NULL;
7413         for (i = 0; i < refs_size; i++) {
7414                 if (!strcmp(id, refs[i]->id) &&
7415                     realloc_refs_list(&list->refs, list->size, 1))
7416                         list->refs[list->size++] = refs[i];
7417         }
7419         if (!list->refs) {
7420                 free(list);
7421                 return NULL;
7422         }
7424         qsort(list->refs, list->size, sizeof(*list->refs), compare_refs);
7425         ref_lists[ref_lists_size++] = list;
7426         return list;
7429 static int
7430 read_ref(char *id, size_t idlen, char *name, size_t namelen, void *data)
7432         struct ref *ref = NULL;
7433         bool tag = FALSE;
7434         bool ltag = FALSE;
7435         bool remote = FALSE;
7436         bool tracked = FALSE;
7437         bool head = FALSE;
7438         int from = 0, to = refs_size - 1;
7440         if (!prefixcmp(name, "refs/tags/")) {
7441                 if (!suffixcmp(name, namelen, "^{}")) {
7442                         namelen -= 3;
7443                         name[namelen] = 0;
7444                 } else {
7445                         ltag = TRUE;
7446                 }
7448                 tag = TRUE;
7449                 namelen -= STRING_SIZE("refs/tags/");
7450                 name    += STRING_SIZE("refs/tags/");
7452         } else if (!prefixcmp(name, "refs/remotes/")) {
7453                 remote = TRUE;
7454                 namelen -= STRING_SIZE("refs/remotes/");
7455                 name    += STRING_SIZE("refs/remotes/");
7456                 tracked  = !strcmp(opt_remote, name);
7458         } else if (!prefixcmp(name, "refs/heads/")) {
7459                 namelen -= STRING_SIZE("refs/heads/");
7460                 name    += STRING_SIZE("refs/heads/");
7461                 if (!strncmp(opt_head, name, namelen))
7462                         return OK;
7464         } else if (!strcmp(name, "HEAD")) {
7465                 head     = TRUE;
7466                 if (*opt_head) {
7467                         namelen  = strlen(opt_head);
7468                         name     = opt_head;
7469                 }
7470         }
7472         /* If we are reloading or it's an annotated tag, replace the
7473          * previous SHA1 with the resolved commit id; relies on the fact
7474          * git-ls-remote lists the commit id of an annotated tag right
7475          * before the commit id it points to. */
7476         while (from <= to) {
7477                 size_t pos = (to + from) / 2;
7478                 int cmp = strcmp(name, refs[pos]->name);
7480                 if (!cmp) {
7481                         ref = refs[pos];
7482                         break;
7483                 }
7485                 if (cmp < 0)
7486                         to = pos - 1;
7487                 else
7488                         from = pos + 1;
7489         }
7491         if (!ref) {
7492                 if (!realloc_refs(&refs, refs_size, 1))
7493                         return ERR;
7494                 ref = calloc(1, sizeof(*ref) + namelen);
7495                 if (!ref)
7496                         return ERR;
7497                 memmove(refs + from + 1, refs + from,
7498                         (refs_size - from) * sizeof(*refs));
7499                 refs[from] = ref;
7500                 strncpy(ref->name, name, namelen);
7501                 refs_size++;
7502         }
7504         ref->head = head;
7505         ref->tag = tag;
7506         ref->ltag = ltag;
7507         ref->remote = remote;
7508         ref->tracked = tracked;
7509         string_copy_rev(ref->id, id);
7511         if (head)
7512                 refs_head = ref;
7513         return OK;
7516 static int
7517 load_refs(void)
7519         const char *head_argv[] = {
7520                 "git", "symbolic-ref", "HEAD", NULL
7521         };
7522         static const char *ls_remote_argv[SIZEOF_ARG] = {
7523                 "git", "ls-remote", opt_git_dir, NULL
7524         };
7525         static bool init = FALSE;
7526         size_t i;
7528         if (!init) {
7529                 if (!argv_from_env(ls_remote_argv, "TIG_LS_REMOTE"))
7530                         die("TIG_LS_REMOTE contains too many arguments");
7531                 init = TRUE;
7532         }
7534         if (!*opt_git_dir)
7535                 return OK;
7537         if (io_run_buf(head_argv, opt_head, sizeof(opt_head)) &&
7538             !prefixcmp(opt_head, "refs/heads/")) {
7539                 char *offset = opt_head + STRING_SIZE("refs/heads/");
7541                 memmove(opt_head, offset, strlen(offset) + 1);
7542         }
7544         refs_head = NULL;
7545         for (i = 0; i < refs_size; i++)
7546                 refs[i]->id[0] = 0;
7548         if (io_run_load(ls_remote_argv, "\t", read_ref, NULL) == ERR)
7549                 return ERR;
7551         /* Update the ref lists to reflect changes. */
7552         for (i = 0; i < ref_lists_size; i++) {
7553                 struct ref_list *list = ref_lists[i];
7554                 size_t old, new;
7556                 for (old = new = 0; old < list->size; old++)
7557                         if (!strcmp(list->id, list->refs[old]->id))
7558                                 list->refs[new++] = list->refs[old];
7559                 list->size = new;
7560         }
7562         return OK;
7565 static void
7566 set_remote_branch(const char *name, const char *value, size_t valuelen)
7568         if (!strcmp(name, ".remote")) {
7569                 string_ncopy(opt_remote, value, valuelen);
7571         } else if (*opt_remote && !strcmp(name, ".merge")) {
7572                 size_t from = strlen(opt_remote);
7574                 if (!prefixcmp(value, "refs/heads/"))
7575                         value += STRING_SIZE("refs/heads/");
7577                 if (!string_format_from(opt_remote, &from, "/%s", value))
7578                         opt_remote[0] = 0;
7579         }
7582 static void
7583 set_repo_config_option(char *name, char *value, enum option_code (*cmd)(int, const char **))
7585         const char *argv[SIZEOF_ARG] = { name, "=" };
7586         int argc = 1 + (cmd == option_set_command);
7587         enum option_code error;
7589         if (!argv_from_string(argv, &argc, value))
7590                 error = OPT_ERR_TOO_MANY_OPTION_ARGUMENTS;
7591         else
7592                 error = cmd(argc, argv);
7594         if (error != OPT_OK)
7595                 warn("Option 'tig.%s': %s", name, option_errors[error]);
7598 static bool
7599 set_environment_variable(const char *name, const char *value)
7601         size_t len = strlen(name) + 1 + strlen(value) + 1;
7602         char *env = malloc(len);
7604         if (env &&
7605             string_nformat(env, len, NULL, "%s=%s", name, value) &&
7606             putenv(env) == 0)
7607                 return TRUE;
7608         free(env);
7609         return FALSE;
7612 static void
7613 set_work_tree(const char *value)
7615         char cwd[SIZEOF_STR];
7617         if (!getcwd(cwd, sizeof(cwd)))
7618                 die("Failed to get cwd path: %s", strerror(errno));
7619         if (chdir(opt_git_dir) < 0)
7620                 die("Failed to chdir(%s): %s", strerror(errno));
7621         if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
7622                 die("Failed to get git path: %s", strerror(errno));
7623         if (chdir(cwd) < 0)
7624                 die("Failed to chdir(%s): %s", cwd, strerror(errno));
7625         if (chdir(value) < 0)
7626                 die("Failed to chdir(%s): %s", value, strerror(errno));
7627         if (!getcwd(cwd, sizeof(cwd)))
7628                 die("Failed to get cwd path: %s", strerror(errno));
7629         if (!set_environment_variable("GIT_WORK_TREE", cwd))
7630                 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
7631         if (!set_environment_variable("GIT_DIR", opt_git_dir))
7632                 die("Failed to set GIT_DIR to '%s'", opt_git_dir);
7633         opt_is_inside_work_tree = TRUE;
7636 static int
7637 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen, void *data)
7639         if (!strcmp(name, "i18n.commitencoding"))
7640                 string_ncopy(opt_encoding, value, valuelen);
7642         else if (!strcmp(name, "core.editor"))
7643                 string_ncopy(opt_editor, value, valuelen);
7645         else if (!strcmp(name, "core.worktree"))
7646                 set_work_tree(value);
7648         else if (!prefixcmp(name, "tig.color."))
7649                 set_repo_config_option(name + 10, value, option_color_command);
7651         else if (!prefixcmp(name, "tig.bind."))
7652                 set_repo_config_option(name + 9, value, option_bind_command);
7654         else if (!prefixcmp(name, "tig."))
7655                 set_repo_config_option(name + 4, value, option_set_command);
7657         else if (*opt_head && !prefixcmp(name, "branch.") &&
7658                  !strncmp(name + 7, opt_head, strlen(opt_head)))
7659                 set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
7661         return OK;
7664 static int
7665 load_git_config(void)
7667         const char *config_list_argv[] = { "git", "config", "--list", NULL };
7669         return io_run_load(config_list_argv, "=", read_repo_config_option, NULL);
7672 static int
7673 read_repo_info(char *name, size_t namelen, char *value, size_t valuelen, void *data)
7675         if (!opt_git_dir[0]) {
7676                 string_ncopy(opt_git_dir, name, namelen);
7678         } else if (opt_is_inside_work_tree == -1) {
7679                 /* This can be 3 different values depending on the
7680                  * version of git being used. If git-rev-parse does not
7681                  * understand --is-inside-work-tree it will simply echo
7682                  * the option else either "true" or "false" is printed.
7683                  * Default to true for the unknown case. */
7684                 opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
7686         } else if (*name == '.') {
7687                 string_ncopy(opt_cdup, name, namelen);
7689         } else {
7690                 string_ncopy(opt_prefix, name, namelen);
7691         }
7693         return OK;
7696 static int
7697 load_repo_info(void)
7699         const char *rev_parse_argv[] = {
7700                 "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
7701                         "--show-cdup", "--show-prefix", NULL
7702         };
7704         return io_run_load(rev_parse_argv, "=", read_repo_info, NULL);
7708 /*
7709  * Main
7710  */
7712 static const char usage[] =
7713 "tig " TIG_VERSION " (" __DATE__ ")\n"
7714 "\n"
7715 "Usage: tig        [options] [revs] [--] [paths]\n"
7716 "   or: tig show   [options] [revs] [--] [paths]\n"
7717 "   or: tig blame  [rev] path\n"
7718 "   or: tig status\n"
7719 "   or: tig <      [git command output]\n"
7720 "\n"
7721 "Options:\n"
7722 "  -v, --version   Show version and exit\n"
7723 "  -h, --help      Show help message and exit";
7725 static void __NORETURN
7726 quit(int sig)
7728         /* XXX: Restore tty modes and let the OS cleanup the rest! */
7729         if (cursed)
7730                 endwin();
7731         exit(0);
7734 static void __NORETURN
7735 die(const char *err, ...)
7737         va_list args;
7739         endwin();
7741         va_start(args, err);
7742         fputs("tig: ", stderr);
7743         vfprintf(stderr, err, args);
7744         fputs("\n", stderr);
7745         va_end(args);
7747         exit(1);
7750 static void
7751 warn(const char *msg, ...)
7753         va_list args;
7755         va_start(args, msg);
7756         fputs("tig warning: ", stderr);
7757         vfprintf(stderr, msg, args);
7758         fputs("\n", stderr);
7759         va_end(args);
7762 static int
7763 read_filter_args(char *name, size_t namelen, char *value, size_t valuelen, void *data)
7765         const char ***filter_args = data;
7767         return argv_append(filter_args, name) ? OK : ERR;
7770 static void
7771 filter_rev_parse(const char ***args, const char *arg1, const char *arg2, const char *argv[])
7773         const char *rev_parse_argv[SIZEOF_ARG] = { "git", "rev-parse", arg1, arg2 };
7774         const char **all_argv = NULL;
7776         if (!argv_append_array(&all_argv, rev_parse_argv) ||
7777             !argv_append_array(&all_argv, argv) ||
7778             !io_run_load(all_argv, "\n", read_filter_args, args) == ERR)
7779                 die("Failed to split arguments");
7780         argv_free(all_argv);
7781         free(all_argv);
7784 static void
7785 filter_options(const char *argv[])
7787         filter_rev_parse(&opt_file_argv, "--no-revs", "--no-flags", argv);
7788         filter_rev_parse(&opt_diff_argv, "--no-revs", "--flags", argv);
7789         filter_rev_parse(&opt_rev_argv, "--symbolic", "--revs-only", argv);
7792 static enum request
7793 parse_options(int argc, const char *argv[])
7795         enum request request = REQ_VIEW_MAIN;
7796         const char *subcommand;
7797         bool seen_dashdash = FALSE;
7798         const char **filter_argv = NULL;
7799         int i;
7801         if (!isatty(STDIN_FILENO))
7802                 return REQ_VIEW_PAGER;
7804         if (argc <= 1)
7805                 return REQ_VIEW_MAIN;
7807         subcommand = argv[1];
7808         if (!strcmp(subcommand, "status")) {
7809                 if (argc > 2)
7810                         warn("ignoring arguments after `%s'", subcommand);
7811                 return REQ_VIEW_STATUS;
7813         } else if (!strcmp(subcommand, "blame")) {
7814                 if (argc <= 2 || argc > 4)
7815                         die("invalid number of options to blame\n\n%s", usage);
7817                 i = 2;
7818                 if (argc == 4) {
7819                         string_ncopy(opt_ref, argv[i], strlen(argv[i]));
7820                         i++;
7821                 }
7823                 string_ncopy(opt_file, argv[i], strlen(argv[i]));
7824                 return REQ_VIEW_BLAME;
7826         } else if (!strcmp(subcommand, "show")) {
7827                 request = REQ_VIEW_DIFF;
7829         } else {
7830                 subcommand = NULL;
7831         }
7833         for (i = 1 + !!subcommand; i < argc; i++) {
7834                 const char *opt = argv[i];
7836                 if (seen_dashdash) {
7837                         argv_append(&opt_file_argv, opt);
7838                         continue;
7840                 } else if (!strcmp(opt, "--")) {
7841                         seen_dashdash = TRUE;
7842                         continue;
7844                 } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
7845                         printf("tig version %s\n", TIG_VERSION);
7846                         quit(0);
7848                 } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
7849                         printf("%s\n", usage);
7850                         quit(0);
7852                 } else if (!strcmp(opt, "--all")) {
7853                         argv_append(&opt_rev_argv, opt);
7854                         continue;
7855                 }
7857                 if (!argv_append(&filter_argv, opt))
7858                         die("command too long");
7859         }
7861         if (filter_argv)
7862                 filter_options(filter_argv);
7864         return request;
7867 int
7868 main(int argc, const char *argv[])
7870         const char *codeset = "UTF-8";
7871         enum request request = parse_options(argc, argv);
7872         struct view *view;
7873         size_t i;
7875         signal(SIGINT, quit);
7876         signal(SIGPIPE, SIG_IGN);
7878         if (setlocale(LC_ALL, "")) {
7879                 codeset = nl_langinfo(CODESET);
7880         }
7882         if (load_repo_info() == ERR)
7883                 die("Failed to load repo info.");
7885         if (load_options() == ERR)
7886                 die("Failed to load user config.");
7888         if (load_git_config() == ERR)
7889                 die("Failed to load repo config.");
7891         /* Require a git repository unless when running in pager mode. */
7892         if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
7893                 die("Not a git repository");
7895         if (*opt_encoding && strcmp(codeset, "UTF-8")) {
7896                 opt_iconv_in = iconv_open("UTF-8", opt_encoding);
7897                 if (opt_iconv_in == ICONV_NONE)
7898                         die("Failed to initialize character set conversion");
7899         }
7901         if (codeset && strcmp(codeset, "UTF-8")) {
7902                 opt_iconv_out = iconv_open(codeset, "UTF-8");
7903                 if (opt_iconv_out == ICONV_NONE)
7904                         die("Failed to initialize character set conversion");
7905         }
7907         if (load_refs() == ERR)
7908                 die("Failed to load refs.");
7910         foreach_view (view, i) {
7911                 if (getenv(view->cmd_env))
7912                         warn("Use of the %s environment variable is deprecated,"
7913                              " use options or TIG_DIFF_ARGS instead",
7914                              view->cmd_env);
7915                 if (!argv_from_env(view->ops->argv, view->cmd_env))
7916                         die("Too many arguments in the `%s` environment variable",
7917                             view->cmd_env);
7918         }
7920         init_display();
7922         while (view_driver(display[current_view], request)) {
7923                 int key = get_input(0);
7925                 view = display[current_view];
7926                 request = get_keybinding(view->keymap, key);
7928                 /* Some low-level request handling. This keeps access to
7929                  * status_win restricted. */
7930                 switch (request) {
7931                 case REQ_NONE:
7932                         report("Unknown key, press %s for help",
7933                                get_key(view->keymap, REQ_VIEW_HELP));
7934                         break;
7935                 case REQ_PROMPT:
7936                 {
7937                         char *cmd = read_prompt(":");
7939                         if (cmd && isdigit(*cmd)) {
7940                                 int lineno = view->lineno + 1;
7942                                 if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
7943                                         select_view_line(view, lineno - 1);
7944                                         report("");
7945                                 } else {
7946                                         report("Unable to parse '%s' as a line number", cmd);
7947                                 }
7949                         } else if (cmd) {
7950                                 struct view *next = VIEW(REQ_VIEW_PAGER);
7951                                 const char *argv[SIZEOF_ARG] = { "git" };
7952                                 int argc = 1;
7954                                 /* When running random commands, initially show the
7955                                  * command in the title. However, it maybe later be
7956                                  * overwritten if a commit line is selected. */
7957                                 string_ncopy(next->ref, cmd, strlen(cmd));
7959                                 if (!argv_from_string(argv, &argc, cmd)) {
7960                                         report("Too many arguments");
7961                                 } else if (!prepare_update(next, argv, NULL)) {
7962                                         report("Failed to format command");
7963                                 } else {
7964                                         open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
7965                                 }
7966                         }
7968                         request = REQ_NONE;
7969                         break;
7970                 }
7971                 case REQ_SEARCH:
7972                 case REQ_SEARCH_BACK:
7973                 {
7974                         const char *prompt = request == REQ_SEARCH ? "/" : "?";
7975                         char *search = read_prompt(prompt);
7977                         if (search)
7978                                 string_ncopy(opt_search, search, strlen(search));
7979                         else if (*opt_search)
7980                                 request = request == REQ_SEARCH ?
7981                                         REQ_FIND_NEXT :
7982                                         REQ_FIND_PREV;
7983                         else
7984                                 request = REQ_NONE;
7985                         break;
7986                 }
7987                 default:
7988                         break;
7989                 }
7990         }
7992         quit(0);
7994         return 0;